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:
Alex Christoffer Rasmussen
2025-08-14 19:29:25 +02:00
committed by Carlos Valente
parent ed93cf313a
commit 048a9c96ea
26 changed files with 400 additions and 518 deletions
+24 -23
View File
@@ -58,8 +58,8 @@ export const setMessage = {
export const usePlaybackControl = createSelector((state: RuntimeStore) => ({
playback: state.timer.playback,
selectedEventIndex: state.runtime.selectedEventIndex,
numEvents: state.runtime.numEvents,
selectedEventIndex: state.rundown.selectedEventIndex,
numEvents: state.rundown.numEvents,
timerPhase: state.timer.phase,
}));
@@ -150,8 +150,8 @@ export const useClock = createSelector((state: RuntimeStore) => ({
}));
export const useNextFlag = createSelector((state: RuntimeStore) => ({
id: state.nextFlag?.id ?? null,
expectedStart: state.nextFlag?.expectedStart ?? null,
id: state.eventFlag?.id ?? null,
expectedStart: state.offset.expectedFlagStart,
}));
/** Used by the progress bar components */
@@ -162,35 +162,35 @@ export const useProgressData = createSelector((state: RuntimeStore) => ({
timeDanger: state.eventNow?.timeDanger ?? null,
}));
export const useRuntimeOverview = createSelector((state: RuntimeStore) => ({
plannedStart: state.runtime.plannedStart,
actualStart: state.runtime.actualStart,
plannedEnd: state.runtime.plannedEnd,
expectedEnd: state.runtime.expectedEnd,
export const useRundownOverview = createSelector((state: RuntimeStore) => ({
plannedStart: state.rundown.plannedStart,
actualStart: state.rundown.actualStart,
plannedEnd: state.rundown.plannedEnd,
expectedEnd: state.offset.expectedRundownEnd,
}));
export const useRuntimePlaybackOverview = createSelector((state: RuntimeStore) => ({
playback: state.timer.playback,
clock: state.clock,
numEvents: state.runtime.numEvents,
selectedEventIndex: state.runtime.selectedEventIndex,
offset: state.runtime.offsetMode === OffsetMode.Absolute ? state.runtime.offsetAbs : state.runtime.offsetRel,
numEvents: state.rundown.numEvents,
selectedEventIndex: state.rundown.selectedEventIndex,
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) => ({
clock: state.clock,
offset: state.runtime.offsetAbs,
offset: state.offset.absolute,
}));
export const useExpectedStartData = createSelector((state: RuntimeStore) => ({
offset: state.runtime.offsetMode === OffsetMode.Absolute ? state.runtime.offsetAbs : state.runtime.offsetRel,
offsetMode: state.runtime.offsetMode,
offset: state.offset.mode === OffsetMode.Absolute ? state.offset.absolute : state.offset.relative,
mode: state.offset.mode,
currentDay: state.eventNow?.dayOffset ?? 0,
actualStart: state.runtime.actualStart,
plannedStart: state.runtime.plannedStart,
actualStart: state.rundown.actualStart,
plannedStart: state.rundown.plannedStart,
clock: state.clock,
}));
@@ -199,7 +199,7 @@ export const useCurrentDay = createSelector((state: RuntimeStore) => ({
}));
export const useRuntimeOffset = createSelector((state: RuntimeStore) => ({
offset: state.runtime.offsetAbs,
offset: state.offset.absolute,
}));
export const usePing = createSelector((state: RuntimeStore) => ({
@@ -212,7 +212,7 @@ export const useIsOnline = 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);
@@ -251,7 +251,7 @@ export const useCountdownSocket = createSelector((state: RuntimeStore) => ({
export const useBackstageSocket = createSelector((state: RuntimeStore) => ({
eventNext: state.eventNext,
eventNow: state.eventNow,
runtime: state.runtime,
rundown: state.rundown,
selectedEventId: state.eventNow?.id ?? null,
time: state.timer,
}));
@@ -266,10 +266,11 @@ export const useStudioTimersSocket = createSelector((state: RuntimeStore) => ({
eventNow: state.eventNow,
message: state.message,
time: state.timer,
runtime: state.runtime,
offset: state.offset,
rundown: state.rundown,
}));
export const useTimelineSocket = createSelector((state: RuntimeStore) => ({
clock: state.clock,
offsetAbs: state.runtime.offsetAbs,
offsetAbs: state.offset.absolute,
}));
+2 -2
View File
@@ -145,12 +145,12 @@ export function useTimeUntilExpectedStart(
isLinkedToLoaded: boolean;
},
): number {
const { offset, currentDay, offsetMode, actualStart, plannedStart, clock } = useExpectedStartData();
const { offset, currentDay, mode, actualStart, plannedStart, clock } = useExpectedStartData();
if (event === null) return 0;
const expectedStart = getExpectedStart(
{ ...event },
{ ...state, currentDay, offset, offsetMode, actualStart, plannedStart },
{ ...state, currentDay, offset, mode, actualStart, plannedStart },
);
return expectedStart - clock;
}
@@ -18,15 +18,16 @@ const staticAutocompleteOptions = [
'{{timer.expectedFinish}}',
'{{timer.secondaryTimer}}',
'{{timer.startedAt}}',
'{{runtime.selectedEventIndex}}',
'{{runtime.numEvents}}',
'{{runtime.offset}}',
'{{runtime.plannedStart}}',
'{{runtime.plannedEnd}}',
'{{runtime.actualStart}}',
'{{runtime.expectedEnd}}',
'{{currentGroup.id}}',
'{{currentGroup.startedAt}}',
'{{rundown.selectedEventIndex}}',
'{{rundown.numEvents}}',
'{{rundown.plannedStart}}',
'{{rundown.plannedEnd}}',
'{{rundown.actualStart}}',
'{{offset.absolute}}',
'{{offset.relative}}',
'{{offset.expectedRundownEnd}}',
'{{offset.expectedGroupEnd}}',
'{{offset.expectedFlagStart}}',
];
const eventStaticPropertiesNow = [
@@ -16,7 +16,7 @@ import {
useClock,
useCurrentGroupId,
useNextFlag,
useRuntimeOverview,
useRundownOverview,
useRuntimePlaybackOverview,
useTimer,
} from '../../../common/hooks/useSocket';
@@ -31,7 +31,7 @@ import { OverUnder, TimeColumn } from './TimeLayout';
import style from './TimeElements.module.scss';
export function StartTimes() {
const { plannedEnd, plannedStart, actualStart, expectedEnd } = useRuntimeOverview();
const { plannedEnd, plannedStart, actualStart, expectedEnd } = useRundownOverview();
const plannedStartText = plannedStart === null ? timerPlaceholder : formatTime(plannedStart);
@@ -44,7 +44,7 @@ export default function BackstageLoader() {
function Backstage({ events, customFields, projectData, isMirrored, settings }: BackstageData) {
const { getLocalizedString } = useTranslation();
const { secondarySource, extraInfo } = useBackstageOptions();
const { eventNext, eventNow, runtime, selectedEventId, time } = useBackstageSocket();
const { eventNext, eventNow, rundown, selectedEventId, time } = useBackstageSocket();
const [blinkClass, setBlinkClass] = useState(false);
const { height: screenHeight } = useViewportSize();
@@ -76,13 +76,13 @@ function Backstage({ events, customFields, projectData, isMirrored, settings }:
const scheduledStart = (() => {
if (showNow) 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 = (() => {
if (showNow) 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 });
@@ -17,9 +17,13 @@ interface StudioTimersProps {
export default function StudioTimers({ viewSettings }: StudioTimersProps) {
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 eventNextTitle = eventNext?.title || '-';
const formattedTimerMessage = (message.timer.visible && message.timer.text) || '-';
@@ -33,7 +37,7 @@ export default function StudioTimers({ viewSettings }: StudioTimersProps) {
time.phase === TimerPhase.Danger,
);
const offsetState = getOffsetState(runtime.offsetAbs);
const offsetState = getOffsetState(offset.absolute);
return (
<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 { getOffsetText } from '../../common/utils/offset';
import { formatTime } from '../../common/utils/time';
const timeFormat = { format12: 'h:mm a', format24: 'HH:mm' };
export function getFormattedScheduleTimes(runtime: Runtime) {
const correctedOffset = runtime.offsetAbs !== null ? runtime.offsetAbs * -1 : null;
export function getFormattedScheduleTimes(data: {
offset: number;
actualStart: MaybeNumber;
expectedEnd: MaybeNumber;
}) {
return {
actualStart: formatTime(runtime.actualStart, timeFormat),
expectedEnd: formatTime(runtime.expectedEnd, timeFormat),
offset: getOffsetText(correctedOffset),
actualStart: formatTime(data.actualStart, timeFormat),
expectedEnd: formatTime(data.expectedEnd, timeFormat),
offset: getOffsetText(data.offset),
};
}