refactor: normalise data (#756)

* chore: remove legal from bundle

* refactor: create normalised dataset

* refactor: cuesheet uses flat rundown

* refactor: multi-selection

* refactor: prevent stale data on server restart

* refactor: increase ID size

* chore: instrument operation

* chore: update csv tests

* fix: resolve directory to test-db (#758)
This commit is contained in:
Carlos Valente
2024-02-03 21:43:17 +01:00
committed by GitHub
parent 47a519bac1
commit 1890fc49d7
62 changed files with 1838 additions and 3341 deletions
@@ -1,12 +1,11 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useCallback, useEffect, useMemo } from 'react';
import { OntimeRundownEntry, ProjectData } from 'ontime-types';
import Empty from '../../common/components/state/Empty';
import { useEventAction } from '../../common/hooks/useEventAction';
import { useCuesheet } from '../../common/hooks/useSocket';
import useRundown from '../../common/hooks-query/useRundown';
import { useFlatRundown } from '../../common/hooks-query/useRundown';
import useUserFields from '../../common/hooks-query/useUserFields';
import ExportModal, { ExportType } from '../modals/export-modal/ExportModal';
import CuesheetProgress from './cuesheet-progress/CuesheetProgress';
import CuesheetTableHeader from './cuesheet-table-header/CuesheetTableHeader';
@@ -17,13 +16,12 @@ import { makeCSV, makeTable } from './cuesheetUtils';
import styles from './CuesheetWrapper.module.scss';
export default function CuesheetWrapper() {
const { data: rundown } = useRundown();
// TODO: can we use the normalised rundown for the table?
const { data: flatRundown, status: rundownStatus } = useFlatRundown();
const { data: userFields } = useUserFields();
const { updateEvent } = useEventAction();
const featureData = useCuesheet();
const columns = useMemo(() => makeCuesheetColumns(userFields), [userFields]);
const [isModalOpen, setIsModalOpen] = useState(false);
const [headerData, setheaderData] = useState<ProjectData | null>(null);
// Set window title
useEffect(() => {
@@ -32,7 +30,7 @@ export default function CuesheetWrapper() {
const handleUpdate = useCallback(
async (rowIndex: number, accessor: keyof OntimeRundownEntry, payload: unknown) => {
if (!rundown) {
if (!flatRundown || rundownStatus !== 'success') {
return;
}
@@ -41,7 +39,7 @@ export default function CuesheetWrapper() {
}
// check if value is the same
const event = rundown[rowIndex];
const event = flatRundown[rowIndex];
if (!event) {
return;
}
@@ -69,41 +67,21 @@ export default function CuesheetWrapper() {
console.error(error);
}
},
[updateEvent, rundown],
[flatRundown, rundownStatus, updateEvent],
);
const exportHandler = useCallback(
(headerData: ProjectData, exportType: ExportType) => {
if (!headerData || !rundown || !userFields) {
(headerData: ProjectData) => {
if (!userFields || !flatRundown || rundownStatus !== 'success') {
return;
}
const sheetData = makeTable(headerData, flatRundown, userFields);
const csvContent = makeCSV(sheetData);
let fileName = '';
let url = '';
const fileName = 'ontime rundown.csv';
if (exportType === 'json') {
const jsonContent = JSON.stringify({
headerData,
rundown,
userFields,
});
fileName = 'ontime export.json';
const blob = new Blob([jsonContent], { type: 'application/json;charset=utf-8;' });
url = URL.createObjectURL(blob);
} else if (exportType === 'csv') {
const sheetData = makeTable(headerData, rundown, userFields);
const csvContent = makeCSV(sheetData);
fileName = 'ontime export.csv';
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
url = URL.createObjectURL(blob);
} else {
console.error('Invalid export type: ', exportType);
return;
}
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.setAttribute('href', url);
@@ -114,36 +92,23 @@ export default function CuesheetWrapper() {
URL.revokeObjectURL(url);
return;
},
[rundown, userFields],
[flatRundown, rundownStatus, userFields],
);
const onModalClose = (exportType?: ExportType) => {
setIsModalOpen(false);
if (!exportType) {
return;
}
if (headerData) {
exportHandler(headerData, exportType);
}
};
const handleOpenModal = (projectData: ProjectData) => {
setheaderData(projectData);
setIsModalOpen(true);
};
if (!rundown || !userFields) {
if (!userFields || !flatRundown || rundownStatus !== 'success') {
return <Empty text='Loading...' />;
}
return (
<div className={styles.tableWrapper} data-testid='cuesheet'>
<CuesheetTableHeader handleExport={handleOpenModal} featureData={featureData} />
<CuesheetTableHeader handleExport={exportHandler} featureData={featureData} />
<CuesheetProgress />
<Cuesheet data={rundown} columns={columns} handleUpdate={handleUpdate} selectedId={featureData.selectedEventId} />
<ExportModal isOpen={isModalOpen} onClose={onModalClose} />
<Cuesheet
data={flatRundown}
columns={columns}
handleUpdate={handleUpdate}
selectedId={featureData.selectedEventId}
/>
</div>
);
}
@@ -3,25 +3,14 @@
exports[`makeTable() > returns array of arrays with given fields 1`] = `
[
[
"Ontime · Schedule Template",
"Ontime · Rundown export",
],
[
"Project Title",
"",
"Project title: test title",
],
[
"Project Description",
"",
"Project description: test description",
],
[
"Public URL",
"",
],
[
"Backstage URL",
"",
],
[],
[
"Time Start",
"Time End",
@@ -59,7 +59,10 @@ describe('parseField()', () => {
describe('makeTable()', () => {
it('returns array of arrays with given fields', () => {
const headerData = {};
const headerData = {
title: 'test title',
description: 'test description',
};
const tableData = [
{
title: 'test title 1',
@@ -9,7 +9,7 @@ $active-colour: $gray-500;
}
@mixin time {
font-family: "Open Sans Light", $ontime-font-family;
font-family: 'Open Sans Light', $ontime-font-family;
font-size: 2rem;
text-align: center;
}
@@ -22,8 +22,7 @@ $active-colour: $gray-500;
height: max-content;
column-gap: 2rem;
grid-template-areas:
'event playback timer clock actions';
grid-template-areas: 'event playback timer clock actions';
grid-template-columns: 1fr auto auto auto auto;
align-items: center;
justify-items: center;
@@ -91,11 +90,12 @@ $active-colour: $gray-500;
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 1.125rem;
color: $label-colour;
height: 100%;
font-size: 1rem;
.actionIcon {
.actionIcon,
.actionText {
cursor: pointer;
&.enabled {
@@ -106,6 +106,10 @@ $active-colour: $gray-500;
color: $active-colour;
}
}
.actionIcon {
font-size: 1.25rem;
}
}
@media (min-width: 1200px) {
@@ -8,6 +8,7 @@ import { Playback, ProjectData } from 'ontime-types';
import PlaybackIcon from '../../../common/components/playback-icon/PlaybackIcon';
import useFullscreen from '../../../common/hooks/useFullscreen';
import useProjectData from '../../../common/hooks-query/useProjectData';
import { cx } from '../../../common/utils/styleUtils';
import { tooltipDelayFast } from '../../../ontimeConfig';
import { useCuesheetSettings } from '../store/CuesheetSettings';
@@ -58,12 +59,18 @@ export default function CuesheetTableHeader({ handleExport, featureData }: Cuesh
<CuesheetTableHeaderTimers />
<div className={style.headerActions}>
<Tooltip openDelay={tooltipDelayFast} label='Toggle follow'>
<span onClick={() => toggleFollow()} className={`${style.actionIcon} ${followSelected ? style.enabled : ''}`}>
<span
onClick={() => toggleFollow()}
className={cx([style.actionIcon, followSelected ? style.enabled : null])}
>
<IoLocate />
</span>
</Tooltip>
<Tooltip openDelay={tooltipDelayFast} label='Toggle settings'>
<span onClick={() => toggleSettings()} className={`${style.actionIcon} ${showSettings ? style.enabled : ''}`}>
<span
onClick={() => toggleSettings()}
className={cx([style.actionIcon, showSettings ? style.enabled : null])}
>
<IoSettingsOutline />
</span>
</Tooltip>
@@ -73,8 +80,8 @@ export default function CuesheetTableHeader({ handleExport, featureData }: Cuesh
</span>
</Tooltip>
<Tooltip openDelay={tooltipDelayFast} label='Export rundown'>
<span className={style.actionIcon} onClick={exportProject}>
Export
<span className={style.actionText} onClick={exportProject}>
Export CSV
</span>
</Tooltip>
</div>
@@ -39,14 +39,9 @@ export const parseField = <T extends OntimeEntryCommonKeys>(field: T, data: unkn
* @return {(string[])[]}
*/
export const makeTable = (headerData: ProjectData, rundown: OntimeRundown, userFields: UserFields): string[][] => {
const data = [
['Ontime · Schedule Template'],
['Project Title', headerData?.title || ''],
['Project Description', headerData?.description || ''],
['Public URL', headerData?.publicUrl || ''],
['Backstage URL', headerData?.backstageUrl || ''],
[],
];
const data = [['Ontime · Rundown export']];
if (headerData.title) data.push([`Project title: ${headerData.title}`]);
if (headerData.description) data.push([`Project description: ${headerData.description}`]);
const fieldOrder: OntimeEntryCommonKeys[] = [
'timeStart',