diff --git a/apps/client/src/common/hooks/useSocket.ts b/apps/client/src/common/hooks/useSocket.ts index ee56a8b5ae..815b5a3921 100644 --- a/apps/client/src/common/hooks/useSocket.ts +++ b/apps/client/src/common/hooks/useSocket.ts @@ -65,6 +65,7 @@ export const useMessagePreview = () => { showExternalMessage: state.message.timer.secondarySource === 'external' && Boolean(state.message.external), showTimerMessage: state.message.timer.visible && Boolean(state.message.timer.text), timerType: state.eventNow?.timerType ?? null, + isTimeToEnd: state.eventNow?.isTimeToEnd ?? false, }); return useRuntimeStore(featureSelector); diff --git a/apps/client/src/common/models/TimeManager.type.ts b/apps/client/src/common/models/TimeManager.type.ts index 909dfb68c1..46bd672a5b 100644 --- a/apps/client/src/common/models/TimeManager.type.ts +++ b/apps/client/src/common/models/TimeManager.type.ts @@ -15,4 +15,5 @@ export type ViewExtendedTimer = { clock: number; timerType: TimerType; + isTimeToEnd: boolean; }; diff --git a/apps/client/src/common/utils/eventsManager.ts b/apps/client/src/common/utils/eventsManager.ts index 485648919f..eb0370b26a 100644 --- a/apps/client/src/common/utils/eventsManager.ts +++ b/apps/client/src/common/utils/eventsManager.ts @@ -17,6 +17,7 @@ export const cloneEvent = (event: OntimeEvent, after?: string): ClonedEvent => { timeEnd: event.timeEnd, timerType: event.timerType, timeStrategy: event.timeStrategy, + isTimeToEnd: event.isTimeToEnd, linkStart: event.linkStart, endAction: event.endAction, isPublic: event.isPublic, diff --git a/apps/client/src/features/app-settings/panel/interface-panel/EditorSettingsForm.tsx b/apps/client/src/features/app-settings/panel/interface-panel/EditorSettingsForm.tsx index e865ca67e7..f6bcd00f6e 100644 --- a/apps/client/src/features/app-settings/panel/interface-panel/EditorSettingsForm.tsx +++ b/apps/client/src/features/app-settings/panel/interface-panel/EditorSettingsForm.tsx @@ -88,7 +88,6 @@ export default function EditorSettingsForm() { > - diff --git a/apps/client/src/features/control/message/TimerPreview.tsx b/apps/client/src/features/control/message/TimerPreview.tsx index 4c3765ad55..9eaac95d5d 100644 --- a/apps/client/src/features/control/message/TimerPreview.tsx +++ b/apps/client/src/features/control/message/TimerPreview.tsx @@ -16,7 +16,7 @@ import { Corner } from '../../editors/editor-utils/EditorUtils'; import style from './MessageControl.module.scss'; export default function TimerPreview() { - const { blink, blackout, phase, showAuxTimer, showExternalMessage, showTimerMessage, timerType } = + const { blink, blackout, isTimeToEnd, phase, showAuxTimer, showExternalMessage, showTimerMessage, timerType } = useMessagePreview(); const { data } = useViewSettings(); @@ -24,11 +24,11 @@ export default function TimerPreview() { const main = (() => { if (showTimerMessage) return 'Message'; + if (timerType === TimerType.None) return timerPlaceholder; if (phase === TimerPhase.Pending) return 'Standby to start'; if (phase === TimerPhase.Overtime && data.endMessage) return 'Custom end message'; - if (timerType === TimerType.TimeToEnd) return 'Time to end'; if (timerType === TimerType.Clock) return 'Clock'; - if (timerType === TimerType.None) return timerPlaceholder; + if (isTimeToEnd) return 'Target event scheduled end'; return 'Timer'; })(); @@ -74,12 +74,16 @@ export default function TimerPreview() { - - - + + + ); diff --git a/apps/client/src/features/editors/editor-utils/EditorUtils.module.scss b/apps/client/src/features/editors/editor-utils/EditorUtils.module.scss index b2a4f075aa..cb6f1c45a3 100644 --- a/apps/client/src/features/editors/editor-utils/EditorUtils.module.scss +++ b/apps/client/src/features/editors/editor-utils/EditorUtils.module.scss @@ -27,7 +27,7 @@ .title { font-size: 1rem; - color: $label-gray; + color: $ui-white; } .label { diff --git a/apps/client/src/features/rundown/RundownEntry.tsx b/apps/client/src/features/rundown/RundownEntry.tsx index 5df99938c2..1166961f13 100644 --- a/apps/client/src/features/rundown/RundownEntry.tsx +++ b/apps/client/src/features/rundown/RundownEntry.tsx @@ -150,6 +150,7 @@ export default function RundownEntry(props: RundownEntryProps) { if (data.type === SupportedEvent.Event) { return ( { +function EventBlockInner(props: EventBlockInnerProps) { const { + eventId, timeStart, timeEnd, duration, timeStrategy, linkStart, - eventId, + isTimeToEnd, isPublic = true, endAction, timerType, @@ -91,7 +93,7 @@ const EventBlockInner = (props: EventBlockInnerProps) => { delay={delay} timeStrategy={timeStrategy} linkStart={linkStart} - timerType={timerType} + isTimeToEnd={isTimeToEnd} />
@@ -122,6 +124,11 @@ const EventBlockInner = (props: EventBlockInnerProps) => { + + + + + @@ -131,7 +138,7 @@ const EventBlockInner = (props: EventBlockInnerProps) => {
); -}; +} export default memo(EventBlockInner); @@ -162,9 +169,5 @@ function TimerIcon(props: { type: TimerType; className: string }) { if (type === TimerType.None) { return ; } - if (type === TimerType.TimeToEnd) { - const classes = cx([style.active, className]); - return ; - } return ; } diff --git a/apps/client/src/features/rundown/event-editor/EventEditor.module.scss b/apps/client/src/features/rundown/event-editor/EventEditor.module.scss index 99d5b4edc7..7f9b8469bd 100644 --- a/apps/client/src/features/rundown/event-editor/EventEditor.module.scss +++ b/apps/client/src/features/rundown/event-editor/EventEditor.module.scss @@ -16,7 +16,7 @@ flex: 1; display: flex; flex-direction: column; - gap: 2rem; + gap: 1rem; overflow-y: auto; } @@ -38,6 +38,7 @@ display: flex; flex-direction: column; gap: 1rem; + margin-top: 0.5rem; } .decorated { @@ -63,6 +64,7 @@ gap: 0.5rem; max-width: max-content; cursor: pointer; + height: 30px; // manually match the height of a text input } .inline { diff --git a/apps/client/src/features/rundown/event-editor/EventEditor.tsx b/apps/client/src/features/rundown/event-editor/EventEditor.tsx index 7cb1806698..15f8efa3fe 100644 --- a/apps/client/src/features/rundown/event-editor/EventEditor.tsx +++ b/apps/client/src/features/rundown/event-editor/EventEditor.tsx @@ -83,6 +83,7 @@ export default function EventEditor() { duration={event.duration} timeStrategy={event.timeStrategy} linkStart={event.linkStart} + isTimeToEnd={event.isTimeToEnd} delay={event.delay ?? 0} isPublic={event.isPublic} endAction={event.endAction} 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 671db18830..6a5e6e8d5a 100644 --- a/apps/client/src/features/rundown/event-editor/composite/EventEditorTimes.tsx +++ b/apps/client/src/features/rundown/event-editor/composite/EventEditorTimes.tsx @@ -18,6 +18,7 @@ interface EventEditorTimesProps { duration: number; timeStrategy: TimeStrategy; linkStart: MaybeString; + isTimeToEnd: boolean; delay: number; isPublic: boolean; endAction: EndAction; @@ -26,9 +27,9 @@ interface EventEditorTimesProps { timeDanger: number; } -type HandledActions = 'timerType' | 'endAction' | 'isPublic' | 'timeWarning' | 'timeDanger'; +type HandledActions = 'isTimeToEnd' | 'timerType' | 'endAction' | 'isPublic' | 'timeWarning' | 'timeDanger'; -const EventEditorTimes = (props: EventEditorTimesProps) => { +function EventEditorTimes(props: EventEditorTimesProps) { const { eventId, timeStart, @@ -36,6 +37,7 @@ const EventEditorTimes = (props: EventEditorTimesProps) => { duration, timeStrategy, linkStart, + isTimeToEnd, delay, isPublic, endAction, @@ -51,6 +53,11 @@ const EventEditorTimes = (props: EventEditorTimesProps) => { return; } + if (field === 'isTimeToEnd') { + updateEvent({ id: eventId, isTimeToEnd: !(value as boolean) }); + return; + } + if (field === 'timeWarning' || field === 'timeDanger') { const newTime = parseUserTime(value as string); updateEvent({ id: eventId, [field]: newTime }); @@ -71,95 +78,115 @@ const EventEditorTimes = (props: EventEditorTimesProps) => { : ''; return ( -
-
- Event schedule -
- -
-
{delayLabel}
-
- -
-
- Warning Time - -
-
- Timer Type - -
+ <> +
- Danger Time - + Event schedule +
+ +
+
{delayLabel}
-
- End Action - + + Event behaviour +
+
+ End Action + +
+
+ Target Event Scheduled End + + handleSubmit('isTimeToEnd', isTimeToEnd)} + variant='ontime' + /> + {isTimeToEnd ? 'On' : 'Off'} + +
+
+ Display options +
+
+ Timer Type + +
+
+ Warning Time + +
-
- Event Visibility - - handleSubmit('isPublic', isPublic)} - variant='ontime' - /> - {isPublic ? 'Public' : 'Private'} - +
+ Event Visibility + + handleSubmit('isPublic', isPublic)} + variant='ontime' + /> + {isPublic ? 'Public' : 'Private'} + +
+
+ Danger Time + +
+
-
+ ); -}; +} export default memo(EventEditorTimes); 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 02c061238c..73dd88764c 100644 --- a/apps/client/src/features/rundown/event-editor/composite/EventEditorTitles.tsx +++ b/apps/client/src/features/rundown/event-editor/composite/EventEditorTitles.tsx @@ -29,6 +29,7 @@ const EventEditorTitles = (props: EventEditorTitlesProps) => { return (
+ Event data
Event ID (read only) diff --git a/apps/client/src/features/rundown/time-input-flow/TimeInputFlow.tsx b/apps/client/src/features/rundown/time-input-flow/TimeInputFlow.tsx index b68a439780..f71cb1b27e 100644 --- a/apps/client/src/features/rundown/time-input-flow/TimeInputFlow.tsx +++ b/apps/client/src/features/rundown/time-input-flow/TimeInputFlow.tsx @@ -5,7 +5,7 @@ import { IoLink } from '@react-icons/all-files/io5/IoLink'; import { IoLockClosed } from '@react-icons/all-files/io5/IoLockClosed'; import { IoLockOpenOutline } from '@react-icons/all-files/io5/IoLockOpenOutline'; import { IoUnlink } from '@react-icons/all-files/io5/IoUnlink'; -import { MaybeString, TimerType, TimeStrategy } from 'ontime-types'; +import { MaybeString, TimeStrategy } from 'ontime-types'; import TimeInputWithButton from '../../../common/components/input/time-input/TimeInputWithButton'; import { useEventAction } from '../../../common/hooks/useEventAction'; @@ -16,19 +16,19 @@ import style from './TimeInputFlow.module.scss'; interface EventBlockTimerProps { eventId: string; + isTimeToEnd: boolean; timeStart: number; timeEnd: number; duration: number; timeStrategy: TimeStrategy; linkStart: MaybeString; delay: number; - timerType: TimerType; } type TimeActions = 'timeStart' | 'timeEnd' | 'duration'; -const TimeInputFlow = (props: EventBlockTimerProps) => { - const { eventId, timeStart, timeEnd, duration, timeStrategy, linkStart, delay, timerType } = props; +function TimeInputFlow(props: EventBlockTimerProps) { + const { eventId, isTimeToEnd, timeStart, timeEnd, duration, timeStrategy, linkStart, delay } = props; const { updateEvent, updateTimer } = useEventAction(); // In sync with EventEditorTimes @@ -49,8 +49,8 @@ const TimeInputFlow = (props: EventBlockTimerProps) => { warnings.push('Over midnight'); } - if (timerType === TimerType.TimeToEnd) { - warnings.push('Time to end'); + if (isTimeToEnd) { + warnings.push('Target event scheduled end'); } const hasDelay = delay !== 0; @@ -128,6 +128,6 @@ const TimeInputFlow = (props: EventBlockTimerProps) => { )} ); -}; +} export default memo(TimeInputFlow); diff --git a/apps/client/src/features/viewers/ViewWrapper.tsx b/apps/client/src/features/viewers/ViewWrapper.tsx index a0b78bd9bf..eb6819e200 100644 --- a/apps/client/src/features/viewers/ViewWrapper.tsx +++ b/apps/client/src/features/viewers/ViewWrapper.tsx @@ -9,6 +9,7 @@ import { Settings, SimpleTimerState, SupportedEvent, + TimerType, ViewSettings, } from 'ontime-types'; import { useStore } from 'zustand'; @@ -74,16 +75,14 @@ const withData =

(Component: ComponentType

) => { const selectedId = eventNow?.id ?? null; const nextId = eventNext?.id ?? null; - /******************************************/ - /*** + TimeManagerType ***/ - /*** WRAP INFORMATION RELATED TO TIME ***/ - /*** -------------------------------- ***/ - /******************************************/ - - const TimeManagerType = { + /** + * Contains an extended timer object with properties from the current event + */ + const timeManagerType: ViewExtendedTimer = { ...timer, clock, - timerType: eventNow?.timerType ?? null, + timerType: eventNow?.timerType ?? TimerType.CountDown, + isTimeToEnd: eventNow?.isTimeToEnd ?? false, }; return ( @@ -108,7 +107,7 @@ const withData =

(Component: ComponentType

) => { runtime={runtime} selectedId={selectedId} settings={settings} - time={TimeManagerType} + time={timeManagerType} viewSettings={viewSettings} /> diff --git a/apps/client/src/features/viewers/common/viewUtils.ts b/apps/client/src/features/viewers/common/viewUtils.ts index 32158f01cb..3111d2cdb0 100644 --- a/apps/client/src/features/viewers/common/viewUtils.ts +++ b/apps/client/src/features/viewers/common/viewUtils.ts @@ -5,16 +5,22 @@ import type { ViewExtendedTimer } from '../../../common/models/TimeManager.type' import { timerPlaceholder, timerPlaceholderMin } from '../../../common/utils/styleUtils'; import { formatTime } from '../../../common/utils/time'; -type TimerTypeParams = Pick; +type TimerTypeParams = Pick; export function getTimerByType(freezeEnd: boolean, timerObject?: TimerTypeParams): number | null { if (!timerObject) { return null; } + if (timerObject.isTimeToEnd) { + if (timerObject.current === null) { + return null; + } + return freezeEnd ? Math.max(timerObject.current, 0) : timerObject.current; + } + switch (timerObject.timerType) { case TimerType.CountDown: - case TimerType.TimeToEnd: if (timerObject.current === null) { return null; } diff --git a/apps/client/src/features/viewers/minimal-timer/MinimalTimer.tsx b/apps/client/src/features/viewers/minimal-timer/MinimalTimer.tsx index 633b0aee7c..83cc0f6d73 100644 --- a/apps/client/src/features/viewers/minimal-timer/MinimalTimer.tsx +++ b/apps/client/src/features/viewers/minimal-timer/MinimalTimer.tsx @@ -124,7 +124,7 @@ export default function MinimalTimer(props: MinimalTimerProps) { const isPlaying = time.playback !== Playback.Pause; - const shouldShowModifiers = time.timerType === TimerType.CountDown || time.timerType === TimerType.TimeToEnd; + const shouldShowModifiers = time.timerType === TimerType.CountDown || time.isTimeToEnd; const finished = time.phase === TimerPhase.Overtime; const showEndMessage = shouldShowModifiers && finished && viewSettings.endMessage && !hideEndMessage; const showFinished = diff --git a/apps/client/src/features/viewers/timer/Timer.tsx b/apps/client/src/features/viewers/timer/Timer.tsx index d0b82cd225..a32052cb3c 100644 --- a/apps/client/src/features/viewers/timer/Timer.tsx +++ b/apps/client/src/features/viewers/timer/Timer.tsx @@ -118,7 +118,7 @@ export default function Timer(props: TimerProps) { const finished = time.phase === TimerPhase.Overtime; const totalTime = (time.duration ?? 0) + (time.addedTime ?? 0); - const shouldShowModifiers = time.timerType === TimerType.CountDown || time.timerType === TimerType.TimeToEnd; + const shouldShowModifiers = time.timerType === TimerType.CountDown || time.isTimeToEnd; const showEndMessage = shouldShowModifiers && finished && viewSettings.endMessage; const showProgress = eventNow !== null && diff --git a/apps/server/src/models/demoProject.ts b/apps/server/src/models/demoProject.ts index 78ed0cef97..91e3abdcc5 100644 --- a/apps/server/src/models/demoProject.ts +++ b/apps/server/src/models/demoProject.ts @@ -10,6 +10,7 @@ export const demoDb: DatabaseModel = { note: 'SF1.01', endAction: EndAction.None, timerType: TimerType.CountDown, + isTimeToEnd: false, linkStart: null, timeStrategy: TimeStrategy.LockEnd, timeStart: 36000000, @@ -34,6 +35,7 @@ export const demoDb: DatabaseModel = { note: 'SF1.02', endAction: EndAction.None, timerType: TimerType.CountDown, + isTimeToEnd: false, linkStart: null, timeStrategy: TimeStrategy.LockEnd, timeStart: 37500000, @@ -58,6 +60,7 @@ export const demoDb: DatabaseModel = { note: 'SF1.03', endAction: EndAction.None, timerType: TimerType.CountDown, + isTimeToEnd: false, linkStart: null, timeStrategy: TimeStrategy.LockEnd, timeStart: 39000000, @@ -82,6 +85,7 @@ export const demoDb: DatabaseModel = { note: 'SF1.04', endAction: EndAction.None, timerType: TimerType.CountDown, + isTimeToEnd: false, linkStart: null, timeStrategy: TimeStrategy.LockEnd, timeStart: 40500000, @@ -106,6 +110,7 @@ export const demoDb: DatabaseModel = { note: 'SF1.05', endAction: EndAction.None, timerType: TimerType.CountDown, + isTimeToEnd: false, linkStart: null, timeStrategy: TimeStrategy.LockEnd, timeStart: 42000000, @@ -135,6 +140,7 @@ export const demoDb: DatabaseModel = { note: 'SF1.06', endAction: EndAction.None, timerType: TimerType.CountDown, + isTimeToEnd: false, linkStart: null, timeStrategy: TimeStrategy.LockEnd, timeStart: 47100000, @@ -159,6 +165,7 @@ export const demoDb: DatabaseModel = { note: 'SF1.07', endAction: EndAction.None, timerType: TimerType.CountDown, + isTimeToEnd: false, linkStart: null, timeStrategy: TimeStrategy.LockEnd, timeStart: 48600000, @@ -183,6 +190,7 @@ export const demoDb: DatabaseModel = { note: 'SF1.08', endAction: EndAction.None, timerType: TimerType.CountDown, + isTimeToEnd: false, linkStart: null, timeStrategy: TimeStrategy.LockEnd, timeStart: 50100000, @@ -207,6 +215,7 @@ export const demoDb: DatabaseModel = { note: 'SF1.09', endAction: EndAction.None, timerType: TimerType.CountDown, + isTimeToEnd: false, linkStart: null, timeStrategy: TimeStrategy.LockEnd, timeStart: 51600000, @@ -231,6 +240,7 @@ export const demoDb: DatabaseModel = { note: 'SF1.10', endAction: EndAction.None, timerType: TimerType.CountDown, + isTimeToEnd: false, linkStart: null, timeStrategy: TimeStrategy.LockEnd, timeStart: 53100000, @@ -260,6 +270,7 @@ export const demoDb: DatabaseModel = { note: 'SF1.11', endAction: EndAction.None, timerType: TimerType.CountDown, + isTimeToEnd: false, linkStart: null, timeStrategy: TimeStrategy.LockEnd, timeStart: 56100000, @@ -284,9 +295,9 @@ export const demoDb: DatabaseModel = { note: 'SF1.12', endAction: EndAction.None, timerType: TimerType.CountDown, + isTimeToEnd: false, linkStart: null, timeStrategy: TimeStrategy.LockEnd, - timeStart: 57600000, timeEnd: 58800000, duration: 1200000, @@ -309,6 +320,7 @@ export const demoDb: DatabaseModel = { note: 'SF1.13', endAction: EndAction.None, timerType: TimerType.CountDown, + isTimeToEnd: false, linkStart: null, timeStrategy: TimeStrategy.LockEnd, timeStart: 59100000, @@ -333,6 +345,7 @@ export const demoDb: DatabaseModel = { note: 'SF1.14', endAction: EndAction.None, timerType: TimerType.CountDown, + isTimeToEnd: false, linkStart: null, timeStrategy: TimeStrategy.LockEnd, timeStart: 60600000, diff --git a/apps/server/src/models/eventsDefinition.ts b/apps/server/src/models/eventsDefinition.ts index 4760d7c3c2..cc8bc563ce 100644 --- a/apps/server/src/models/eventsDefinition.ts +++ b/apps/server/src/models/eventsDefinition.ts @@ -15,6 +15,7 @@ export const event: Omit = { timerType: TimerType.CountDown, timeStrategy: TimeStrategy.LockDuration, linkStart: null, + isTimeToEnd: false, timeStart: 0, timeEnd: 0, duration: 0, diff --git a/apps/server/src/services/__tests__/timerUtils.test.ts b/apps/server/src/services/__tests__/timerUtils.test.ts index 2f9f418aeb..01fd0276cd 100644 --- a/apps/server/src/services/__tests__/timerUtils.test.ts +++ b/apps/server/src/services/__tests__/timerUtils.test.ts @@ -175,7 +175,7 @@ describe('getExpectedFinish()', () => { const state = { eventNow: { timeEnd: 30, - timerType: TimerType.TimeToEnd, + isTimeToEnd: true, }, timer: { addedTime: 10, @@ -195,7 +195,7 @@ describe('getExpectedFinish()', () => { const state = { eventNow: { timeEnd: 600000, // 00:10:00 - timerType: TimerType.TimeToEnd, + isTimeToEnd: true, }, timer: { addedTime: 0, @@ -351,7 +351,7 @@ describe('getCurrent()', () => { const state = { eventNow: { timeEnd: 100, - timerType: TimerType.TimeToEnd, + isTimeToEnd: true, }, clock: 30, timer: { @@ -376,7 +376,7 @@ describe('getCurrent()', () => { const state = { eventNow: { timeEnd: 100, - timerType: TimerType.TimeToEnd, + isTimeToEnd: true, }, clock: 30, timer: { @@ -401,7 +401,7 @@ describe('getCurrent()', () => { const state = { eventNow: { timeEnd: 100, - timerType: TimerType.TimeToEnd, + isTimeToEnd: true, }, clock: 30, timer: { @@ -427,7 +427,7 @@ describe('getCurrent()', () => { eventNow: { timeStart: 79200000, // 22:00:00 timeEnd: 600000, // 00:10:00 - timerType: TimerType.TimeToEnd, + isTimeToEnd: true, }, clock: 79500000, // 22:05:00 timer: { @@ -456,7 +456,7 @@ describe('getCurrent()', () => { timeStart: 77400000, // 21:30:00 timeEnd: 81000000, // 22:30:00 duration: 3600000, // 01:00:00 - timerType: TimerType.TimeToEnd, + isTimeToEnd: true, }, timer: { addedTime: 0, @@ -909,7 +909,8 @@ describe('getRuntimeOffset()', () => { timeStrategy: TimeStrategy.LockEnd, linkStart: null, endAction: EndAction.None, - timerType: TimerType.TimeToEnd, + timerType: TimerType.CountDown, + isTimeToEnd: true, isPublic: true, skip: false, note: '', @@ -961,7 +962,8 @@ describe('getRuntimeOffset()', () => { timeStrategy: TimeStrategy.LockEnd, linkStart: null, endAction: EndAction.None, - timerType: TimerType.TimeToEnd, + timerType: TimerType.CountDown, + isTimeToEnd: true, isPublic: true, skip: false, note: '', @@ -1011,7 +1013,8 @@ describe('getRuntimeOffset()', () => { timeStrategy: TimeStrategy.LockEnd, linkStart: null, endAction: EndAction.None, - timerType: TimerType.TimeToEnd, // <--- but this is time to end + timerType: TimerType.CountDown, + isTimeToEnd: true, }, runtime: { selectedEventIndex: 0, 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 c6af0745b2..5eccaeacc8 100644 --- a/apps/server/src/services/rundown-service/__tests__/rundownCache.test.ts +++ b/apps/server/src/services/rundown-service/__tests__/rundownCache.test.ts @@ -565,6 +565,7 @@ describe('calculateRuntimeDelays', () => { note: '', endAction: EndAction.None, timerType: TimerType.CountDown, + isTimeToEnd: false, timeStrategy: TimeStrategy.LockEnd, linkStart: null, timeStart: 600000, @@ -591,6 +592,7 @@ describe('calculateRuntimeDelays', () => { note: '', endAction: EndAction.None, timerType: TimerType.CountDown, + isTimeToEnd: false, timeStrategy: TimeStrategy.LockEnd, linkStart: null, timeStart: 1200000, @@ -617,6 +619,7 @@ describe('calculateRuntimeDelays', () => { note: '', endAction: EndAction.None, timerType: TimerType.CountDown, + isTimeToEnd: false, timeStrategy: TimeStrategy.LockEnd, linkStart: null, timeStart: 600000, @@ -643,6 +646,7 @@ describe('calculateRuntimeDelays', () => { note: '', endAction: EndAction.None, timerType: TimerType.CountDown, + isTimeToEnd: false, timeStrategy: TimeStrategy.LockEnd, linkStart: null, timeStart: 1200000, @@ -678,6 +682,7 @@ describe('getDelayAt()', () => { note: '', endAction: EndAction.None, timerType: TimerType.CountDown, + isTimeToEnd: false, timeStrategy: TimeStrategy.LockEnd, linkStart: null, timeStart: 600000, @@ -705,6 +710,7 @@ describe('getDelayAt()', () => { note: '', endAction: EndAction.None, timerType: TimerType.CountDown, + isTimeToEnd: false, timeStrategy: TimeStrategy.LockEnd, linkStart: null, timeStart: 1200000, @@ -732,6 +738,7 @@ describe('getDelayAt()', () => { note: '', endAction: EndAction.None, timerType: TimerType.CountDown, + isTimeToEnd: false, timeStrategy: TimeStrategy.LockEnd, linkStart: null, timeStart: 600000, @@ -759,6 +766,7 @@ describe('getDelayAt()', () => { note: '', endAction: EndAction.None, timerType: TimerType.CountDown, + isTimeToEnd: false, timeStrategy: TimeStrategy.LockEnd, linkStart: null, timeStart: 1200000, @@ -812,6 +820,7 @@ describe('calculateRuntimeDelaysFrom()', () => { note: '', endAction: EndAction.None, timerType: TimerType.CountDown, + isTimeToEnd: false, timeStrategy: TimeStrategy.LockEnd, linkStart: null, timeStart: 600000, @@ -839,6 +848,7 @@ describe('calculateRuntimeDelaysFrom()', () => { note: '', endAction: EndAction.None, timerType: TimerType.CountDown, + isTimeToEnd: false, timeStrategy: TimeStrategy.LockEnd, linkStart: null, timeStart: 1200000, @@ -866,6 +876,7 @@ describe('calculateRuntimeDelaysFrom()', () => { note: '', endAction: EndAction.None, timerType: TimerType.CountDown, + isTimeToEnd: false, timeStrategy: TimeStrategy.LockEnd, linkStart: null, timeStart: 600000, @@ -893,6 +904,7 @@ describe('calculateRuntimeDelaysFrom()', () => { note: '', endAction: EndAction.None, timerType: TimerType.CountDown, + isTimeToEnd: false, timeStrategy: TimeStrategy.LockEnd, linkStart: null, timeStart: 1200000, 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 3304c74c13..c4fb5c6b0b 100644 --- a/apps/server/src/services/sheet-service/__tests__/sheetUtils.test.ts +++ b/apps/server/src/services/sheet-service/__tests__/sheetUtils.test.ts @@ -31,6 +31,7 @@ describe('cellRequestFromEvent()', () => { linkStart: null, endAction: EndAction.None, timerType: TimerType.CountDown, + isTimeToEnd: false, duration: 10800000, isPublic: false, skip: false, @@ -73,6 +74,7 @@ describe('cellRequestFromEvent()', () => { timeEnd: 57600000, endAction: EndAction.None, timerType: TimerType.CountDown, + isTimeToEnd: false, duration: 10800000, timeStrategy: TimeStrategy.LockEnd, linkStart: null, @@ -119,6 +121,7 @@ describe('cellRequestFromEvent()', () => { timeEnd: 57600000, endAction: EndAction.None, timerType: TimerType.CountDown, + isTimeToEnd: false, duration: 10800000, timeStrategy: TimeStrategy.LockEnd, linkStart: null, @@ -164,6 +167,7 @@ describe('cellRequestFromEvent()', () => { timeEnd: 57600000, endAction: EndAction.None, timerType: TimerType.CountDown, + isTimeToEnd: false, timeStrategy: TimeStrategy.LockEnd, linkStart: null, duration: 10800000, @@ -195,6 +199,7 @@ describe('cellRequestFromEvent()', () => { timeEnd: 57600000, endAction: EndAction.None, timerType: TimerType.CountDown, + isTimeToEnd: false, duration: 10800000, timeStrategy: TimeStrategy.LockEnd, linkStart: null, @@ -227,6 +232,7 @@ describe('cellRequestFromEvent()', () => { timeEnd: 57600000, endAction: EndAction.None, timerType: TimerType.CountDown, + isTimeToEnd: false, duration: 10800000, timeStrategy: TimeStrategy.LockEnd, linkStart: null, diff --git a/apps/server/src/services/timerUtils.ts b/apps/server/src/services/timerUtils.ts index 28fd412635..df7d3f7c25 100644 --- a/apps/server/src/services/timerUtils.ts +++ b/apps/server/src/services/timerUtils.ts @@ -1,4 +1,4 @@ -import { MaybeNumber, Playback, TimerPhase, TimerType } from 'ontime-types'; +import { MaybeNumber, Playback, TimerPhase } from 'ontime-types'; import { dayInMs } from 'ontime-utils'; import { RuntimeState } from '../stores/runtimeState.js'; @@ -19,7 +19,7 @@ export function getExpectedFinish(state: RuntimeState): MaybeNumber { return null; } - const { timerType, timeEnd } = state.eventNow; + const { isTimeToEnd, timeEnd } = state.eventNow; const { pausedAt } = state._timer; const { clock } = state; @@ -33,7 +33,7 @@ export function getExpectedFinish(state: RuntimeState): MaybeNumber { const pausedTime = pausedAt != null ? clock - pausedAt : 0; - if (timerType === TimerType.TimeToEnd) { + if (isTimeToEnd) { return timeEnd + addedTime + pausedTime; } @@ -62,11 +62,11 @@ export function getCurrent(state: RuntimeState): number { } } const { startedAt, duration, addedTime } = state.timer; - const { timerType, timeStart, timeEnd } = state.eventNow; + const { isTimeToEnd, timeStart, timeEnd } = state.eventNow; const { pausedAt } = state._timer; const { clock } = state; - if (timerType === TimerType.TimeToEnd) { + if (isTimeToEnd) { const isEventOverMidnight = timeStart > timeEnd; const correctDay = isEventOverMidnight ? dayInMs : 0; return correctDay - clock + timeEnd + addedTime; @@ -131,7 +131,7 @@ export function getRuntimeOffset(state: RuntimeState): number { } const { clock } = state; - const { timeStart, timerType } = state.eventNow; + const { isTimeToEnd, timeStart } = state.eventNow; const { addedTime, current, startedAt } = state.timer; // if we havent started, but the timer is armed @@ -142,7 +142,7 @@ export function getRuntimeOffset(state: RuntimeState): number { const overtime = Math.min(current, 0); // in time-to-end, offset is overtime - if (timerType === TimerType.TimeToEnd) { + if (isTimeToEnd) { return overtime; } diff --git a/apps/server/src/utils/__tests__/parser.test.ts b/apps/server/src/utils/__tests__/parser.test.ts index dc9d765e00..f8b32b1f3f 100644 --- a/apps/server/src/utils/__tests__/parser.test.ts +++ b/apps/server/src/utils/__tests__/parser.test.ts @@ -463,7 +463,7 @@ describe('test event validator', () => { const event = { title: 'test', }; - const validated = createEvent(event, 'test'); + const validated = createEvent(event, 1); expect(validated).toEqual( expect.objectContaining({ @@ -471,12 +471,13 @@ describe('test event validator', () => { note: expect.any(String), timeStart: expect.any(Number), timeEnd: expect.any(Number), + isTimeToEnd: expect.any(Boolean), isPublic: expect.any(Boolean), skip: expect.any(Boolean), revision: expect.any(Number), type: expect.any(String), id: expect.any(String), - cue: 'test', + cue: '2', colour: expect.any(String), custom: expect.any(Object), }), @@ -485,7 +486,7 @@ describe('test event validator', () => { it('fails an empty object', () => { const event = {}; - const validated = createEvent(event, 'none'); + const validated = createEvent(event, 1); expect(validated).toEqual(null); }); @@ -495,7 +496,7 @@ describe('test event validator', () => { 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'); + const validated = createEvent(event, 1); if (validated === null) { throw new Error('unexpected value'); } @@ -1659,7 +1660,7 @@ describe('parseExcel()', () => { expect((events.at(0) as OntimeEvent).colour).toEqual('#F00'); //<--trailing white space in Excel data }); - it('link start', () => { + it('parses link start and checks that is applicable', () => { const testData = [ [ 'Time Start', @@ -1675,11 +1676,11 @@ describe('parseExcel()', () => { 'Timer type', ], ['4:30:00', '9:45:00', 'A', 'load-next', '', '', 'Rainbow chase', '#F00', 102, '', 'count-down'], - ['9:45:00', '10:56:00', 'C', 'load-next', 'x', '', 'Rainbow chase', '#0F0', 103, 'x', 'count-down'], - ['10:00:00', '16:36:00', 'D', 'load-next', 'x', '', 'Rainbow chase', '#F00', 102, 'x', 'count-down'], //<-- incorrect start times are overridden - ['21:45:00', '22:56:00', 'E', 'load-next', 'x', '', 'Rainbow chase', '#0F0', 103, '', 'count-down'], + ['9:45:00', '10:56:00', 'B', 'load-next', 'x', '', 'Rainbow chase', '#0F0', 103, 'x', 'count-down'], + ['10:00:00', '16:36:00', 'C', 'load-next', 'x', '', 'Rainbow chase', '#F00', 102, 'x', 'count-down'], // <-- incorrect start times are overridden + ['21:45:00', '22:56:00', 'D', 'load-next', 'x', '', 'Rainbow chase', '#0F0', 103, '', 'count-down'], ['', '', 'BLOCK', '', '', '', '', '', '', '', 'block'], - ['00:0:00', '23:56:00', 'G', 'load-next', 'x', '', 'Rainbow chase', '#0F0', 103, 'x', 'count-down'], //<-- link past blocks + ['00:0:00', '23:56:00', 'E', 'load-next', 'x', '', 'Rainbow chase', '#0F0', 103, 'x', 'count-down'], // <-- link past blocks [], ]; @@ -1709,30 +1710,46 @@ describe('parseExcel()', () => { const { rundown, order } = cache.get(); const firstId = order.at(0); // A - const secondId = order.at(1); // C - const thirdId = order.at(2); // D - const fourthId = order.at(3); // E - const fifhtId = order.at(4); // Block - const sixthId = order.at(5); // G + const secondId = order.at(1); // B + const thirdId = order.at(2); // C + const fourthId = order.at(3); // D + const fifthId = order.at(4); // Block + const sixthId = order.at(5); // E - if (!firstId || !secondId || !thirdId || !fourthId || !fifhtId || !sixthId) { + if (!firstId || !secondId || !thirdId || !fourthId || !fifthId || !sixthId) { throw new Error('Unexpected value'); } - expect((rundown[firstId] as OntimeEvent).timeStart).toEqual(16200000); - - expect((rundown[secondId] as OntimeEvent).timeStart).toEqual((rundown[firstId] as OntimeEvent).timeEnd); - expect((rundown[secondId] as OntimeEvent).linkStart).toEqual((rundown[firstId] as OntimeEvent).id); - - expect((rundown[thirdId] as OntimeEvent).timeStart).toEqual((rundown[secondId] as OntimeEvent).timeEnd); - expect((rundown[thirdId] as OntimeEvent).linkStart).toEqual((rundown[secondId] as OntimeEvent).id); - - expect((rundown[fourthId] as OntimeEvent).timeStart).toEqual(78300000); - - expect((rundown[fifhtId] as OntimeEvent).type).toEqual(SupportedEvent.Block); - - expect((rundown[sixthId] as OntimeEvent).timeStart).toEqual((rundown[fourthId] as OntimeEvent).timeEnd); - expect((rundown[sixthId] as OntimeEvent).linkStart).toEqual((rundown[fourthId] as OntimeEvent).id); + expect(rundown).toMatchObject({ + [firstId]: { + title: 'A', + timeStart: 16200000, + }, + [secondId]: { + title: 'B', + timeStart: (rundown[firstId] as OntimeEvent).timeEnd, + linkStart: (rundown[firstId] as OntimeEvent).id, + }, + [thirdId]: { + title: 'C', + timeStart: (rundown[secondId] as OntimeEvent).timeEnd, + linkStart: (rundown[secondId] as OntimeEvent).id, + }, + [fourthId]: { + title: 'D', + timeStart: 78300000, + linkStart: null, + }, + [fifthId]: { + title: 'BLOCK', + type: SupportedEvent.Block, + }, + [sixthId]: { + title: 'E', + timeStart: (rundown[fourthId] as OntimeEvent).timeEnd, + linkStart: (rundown[fourthId] as OntimeEvent).id, + }, + }); }); it('#971 BUG: parses time fields and booleans', () => { @@ -1762,7 +1779,7 @@ describe('parseExcel()', () => { 'false', 'Setup', '', - 'time-to-end', + 'count-down', 'none', '15', '00:05:00', @@ -1778,7 +1795,7 @@ describe('parseExcel()', () => { 'false', 'Meeting 1', '', - 'time-to-end', + 'count-down', 'none', 15, '00:05:00', @@ -1794,7 +1811,7 @@ describe('parseExcel()', () => { 'false', 'Meeting 2', '', - 'time-to-end', + 'count-down', 'none', '13', '5', @@ -1810,7 +1827,7 @@ describe('parseExcel()', () => { 'true', 'Lunch', '', - 'time-to-end', + 'count-down', 'none', 13, 5, @@ -1839,26 +1856,46 @@ describe('parseExcel()', () => { const parsedData = parseExcel(testData, {}); const { rundown } = parsedData; + // '15' as a string is parsed by smart time entry as minutes + expect(rundown[0]).toMatchObject({ + cue: 'SETUP', + timeWarning: 15 * MILLIS_PER_MINUTE, + }); + // elements in bug report // 15 is a number, in which case we parse it as a minutes value - expect((rundown.at(1) as OntimeEvent).timeWarning).toBe(15 * MILLIS_PER_MINUTE); + expect(rundown[1]).toMatchObject({ + cue: 'MEET1', + timeWarning: 15 * MILLIS_PER_MINUTE, + }); // in the case where a string is passed, we need to check whether it is an ISO 8601 date - expect((rundown.at(2) as OntimeEvent).duration).toBe(60 * MILLIS_PER_MINUTE); - expect((rundown.at(2) as OntimeEvent).timeDanger).toBe(5 * MILLIS_PER_MINUTE); - - expect((rundown.at(3) as OntimeEvent).timeWarning).toBe(13 * MILLIS_PER_MINUTE); - expect((rundown.at(3) as OntimeEvent).timeDanger).toBe(5 * MILLIS_PER_MINUTE); + expect(rundown[2]).toMatchObject({ + cue: 'MEET2', + duration: 60 * MILLIS_PER_MINUTE, + timeDanger: 5 * MILLIS_PER_MINUTE, + }); - expect((rundown.at(4) as OntimeEvent).duration).toBe(90 * MILLIS_PER_MINUTE); - expect((rundown.at(4) as OntimeEvent).linkStart).toBe(false); - expect((rundown.at(4) as OntimeEvent).timeWarning).toBe(11 * MILLIS_PER_MINUTE); - expect((rundown.at(4) as OntimeEvent).timeDanger).toBe(5 * MILLIS_PER_MINUTE); + expect(rundown[3]).toMatchObject({ + cue: 'lunch', + timeWarning: 13 * MILLIS_PER_MINUTE, + timeDanger: 5 * MILLIS_PER_MINUTE, + }); - expect((rundown.at(5) as OntimeEvent).duration).toBe(30 * MILLIS_PER_MINUTE); + expect(rundown[4]).toMatchObject({ + cue: 'MEET3', + duration: 90 * MILLIS_PER_MINUTE, + linkStart: false, + timeWarning: 11 * MILLIS_PER_MINUTE, + timeDanger: 5 * MILLIS_PER_MINUTE, + }); - // if we get a boolean, we should just use that - expect((rundown.at(5) as OntimeEvent).linkStart).toBe(true); - expect((rundown.at(5) as OntimeEvent).timeWarning).toBe(11 * MILLIS_PER_MINUTE); + expect(rundown[5]).toMatchObject({ + cue: 'MEET4', + duration: 30 * MILLIS_PER_MINUTE, + timeWarning: 11 * MILLIS_PER_MINUTE, + // if we get a boolean, we should just use that + linkStart: true, + }); }); }); diff --git a/apps/server/src/utils/__tests__/parserFunctions.test.ts b/apps/server/src/utils/__tests__/parserFunctions.test.ts index ad0de09373..7a6e038245 100644 --- a/apps/server/src/utils/__tests__/parserFunctions.test.ts +++ b/apps/server/src/utils/__tests__/parserFunctions.test.ts @@ -405,28 +405,6 @@ describe('sanitiseCustomFields()', () => { }); describe('parseRundown() linking', () => { - const blankEvent: OntimeEvent = { - id: '', - type: SupportedEvent.Event, - cue: '', - title: '', - note: '', - endAction: EndAction.None, - timerType: TimerType.CountDown, - linkStart: null, - timeStrategy: TimeStrategy.LockDuration, - timeStart: 0, - timeEnd: 0, - duration: 0, - isPublic: false, - skip: false, - colour: '', - revision: 0, - timeWarning: 120000, - timeDanger: 60000, - custom: {}, - }; - it('returns linked events', () => { const data: Partial = { rundown: [ @@ -445,12 +423,11 @@ describe('parseRundown() linking', () => { customFields: {}, }; - const expected: OntimeRundown = [ - { ...blankEvent, id: '1', cue: '0' }, - { ...blankEvent, id: '2', cue: '1', linkStart: '1' }, - ]; const result = parseRundown(data); - expect(result.rundown).toEqual(expected); + expect(result.rundown[1]).toMatchObject({ + id: '2', + linkStart: '1', + }); }); it('returns unlinked if no previous', () => { @@ -466,9 +443,11 @@ describe('parseRundown() linking', () => { customFields: {}, }; - const expected: OntimeRundown = [{ ...blankEvent, id: '2', cue: '0' }]; const result = parseRundown(data); - expect(result.rundown).toEqual(expected); + expect(result.rundown[0]).toMatchObject({ + id: '2', + linkStart: null, + }); }); it('returns linked events past blocks and delays', () => { @@ -505,14 +484,65 @@ describe('parseRundown() linking', () => { customFields: {}, }; - const expected: OntimeRundown = [ - { ...blankEvent, id: '1', cue: '0' }, - { id: 'delay1', type: SupportedEvent.Delay, duration: 0 }, - { ...blankEvent, id: '2', cue: '1', linkStart: '1' }, - { id: 'block1', type: SupportedEvent.Block, title: '' }, - { ...blankEvent, id: '3', cue: '2', linkStart: '2' }, - ]; const result = parseRundown(data); - expect(result.rundown).toEqual(expected); + expect(result.rundown[0]).toMatchObject({ + id: '1', + cue: '1', + }); + // skip delay + expect(result.rundown[2]).toMatchObject({ + id: '2', + cue: '2', + linkStart: '1', + }); + // skip block + expect(result.rundown[4]).toMatchObject({ + id: '3', + cue: '3', + linkStart: '2', + }); + }); +}); + +describe('parseRundown() migrations', () => { + const legacyEvent = { + id: '1', + type: SupportedEvent.Event, + cue: '', + title: '', + note: '', + endAction: EndAction.None, + timerType: 'time-to-end', + linkStart: null, + timeStrategy: TimeStrategy.LockDuration, + timeStart: 0, + timeEnd: 0, + duration: 0, + isPublic: false, + skip: false, + colour: '', + revision: 0, + timeWarning: 120000, + timeDanger: 60000, + custom: {}, + }; + + it('migrates an event with time-to-end', () => { + const result = parseRundown({ rundown: [legacyEvent] as OntimeRundown }); + expect(result.rundown[0]).toMatchObject({ + id: '1', + timerType: TimerType.CountDown, + isTimeToEnd: true, + }); + }); + + it('migrates an event without time-to-end', () => { + const countdownEvent = { ...legacyEvent, timerType: TimerType.CountDown }; + const result = parseRundown({ rundown: [countdownEvent] as OntimeRundown }); + expect(result.rundown[0]).toMatchObject({ + id: '1', + timerType: TimerType.CountDown, + isTimeToEnd: false, + }); }); }); diff --git a/apps/server/src/utils/parser.ts b/apps/server/src/utils/parser.ts index db410bfb68..e9822f9840 100644 --- a/apps/server/src/utils/parser.ts +++ b/apps/server/src/utils/parser.ts @@ -13,8 +13,8 @@ import { CustomFields, DatabaseModel, EventCustomFields, + isOntimeBlock, LogOrigin, - OntimeBlock, OntimeEvent, OntimeRundown, SupportedEvent, @@ -271,10 +271,11 @@ export const parseExcel = ( // if any data was found in row, push to array const keysFound = Object.keys(event).length + Object.keys(eventCustomFields).length; + console.log('keys found ---->', event) if (keysFound > 0) { // if it is a Block type drop all other filed - if (event.type === SupportedEvent.Block) { - rundown.push({ type: event.type, id: event.id, title: event.title } as OntimeBlock); + if (isOntimeBlock(event)) { + rundown.push({ type: event.type, id: event.id, title: event.title }); } else { if (timerTypeIndex === null) { event.timerType = TimerType.CountDown; @@ -360,7 +361,6 @@ export function createPatch(originalEvent: OntimeEvent, patchEvent: Partial, cueFallback: string): OntimeEvent | null => { +export const createEvent = (eventArgs: Partial, eventIndex: number | string): OntimeEvent | null => { if (Object.keys(eventArgs).length === 0) { return null; } + const cue = typeof eventIndex === 'number' ? String(eventIndex + 1) : eventIndex; + const baseEvent = { id: eventArgs?.id ?? generateId(), - cue: cueFallback, + cue, ...eventDef, }; const event = createPatch(baseEvent, eventArgs); diff --git a/apps/server/src/utils/parserFunctions.ts b/apps/server/src/utils/parserFunctions.ts index dadde53bbd..1889ac2501 100644 --- a/apps/server/src/utils/parserFunctions.ts +++ b/apps/server/src/utils/parserFunctions.ts @@ -12,6 +12,7 @@ import { OscSubscription, ProjectData, Settings, + TimerType, URLPreset, ViewSettings, isOntimeBlock, @@ -19,13 +20,7 @@ import { isOntimeDelay, isOntimeEvent, } from 'ontime-types'; -import { - customFieldLabelToKey, - generateId, - getErrorMessage, - getLastEvent, - isAlphanumericWithSpace, -} from 'ontime-utils'; +import { customFieldLabelToKey, generateId, getErrorMessage, isAlphanumericWithSpace } from 'ontime-utils'; import { dbModel } from '../models/dataModel.js'; import { block as blockDef, delay as delayDef } from '../models/eventsDefinition.js'; @@ -52,6 +47,7 @@ export function parseRundown( const rundown: OntimeRundown = []; let eventIndex = 0; + let previousId: string | null = null; const ids: string[] = []; for (const event of data.rundown) { @@ -64,12 +60,13 @@ export function parseRundown( let newEvent: OntimeEvent | OntimeDelay | OntimeBlock | null; if (isOntimeEvent(event)) { + const maybeEvent = runEventMigrations({ ...event, id }); + if (event.linkStart) { - const prevId = getLastEvent(rundown).lastEvent?.id ?? null; - event.linkStart = prevId; + maybeEvent.linkStart = previousId; } - newEvent = createEvent(event, eventIndex.toString()); + newEvent = createEvent(maybeEvent, eventIndex); // skip if event is invalid if (newEvent == null) { emitError?.('Skipping event without payload'); @@ -84,6 +81,7 @@ export function parseRundown( } } + previousId = id; eventIndex += 1; } else if (isOntimeDelay(event)) { newEvent = { ...delayDef, duration: event.duration, id }; @@ -349,3 +347,22 @@ export function sanitiseCustomFields(data: object): CustomFields { return newCustomFields; } + +/** + * Time to end was moved from a TimerType to a standalone boolean + * Released as part of v3.10.0 + */ +function migrateTimeToEnd(event: any): OntimeEvent { + if (event.timerType === 'time-to-end') { + event.timerType = TimerType.CountDown; + event.isTimeToEnd = true; + } + return event; +} + +/** + * Mutating function migrates event data entries + */ +function runEventMigrations(event: any): OntimeEvent { + return migrateTimeToEnd(event); +} diff --git a/e2e/tests/fixtures/test-sheet.xlsx b/e2e/tests/fixtures/test-sheet.xlsx index aa335bea13..a5f2a8f969 100644 Binary files a/e2e/tests/fixtures/test-sheet.xlsx and b/e2e/tests/fixtures/test-sheet.xlsx differ diff --git a/packages/types/src/definitions/TimerType.type.ts b/packages/types/src/definitions/TimerType.type.ts index e9bae59fb8..df9e0757d6 100644 --- a/packages/types/src/definitions/TimerType.type.ts +++ b/packages/types/src/definitions/TimerType.type.ts @@ -1,7 +1,6 @@ export enum TimerType { CountDown = 'count-down', CountUp = 'count-up', - TimeToEnd = 'time-to-end', Clock = 'clock', None = 'none', } diff --git a/packages/types/src/definitions/core/OntimeEvent.type.ts b/packages/types/src/definitions/core/OntimeEvent.type.ts index ac9d08a250..6cef9896c8 100644 --- a/packages/types/src/definitions/core/OntimeEvent.type.ts +++ b/packages/types/src/definitions/core/OntimeEvent.type.ts @@ -29,6 +29,7 @@ export type OntimeEvent = OntimeBaseEvent & { note: string; endAction: EndAction; timerType: TimerType; + isTimeToEnd: boolean; linkStart: MaybeString; // ID of event to link to timeStrategy: TimeStrategy; timeStart: number; diff --git a/packages/utils/src/validate-events/validateEvent.test.ts b/packages/utils/src/validate-events/validateEvent.test.ts index c7fa207fbf..7ccd771531 100644 --- a/packages/utils/src/validate-events/validateEvent.test.ts +++ b/packages/utils/src/validate-events/validateEvent.test.ts @@ -18,8 +18,8 @@ describe('validateEndAction()', () => { describe('validateTimerType()', () => { it('recognises a string representation of an action', () => { - const timerType = validateTimerType('time-to-end'); - expect(timerType).toBe(TimerType.TimeToEnd); + const timerType = validateTimerType('count-up'); + expect(timerType).toBe(TimerType.CountUp); }); it('returns fallback otherwise', () => { const emptyType = validateTimerType('', TimerType.Clock);