refactor: cleanup store (#727)

* refactor: isolate clock

* refactor: isolate private

* refactor: remove duplicate

* chore: rename loaded > runtime

* fix: handle no events loaded
This commit is contained in:
Carlos Valente
2024-01-24 00:20:10 +01:00
committed by GitHub
parent 0cd2ca4191
commit b4ad533e6a
31 changed files with 411 additions and 384 deletions
@@ -3,7 +3,7 @@ import React from 'react';
// skipcq: JS-C1003 - sentry does not expose itself as an ES Module.
import * as Sentry from '@sentry/react';
import { runtime } from '@/common/stores/runtime';
import { runtimeStore } from '@/common/stores/runtime';
import { hasConnected, reconnectAttempts, shouldReconnect } from '@/common/utils/socket';
import style from './ErrorBoundary.module.scss';
@@ -29,7 +29,7 @@ class ErrorBoundary extends React.Component {
Sentry.withScope((scope) => {
scope.setExtras('error', error);
scope.setExtras('store', runtime.getState());
scope.setExtras('store', runtimeStore.getState());
scope.setExtras('hasSocket', { hasConnected, shouldReconnect, reconnectAttempts });
const eventId = Sentry.captureException(error);
this.setState({ eventId, info });
@@ -1,14 +1,16 @@
import { MaybeNumber } from 'ontime-types';
import { clamp } from '../../utils/math';
import './MultiPartProgressBar.scss';
interface MultiPartProgressBar {
now: number | null;
now: MaybeNumber;
complete: number;
normalColor: string;
warning?: number | null;
warning?: MaybeNumber;
warningColor: string;
danger?: number | null;
danger?: MaybeNumber;
dangerColor: string;
hidden?: boolean;
className?: string;
+47 -16
View File
@@ -5,9 +5,9 @@ import { socketSendJson } from '../utils/socket';
export const useRundownEditor = () => {
const featureSelector = (state: RuntimeStore) => ({
playback: state.playback,
selectedEventId: state.loaded.selectedEventId,
nextEventId: state.loaded.nextEventId,
playback: state.timer.playback,
selectedEventId: state.eventNow?.id ?? null,
nextEventId: state.eventNext?.id ?? null,
});
return useRuntimeStore(featureSelector);
@@ -15,8 +15,8 @@ export const useRundownEditor = () => {
export const useOperator = () => {
const featureSelector = (state: RuntimeStore) => ({
playback: state.playback,
selectedEventId: state.loaded.selectedEventId,
playback: state.timer.playback,
selectedEventId: state.eventNow?.id ?? null,
});
return useRuntimeStore(featureSelector);
@@ -50,9 +50,9 @@ export const setMessage = {
export const usePlaybackControl = () => {
const featureSelector = (state: RuntimeStore) => ({
playback: state.playback,
selectedEventIndex: state.loaded.selectedEventIndex,
numEvents: state.loaded.numEvents,
playback: state.timer.playback,
selectedEventIndex: state.runtime.selectedEventIndex,
numEvents: state.runtime.numEvents,
});
return useRuntimeStore(featureSelector);
@@ -80,12 +80,24 @@ export const setPlayback = {
},
};
export const useInfoPanel = () => {
const featureSelector = (state: RuntimeStore) => ({
eventNow: state.eventNow,
eventNext: state.eventNext,
playback: state.timer.playback,
selectedEventIndex: state.runtime.selectedEventIndex,
numEvents: state.runtime.numEvents,
});
return useRuntimeStore(featureSelector);
};
export const useCuesheet = () => {
const featureSelector = (state: RuntimeStore) => ({
playback: state.playback,
selectedEventId: state.loaded.selectedEventId,
selectedEventIndex: state.loaded.selectedEventIndex,
numEvents: state.loaded.numEvents,
playback: state.timer.playback,
selectedEventId: state.eventNow?.id ?? null,
selectedEventIndex: state.runtime.selectedEventIndex,
numEvents: state.runtime.numEvents,
titleNow: state.eventNow?.title || '',
});
@@ -107,14 +119,33 @@ export const useTimer = () => {
return useRuntimeStore(featureSelector);
};
export const useClock = () => {
const featureSelector = (state: RuntimeStore) => ({
clock: state.clock,
});
return useRuntimeStore(featureSelector);
};
/** Used by the progress bar components */
export const useProgressData = () => {
const featureSelector = (state: RuntimeStore) => ({
addedTime: state.timer.addedTime,
current: state.timer.current,
duration: state.timer.duration,
timeWarning: state.eventNow?.timeWarning ?? null,
timeDanger: state.eventNow?.timeDanger ?? null,
});
return useRuntimeStore(featureSelector);
};
export const setClientName = (newName: string) => socketSendJson('set-client-name', newName);
export const useRuntimeOverview = () => {
const featureSelector = (state: RuntimeStore) => ({
playback: state.playback,
clock: state.timer.clock,
numEvents: state.loaded.numEvents,
selectedEventIndex: state.loaded.selectedEventIndex,
playback: state.timer.playback,
clock: state.clock,
selectedEventIndex: state.runtime.selectedEventIndex,
numEvents: state.runtime.numEvents,
});
return useRuntimeStore(featureSelector);
@@ -1,21 +1,19 @@
import { Playback, TimerType } from 'ontime-types';
import { MaybeNumber, Playback, TimerType } from 'ontime-types';
export type TimeManagerType = {
clock: number;
current: null | number;
elapsed: null | number;
duration: null | number;
timerBehaviour?: string;
timerType: TimerType;
expectedFinish: null | number;
// first set extends TimerState
export type ViewExtendedTimer = {
addedTime: number;
startedAt: null | number;
finishedAt: null | number;
secondaryTimer: null | number;
finished: boolean;
current: MaybeNumber;
duration: MaybeNumber;
elapsed: MaybeNumber;
expectedFinish: MaybeNumber;
finishedAt: MaybeNumber;
playback: Playback;
secondaryTimer: MaybeNumber;
startedAt: MaybeNumber;
timeWarning: number | null;
timeDanger: number | null;
clock: number;
timeDanger: MaybeNumber;
timeWarning: MaybeNumber;
timerType: TimerType;
};
+9 -17
View File
@@ -3,22 +3,19 @@ import { Playback, RuntimeStore } from 'ontime-types';
import { createWithEqualityFn, useStoreWithEqualityFn } from 'zustand/traditional';
export const runtimeStorePlaceholder: RuntimeStore = {
clock: 0,
timer: {
clock: 0,
addedTime: 0,
current: null,
duration: null,
elapsed: null,
expectedFinish: null,
addedTime: 0,
startedAt: null,
finishedAt: null,
playback: Playback.Stop,
secondaryTimer: null,
duration: null,
timerType: null,
endAction: null,
timeWarning: null,
timeDanger: null,
startedAt: null,
},
playback: Playback.Stop,
onAir: false,
message: {
timer: {
text: '',
@@ -39,14 +36,9 @@ export const runtimeStorePlaceholder: RuntimeStore = {
visible: false,
},
},
onAir: false,
loaded: {
runtime: {
numEvents: 0,
selectedEventIndex: null,
selectedEventId: null,
selectedPublicEventId: null,
nextEventId: null,
nextPublicEventId: null,
},
eventNow: null,
eventNext: null,
@@ -56,7 +48,7 @@ export const runtimeStorePlaceholder: RuntimeStore = {
const deepCompare = <T>(a: T, b: T) => isEqual(a, b);
export const runtime = createWithEqualityFn<RuntimeStore>(
export const runtimeStore = createWithEqualityFn<RuntimeStore>(
() => ({
...runtimeStorePlaceholder,
}),
@@ -64,4 +56,4 @@ export const runtime = createWithEqualityFn<RuntimeStore>(
);
export const useRuntimeStore = <T>(selector: (state: RuntimeStore) => T) =>
useStoreWithEqualityFn(runtime, selector, deepCompare);
useStoreWithEqualityFn(runtimeStore, selector, deepCompare);
+15 -15
View File
@@ -4,7 +4,7 @@ import { isProduction, RUNTIME, websocketUrl } from '../api/apiConstants';
import { ontimeQueryClient } from '../queryClient';
import { socketClientName } from '../stores/connectionName';
import { addLog } from '../stores/logger';
import { runtime } from '../stores/runtime';
import { runtimeStore } from '../stores/runtime';
export let websocket: WebSocket | null = null;
let reconnectTimeout: NodeJS.Timeout | null = null;
@@ -63,40 +63,40 @@ export const connectSocket = (preferredClientName?: string) => {
break;
}
case 'ontime': {
runtime.setState(payload as RuntimeStore);
runtimeStore.setState(payload as RuntimeStore);
if (!isProduction) {
ontimeQueryClient.setQueryData(RUNTIME, data.payload);
}
break;
}
case 'ontime-playback': {
const state = runtime.getState();
state.playback = payload;
runtime.setState(state);
const state = runtimeStore.getState();
state.timer.playback = payload;
runtimeStore.setState(state);
break;
}
case 'ontime-timer': {
const state = runtime.getState();
const state = runtimeStore.getState();
state.timer = payload;
runtime.setState(state);
runtimeStore.setState(state);
break;
}
case 'ontime-loaded': {
const state = runtime.getState();
state.loaded = payload;
runtime.setState(state);
case 'ontime-runtime': {
const state = runtimeStore.getState();
state.runtime = payload;
runtimeStore.setState(state);
break;
}
case 'ontime-message': {
const state = runtime.getState();
const state = runtimeStore.getState();
state.message = payload;
runtime.setState(state);
runtimeStore.setState(state);
break;
}
case 'ontime-onAir': {
const state = runtime.getState();
const state = runtimeStore.getState();
state.onAir = payload;
runtime.setState(state);
runtimeStore.setState(state);
break;
}
}
@@ -1,22 +1,22 @@
import MultiPartProgressBar from '../../../common/components/multi-part-progress-bar/MultiPartProgressBar';
import { useTimer } from '../../../common/hooks/useSocket';
import { useProgressData } from '../../../common/hooks/useSocket';
import useViewSettings from '../../../common/hooks-query/useViewSettings';
import styles from './CuesheetProgress.module.scss';
export default function CuesheetProgress() {
const { data } = useViewSettings();
const timer = useTimer();
const totalTime = (timer.duration ?? 0) + (timer.addedTime ?? 0);
const { addedTime, current, duration, timeWarning, timeDanger } = useProgressData();
const totalTime = (duration ?? 0) + (addedTime ?? 0);
return (
<MultiPartProgressBar
now={timer.current}
now={current}
complete={totalTime}
normalColor={data!.normalColor}
warning={timer.timeWarning}
warning={timeWarning}
warningColor={data!.warningColor}
danger={timer.timeDanger}
danger={timeDanger}
dangerColor={data!.dangerColor}
className={styles.progressOverride}
/>
@@ -1,21 +1,22 @@
import { useTimer } from '../../../common/hooks/useSocket';
import { useClock, useTimer } from '../../../common/hooks/useSocket';
import ClockTime from '../../viewers/common/clock-time/ClockTime';
import RunningTime from '../../viewers/common/running-time/RunningTime';
import style from './CuesheetTableHeader.module.scss';
export default function CuesheetTableHeaderTimers() {
const timer = useTimer();
const { current } = useTimer();
const { clock } = useClock();
return (
<>
<div className={style.timer}>
<div className={style.timerLabel}>Running Timer</div>
<RunningTime className={style.value} value={timer.current} hideLeadingZero />
<RunningTime className={style.value} value={current} hideLeadingZero />
</div>
<div className={style.clock}>
<div className={style.clockLabel}>Time Now</div>
<ClockTime className={style.value} value={timer.clock} />
<ClockTime className={style.value} value={clock} />
</div>
</>
);
@@ -30,8 +30,8 @@ interface OperatorEventProps {
// extract this to contain re-renders
function RollingTime() {
const timer = useTimer();
return <RunningTime value={timer.current} />;
const { current } = useTimer();
return <RunningTime value={current} />;
}
function OperatorEvent(props: OperatorEventProps) {
@@ -1,7 +1,7 @@
import { ViewSettings } from 'ontime-types';
import MultiPartProgressBar from '../../../common/components/multi-part-progress-bar/MultiPartProgressBar';
import { useTimer } from '../../../common/hooks/useSocket';
import { useProgressData } from '../../../common/hooks/useSocket';
import styles from './StatusBar.module.scss';
@@ -11,18 +11,17 @@ interface StatusBarProgressProps {
export default function StatusBarProgress(props: StatusBarProgressProps) {
const { viewSettings } = props;
const timer = useTimer();
const totalTime = (timer.duration ?? 0) + (timer.addedTime ?? 0);
const { addedTime, current, duration, timeWarning, timeDanger } = useProgressData();
const totalTime = (duration ?? 0) + (addedTime ?? 0);
return (
<MultiPartProgressBar
now={timer.current}
now={current}
complete={totalTime}
normalColor={viewSettings.normalColor}
warning={timer?.timeWarning}
warning={timeWarning}
warningColor={viewSettings.warningColor}
danger={timer?.timeDanger}
danger={timeDanger}
dangerColor={viewSettings.dangerColor}
className={styles.progressOverride}
/>
@@ -2,7 +2,7 @@ import { useMemo } from 'react';
import { MaybeNumber, Playback } from 'ontime-types';
import PlaybackIcon from '../../../common/components/playback-icon/PlaybackIcon';
import { useTimer } from '../../../common/hooks/useSocket';
import { useClock, useTimer } from '../../../common/hooks/useSocket';
import { cx } from '../../../common/utils/styleUtils';
import ClockTime from '../../viewers/common/clock-time/ClockTime';
import RunningTime from '../../viewers/common/running-time/RunningTime';
@@ -23,6 +23,7 @@ export default function StatusBarTimers(props: StatusBarTimersProps) {
const { projectTitle, playback, selectedEventId, firstStart, firstId, lastEnd, lastId } = props;
const timer = useTimer();
const { clock } = useClock();
const getTimeStart = (): MaybeNumber => {
if (firstStart === undefined) {
@@ -61,7 +62,7 @@ export default function StatusBarTimers(props: StatusBarTimersProps) {
{PlaybackIconComponent}
<div className={styles.timeNow}>
<span className={styles.label}>Time now</span>
<ClockTime className={styles.timer} value={timer.clock} />
<ClockTime className={styles.timer} value={clock} />
</div>
<div className={styles.elapsedTime}>
<span className={styles.label}>Elapsed time</span>
@@ -1,5 +1,5 @@
import { ComponentType, useMemo } from 'react';
import { TimeManagerType } from 'common/models/TimeManager.type';
import { ViewExtendedTimer } from 'common/models/TimeManager.type';
import { Message, OntimeEvent, ProjectData, Settings, SupportedEvent, TimerMessage, ViewSettings } from 'ontime-types';
import { useStore } from 'zustand';
@@ -7,7 +7,7 @@ import useProjectData from '../../common/hooks-query/useProjectData';
import useRundown from '../../common/hooks-query/useRundown';
import useSettings from '../../common/hooks-query/useSettings';
import useViewSettings from '../../common/hooks-query/useViewSettings';
import { runtime } from '../../common/stores/runtime';
import { runtimeStore } from '../../common/stores/runtime';
import { useViewOptionsStore } from '../../common/stores/viewOptions';
type WithDataProps = {
@@ -20,7 +20,7 @@ type WithDataProps = {
publicEventNow: OntimeEvent | null;
eventNext: OntimeEvent | null;
publicEventNext: OntimeEvent | null;
time: TimeManagerType;
time: ViewExtendedTimer;
events: OntimeEvent[];
backstageEvents: OntimeEvent[];
selectedId: string | null;
@@ -55,11 +55,11 @@ const withData = <P extends WithDataProps>(Component: ComponentType<P>) => {
}, [rundownData]);
// websocket data
const { timer, message, playback, onAir, eventNext, publicEventNext, publicEventNow, eventNow, loaded } =
useStore(runtime);
const publicSelectedId = loaded.selectedPublicEventId;
const selectedId = loaded.selectedEventId;
const nextId = loaded.nextEventId;
const { clock, timer, message, onAir, eventNext, publicEventNext, publicEventNow, eventNow } =
useStore(runtimeStore);
const publicSelectedId = publicEventNow?.id ?? null;
const selectedId = eventNow?.id ?? null;
const nextId = eventNext?.id ?? null;
/******************************************/
/*** + TimeManagerType ***/
@@ -69,7 +69,10 @@ const withData = <P extends WithDataProps>(Component: ComponentType<P>) => {
const TimeManagerType = {
...timer,
playback,
clock,
timerType: eventNow?.timerType ?? null,
timeWarning: eventNow?.timeWarning ?? null,
timeDanger: eventNow?.timeWarning ?? null,
};
// prevent render until we get all the data we need
@@ -14,7 +14,7 @@ import TitleCard from '../../../common/components/title-card/TitleCard';
import { getBackstageOptions } from '../../../common/components/view-params-editor/constants';
import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor';
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
import { TimeManagerType } from '../../../common/models/TimeManager.type';
import { ViewExtendedTimer } from '../../../common/models/TimeManager.type';
import { formatTime, getDefaultFormat } from '../../../common/utils/time';
import { useTranslation } from '../../../translation/TranslationProvider';
import { titleVariants } from '../common/animation';
@@ -27,7 +27,7 @@ interface BackstageProps {
publ: Message;
eventNow: OntimeEvent | null;
eventNext: OntimeEvent | null;
time: TimeManagerType;
time: ViewExtendedTimer;
backstageEvents: OntimeEvent[];
selectedId: string | null;
general: ProjectData;
@@ -7,7 +7,7 @@ import NavigationMenu from '../../../common/components/navigation-menu/Navigatio
import { getClockOptions } from '../../../common/components/view-params-editor/constants';
import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor';
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
import { TimeManagerType } from '../../../common/models/TimeManager.type';
import { ViewExtendedTimer } from '../../../common/models/TimeManager.type';
import { OverridableOptions } from '../../../common/models/View.types';
import { formatTime, getDefaultFormat } from '../../../common/utils/time';
import SuperscriptTime from '../common/superscript-time/SuperscriptTime';
@@ -16,7 +16,7 @@ import './Clock.scss';
interface ClockProps {
isMirrored: boolean;
time: TimeManagerType;
time: ViewExtendedTimer;
viewSettings: ViewSettings;
settings: Settings | undefined;
}
@@ -1,8 +1,8 @@
import { TimerType } from 'ontime-types';
import type { TimeManagerType } from '../../../common/models/TimeManager.type';
import type { ViewExtendedTimer } from '../../../common/models/TimeManager.type';
type TimerTypeParams = Pick<TimeManagerType, 'timerType' | 'current' | 'elapsed' | 'clock'>;
type TimerTypeParams = Pick<ViewExtendedTimer, 'timerType' | 'current' | 'elapsed' | 'clock'>;
export function getTimerByType(timerObject?: TimerTypeParams): number | null {
if (!timerObject) {
@@ -8,7 +8,7 @@ import NavigationMenu from '../../../common/components/navigation-menu/Navigatio
import { getCountdownOptions } from '../../../common/components/view-params-editor/constants';
import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor';
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
import { TimeManagerType } from '../../../common/models/TimeManager.type';
import { ViewExtendedTimer } from '../../../common/models/TimeManager.type';
import { formatTime, getDefaultFormat } from '../../../common/utils/time';
import { useTranslation } from '../../../translation/TranslationProvider';
import SuperscriptTime from '../common/superscript-time/SuperscriptTime';
@@ -21,7 +21,7 @@ import './Countdown.scss';
interface CountdownProps {
isMirrored: boolean;
backstageEvents: OntimeEvent[];
time: TimeManagerType;
time: ViewExtendedTimer;
selectedId: string | null;
viewSettings: ViewSettings;
settings: Settings | undefined;
@@ -1,6 +1,6 @@
import { OntimeEvent, Playback } from 'ontime-types';
import { TimeManagerType } from '../../../common/models/TimeManager.type';
import { ViewExtendedTimer } from '../../../common/models/TimeManager.type';
export enum TimerMessage {
toStart = 'to_start',
@@ -19,7 +19,7 @@ export const sanitiseTitle = (title: string | null) => (title ? title : '{no tit
* Returns a parsed timer and relevant status message
*/
export const fetchTimerData = (
time: TimeManagerType,
time: ViewExtendedTimer,
follow: OntimeEvent,
selectedId: string | null,
): { message: TimerMessage; timer: number } => {
@@ -8,7 +8,7 @@ import NavigationMenu from '../../../common/components/navigation-menu/Navigatio
import { MINIMAL_TIMER_OPTIONS } from '../../../common/components/view-params-editor/constants';
import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor';
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
import { TimeManagerType } from '../../../common/models/TimeManager.type';
import { ViewExtendedTimer } from '../../../common/models/TimeManager.type';
import { OverridableOptions } from '../../../common/models/View.types';
import { isStringBoolean } from '../../../common/utils/viewUtils';
import { useTranslation } from '../../../translation/TranslationProvider';
@@ -19,7 +19,7 @@ import './MinimalTimer.scss';
interface MinimalTimerProps {
isMirrored: boolean;
pres: TimerMessage;
time: TimeManagerType;
time: ViewExtendedTimer;
viewSettings: ViewSettings;
}
@@ -12,7 +12,7 @@ import TitleCard from '../../../common/components/title-card/TitleCard';
import { getPublicOptions } from '../../../common/components/view-params-editor/constants';
import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor';
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
import { TimeManagerType } from '../../../common/models/TimeManager.type';
import { ViewExtendedTimer } from '../../../common/models/TimeManager.type';
import { formatTime, getDefaultFormat } from '../../../common/utils/time';
import { useTranslation } from '../../../translation/TranslationProvider';
import { titleVariants } from '../common/animation';
@@ -25,7 +25,7 @@ interface BackstageProps {
publ: Message;
publicEventNow: OntimeEvent | null;
publicEventNext: OntimeEvent | null;
time: TimeManagerType;
time: ViewExtendedTimer;
events: OntimeEvent[];
publicSelectedId: string | null;
general: ProjectData;
@@ -10,7 +10,7 @@ import { getStudioClockOptions } from '../../../common/components/view-params-ed
import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor';
import useFitText from '../../../common/hooks/useFitText';
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
import { TimeManagerType } from '../../../common/models/TimeManager.type';
import { ViewExtendedTimer } from '../../../common/models/TimeManager.type';
import { formatTime, getDefaultFormat } from '../../../common/utils/time';
import { isStringBoolean } from '../../../common/utils/viewUtils';
import SuperscriptTime from '../common/superscript-time/SuperscriptTime';
@@ -22,7 +22,7 @@ import './StudioClock.scss';
interface StudioClockProps {
isMirrored: boolean;
eventNext: OntimeEvent | null;
time: TimeManagerType;
time: ViewExtendedTimer;
backstageEvents: OntimeRundown;
selectedId: string | null;
nextId: string | null;
@@ -11,7 +11,7 @@ import TitleCard from '../../../common/components/title-card/TitleCard';
import { getTimerOptions } from '../../../common/components/view-params-editor/constants';
import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor';
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
import { TimeManagerType } from '../../../common/models/TimeManager.type';
import { ViewExtendedTimer } from '../../../common/models/TimeManager.type';
import { formatTime, getDefaultFormat } from '../../../common/utils/time';
import { isStringBoolean } from '../../../common/utils/viewUtils';
import { useTranslation } from '../../../translation/TranslationProvider';
@@ -42,7 +42,7 @@ interface TimerProps {
external: Message;
eventNow: OntimeEvent | null;
eventNext: OntimeEvent | null;
time: TimeManagerType;
time: ViewExtendedTimer;
viewSettings: ViewSettings;
settings: Settings | undefined;
}