diff --git a/apps/client/src/common/components/view-params-editor/common.options.ts b/apps/client/src/common/components/view-params-editor/common.options.ts index 90af2968d..d3821fc6d 100644 --- a/apps/client/src/common/components/view-params-editor/common.options.ts +++ b/apps/client/src/common/components/view-params-editor/common.options.ts @@ -26,3 +26,14 @@ export const showLeadingZeros: ParamField = { type: 'boolean', defaultValue: false, }; + +export type TimeOptions = { + timeformat: string | null; +}; + +/** + * Helper to get value of 'timeformat' from either source, prioritizing defaultValues + */ +export function getTimeOptionsFromParams(searchParams: URLSearchParams, defaultValues?: URLSearchParams) { + return defaultValues?.get('timeformat') ?? searchParams.get('timeformat'); +} diff --git a/apps/client/src/common/utils/time.ts b/apps/client/src/common/utils/time.ts index 0dce7c63a..9990092ce 100644 --- a/apps/client/src/common/utils/time.ts +++ b/apps/client/src/common/utils/time.ts @@ -1,4 +1,4 @@ -import { MaybeNumber, OntimeEvent, Settings, TimeFormat } from 'ontime-types'; +import { MaybeNumber, MaybeString, OntimeEvent, Settings, TimeFormat } from 'ontime-types'; import { MILLIS_PER_HOUR, MILLIS_PER_MINUTE, @@ -75,8 +75,9 @@ function resolveTimeFormat(fallback12: string, fallback24: string): string { } type FormatOptions = { - format12: string; - format24: string; + format12?: string; + format24?: string; + override?: MaybeString; }; /** @@ -97,7 +98,7 @@ export const formatTime = ( return '...'; } - const timeFormat = resolver(options?.format12 ?? FORMAT_12, options?.format24 ?? FORMAT_24); + const timeFormat = options?.override ?? resolver(options?.format12 ?? FORMAT_12, options?.format24 ?? FORMAT_24); const display = formatFromMillis(Math.abs(milliseconds), timeFormat); const isNegative = milliseconds < 0; diff --git a/apps/client/src/views/backstage/Backstage.tsx b/apps/client/src/views/backstage/Backstage.tsx index ec87ebfcd..229ba5213 100644 --- a/apps/client/src/views/backstage/Backstage.tsx +++ b/apps/client/src/views/backstage/Backstage.tsx @@ -43,7 +43,7 @@ export default function BackstageLoader() { function Backstage({ events, customFields, projectData, isMirrored, settings }: BackstageData) { const { getLocalizedString } = useTranslation(); - const { mainSource, secondarySource, extraInfo } = useBackstageOptions(); + const { mainSource, secondarySource, extraInfo, timeformat } = useBackstageOptions(); const { eventNext, eventNow, rundown, selectedEventId, time } = useBackstageSocket(); const [blinkClass, setBlinkClass] = useState(false); const { height: screenHeight } = useViewportSize(); @@ -71,18 +71,20 @@ function Backstage({ events, customFields, projectData, isMirrored, settings }: // gather timer data const isPendingStart = getIsPendingStart(time.playback, time.phase); - const startedAt = isPendingStart ? formatTime(time.secondaryTimer) : formatTime(time.startedAt); + const startedAt = isPendingStart + ? formatTime(time.secondaryTimer, { override: timeformat }) + : formatTime(time.startedAt, { override: timeformat }); const scheduledStart = (() => { if (showNow) return undefined; if (!hasEvents) return undefined; - return formatTime(rundown.plannedStart, { format12: 'h:mm a', format24: 'HH:mm' }); + return formatTime(rundown.plannedStart, { format12: 'h:mm a', format24: 'HH:mm', override: timeformat }); })(); const scheduledEnd = (() => { if (showNow) return undefined; if (!hasEvents) return undefined; - return formatTime(rundown.plannedEnd, { format12: 'h:mm a', format24: 'HH:mm' }); + return formatTime(rundown.plannedEnd, { format12: 'h:mm a', format24: 'HH:mm', override: timeformat }); })(); let displayTimer = millisToString(time.current, { fallback: timerPlaceholderMin }); @@ -107,7 +109,7 @@ function Backstage({ events, customFields, projectData, isMirrored, settings }:
{projectData?.logo && }
{projectData.title}
- +
{showProgress && } @@ -131,7 +133,10 @@ function Backstage({ events, customFields, projectData, isMirrored, settings }: {isOvertime(time.current) ? (
{getLocalizedString('countdown.overtime')}
) : ( - + )}
@@ -213,12 +218,12 @@ function ExtraInfo({ projectData, size, source }: ExtraInfoProps) { ); } -function BackstageClock() { +function BackstageClock({ timeformat }: { timeformat: string | null }) { const { getLocalizedString } = useTranslation(); const clock = useAutoTickingClock(); // gather timer data - const formattedClock = formatTime(clock); + const formattedClock = formatTime(clock, { override: timeformat }); return (
diff --git a/apps/client/src/views/backstage/backstage.options.ts b/apps/client/src/views/backstage/backstage.options.ts index 2e7cf1ddd..9f67c2fa4 100644 --- a/apps/client/src/views/backstage/backstage.options.ts +++ b/apps/client/src/views/backstage/backstage.options.ts @@ -2,7 +2,11 @@ import { CustomFields, OntimeEvent, ProjectData } from 'ontime-types'; import { use, useMemo } from 'react'; import { useSearchParams } from 'react-router'; -import { getTimeOption } from '../../common/components/view-params-editor/common.options'; +import { + getTimeOption, + getTimeOptionsFromParams, + TimeOptions, +} from '../../common/components/view-params-editor/common.options'; import { OptionTitle } from '../../common/components/view-params-editor/constants'; import { ViewOption } from '../../common/components/view-params-editor/viewParams.types'; import { @@ -71,7 +75,7 @@ type BackstageOptions = { mainSource: keyof OntimeEvent | null; secondarySource: keyof OntimeEvent | null; extraInfo: string | null; -}; +} & TimeOptions; /** * Utility extract the view options from URL Params @@ -85,6 +89,7 @@ function getOptionsFromParams(searchParams: URLSearchParams, defaultValues?: URL mainSource: getValue('main') as keyof OntimeEvent | null, secondarySource: getValue('secondary-src') as keyof OntimeEvent | null, extraInfo: getValue('extra-info'), + timeformat: getTimeOptionsFromParams(searchParams, defaultValues), }; } diff --git a/apps/client/src/views/common/viewUtils.ts b/apps/client/src/views/common/viewUtils.ts index a1d9c43a7..e70ff21a1 100644 --- a/apps/client/src/views/common/viewUtils.ts +++ b/apps/client/src/views/common/viewUtils.ts @@ -101,6 +101,7 @@ export function getPropertyValue( type FormattingOptions = { removeSeconds: boolean; removeLeadingZero: boolean; + clockFormat?: MaybeString; }; export function getFormattedTimer( @@ -114,7 +115,7 @@ export function getFormattedTimer( } if (timerType === TimerType.Clock) { - return formatTime(timer); + return formatTime(timer, { override: options?.clockFormat }); } let timeToParse = timer; diff --git a/apps/client/src/views/countdown/Countdown.tsx b/apps/client/src/views/countdown/Countdown.tsx index f657b3cbc..6c096b7d2 100644 --- a/apps/client/src/views/countdown/Countdown.tsx +++ b/apps/client/src/views/countdown/Countdown.tsx @@ -139,11 +139,12 @@ function CountdownContents({ playableEvents, subscriptions, goToEditMode }: Coun } function CountdownClock() { + const { timeformat } = useCountdownOptions(); const { getLocalizedString } = useTranslation(); const clock = useAutoTickingClock(); // gather timer data - const formattedClock = formatTime(clock); + const formattedClock = formatTime(clock, { override: timeformat }); return (
diff --git a/apps/client/src/views/countdown/countdown.options.ts b/apps/client/src/views/countdown/countdown.options.ts index 7f0e12cc7..788a3bd48 100644 --- a/apps/client/src/views/countdown/countdown.options.ts +++ b/apps/client/src/views/countdown/countdown.options.ts @@ -2,7 +2,11 @@ import { CustomFields, EntryId, OntimeEvent } from 'ontime-types'; import { use, useMemo } from 'react'; import { useSearchParams } from 'react-router'; -import { getTimeOption } from '../../common/components/view-params-editor/common.options'; +import { + getTimeOption, + getTimeOptionsFromParams, + TimeOptions, +} from '../../common/components/view-params-editor/common.options'; import { OptionTitle } from '../../common/components/view-params-editor/constants'; import { ViewOption } from '../../common/components/view-params-editor/viewParams.types'; import { makeOptionsFromCustomFields } from '../../common/components/view-params-editor/viewParams.utils'; @@ -91,7 +95,7 @@ type CountdownOptions = { secondarySource: keyof OntimeEvent | null; showExpected: boolean; hidePast: boolean; -}; +} & TimeOptions; /** * Utility extract the view options from URL Params @@ -115,6 +119,7 @@ function getOptionsFromParams(searchParams: URLSearchParams, defaultValues?: URL secondarySource: getValue('secondary-src') as keyof OntimeEvent | null, showExpected: isStringBoolean(getValue('showExpected')), hidePast: isStringBoolean(getValue('hidePast')), + timeformat: getTimeOptionsFromParams(searchParams, defaultValues), }; } diff --git a/apps/client/src/views/studio/StudioClock.tsx b/apps/client/src/views/studio/StudioClock.tsx index f764aee45..585a6a8fc 100644 --- a/apps/client/src/views/studio/StudioClock.tsx +++ b/apps/client/src/views/studio/StudioClock.tsx @@ -6,6 +6,7 @@ import { useStudioClockSocket } from '../../common/hooks/useSocket'; import { cx } from '../../common/utils/styleUtils'; import { formatTime } from '../../common/utils/time'; import SuperscriptTime from '../common/superscript-time/SuperscriptTime'; +import { useStudioOptions } from './studio.options'; import { getLargeClockData } from './studioClock.utils'; import './StudioClock.scss'; @@ -18,6 +19,7 @@ interface StudioClockProps { } export default function StudioClock({ hideCards }: StudioClockProps) { + const { timeformat } = useStudioOptions(); const isSmallScreen = useIsSmallScreen(); const clock = useAutoTickingClock(); const { playback } = useStudioClockSocket(); @@ -25,10 +27,10 @@ export default function StudioClock({ hideCards }: StudioClockProps) { // if we are on mobile and have to show the cards if (isSmallScreen && !hideCards) { - return ; + return ; } - const { seconds, display, meridian } = getLargeClockData(clock); + const { seconds, display, meridian } = getLargeClockData(clock, timeformat); return (
@@ -62,10 +64,11 @@ export default function StudioClock({ hideCards }: StudioClockProps) { interface StudioClockMobileProps { clock: number; onAir: boolean; + timeformat: string | null; } -function StudioClockMobile({ clock, onAir }: StudioClockMobileProps) { - const displayClock = formatTime(clock); +function StudioClockMobile({ clock, onAir, timeformat }: StudioClockMobileProps) { + const displayClock = formatTime(clock, { override: timeformat }); return (
diff --git a/apps/client/src/views/studio/studio.options.ts b/apps/client/src/views/studio/studio.options.ts index f13075d92..16f4551c8 100644 --- a/apps/client/src/views/studio/studio.options.ts +++ b/apps/client/src/views/studio/studio.options.ts @@ -2,7 +2,11 @@ import { CustomFields, OntimeEvent } from 'ontime-types'; import { use, useMemo } from 'react'; import { useSearchParams } from 'react-router'; -import { getTimeOption } from '../../common/components/view-params-editor/common.options'; +import { + getTimeOption, + getTimeOptionsFromParams, + TimeOptions, +} from '../../common/components/view-params-editor/common.options'; import { OptionTitle } from '../../common/components/view-params-editor/constants'; import { ViewOption } from '../../common/components/view-params-editor/viewParams.types'; import { makeOptionsFromCustomFields } from '../../common/components/view-params-editor/viewParams.utils'; @@ -47,7 +51,7 @@ export const getStudioOptions = (timeFormat: string, customFields: CustomFields) type StudioOptions = { mainSource: keyof OntimeEvent | null; hideCards: boolean; -}; +} & TimeOptions; /** * Utility extract the view options from URL Params @@ -60,6 +64,7 @@ function getOptionsFromParams(searchParams: URLSearchParams, defaultValues?: URL return { mainSource: getValue('main') as keyof OntimeEvent | null, hideCards: isStringBoolean(getValue('hideCards')), + timeformat: getTimeOptionsFromParams(searchParams, defaultValues), }; } diff --git a/apps/client/src/views/studio/studioClock.utils.ts b/apps/client/src/views/studio/studioClock.utils.ts index 6dcd0812c..4bee0bfa1 100644 --- a/apps/client/src/views/studio/studioClock.utils.ts +++ b/apps/client/src/views/studio/studioClock.utils.ts @@ -5,9 +5,9 @@ import { formatTime } from '../../common/utils/time'; /** * Gathers display elements for the large studio clock */ -export function getLargeClockData(clock: number) { +export function getLargeClockData(clock: number, timeformat: string | null) { const [display, meridian] = (() => { - const formatted = formatTime(clock); + const formatted = formatTime(clock, { override: timeformat }); if (formatted.endsWith('AM')) { return [formatted.slice(0, -2), 'AM']; } diff --git a/apps/client/src/views/timeline/TimelinePage.tsx b/apps/client/src/views/timeline/TimelinePage.tsx index 8882ec81a..c6d0cdb49 100644 --- a/apps/client/src/views/timeline/TimelinePage.tsx +++ b/apps/client/src/views/timeline/TimelinePage.tsx @@ -37,7 +37,7 @@ export default function TimelinePageLoader() { function TimelinePage({ events, customFields, projectData, settings }: TimelineData) { const selectedEventId = useSelectedEventId(); - const { mainSource } = useTimelineOptions(); + const { mainSource, timeformat } = useTimelineOptions(); // holds copy of the rundown with only relevant events const { scopedRundown, firstStart, totalDuration } = useScopedRundown(events, selectedEventId); @@ -56,7 +56,7 @@ function TimelinePage({ events, customFields, projectData, settings }: TimelineD
{projectData?.logo && }
{projectData.title}
- +
@@ -71,12 +71,12 @@ function TimelinePage({ events, customFields, projectData, settings }: TimelineD ); } -function TimelineClock() { +function TimelineClock({ timeformat }: { timeformat: string | null }) { const { getLocalizedString } = useTranslation(); const clock = useAutoTickingClock(); // gather timer data - const formattedClock = formatTime(clock); + const formattedClock = formatTime(clock, { override: timeformat }); return (
diff --git a/apps/client/src/views/timeline/timeline.options.ts b/apps/client/src/views/timeline/timeline.options.ts index 23c5cbfa3..a322937dc 100644 --- a/apps/client/src/views/timeline/timeline.options.ts +++ b/apps/client/src/views/timeline/timeline.options.ts @@ -2,7 +2,11 @@ import { CustomFields, OntimeEvent } from 'ontime-types'; import { use, useMemo } from 'react'; import { useSearchParams } from 'react-router'; -import { getTimeOption } from '../../common/components/view-params-editor/common.options'; +import { + getTimeOption, + getTimeOptionsFromParams, + TimeOptions, +} from '../../common/components/view-params-editor/common.options'; import { OptionTitle } from '../../common/components/view-params-editor/constants'; import { ViewOption } from '../../common/components/view-params-editor/viewParams.types'; import { makeOptionsFromCustomFields } from '../../common/components/view-params-editor/viewParams.utils'; @@ -55,7 +59,7 @@ type TimelineOptions = { mainSource: keyof OntimeEvent | null; hidePast: boolean; fixedSize: boolean; -}; +} & TimeOptions; /** * Utility extract the view options from URL Params @@ -69,6 +73,7 @@ function getOptionsFromParams(searchParams: URLSearchParams, defaultValues?: URL mainSource: getValue('main') as keyof OntimeEvent | null, hidePast: isStringBoolean(getValue('hidePast')), fixedSize: isStringBoolean(getValue('fixedSize')), + timeformat: getTimeOptionsFromParams(searchParams, defaultValues), }; } diff --git a/apps/client/src/views/timer/Timer.tsx b/apps/client/src/views/timer/Timer.tsx index 4605b3d3a..3ae139d26 100644 --- a/apps/client/src/views/timer/Timer.tsx +++ b/apps/client/src/views/timer/Timer.tsx @@ -1,4 +1,4 @@ -import { OntimeView, TimerType } from 'ontime-types'; +import { MaybeString, OntimeView, TimerType } from 'ontime-types'; import { useMemo } from 'react'; import { FitText } from '../../common/components/fit-text/FitText'; @@ -69,6 +69,7 @@ function Timer({ customFields, projectData, isMirrored, settings, viewSettings, font, keyColour, timerColour, + timeformat, } = useTimerOptions(); const { getLocalizedString } = useTranslation(); @@ -106,6 +107,7 @@ function Timer({ customFields, projectData, isMirrored, settings, viewSettings, const display = getFormattedTimer(stageTimer, viewTimerType, localisedMinutes, { removeSeconds: hideTimerSeconds, removeLeadingZero: removeLeadingZeros, + clockFormat: timeformat, }); const currentAux = (() => { @@ -164,7 +166,7 @@ function Timer({ customFields, projectData, isMirrored, settings, viewSettings,
)} - {showClock && } + {showClock && }
{showEndMessage ? ( @@ -212,9 +214,9 @@ function Timer({ customFields, projectData, isMirrored, settings, viewSettings, ); } -function TimerAutoTickingClock() { +function TimerAutoTickingClock({ clockFormat }: { clockFormat: MaybeString }) { const autoTickingClock = useAutoTickingClock(); - const formattedClock = formatTime(autoTickingClock); + const formattedClock = formatTime(autoTickingClock, { override: clockFormat }); const { getLocalizedString } = useTranslation(); return ( diff --git a/apps/client/src/views/timer/timer.options.ts b/apps/client/src/views/timer/timer.options.ts index 9533d8852..c6bde0f29 100644 --- a/apps/client/src/views/timer/timer.options.ts +++ b/apps/client/src/views/timer/timer.options.ts @@ -6,8 +6,10 @@ import { useSearchParams } from 'react-router'; import type { SelectOption } from '../../common/components/select/Select'; import { getTimeOption, + getTimeOptionsFromParams, hideTimerSeconds, showLeadingZeros, + TimeOptions, } from '../../common/components/view-params-editor/common.options'; import { OptionTitle } from '../../common/components/view-params-editor/constants'; import { ViewOption } from '../../common/components/view-params-editor/viewParams.types'; @@ -194,7 +196,7 @@ type TimerOptions = { font?: string; keyColour?: string; timerColour?: string; -}; +} & TimeOptions; /** * Utility extract the view options from URL Params @@ -229,6 +231,7 @@ function getOptionsFromParams(searchParams: URLSearchParams, defaultValues?: URL font: getValue('font') ?? undefined, keyColour: makeColourString(getValue('keyColour')), timerColour: makeColourString(getValue('timerColour')), + timeformat: getTimeOptionsFromParams(searchParams, defaultValues), }; }