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);