diff --git a/apps/client/src/features/viewers/common/viewUtils.ts b/apps/client/src/features/viewers/common/viewUtils.ts index 66ceb80e6..cb4093a55 100644 --- a/apps/client/src/features/viewers/common/viewUtils.ts +++ b/apps/client/src/features/viewers/common/viewUtils.ts @@ -7,11 +7,17 @@ import { formatTime } from '../../../common/utils/time'; type TimerTypeParams = Pick; -export function getTimerByType(freezeEnd: boolean, timerObject?: TimerTypeParams): number | null { +export function getTimerByType( + freezeEnd: boolean, + timerObject?: TimerTypeParams, + timerTypeOverride?: TimerType, +): number | null { if (!timerObject) { return null; } + const viewTimerType = timerTypeOverride ?? timerObject.timerType; + if (timerObject.countToEnd) { if (timerObject.current === null) { return null; @@ -19,7 +25,7 @@ export function getTimerByType(freezeEnd: boolean, timerObject?: TimerTypeParams return freezeEnd ? Math.max(timerObject.current, 0) : timerObject.current; } - switch (timerObject.timerType) { + switch (viewTimerType) { case TimerType.CountDown: if (timerObject.current === null) { return null; @@ -31,10 +37,8 @@ export function getTimerByType(freezeEnd: boolean, timerObject?: TimerTypeParams return timerObject.clock; case TimerType.None: return null; - default: { - const exhaustiveCheck: never = timerObject.timerType; - return exhaustiveCheck; - } + default: + return null; } } diff --git a/apps/client/src/views/timer/Timer.tsx b/apps/client/src/views/timer/Timer.tsx index ae14a9e5e..b1333205b 100644 --- a/apps/client/src/views/timer/Timer.tsx +++ b/apps/client/src/views/timer/Timer.tsx @@ -65,6 +65,7 @@ export default function Timer(props: TimerProps) { removeLeadingZeros, mainSource, secondarySource, + timerType, } = useTimerOptions(); const { getLocalizedString } = useTranslation(); @@ -73,6 +74,7 @@ export default function Timer(props: TimerProps) { useWindowTitle('Timer'); // gather modifiers + const viewTimerType = timerType ?? time.timerType; const showOverlay = getShowMessage(message.timer); const { showEndMessage, showFinished, showWarning, showDanger } = getShowModifiers( time.timerType, @@ -81,8 +83,8 @@ export default function Timer(props: TimerProps) { viewSettings, ); const isPlaying = getIsPlaying(time.playback); - const showClock = !hideClock && getShowClock(time.timerType); - const showProgressBar = !hideProgress && getShowProgressBar(time.timerType); + const showClock = !hideClock && getShowClock(viewTimerType); + const showProgressBar = !hideProgress && getShowProgressBar(viewTimerType); // gather card data const { showNow, nowMain, nowSecondary, showNext, nextMain, nextSecondary } = getCardData( @@ -97,8 +99,8 @@ export default function Timer(props: TimerProps) { // gather timer data const totalTime = getTotalTime(time.duration, time.addedTime); const clock = formatTime(time.clock); - const stageTimer = getTimerByType(viewSettings.freezeEnd, time); - const display = getFormattedTimer(stageTimer, time.timerType, localisedMinutes, { + const stageTimer = getTimerByType(viewSettings.freezeEnd, time, timerType); + const display = getFormattedTimer(stageTimer, viewTimerType, localisedMinutes, { removeSeconds: hideTimerSeconds, removeLeadingZero: removeLeadingZeros, }); diff --git a/apps/client/src/views/timer/timer.options.ts b/apps/client/src/views/timer/timer.options.ts index 2fcc39939..a8a33c8bc 100644 --- a/apps/client/src/views/timer/timer.options.ts +++ b/apps/client/src/views/timer/timer.options.ts @@ -1,6 +1,7 @@ import { useMemo } from 'react'; import { useSearchParams } from 'react-router-dom'; -import { CustomFields, OntimeEvent } from 'ontime-types'; +import { CustomFields, OntimeEvent, TimerType } from 'ontime-types'; +import { validateTimerType } from 'ontime-utils'; import { getTimeOption, @@ -11,6 +12,14 @@ import { import { ViewOption } from '../../common/components/view-params-editor/types'; import { isStringBoolean } from '../../features/viewers/common/viewUtils'; +// manually match the properties of TimerType excluding the None +const timerDisplayOptions = { + 'no-overrides': 'No Overrides', + 'count-up': 'Count up', + 'count-down': 'Count down', + clock: 'Clock', +}; + export const getTimerOptions = (timeFormat: string, customFields: CustomFields): ViewOption[] => { const mainOptions = makeOptionsFromCustomFields(customFields, { title: 'Title', note: 'Note' }); const secondaryOptions = makeOptionsFromCustomFields(customFields, { title: 'Title', note: 'Note' }); @@ -21,6 +30,14 @@ export const getTimerOptions = (timeFormat: string, customFields: CustomFields): { section: 'Timer Options' }, hideTimerSeconds, showLeadingZeros, + { + id: 'timerType', + title: 'Timer type', + description: 'Override the timer type', + type: 'option', + values: timerDisplayOptions, + defaultValue: 'no-overrides', + }, { section: 'Data sources' }, { id: 'main', @@ -87,6 +104,7 @@ type TimerOptions = { removeLeadingZeros: boolean; mainSource: keyof OntimeEvent | null; secondarySource: keyof OntimeEvent | null; + timerType?: TimerType; }; /** @@ -94,6 +112,7 @@ type TimerOptions = { * the names and fallbacks are manually matched with timerOptions */ function getOptionsFromParams(searchParams: URLSearchParams): TimerOptions { + const timerType = validateTimerType(searchParams.get('timerType'), TimerType.None); // we manually make an object that matches the key above return { hideClock: isStringBoolean(searchParams.get('hideClock')), @@ -106,6 +125,9 @@ function getOptionsFromParams(searchParams: URLSearchParams): TimerOptions { mainSource: searchParams.get('main') as keyof OntimeEvent | null, secondarySource: searchParams.get('secondary-src') as keyof OntimeEvent | null, + + // none doesnt make sense as a configuration of the view + timerType: timerType === TimerType.None ? undefined : timerType, }; } diff --git a/packages/utils/src/validate-events/validateEvent.test.ts b/packages/utils/src/validate-events/validateEvent.test.ts index 7ccd77153..aa61b4106 100644 --- a/packages/utils/src/validate-events/validateEvent.test.ts +++ b/packages/utils/src/validate-events/validateEvent.test.ts @@ -27,4 +27,8 @@ describe('validateTimerType()', () => { expect(emptyType).toBe(TimerType.Clock); expect(invalidType).toBe(TimerType.CountDown); }); + it('handles a null value from params', () => { + const nullType = validateTimerType(null, TimerType.Clock); + expect(nullType).toBe(TimerType.Clock); + }); });