From 8df419d83588096b13d2db404da1817e7fa1bd35 Mon Sep 17 00:00:00 2001 From: Carlos Valente <34649812+cpvalente@users.noreply.github.com> Date: Thu, 29 Feb 2024 18:19:58 +0100 Subject: [PATCH] refactor: deprecate presenter and subtitle (#795) * refactor: deprecate presenter and subtitle * style: reduce spacing between related sections * refactor: optional secondary field * refactor: remove secondary field * refactor: dynamic data source --- .../common/components/schedule/Schedule.scss | 7 +- .../components/schedule/ScheduleContext.tsx | 2 +- .../components/schedule/ScheduleItem.tsx | 4 +- .../components/title-card/TitleCard.scss | 54 +- .../components/title-card/TitleCard.tsx | 16 +- .../view-params-editor/ParamInput.tsx | 2 +- .../ViewParamsEditor.module.scss | 6 +- .../view-params-editor/constants.ts | 486 ++++++++++-------- .../utils/__tests__/eventsManager.test.ts | 4 - apps/client/src/common/utils/eventsManager.ts | 2 - apps/client/src/common/utils/viewUtils.ts | 6 - .../__test__/importMapUtils.test.ts | 2 - .../import-map/importMapUtils.ts | 4 - .../sources-panel/preview/PreviewRundown.tsx | 4 - .../__snapshots__/utils.test.js.snap | 4 - .../features/cuesheet/__tests__/utils.test.js | 5 +- .../src/features/cuesheet/cuesheetCols.tsx | 15 +- .../src/features/cuesheet/cuesheetUtils.ts | 4 - apps/client/src/features/cuesheet/defaults.ts | 2 - .../client/src/features/operator/Operator.tsx | 8 +- .../operator-event/OperatorEvent.module.scss | 2 +- .../rundown/event-editor/EventEditor.tsx | 4 +- .../composite/EventEditorTimes.tsx | 4 +- .../composite/EventEditorTitles.tsx | 10 +- .../src/features/viewers/ViewWrapper.tsx | 67 ++- .../features/viewers/backstage/Backstage.tsx | 41 +- .../src/features/viewers/common/viewUtils.ts | 56 ++ .../features/viewers/common/viewerUtils.ts | 25 - .../viewers/lower-thirds/LowerThird.scss | 2 +- .../viewers/lower-thirds/LowerThird.tsx | 70 ++- .../viewers/minimal-timer/MinimalTimer.tsx | 3 +- .../src/features/viewers/public/Public.tsx | 28 +- .../features/viewers/studio/StudioClock.tsx | 2 +- .../src/features/viewers/timer/Timer.tsx | 47 +- .../integrationController.config.ts | 2 - apps/server/src/models/eventsDefinition.ts | 2 - .../__tests__/rundownCache.test.ts | 24 - .../__tests__/sheetUtils.test.ts | 23 - .../server/src/utils/__tests__/parser.test.ts | 44 -- apps/server/src/utils/parser.ts | 16 - apps/server/test-db/db.json | 110 ++-- demo-db/db.json | 98 ++-- ...le.spec.ts => 000-upload-showfile.spec.ts} | 0 ...-tests.spec.ts => 001-smoke-tests.spec.ts} | 0 ...on.spec.ts => 002-view-navigation.spec.ts} | 0 e2e/tests/features/202-cuesheet.spec.ts | 2 - e2e/tests/fixtures/test-db.json | 98 ++-- .../src/definitions/core/OntimeEvent.type.ts | 2 - .../__tests__/spreadsheetImport.test.ts | 4 - .../spreadsheet-import/spreadsheetImport.ts | 2 - test-db/db.json | 41 ++ 51 files changed, 735 insertions(+), 731 deletions(-) delete mode 100644 apps/client/src/common/utils/viewUtils.ts create mode 100644 apps/client/src/features/viewers/common/viewUtils.ts delete mode 100644 apps/client/src/features/viewers/common/viewerUtils.ts rename e2e/tests/{002-upload-showfile.spec.ts => 000-upload-showfile.spec.ts} (100%) rename e2e/tests/{000-smoke-tests.spec.ts => 001-smoke-tests.spec.ts} (100%) rename e2e/tests/{001-view-navigation.spec.ts => 002-view-navigation.spec.ts} (100%) create mode 100644 test-db/db.json diff --git a/apps/client/src/common/components/schedule/Schedule.scss b/apps/client/src/common/components/schedule/Schedule.scss index aa2e8d2ca..c7239904c 100644 --- a/apps/client/src/common/components/schedule/Schedule.scss +++ b/apps/client/src/common/components/schedule/Schedule.scss @@ -31,6 +31,12 @@ text-overflow: ellipsis; } + .entry-secondary { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + &:not(:last-child) { padding-bottom: 8px; } @@ -68,4 +74,3 @@ } } } - diff --git a/apps/client/src/common/components/schedule/ScheduleContext.tsx b/apps/client/src/common/components/schedule/ScheduleContext.tsx index 40497ee43..34337ce4e 100644 --- a/apps/client/src/common/components/schedule/ScheduleContext.tsx +++ b/apps/client/src/common/components/schedule/ScheduleContext.tsx @@ -2,8 +2,8 @@ import { createContext, PropsWithChildren, useContext, useState } from 'react'; import { useSearchParams } from 'react-router-dom'; import { OntimeEvent } from 'ontime-types'; +import { isStringBoolean } from '../../../features/viewers/common/viewUtils'; import { useInterval } from '../../hooks/useInterval'; -import { isStringBoolean } from '../../utils/viewUtils'; interface ScheduleContextState { events: OntimeEvent[]; diff --git a/apps/client/src/common/components/schedule/ScheduleItem.tsx b/apps/client/src/common/components/schedule/ScheduleItem.tsx index 102ee7f4c..a1029b51f 100644 --- a/apps/client/src/common/components/schedule/ScheduleItem.tsx +++ b/apps/client/src/common/components/schedule/ScheduleItem.tsx @@ -13,14 +13,13 @@ interface ScheduleItemProps { timeStart: number; timeEnd: number; title: string; - presenter?: string; backstageEvent: boolean; colour: string; skip: boolean; } export default function ScheduleItem(props: ScheduleItemProps) { - const { selected, timeStart, timeEnd, title, presenter, backstageEvent, colour, skip } = props; + const { selected, timeStart, timeEnd, title, backstageEvent, colour, skip } = props; const start = formatTime(timeStart, formatOptions); const end = formatTime(timeEnd, formatOptions); @@ -39,7 +38,6 @@ export default function ScheduleItem(props: ScheduleItemProps) {
{title}
- {presenter &&
{presenter}
} ); } diff --git a/apps/client/src/common/components/title-card/TitleCard.scss b/apps/client/src/common/components/title-card/TitleCard.scss index d8c74e7c8..e9d0b130a 100644 --- a/apps/client/src/common/components/title-card/TitleCard.scss +++ b/apps/client/src/common/components/title-card/TitleCard.scss @@ -3,34 +3,38 @@ .title-card { display: flex; flex-direction: column; - gap: 8px; + gap: 0.5rem; +} - .inline { - display: flex; - } +.inline { + display: flex; +} - .title { - font-weight: 600; - font-size: clamp(32px, 3.5vw, 50px); - color: var(--color-override, $viewer-color); - line-height: 1.1em; - } +.title-card__title { + font-weight: 600; + font-size: clamp(32px, 3.5vw, 50px); + color: var(--color-override, $viewer-color); + line-height: 1.1em; +} - .subtitle, .presenter { - font-size: clamp(24px, 2vw, 35px); - color: var(--secondary-color-override, $viewer-secondary-color); - line-height: 1.1em; - } +.title-card__label { + font-size: clamp(1rem, 1.5vw, 1.5rem); + font-weight: 400; + color: var(--secondary-color-override, $viewer-secondary-color); + margin-left: auto; + text-transform: uppercase; - .label { - font-size: clamp(16px, 1.5vw, 24px); - font-weight: 400; - color: var(--secondary-color-override, $viewer-secondary-color); - margin-left: auto; - text-transform: uppercase; - - &.accent { - color: var(--accent-color-override, $accent-color); - } + &--accent { + color: var(--accent-color-override, $accent-color); + } +} + +.title-card__secondary { + font-size: clamp(1.5rem, 2vw, 2.25rem); + color: var(--secondary-color-override, $viewer-secondary-color); + line-height: 1.1em; + + &::after { + content: '\200b'; } } diff --git a/apps/client/src/common/components/title-card/TitleCard.tsx b/apps/client/src/common/components/title-card/TitleCard.tsx index f6b9a43f0..7e9220fb3 100644 --- a/apps/client/src/common/components/title-card/TitleCard.tsx +++ b/apps/client/src/common/components/title-card/TitleCard.tsx @@ -4,13 +4,12 @@ import './TitleCard.scss'; interface TitleCardProps { label: 'now' | 'next'; - title: string | null; - subtitle: string | null; - presenter: string | null; + title: string; + secondary?: string; } export default function TitleCard(props: TitleCardProps) { - const { label, title, subtitle, presenter } = props; + const { label, title, secondary } = props; const { getLocalizedString } = useTranslation(); const accent = label === 'now'; @@ -18,11 +17,12 @@ export default function TitleCard(props: TitleCardProps) { return (
- {presenter} - {getLocalizedString(`common.${label}`)} + {title} + + {getLocalizedString(`common.${label}`)} +
-
{title}
-
{subtitle}
+
{secondary}
); } diff --git a/apps/client/src/common/components/view-params-editor/ParamInput.tsx b/apps/client/src/common/components/view-params-editor/ParamInput.tsx index 282e3212e..24013d963 100644 --- a/apps/client/src/common/components/view-params-editor/ParamInput.tsx +++ b/apps/client/src/common/components/view-params-editor/ParamInput.tsx @@ -1,7 +1,7 @@ import { useSearchParams } from 'react-router-dom'; import { Input, InputGroup, InputLeftElement, Select, Switch } from '@chakra-ui/react'; -import { isStringBoolean } from '../../utils/viewUtils'; +import { isStringBoolean } from '../../../features/viewers/common/viewUtils'; import { ParamField } from './types'; diff --git a/apps/client/src/common/components/view-params-editor/ViewParamsEditor.module.scss b/apps/client/src/common/components/view-params-editor/ViewParamsEditor.module.scss index 2d2567d00..48196313f 100644 --- a/apps/client/src/common/components/view-params-editor/ViewParamsEditor.module.scss +++ b/apps/client/src/common/components/view-params-editor/ViewParamsEditor.module.scss @@ -11,7 +11,7 @@ @extend .drawerContent; display: flex; justify-content: start; - gap: $element-spacing; + gap: $section-spacing; button[type='reset'] { padding: 0 2em; @@ -30,9 +30,9 @@ .columnSection { display: flex; - padding: $element-spacing; + padding: $section-spacing 0; flex-direction: column; - gap: $element-inner-spacing; + gap: $element-spacing; } .title { diff --git a/apps/client/src/common/components/view-params-editor/constants.ts b/apps/client/src/common/components/view-params-editor/constants.ts index 14325c156..bf8b01e5e 100644 --- a/apps/client/src/common/components/view-params-editor/constants.ts +++ b/apps/client/src/common/components/view-params-editor/constants.ts @@ -1,7 +1,16 @@ import { CustomFields } from 'ontime-types'; +import { capitaliseFirstLetter } from '../../../features/viewers/common/viewUtils'; + import { ParamField } from './types'; +const makeOptionsFromCustomFields = (customFields: CustomFields, additionalOptions?: Record) => { + const customFieldOptions = Object.keys(customFields).reduce((acc, key) => { + return { ...acc, [`custom-${key}`]: `Custom: ${capitaliseFirstLetter(key)}` }; + }, additionalOptions ?? {}); + return customFieldOptions; +}; + const getTimeOption = (timeFormat: string): ParamField => { const placeholder = `${timeFormat} (default)`; return { @@ -93,45 +102,56 @@ export const getClockOptions = (timeFormat: string): ParamField[] => [ }, ]; -export const getTimerOptions = (timeFormat: string): ParamField[] => [ - getTimeOption(timeFormat), - hideTimerSeconds, - { - id: 'hideClock', - title: 'Hide Time Now', - description: 'Hides the Time Now field', - type: 'boolean', - defaultValue: false, - }, - { - id: 'hideCards', - title: 'Hide Cards', - description: 'Hides the Now and Next cards', - type: 'boolean', - defaultValue: false, - }, - { - id: 'hideProgress', - title: 'Hide progress bar', - description: 'Hides the progress bar', - type: 'boolean', - defaultValue: false, - }, - { - id: 'hideMessage', - title: 'Hide Presenter Message', - description: 'Prevents the screen from displaying messages from the presenter', - type: 'boolean', - defaultValue: false, - }, - { - id: 'hideExternal', - title: 'Hide External', - description: 'Prevents the screen from displaying the external field', - type: 'boolean', - defaultValue: false, - }, -]; +export const getTimerOptions = (timeFormat: string, customFields: CustomFields): ParamField[] => { + const secondaryOptions = makeOptionsFromCustomFields(customFields); + return [ + getTimeOption(timeFormat), + hideTimerSeconds, + { + id: 'hideClock', + title: 'Hide Time Now', + description: 'Hides the Time Now field', + type: 'boolean', + defaultValue: false, + }, + { + id: 'secondary-src', + title: 'Secondary text', + description: 'Select the data source for the secondary text', + type: 'option', + values: secondaryOptions, + defaultValue: '', + }, + { + id: 'hideCards', + title: 'Hide Cards', + description: 'Hides the Now and Next cards', + type: 'boolean', + defaultValue: false, + }, + { + id: 'hideProgress', + title: 'Hide progress bar', + description: 'Hides the progress bar', + type: 'boolean', + defaultValue: false, + }, + { + id: 'hideMessage', + title: 'Hide Presenter Message', + description: 'Prevents the screen from displaying messages from the presenter', + type: 'boolean', + defaultValue: false, + }, + { + id: 'hideExternal', + title: 'Hide External', + description: 'Prevents the screen from displaying the external field', + type: 'boolean', + defaultValue: false, + }, + ]; +}; export const MINIMAL_TIMER_OPTIONS: ParamField[] = [ hideTimerSeconds, @@ -226,187 +246,216 @@ export const MINIMAL_TIMER_OPTIONS: ParamField[] = [ }, ]; -export const LOWER_THIRD_OPTIONS: ParamField[] = [ - { - id: 'trigger', - title: 'Animation Trigger', - description: '', - type: 'option', - values: { - event: 'Event Load', - manual: 'Manual', - }, - defaultValue: 'event', - }, - { - id: 'top-src', - title: 'Top Text', - description: '', - type: 'option', - values: { - title: 'Title', - subtitle: 'Subtitle', - presenter: 'Presenter', - lowerMsg: 'Lower Third Message', - }, - defaultValue: 'title', - }, - { - id: 'bottom-src', - title: 'Bottom Text', - description: 'Select the text source for the bottom element', - type: 'option', - values: { - title: 'Title', - subtitle: 'Subtitle', - presenter: 'Presenter', - lowerMsg: 'Lower Third Message', - }, - defaultValue: 'subtitle', - }, - { - id: 'top-colour', - title: 'Top Text Colour', - description: 'Top text colour in hexadecimal', - prefix: '#', - type: 'string', - placeholder: '0000ff (default)', - }, - { - id: 'bottom-colour', - title: 'Bottom Text Colour', - description: 'Bottom text colour in hexadecimal', - prefix: '#', - type: 'string', - placeholder: '0000ff (default)', - }, - { - id: 'top-bg', - title: 'Top Background Colour', - description: 'Top text background colour in hexadecimal', - prefix: '#', - type: 'string', - placeholder: '00000000 (default)', - }, - { - id: 'bottom-bg', - title: 'Bottom Background Colour', - description: 'Bottom text background colour in hexadecimal', - prefix: '#', - type: 'string', - placeholder: '00000000 (default)', - }, - { - id: 'top-size', - title: 'Top Text Size', - description: 'Font size of the top text', - type: 'string', - placeholder: '65px', - }, - { - id: 'bottom-size', - title: 'Bottom Text Size', - description: 'Font size of the bottom text', - type: 'string', - placeholder: '64px', - }, - { - id: 'width', - title: 'Minimum Width', - description: 'Minimum Width of the element', - type: 'number', - prefix: '%', - placeholder: '45 (default)', - }, - { - id: 'transition', - title: 'Transition', - description: 'Transition in time in seconds (default 3)', - type: 'number', - placeholder: '3 (default)', - }, - { - id: 'delay', - title: 'Delay', - description: 'Delay between transition in and out in seconds (default 3)', - type: 'number', - placeholder: '3 (default)', - }, - { - id: 'key', - title: 'Key Colour', - description: 'Colour of the background', - prefix: '#', - type: 'string', - placeholder: 'ffffffff (default)', - }, - { - id: 'line-colour', - title: 'Line Colour', - description: 'Colour of the line', - prefix: '#', - type: 'string', - placeholder: 'ff0000ff (default)', - }, -]; +export const getLowerThirdOptions = (customFields: CustomFields): ParamField[] => { + const topSourceOptions = makeOptionsFromCustomFields(customFields, { + title: 'Title', + lowerMsg: 'Lower Third Message', + }); -export const getBackstageOptions = (timeFormat: string): ParamField[] => [ - getTimeOption(timeFormat), - { - id: 'hidePast', - title: 'Hide past events', - description: 'Scheduler will only show upcoming events', - type: 'boolean', - defaultValue: false, - }, - { - id: 'stopCycle', - title: 'Stop cycling through event pages', - description: 'Schedule will not auto-cycle through events', - type: 'boolean', - defaultValue: false, - }, - { - id: 'eventsPerPage', - title: 'Events per page', - description: 'Sets the number of events on the page, can cause overlow', - type: 'number', - placeholder: '7 (default)', - }, -]; + const bottomSourceOptions = makeOptionsFromCustomFields(customFields, { + title: 'Title', + lowerMsg: 'Lower Third Message', + }); + + return [ + { + id: 'trigger', + title: 'Animation Trigger', + description: '', + type: 'option', + values: { + event: 'Event Load', + manual: 'Manual', + }, + defaultValue: 'event', + }, + { + id: 'top-src', + title: 'Top Text', + description: '', + type: 'option', + values: topSourceOptions, + defaultValue: 'title', + }, + { + id: 'bottom-src', + title: 'Bottom Text', + description: 'Select the data source for the bottom element', + type: 'option', + values: bottomSourceOptions, + defaultValue: 'lowerMsg', + }, + { + id: 'top-colour', + title: 'Top Text Colour', + description: 'Top text colour in hexadecimal', + prefix: '#', + type: 'string', + placeholder: '0000ff (default)', + }, + { + id: 'bottom-colour', + title: 'Bottom Text Colour', + description: 'Bottom text colour in hexadecimal', + prefix: '#', + type: 'string', + placeholder: '0000ff (default)', + }, + { + id: 'top-bg', + title: 'Top Background Colour', + description: 'Top text background colour in hexadecimal', + prefix: '#', + type: 'string', + placeholder: '00000000 (default)', + }, + { + id: 'bottom-bg', + title: 'Bottom Background Colour', + description: 'Bottom text background colour in hexadecimal', + prefix: '#', + type: 'string', + placeholder: '00000000 (default)', + }, + { + id: 'top-size', + title: 'Top Text Size', + description: 'Font size of the top text', + type: 'string', + placeholder: '65px', + }, + { + id: 'bottom-size', + title: 'Bottom Text Size', + description: 'Font size of the bottom text', + type: 'string', + placeholder: '64px', + }, + { + id: 'width', + title: 'Minimum Width', + description: 'Minimum Width of the element', + type: 'number', + prefix: '%', + placeholder: '45 (default)', + }, + { + id: 'transition', + title: 'Transition', + description: 'Transition in time in seconds (default 3)', + type: 'number', + placeholder: '3 (default)', + }, + { + id: 'delay', + title: 'Delay', + description: 'Delay between transition in and out in seconds (default 3)', + type: 'number', + placeholder: '3 (default)', + }, + { + id: 'key', + title: 'Key Colour', + description: 'Colour of the background', + prefix: '#', + type: 'string', + placeholder: 'ffffffff (default)', + }, + { + id: 'line-colour', + title: 'Line Colour', + description: 'Colour of the line', + prefix: '#', + type: 'string', + placeholder: 'ff0000ff (default)', + }, + ]; +}; + +export const getBackstageOptions = (timeFormat: string, customFields: CustomFields): ParamField[] => { + const secondaryOptions = makeOptionsFromCustomFields(customFields, { note: 'Note' }); + + return [ + getTimeOption(timeFormat), + { + id: 'hidePast', + title: 'Hide past events', + description: 'Scheduler will only show upcoming events', + type: 'boolean', + defaultValue: false, + }, + { + id: 'stopCycle', + title: 'Stop cycling through event pages', + description: 'Schedule will not auto-cycle through events', + type: 'boolean', + defaultValue: false, + }, + { + id: 'eventsPerPage', + title: 'Events per page', + description: 'Sets the number of events on the page, can cause overlow', + type: 'number', + placeholder: '7 (default)', + }, + { + id: 'secondary-src', + title: 'Event secondary text', + description: 'Select the data source for auxiliary text shown in now and next cards', + type: 'option', + values: secondaryOptions, + defaultValue: '', + }, + ]; +}; + +export const getPublicOptions = (timeFormat: string, customFields: CustomFields): ParamField[] => { + const secondaryOptions = makeOptionsFromCustomFields(customFields); + + return [ + getTimeOption(timeFormat), + { + id: 'hidePast', + title: 'Hide past events', + description: 'Scheduler will only show upcoming events', + type: 'boolean', + defaultValue: false, + }, + { + id: 'stopCycle', + title: 'Stop cycling through event pages', + description: 'Schedule will not auto-cycle through events', + type: 'boolean', + defaultValue: false, + }, + { + id: 'eventsPerPage', + title: 'Events per page', + description: 'Sets the number of events on the page, can cause overlow', + type: 'number', + placeholder: '7 (default)', + }, + { + id: 'secondary-src', + title: 'Event secondary text', + description: 'Select the data source for auxiliary text shown in now and next cards', + type: 'option', + values: secondaryOptions, + defaultValue: '', + }, + ]; +}; -export const getPublicOptions = (timeFormat: string): ParamField[] => [ - getTimeOption(timeFormat), - { - id: 'hidePast', - title: 'Hide past events', - description: 'Scheduler will only show upcoming events', - type: 'boolean', - defaultValue: false, - }, - { - id: 'stopCycle', - title: 'Stop cycling through event pages', - description: 'Schedule will not auto-cycle through events', - type: 'boolean', - defaultValue: false, - }, - { - id: 'eventsPerPage', - title: 'Events per page', - description: 'Sets the number of events on the page, can cause overlow', - type: 'number', - placeholder: '7 (default)', - }, -]; export const getStudioClockOptions = (timeFormat: string): ParamField[] => [ getTimeOption(timeFormat), hideTimerSeconds, ]; export const getOperatorOptions = (customFields: CustomFields, timeFormat: string): ParamField[] => { + const fieldOptions = makeOptionsFromCustomFields(customFields, { title: 'Title', note: 'Note' }); + const customFieldSelect = Object.keys(customFields).reduce((acc, key) => { - return { ...acc, [key]: key }; + return { ...acc, [key]: `Custom: ${capitaliseFirstLetter(key)}` }; }, {}); return [ @@ -423,22 +472,16 @@ export const getOperatorOptions = (customFields: CustomFields, timeFormat: strin title: 'Main data field', description: 'Field to be shown in the first line of text', type: 'option', - values: { - title: 'Title', - subtitle: 'Subtitle', - presenter: 'Presenter', - }, + values: fieldOptions, + defaultValue: 'title', }, { id: 'secondary', title: 'Secondary data field', description: 'Field to be shown in the second line of text', type: 'option', - values: { - title: 'Title', - subtitle: 'Subtitle', - presenter: 'Presenter', - }, + values: fieldOptions, + defaultValue: '', }, { id: 'subscribe', @@ -446,6 +489,7 @@ export const getOperatorOptions = (customFields: CustomFields, timeFormat: strin description: 'Choose a custom field to highlight', type: 'option', values: customFieldSelect, + defaultValue: '', }, { id: 'shouldEdit', diff --git a/apps/client/src/common/utils/__tests__/eventsManager.test.ts b/apps/client/src/common/utils/__tests__/eventsManager.test.ts index 9379ee308..005d92618 100644 --- a/apps/client/src/common/utils/__tests__/eventsManager.test.ts +++ b/apps/client/src/common/utils/__tests__/eventsManager.test.ts @@ -9,8 +9,6 @@ describe('cloneEvent()', () => { type: SupportedEvent.Event, title: 'title', cue: 'cue', - subtitle: 'subtitle', - presenter: 'presenter', note: 'note', timeStart: 0, duration: 10, @@ -33,8 +31,6 @@ describe('cloneEvent()', () => { // @ts-expect-error -- safeguarding this expect(cloned?.id).toBe(undefined); expect(cloned.title).toBe(original.title); - expect(cloned.subtitle).toBe(original.subtitle); - expect(cloned.presenter).toBe(original.presenter); expect(cloned.note).toBe(original.note); expect(cloned.endAction).toBe(original.endAction); expect(cloned.timerType).toBe(original.timerType); diff --git a/apps/client/src/common/utils/eventsManager.ts b/apps/client/src/common/utils/eventsManager.ts index f90f01431..485648919 100644 --- a/apps/client/src/common/utils/eventsManager.ts +++ b/apps/client/src/common/utils/eventsManager.ts @@ -11,8 +11,6 @@ export const cloneEvent = (event: OntimeEvent, after?: string): ClonedEvent => { return { type: SupportedEvent.Event, title: event.title, - subtitle: event.subtitle, - presenter: event.presenter, note: event.note, timeStart: event.timeStart, duration: event.duration, diff --git a/apps/client/src/common/utils/viewUtils.ts b/apps/client/src/common/utils/viewUtils.ts deleted file mode 100644 index e31f1aeb0..000000000 --- a/apps/client/src/common/utils/viewUtils.ts +++ /dev/null @@ -1,6 +0,0 @@ -export function isStringBoolean(text: string | null) { - if (text === null) { - return false; - } - return text?.toLowerCase() === 'true' || text === '1'; -} diff --git a/apps/client/src/features/app-settings/panel/sources-panel/import-map/__test__/importMapUtils.test.ts b/apps/client/src/features/app-settings/panel/sources-panel/import-map/__test__/importMapUtils.test.ts index c94fc2abe..1f1c07b72 100644 --- a/apps/client/src/features/app-settings/panel/sources-panel/import-map/__test__/importMapUtils.test.ts +++ b/apps/client/src/features/app-settings/panel/sources-panel/import-map/__test__/importMapUtils.test.ts @@ -11,8 +11,6 @@ describe('convertToImportMap', () => { Duration: 'duration', Cue: 'cue', Title: 'title', - Presenter: 'presenter', - Subtitle: 'subtitle', 'Is Public': 'public', Skip: 'skip', Note: 'notes', diff --git a/apps/client/src/features/app-settings/panel/sources-panel/import-map/importMapUtils.ts b/apps/client/src/features/app-settings/panel/sources-panel/import-map/importMapUtils.ts index 54dee8af9..e287596e1 100644 --- a/apps/client/src/features/app-settings/panel/sources-panel/import-map/importMapUtils.ts +++ b/apps/client/src/features/app-settings/panel/sources-panel/import-map/importMapUtils.ts @@ -10,8 +10,6 @@ export const namedImportMap = { Duration: 'duration', Cue: 'cue', Title: 'title', - Presenter: 'presenter', - Subtitle: 'subtitle', 'Is Public': 'public', Skip: 'skip', Note: 'notes', @@ -38,8 +36,6 @@ export function convertToImportMap(namedImportMap: NamedImportMap): ImportMap { duration: namedImportMap.Duration, cue: namedImportMap.Cue, title: namedImportMap.Title, - presenter: namedImportMap.Presenter, - subtitle: namedImportMap.Subtitle, isPublic: namedImportMap['Is Public'], skip: namedImportMap.Skip, note: namedImportMap.Note, diff --git a/apps/client/src/features/app-settings/panel/sources-panel/preview/PreviewRundown.tsx b/apps/client/src/features/app-settings/panel/sources-panel/preview/PreviewRundown.tsx index bd7a52c59..da042e9ca 100644 --- a/apps/client/src/features/app-settings/panel/sources-panel/preview/PreviewRundown.tsx +++ b/apps/client/src/features/app-settings/panel/sources-panel/preview/PreviewRundown.tsx @@ -33,8 +33,6 @@ export default function PreviewRundown(props: PreviewRundownProps) { Type Cue Title - Subtitle - Presenter Time Start Time End Duration @@ -85,8 +83,6 @@ export default function PreviewRundown(props: PreviewRundownProps) { {event.cue} {event.title} - {event.subtitle} - {event.presenter} {millisToString(event.timeStart)} {millisToString(event.timeEnd)} {millisToString(event.duration)} diff --git a/apps/client/src/features/cuesheet/__tests__/__snapshots__/utils.test.js.snap b/apps/client/src/features/cuesheet/__tests__/__snapshots__/utils.test.js.snap index f6c089f1a..5214b2670 100644 --- a/apps/client/src/features/cuesheet/__tests__/__snapshots__/utils.test.js.snap +++ b/apps/client/src/features/cuesheet/__tests__/__snapshots__/utils.test.js.snap @@ -19,8 +19,6 @@ exports[`makeTable() > returns array of arrays with given fields 1`] = ` "Colour", "Cue", "Title", - "Subtitle", - "Presenter", "Note", "Is Public? (x)", "Skip?", @@ -35,8 +33,6 @@ exports[`makeTable() > returns array of arrays with given fields 1`] = ` "", "test title 1", "", - "", - "", "x", "", "", diff --git a/apps/client/src/features/cuesheet/__tests__/utils.test.js b/apps/client/src/features/cuesheet/__tests__/utils.test.js index 1c0937390..d62ed12e7 100644 --- a/apps/client/src/features/cuesheet/__tests__/utils.test.js +++ b/apps/client/src/features/cuesheet/__tests__/utils.test.js @@ -27,15 +27,13 @@ describe('parseField()', () => { }); it('returns an empty string on undefined fields', () => { - expect(parseField('presenter')).toBe(''); + expect(parseField('title')).toBe(''); }); describe('simply returns any other value in any other field', () => { const testFields = [ { field: 'nothing', value: '123' }, { field: 'title', value: 'test' }, - { field: 'presenter', value: 'test' }, - { field: 'subtitle', value: 'test' }, { field: 'note', value: 'test' }, { field: 'colour', value: 'test' }, ]; @@ -57,7 +55,6 @@ describe('makeTable()', () => { const tableData = [ { title: 'test title 1', - presenter: '', timeStart: 0, timeEnd: 0, isPublic: 'x', diff --git a/apps/client/src/features/cuesheet/cuesheetCols.tsx b/apps/client/src/features/cuesheet/cuesheetCols.tsx index 709b42be7..cbbde5277 100644 --- a/apps/client/src/features/cuesheet/cuesheetCols.tsx +++ b/apps/client/src/features/cuesheet/cuesheetCols.tsx @@ -61,6 +61,7 @@ export function makeCuesheetColumns(customFields: CustomFields): ColumnDef row.getValue(), - }, - { - accessorKey: 'subtitle', - id: 'subtitle', - header: 'Subtitle', - cell: (row) => row.getValue(), - }, - { - accessorKey: 'presenter', - id: 'presenter', - header: 'Presenter', - cell: (row) => row.getValue(), + size: 250, }, { accessorKey: 'note', id: 'note', header: 'Note', cell: (row) => row.getValue(), + size: 250, }, ...dynamicCustomFields, ]; diff --git a/apps/client/src/features/cuesheet/cuesheetUtils.ts b/apps/client/src/features/cuesheet/cuesheetUtils.ts index c06def0d8..51334ac07 100644 --- a/apps/client/src/features/cuesheet/cuesheetUtils.ts +++ b/apps/client/src/features/cuesheet/cuesheetUtils.ts @@ -56,8 +56,6 @@ export const makeTable = (headerData: ProjectData, rundown: OntimeRundown, custo 'colour', 'cue', 'title', - 'subtitle', - 'presenter', 'note', 'isPublic', 'skip', @@ -72,8 +70,6 @@ export const makeTable = (headerData: ProjectData, rundown: OntimeRundown, custo 'Colour', 'Cue', 'Title', - 'Subtitle', - 'Presenter', 'Note', 'Is Public? (x)', 'Skip?', diff --git a/apps/client/src/features/cuesheet/defaults.ts b/apps/client/src/features/cuesheet/defaults.ts index dd755e213..c57bc6e2a 100644 --- a/apps/client/src/features/cuesheet/defaults.ts +++ b/apps/client/src/features/cuesheet/defaults.ts @@ -10,8 +10,6 @@ export const defaultColumnOrder: OntimeEntryCommonKeys[] = [ 'timeEnd', 'duration', 'title', - 'subtitle', - 'presenter', 'note', ]; diff --git a/apps/client/src/features/operator/Operator.tsx b/apps/client/src/features/operator/Operator.tsx index e4378087c..c2ce6ab41 100644 --- a/apps/client/src/features/operator/Operator.tsx +++ b/apps/client/src/features/operator/Operator.tsx @@ -15,7 +15,7 @@ import useRundown from '../../common/hooks-query/useRundown'; import useSettings from '../../common/hooks-query/useSettings'; import { debounce } from '../../common/utils/debounce'; import { getDefaultFormat } from '../../common/utils/time'; -import { isStringBoolean } from '../../common/utils/viewUtils'; +import { getPropertyValue, isStringBoolean } from '../viewers/common/viewUtils'; import EditModal from './edit-modal/EditModal'; import FollowButton from './follow-button/FollowButton'; @@ -27,7 +27,7 @@ import style from './Operator.module.scss'; const selectedOffset = 50; -type TitleFields = Pick; +type TitleFields = Pick; export type EditEvent = Pick & { fieldLabel?: string; fieldValue: string }; export type PartialEdit = EditEvent & { field: keyof CustomFields; @@ -129,7 +129,7 @@ export default function Operator() { const canEdit = shouldEdit && subscribe; const main = searchParams.get('main') as keyof TitleFields | null; - const secondary = searchParams.get('secondary') as keyof TitleFields | null; + const secondary = searchParams.get('secondary'); const defaultFormat = getDefaultFormat(settings?.timeFormat); const operatorOptions = getOperatorOptions(customFields, defaultFormat); @@ -176,7 +176,7 @@ export default function Operator() { } const mainField = main ? entry?.[main] || entry.title : entry.title; - const secondaryField = secondary ? entry?.[secondary] || entry.subtitle : entry.subtitle; + const secondaryField = getPropertyValue(entry, secondary) ?? ''; const subscribedData = entry.custom[subscribe]?.value; return ( diff --git a/apps/client/src/features/operator/operator-event/OperatorEvent.module.scss b/apps/client/src/features/operator/operator-event/OperatorEvent.module.scss index d5a867d8c..cf1d38286 100644 --- a/apps/client/src/features/operator/operator-event/OperatorEvent.module.scss +++ b/apps/client/src/features/operator/operator-event/OperatorEvent.module.scss @@ -102,7 +102,7 @@ } .value { - color: $orange-500 + color: $orange-500; } } diff --git a/apps/client/src/features/rundown/event-editor/EventEditor.tsx b/apps/client/src/features/rundown/event-editor/EventEditor.tsx index 7a2681109..c039fddda 100644 --- a/apps/client/src/features/rundown/event-editor/EventEditor.tsx +++ b/apps/client/src/features/rundown/event-editor/EventEditor.tsx @@ -16,7 +16,7 @@ import style from './EventEditor.module.scss'; export type EventEditorSubmitActions = keyof OntimeEvent; -export type EditorUpdateFields = 'cue' | 'title' | 'presenter' | 'subtitle' | 'note' | 'colour' | CustomFieldLabel; // TODO: keyof customFields +export type EditorUpdateFields = 'cue' | 'title' | 'note' | 'colour' | CustomFieldLabel; export default function EventEditor() { const selectedEvents = useEventSelection((state) => state.selectedEvents); @@ -90,8 +90,6 @@ export default function EventEditor() { eventId={event.id} cue={event.cue} title={event.title} - presenter={event.presenter} - subtitle={event.subtitle} note={event.note} colour={event.colour} handleSubmit={handleSubmit} diff --git a/apps/client/src/features/rundown/event-editor/composite/EventEditorTimes.tsx b/apps/client/src/features/rundown/event-editor/composite/EventEditorTimes.tsx index b05850c08..0a16b4453 100644 --- a/apps/client/src/features/rundown/event-editor/composite/EventEditorTimes.tsx +++ b/apps/client/src/features/rundown/event-editor/composite/EventEditorTimes.tsx @@ -70,7 +70,7 @@ const EventEditorTimes = (props: EventEditorTimesProps) => { : ''; return ( - <> +
Event schedule
@@ -139,7 +139,7 @@ const EventEditorTimes = (props: EventEditorTimesProps) => { {isPublic ? 'Public' : 'Private'}
- +
); }; diff --git a/apps/client/src/features/rundown/event-editor/composite/EventEditorTitles.tsx b/apps/client/src/features/rundown/event-editor/composite/EventEditorTitles.tsx index 31b308109..a4d764427 100644 --- a/apps/client/src/features/rundown/event-editor/composite/EventEditorTitles.tsx +++ b/apps/client/src/features/rundown/event-editor/composite/EventEditorTitles.tsx @@ -10,19 +10,17 @@ import EventTextInput from './EventTextInput'; import style from '../EventEditor.module.scss'; -interface EventEditorLeftProps { +interface EventEditorTitlesProps { eventId: string; cue: string; title: string; - presenter: string; - subtitle: string; note: string; colour: string; handleSubmit: (field: EditorUpdateFields, value: string) => void; } -const EventEditorTitles = (props: EventEditorLeftProps) => { - const { eventId, cue, title, presenter, subtitle, note, colour, handleSubmit } = props; +const EventEditorTitles = (props: EventEditorTitlesProps) => { + const { eventId, cue, title, note, colour, handleSubmit } = props; const cueSubmitHandler = (_field: string, newValue: string) => { handleSubmit('cue', sanitiseCue(newValue)); @@ -51,8 +49,6 @@ const EventEditorTitles = (props: EventEditorLeftProps) => {
- - ); diff --git a/apps/client/src/features/viewers/ViewWrapper.tsx b/apps/client/src/features/viewers/ViewWrapper.tsx index 790211a4d..f54922ec1 100644 --- a/apps/client/src/features/viewers/ViewWrapper.tsx +++ b/apps/client/src/features/viewers/ViewWrapper.tsx @@ -1,8 +1,18 @@ import { ComponentType, useMemo } from 'react'; import { ViewExtendedTimer } from 'common/models/TimeManager.type'; -import { Message, OntimeEvent, ProjectData, Settings, SupportedEvent, TimerMessage, ViewSettings } from 'ontime-types'; +import { + CustomFields, + Message, + OntimeEvent, + ProjectData, + Settings, + SupportedEvent, + TimerMessage, + ViewSettings, +} from 'ontime-types'; import { useStore } from 'zustand'; +import useCustomFields from '../../common/hooks-query/useCustomFields'; import useProjectData from '../../common/hooks-query/useProjectData'; import { useFlatRundown } from '../../common/hooks-query/useRundown'; import useSettings from '../../common/hooks-query/useSettings'; @@ -11,25 +21,26 @@ import { runtimeStore } from '../../common/stores/runtime'; import { useViewOptionsStore } from '../../common/stores/viewOptions'; type WithDataProps = { + backstageEvents: OntimeEvent[]; + customFields: CustomFields; + eventNext: OntimeEvent | null; + eventNow: OntimeEvent | null; + events: OntimeEvent[]; + external: Message; + general: ProjectData; isMirrored: boolean; + lower: Message; + nextId: string | null; + onAir: boolean; pres: TimerMessage; publ: Message; - lower: Message; - external: Message; - eventNow: OntimeEvent | null; - publicEventNow: OntimeEvent | null; - eventNext: OntimeEvent | null; publicEventNext: OntimeEvent | null; - time: ViewExtendedTimer; - events: OntimeEvent[]; - backstageEvents: OntimeEvent[]; - selectedId: string | null; + publicEventNow: OntimeEvent | null; publicSelectedId: string | null; - nextId: string | null; - general: ProjectData; - viewSettings: ViewSettings; + selectedId: string | null; settings: Settings | undefined; - onAir: boolean; + time: ViewExtendedTimer; + viewSettings: ViewSettings; }; function getDisplayName(Component: React.ComponentType): string { @@ -46,6 +57,7 @@ const withData =

(Component: ComponentType

) => { const { data: project } = useProjectData(); const { data: viewSettings } = useViewSettings(); const { data: settings } = useSettings(); + const { data: customFields } = useCustomFields(); const publicEvents = useMemo(() => { if (Array.isArray(rundownData)) { @@ -83,25 +95,26 @@ const withData =

(Component: ComponentType

) => { return ( ); }; diff --git a/apps/client/src/features/viewers/backstage/Backstage.tsx b/apps/client/src/features/viewers/backstage/Backstage.tsx index 6e8bffb36..5a38da74e 100644 --- a/apps/client/src/features/viewers/backstage/Backstage.tsx +++ b/apps/client/src/features/viewers/backstage/Backstage.tsx @@ -1,7 +1,8 @@ import { useEffect, useState } from 'react'; import QRCode from 'react-qr-code'; +import { useSearchParams } from 'react-router-dom'; import { AnimatePresence, motion } from 'framer-motion'; -import { Message, OntimeEvent, ProjectData, Settings, SupportedEvent, ViewSettings } from 'ontime-types'; +import { CustomFields, Message, OntimeEvent, ProjectData, Settings, SupportedEvent, ViewSettings } from 'ontime-types'; import { millisToString, removeLeadingZero } from 'ontime-utils'; import { overrideStylesURL } from '../../../common/api/apiConstants'; @@ -19,10 +20,12 @@ import { formatTime, getDefaultFormat } from '../../../common/utils/time'; import { useTranslation } from '../../../translation/TranslationProvider'; import { titleVariants } from '../common/animation'; import SuperscriptTime from '../common/superscript-time/SuperscriptTime'; +import { getPropertyValue } from '../common/viewUtils'; import './Backstage.scss'; interface BackstageProps { + customFields: CustomFields; isMirrored: boolean; publ: Message; eventNow: OntimeEvent | null; @@ -36,12 +39,24 @@ interface BackstageProps { } export default function Backstage(props: BackstageProps) { - const { isMirrored, publ, eventNow, eventNext, time, backstageEvents, selectedId, general, viewSettings, settings } = - props; + const { + customFields, + isMirrored, + publ, + eventNow, + eventNext, + time, + backstageEvents, + selectedId, + general, + viewSettings, + settings, + } = props; const { shouldRender } = useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL); const { getLocalizedString } = useTranslation(); const [blinkClass, setBlinkClass] = useState(false); + const [searchParams] = useSearchParams(); // Set window title useEffect(() => { @@ -74,12 +89,16 @@ export default function Backstage(props: BackstageProps) { const showPublicMessage = publ.text && publ.visible; const showProgress = time.playback !== 'stop'; + const secondarySource = searchParams.get('secondary-src'); + const secondaryTextNext = getPropertyValue(eventNext, secondarySource); + const secondaryTextNow = getPropertyValue(eventNow, secondarySource); + let stageTimer = millisToString(time.current, { fallback: '- - : - -' }); stageTimer = removeLeadingZero(stageTimer); const totalTime = (time.duration ?? 0) + (time.addedTime ?? 0); const defaultFormat = getDefaultFormat(settings?.timeFormat); - const backstageOptions = getBackstageOptions(defaultFormat); + const backstageOptions = getBackstageOptions(defaultFormat, customFields); return (

@@ -111,12 +130,7 @@ export default function Backstage(props: BackstageProps) { animate='visible' exit='exit' > - +
{getLocalizedString('common.started_at')}
@@ -151,12 +165,7 @@ export default function Backstage(props: BackstageProps) { animate='visible' exit='exit' > - + )} diff --git a/apps/client/src/features/viewers/common/viewUtils.ts b/apps/client/src/features/viewers/common/viewUtils.ts new file mode 100644 index 000000000..0c42a25ee --- /dev/null +++ b/apps/client/src/features/viewers/common/viewUtils.ts @@ -0,0 +1,56 @@ +import { MaybeString, OntimeEvent, TimerType } from 'ontime-types'; + +import type { ViewExtendedTimer } from '../../../common/models/TimeManager.type'; + +type TimerTypeParams = Pick; + +export function getTimerByType(timerObject?: TimerTypeParams): number | null { + if (!timerObject) { + return null; + } + + switch (timerObject.timerType) { + case TimerType.CountDown: + case TimerType.TimeToEnd: + return timerObject.current; + case TimerType.CountUp: + return Math.abs(timerObject.elapsed ?? 0); + case TimerType.Clock: + return timerObject.clock; + default: { + const exhaustiveCheck: never = timerObject.timerType; + return exhaustiveCheck; + } + } +} + +export function isStringBoolean(text: string | null) { + if (text === null) { + return false; + } + return text?.toLowerCase() === 'true' || text === '1'; +} + +/** + * Retrieves a dynamic property from an event + * Considers custom fields + */ +export function getPropertyValue(event: OntimeEvent | null, property: MaybeString): string | undefined { + if (!event) { + return undefined; + } + if (typeof property !== 'string') { + return undefined; + } + + if (property.startsWith('custom-')) { + const field = property.split('custom-')[1]; + return event.custom?.[field]?.value; + } + + return event[property as keyof OntimeEvent] as string; +} + +export function capitaliseFirstLetter(string: string) { + return string.charAt(0).toUpperCase() + string.slice(1); +} diff --git a/apps/client/src/features/viewers/common/viewerUtils.ts b/apps/client/src/features/viewers/common/viewerUtils.ts deleted file mode 100644 index c83b6fbcd..000000000 --- a/apps/client/src/features/viewers/common/viewerUtils.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { TimerType } from 'ontime-types'; - -import type { ViewExtendedTimer } from '../../../common/models/TimeManager.type'; - -type TimerTypeParams = Pick; - -export function getTimerByType(timerObject?: TimerTypeParams): number | null { - if (!timerObject) { - return null; - } - - switch (timerObject.timerType) { - case TimerType.CountDown: - case TimerType.TimeToEnd: - return timerObject.current; - case TimerType.CountUp: - return Math.abs(timerObject.elapsed ?? 0); - case TimerType.Clock: - return timerObject.clock; - default: { - const exhaustiveCheck: never = timerObject.timerType; - return exhaustiveCheck; - } - } -} diff --git a/apps/client/src/features/viewers/lower-thirds/LowerThird.scss b/apps/client/src/features/viewers/lower-thirds/LowerThird.scss index 603206d18..b17ffe078 100644 --- a/apps/client/src/features/viewers/lower-thirds/LowerThird.scss +++ b/apps/client/src/features/viewers/lower-thirds/LowerThird.scss @@ -69,7 +69,7 @@ .data-bottom, .data-top { padding: 0 3vw; - font-family: var(--lowerThird-font-family-override, 'Lato'); + font-family: var(--lowerThird-font-family-override), Lato, Arial, sans-serif; text-align: var(--lowerThird-text-align-override, left); white-space: nowrap; } diff --git a/apps/client/src/features/viewers/lower-thirds/LowerThird.tsx b/apps/client/src/features/viewers/lower-thirds/LowerThird.tsx index 56679ab76..b20e05b83 100644 --- a/apps/client/src/features/viewers/lower-thirds/LowerThird.tsx +++ b/apps/client/src/features/viewers/lower-thirds/LowerThird.tsx @@ -1,22 +1,16 @@ import { useEffect, useMemo, useState } from 'react'; import { useSearchParams } from 'react-router-dom'; -import { Message, OntimeEvent, ViewSettings } from 'ontime-types'; +import { CustomFields, Message, OntimeEvent, ViewSettings } from 'ontime-types'; import { overrideStylesURL } from '../../../common/api/apiConstants'; import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu'; -import { LOWER_THIRD_OPTIONS } from '../../../common/components/view-params-editor/constants'; +import { getLowerThirdOptions } from '../../../common/components/view-params-editor/constants'; import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor'; import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet'; +import { getPropertyValue } from '../common/viewUtils'; import './LowerThird.scss'; -enum SrcKeys { - Title = 'title', - Subtitle = 'subtitle', - Presenter = 'presenter', - LowerMsg = 'lowerMsg', -} - enum TriggerType { Event = 'event', Manual = 'manual', @@ -25,8 +19,8 @@ enum TriggerType { type LowerOptions = { width: number; trigger: TriggerType; - topSrc: SrcKeys; - bottomSrc: SrcKeys; + topSrc: string; + bottomSrc: string; topColour: string; bottomColour: string; topBg: string; @@ -41,6 +35,7 @@ type LowerOptions = { }; interface LowerProps { + customFields: CustomFields; eventNow: OntimeEvent | null; viewSettings: ViewSettings; lower: Message; @@ -49,8 +44,8 @@ interface LowerProps { const defaultOptions: Readonly = { width: 45, trigger: TriggerType.Event, - topSrc: SrcKeys.Title, - bottomSrc: SrcKeys.Subtitle, + topSrc: 'title', + bottomSrc: 'lowerMsg', topColour: '000000ff', bottomColour: '000000ff', topBg: '00000000', @@ -65,7 +60,7 @@ const defaultOptions: Readonly = { }; export default function LowerThird(props: LowerProps) { - const { eventNow, lower, viewSettings } = props; + const { customFields, eventNow, lower, viewSettings } = props; const [searchParams] = useSearchParams(); const options = useMemo(() => { @@ -81,12 +76,12 @@ export default function LowerThird(props: LowerProps) { newOptions.trigger = trigger; } - const topSrc = Object.values(SrcKeys).find((s) => s === searchParams.get('top-src')); + const topSrc = searchParams.get('top-src'); if (topSrc) { newOptions.topSrc = topSrc; } - const bottomSrc = Object.values(SrcKeys).find((s) => s === searchParams.get('bottom-src')); + const bottomSrc = searchParams.get('bottom-src'); if (bottomSrc) { newOptions.bottomSrc = bottomSrc; } @@ -144,34 +139,14 @@ export default function LowerThird(props: LowerProps) { return newOptions; }, [searchParams]); + const [playState, setPlayState] = useState<'pre' | 'in' | 'out'>('pre'); useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL); - const [playState, setPlayState] = useState<'pre' | 'in' | 'out'>('pre'); - + // set window title useEffect(() => { document.title = 'ontime - Lower Third'; }, []); - const topText = useMemo(() => { - if (options.topSrc === SrcKeys.LowerMsg) { - return lower.text; - } else if (eventNow) { - return eventNow[options.topSrc]; - } - return ''; - }, [eventNow, lower.text, options]); - - const bottomText = useMemo(() => { - if (options.bottomSrc === SrcKeys.LowerMsg) { - return lower.text; - } else if (eventNow) { - return eventNow[options.bottomSrc]; - } - return ''; - }, [eventNow, lower.text, options]); - - const transition = `${options.transition}s`; - const trigger = useMemo(() => { if (options.trigger === TriggerType.Event) { return eventNow?.id; @@ -181,6 +156,7 @@ export default function LowerThird(props: LowerProps) { return false; }, [eventNow?.id, lower.visible, options.trigger]); + // coordinate load-unload of lower third useEffect(() => { if (options.trigger === TriggerType.Event && trigger) { setPlayState('in'); @@ -197,10 +173,26 @@ export default function LowerThird(props: LowerProps) { return () => null; }, [options.delay, options.transition, options.trigger, trigger]); + const topText = useMemo(() => { + if (options.topSrc === 'lowerMsg') { + return lower.text; + } + return getPropertyValue(eventNow, options.topSrc) ?? ''; + }, [eventNow, lower.text, options]); + + const bottomText = useMemo(() => { + if (options.bottomSrc === 'lowerMsg') { + return lower.text; + } + return getPropertyValue(eventNow, options.bottomSrc) ?? ''; + }, [eventNow, lower.text, options]); + + const transition = `${options.transition}s`; + return (
- +
{ document.title = 'ontime - Public Screen'; }, []); @@ -64,7 +70,11 @@ export default function Public(props: BackstageProps) { const qrSize = Math.max(window.innerWidth / 15, 128); const defaultFormat = getDefaultFormat(settings?.timeFormat); - const publicOptions = getPublicOptions(defaultFormat); + const publicOptions = getPublicOptions(defaultFormat, customFields); + + const secondarySource = searchParams.get('secondary-src'); + const secondaryTextNext = getPropertyValue(publicEventNext, secondarySource); + const secondaryTextNow = getPropertyValue(publicEventNow, secondarySource); return (
@@ -89,12 +99,7 @@ export default function Public(props: BackstageProps) { animate='visible' exit='exit' > - + )} @@ -109,12 +114,7 @@ export default function Public(props: BackstageProps) { animate='visible' exit='exit' > - + )} diff --git a/apps/client/src/features/viewers/studio/StudioClock.tsx b/apps/client/src/features/viewers/studio/StudioClock.tsx index 34e8c8c8e..29a1ee46e 100644 --- a/apps/client/src/features/viewers/studio/StudioClock.tsx +++ b/apps/client/src/features/viewers/studio/StudioClock.tsx @@ -12,8 +12,8 @@ import useFitText from '../../../common/hooks/useFitText'; import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet'; import { ViewExtendedTimer } from '../../../common/models/TimeManager.type'; import { formatTime, getDefaultFormat } from '../../../common/utils/time'; -import { isStringBoolean } from '../../../common/utils/viewUtils'; import SuperscriptTime from '../common/superscript-time/SuperscriptTime'; +import { isStringBoolean } from '../common/viewUtils'; import { secondsInMillis, trimRundown } from './studioClock.utils'; diff --git a/apps/client/src/features/viewers/timer/Timer.tsx b/apps/client/src/features/viewers/timer/Timer.tsx index 62d749f4f..365cb9767 100644 --- a/apps/client/src/features/viewers/timer/Timer.tsx +++ b/apps/client/src/features/viewers/timer/Timer.tsx @@ -1,7 +1,16 @@ import { useEffect } from 'react'; import { useSearchParams } from 'react-router-dom'; import { AnimatePresence, motion } from 'framer-motion'; -import { Message, OntimeEvent, Playback, Settings, TimerMessage, TimerType, ViewSettings } from 'ontime-types'; +import { + CustomFields, + Message, + OntimeEvent, + Playback, + Settings, + TimerMessage, + TimerType, + ViewSettings, +} from 'ontime-types'; import { millisToString, removeLeadingZero, removeSeconds } from 'ontime-utils'; import { overrideStylesURL } from '../../../common/api/apiConstants'; @@ -14,10 +23,9 @@ import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet import { ViewExtendedTimer } from '../../../common/models/TimeManager.type'; import { timerPlaceholder } from '../../../common/utils/styleUtils'; import { formatTime, getDefaultFormat } from '../../../common/utils/time'; -import { isStringBoolean } from '../../../common/utils/viewUtils'; import { useTranslation } from '../../../translation/TranslationProvider'; import SuperscriptTime from '../common/superscript-time/SuperscriptTime'; -import { getTimerByType } from '../common/viewerUtils'; +import { getPropertyValue, getTimerByType, isStringBoolean } from '../common/viewUtils'; import './Timer.scss'; @@ -38,18 +46,19 @@ const titleVariants = { }; interface TimerProps { + customFields: CustomFields; + eventNext: OntimeEvent | null; + eventNow: OntimeEvent | null; + external: Message; isMirrored: boolean; pres: TimerMessage; - external: Message; - eventNow: OntimeEvent | null; - eventNext: OntimeEvent | null; + settings: Settings | undefined; time: ViewExtendedTimer; viewSettings: ViewSettings; - settings: Settings | undefined; } export default function Timer(props: TimerProps) { - const { isMirrored, pres, eventNow, eventNext, time, viewSettings, external, settings } = props; + const { customFields, isMirrored, pres, eventNow, eventNext, time, viewSettings, external, settings } = props; const { shouldRender } = useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL); const { getLocalizedString } = useTranslation(); const [searchParams] = useSearchParams(); @@ -92,6 +101,10 @@ export default function Timer(props: TimerProps) { const hideTimerSeconds = searchParams.get('hideTimerSeconds'); userOptions.hideTimerSeconds = isStringBoolean(hideTimerSeconds); + const secondarySource = searchParams.get('secondary-src'); + const secondaryTextNow = getPropertyValue(eventNow, secondarySource); + const secondaryTextNext = getPropertyValue(eventNext, secondarySource); + const showOverlay = pres.text !== '' && pres.visible; const isPlaying = time.playback !== Playback.Pause; @@ -143,7 +156,7 @@ export default function Timer(props: TimerProps) { const timerClasses = `timer ${!isPlaying ? 'timer--paused' : ''} ${showFinished ? 'timer--finished' : ''}`; const defaultFormat = getDefaultFormat(settings?.timeFormat); - const timerOptions = getTimerOptions(defaultFormat); + const timerOptions = getTimerOptions(defaultFormat, customFields); return (
@@ -201,7 +214,7 @@ export default function Timer(props: TimerProps) { {!userOptions.hideCards && ( <> - {eventNow && !finished && ( + {eventNow && ( - + )} @@ -230,12 +238,7 @@ export default function Timer(props: TimerProps) { animate='visible' exit='exit' > - + )} diff --git a/apps/server/src/controllers/integrationController.config.ts b/apps/server/src/controllers/integrationController.config.ts index f03226fb0..a1e954b36 100644 --- a/apps/server/src/controllers/integrationController.config.ts +++ b/apps/server/src/controllers/integrationController.config.ts @@ -6,8 +6,6 @@ import { coerceString, coerceNumber, coerceBoolean, coerceColour } from '../util // TODO: handle custom fields const whitelistedPayload = { title: coerceString, - subtitle: coerceString, - presenter: coerceString, note: coerceString, cue: coerceString, diff --git a/apps/server/src/models/eventsDefinition.ts b/apps/server/src/models/eventsDefinition.ts index 654e448e6..4760d7c3c 100644 --- a/apps/server/src/models/eventsDefinition.ts +++ b/apps/server/src/models/eventsDefinition.ts @@ -10,8 +10,6 @@ import { export const event: Omit = { title: '', - subtitle: '', - presenter: '', note: '', endAction: EndAction.None, timerType: TimerType.CountDown, diff --git a/apps/server/src/services/rundown-service/__tests__/rundownCache.test.ts b/apps/server/src/services/rundown-service/__tests__/rundownCache.test.ts index 1d86c7fd9..04888ec37 100644 --- a/apps/server/src/services/rundown-service/__tests__/rundownCache.test.ts +++ b/apps/server/src/services/rundown-service/__tests__/rundownCache.test.ts @@ -395,8 +395,6 @@ describe('calculateRuntimeDelays', () => { const rundown: OntimeRundown = [ { title: '', - subtitle: '', - presenter: '', note: '', endAction: EndAction.None, timerType: TimerType.CountDown, @@ -423,8 +421,6 @@ describe('calculateRuntimeDelays', () => { }, { title: '', - subtitle: '', - presenter: '', note: '', endAction: EndAction.None, timerType: TimerType.CountDown, @@ -451,8 +447,6 @@ describe('calculateRuntimeDelays', () => { }, { title: '', - subtitle: '', - presenter: '', note: '', endAction: EndAction.None, timerType: TimerType.CountDown, @@ -479,8 +473,6 @@ describe('calculateRuntimeDelays', () => { }, { title: '', - subtitle: '', - presenter: '', note: '', endAction: EndAction.None, timerType: TimerType.CountDown, @@ -516,8 +508,6 @@ describe('getDelayAt()', () => { const delayedRundown: OntimeRundown = [ { title: '', - subtitle: '', - presenter: '', note: '', endAction: EndAction.None, timerType: TimerType.CountDown, @@ -545,8 +535,6 @@ describe('getDelayAt()', () => { }, { title: '', - subtitle: '', - presenter: '', note: '', endAction: EndAction.None, timerType: TimerType.CountDown, @@ -574,8 +562,6 @@ describe('getDelayAt()', () => { }, { title: '', - subtitle: '', - presenter: '', note: '', endAction: EndAction.None, timerType: TimerType.CountDown, @@ -603,8 +589,6 @@ describe('getDelayAt()', () => { }, { title: '', - subtitle: '', - presenter: '', note: '', endAction: EndAction.None, timerType: TimerType.CountDown, @@ -658,8 +642,6 @@ describe('calculateRuntimeDelaysFrom()', () => { const delayedRundown: OntimeRundown = [ { title: '', - subtitle: '', - presenter: '', note: '', endAction: EndAction.None, timerType: TimerType.CountDown, @@ -687,8 +669,6 @@ describe('calculateRuntimeDelaysFrom()', () => { }, { title: '', - subtitle: '', - presenter: '', note: '', endAction: EndAction.None, timerType: TimerType.CountDown, @@ -716,8 +696,6 @@ describe('calculateRuntimeDelaysFrom()', () => { }, { title: '', - subtitle: '', - presenter: '', note: '', endAction: EndAction.None, timerType: TimerType.CountDown, @@ -745,8 +723,6 @@ describe('calculateRuntimeDelaysFrom()', () => { }, { title: '', - subtitle: '', - presenter: '', note: '', endAction: EndAction.None, timerType: TimerType.CountDown, diff --git a/apps/server/src/services/sheet-service/__tests__/sheetUtils.test.ts b/apps/server/src/services/sheet-service/__tests__/sheetUtils.test.ts index 109d6b2b3..be6d77233 100644 --- a/apps/server/src/services/sheet-service/__tests__/sheetUtils.test.ts +++ b/apps/server/src/services/sheet-service/__tests__/sheetUtils.test.ts @@ -24,8 +24,6 @@ describe('cellRequestFromEvent()', () => { type: SupportedEvent.Event, cue: '1', title: 'Fancy', - subtitle: 'Wow', - presenter: 'Mr. Presenter', note: 'Blue button on the right', timeStart: 46800000, timeEnd: 57600000, @@ -47,8 +45,6 @@ describe('cellRequestFromEvent()', () => { type: { row: 1, col: 14 }, cue: { row: 1, col: 15 }, title: { row: 1, col: 16 }, - subtitle: { row: 1, col: 17 }, - presenter: { row: 1, col: 18 }, note: { row: 1, col: 19 }, timeStart: { row: 1, col: 20 }, timeEnd: { row: 1, col: 21 }, @@ -72,8 +68,6 @@ describe('cellRequestFromEvent()', () => { type: SupportedEvent.Event, cue: '1', title: 'Fancy', - subtitle: 'Wow', - presenter: 'Mr. Presenter', note: 'Blue button on the right', timeStart: 46800000, timeEnd: 57600000, @@ -95,8 +89,6 @@ describe('cellRequestFromEvent()', () => { type: { row: 1, col: 14 }, cue: { row: 1, col: 15 }, title: { row: 1, col: 16 }, - subtitle: { row: 1, col: 17 }, - presenter: { row: 1, col: 18 }, note: { row: 1, col: 19 }, timeStart: { row: 1, col: 20 }, timeEnd: { row: 1, col: 21 }, @@ -121,8 +113,6 @@ describe('cellRequestFromEvent()', () => { type: SupportedEvent.Event, cue: '1', title: 'Fancy', - subtitle: 'Wow', - presenter: 'Mr. Presenter', note: 'Blue button on the right', timeStart: 46800000, timeEnd: 57600000, @@ -144,8 +134,6 @@ describe('cellRequestFromEvent()', () => { type: { row: 1, col: 14 }, cue: { row: 1, col: 15 }, title: { row: 1, col: 16 }, - subtitle: { row: 1, col: 17 }, - presenter: { row: 1, col: 18 }, note: { row: 1, col: 19 }, timeStart: { row: 1, col: 20 }, timeEnd: { row: 1, col: 21 }, @@ -170,8 +158,6 @@ describe('cellRequestFromEvent()', () => { type: SupportedEvent.Event, cue: '1', title: 'Fancy', - subtitle: 'Wow', - presenter: 'Mr. Presenter', note: 'Blue button on the right', timeStart: 46800000, timeEnd: 57600000, @@ -192,12 +178,10 @@ describe('cellRequestFromEvent()', () => { const metadata = { cue: { row: 1, col: 0 }, title: { row: 1, col: 6 }, - subtitle: { row: 1, col: 10 }, }; const result = cellRequestFromEvent(event, 1, 1234, metadata); expect(result.updateCells.rows[0].values[0].userEnteredValue.stringValue).toStrictEqual(event.cue); expect(result.updateCells.rows[0].values[6].userEnteredValue.stringValue).toStrictEqual(event.title); - expect(result.updateCells.rows[0].values[10].userEnteredValue.stringValue).toStrictEqual(event.subtitle); }); test('metadata offset from zero', () => { @@ -205,8 +189,6 @@ describe('cellRequestFromEvent()', () => { type: SupportedEvent.Event, cue: '1', title: 'Fancy', - subtitle: 'Wow', - presenter: 'Mr. Presenter', note: 'Blue button on the right', timeStart: 46800000, timeEnd: 57600000, @@ -227,13 +209,11 @@ describe('cellRequestFromEvent()', () => { const metadata = { cue: { row: 1, col: 5 }, title: { row: 1, col: 6 }, - subtitle: { row: 1, col: 10 }, user0: { row: 1, col: 16 }, }; const result = cellRequestFromEvent(event, 1, 1234, metadata); expect(result.updateCells.rows[0].values[0].userEnteredValue.stringValue).toStrictEqual(event.cue); expect(result.updateCells.rows[0].values[1].userEnteredValue.stringValue).toStrictEqual(event.title); - expect(result.updateCells.rows[0].values[5].userEnteredValue.stringValue).toStrictEqual(event.subtitle); }); test('sheet setup', () => { @@ -241,8 +221,6 @@ describe('cellRequestFromEvent()', () => { type: SupportedEvent.Event, cue: '1', title: 'Fancy', - subtitle: 'Wow', - presenter: 'Mr. Presenter', note: 'Blue button on the right', timeStart: 46800000, timeEnd: 57600000, @@ -263,7 +241,6 @@ describe('cellRequestFromEvent()', () => { const metadata = { cue: { row: 10, col: 5 }, title: { row: 10, col: 6 }, - subtitle: { row: 1, col: 10 }, }; const result1 = cellRequestFromEvent(event, 1, 1234, metadata); expect(result1.updateCells.start.sheetId).toStrictEqual(1234); diff --git a/apps/server/src/utils/__tests__/parser.test.ts b/apps/server/src/utils/__tests__/parser.test.ts index 28bedf1c9..39be45952 100644 --- a/apps/server/src/utils/__tests__/parser.test.ts +++ b/apps/server/src/utils/__tests__/parser.test.ts @@ -28,8 +28,6 @@ describe('test json parser with valid def', () => { cue: 'Guest Welcoming', type: SupportedEvent.Event, title: 'Guest Welcoming', - subtitle: '', - presenter: '', note: '', endAction: EndAction.PlayNext, timerType: TimerType.Clock, @@ -51,8 +49,6 @@ describe('test json parser with valid def', () => { cue: 'Good Morning', type: SupportedEvent.Event, title: 'Good Morning', - subtitle: 'Days schedule', - presenter: 'Carlos Valente', note: '', endAction: EndAction.PlayNext, timerType: TimerType.CountUp, @@ -74,8 +70,6 @@ describe('test json parser with valid def', () => { cue: 'Stage 2 setup', type: SupportedEvent.Event, title: 'Stage 2 setup', - subtitle: '', - presenter: '', note: '', endAction: 'wrong action' as EndAction, // testing timerType: TimerType.Clock, @@ -98,8 +92,6 @@ describe('test json parser with valid def', () => { cue: 'Working Procedures', type: SupportedEvent.Event, title: 'Working Procedures', - subtitle: '', - presenter: 'Filip Johansen', note: '', endAction: EndAction.None, timerType: TimerType.Clock, @@ -118,8 +110,6 @@ describe('test json parser with valid def', () => { cue: 'Lunch', title: 'Lunch', type: SupportedEvent.Event, - subtitle: '', - presenter: '', note: '', endAction: EndAction.None, timerType: TimerType.Clock, @@ -141,8 +131,6 @@ describe('test json parser with valid def', () => { cue: 'A day being carlos', title: 'A day being carlos', type: SupportedEvent.Event, - subtitle: 'My life in a song', - presenter: 'Carlos Valente', note: '', endAction: EndAction.None, timerType: TimerType.Clock, @@ -165,8 +153,6 @@ describe('test json parser with valid def', () => { cue: 'Hamburgers and Cheese', title: 'Hamburgers and Cheese', type: SupportedEvent.Event, - subtitle: '... and other life questions', - presenter: 'Filip Johansen', note: '', endAction: EndAction.None, timerType: TimerType.Clock, @@ -470,8 +456,6 @@ describe('test event validator', () => { expect(validated).toEqual( expect.objectContaining({ title: expect.any(String), - subtitle: expect.any(String), - presenter: expect.any(String), note: expect.any(String), timeStart: expect.any(Number), timeEnd: expect.any(Number), @@ -496,15 +480,11 @@ describe('test event validator', () => { it('makes objects strings', () => { const event = { title: 2, - subtitle: true, - presenter: 3.2, note: '1899-12-30T08:00:10.000Z', }; // @ts-expect-error -- we know this is wrong, testing imports outside domain const validated = createEvent(event, 'not-used'); expect(typeof validated.title).toEqual('string'); - expect(typeof validated.subtitle).toEqual('string'); - expect(typeof validated.presenter).toEqual('string'); expect(typeof validated.note).toEqual('string'); }); @@ -697,8 +677,6 @@ describe('test import of v2 datamodel', () => { id: expect.any(String), cue: expect.any(String), title: expect.any(String), - subtitle: expect.any(String), - presenter: expect.any(String), note: expect.any(String), endAction: expect.any(String), timerType: expect.any(String), @@ -757,8 +735,6 @@ describe('getCustomFieldData()', () => { duration: 'duration', cue: 'cue', title: 'title', - presenter: 'presenter', - subtitle: 'subtitle', isPublic: 'public', skip: 'skip', note: 'notes', @@ -811,8 +787,6 @@ describe('parseExcel()', () => { 'Time Start', 'Time End', 'Title', - 'Presenter', - 'Subtitle', 'End Action', 'Timer type', 'Public', @@ -835,8 +809,6 @@ describe('parseExcel()', () => { '1899-12-30T07:00:00.000Z', '1899-12-30T08:00:10.000Z', 'Guest Welcome', - 'Carlos', - 'Getting things started', '', '', 'x', @@ -859,8 +831,6 @@ describe('parseExcel()', () => { '1899-12-30T08:00:00.000Z', '1899-12-30T08:30:00.000Z', 'A song from the hearth', - 'Still Carlos', - 'Derailing early', 'load-next', 'clock', '', @@ -904,8 +874,6 @@ describe('parseExcel()', () => { //timeStart: 28800000, //timeEnd: 32410000, title: 'Guest Welcome', - presenter: 'Carlos', - subtitle: 'Getting things started', timerType: 'count-down', endAction: 'none', isPublic: true, @@ -931,8 +899,6 @@ describe('parseExcel()', () => { //timeStart: 32400000, //timeEnd: 34200000, title: 'A song from the hearth', - presenter: 'Still Carlos', - subtitle: 'Derailing early', timerType: 'clock', endAction: 'load-next', isPublic: false, @@ -1014,8 +980,6 @@ describe('parseExcel()', () => { 'Time Start', 'Time End', 'Title', - 'Presenter', - 'Subtitle', 'End Action', 'Timer type', 'Public', @@ -1038,8 +1002,6 @@ describe('parseExcel()', () => { '1899-12-30T07:00:00.000Z', '1899-12-30T08:00:10.000Z', 'Guest Welcome', - 'Carlos', - 'Getting things started', '', '', 'x', @@ -1062,8 +1024,6 @@ describe('parseExcel()', () => { '1899-12-30T08:00:00.000Z', '1899-12-30T08:30:00.000Z', 'A song from the hearth', - 'Still Carlos', - 'Derailing early', 'load-next', 'clock', '', @@ -1099,8 +1059,6 @@ describe('parseExcel()', () => { //timeStart: 28800000, //timeEnd: 32410000, title: 'Guest Welcome', - presenter: 'Carlos', - subtitle: 'Getting things started', timerType: 'count-down', endAction: 'none', isPublic: true, @@ -1115,8 +1073,6 @@ describe('parseExcel()', () => { //timeStart: 32400000, //timeEnd: 34200000, title: 'A song from the hearth', - presenter: 'Still Carlos', - subtitle: 'Derailing early', timerType: 'clock', endAction: 'load-next', isPublic: false, diff --git a/apps/server/src/utils/parser.ts b/apps/server/src/utils/parser.ts index 7f8998514..00438fd1f 100644 --- a/apps/server/src/utils/parser.ts +++ b/apps/server/src/utils/parser.ts @@ -91,8 +91,6 @@ export const parseExcel = (excelData: unknown[][], options?: Partial) // title stuff: strings let titleIndex: number | null = null; let cueIndex: number | null = null; - let presenterIndex: number | null = null; - let subtitleIndex: number | null = null; let notesIndex: number | null = null; let colourIndex: number | null = null; @@ -142,14 +140,6 @@ export const parseExcel = (excelData: unknown[][], options?: Partial) titleIndex = col; rundownMetadata['title'] = { row, col }; }, - [importMap.presenter]: (row: number, col: number) => { - presenterIndex = col; - rundownMetadata['presenter'] = { row, col }; - }, - [importMap.subtitle]: (row: number, col: number) => { - subtitleIndex = col; - rundownMetadata['subtitle'] = { row, col }; - }, [importMap.isPublic]: (row: number, col: number) => { isPublicIndex = col; rundownMetadata['isPublic'] = { row, col }; @@ -217,10 +207,6 @@ export const parseExcel = (excelData: unknown[][], options?: Partial) event.duration = parseExcelDate(column); } else if (j === cueIndex) { event.cue = makeString(column, ''); - } else if (j === presenterIndex) { - event.presenter = makeString(column, ''); - } else if (j === subtitleIndex) { - event.subtitle = makeString(column, ''); } else if (j === isPublicIndex) { event.isPublic = column == 'x' ? true : coerceBoolean(column); } else if (j === skipIndex) { @@ -337,8 +323,6 @@ export function createPatch(originalEvent: OntimeEvent, patchEvent: Partial { 'Colour', 'Cue', 'Title', - 'Subtitle', - 'Presenter', 'Note', 'Is Public? (x)', 'Skip?', diff --git a/e2e/tests/fixtures/test-db.json b/e2e/tests/fixtures/test-db.json index d46b129d3..500ead203 100644 --- a/e2e/tests/fixtures/test-db.json +++ b/e2e/tests/fixtures/test-db.json @@ -2,8 +2,6 @@ "rundown": [ { "title": "Albania", - "subtitle": "Sekret", - "presenter": "Ronela Hajati", "note": "SF1.01", "endAction": "none", "timerType": "count-down", @@ -17,12 +15,13 @@ "revision": 0, "id": "32d31", "cue": "SF1.01", - "custom": {} + "custom": { + "song": "Sekret", + "artist": "Ronela Hajati" + } }, { "title": "Latvia", - "subtitle": "Eat Your Salad", - "presenter": "Citi Zeni", "note": "SF1.02", "endAction": "none", "timerType": "count-down", @@ -36,12 +35,13 @@ "revision": 0, "id": "21cd2", "cue": "SF1.02", - "custom": {} + "custom": { + "song": "Eat Your Salad", + "artist": "Citi Zeni" + } }, { "title": "Lithuania", - "subtitle": "Sentimentai", - "presenter": "Monika Liu", "note": "SF1.03", "endAction": "none", "timerType": "count-down", @@ -55,12 +55,13 @@ "revision": 0, "id": "0b371", "cue": "SF1.03", - "custom": {} + "custom": { + "song": "Sentimentai", + "artist": "Monika Liu" + } }, { "title": "Switzerland", - "subtitle": "Boys Do Cry", - "presenter": "Marius Bear", "note": "SF1.04", "endAction": "none", "timerType": "count-down", @@ -74,12 +75,13 @@ "revision": 0, "id": "3cd28", "cue": "SF1.04", - "custom": {} + "custom": { + "song": "Boys Do Cry", + "artist": "Marius Bear" + } }, { "title": "Slovenia", - "subtitle": "Disko", - "presenter": "LPS", "note": "SF1.05", "endAction": "none", "timerType": "count-down", @@ -93,7 +95,10 @@ "revision": 0, "id": "e457f", "cue": "SF1.05", - "custom": {} + "custom": { + "song": "Disko", + "artist": "LPS" + } }, { "title": "Lunch break", @@ -102,8 +107,6 @@ }, { "title": "Ukraine", - "subtitle": "Stefania", - "presenter": "Kalush Orchestra", "note": "SF1.06", "endAction": "none", "timerType": "count-down", @@ -117,12 +120,13 @@ "revision": 0, "id": "1c420", "cue": "SF1.06", - "custom": {} + "custom": { + "song": "Stefania", + "artist": "Kalush Orchestra" + } }, { "title": "Bulgaria", - "subtitle": "Intention", - "presenter": "Intelligent Music Project", "note": "SF1.07", "endAction": "none", "timerType": "count-down", @@ -136,12 +140,13 @@ "revision": 0, "id": "b7737", "cue": "SF1.07", - "custom": {} + "custom": { + "song": "Intention", + "artist": "Intelligent Music Project" + } }, { "title": "Netherlands", - "subtitle": "De Diepte", - "presenter": "S10", "note": "SF1.08", "endAction": "none", "timerType": "count-down", @@ -155,12 +160,13 @@ "revision": 0, "id": "d3a80", "cue": "SF1.08", - "custom": {} + "custom": { + "song": "De Diepte", + "artist": "S10" + } }, { "title": "Moldova", - "subtitle": "Trenuletul", - "presenter": "Zdob si Zdub", "note": "SF1.09", "endAction": "none", "timerType": "count-down", @@ -174,12 +180,13 @@ "revision": 0, "id": "8276c", "cue": "SF1.09", - "custom": {} + "custom": { + "song": "Trenuletul", + "artist": "Zdob si Zdub" + } }, { "title": "Portugal", - "subtitle": "Saudade Saudade", - "presenter": "Maro", "note": "SF1.10", "endAction": "none", "timerType": "count-down", @@ -193,7 +200,10 @@ "revision": 0, "id": "2340b", "cue": "SF1.10", - "custom": {} + "custom": { + "song": "Saudade Saudade", + "artist": "Maro" + } }, { "title": "Afternoon break", @@ -202,8 +212,6 @@ }, { "title": "Croatia", - "subtitle": "Guilty Pleasure", - "presenter": "Mia Dimsic", "note": "SF1.11", "endAction": "none", "timerType": "count-down", @@ -217,12 +225,13 @@ "revision": 0, "id": "503c4", "cue": "SF1.11", - "custom": {} + "custom": { + "song": "Guilty Pleasure", + "artist": "Mia Dimsic" + } }, { "title": "Denmark", - "subtitle": "The Show", - "presenter": "Reddi", "note": "SF1.12", "endAction": "none", "timerType": "count-down", @@ -236,12 +245,13 @@ "revision": 0, "id": "5e965", "cue": "SF1.12", - "custom": {} + "custom": { + "song": "The Show", + "artist": "Reddi" + } }, { "title": "Austria", - "subtitle": "Halo", - "presenter": "LUM!X & Pia Maria", "note": "SF1.13", "endAction": "none", "timerType": "count-down", @@ -255,12 +265,13 @@ "revision": 0, "id": "bab4a", "cue": "SF1.13", - "custom": {} + "custom": { + "song": "Halo", + "artist": "LUM!X & Pia Maria" + } }, { "title": "Greece", - "subtitle": "Die Together", - "presenter": "Amanda Tenfjord", "note": "SF1.14", "endAction": "none", "timerType": "count-down", @@ -274,7 +285,10 @@ "revision": 0, "id": "d3eb1", "cue": "SF1.14", - "custom": {} + "custom": { + "song": "Die Together", + "artist": "Amanda Tenfjord" + } } ], "project": { diff --git a/packages/types/src/definitions/core/OntimeEvent.type.ts b/packages/types/src/definitions/core/OntimeEvent.type.ts index 0eb9293d8..601d7e60a 100644 --- a/packages/types/src/definitions/core/OntimeEvent.type.ts +++ b/packages/types/src/definitions/core/OntimeEvent.type.ts @@ -26,8 +26,6 @@ export type OntimeEvent = OntimeBaseEvent & { type: SupportedEvent.Event; cue: string; title: string; - subtitle: string; - presenter: string; note: string; endAction: EndAction; timerType: TimerType; diff --git a/packages/utils/src/feature/spreadsheet-import/__tests__/spreadsheetImport.test.ts b/packages/utils/src/feature/spreadsheet-import/__tests__/spreadsheetImport.test.ts index 518b8e63d..157fd989a 100644 --- a/packages/utils/src/feature/spreadsheet-import/__tests__/spreadsheetImport.test.ts +++ b/packages/utils/src/feature/spreadsheet-import/__tests__/spreadsheetImport.test.ts @@ -9,8 +9,6 @@ describe('isImportMap()', () => { duration: 'duration', cue: 'cue', title: 'title', - presenter: 'presenter', - subtitle: 'subtitle', isPublic: 'public', skip: 'skip', note: 'notes', @@ -33,8 +31,6 @@ describe('isImportMap()', () => { duration: 'duration', cue: 'cue', title: 'title', - presenter: 'presenter', - subtitle: 'subtitle', isPublic: 'public', skip: 'skip', note: 'notes', diff --git a/packages/utils/src/feature/spreadsheet-import/spreadsheetImport.ts b/packages/utils/src/feature/spreadsheet-import/spreadsheetImport.ts index 82205dffc..8b6394f31 100644 --- a/packages/utils/src/feature/spreadsheet-import/spreadsheetImport.ts +++ b/packages/utils/src/feature/spreadsheet-import/spreadsheetImport.ts @@ -10,8 +10,6 @@ export const defaultImportMap = { duration: 'duration', cue: 'cue', title: 'title', - presenter: 'presenter', - subtitle: 'subtitle', isPublic: 'public', skip: 'skip', note: 'notes', diff --git a/test-db/db.json b/test-db/db.json new file mode 100644 index 000000000..1dd46eb8e --- /dev/null +++ b/test-db/db.json @@ -0,0 +1,41 @@ +{ + "rundown": [], + "project": { + "title": "", + "description": "", + "publicUrl": "", + "publicInfo": "", + "backstageUrl": "", + "backstageInfo": "" + }, + "settings": { + "app": "ontime", + "version": "3.0.0-alpha", + "serverPort": 4001, + "editorKey": null, + "operatorKey": null, + "timeFormat": "24", + "language": "en" + }, + "viewSettings": { + "overrideStyles": false, + "normalColor": "#ffffffcc", + "warningColor": "#FFAB33", + "dangerColor": "#ED3333", + "endMessage": "" + }, + "aliases": [], + "customFields": {}, + "osc": { + "portIn": 8888, + "portOut": 9999, + "targetIP": "127.0.0.1", + "enabledIn": false, + "enabledOut": false, + "subscriptions": [] + }, + "http": { + "enabledOut": false, + "subscriptions": [] + } +} \ No newline at end of file