diff --git a/apps/client/src/features/app-settings/panel/manage-panel/sources-panel/ImportReview.tsx b/apps/client/src/features/app-settings/panel/manage-panel/sources-panel/ImportReview.tsx index 73e0d49f7..9ebccff4f 100644 --- a/apps/client/src/features/app-settings/panel/manage-panel/sources-panel/ImportReview.tsx +++ b/apps/client/src/features/app-settings/panel/manage-panel/sources-panel/ImportReview.tsx @@ -1,5 +1,6 @@ import { useState } from 'react'; import { CustomFields, Rundown } from 'ontime-types'; +import { getFirstEventNormal, getLastEventNormal, millisToString } from 'ontime-utils'; import Button from '../../../../../common/components/buttons/Button'; import useRundown from '../../../../../common/hooks-query/useRundown'; @@ -43,6 +44,9 @@ export default function ImportReview(props: ImportReviewProps) { onFinished(); }; + const { firstEvent } = getFirstEventNormal(rundown.entries, rundown.flatOrder); + const { lastEvent } = getLastEventNormal(rundown.entries, rundown.flatOrder); + return ( @@ -56,6 +60,24 @@ export default function ImportReview(props: ImportReviewProps) { + + + Title {rundown.title} + + + Number of entries {rundown.flatOrder.length} + + {firstEvent && ( + + Start Time {millisToString(firstEvent.timeStart)} + + )} + {lastEvent && ( + + End Time {millisToString(lastEvent.timeEnd)} + + )} + ); diff --git a/apps/client/src/features/app-settings/panel/manage-panel/sources-panel/import-map/ImportMapForm.tsx b/apps/client/src/features/app-settings/panel/manage-panel/sources-panel/import-map/ImportMapForm.tsx index d1d354006..30968d7d0 100644 --- a/apps/client/src/features/app-settings/panel/manage-panel/sources-panel/import-map/ImportMapForm.tsx +++ b/apps/client/src/features/app-settings/panel/manage-panel/sources-panel/import-map/ImportMapForm.tsx @@ -5,6 +5,7 @@ import { checkRegex, ImportMap } from 'ontime-utils'; import Button from '../../../../../../common/components/buttons/Button'; import IconButton from '../../../../../../common/components/buttons/IconButton'; +import Info from '../../../../../../common/components/info/Info'; import Input from '../../../../../../common/components/input/input/Input'; import Select from '../../../../../../common/components/select/Select'; import Tooltip from '../../../../../../common/components/tooltip/Tooltip'; @@ -136,6 +137,10 @@ export default function ImportMapForm({ + + Match your spreadsheet columns to Ontime fields.
+ You can also add Custom Fields by providing a name for Ontime and the spreadsheet column name. +
diff --git a/apps/client/src/features/app-settings/panel/manage-panel/sources-panel/preview/PreviewRundown.tsx b/apps/client/src/features/app-settings/panel/manage-panel/sources-panel/preview/PreviewRundown.tsx index dd9251c8f..b6a96c4fb 100644 --- a/apps/client/src/features/app-settings/panel/manage-panel/sources-panel/preview/PreviewRundown.tsx +++ b/apps/client/src/features/app-settings/panel/manage-panel/sources-panel/preview/PreviewRundown.tsx @@ -18,9 +18,7 @@ function booleanToText(value?: boolean) { return value ? 'Yes' : undefined; } -export default function PreviewRundown(props: PreviewRundownProps) { - const { rundown, customFields } = props; - +export default function PreviewRundown({ rundown, customFields }: PreviewRundownProps) { // we only count Ontime Events which are 1 based in client let eventIndex = 0; @@ -59,9 +57,7 @@ export default function PreviewRundown(props: PreviewRundownProps) { const colour = entry.colour ? getAccessibleColour(entry.colour) : {}; return ( - - - - + {/** Index */} {entry.type} diff --git a/apps/server/src/api-data/excel/__mocks__/simpleTemplate.ts b/apps/server/src/api-data/excel/__mocks__/simpleTemplate.ts new file mode 100644 index 000000000..fcd7e6239 --- /dev/null +++ b/apps/server/src/api-data/excel/__mocks__/simpleTemplate.ts @@ -0,0 +1,124 @@ +export const simpleTemplate = [ + ['ⓘ Notes on import', '', '', '', '', '', 'ⓘ How to use'], + [ + 'Ontime will import this spreasheet as a rundown in your project', + '', + '', + '', + '', + '', + '1. Make your own copy of the spreadsheet by either File -> Make a copy or File -> Download -> Microsoft Excel', + ], + [ + '- Each spreadsheet can only contain one rundown', + '', + '', + '', + '', + '', + '2. Delete the table header with the Notes on import and How to use instructions', + ], + [ + '- You can have as many columns as necessary, Ontime only imports the columns you request', + '', + '', + '', + '', + '', + '3. Remove any columns that are not necessary to your workflow', + ], + [ + '- The names of the worksheet and columns are given in Ontime on import', + '', + '', + '', + '', + '', + 'See documentation for spreadsheet import', + '', + 'https://docs.getontime.no/features/import-spreadsheet/', + ], + [ + '- Ontime creates an entry for every row on the table with a known Timer Type (see note on column header)', + '', + '', + '', + '', + '', + 'See documentation for Google Sheet import', + '', + 'https://docs.getontime.no/features/import-spreadsheet-gsheet/', + ], + ['- All fields in a row are optional, Ontime will fill in with default data as needed'], + ['ⓘ Hover over note tags to see additional help on column data'], + [ + '', + '', + '', + 'Note ---------------------------------------------', + '', + '', + '', + 'Note', + 'Note --------------------------------------------------------', + ], + ['Cue', 'Title', 'Colour', 'Time Start', 'Time End', 'Duration', 'Note', 'Timer Type', 'Video Notes'], + [ + 'Demo', + 'Clear all, or Create New Project to start fresh', + '#9d9d9d', + '', + '', + '', + 'Moderator - Emma Thompson\n' + + '\n' + + 'Speakers\n' + + '- Liam Carter + Sophia Patel\n' + + '- Ethan Brooks\n' + + '- Lucas Bennett', + 'milestone', + ], + ['', 'Morning Sessions', '#339E4E', '', '', '', '', 'group-start'], + [ + '1', + 'Pre-show Countdown', + '#77C785', + '10:00:00', + '11:00:00', + '', + 'Music plays, holding slide on screens', + 'count-down', + 'Camera + PowerPoint on stream\nPowerPoint on screens', + ], + ['Standby', '10:45 - Presenters ready side stage', '#A790F5', '', '', '', '', 'milestone'], + ['1.1', 'Welcome', '#FFCC78', '', '', '00:10:00', '', 'count-down', 'Cameras on stream\nPowerPoint on screens'], + [ + '1.2', + 'Session 1', + '#77C785', + '', + '', + '00:50:00', + '', + 'count-down', + 'Camera + PowerPoint on stream\nPowerPoint on screens', + ], + ['House', '11:30 - House staff setup lunch in lobby', '#A790F5', '', '', '', '', 'milestone'], + ['', 'Lunch', '#3E75E8', '', '', '', '', 'group-start'], + ['2.1', 'Lunch / Countdown to next session', '#779BE7', '12:00:00', '13:00:00', '', '', 'count-down'], + ['', 'Afternoon Sessions', '#339E4E', '', '', '', '', 'group-start'], + ['Standby', '12:45 - Presenters ready side stage', '#A790F5', '', '', '', '', 'milestone'], + [ + '3.2', + 'Session 2', + '#77C785', + '13:00:00', + '', + '00:50:00', + '', + 'count-down', + 'Camera + PPT + Video on stream\n' + 'PowerPoint + Video on screens\n' + '\n' + 'Video file: Session2.mp4', + ], + ['3.2', 'Wrap up', '#FFCC78', '', '', '00:10:00', '', 'count-down', 'Holding slide on screens'], + ['Strike', '14:00 - AV & Room strike', '#A790F5', '', '', '', '', 'milestone'], +]; diff --git a/apps/server/src/api-data/excel/__tests__/excel.parser.test.ts b/apps/server/src/api-data/excel/__tests__/excel.parser.test.ts index ffcb90692..35251abcd 100644 --- a/apps/server/src/api-data/excel/__tests__/excel.parser.test.ts +++ b/apps/server/src/api-data/excel/__tests__/excel.parser.test.ts @@ -1,12 +1,34 @@ import { CustomFields, OntimeEvent, OntimeGroup, SupportedEntry, TimerType } from 'ontime-types'; -import { ImportMap, MILLIS_PER_MINUTE } from 'ontime-utils'; +import { ImportMap, MILLIS_PER_MINUTE, millisToString } from 'ontime-utils'; import { parseExcel } from '../excel.parser.js'; import { dataFromExcelTemplate } from './mockData.js'; +import { simpleTemplate } from '../__mocks__/simpleTemplate.js'; describe('parseExcel()', () => { - it('parses the example file', () => { + it('parses the simple template file', () => { + const result = parseExcel(simpleTemplate, {}); + + expect(result.rundown.flatOrder.length).toBe(14); + expect(result.rundown.order.length).toBe(4); + + const firstEvent = result.rundown.entries[result.rundown.flatOrder[2]]; + expect(firstEvent).toMatchObject({ + type: SupportedEntry.Event, + title: 'Pre-show Countdown', + }); + expect(millisToString((firstEvent as OntimeEvent).timeStart)).toBe('10:00:00'); + + const lastEvent = result.rundown.entries[result.rundown.flatOrder[12]]; + expect(lastEvent).toMatchObject({ + type: SupportedEntry.Event, + title: 'Wrap up', + }); + // the excel step does not relate elements yet + expect(millisToString((lastEvent as OntimeEvent).timeEnd)).toBe('00:00:00'); + }); + it('parses an import map with only custom fields', () => { // partial import map with only custom fields const importMap = { custom: { diff --git a/apps/server/src/api-data/excel/excel.parser.ts b/apps/server/src/api-data/excel/excel.parser.ts index 5d367a081..e85d770e1 100644 --- a/apps/server/src/api-data/excel/excel.parser.ts +++ b/apps/server/src/api-data/excel/excel.parser.ts @@ -212,7 +212,7 @@ export const parseExcel = ( return; } - //and fall through to treat it as an event + // after group and milestones we only have events remaining const event = { ...entry, custom: { ...entryCustomFields }, @@ -223,6 +223,7 @@ export const parseExcel = ( event.timerType = TimerType.CountDown; } + // we link all events unless user specifies otherwise if (entry.linkStart === undefined) { event.linkStart = true; }