diff --git a/packages/corelib/src/dataModel/RundownPlaylist/TTimers.ts b/packages/corelib/src/dataModel/RundownPlaylist/TTimers.ts index ef974b741a5..ed05a3a47a1 100644 --- a/packages/corelib/src/dataModel/RundownPlaylist/TTimers.ts +++ b/packages/corelib/src/dataModel/RundownPlaylist/TTimers.ts @@ -164,3 +164,15 @@ export interface RundownTTimer { * display: { ... } // some kind of options for how to display in the ui */ } + +export const DEFAULT_RUNDOWN_T_TIMERS: [RundownTTimer, RundownTTimer, RundownTTimer] = [ + { index: 1, label: '', mode: null, state: null }, + { index: 2, label: '', mode: null, state: null }, + { index: 3, label: '', mode: null, state: null }, +] + +export function getRundownTTimers( + tTimers: [RundownTTimer, RundownTTimer, RundownTTimer] | null | undefined +): [RundownTTimer, RundownTTimer, RundownTTimer] { + return tTimers ?? DEFAULT_RUNDOWN_T_TIMERS +} diff --git a/packages/corelib/src/settings/__tests__/objectWithOverrides.spec.ts b/packages/corelib/src/settings/__tests__/objectWithOverrides.spec.ts index 55699dbda25..06b9585256c 100644 --- a/packages/corelib/src/settings/__tests__/objectWithOverrides.spec.ts +++ b/packages/corelib/src/settings/__tests__/objectWithOverrides.spec.ts @@ -22,6 +22,16 @@ interface BasicType { } describe('applyAndValidateOverrides', () => { + test('undefined input', () => { + const res = applyAndValidateOverrides(undefined) + expect(res).toBeTruthy() + + expect(res.obj).toStrictEqual({}) + expect(res.invalid).toHaveLength(0) + expect(res.preserve).toHaveLength(0) + expect(res.unused).toHaveLength(0) + }) + test('no overrides', () => { const inputObj = { abc: 'def', diff --git a/packages/corelib/src/settings/objectWithOverrides.ts b/packages/corelib/src/settings/objectWithOverrides.ts index 59a015d7164..d22dbfdf7e2 100644 --- a/packages/corelib/src/settings/objectWithOverrides.ts +++ b/packages/corelib/src/settings/objectWithOverrides.ts @@ -52,9 +52,11 @@ export function wrapDefaultObject(obj: T): ObjectWithOverrides overrides: [], } } -export function isObjectWithOverrides(o: ObjectWithOverrides | T): o is ObjectWithOverrides { +export function isObjectWithOverrides( + o: ObjectWithOverrides | T | ReadonlyDeep> | undefined | null +): o is ObjectWithOverrides { const oAny = o as any - return typeof oAny.defaults === 'object' && Array.isArray(oAny.overrides) + return oAny != null && typeof oAny.defaults === 'object' && oAny.defaults != null && Array.isArray(oAny.overrides) } /** * In some cases, an ObjectWithOverrides should have no defaults. This is common for when the user owns the object containing the ObjectWithOverrides. @@ -194,10 +196,19 @@ function recursivelyGenerateOverrides( * Note: No validation is done to make sure the type conforms to the typescript definition. It is assumed that the definitions which drive ui ensure that they dont violate the typings, and that any changes will be backwards compatible with old overrides */ export function applyAndValidateOverrides( - obj: ReadonlyDeep> + obj: ReadonlyDeep> | undefined | null ): ApplyOverridesResult { + if (obj == null || !isObjectWithOverrides(obj)) { + return { + obj: {} as T, + preserve: [], + unused: [], + invalid: [], + } + } + const result: ApplyOverridesResult = { - obj: clone(obj.defaults), + obj: clone(obj.defaults) as T, preserve: [], unused: [], invalid: [], diff --git a/packages/webui/src/client/lib/tTimerUtils.ts b/packages/webui/src/client/lib/tTimerUtils.ts index dd81678dbec..a2d3f211162 100644 --- a/packages/webui/src/client/lib/tTimerUtils.ts +++ b/packages/webui/src/client/lib/tTimerUtils.ts @@ -1,5 +1,6 @@ import { type RundownTTimer, + getRundownTTimers, timerStateToDuration, } from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist/TTimers' @@ -49,6 +50,8 @@ export function calculateTTimerOverUnder(timer: RundownTTimer, now: number): num return projectedDuration - duration } -export function getDefaultTTimer(tTimers: [RundownTTimer, RundownTTimer, RundownTTimer]): RundownTTimer | undefined { - return tTimers.find((t) => t.mode) +export function getDefaultTTimer( + tTimers: [RundownTTimer, RundownTTimer, RundownTTimer] | null | undefined +): RundownTTimer | undefined { + return getRundownTTimers(tTimers).find((t) => t.mode) } diff --git a/packages/webui/src/client/ui/RundownView/RundownHeader/RundownHeaderTimers.tsx b/packages/webui/src/client/ui/RundownView/RundownHeader/RundownHeaderTimers.tsx index 176eb08a251..73715bf9d26 100644 --- a/packages/webui/src/client/ui/RundownView/RundownHeader/RundownHeaderTimers.tsx +++ b/packages/webui/src/client/ui/RundownView/RundownHeader/RundownHeaderTimers.tsx @@ -1,4 +1,4 @@ -import type { RundownTTimer } from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist/TTimers' +import { type RundownTTimer, getRundownTTimers } from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist/TTimers' import { useTiming } from '../RundownTiming/withTiming' import { RundownUtils } from '../../../lib/rundown.js' import { calculateTTimerDiff, calculateTTimerOverUnder } from '../../../lib/tTimerUtils' @@ -8,13 +8,15 @@ import { Countdown } from './Countdown' import { OverUnderChip } from '../../../lib/Components/OverUnderChip' interface IProps { - tTimers: [RundownTTimer, RundownTTimer, RundownTTimer] + tTimers: [RundownTTimer, RundownTTimer, RundownTTimer] | null | undefined } export const RundownHeaderTimers: React.FC = ({ tTimers }) => { useTiming() - const activeTimers = tTimers.filter((t) => t.mode).slice(0, 2) + const activeTimers = getRundownTTimers(tTimers) + .filter((t) => t.mode) + .slice(0, 2) if (activeTimers.length == 0) return null return (