mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-20 22:49:18 +00:00
Refactor: split runtime state (#1725)
* refactor: runtime types * refactor: runtimeState functions to use the split type * refactor: RuntimeService to use the new split data * refactor: use the split data in the UI * update comments * refactor: rename runtime to offset * fix utils test
This commit is contained in:
committed by
Carlos Valente
parent
ed93cf313a
commit
048a9c96ea
@@ -58,8 +58,8 @@ export const setMessage = {
|
|||||||
|
|
||||||
export const usePlaybackControl = createSelector((state: RuntimeStore) => ({
|
export const usePlaybackControl = createSelector((state: RuntimeStore) => ({
|
||||||
playback: state.timer.playback,
|
playback: state.timer.playback,
|
||||||
selectedEventIndex: state.runtime.selectedEventIndex,
|
selectedEventIndex: state.rundown.selectedEventIndex,
|
||||||
numEvents: state.runtime.numEvents,
|
numEvents: state.rundown.numEvents,
|
||||||
timerPhase: state.timer.phase,
|
timerPhase: state.timer.phase,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -150,8 +150,8 @@ export const useClock = createSelector((state: RuntimeStore) => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
export const useNextFlag = createSelector((state: RuntimeStore) => ({
|
export const useNextFlag = createSelector((state: RuntimeStore) => ({
|
||||||
id: state.nextFlag?.id ?? null,
|
id: state.eventFlag?.id ?? null,
|
||||||
expectedStart: state.nextFlag?.expectedStart ?? null,
|
expectedStart: state.offset.expectedFlagStart,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
/** Used by the progress bar components */
|
/** Used by the progress bar components */
|
||||||
@@ -162,35 +162,35 @@ export const useProgressData = createSelector((state: RuntimeStore) => ({
|
|||||||
timeDanger: state.eventNow?.timeDanger ?? null,
|
timeDanger: state.eventNow?.timeDanger ?? null,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const useRuntimeOverview = createSelector((state: RuntimeStore) => ({
|
export const useRundownOverview = createSelector((state: RuntimeStore) => ({
|
||||||
plannedStart: state.runtime.plannedStart,
|
plannedStart: state.rundown.plannedStart,
|
||||||
actualStart: state.runtime.actualStart,
|
actualStart: state.rundown.actualStart,
|
||||||
plannedEnd: state.runtime.plannedEnd,
|
plannedEnd: state.rundown.plannedEnd,
|
||||||
expectedEnd: state.runtime.expectedEnd,
|
expectedEnd: state.offset.expectedRundownEnd,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const useRuntimePlaybackOverview = createSelector((state: RuntimeStore) => ({
|
export const useRuntimePlaybackOverview = createSelector((state: RuntimeStore) => ({
|
||||||
playback: state.timer.playback,
|
playback: state.timer.playback,
|
||||||
clock: state.clock,
|
clock: state.clock,
|
||||||
|
|
||||||
numEvents: state.runtime.numEvents,
|
numEvents: state.rundown.numEvents,
|
||||||
selectedEventIndex: state.runtime.selectedEventIndex,
|
selectedEventIndex: state.rundown.selectedEventIndex,
|
||||||
offset: state.runtime.offsetMode === OffsetMode.Absolute ? state.runtime.offsetAbs : state.runtime.offsetRel,
|
offset: state.offset.mode === OffsetMode.Absolute ? state.offset.absolute : state.offset.relative,
|
||||||
|
|
||||||
groupExpectedEnd: state.groupNow?.expectedEnd ?? null,
|
groupExpectedEnd: state.offset.expectedGroupEnd,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const useTimelineStatus = createSelector((state: RuntimeStore) => ({
|
export const useTimelineStatus = createSelector((state: RuntimeStore) => ({
|
||||||
clock: state.clock,
|
clock: state.clock,
|
||||||
offset: state.runtime.offsetAbs,
|
offset: state.offset.absolute,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const useExpectedStartData = createSelector((state: RuntimeStore) => ({
|
export const useExpectedStartData = createSelector((state: RuntimeStore) => ({
|
||||||
offset: state.runtime.offsetMode === OffsetMode.Absolute ? state.runtime.offsetAbs : state.runtime.offsetRel,
|
offset: state.offset.mode === OffsetMode.Absolute ? state.offset.absolute : state.offset.relative,
|
||||||
offsetMode: state.runtime.offsetMode,
|
mode: state.offset.mode,
|
||||||
currentDay: state.eventNow?.dayOffset ?? 0,
|
currentDay: state.eventNow?.dayOffset ?? 0,
|
||||||
actualStart: state.runtime.actualStart,
|
actualStart: state.rundown.actualStart,
|
||||||
plannedStart: state.runtime.plannedStart,
|
plannedStart: state.rundown.plannedStart,
|
||||||
clock: state.clock,
|
clock: state.clock,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -199,7 +199,7 @@ export const useCurrentDay = createSelector((state: RuntimeStore) => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
export const useRuntimeOffset = createSelector((state: RuntimeStore) => ({
|
export const useRuntimeOffset = createSelector((state: RuntimeStore) => ({
|
||||||
offset: state.runtime.offsetAbs,
|
offset: state.offset.absolute,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const usePing = createSelector((state: RuntimeStore) => ({
|
export const usePing = createSelector((state: RuntimeStore) => ({
|
||||||
@@ -212,7 +212,7 @@ export const useIsOnline = createSelector((state: RuntimeStore) => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
export const useOffsetMode = createSelector((state: RuntimeStore) => ({
|
export const useOffsetMode = createSelector((state: RuntimeStore) => ({
|
||||||
offsetMode: state.runtime.offsetMode,
|
offsetMode: state.offset.mode,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const setOffsetMode = (payload: OffsetMode) => sendSocket('offsetmode', payload);
|
export const setOffsetMode = (payload: OffsetMode) => sendSocket('offsetmode', payload);
|
||||||
@@ -251,7 +251,7 @@ export const useCountdownSocket = createSelector((state: RuntimeStore) => ({
|
|||||||
export const useBackstageSocket = createSelector((state: RuntimeStore) => ({
|
export const useBackstageSocket = createSelector((state: RuntimeStore) => ({
|
||||||
eventNext: state.eventNext,
|
eventNext: state.eventNext,
|
||||||
eventNow: state.eventNow,
|
eventNow: state.eventNow,
|
||||||
runtime: state.runtime,
|
rundown: state.rundown,
|
||||||
selectedEventId: state.eventNow?.id ?? null,
|
selectedEventId: state.eventNow?.id ?? null,
|
||||||
time: state.timer,
|
time: state.timer,
|
||||||
}));
|
}));
|
||||||
@@ -266,10 +266,11 @@ export const useStudioTimersSocket = createSelector((state: RuntimeStore) => ({
|
|||||||
eventNow: state.eventNow,
|
eventNow: state.eventNow,
|
||||||
message: state.message,
|
message: state.message,
|
||||||
time: state.timer,
|
time: state.timer,
|
||||||
runtime: state.runtime,
|
offset: state.offset,
|
||||||
|
rundown: state.rundown,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const useTimelineSocket = createSelector((state: RuntimeStore) => ({
|
export const useTimelineSocket = createSelector((state: RuntimeStore) => ({
|
||||||
clock: state.clock,
|
clock: state.clock,
|
||||||
offsetAbs: state.runtime.offsetAbs,
|
offsetAbs: state.offset.absolute,
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -145,12 +145,12 @@ export function useTimeUntilExpectedStart(
|
|||||||
isLinkedToLoaded: boolean;
|
isLinkedToLoaded: boolean;
|
||||||
},
|
},
|
||||||
): number {
|
): number {
|
||||||
const { offset, currentDay, offsetMode, actualStart, plannedStart, clock } = useExpectedStartData();
|
const { offset, currentDay, mode, actualStart, plannedStart, clock } = useExpectedStartData();
|
||||||
if (event === null) return 0;
|
if (event === null) return 0;
|
||||||
|
|
||||||
const expectedStart = getExpectedStart(
|
const expectedStart = getExpectedStart(
|
||||||
{ ...event },
|
{ ...event },
|
||||||
{ ...state, currentDay, offset, offsetMode, actualStart, plannedStart },
|
{ ...state, currentDay, offset, mode, actualStart, plannedStart },
|
||||||
);
|
);
|
||||||
return expectedStart - clock;
|
return expectedStart - clock;
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-9
@@ -18,15 +18,16 @@ const staticAutocompleteOptions = [
|
|||||||
'{{timer.expectedFinish}}',
|
'{{timer.expectedFinish}}',
|
||||||
'{{timer.secondaryTimer}}',
|
'{{timer.secondaryTimer}}',
|
||||||
'{{timer.startedAt}}',
|
'{{timer.startedAt}}',
|
||||||
'{{runtime.selectedEventIndex}}',
|
'{{rundown.selectedEventIndex}}',
|
||||||
'{{runtime.numEvents}}',
|
'{{rundown.numEvents}}',
|
||||||
'{{runtime.offset}}',
|
'{{rundown.plannedStart}}',
|
||||||
'{{runtime.plannedStart}}',
|
'{{rundown.plannedEnd}}',
|
||||||
'{{runtime.plannedEnd}}',
|
'{{rundown.actualStart}}',
|
||||||
'{{runtime.actualStart}}',
|
'{{offset.absolute}}',
|
||||||
'{{runtime.expectedEnd}}',
|
'{{offset.relative}}',
|
||||||
'{{currentGroup.id}}',
|
'{{offset.expectedRundownEnd}}',
|
||||||
'{{currentGroup.startedAt}}',
|
'{{offset.expectedGroupEnd}}',
|
||||||
|
'{{offset.expectedFlagStart}}',
|
||||||
];
|
];
|
||||||
|
|
||||||
const eventStaticPropertiesNow = [
|
const eventStaticPropertiesNow = [
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
useClock,
|
useClock,
|
||||||
useCurrentGroupId,
|
useCurrentGroupId,
|
||||||
useNextFlag,
|
useNextFlag,
|
||||||
useRuntimeOverview,
|
useRundownOverview,
|
||||||
useRuntimePlaybackOverview,
|
useRuntimePlaybackOverview,
|
||||||
useTimer,
|
useTimer,
|
||||||
} from '../../../common/hooks/useSocket';
|
} from '../../../common/hooks/useSocket';
|
||||||
@@ -31,7 +31,7 @@ import { OverUnder, TimeColumn } from './TimeLayout';
|
|||||||
import style from './TimeElements.module.scss';
|
import style from './TimeElements.module.scss';
|
||||||
|
|
||||||
export function StartTimes() {
|
export function StartTimes() {
|
||||||
const { plannedEnd, plannedStart, actualStart, expectedEnd } = useRuntimeOverview();
|
const { plannedEnd, plannedStart, actualStart, expectedEnd } = useRundownOverview();
|
||||||
|
|
||||||
const plannedStartText = plannedStart === null ? timerPlaceholder : formatTime(plannedStart);
|
const plannedStartText = plannedStart === null ? timerPlaceholder : formatTime(plannedStart);
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export default function BackstageLoader() {
|
|||||||
function Backstage({ events, customFields, projectData, isMirrored, settings }: BackstageData) {
|
function Backstage({ events, customFields, projectData, isMirrored, settings }: BackstageData) {
|
||||||
const { getLocalizedString } = useTranslation();
|
const { getLocalizedString } = useTranslation();
|
||||||
const { secondarySource, extraInfo } = useBackstageOptions();
|
const { secondarySource, extraInfo } = useBackstageOptions();
|
||||||
const { eventNext, eventNow, runtime, selectedEventId, time } = useBackstageSocket();
|
const { eventNext, eventNow, rundown, selectedEventId, time } = useBackstageSocket();
|
||||||
const [blinkClass, setBlinkClass] = useState(false);
|
const [blinkClass, setBlinkClass] = useState(false);
|
||||||
const { height: screenHeight } = useViewportSize();
|
const { height: screenHeight } = useViewportSize();
|
||||||
|
|
||||||
@@ -76,13 +76,13 @@ function Backstage({ events, customFields, projectData, isMirrored, settings }:
|
|||||||
const scheduledStart = (() => {
|
const scheduledStart = (() => {
|
||||||
if (showNow) return undefined;
|
if (showNow) return undefined;
|
||||||
if (!hasEvents) return undefined;
|
if (!hasEvents) return undefined;
|
||||||
return formatTime(runtime.plannedStart, { format12: 'hh:mm a', format24: 'HH:mm' });
|
return formatTime(rundown.plannedStart, { format12: 'hh:mm a', format24: 'HH:mm' });
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const scheduledEnd = (() => {
|
const scheduledEnd = (() => {
|
||||||
if (showNow) return undefined;
|
if (showNow) return undefined;
|
||||||
if (!hasEvents) return undefined;
|
if (!hasEvents) return undefined;
|
||||||
return formatTime(runtime.plannedEnd, { format12: 'hh:mm a', format24: 'HH:mm' });
|
return formatTime(rundown.plannedEnd, { format12: 'hh:mm a', format24: 'HH:mm' });
|
||||||
})();
|
})();
|
||||||
|
|
||||||
let displayTimer = millisToString(time.current, { fallback: timerPlaceholderMin });
|
let displayTimer = millisToString(time.current, { fallback: timerPlaceholderMin });
|
||||||
|
|||||||
@@ -17,9 +17,13 @@ interface StudioTimersProps {
|
|||||||
|
|
||||||
export default function StudioTimers({ viewSettings }: StudioTimersProps) {
|
export default function StudioTimers({ viewSettings }: StudioTimersProps) {
|
||||||
const { getLocalizedString } = useTranslation();
|
const { getLocalizedString } = useTranslation();
|
||||||
const { eventNow, eventNext, message, time, runtime } = useStudioTimersSocket();
|
const { eventNow, eventNext, message, time, offset, rundown } = useStudioTimersSocket();
|
||||||
|
|
||||||
const schedule = getFormattedScheduleTimes(runtime);
|
const schedule = getFormattedScheduleTimes({
|
||||||
|
offset: offset.absolute,
|
||||||
|
actualStart: rundown.actualStart,
|
||||||
|
expectedEnd: offset.expectedRundownEnd,
|
||||||
|
});
|
||||||
const event = getFormattedEventData(eventNow, time);
|
const event = getFormattedEventData(eventNow, time);
|
||||||
const eventNextTitle = eventNext?.title || '-';
|
const eventNextTitle = eventNext?.title || '-';
|
||||||
const formattedTimerMessage = (message.timer.visible && message.timer.text) || '-';
|
const formattedTimerMessage = (message.timer.visible && message.timer.text) || '-';
|
||||||
@@ -33,7 +37,7 @@ export default function StudioTimers({ viewSettings }: StudioTimersProps) {
|
|||||||
time.phase === TimerPhase.Danger,
|
time.phase === TimerPhase.Danger,
|
||||||
);
|
);
|
||||||
|
|
||||||
const offsetState = getOffsetState(runtime.offsetAbs);
|
const offsetState = getOffsetState(offset.absolute);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='studio__timers'>
|
<div className='studio__timers'>
|
||||||
|
|||||||
@@ -1,16 +1,19 @@
|
|||||||
import { OntimeEvent, Runtime, TimerState } from 'ontime-types';
|
import { MaybeNumber, OntimeEvent, TimerState } from 'ontime-types';
|
||||||
import { millisToString } from 'ontime-utils';
|
import { millisToString } from 'ontime-utils';
|
||||||
|
|
||||||
import { getOffsetText } from '../../common/utils/offset';
|
import { getOffsetText } from '../../common/utils/offset';
|
||||||
import { formatTime } from '../../common/utils/time';
|
import { formatTime } from '../../common/utils/time';
|
||||||
|
|
||||||
const timeFormat = { format12: 'h:mm a', format24: 'HH:mm' };
|
const timeFormat = { format12: 'h:mm a', format24: 'HH:mm' };
|
||||||
export function getFormattedScheduleTimes(runtime: Runtime) {
|
export function getFormattedScheduleTimes(data: {
|
||||||
const correctedOffset = runtime.offsetAbs !== null ? runtime.offsetAbs * -1 : null;
|
offset: number;
|
||||||
|
actualStart: MaybeNumber;
|
||||||
|
expectedEnd: MaybeNumber;
|
||||||
|
}) {
|
||||||
return {
|
return {
|
||||||
actualStart: formatTime(runtime.actualStart, timeFormat),
|
actualStart: formatTime(data.actualStart, timeFormat),
|
||||||
expectedEnd: formatTime(runtime.expectedEnd, timeFormat),
|
expectedEnd: formatTime(data.expectedEnd, timeFormat),
|
||||||
offset: getOffsetText(correctedOffset),
|
offset: getOffsetText(data.offset),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -184,12 +184,12 @@ export const startServer = async (): Promise<{ message: string; serverPort: numb
|
|||||||
clock: state.clock,
|
clock: state.clock,
|
||||||
timer: state.timer,
|
timer: state.timer,
|
||||||
message: { ...runtimeStorePlaceholder.message },
|
message: { ...runtimeStorePlaceholder.message },
|
||||||
runtime: state.runtime,
|
offset: state.offset,
|
||||||
|
rundown: state.rundown,
|
||||||
eventNow: state.eventNow,
|
eventNow: state.eventNow,
|
||||||
eventNext: state.eventNext,
|
eventNext: state.eventNext,
|
||||||
|
eventFlag: null,
|
||||||
groupNow: null,
|
groupNow: null,
|
||||||
groupNext: null,
|
|
||||||
nextFlag: null,
|
|
||||||
auxtimer1: {
|
auxtimer1: {
|
||||||
duration: timerConfig.auxTimerDefault,
|
duration: timerConfig.auxTimerDefault,
|
||||||
current: timerConfig.auxTimerDefault,
|
current: timerConfig.auxTimerDefault,
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ export type RestorePoint = {
|
|||||||
addedTime: number;
|
addedTime: number;
|
||||||
pausedAt: MaybeNumber;
|
pausedAt: MaybeNumber;
|
||||||
firstStart: MaybeNumber;
|
firstStart: MaybeNumber;
|
||||||
groupStartAt: MaybeNumber;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -51,10 +50,6 @@ export function isRestorePoint(obj: unknown): obj is RestorePoint {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof restorePoint.groupStartAt !== 'number' && restorePoint.groupStartAt !== null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ describe('isRestorePoint()', () => {
|
|||||||
addedTime: 2,
|
addedTime: 2,
|
||||||
pausedAt: 3,
|
pausedAt: 3,
|
||||||
firstStart: 1,
|
firstStart: 1,
|
||||||
groupStartAt: 10,
|
|
||||||
};
|
};
|
||||||
expect(isRestorePoint(restorePoint)).toBe(true);
|
expect(isRestorePoint(restorePoint)).toBe(true);
|
||||||
|
|
||||||
@@ -25,7 +24,6 @@ describe('isRestorePoint()', () => {
|
|||||||
addedTime: 0,
|
addedTime: 0,
|
||||||
pausedAt: null,
|
pausedAt: null,
|
||||||
firstStart: 1,
|
firstStart: 1,
|
||||||
groupStartAt: null,
|
|
||||||
};
|
};
|
||||||
expect(isRestorePoint(restorePoint)).toBe(true);
|
expect(isRestorePoint(restorePoint)).toBe(true);
|
||||||
});
|
});
|
||||||
@@ -76,7 +74,6 @@ describe('RestoreService()', () => {
|
|||||||
addedTime: 5678,
|
addedTime: 5678,
|
||||||
pausedAt: 9087,
|
pausedAt: 9087,
|
||||||
firstStart: 1234,
|
firstStart: 1234,
|
||||||
groupStartAt: 1652,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const restoreService = new RestoreService('/path/to/restore/file');
|
const restoreService = new RestoreService('/path/to/restore/file');
|
||||||
@@ -94,7 +91,6 @@ describe('RestoreService()', () => {
|
|||||||
addedTime: 0,
|
addedTime: 0,
|
||||||
pausedAt: null,
|
pausedAt: null,
|
||||||
firstStart: 1234,
|
firstStart: 1234,
|
||||||
groupStartAt: null,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const restoreService = new RestoreService('/path/to/restore/file');
|
const restoreService = new RestoreService('/path/to/restore/file');
|
||||||
@@ -132,7 +128,6 @@ describe('RestoreService()', () => {
|
|||||||
addedTime: 1234,
|
addedTime: 1234,
|
||||||
pausedAt: 1234,
|
pausedAt: 1234,
|
||||||
firstStart: 1234,
|
firstStart: 1234,
|
||||||
groupStartAt: null,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const restoreService = new RestoreService('/path/to/restore/file');
|
const restoreService = new RestoreService('/path/to/restore/file');
|
||||||
|
|||||||
@@ -204,7 +204,7 @@ describe('getExpectedFinish()', () => {
|
|||||||
pausedAt: null,
|
pausedAt: null,
|
||||||
hasFinished: false,
|
hasFinished: false,
|
||||||
},
|
},
|
||||||
runtime: {
|
rundown: {
|
||||||
actualStart: 79200000,
|
actualStart: 79200000,
|
||||||
plannedEnd: 600000,
|
plannedEnd: 600000,
|
||||||
},
|
},
|
||||||
@@ -358,7 +358,7 @@ describe('getCurrent()', () => {
|
|||||||
duration: 100,
|
duration: 100,
|
||||||
startedAt: null,
|
startedAt: null,
|
||||||
},
|
},
|
||||||
runtime: {
|
rundown: {
|
||||||
plannedEnd: null,
|
plannedEnd: null,
|
||||||
},
|
},
|
||||||
_timer: {
|
_timer: {
|
||||||
@@ -383,7 +383,7 @@ describe('getCurrent()', () => {
|
|||||||
duration: 100,
|
duration: 100,
|
||||||
startedAt: 10,
|
startedAt: 10,
|
||||||
},
|
},
|
||||||
runtime: {
|
rundown: {
|
||||||
plannedEnd: 100,
|
plannedEnd: 100,
|
||||||
},
|
},
|
||||||
_timer: {
|
_timer: {
|
||||||
@@ -408,7 +408,7 @@ describe('getCurrent()', () => {
|
|||||||
duration: 100,
|
duration: 100,
|
||||||
startedAt: 10,
|
startedAt: 10,
|
||||||
},
|
},
|
||||||
runtime: {
|
rundown: {
|
||||||
plannedEnd: 100,
|
plannedEnd: 100,
|
||||||
},
|
},
|
||||||
_timer: {
|
_timer: {
|
||||||
@@ -434,7 +434,7 @@ describe('getCurrent()', () => {
|
|||||||
duration: Infinity, // not relevant,
|
duration: Infinity, // not relevant,
|
||||||
startedAt: 79200000, // 22:00:00
|
startedAt: 79200000, // 22:00:00
|
||||||
},
|
},
|
||||||
runtime: {
|
rundown: {
|
||||||
actualStart: 79200000,
|
actualStart: 79200000,
|
||||||
plannedEnd: 600000,
|
plannedEnd: 600000,
|
||||||
},
|
},
|
||||||
@@ -462,7 +462,7 @@ describe('getCurrent()', () => {
|
|||||||
duration: Infinity, // not relevant,
|
duration: Infinity, // not relevant,
|
||||||
startedAt: 79200000, // 22:00:00
|
startedAt: 79200000, // 22:00:00
|
||||||
},
|
},
|
||||||
runtime: {
|
rundown: {
|
||||||
actualStart: 82000000, // 22:46:40 <--- started now
|
actualStart: 82000000, // 22:46:40 <--- started now
|
||||||
plannedEnd: 81000000, // 22:30:00
|
plannedEnd: 81000000, // 22:30:00
|
||||||
},
|
},
|
||||||
@@ -735,14 +735,14 @@ describe('getRuntimeOffset()', () => {
|
|||||||
_timer: {
|
_timer: {
|
||||||
pausedAt: null,
|
pausedAt: null,
|
||||||
},
|
},
|
||||||
runtime: {
|
rundown: {
|
||||||
actualStart: 150,
|
actualStart: 150,
|
||||||
plannedStart: 100,
|
plannedStart: 100,
|
||||||
},
|
},
|
||||||
} as RuntimeState;
|
} as RuntimeState;
|
||||||
|
|
||||||
const { offsetAbs } = getRuntimeOffset(state);
|
const { absolute } = getRuntimeOffset(state);
|
||||||
expect(offsetAbs).toBe(50);
|
expect(absolute).toBe(50);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('added time subtracts time offset (positive offset)', () => {
|
it('added time subtracts time offset (positive offset)', () => {
|
||||||
@@ -759,14 +759,14 @@ describe('getRuntimeOffset()', () => {
|
|||||||
_timer: {
|
_timer: {
|
||||||
pausedAt: null,
|
pausedAt: null,
|
||||||
},
|
},
|
||||||
runtime: {
|
rundown: {
|
||||||
actualStart: 150,
|
actualStart: 150,
|
||||||
plannedStart: 100,
|
plannedStart: 100,
|
||||||
},
|
},
|
||||||
} as RuntimeState;
|
} as RuntimeState;
|
||||||
|
|
||||||
const { offsetAbs } = getRuntimeOffset(state);
|
const { absolute } = getRuntimeOffset(state);
|
||||||
expect(offsetAbs).toBe(60);
|
expect(absolute).toBe(60);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('considers running overtime (negative offset)', () => {
|
it('considers running overtime (negative offset)', () => {
|
||||||
@@ -784,14 +784,14 @@ describe('getRuntimeOffset()', () => {
|
|||||||
_timer: {
|
_timer: {
|
||||||
pausedAt: null,
|
pausedAt: null,
|
||||||
},
|
},
|
||||||
runtime: {
|
rundown: {
|
||||||
actualStart: 100,
|
actualStart: 100,
|
||||||
plannedStart: 100,
|
plannedStart: 100,
|
||||||
},
|
},
|
||||||
} as RuntimeState;
|
} as RuntimeState;
|
||||||
|
|
||||||
const { offsetAbs } = getRuntimeOffset(state);
|
const { absolute } = getRuntimeOffset(state);
|
||||||
expect(offsetAbs).toBe(10);
|
expect(absolute).toBe(10);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('paused time is delayed time (negative offset)', () => {
|
it('paused time is delayed time (negative offset)', () => {
|
||||||
@@ -810,14 +810,14 @@ describe('getRuntimeOffset()', () => {
|
|||||||
_timer: {
|
_timer: {
|
||||||
pausedAt: 125, // we have been paused for 25ms (see clock)
|
pausedAt: 125, // we have been paused for 25ms (see clock)
|
||||||
},
|
},
|
||||||
runtime: {
|
rundown: {
|
||||||
actualStart: 100,
|
actualStart: 100,
|
||||||
plannedStart: 100,
|
plannedStart: 100,
|
||||||
},
|
},
|
||||||
} as RuntimeState;
|
} as RuntimeState;
|
||||||
|
|
||||||
const { offsetAbs } = getRuntimeOffset(state);
|
const { absolute } = getRuntimeOffset(state);
|
||||||
expect(offsetAbs).toBe(25);
|
expect(absolute).toBe(25);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('offset doesnt exist if we havent started', () => {
|
it('offset doesnt exist if we havent started', () => {
|
||||||
@@ -831,14 +831,16 @@ describe('getRuntimeOffset()', () => {
|
|||||||
timeStrategy: 'lock-duration',
|
timeStrategy: 'lock-duration',
|
||||||
linkStart: false,
|
linkStart: false,
|
||||||
},
|
},
|
||||||
runtime: {
|
rundown: {
|
||||||
selectedEventIndex: 0,
|
selectedEventIndex: 0,
|
||||||
numEvents: 2,
|
numEvents: 2,
|
||||||
offsetAbs: -77400000,
|
|
||||||
plannedStart: 77400000,
|
plannedStart: 77400000,
|
||||||
plannedEnd: 84600000,
|
plannedEnd: 84600000,
|
||||||
actualStart: null,
|
actualStart: null,
|
||||||
expectedEnd: null,
|
},
|
||||||
|
offset: {
|
||||||
|
absolute: -77400000,
|
||||||
|
expectedRundownEnd: null,
|
||||||
},
|
},
|
||||||
timer: {
|
timer: {
|
||||||
addedTime: 0,
|
addedTime: 0,
|
||||||
@@ -853,8 +855,8 @@ describe('getRuntimeOffset()', () => {
|
|||||||
_timer: { pausedAt: null },
|
_timer: { pausedAt: null },
|
||||||
} as RuntimeState;
|
} as RuntimeState;
|
||||||
|
|
||||||
const { offsetAbs } = getRuntimeOffset(state);
|
const { absolute } = getRuntimeOffset(state);
|
||||||
expect(offsetAbs).toBe(0);
|
expect(absolute).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('with time-to-end, offsets dont exist if we are not in overtime', () => {
|
it('with time-to-end, offsets dont exist if we are not in overtime', () => {
|
||||||
@@ -882,14 +884,16 @@ describe('getRuntimeOffset()', () => {
|
|||||||
custom: {},
|
custom: {},
|
||||||
delay: 0,
|
delay: 0,
|
||||||
},
|
},
|
||||||
runtime: {
|
rundown: {
|
||||||
selectedEventIndex: 0,
|
selectedEventIndex: 0,
|
||||||
numEvents: 1,
|
numEvents: 1,
|
||||||
offsetAbs: 0,
|
|
||||||
plannedStart: 77400000, // 21:30:00
|
plannedStart: 77400000, // 21:30:00
|
||||||
plannedEnd: 81000000, // 22:30:00
|
plannedEnd: 81000000, // 22:30:00
|
||||||
actualStart: 78000000, // 21:40:00
|
actualStart: 78000000, // 21:40:00
|
||||||
expectedEnd: 81600000, // 22:40:00
|
},
|
||||||
|
offset: {
|
||||||
|
absolute: 0,
|
||||||
|
expectedRundownEnd: 81600000, // 22:40:00
|
||||||
},
|
},
|
||||||
timer: {
|
timer: {
|
||||||
addedTime: 0,
|
addedTime: 0,
|
||||||
@@ -904,8 +908,8 @@ describe('getRuntimeOffset()', () => {
|
|||||||
_timer: { pausedAt: null },
|
_timer: { pausedAt: null },
|
||||||
} as RuntimeState;
|
} as RuntimeState;
|
||||||
|
|
||||||
const { offsetAbs } = getRuntimeOffset(state);
|
const { absolute } = getRuntimeOffset(state);
|
||||||
expect(offsetAbs).toBe(0);
|
expect(absolute).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('with time-to-end, offset is the overtime', () => {
|
it('with time-to-end, offset is the overtime', () => {
|
||||||
@@ -933,14 +937,16 @@ describe('getRuntimeOffset()', () => {
|
|||||||
custom: {},
|
custom: {},
|
||||||
delay: 0,
|
delay: 0,
|
||||||
},
|
},
|
||||||
runtime: {
|
rundown: {
|
||||||
selectedEventIndex: 0,
|
selectedEventIndex: 0,
|
||||||
numEvents: 1,
|
numEvents: 1,
|
||||||
offsetAbs: 0,
|
|
||||||
plannedStart: 77400000, // 21:30:00
|
plannedStart: 77400000, // 21:30:00
|
||||||
plannedEnd: 81000000, // 22:30:00
|
plannedEnd: 81000000, // 22:30:00
|
||||||
actualStart: 78000000, // 21:40:00
|
actualStart: 78000000, // 21:40:00
|
||||||
expectedEnd: 81600000, // 22:40:00
|
},
|
||||||
|
offset: {
|
||||||
|
absolute: 0,
|
||||||
|
expectedRundownEnd: 81600000, // 22:40:00
|
||||||
},
|
},
|
||||||
timer: {
|
timer: {
|
||||||
addedTime: -200000,
|
addedTime: -200000,
|
||||||
@@ -955,8 +961,8 @@ describe('getRuntimeOffset()', () => {
|
|||||||
_timer: { pausedAt: null },
|
_timer: { pausedAt: null },
|
||||||
} as RuntimeState;
|
} as RuntimeState;
|
||||||
|
|
||||||
const { offsetAbs } = getRuntimeOffset(state);
|
const { absolute } = getRuntimeOffset(state);
|
||||||
expect(offsetAbs).toBe(400000); // <--- offset is always the overtime
|
expect(absolute).toBe(400000); // <--- offset is always the overtime
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles time-to-end started after the end time', () => {
|
it('handles time-to-end started after the end time', () => {
|
||||||
@@ -973,14 +979,16 @@ describe('getRuntimeOffset()', () => {
|
|||||||
timerType: TimerType.CountDown,
|
timerType: TimerType.CountDown,
|
||||||
countToEnd: true,
|
countToEnd: true,
|
||||||
},
|
},
|
||||||
runtime: {
|
rundown: {
|
||||||
selectedEventIndex: 0,
|
selectedEventIndex: 0,
|
||||||
numEvents: 1,
|
numEvents: 1,
|
||||||
offsetAbs: 0,
|
|
||||||
plannedStart: 77400000, // 21:30:00
|
plannedStart: 77400000, // 21:30:00
|
||||||
plannedEnd: 81000000, // 22:30:00
|
plannedEnd: 81000000, // 22:30:00
|
||||||
actualStart: 82000000, // 22:46:40 <--- started now
|
actualStart: 82000000, // 22:46:40 <--- started now
|
||||||
expectedEnd: 82000000 + 3600000, // <--- now + duration
|
},
|
||||||
|
offset: {
|
||||||
|
absolute: 0,
|
||||||
|
expectedRundownEnd: 82000000 + 3600000, // <--- now + duration
|
||||||
},
|
},
|
||||||
timer: {
|
timer: {
|
||||||
addedTime: 0,
|
addedTime: 0,
|
||||||
@@ -997,9 +1005,9 @@ describe('getRuntimeOffset()', () => {
|
|||||||
|
|
||||||
const updateCurrent = getCurrent(state);
|
const updateCurrent = getCurrent(state);
|
||||||
state.timer.current = updateCurrent;
|
state.timer.current = updateCurrent;
|
||||||
const { offsetAbs } = getRuntimeOffset(state);
|
const { absolute } = getRuntimeOffset(state);
|
||||||
expect(millisToString(offsetAbs)).toBe('00:16:40');
|
expect(millisToString(absolute)).toBe('00:16:40');
|
||||||
expect(offsetAbs).toBe(82000000 - 81000000); // <-- now - planned end
|
expect(absolute).toBe(82000000 - 81000000); // <-- now - planned end
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1018,15 +1026,15 @@ describe('getoffsetRel()', () => {
|
|||||||
_timer: {
|
_timer: {
|
||||||
pausedAt: null,
|
pausedAt: null,
|
||||||
},
|
},
|
||||||
runtime: {
|
rundown: {
|
||||||
actualStart: 150,
|
actualStart: 150,
|
||||||
plannedStart: 150,
|
plannedStart: 150,
|
||||||
},
|
},
|
||||||
} as RuntimeState;
|
} as RuntimeState;
|
||||||
|
|
||||||
const { offsetAbs, offsetRel } = getRuntimeOffset(state);
|
const { absolute, relative } = getRuntimeOffset(state);
|
||||||
expect(offsetAbs).toBe(0);
|
expect(absolute).toBe(0);
|
||||||
expect(offsetRel).toBe(0);
|
expect(relative).toBe(0);
|
||||||
});
|
});
|
||||||
it('relative offset is 0 when starting after the planed time', () => {
|
it('relative offset is 0 when starting after the planed time', () => {
|
||||||
const state = {
|
const state = {
|
||||||
@@ -1042,15 +1050,15 @@ describe('getoffsetRel()', () => {
|
|||||||
_timer: {
|
_timer: {
|
||||||
pausedAt: null,
|
pausedAt: null,
|
||||||
},
|
},
|
||||||
runtime: {
|
rundown: {
|
||||||
actualStart: 150,
|
actualStart: 150,
|
||||||
plannedStart: 100,
|
plannedStart: 100,
|
||||||
},
|
},
|
||||||
} as RuntimeState;
|
} as RuntimeState;
|
||||||
|
|
||||||
const { offsetAbs, offsetRel } = getRuntimeOffset(state);
|
const { absolute, relative } = getRuntimeOffset(state);
|
||||||
expect(offsetAbs).toBe(50);
|
expect(absolute).toBe(50);
|
||||||
expect(offsetRel).toBe(0);
|
expect(relative).toBe(0);
|
||||||
});
|
});
|
||||||
it('relative offset is 0 when starting before the planed time', () => {
|
it('relative offset is 0 when starting before the planed time', () => {
|
||||||
const state = {
|
const state = {
|
||||||
@@ -1066,15 +1074,15 @@ describe('getoffsetRel()', () => {
|
|||||||
_timer: {
|
_timer: {
|
||||||
pausedAt: null,
|
pausedAt: null,
|
||||||
},
|
},
|
||||||
runtime: {
|
rundown: {
|
||||||
actualStart: 100,
|
actualStart: 100,
|
||||||
plannedStart: 150,
|
plannedStart: 150,
|
||||||
},
|
},
|
||||||
} as RuntimeState;
|
} as RuntimeState;
|
||||||
|
|
||||||
const { offsetAbs, offsetRel } = getRuntimeOffset(state);
|
const { absolute, relative } = getRuntimeOffset(state);
|
||||||
expect(offsetAbs).toBe(-50);
|
expect(absolute).toBe(-50);
|
||||||
expect(offsetRel).toBe(0);
|
expect(relative).toBe(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1175,14 +1183,16 @@ describe('getTimerPhase()', () => {
|
|||||||
clock: 55691050,
|
clock: 55691050,
|
||||||
eventNow: null,
|
eventNow: null,
|
||||||
eventNext: null,
|
eventNext: null,
|
||||||
runtime: {
|
rundown: {
|
||||||
selectedEventIndex: null,
|
selectedEventIndex: null,
|
||||||
numEvents: 1,
|
numEvents: 1,
|
||||||
offsetAbs: 0,
|
|
||||||
plannedStart: 55860000,
|
plannedStart: 55860000,
|
||||||
plannedEnd: 55880000,
|
plannedEnd: 55880000,
|
||||||
actualStart: null,
|
actualStart: null,
|
||||||
expectedEnd: null,
|
},
|
||||||
|
offset: {
|
||||||
|
absolute: 0,
|
||||||
|
expectedRundownEnd: null,
|
||||||
},
|
},
|
||||||
timer: {
|
timer: {
|
||||||
addedTime: 0,
|
addedTime: 0,
|
||||||
@@ -1213,14 +1223,16 @@ describe('getTimerPhase()', () => {
|
|||||||
clock: 55691050,
|
clock: 55691050,
|
||||||
eventNow: null,
|
eventNow: null,
|
||||||
eventNext: null,
|
eventNext: null,
|
||||||
runtime: {
|
rundown: {
|
||||||
selectedEventIndex: null,
|
selectedEventIndex: null,
|
||||||
numEvents: 1,
|
numEvents: 1,
|
||||||
offsetAbs: 0,
|
|
||||||
plannedStart: 55860000,
|
plannedStart: 55860000,
|
||||||
plannedEnd: 55880000,
|
plannedEnd: 55880000,
|
||||||
actualStart: null,
|
actualStart: null,
|
||||||
expectedEnd: null,
|
},
|
||||||
|
offset: {
|
||||||
|
absolute: 0,
|
||||||
|
expectedRundownEnd: null,
|
||||||
},
|
},
|
||||||
timer: {
|
timer: {
|
||||||
addedTime: 0,
|
addedTime: 0,
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ import {
|
|||||||
isOntimeEvent,
|
isOntimeEvent,
|
||||||
isPlayableEvent,
|
isPlayableEvent,
|
||||||
LogOrigin,
|
LogOrigin,
|
||||||
MaybeNumber,
|
|
||||||
OffsetMode,
|
OffsetMode,
|
||||||
OntimeEvent,
|
OntimeEvent,
|
||||||
Playback,
|
Playback,
|
||||||
|
RuntimeStore,
|
||||||
TimerLifeCycle,
|
TimerLifeCycle,
|
||||||
TimerPhase,
|
TimerPhase,
|
||||||
TimerState,
|
TimerState,
|
||||||
@@ -34,16 +34,12 @@ import {
|
|||||||
findPreviousPlayableId,
|
findPreviousPlayableId,
|
||||||
getEventAtIndex,
|
getEventAtIndex,
|
||||||
getShouldClockUpdate,
|
getShouldClockUpdate,
|
||||||
getShouldFlagUpdate,
|
getShouldOffsetUpdate,
|
||||||
getShouldGroupUpdate,
|
|
||||||
getShouldRuntimeUpdate,
|
|
||||||
getShouldTimerUpdate,
|
getShouldTimerUpdate,
|
||||||
isNewSecond,
|
isNewSecond,
|
||||||
} from './rundownService.utils.js';
|
} from './rundownService.utils.js';
|
||||||
import { RundownMetadata } from '../../api-data/rundown/rundown.types.js';
|
import { RundownMetadata } from '../../api-data/rundown/rundown.types.js';
|
||||||
|
|
||||||
type RuntimeStateEventKeys = keyof Pick<RuntimeState, 'eventNext' | 'eventNow'>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service manages runtime status of app
|
* Service manages runtime status of app
|
||||||
* Coordinating with necessary services
|
* Coordinating with necessary services
|
||||||
@@ -53,22 +49,11 @@ class RuntimeService {
|
|||||||
private lastIntegrationClockUpdate = -1;
|
private lastIntegrationClockUpdate = -1;
|
||||||
private lastIntegrationTimerValue = -1;
|
private lastIntegrationTimerValue = -1;
|
||||||
|
|
||||||
/** last time we updated the socket */
|
|
||||||
static previousTimerUpdate: number;
|
|
||||||
static previousRuntimeUpdate: number;
|
|
||||||
static previousTimerValue: MaybeNumber; // previous timer value, could be null
|
|
||||||
static previousClockUpdate: number;
|
|
||||||
|
|
||||||
/** last known state */
|
/** last known state */
|
||||||
static previousState: RuntimeState;
|
static previousState: RuntimeState;
|
||||||
|
|
||||||
constructor(eventTimer: EventTimer) {
|
constructor(eventTimer: EventTimer) {
|
||||||
this.eventTimer = eventTimer;
|
this.eventTimer = eventTimer;
|
||||||
|
|
||||||
RuntimeService.previousTimerUpdate = -1;
|
|
||||||
RuntimeService.previousRuntimeUpdate = -1;
|
|
||||||
RuntimeService.previousTimerValue = -1;
|
|
||||||
RuntimeService.previousClockUpdate = -1;
|
|
||||||
RuntimeService.previousState = {} as RuntimeState;
|
RuntimeService.previousState = {} as RuntimeState;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,7 +86,7 @@ class RuntimeService {
|
|||||||
// 2. handle edge cases related to roll
|
// 2. handle edge cases related to roll
|
||||||
if (newState.timer.playback === Playback.Roll) {
|
if (newState.timer.playback === Playback.Roll) {
|
||||||
// check if we need to call any side effects
|
// check if we need to call any side effects
|
||||||
const keepOffset = newState.runtime.offsetAbs;
|
const keepOffset = newState.offset.absolute;
|
||||||
if (hasSecondaryTimerFinished) {
|
if (hasSecondaryTimerFinished) {
|
||||||
// if the secondary timer has finished, we need to call roll
|
// if the secondary timer has finished, we need to call roll
|
||||||
// since event is already loaded
|
// since event is already loaded
|
||||||
@@ -292,7 +277,7 @@ class RuntimeService {
|
|||||||
rundown,
|
rundown,
|
||||||
playableEventOrder,
|
playableEventOrder,
|
||||||
cue,
|
cue,
|
||||||
state.runtime.selectedEventIndex ?? undefined,
|
state.rundown.selectedEventIndex ?? undefined,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!event) {
|
if (!event) {
|
||||||
@@ -353,7 +338,7 @@ class RuntimeService {
|
|||||||
rundown,
|
rundown,
|
||||||
playableEventOrder,
|
playableEventOrder,
|
||||||
cue,
|
cue,
|
||||||
state.runtime.selectedEventIndex ?? undefined,
|
state.rundown.selectedEventIndex ?? undefined,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!event) {
|
if (!event) {
|
||||||
@@ -412,7 +397,7 @@ class RuntimeService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (state.timer.playback === Playback.Roll) {
|
if (state.timer.playback === Playback.Roll) {
|
||||||
return this.loadEvent(nextEvent, { firstStart: state.runtime.actualStart });
|
return this.loadEvent(nextEvent, { firstStart: state.rundown.actualStart });
|
||||||
}
|
}
|
||||||
return this.loadEvent(nextEvent);
|
return this.loadEvent(nextEvent);
|
||||||
}
|
}
|
||||||
@@ -667,20 +652,24 @@ function broadcastResult(_target: any, _propertyKey: string, descriptor: Propert
|
|||||||
// we do the comparison by explicitly for each property
|
// we do the comparison by explicitly for each property
|
||||||
// to apply custom logic for different datasets
|
// to apply custom logic for different datasets
|
||||||
|
|
||||||
// if a new event was loaded most things should update
|
// Update the entry if they have changed
|
||||||
const hasNewLoaded = state.eventNow?.id !== RuntimeService.previousState?.eventNow?.id;
|
let entryChanged = false;
|
||||||
|
entryChanged ||= updateMaybeEntryIfChanged('eventNow');
|
||||||
|
entryChanged ||= updateMaybeEntryIfChanged('eventNext');
|
||||||
|
entryChanged ||= updateMaybeEntryIfChanged('eventFlag');
|
||||||
|
entryChanged ||= updateMaybeEntryIfChanged('groupNow');
|
||||||
|
|
||||||
// for the very fist run there will be nothing in the previousState so we force an update
|
// for the very fist run there will be nothing in the previousState so we force an update
|
||||||
const justStarted = !RuntimeService.previousState?.timer;
|
const justStarted = !RuntimeService.previousState?.timer;
|
||||||
|
|
||||||
// offset mode has been changed
|
// offset mode has been changed
|
||||||
const offsetModeChanged = RuntimeService.previousState?.runtime?.offsetMode !== state.runtime.offsetMode;
|
const offsetModeChanged = RuntimeService.previousState?.offset?.mode !== state.offset.mode;
|
||||||
|
|
||||||
// if playback changes most things should update
|
// if playback changes most things should update
|
||||||
const hasChangedPlayback = RuntimeService.previousState.timer?.playback !== state.timer.playback;
|
const hasChangedPlayback = RuntimeService.previousState.timer?.playback !== state.timer.playback;
|
||||||
|
|
||||||
// combine all big changes
|
// combine all big changes
|
||||||
const hasImmediateChanges = hasNewLoaded || justStarted || hasChangedPlayback || offsetModeChanged;
|
const hasImmediateChanges = entryChanged || justStarted || hasChangedPlayback || offsetModeChanged;
|
||||||
|
|
||||||
// clock has changed by a second or more
|
// clock has changed by a second or more
|
||||||
const updateClock = getShouldClockUpdate(RuntimeService.previousState.clock, state.clock);
|
const updateClock = getShouldClockUpdate(RuntimeService.previousState.clock, state.clock);
|
||||||
@@ -693,74 +682,43 @@ function broadcastResult(_target: any, _propertyKey: string, descriptor: Propert
|
|||||||
const updateTimer = getShouldTimerUpdate(RuntimeService.previousState?.timer, state.timer);
|
const updateTimer = getShouldTimerUpdate(RuntimeService.previousState?.timer, state.timer);
|
||||||
if (updateTimer) {
|
if (updateTimer) {
|
||||||
batch.add('timer', state.timer);
|
batch.add('timer', state.timer);
|
||||||
RuntimeService.previousTimerUpdate = state.clock;
|
|
||||||
RuntimeService.previousTimerValue = state.timer.current;
|
|
||||||
RuntimeService.previousState.timer = { ...state.timer };
|
RuntimeService.previousState.timer = { ...state.timer };
|
||||||
}
|
}
|
||||||
|
|
||||||
// if any values have changed, values that have the possibility to tick are modulated by `hasClockUpdate`
|
// if any values have changed, values that have the possibility to tick are modulated by `hasClockUpdate`
|
||||||
const updateRuntime = getShouldRuntimeUpdate(
|
const updateRuntime = getShouldOffsetUpdate(
|
||||||
RuntimeService.previousState?.runtime,
|
RuntimeService.previousState?.offset,
|
||||||
state.runtime,
|
state.offset,
|
||||||
updateClock || hasImmediateChanges,
|
updateClock || hasImmediateChanges,
|
||||||
);
|
);
|
||||||
if (updateRuntime) {
|
if (updateRuntime) {
|
||||||
batch.add('runtime', state.runtime);
|
batch.add('offset', state.offset);
|
||||||
RuntimeService.previousRuntimeUpdate = state.clock;
|
RuntimeService.previousState.offset = structuredClone(state.offset);
|
||||||
RuntimeService.previousState.runtime = structuredClone(state.runtime);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// if any values have changed, values that have the possibility to tick are modulated by `hasClockUpdate`
|
// if any values have changed
|
||||||
const updateGroupNow = getShouldGroupUpdate(
|
const updateRundownData = !deepEqual(RuntimeService.previousState.rundown, state.rundown);
|
||||||
RuntimeService.previousState.groupNow,
|
if (updateRundownData) {
|
||||||
state.groupNow,
|
batch.add('rundown', state.rundown);
|
||||||
updateClock || hasImmediateChanges,
|
RuntimeService.previousState.rundown = structuredClone(state.rundown);
|
||||||
);
|
|
||||||
if (updateGroupNow) {
|
|
||||||
batch.add('groupNow', state.groupNow);
|
|
||||||
RuntimeService.previousState.groupNow = structuredClone(state.groupNow);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// next group is just a simple string or null compare
|
function updateMaybeEntryIfChanged<
|
||||||
const updateGroupNext = RuntimeService.previousState.groupNext !== state.groupNext;
|
K extends keyof Pick<RuntimeState, 'eventNow' | 'eventNext' | 'eventFlag' | 'groupNow'>,
|
||||||
if (updateGroupNext) {
|
>(key: K) {
|
||||||
batch.add('groupNext', state.groupNext);
|
const previousEntry = RuntimeService.previousState[key];
|
||||||
RuntimeService.previousState.groupNext = structuredClone(state.groupNext);
|
const currentEntry = state[key];
|
||||||
}
|
|
||||||
|
|
||||||
// if any values have changed, values that have the possibility to tick are modulated by `hasClockUpdate`
|
if (!previousEntry && !currentEntry) return false; // if both are null -> skip
|
||||||
const updateFlag = getShouldFlagUpdate(
|
|
||||||
RuntimeService.previousState.nextFlag,
|
|
||||||
state.nextFlag,
|
|
||||||
updateClock || hasImmediateChanges,
|
|
||||||
);
|
|
||||||
if (updateFlag) {
|
|
||||||
batch.add('nextFlag', state.nextFlag);
|
|
||||||
RuntimeService.previousState.nextFlag = structuredClone(state.nextFlag);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the events if they have changed
|
// if they have the same id the check if the contents have changed
|
||||||
updateEventIfChanged('eventNow', state);
|
if (previousEntry?.id === currentEntry?.id) {
|
||||||
updateEventIfChanged('eventNext', state);
|
if (deepEqual(previousEntry, currentEntry)) return false; // contents are the same -> skip
|
||||||
|
}
|
||||||
// Helper function to update an event if it has changed
|
// at this point we know that either the id or the contents has changed
|
||||||
function updateEventIfChanged(eventKey: RuntimeStateEventKeys, state: runtimeState.RuntimeState) {
|
batch.add(key, currentEntry as RuntimeStore[K]); // we know that there is the necessary overlap in the types to cast this
|
||||||
const previous = RuntimeService.previousState?.[eventKey];
|
RuntimeService.previousState[key] = structuredClone(currentEntry);
|
||||||
const now = state[eventKey];
|
return true;
|
||||||
|
|
||||||
// if there was nothing, and there is nothing, noop
|
|
||||||
if (!previous?.id && !now?.id) return;
|
|
||||||
|
|
||||||
const eventChanged =
|
|
||||||
// if load status changed, save new
|
|
||||||
previous?.id !== now?.id ||
|
|
||||||
// maybe the event itself has changed
|
|
||||||
!deepEqual(RuntimeService.previousState?.[eventKey], state[eventKey]);
|
|
||||||
|
|
||||||
if (!eventChanged) return;
|
|
||||||
|
|
||||||
batch.add(eventKey, state[eventKey]);
|
|
||||||
RuntimeService.previousState[eventKey] = structuredClone(state[eventKey]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// save the restore state
|
// save the restore state
|
||||||
@@ -772,8 +730,7 @@ function broadcastResult(_target: any, _propertyKey: string, descriptor: Propert
|
|||||||
startedAt: state.timer.startedAt,
|
startedAt: state.timer.startedAt,
|
||||||
addedTime: state.timer.addedTime,
|
addedTime: state.timer.addedTime,
|
||||||
pausedAt: state._timer.pausedAt,
|
pausedAt: state._timer.pausedAt,
|
||||||
firstStart: state.runtime.actualStart,
|
firstStart: state.rundown.actualStart,
|
||||||
groupStartAt: state.groupNow?.startedAt ?? null,
|
|
||||||
})
|
})
|
||||||
.catch((_e) => {
|
.catch((_e) => {
|
||||||
//we don't do anything with the error here
|
//we don't do anything with the error here
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
import { millisToSeconds } from 'ontime-utils';
|
import { millisToSeconds } from 'ontime-utils';
|
||||||
import {
|
import {
|
||||||
EntryId,
|
EntryId,
|
||||||
GroupState,
|
|
||||||
isOntimeEvent,
|
isOntimeEvent,
|
||||||
isPlayableEvent,
|
isPlayableEvent,
|
||||||
MaybeNumber,
|
MaybeNumber,
|
||||||
OntimeEvent,
|
OntimeEvent,
|
||||||
Rundown,
|
Rundown,
|
||||||
Runtime,
|
Offset,
|
||||||
TimerState,
|
TimerState,
|
||||||
TimerType,
|
TimerType,
|
||||||
UpcomingEntry,
|
|
||||||
} from 'ontime-types';
|
} from 'ontime-types';
|
||||||
|
|
||||||
import { deepEqual } from 'fast-equals';
|
import { deepEqual } from 'fast-equals';
|
||||||
@@ -55,51 +53,15 @@ export function getShouldTimerUpdate(previousValue: TimerState | undefined, curr
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getShouldRuntimeUpdate(
|
export function getShouldOffsetUpdate(
|
||||||
previousValue: Runtime | undefined,
|
previousValue: Offset | undefined,
|
||||||
currentValue: Runtime,
|
currentValue: Offset,
|
||||||
didDependencyUpdate: boolean,
|
didDependencyUpdate: boolean,
|
||||||
): boolean {
|
): boolean {
|
||||||
if (previousValue === undefined) return true;
|
if (previousValue === undefined) return true;
|
||||||
if (didDependencyUpdate) return !deepEqual(previousValue, currentValue);
|
if (previousValue.mode !== currentValue.mode) return true;
|
||||||
|
// absolute, relative, expected*End are ticked with `didDependencyUpdate`
|
||||||
return (
|
return didDependencyUpdate && !deepEqual(previousValue, currentValue);
|
||||||
previousValue.selectedEventIndex !== currentValue.selectedEventIndex ||
|
|
||||||
previousValue.numEvents !== currentValue.numEvents ||
|
|
||||||
previousValue.plannedStart !== currentValue.plannedStart ||
|
|
||||||
previousValue.plannedEnd !== currentValue.plannedEnd ||
|
|
||||||
previousValue.actualStart !== currentValue.actualStart ||
|
|
||||||
previousValue.offsetMode !== currentValue.offsetMode
|
|
||||||
// offsetAbs, offsetRel and expectedEnd are ticked with `didDependencyUpdate`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getShouldGroupUpdate(
|
|
||||||
previousValue: GroupState | null | undefined,
|
|
||||||
currentValue: GroupState | null,
|
|
||||||
didDependencyUpdate: boolean,
|
|
||||||
): boolean {
|
|
||||||
if (previousValue === undefined) return true;
|
|
||||||
if (didDependencyUpdate) return !deepEqual(previousValue, currentValue);
|
|
||||||
|
|
||||||
return (
|
|
||||||
previousValue?.id !== currentValue?.id || previousValue?.startedAt !== currentValue?.startedAt
|
|
||||||
// expectedEnd are ticked with `didDependencyUpdate`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getShouldFlagUpdate(
|
|
||||||
previousValue: UpcomingEntry | null | undefined,
|
|
||||||
currentValue: UpcomingEntry | null,
|
|
||||||
didDependencyUpdate: boolean,
|
|
||||||
): boolean {
|
|
||||||
if (previousValue === undefined) return true;
|
|
||||||
if (didDependencyUpdate) return !deepEqual(previousValue, currentValue);
|
|
||||||
|
|
||||||
return (
|
|
||||||
previousValue?.id !== currentValue?.id
|
|
||||||
// expectedStart
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -112,23 +112,23 @@ export function skippedOutOfEvent(state: RuntimeState, previousTime: number, ski
|
|||||||
* Positive offset is over time / behind schedule
|
* Positive offset is over time / behind schedule
|
||||||
* Negative offset is under time / ahead of schedule
|
* Negative offset is under time / ahead of schedule
|
||||||
*/
|
*/
|
||||||
export function getRuntimeOffset(state: RuntimeState): { offsetAbs: number; offsetRel: number } {
|
export function getRuntimeOffset(state: RuntimeState): { absolute: number; relative: number } {
|
||||||
const { eventNow, clock } = state;
|
const { eventNow, clock } = state;
|
||||||
const { addedTime, current, startedAt } = state.timer;
|
const { addedTime, current, startedAt } = state.timer;
|
||||||
// nothing to calculate if there are no loaded events or if we havent started
|
// nothing to calculate if there are no loaded events or if we havent started
|
||||||
if (eventNow === null || startedAt === null) {
|
if (eventNow === null || startedAt === null) {
|
||||||
return { offsetAbs: 0, offsetRel: 0 };
|
return { absolute: 0, relative: 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
const { countToEnd, timeStart } = eventNow;
|
const { countToEnd, timeStart } = eventNow;
|
||||||
const { plannedStart, actualStart } = state.runtime;
|
const { plannedStart, actualStart } = state.rundown;
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-labels -- dev code path
|
// eslint-disable-next-line no-unused-labels -- dev code path
|
||||||
DEV: {
|
DEV: {
|
||||||
// we know current exists as long as eventNow exists
|
// we know current exists as long as eventNow exists
|
||||||
if (current === null) throw new Error('timerUtils.getRuntimeOffset: state.timer.current must be set');
|
if (current === null) throw new Error('timerUtils.getRuntimeOffset: state.timer.current must be set');
|
||||||
if (plannedStart === null) throw new Error('timerUtils.getRuntimeOffset: state.runtime.plannedStart must be set');
|
if (plannedStart === null) throw new Error('timerUtils.getRuntimeOffset: state.rundown.plannedStart must be set');
|
||||||
if (actualStart === null) throw new Error('timerUtils.getRuntimeOffset: state.runtime.actualStart must be set');
|
if (actualStart === null) throw new Error('timerUtils.getRuntimeOffset: state.rundown.plannedStart must be set');
|
||||||
}
|
}
|
||||||
|
|
||||||
// difference between planned event start and actual event start (will be positive if we stared behind )
|
// difference between planned event start and actual event start (will be positive if we stared behind )
|
||||||
@@ -140,13 +140,13 @@ export function getRuntimeOffset(state: RuntimeState): { offsetAbs: number; offs
|
|||||||
// time the playback was paused, the different from now to when we paused is added to the offset TODO: brakes when crossing midnight
|
// time the playback was paused, the different from now to when we paused is added to the offset TODO: brakes when crossing midnight
|
||||||
const pausedTime = state._timer.pausedAt === null ? 0 : clock - state._timer.pausedAt;
|
const pausedTime = state._timer.pausedAt === null ? 0 : clock - state._timer.pausedAt;
|
||||||
|
|
||||||
const offsetAbs = eventStartOffset + overtime + pausedTime + addedTime;
|
const absolute = eventStartOffset + overtime + pausedTime + addedTime;
|
||||||
|
|
||||||
// the relative offset i the same as the absolute offset but adjusted relative to the actual start time
|
// the relative offset i the same as the absolute offset but adjusted relative to the actual start time
|
||||||
const offsetRel = offsetAbs + plannedStart - actualStart;
|
const relative = absolute + plannedStart - actualStart;
|
||||||
|
|
||||||
// in case of count to end, the absolute offset is just the overtime
|
// in case of count to end, the absolute offset is just the overtime
|
||||||
return countToEnd ? { offsetAbs: overtime, offsetRel } : { offsetAbs, offsetRel };
|
return countToEnd ? { absolute: overtime, relative } : { absolute, relative };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -6,19 +6,22 @@ const baseState: RuntimeState = {
|
|||||||
clock: 0,
|
clock: 0,
|
||||||
eventNow: null,
|
eventNow: null,
|
||||||
eventNext: null,
|
eventNext: null,
|
||||||
|
eventFlag: null,
|
||||||
groupNow: null,
|
groupNow: null,
|
||||||
groupNext: null,
|
rundown: {
|
||||||
nextFlag: null,
|
|
||||||
runtime: {
|
|
||||||
selectedEventIndex: null,
|
selectedEventIndex: null,
|
||||||
numEvents: 0,
|
numEvents: 0,
|
||||||
offsetAbs: 0,
|
|
||||||
offsetRel: 0,
|
|
||||||
plannedStart: 0,
|
plannedStart: 0,
|
||||||
plannedEnd: 0,
|
plannedEnd: 0,
|
||||||
actualStart: null,
|
actualStart: null,
|
||||||
expectedEnd: null,
|
},
|
||||||
offsetMode: OffsetMode.Absolute,
|
offset: {
|
||||||
|
absolute: 0,
|
||||||
|
relative: 0,
|
||||||
|
mode: OffsetMode.Absolute,
|
||||||
|
expectedRundownEnd: null,
|
||||||
|
expectedGroupEnd: null,
|
||||||
|
expectedFlagStart: null,
|
||||||
},
|
},
|
||||||
timer: {
|
timer: {
|
||||||
addedTime: 0,
|
addedTime: 0,
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ const mockState = {
|
|||||||
clock: 666,
|
clock: 666,
|
||||||
eventNow: null,
|
eventNow: null,
|
||||||
eventNext: null,
|
eventNext: null,
|
||||||
runtime: {
|
rundown: {
|
||||||
selectedEventIndex: null,
|
selectedEventIndex: null,
|
||||||
numEvents: 0,
|
numEvents: 0,
|
||||||
},
|
},
|
||||||
@@ -116,7 +116,7 @@ describe('mutation on runtimeState', () => {
|
|||||||
expect(newState.timer).toMatchObject({
|
expect(newState.timer).toMatchObject({
|
||||||
playback: Playback.Play,
|
playback: Playback.Play,
|
||||||
});
|
});
|
||||||
expect(newState.runtime.actualStart).toBe(newState.clock);
|
expect(newState.rundown.actualStart).toBe(newState.clock);
|
||||||
|
|
||||||
// 3. Pause event
|
// 3. Pause event
|
||||||
vi.setSystemTime('jan 1 00:03');
|
vi.setSystemTime('jan 1 00:03');
|
||||||
@@ -166,7 +166,7 @@ describe('mutation on runtimeState', () => {
|
|||||||
expectedFinish: null,
|
expectedFinish: null,
|
||||||
startedAt: null,
|
startedAt: null,
|
||||||
});
|
});
|
||||||
expect(newState.runtime.actualStart).toBeNull();
|
expect(newState.rundown.actualStart).toBeNull();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -187,24 +187,24 @@ describe('mutation on runtimeState', () => {
|
|||||||
vi.setSystemTime('jan 1 00:09');
|
vi.setSystemTime('jan 1 00:09');
|
||||||
load(entries.event1, rundown, metadata);
|
load(entries.event1, rundown, metadata);
|
||||||
let newState = getState();
|
let newState = getState();
|
||||||
expect(newState.runtime.actualStart).toBeNull();
|
expect(newState.rundown.actualStart).toBeNull();
|
||||||
expect(newState.runtime.plannedStart).toBe(0);
|
expect(newState.rundown.plannedStart).toBe(0);
|
||||||
expect(newState.runtime.plannedEnd).toBe(1500);
|
expect(newState.rundown.plannedEnd).toBe(1500);
|
||||||
expect(newState.groupNow).toBeNull();
|
expect(newState.groupNow).toBeNull();
|
||||||
expect(newState.runtime.offsetAbs).toBe(0);
|
expect(newState.offset.absolute).toBe(0);
|
||||||
|
|
||||||
// 2. Start event
|
// 2. Start event
|
||||||
vi.setSystemTime('jan 1 00:10');
|
vi.setSystemTime('jan 1 00:10');
|
||||||
start();
|
start();
|
||||||
newState = getState();
|
newState = getState();
|
||||||
const firstStart = newState.clock;
|
const firstStart = newState.clock;
|
||||||
if (newState.runtime.offsetAbs === null) {
|
if (newState.offset.absolute === null) {
|
||||||
throw new Error('Value cannot be null at this stage');
|
throw new Error('Value cannot be null at this stage');
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(newState.runtime.actualStart).toBe(newState.clock);
|
expect(newState.rundown.actualStart).toBe(newState.clock);
|
||||||
expect(newState.runtime.offsetAbs).toBe(newState.clock - entries.event1.timeStart);
|
expect(newState.offset.absolute).toBe(newState.clock - entries.event1.timeStart);
|
||||||
expect(newState.runtime.expectedEnd).toBe(entries.event2.timeEnd + newState.runtime.offsetAbs);
|
expect(newState.offset.expectedRundownEnd).toBe(entries.event2.timeEnd + newState.offset.absolute);
|
||||||
|
|
||||||
// 3. Next event
|
// 3. Next event
|
||||||
vi.setSystemTime('jan 1 00:12');
|
vi.setSystemTime('jan 1 00:12');
|
||||||
@@ -212,37 +212,37 @@ describe('mutation on runtimeState', () => {
|
|||||||
start();
|
start();
|
||||||
|
|
||||||
newState = getState();
|
newState = getState();
|
||||||
if (newState.runtime.actualStart === null || newState.runtime.offsetAbs === null) {
|
if (newState.rundown.actualStart === null || newState.offset.absolute === null) {
|
||||||
throw new Error('Value cannot be null at this stage');
|
throw new Error('Value cannot be null at this stage');
|
||||||
}
|
}
|
||||||
|
|
||||||
// there is a case where the calculation time overflows the millisecond which makes
|
// there is a case where the calculation time overflows the millisecond which makes
|
||||||
// tests fail
|
// tests fail
|
||||||
const forgivingActualStart = Math.abs(newState.runtime.actualStart - firstStart);
|
const forgivingActualStart = Math.abs(newState.rundown.actualStart - firstStart);
|
||||||
expect(forgivingActualStart).toBeLessThanOrEqual(1);
|
expect(forgivingActualStart).toBeLessThanOrEqual(1);
|
||||||
// we are over-under, the difference between the schedule and the actual start
|
// we are over-under, the difference between the schedule and the actual start
|
||||||
const delayBefore = newState.clock - entries.event2.timeStart;
|
const delayBefore = newState.clock - entries.event2.timeStart;
|
||||||
expect(newState.runtime.offsetAbs).toBe(delayBefore);
|
expect(newState.offset.absolute).toBe(delayBefore);
|
||||||
// finish is the difference between the runtime and the schedule
|
// finish is the difference between the runtime and the schedule
|
||||||
expect(newState.runtime.expectedEnd).toBe(newState.runtime.offsetAbs + entries.event2.timeEnd);
|
expect(newState.offset.expectedRundownEnd).toBe(newState.offset.absolute + entries.event2.timeEnd);
|
||||||
expect(newState.groupNow).toBeNull();
|
expect(newState.groupNow).toBeNull();
|
||||||
|
|
||||||
// 4. Add time
|
// 4. Add time
|
||||||
addTime(10);
|
addTime(10);
|
||||||
newState = getState();
|
newState = getState();
|
||||||
if (newState.runtime.offsetAbs === null) {
|
if (newState.offset.absolute === null) {
|
||||||
throw new Error('Value cannot be null at this stage');
|
throw new Error('Value cannot be null at this stage');
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(newState.runtime.offsetAbs).toBe(delayBefore + 10);
|
expect(newState.offset.absolute).toBe(delayBefore + 10);
|
||||||
expect(newState.runtime.expectedEnd).toBe(entries.event2.timeEnd + newState.runtime.offsetAbs);
|
expect(newState.offset.expectedRundownEnd).toBe(entries.event2.timeEnd + newState.offset.absolute);
|
||||||
|
|
||||||
// 5. Stop event
|
// 5. Stop event
|
||||||
stop();
|
stop();
|
||||||
newState = getState();
|
newState = getState();
|
||||||
expect(newState.runtime.actualStart).toBeNull();
|
expect(newState.rundown.actualStart).toBeNull();
|
||||||
expect(newState.runtime.offsetAbs).toBe(0);
|
expect(newState.offset.absolute).toBe(0);
|
||||||
expect(newState.runtime.expectedEnd).toBeNull();
|
expect(newState.offset.expectedRundownEnd).toBeNull();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -332,7 +332,7 @@ describe('roll mode', () => {
|
|||||||
start();
|
start();
|
||||||
const result = roll(rundown, metadata);
|
const result = roll(rundown, metadata);
|
||||||
expect(result).toStrictEqual({ eventId: '1', didStart: false });
|
expect(result).toStrictEqual({ eventId: '1', didStart: false });
|
||||||
expect(getState().runtime.offsetAbs).toBe(-1000);
|
expect(getState().offset.absolute).toBe(-1000);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -356,29 +356,29 @@ describe('roll mode', () => {
|
|||||||
load(rundown.entries[1] as PlayableEvent, rundown, metadata);
|
load(rundown.entries[1] as PlayableEvent, rundown, metadata);
|
||||||
start();
|
start();
|
||||||
// the current offset after manual play
|
// the current offset after manual play
|
||||||
const currentOffset = getState().runtime.offsetAbs;
|
const currentOffset = getState().offset.absolute;
|
||||||
let result = roll(rundown, metadata, getState().runtime.offsetAbs);
|
let result = roll(rundown, metadata, getState().offset.absolute);
|
||||||
expect(result).toStrictEqual({ eventId: '1', didStart: false });
|
expect(result).toStrictEqual({ eventId: '1', didStart: false });
|
||||||
// the current offset should be maintain by roll mode when taking over from play
|
// the current offset should be maintain by roll mode when taking over from play
|
||||||
expect(getState().runtime.offsetAbs).toBe(currentOffset);
|
expect(getState().offset.absolute).toBe(currentOffset);
|
||||||
|
|
||||||
vi.setSystemTime('jan 1 00:00:01');
|
vi.setSystemTime('jan 1 00:00:01');
|
||||||
result = roll(rundown, metadata, getState().runtime.offsetAbs);
|
result = roll(rundown, metadata, getState().offset.absolute);
|
||||||
expect(result).toStrictEqual({ eventId: '2', didStart: true });
|
expect(result).toStrictEqual({ eventId: '2', didStart: true });
|
||||||
expect(getState().runtime.offsetAbs).toBe(-1000);
|
expect(getState().offset.absolute).toBe(-1000);
|
||||||
|
|
||||||
vi.setSystemTime('jan 1 00:00:02');
|
vi.setSystemTime('jan 1 00:00:02');
|
||||||
result = roll(rundown, metadata, getState().runtime.offsetAbs);
|
result = roll(rundown, metadata, getState().offset.absolute);
|
||||||
expect(result).toStrictEqual({ eventId: '3', didStart: true });
|
expect(result).toStrictEqual({ eventId: '3', didStart: true });
|
||||||
expect(getState().runtime.offsetAbs).toBe(-1000);
|
expect(getState().offset.absolute).toBe(-1000);
|
||||||
|
|
||||||
vi.useRealTimers();
|
vi.useRealTimers();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('loadGroup', () => {
|
describe('loadGroupFlagAndEnd()', () => {
|
||||||
test('from no-group to a group will clear startedAt', () => {
|
test('from no-group to a group will clear expectedGroupEnd', () => {
|
||||||
const rundown = makeRundown({
|
const rundown = makeRundown({
|
||||||
entries: {
|
entries: {
|
||||||
0: makeOntimeEvent({ id: '0', parent: null }),
|
0: makeOntimeEvent({ id: '0', parent: null }),
|
||||||
@@ -393,6 +393,7 @@ describe('loadGroup', () => {
|
|||||||
const state = {
|
const state = {
|
||||||
groupNow: null,
|
groupNow: null,
|
||||||
eventNow: rundown.entries[11],
|
eventNow: rundown.entries[11],
|
||||||
|
offset: { expectedGroupEnd: 123 },
|
||||||
} as RuntimeState;
|
} as RuntimeState;
|
||||||
|
|
||||||
const metadata = { playableEventOrder: ['0', '11', '3'], flags: ['1'] } as RundownMetadata;
|
const metadata = { playableEventOrder: ['0', '11', '3'], flags: ['1'] } as RundownMetadata;
|
||||||
@@ -400,12 +401,13 @@ describe('loadGroup', () => {
|
|||||||
loadGroupFlagAndEnd(rundown, metadata, 2, state);
|
loadGroupFlagAndEnd(rundown, metadata, 2, state);
|
||||||
|
|
||||||
expect(state).toMatchObject({
|
expect(state).toMatchObject({
|
||||||
groupNow: { id: rundown.entries[1].id, startedAt: null },
|
groupNow: rundown.entries[1],
|
||||||
|
offset: { expectedGroupEnd: null },
|
||||||
eventNow: rundown.entries[11],
|
eventNow: rundown.entries[11],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('from a group to a different group will clear startedAt', () => {
|
test('from a group to a different group will clear expectedGroupEnd', () => {
|
||||||
const rundown = makeRundown({
|
const rundown = makeRundown({
|
||||||
entries: {
|
entries: {
|
||||||
0: makeOntimeEvent({ id: '0', parent: null }),
|
0: makeOntimeEvent({ id: '0', parent: null }),
|
||||||
@@ -418,7 +420,8 @@ describe('loadGroup', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
groupNow: { id: rundown.entries[1].id, startedAt: 123 },
|
groupNow: rundown.entries[1],
|
||||||
|
offset: { expectedGroupEnd: 123 },
|
||||||
eventNow: rundown.entries[22],
|
eventNow: rundown.entries[22],
|
||||||
} as RuntimeState;
|
} as RuntimeState;
|
||||||
|
|
||||||
@@ -427,12 +430,13 @@ describe('loadGroup', () => {
|
|||||||
loadGroupFlagAndEnd(rundown, metadata, 1, state);
|
loadGroupFlagAndEnd(rundown, metadata, 1, state);
|
||||||
|
|
||||||
expect(state).toMatchObject({
|
expect(state).toMatchObject({
|
||||||
groupNow: { id: rundown.entries[2].id, startedAt: null },
|
groupNow: rundown.entries[2],
|
||||||
|
offset: { expectedGroupEnd: null },
|
||||||
eventNow: rundown.entries[22],
|
eventNow: rundown.entries[22],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('from group to a no-group will clear startedAt', () => {
|
test('from group to a no-group will clear expectedGroupEnd', () => {
|
||||||
const rundown = makeRundown({
|
const rundown = makeRundown({
|
||||||
entries: {
|
entries: {
|
||||||
0: makeOntimeEvent({ id: '0', parent: null }),
|
0: makeOntimeEvent({ id: '0', parent: null }),
|
||||||
@@ -445,10 +449,8 @@ describe('loadGroup', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
groupNow: {
|
groupNow: rundown.entries[1],
|
||||||
id: rundown.entries[1].id,
|
offset: { expectedGroupEnd: 123 },
|
||||||
startedAt: 123,
|
|
||||||
},
|
|
||||||
eventNow: rundown.entries[0],
|
eventNow: rundown.entries[0],
|
||||||
} as RuntimeState;
|
} as RuntimeState;
|
||||||
|
|
||||||
@@ -458,35 +460,11 @@ describe('loadGroup', () => {
|
|||||||
|
|
||||||
expect(state).toMatchObject({
|
expect(state).toMatchObject({
|
||||||
groupNow: null,
|
groupNow: null,
|
||||||
|
offset: { expectedGroupEnd: null },
|
||||||
eventNow: rundown.entries[0],
|
eventNow: rundown.entries[0],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('from a group to same group will keep startedAt', () => {
|
|
||||||
const rundown = makeRundown({
|
|
||||||
entries: {
|
|
||||||
0: makeOntimeGroup({ id: '0', entries: ['1', '2'] }),
|
|
||||||
1: makeOntimeEvent({ id: '1', parent: '0' }),
|
|
||||||
2: makeOntimeEvent({ id: '2', parent: '0' }),
|
|
||||||
},
|
|
||||||
order: ['0'],
|
|
||||||
});
|
|
||||||
|
|
||||||
const state = {
|
|
||||||
groupNow: { id: rundown.entries[0].id, startedAt: 123 },
|
|
||||||
eventNow: rundown.entries[2],
|
|
||||||
} as RuntimeState;
|
|
||||||
|
|
||||||
const metadata = { playableEventOrder: ['1', '2'], flags: ['1'] } as RundownMetadata;
|
|
||||||
|
|
||||||
loadGroupFlagAndEnd(rundown, metadata, 0, state);
|
|
||||||
|
|
||||||
expect(state).toMatchObject({
|
|
||||||
groupNow: { id: rundown.entries[0].id, startedAt: 123 },
|
|
||||||
eventNow: rundown.entries[2],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('from no-group to no-group will keep startedAt', () => {
|
test('from no-group to no-group will keep startedAt', () => {
|
||||||
const rundown = makeRundown({
|
const rundown = makeRundown({
|
||||||
entries: {
|
entries: {
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import {
|
import {
|
||||||
GroupState,
|
|
||||||
UpcomingEntry,
|
|
||||||
isOntimeEvent,
|
isOntimeEvent,
|
||||||
MaybeNumber,
|
MaybeNumber,
|
||||||
MaybeString,
|
MaybeString,
|
||||||
@@ -10,10 +8,11 @@ import {
|
|||||||
PlayableEvent,
|
PlayableEvent,
|
||||||
Playback,
|
Playback,
|
||||||
Rundown,
|
Rundown,
|
||||||
Runtime,
|
Offset,
|
||||||
runtimeStorePlaceholder,
|
runtimeStorePlaceholder,
|
||||||
TimerPhase,
|
TimerPhase,
|
||||||
TimerState,
|
TimerState,
|
||||||
|
RundownState,
|
||||||
} from 'ontime-types';
|
} from 'ontime-types';
|
||||||
import {
|
import {
|
||||||
calculateDuration,
|
calculateDuration,
|
||||||
@@ -36,13 +35,13 @@ type ExpectedMetadata = { event: OntimeEvent; accumulatedGap: number; isLinkedTo
|
|||||||
|
|
||||||
export type RuntimeState = {
|
export type RuntimeState = {
|
||||||
clock: number; // realtime clock
|
clock: number; // realtime clock
|
||||||
groupNow: GroupState | null;
|
groupNow: OntimeGroup | null;
|
||||||
groupNext: MaybeString;
|
|
||||||
nextFlag: UpcomingEntry | null;
|
|
||||||
eventNow: PlayableEvent | null;
|
eventNow: PlayableEvent | null;
|
||||||
eventNext: PlayableEvent | null;
|
eventNext: PlayableEvent | null;
|
||||||
runtime: Runtime;
|
eventFlag: PlayableEvent | null;
|
||||||
|
offset: Offset;
|
||||||
timer: TimerState;
|
timer: TimerState;
|
||||||
|
rundown: RundownState;
|
||||||
// private properties of the timer calculations
|
// private properties of the timer calculations
|
||||||
_timer: {
|
_timer: {
|
||||||
forceFinish: MaybeNumber; // whether we should declare an event as finished, will contain the finish time
|
forceFinish: MaybeNumber; // whether we should declare an event as finished, will contain the finish time
|
||||||
@@ -61,12 +60,12 @@ export type RuntimeState = {
|
|||||||
const runtimeState: RuntimeState = {
|
const runtimeState: RuntimeState = {
|
||||||
clock: timeNow(),
|
clock: timeNow(),
|
||||||
groupNow: null,
|
groupNow: null,
|
||||||
groupNext: null,
|
|
||||||
nextFlag: null,
|
|
||||||
eventNow: null,
|
eventNow: null,
|
||||||
eventNext: null,
|
eventNext: null,
|
||||||
runtime: { ...runtimeStorePlaceholder.runtime },
|
eventFlag: null,
|
||||||
|
offset: { ...runtimeStorePlaceholder.offset },
|
||||||
timer: { ...runtimeStorePlaceholder.timer },
|
timer: { ...runtimeStorePlaceholder.timer },
|
||||||
|
rundown: { ...runtimeStorePlaceholder.rundown },
|
||||||
_timer: {
|
_timer: {
|
||||||
forceFinish: null,
|
forceFinish: null,
|
||||||
pausedAt: null,
|
pausedAt: null,
|
||||||
@@ -87,7 +86,10 @@ export function getState(): Readonly<RuntimeState> {
|
|||||||
...runtimeState,
|
...runtimeState,
|
||||||
eventNow: runtimeState.eventNow ? { ...runtimeState.eventNow } : null,
|
eventNow: runtimeState.eventNow ? { ...runtimeState.eventNow } : null,
|
||||||
eventNext: runtimeState.eventNext ? { ...runtimeState.eventNext } : null,
|
eventNext: runtimeState.eventNext ? { ...runtimeState.eventNext } : null,
|
||||||
runtime: { ...runtimeState.runtime },
|
eventFlag: runtimeState.eventFlag ? { ...runtimeState.eventFlag } : null,
|
||||||
|
groupNow: runtimeState.groupNow ? { ...runtimeState.groupNow } : null,
|
||||||
|
offset: { ...runtimeState.offset },
|
||||||
|
rundown: { ...runtimeState.rundown },
|
||||||
timer: { ...runtimeState.timer },
|
timer: { ...runtimeState.timer },
|
||||||
_timer: { ...runtimeState._timer },
|
_timer: { ...runtimeState._timer },
|
||||||
_rundown: { ...runtimeState._rundown },
|
_rundown: { ...runtimeState._rundown },
|
||||||
@@ -101,13 +103,16 @@ export function clearEventData() {
|
|||||||
runtimeState.eventNow = null;
|
runtimeState.eventNow = null;
|
||||||
runtimeState.eventNext = null;
|
runtimeState.eventNext = null;
|
||||||
|
|
||||||
runtimeState.runtime.offsetAbs = 0;
|
runtimeState.offset.absolute = 0;
|
||||||
runtimeState.runtime.offsetRel = 0;
|
runtimeState.offset.relative = 0;
|
||||||
runtimeState.runtime.expectedEnd = null;
|
runtimeState.offset.expectedFlagStart = null;
|
||||||
runtimeState.runtime.selectedEventIndex = null;
|
|
||||||
|
runtimeState.rundown.selectedEventIndex = null;
|
||||||
|
|
||||||
//TODO: is there any ExpectedMetadata stuff we need to clear here
|
//TODO: is there any ExpectedMetadata stuff we need to clear here
|
||||||
if (runtimeState.groupNow) runtimeState.groupNow.expectedEnd = null;
|
runtimeState.offset.expectedGroupEnd = null;
|
||||||
|
runtimeState.offset.expectedFlagStart = null;
|
||||||
|
runtimeState.offset.expectedRundownEnd = null;
|
||||||
|
|
||||||
runtimeState.timer.playback = Playback.Stop;
|
runtimeState.timer.playback = Playback.Stop;
|
||||||
runtimeState.clock = timeNow();
|
runtimeState.clock = timeNow();
|
||||||
@@ -124,20 +129,22 @@ export function clearEventData() {
|
|||||||
export function clearState() {
|
export function clearState() {
|
||||||
runtimeState.eventNow = null;
|
runtimeState.eventNow = null;
|
||||||
runtimeState.eventNext = null;
|
runtimeState.eventNext = null;
|
||||||
|
runtimeState.eventFlag = null;
|
||||||
runtimeState.groupNow = null;
|
|
||||||
runtimeState.groupNext = null;
|
|
||||||
runtimeState._group = null;
|
|
||||||
|
|
||||||
runtimeState.nextFlag = null;
|
|
||||||
runtimeState._flag = null;
|
runtimeState._flag = null;
|
||||||
|
|
||||||
runtimeState.runtime.offsetAbs = 0;
|
runtimeState.groupNow = null;
|
||||||
runtimeState.runtime.offsetRel = 0;
|
runtimeState._group = null;
|
||||||
runtimeState.runtime.actualStart = null;
|
|
||||||
runtimeState.runtime.expectedEnd = null;
|
runtimeState.rundown.actualStart = null;
|
||||||
|
runtimeState.rundown.selectedEventIndex = null;
|
||||||
|
|
||||||
|
runtimeState.offset.absolute = 0;
|
||||||
|
runtimeState.offset.relative = 0;
|
||||||
|
runtimeState.offset.expectedRundownEnd = null;
|
||||||
|
runtimeState.offset.expectedGroupEnd = null;
|
||||||
|
runtimeState.offset.expectedFlagStart = null;
|
||||||
|
|
||||||
runtimeState._end = null;
|
runtimeState._end = null;
|
||||||
runtimeState.runtime.selectedEventIndex = null;
|
|
||||||
|
|
||||||
runtimeState.timer.playback = Playback.Stop;
|
runtimeState.timer.playback = Playback.Stop;
|
||||||
runtimeState.clock = timeNow();
|
runtimeState.clock = timeNow();
|
||||||
@@ -168,25 +175,23 @@ function patchTimer(newState: Partial<TimerState & RestorePoint>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type RundownData = {
|
/**
|
||||||
|
* Utility, allows updating data derived from the rundown
|
||||||
|
* @param playableRundown
|
||||||
|
*/
|
||||||
|
export function updateRundownData(rundownData: {
|
||||||
numEvents: number; // length of rundown filtered for timed events
|
numEvents: number; // length of rundown filtered for timed events
|
||||||
firstStart: MaybeNumber;
|
firstStart: MaybeNumber;
|
||||||
lastEnd: MaybeNumber;
|
lastEnd: MaybeNumber;
|
||||||
totalDelay: number;
|
totalDelay: number;
|
||||||
totalDuration: number;
|
totalDuration: number;
|
||||||
};
|
}) {
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility, allows updating data derived from the rundown
|
|
||||||
* @param playableRundown
|
|
||||||
*/
|
|
||||||
export function updateRundownData(rundownData: RundownData) {
|
|
||||||
// we keep this in private state since there is no UI use case for it
|
// we keep this in private state since there is no UI use case for it
|
||||||
runtimeState._rundown.totalDelay = rundownData.totalDelay;
|
runtimeState._rundown.totalDelay = rundownData.totalDelay;
|
||||||
|
|
||||||
runtimeState.runtime.numEvents = rundownData.numEvents;
|
runtimeState.rundown.numEvents = rundownData.numEvents;
|
||||||
runtimeState.runtime.plannedStart = rundownData.firstStart;
|
runtimeState.rundown.plannedStart = rundownData.firstStart;
|
||||||
runtimeState.runtime.plannedEnd =
|
runtimeState.rundown.plannedEnd =
|
||||||
rundownData.firstStart === null ? null : rundownData.firstStart + rundownData.totalDuration;
|
rundownData.firstStart === null ? null : rundownData.firstStart + rundownData.totalDuration;
|
||||||
getExpectedTimes();
|
getExpectedTimes();
|
||||||
}
|
}
|
||||||
@@ -222,22 +227,19 @@ export function load(
|
|||||||
runtimeState.timer.playback = Playback.Armed;
|
runtimeState.timer.playback = Playback.Armed;
|
||||||
runtimeState.timer.duration = calculateDuration(event.timeStart, event.timeEnd);
|
runtimeState.timer.duration = calculateDuration(event.timeStart, event.timeEnd);
|
||||||
runtimeState.timer.current = getCurrent(runtimeState);
|
runtimeState.timer.current = getCurrent(runtimeState);
|
||||||
runtimeState.runtime.numEvents = metadata.timedEventOrder.length;
|
runtimeState.rundown.numEvents = metadata.timedEventOrder.length;
|
||||||
|
|
||||||
// patch with potential provided data
|
// patch with potential provided data
|
||||||
if (initialData) {
|
if (initialData) {
|
||||||
patchTimer(initialData);
|
patchTimer(initialData);
|
||||||
const firstStart = initialData?.firstStart;
|
const firstStart = initialData?.firstStart;
|
||||||
if (firstStart === null || typeof firstStart === 'number') {
|
if (firstStart === null || typeof firstStart === 'number') {
|
||||||
runtimeState.runtime.actualStart = firstStart;
|
runtimeState.rundown.actualStart = firstStart;
|
||||||
const { offsetAbs, offsetRel } = getRuntimeOffset(runtimeState);
|
const { absolute, relative } = getRuntimeOffset(runtimeState);
|
||||||
runtimeState.runtime.offsetAbs = offsetAbs;
|
runtimeState.offset.absolute = absolute;
|
||||||
runtimeState.runtime.offsetRel = offsetRel;
|
runtimeState.offset.relative = relative;
|
||||||
getExpectedTimes();
|
getExpectedTimes();
|
||||||
}
|
}
|
||||||
if (typeof initialData.groupStartAt === 'number' && runtimeState.groupNow) {
|
|
||||||
runtimeState.groupNow.startedAt = initialData.groupStartAt;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return event.id === runtimeState.eventNow?.id;
|
return event.id === runtimeState.eventNow?.id;
|
||||||
}
|
}
|
||||||
@@ -248,17 +250,17 @@ export function load(
|
|||||||
export function loadNow(
|
export function loadNow(
|
||||||
rundown: Rundown,
|
rundown: Rundown,
|
||||||
metadata: RundownMetadata,
|
metadata: RundownMetadata,
|
||||||
eventIndex: MaybeNumber = runtimeState.runtime.selectedEventIndex,
|
eventIndex: MaybeNumber = runtimeState.rundown.selectedEventIndex,
|
||||||
) {
|
) {
|
||||||
if (eventIndex === null) {
|
if (eventIndex === null) {
|
||||||
// reset the state to indicate there is no selection
|
// reset the state to indicate there is no selection
|
||||||
runtimeState.runtime.selectedEventIndex = null;
|
runtimeState.rundown.selectedEventIndex = null;
|
||||||
runtimeState.eventNow = null;
|
runtimeState.eventNow = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const event = rundown.entries[metadata.timedEventOrder[eventIndex]] as PlayableEvent;
|
const event = rundown.entries[metadata.timedEventOrder[eventIndex]] as PlayableEvent;
|
||||||
runtimeState.runtime.selectedEventIndex = eventIndex;
|
runtimeState.rundown.selectedEventIndex = eventIndex;
|
||||||
runtimeState.eventNow = event;
|
runtimeState.eventNow = event;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -268,7 +270,7 @@ export function loadNow(
|
|||||||
export function loadNext(
|
export function loadNext(
|
||||||
rundown: Rundown,
|
rundown: Rundown,
|
||||||
metadata: RundownMetadata,
|
metadata: RundownMetadata,
|
||||||
eventIndex: MaybeNumber = runtimeState.runtime.selectedEventIndex,
|
eventIndex: MaybeNumber = runtimeState.rundown.selectedEventIndex,
|
||||||
) {
|
) {
|
||||||
if (eventIndex === null) {
|
if (eventIndex === null) {
|
||||||
// reset the state to indicate there is no future event
|
// reset the state to indicate there is no future event
|
||||||
@@ -314,7 +316,7 @@ export function updateLoaded(event?: PlayableEvent): string | undefined {
|
|||||||
|
|
||||||
// handle edge cases with roll
|
// handle edge cases with roll
|
||||||
if (runtimeState.timer.playback === Playback.Roll) {
|
if (runtimeState.timer.playback === Playback.Roll) {
|
||||||
const offsetClock = runtimeState.clock - runtimeState.runtime.offsetAbs;
|
const offsetClock = runtimeState.clock - runtimeState.offset.absolute;
|
||||||
// if waiting to roll, we update the targets and potentially start the timer
|
// if waiting to roll, we update the targets and potentially start the timer
|
||||||
if (runtimeState._timer.secondaryTarget !== null) {
|
if (runtimeState._timer.secondaryTarget !== null) {
|
||||||
if (runtimeState.eventNow.timeStart < offsetClock && offsetClock < runtimeState.eventNow.timeEnd) {
|
if (runtimeState.eventNow.timeStart < offsetClock && offsetClock < runtimeState.eventNow.timeEnd) {
|
||||||
@@ -358,13 +360,6 @@ export function updateAll(rundown: Rundown, metadata: RundownMetadata) {
|
|||||||
loadNext(rundown, metadata, eventNowIndex >= 0 ? eventNowIndex : undefined);
|
loadNext(rundown, metadata, eventNowIndex >= 0 ? eventNowIndex : undefined);
|
||||||
updateLoaded(runtimeState.eventNow ?? undefined);
|
updateLoaded(runtimeState.eventNow ?? undefined);
|
||||||
loadGroupFlagAndEnd(rundown, metadata, eventNowIndex);
|
loadGroupFlagAndEnd(rundown, metadata, eventNowIndex);
|
||||||
|
|
||||||
// catch the edge case where the playing event is moved into a group
|
|
||||||
// if the group already has a start value then we dont overwrite it
|
|
||||||
// use the start value from the timer, so if the we are not running it will be set to null
|
|
||||||
if (runtimeState.groupNow && runtimeState.groupNow.startedAt === null) {
|
|
||||||
runtimeState.groupNow.startedAt = runtimeState.timer.startedAt;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function start(state: RuntimeState = runtimeState): boolean {
|
export function start(state: RuntimeState = runtimeState): boolean {
|
||||||
@@ -389,31 +384,26 @@ export function start(state: RuntimeState = runtimeState): boolean {
|
|||||||
state.timer.startedAt = state.clock;
|
state.timer.startedAt = state.clock;
|
||||||
}
|
}
|
||||||
|
|
||||||
// update group start time
|
|
||||||
if (state.groupNow && state.groupNow.startedAt === null) {
|
|
||||||
state.groupNow.startedAt = state.clock;
|
|
||||||
}
|
|
||||||
|
|
||||||
state.timer.playback = Playback.Play;
|
state.timer.playback = Playback.Play;
|
||||||
state.timer.expectedFinish = getExpectedFinish(state);
|
state.timer.expectedFinish = getExpectedFinish(state);
|
||||||
state.timer.elapsed = 0;
|
state.timer.elapsed = 0;
|
||||||
|
|
||||||
if (state.runtime.actualStart === null) {
|
if (state.rundown.actualStart === null) {
|
||||||
state.runtime.actualStart = state.clock;
|
state.rundown.actualStart = state.clock;
|
||||||
}
|
}
|
||||||
|
|
||||||
// update timer phase
|
// update timer phase
|
||||||
runtimeState.timer.phase = getTimerPhase(runtimeState);
|
runtimeState.timer.phase = getTimerPhase(runtimeState);
|
||||||
|
|
||||||
// update offset
|
// update offset
|
||||||
const { offsetAbs, offsetRel } = getRuntimeOffset(runtimeState);
|
const { absolute, relative } = getRuntimeOffset(runtimeState);
|
||||||
runtimeState.runtime.offsetAbs = offsetAbs;
|
runtimeState.offset.absolute = absolute;
|
||||||
runtimeState.runtime.offsetRel = offsetRel;
|
runtimeState.offset.relative = relative;
|
||||||
|
|
||||||
// as long as there is a timer, we need an planned end
|
// as long as there is a timer, we need an planned end
|
||||||
// eslint-disable-next-line no-unused-labels -- dev code path
|
// eslint-disable-next-line no-unused-labels -- dev code path
|
||||||
DEV: {
|
DEV: {
|
||||||
if (state.runtime.plannedEnd === null) {
|
if (state.rundown.plannedEnd === null) {
|
||||||
throw new Error('runtimeState.start: invalid state received');
|
throw new Error('runtimeState.start: invalid state received');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -477,9 +467,9 @@ export function addTime(amount: number) {
|
|||||||
runtimeState.timer.current += amount;
|
runtimeState.timer.current += amount;
|
||||||
|
|
||||||
// update runtime delays: over - under
|
// update runtime delays: over - under
|
||||||
const { offsetAbs, offsetRel } = getRuntimeOffset(runtimeState);
|
const { absolute, relative } = getRuntimeOffset(runtimeState);
|
||||||
runtimeState.runtime.offsetAbs = offsetAbs;
|
runtimeState.offset.absolute = absolute;
|
||||||
runtimeState.runtime.offsetRel = offsetRel;
|
runtimeState.offset.relative = relative;
|
||||||
getExpectedTimes();
|
getExpectedTimes();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -524,9 +514,9 @@ export function update(): UpdateResult {
|
|||||||
runtimeState.timer.elapsed = runtimeState.timer.duration - runtimeState.timer.current;
|
runtimeState.timer.elapsed = runtimeState.timer.duration - runtimeState.timer.current;
|
||||||
|
|
||||||
// update runtime, needs up-to-date timer state
|
// update runtime, needs up-to-date timer state
|
||||||
const { offsetAbs, offsetRel } = getRuntimeOffset(runtimeState);
|
const { absolute, relative } = getRuntimeOffset(runtimeState);
|
||||||
runtimeState.runtime.offsetAbs = offsetAbs;
|
runtimeState.offset.absolute = absolute;
|
||||||
runtimeState.runtime.offsetRel = offsetRel;
|
runtimeState.offset.relative = relative;
|
||||||
|
|
||||||
const finishedNow =
|
const finishedNow =
|
||||||
Boolean(runtimeState._timer.forceFinish) ||
|
Boolean(runtimeState._timer.forceFinish) ||
|
||||||
@@ -556,7 +546,7 @@ export function update(): UpdateResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// account for offset
|
// account for offset
|
||||||
const offsetClock = runtimeState.clock + runtimeState.runtime.offsetAbs;
|
const offsetClock = runtimeState.clock + runtimeState.offset.absolute;
|
||||||
runtimeState.timer.phase = TimerPhase.Pending;
|
runtimeState.timer.phase = TimerPhase.Pending;
|
||||||
|
|
||||||
if (hasCrossedMidnight) {
|
if (hasCrossedMidnight) {
|
||||||
@@ -576,7 +566,7 @@ export function roll(
|
|||||||
offset = 0,
|
offset = 0,
|
||||||
): { eventId: MaybeString; didStart: boolean } {
|
): { eventId: MaybeString; didStart: boolean } {
|
||||||
// 1. if an event is running, we simply take over the playback
|
// 1. if an event is running, we simply take over the playback
|
||||||
if (runtimeState.timer.playback === Playback.Play && runtimeState.runtime.selectedEventIndex !== null) {
|
if (runtimeState.timer.playback === Playback.Play && runtimeState.rundown.selectedEventIndex !== null) {
|
||||||
runtimeState.timer.playback = Playback.Roll;
|
runtimeState.timer.playback = Playback.Roll;
|
||||||
return { eventId: runtimeState.eventNow?.id ?? null, didStart: false };
|
return { eventId: runtimeState.eventNow?.id ?? null, didStart: false };
|
||||||
}
|
}
|
||||||
@@ -590,7 +580,7 @@ export function roll(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
runtimeState.runtime.offsetAbs = offset;
|
runtimeState.offset.absolute = offset;
|
||||||
runtimeState.timer.playback = Playback.Roll;
|
runtimeState.timer.playback = Playback.Roll;
|
||||||
|
|
||||||
// account for event that finishes the day after
|
// account for event that finishes the day after
|
||||||
@@ -601,7 +591,7 @@ export function roll(
|
|||||||
runtimeState.timer.expectedFinish = normalisedEndTime;
|
runtimeState.timer.expectedFinish = normalisedEndTime;
|
||||||
|
|
||||||
//account for offset
|
//account for offset
|
||||||
const offsetClock = runtimeState.clock - runtimeState.runtime.offsetAbs;
|
const offsetClock = runtimeState.clock - runtimeState.offset.absolute;
|
||||||
|
|
||||||
// state catch up
|
// state catch up
|
||||||
runtimeState.timer.duration = calculateDuration(runtimeState.eventNow.timeStart, normalisedEndTime);
|
runtimeState.timer.duration = calculateDuration(runtimeState.eventNow.timeStart, normalisedEndTime);
|
||||||
@@ -613,12 +603,8 @@ export function roll(
|
|||||||
if (isNow) {
|
if (isNow) {
|
||||||
runtimeState.timer.startedAt = runtimeState.clock;
|
runtimeState.timer.startedAt = runtimeState.clock;
|
||||||
|
|
||||||
// update runtime
|
if (runtimeState.rundown.actualStart === null) {
|
||||||
if (runtimeState.groupNow && runtimeState.groupNow.startedAt === null) {
|
runtimeState.rundown.actualStart = runtimeState.clock;
|
||||||
runtimeState.groupNow.startedAt = runtimeState.clock;
|
|
||||||
}
|
|
||||||
if (!runtimeState.runtime.actualStart) {
|
|
||||||
runtimeState.runtime.actualStart = runtimeState.clock;
|
|
||||||
}
|
}
|
||||||
runtimeState.timer.secondaryTimer = null;
|
runtimeState.timer.secondaryTimer = null;
|
||||||
} else {
|
} else {
|
||||||
@@ -639,8 +625,8 @@ export function roll(
|
|||||||
clearEventData();
|
clearEventData();
|
||||||
|
|
||||||
//account for offset but we only keep it if passed to us
|
//account for offset but we only keep it if passed to us
|
||||||
runtimeState.runtime.offsetAbs = offset;
|
runtimeState.offset.absolute = offset;
|
||||||
const offsetClock = runtimeState.clock - runtimeState.runtime.offsetAbs;
|
const offsetClock = runtimeState.clock - runtimeState.offset.absolute;
|
||||||
|
|
||||||
const { index, isPending } = loadRoll(rundown, metadata, offsetClock);
|
const { index, isPending } = loadRoll(rundown, metadata, offsetClock);
|
||||||
|
|
||||||
@@ -651,7 +637,7 @@ export function roll(
|
|||||||
|
|
||||||
// update roll state
|
// update roll state
|
||||||
runtimeState.timer.playback = Playback.Roll;
|
runtimeState.timer.playback = Playback.Roll;
|
||||||
runtimeState.runtime.numEvents = metadata.timedEventOrder.length;
|
runtimeState.rundown.numEvents = metadata.timedEventOrder.length;
|
||||||
|
|
||||||
// in roll mode spec, there should always be something to load
|
// in roll mode spec, there should always be something to load
|
||||||
// as long as playableEvents is not empty
|
// as long as playableEvents is not empty
|
||||||
@@ -676,12 +662,6 @@ export function roll(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// there is something to run, load event
|
// there is something to run, load event
|
||||||
|
|
||||||
// update runtime
|
|
||||||
if (runtimeState.groupNow && runtimeState.groupNow.startedAt === null) {
|
|
||||||
runtimeState.groupNow.startedAt = runtimeState.clock;
|
|
||||||
}
|
|
||||||
|
|
||||||
// event will finish on time
|
// event will finish on time
|
||||||
// account for event that finishes the day after
|
// account for event that finishes the day after
|
||||||
const endTime =
|
const endTime =
|
||||||
@@ -700,25 +680,26 @@ export function roll(
|
|||||||
runtimeState.timer.elapsed = 0;
|
runtimeState.timer.elapsed = 0;
|
||||||
|
|
||||||
// update runtime
|
// update runtime
|
||||||
runtimeState.runtime.actualStart = runtimeState.clock;
|
runtimeState.rundown.actualStart = runtimeState.clock;
|
||||||
return { eventId: runtimeState.eventNow.id, didStart: true };
|
return { eventId: runtimeState.eventNow.id, didStart: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* calculates and sets values directly in state
|
* calculates and sets values directly in state
|
||||||
* - runtime.expectedEnd
|
* - offset.expectedRundownEnd
|
||||||
* - groupNow.expectedEnd
|
* - offset.expectedGroupEnd
|
||||||
* - nextFlag.expectedStart
|
* - offset.expectedFlagStart
|
||||||
*/
|
*/
|
||||||
function getExpectedTimes(state = runtimeState) {
|
function getExpectedTimes(state = runtimeState) {
|
||||||
const { offsetMode, offsetAbs, offsetRel, plannedStart, actualStart } = state.runtime;
|
const { offset } = state;
|
||||||
|
const { plannedStart, actualStart } = state.rundown;
|
||||||
const { eventNow } = state;
|
const { eventNow } = state;
|
||||||
|
|
||||||
if (!eventNow) return;
|
if (!eventNow) return;
|
||||||
|
|
||||||
state.runtime.expectedEnd = null;
|
state.offset.expectedRundownEnd = null;
|
||||||
if (state.groupNow) {
|
if (state.groupNow) {
|
||||||
state.groupNow.expectedEnd = null;
|
state.offset.expectedGroupEnd = null;
|
||||||
const { _group } = state;
|
const { _group } = state;
|
||||||
if (_group !== null) {
|
if (_group !== null) {
|
||||||
const { event: lastEvent, accumulatedGap, isLinkedToLoaded } = _group;
|
const { event: lastEvent, accumulatedGap, isLinkedToLoaded } = _group;
|
||||||
@@ -726,17 +707,17 @@ function getExpectedTimes(state = runtimeState) {
|
|||||||
currentDay: eventNow.dayOffset,
|
currentDay: eventNow.dayOffset,
|
||||||
totalGap: accumulatedGap,
|
totalGap: accumulatedGap,
|
||||||
isLinkedToLoaded,
|
isLinkedToLoaded,
|
||||||
offsetMode,
|
mode: offset.mode,
|
||||||
offset: offsetMode === OffsetMode.Absolute ? offsetAbs : offsetRel,
|
offset: offset.mode === OffsetMode.Absolute ? offset.absolute : offset.relative,
|
||||||
plannedStart,
|
plannedStart,
|
||||||
actualStart,
|
actualStart,
|
||||||
});
|
});
|
||||||
state.groupNow.expectedEnd = lastEventExpectedStart + lastEvent.duration;
|
state.offset.expectedGroupEnd = lastEventExpectedStart + lastEvent.duration;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.nextFlag) {
|
if (state.eventFlag) {
|
||||||
state.nextFlag.expectedStart = null;
|
state.offset.expectedFlagStart = null;
|
||||||
const { _flag } = state;
|
const { _flag } = state;
|
||||||
if (_flag) {
|
if (_flag) {
|
||||||
const { event, accumulatedGap, isLinkedToLoaded } = _flag;
|
const { event, accumulatedGap, isLinkedToLoaded } = _flag;
|
||||||
@@ -744,12 +725,12 @@ function getExpectedTimes(state = runtimeState) {
|
|||||||
currentDay: eventNow.dayOffset,
|
currentDay: eventNow.dayOffset,
|
||||||
totalGap: accumulatedGap,
|
totalGap: accumulatedGap,
|
||||||
isLinkedToLoaded,
|
isLinkedToLoaded,
|
||||||
offsetMode,
|
mode: offset.mode,
|
||||||
offset: offsetMode === OffsetMode.Absolute ? offsetAbs : offsetRel,
|
offset: offset.mode === OffsetMode.Absolute ? offset.absolute : offset.relative,
|
||||||
plannedStart,
|
plannedStart,
|
||||||
actualStart,
|
actualStart,
|
||||||
});
|
});
|
||||||
state.nextFlag.expectedStart = expectedStart;
|
state.offset.expectedFlagStart = expectedStart;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -759,14 +740,14 @@ function getExpectedTimes(state = runtimeState) {
|
|||||||
currentDay: eventNow.dayOffset,
|
currentDay: eventNow.dayOffset,
|
||||||
totalGap: accumulatedGap,
|
totalGap: accumulatedGap,
|
||||||
isLinkedToLoaded,
|
isLinkedToLoaded,
|
||||||
offsetMode,
|
mode: offset.mode,
|
||||||
offset: offsetMode === OffsetMode.Absolute ? offsetAbs : offsetRel,
|
offset: offset.mode === OffsetMode.Absolute ? offset.absolute : offset.relative,
|
||||||
plannedStart,
|
plannedStart,
|
||||||
actualStart,
|
actualStart,
|
||||||
});
|
});
|
||||||
state.runtime.expectedEnd = expectedStart + event.duration;
|
state.offset.expectedRundownEnd = expectedStart + event.duration;
|
||||||
} else {
|
} else {
|
||||||
state.runtime.expectedEnd = null;
|
state.offset.expectedRundownEnd = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -790,7 +771,6 @@ export function loadGroupFlagAndEnd(
|
|||||||
|
|
||||||
// if we don't have a any flags in the rundown then no need to look for it
|
// if we don't have a any flags in the rundown then no need to look for it
|
||||||
let foundFlag = !flagsPresent;
|
let foundFlag = !flagsPresent;
|
||||||
let foundNextGroup = false;
|
|
||||||
// if we don't have a last event for the group there is no need to find its end time
|
// if we don't have a last event for the group there is no need to find its end time
|
||||||
let foundGroupEnd = lastEventInGroup === null;
|
let foundGroupEnd = lastEventInGroup === null;
|
||||||
|
|
||||||
@@ -809,7 +789,7 @@ export function loadGroupFlagAndEnd(
|
|||||||
// and the loaded event is not allowed to be the next flag
|
// and the loaded event is not allowed to be the next flag
|
||||||
if (!foundFlag && metadata.flags.includes(entry.id)) {
|
if (!foundFlag && metadata.flags.includes(entry.id)) {
|
||||||
foundFlag = true;
|
foundFlag = true;
|
||||||
state.nextFlag = { id: entry.id, expectedStart: null };
|
state.eventFlag = entry as PlayableEvent; // we know it is playable as it is coming from the playableEventOrder list
|
||||||
state._flag = { event: entry, isLinkedToLoaded, accumulatedGap };
|
state._flag = { event: entry, isLinkedToLoaded, accumulatedGap };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -818,11 +798,6 @@ export function loadGroupFlagAndEnd(
|
|||||||
foundGroupEnd = true;
|
foundGroupEnd = true;
|
||||||
state._group = { event: lastEventInGroup, isLinkedToLoaded, accumulatedGap };
|
state._group = { event: lastEventInGroup, isLinkedToLoaded, accumulatedGap };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!foundNextGroup && entry.parent !== currentGroupId) {
|
|
||||||
foundNextGroup = true;
|
|
||||||
state.groupNext = entry.parent;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -832,26 +807,24 @@ export function loadGroupFlagAndEnd(
|
|||||||
state._end = { event: lastEvent, isLinkedToLoaded, accumulatedGap };
|
state._end = { event: lastEvent, isLinkedToLoaded, accumulatedGap };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!foundFlag) state.nextFlag = null;
|
if (!foundFlag) state.eventFlag = null;
|
||||||
|
|
||||||
if (currentGroupId === null) {
|
if ((state.groupNow?.id ?? null) !== currentGroupId) {
|
||||||
state.groupNow = null;
|
|
||||||
} else if ((state.groupNow != null && state.groupNow.id != currentGroupId) || state.groupNow == null) {
|
|
||||||
// we went into a new group - and it is different from the one we might have come from
|
// we went into a new group - and it is different from the one we might have come from
|
||||||
// the id is set here, the start time is set when starting events
|
state.offset.expectedGroupEnd = null;
|
||||||
state.groupNow = { id: currentGroupId, startedAt: null, expectedEnd: null };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state.groupNow = currentGroupId ? (entries[currentGroupId] as OntimeGroup) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const resetMetaData = (state = runtimeState) => {
|
const resetMetaData = (state = runtimeState) => {
|
||||||
state.groupNow = null;
|
state.groupNow = null;
|
||||||
state.groupNext = null;
|
|
||||||
state._group = null;
|
state._group = null;
|
||||||
state.nextFlag = null;
|
state.eventFlag = null;
|
||||||
state._flag = null;
|
state._flag = null;
|
||||||
state._end = null;
|
state._end = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function setOffsetMode(mode: OffsetMode) {
|
export function setOffsetMode(mode: OffsetMode) {
|
||||||
runtimeState.runtime.offsetMode = mode;
|
runtimeState.offset.mode = mode;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
import type { MaybeNumber } from '../../utils/utils.type.js';
|
|
||||||
import type { EntryId } from '../core/OntimeEntry.js';
|
|
||||||
|
|
||||||
export type GroupState = {
|
|
||||||
id: EntryId;
|
|
||||||
startedAt: MaybeNumber;
|
|
||||||
expectedEnd: MaybeNumber;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type UpcomingEntry = {
|
|
||||||
id: EntryId;
|
|
||||||
expectedStart: MaybeNumber;
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import type { MaybeNumber } from '../../utils/utils.type.js';
|
||||||
|
|
||||||
|
export enum OffsetMode {
|
||||||
|
Absolute = 'absolute',
|
||||||
|
Relative = 'relative',
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Offset = {
|
||||||
|
absolute: number; // a positive value means that we are in over time aka behind schedule
|
||||||
|
relative: number;
|
||||||
|
mode: OffsetMode;
|
||||||
|
expectedGroupEnd: MaybeNumber;
|
||||||
|
expectedRundownEnd: MaybeNumber;
|
||||||
|
expectedFlagStart: MaybeNumber;
|
||||||
|
};
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import type { MaybeNumber } from '../../utils/utils.type.js';
|
||||||
|
|
||||||
|
export type RundownState = {
|
||||||
|
selectedEventIndex: MaybeNumber;
|
||||||
|
numEvents: number;
|
||||||
|
plannedStart: MaybeNumber;
|
||||||
|
actualStart: MaybeNumber;
|
||||||
|
plannedEnd: MaybeNumber;
|
||||||
|
};
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import type { MaybeNumber } from '../../utils/utils.type.js';
|
|
||||||
|
|
||||||
export enum OffsetMode {
|
|
||||||
Absolute = 'absolute',
|
|
||||||
Relative = 'relative',
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Runtime = {
|
|
||||||
selectedEventIndex: MaybeNumber;
|
|
||||||
numEvents: number;
|
|
||||||
offsetAbs: number; // a positive value means that we are in over time aka behind schedule
|
|
||||||
offsetRel: number;
|
|
||||||
plannedStart: MaybeNumber;
|
|
||||||
actualStart: MaybeNumber;
|
|
||||||
plannedEnd: MaybeNumber;
|
|
||||||
expectedEnd: MaybeNumber;
|
|
||||||
offsetMode: OffsetMode;
|
|
||||||
};
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { SimpleDirection, SimplePlayback } from './AuxTimer.type.js';
|
import { SimpleDirection, SimplePlayback } from './AuxTimer.type.js';
|
||||||
|
import { OffsetMode } from './Offset.type.js';
|
||||||
import { Playback } from './Playback.type.js';
|
import { Playback } from './Playback.type.js';
|
||||||
import { OffsetMode } from './Runtime.type.js';
|
|
||||||
import type { RuntimeStore } from './RuntimeStore.type.js';
|
import type { RuntimeStore } from './RuntimeStore.type.js';
|
||||||
import { TimerPhase } from './TimerState.type.js';
|
import { TimerPhase } from './TimerState.type.js';
|
||||||
|
|
||||||
@@ -27,22 +27,25 @@ export const runtimeStorePlaceholder: Readonly<RuntimeStore> = {
|
|||||||
},
|
},
|
||||||
secondary: '',
|
secondary: '',
|
||||||
},
|
},
|
||||||
runtime: {
|
rundown: {
|
||||||
selectedEventIndex: null, // changes if rundown changes or we load a new event
|
selectedEventIndex: null, // changes if rundown changes or we load a new event
|
||||||
numEvents: 0, // change initiated by user
|
numEvents: 0, // change initiated by user
|
||||||
offsetAbs: 0, // changes at runtime
|
|
||||||
offsetRel: 0, // changes at runtime
|
|
||||||
plannedStart: 0, // only changes if event changes
|
plannedStart: 0, // only changes if event changes
|
||||||
plannedEnd: 0, // only changes if event changes, overflows over dayInMs
|
plannedEnd: 0, // only changes if event changes, overflows over dayInMs
|
||||||
actualStart: null, // set once we start the timer
|
actualStart: null, // set once we start the timer
|
||||||
expectedEnd: null, // changes with runtime, based on offset, overflows over dayInMs
|
},
|
||||||
offsetMode: OffsetMode.Absolute,
|
offset: {
|
||||||
|
absolute: 0, // changes at runtime
|
||||||
|
relative: 0, // changes at runtime
|
||||||
|
mode: OffsetMode.Absolute,
|
||||||
|
expectedFlagStart: null,
|
||||||
|
expectedGroupEnd: null,
|
||||||
|
expectedRundownEnd: null,
|
||||||
},
|
},
|
||||||
groupNow: null,
|
groupNow: null,
|
||||||
groupNext: null,
|
|
||||||
nextFlag: null,
|
|
||||||
eventNow: null,
|
eventNow: null,
|
||||||
eventNext: null,
|
eventNext: null,
|
||||||
|
eventFlag: null,
|
||||||
auxtimer1: {
|
auxtimer1: {
|
||||||
current: 0,
|
current: 0,
|
||||||
direction: SimpleDirection.CountUp,
|
direction: SimpleDirection.CountUp,
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import type { MaybeString } from '../../utils/utils.type.js';
|
import type { OntimeEvent, OntimeGroup } from '../core/OntimeEntry.js';
|
||||||
import type { OntimeEvent } from '../core/OntimeEntry.js';
|
|
||||||
import type { SimpleTimerState } from './AuxTimer.type.js';
|
import type { SimpleTimerState } from './AuxTimer.type.js';
|
||||||
import type { GroupState, UpcomingEntry } from './CurrentGroupState.type.js';
|
|
||||||
import type { MessageState } from './MessageControl.type.js';
|
import type { MessageState } from './MessageControl.type.js';
|
||||||
import type { Runtime } from './Runtime.type.js';
|
import type { Offset } from './Offset.type.js';
|
||||||
|
import type { RundownState } from './RundownState.type.js';
|
||||||
import type { TimerState } from './TimerState.type.js';
|
import type { TimerState } from './TimerState.type.js';
|
||||||
|
|
||||||
export type RuntimeStore = {
|
export type RuntimeStore = {
|
||||||
@@ -15,13 +14,16 @@ export type RuntimeStore = {
|
|||||||
message: MessageState;
|
message: MessageState;
|
||||||
|
|
||||||
// rundown data
|
// rundown data
|
||||||
runtime: Runtime;
|
rundown: RundownState;
|
||||||
|
|
||||||
|
// runtime
|
||||||
|
offset: Offset;
|
||||||
|
|
||||||
|
// relevant entries
|
||||||
eventNow: OntimeEvent | null;
|
eventNow: OntimeEvent | null;
|
||||||
eventNext: OntimeEvent | null;
|
eventNext: OntimeEvent | null;
|
||||||
|
eventFlag: OntimeEvent | null;
|
||||||
groupNow: GroupState | null;
|
groupNow: OntimeGroup | null;
|
||||||
groupNext: MaybeString;
|
|
||||||
nextFlag: UpcomingEntry | null;
|
|
||||||
|
|
||||||
// extra timers
|
// extra timers
|
||||||
auxtimer1: SimpleTimerState;
|
auxtimer1: SimpleTimerState;
|
||||||
|
|||||||
@@ -96,12 +96,12 @@ export { Playback } from './definitions/runtime/Playback.type.js';
|
|||||||
export { TimerLifeCycle, timerLifecycleValues } from './definitions/core/TimerLifecycle.type.js';
|
export { TimerLifeCycle, timerLifecycleValues } from './definitions/core/TimerLifecycle.type.js';
|
||||||
export type { TimerMessage, MessageState, SecondarySource } from './definitions/runtime/MessageControl.type.js';
|
export type { TimerMessage, MessageState, SecondarySource } from './definitions/runtime/MessageControl.type.js';
|
||||||
|
|
||||||
export type { Runtime } from './definitions/runtime/Runtime.type.js';
|
export type { RundownState } from './definitions/runtime/RundownState.type.js';
|
||||||
export { OffsetMode } from './definitions/runtime/Runtime.type.js';
|
export type { Offset } from './definitions/runtime/Offset.type.js';
|
||||||
|
export { OffsetMode } from './definitions/runtime/Offset.type.js';
|
||||||
export type { RuntimeStore } from './definitions/runtime/RuntimeStore.type.js';
|
export type { RuntimeStore } from './definitions/runtime/RuntimeStore.type.js';
|
||||||
export { runtimeStorePlaceholder } from './definitions/runtime/RuntimeStore.js';
|
export { runtimeStorePlaceholder } from './definitions/runtime/RuntimeStore.js';
|
||||||
export { type TimerState, TimerPhase } from './definitions/runtime/TimerState.type.js';
|
export { type TimerState, TimerPhase } from './definitions/runtime/TimerState.type.js';
|
||||||
export type { GroupState, UpcomingEntry } from './definitions/runtime/CurrentGroupState.type.js';
|
|
||||||
|
|
||||||
// ---> Extra Timer
|
// ---> Extra Timer
|
||||||
export { type SimpleTimerState, SimplePlayback, SimpleDirection } from './definitions/runtime/AuxTimer.type.js';
|
export { type SimpleTimerState, SimplePlayback, SimpleDirection } from './definitions/runtime/AuxTimer.type.js';
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ describe('getExpectedStart()', () => {
|
|||||||
currentDay: 0,
|
currentDay: 0,
|
||||||
totalGap: 0,
|
totalGap: 0,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
offsetMode: OffsetMode.Absolute,
|
mode: OffsetMode.Absolute,
|
||||||
actualStart: null,
|
actualStart: null,
|
||||||
plannedStart: null,
|
plannedStart: null,
|
||||||
};
|
};
|
||||||
@@ -34,7 +34,7 @@ describe('getExpectedStart()', () => {
|
|||||||
currentDay: 0,
|
currentDay: 0,
|
||||||
totalGap: 0,
|
totalGap: 0,
|
||||||
offset: 20,
|
offset: 20,
|
||||||
offsetMode: OffsetMode.Absolute,
|
mode: OffsetMode.Absolute,
|
||||||
actualStart: null,
|
actualStart: null,
|
||||||
plannedStart: null,
|
plannedStart: null,
|
||||||
};
|
};
|
||||||
@@ -53,7 +53,7 @@ describe('getExpectedStart()', () => {
|
|||||||
currentDay: 0,
|
currentDay: 0,
|
||||||
totalGap: 0,
|
totalGap: 0,
|
||||||
offset: -10,
|
offset: -10,
|
||||||
offsetMode: OffsetMode.Absolute,
|
mode: OffsetMode.Absolute,
|
||||||
actualStart: null,
|
actualStart: null,
|
||||||
plannedStart: null,
|
plannedStart: null,
|
||||||
};
|
};
|
||||||
@@ -72,7 +72,7 @@ describe('getExpectedStart()', () => {
|
|||||||
currentDay: 0,
|
currentDay: 0,
|
||||||
totalGap: 20,
|
totalGap: 20,
|
||||||
offset: 20,
|
offset: 20,
|
||||||
offsetMode: OffsetMode.Absolute,
|
mode: OffsetMode.Absolute,
|
||||||
actualStart: null,
|
actualStart: null,
|
||||||
plannedStart: null,
|
plannedStart: null,
|
||||||
};
|
};
|
||||||
@@ -91,7 +91,7 @@ describe('getExpectedStart()', () => {
|
|||||||
currentDay: 0,
|
currentDay: 0,
|
||||||
totalGap: 10,
|
totalGap: 10,
|
||||||
offset: 20,
|
offset: 20,
|
||||||
offsetMode: OffsetMode.Absolute,
|
mode: OffsetMode.Absolute,
|
||||||
actualStart: 0,
|
actualStart: 0,
|
||||||
plannedStart: 0,
|
plannedStart: 0,
|
||||||
};
|
};
|
||||||
@@ -109,7 +109,7 @@ describe('getExpectedStart()', () => {
|
|||||||
actualStart: 100,
|
actualStart: 100,
|
||||||
plannedStart: 0,
|
plannedStart: 0,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
offsetMode: OffsetMode.Relative,
|
mode: OffsetMode.Relative,
|
||||||
};
|
};
|
||||||
|
|
||||||
const timeStartEvent2 = 10;
|
const timeStartEvent2 = 10;
|
||||||
@@ -189,7 +189,7 @@ describe('getExpectedStart()', () => {
|
|||||||
actualStart: 100,
|
actualStart: 100,
|
||||||
plannedStart: 0,
|
plannedStart: 0,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
offsetMode: OffsetMode.Relative,
|
mode: OffsetMode.Relative,
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getExpectedStart(testEvent, { ...testState, isLinkedToLoaded: true })).toBe(120);
|
expect(getExpectedStart(testEvent, { ...testState, isLinkedToLoaded: true })).toBe(120);
|
||||||
@@ -213,7 +213,7 @@ describe('getExpectedStart()', () => {
|
|||||||
actualStart: 100,
|
actualStart: 100,
|
||||||
plannedStart: 0,
|
plannedStart: 0,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
offsetMode: OffsetMode.Relative,
|
mode: OffsetMode.Relative,
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(getExpectedStart(testEvent, { ...testState, isLinkedToLoaded: true })).toBe(120);
|
expect(getExpectedStart(testEvent, { ...testState, isLinkedToLoaded: true })).toBe(120);
|
||||||
@@ -236,7 +236,7 @@ describe('getExpectedStart()', () => {
|
|||||||
actualStart: 100,
|
actualStart: 100,
|
||||||
plannedStart: 0,
|
plannedStart: 0,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
offsetMode: OffsetMode.Relative,
|
mode: OffsetMode.Relative,
|
||||||
};
|
};
|
||||||
|
|
||||||
// this event will start the current day
|
// this event will start the current day
|
||||||
@@ -270,7 +270,7 @@ describe('getExpectedStart()', () => {
|
|||||||
actualStart: 100,
|
actualStart: 100,
|
||||||
plannedStart: 0,
|
plannedStart: 0,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
offsetMode: OffsetMode.Relative,
|
mode: OffsetMode.Relative,
|
||||||
isLinkedToLoaded: false,
|
isLinkedToLoaded: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -19,13 +19,13 @@ export function getExpectedStart(
|
|||||||
totalGap: number;
|
totalGap: number;
|
||||||
isLinkedToLoaded: boolean;
|
isLinkedToLoaded: boolean;
|
||||||
offset: number;
|
offset: number;
|
||||||
offsetMode: OffsetMode;
|
mode: OffsetMode;
|
||||||
actualStart: MaybeNumber;
|
actualStart: MaybeNumber;
|
||||||
plannedStart: MaybeNumber;
|
plannedStart: MaybeNumber;
|
||||||
},
|
},
|
||||||
): number {
|
): number {
|
||||||
const { timeStart, dayOffset, delay } = event;
|
const { timeStart, dayOffset, delay } = event;
|
||||||
const { currentDay, totalGap, isLinkedToLoaded, offset, offsetMode, actualStart, plannedStart } = state;
|
const { currentDay, totalGap, isLinkedToLoaded, offset, mode, actualStart, plannedStart } = state;
|
||||||
|
|
||||||
//How many days from the currently running event to this one
|
//How many days from the currently running event to this one
|
||||||
const relativeDayOffset = dayOffset - currentDay;
|
const relativeDayOffset = dayOffset - currentDay;
|
||||||
@@ -37,7 +37,7 @@ export function getExpectedStart(
|
|||||||
|
|
||||||
let relativeStartOffset = 0;
|
let relativeStartOffset = 0;
|
||||||
|
|
||||||
if (offsetMode === OffsetMode.Relative) {
|
if (mode === OffsetMode.Relative) {
|
||||||
relativeStartOffset = (actualStart ?? 0) - (plannedStart ?? 0);
|
relativeStartOffset = (actualStart ?? 0) - (plannedStart ?? 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user