fix: format clock from preset (#2066)

* fix: format clock from preset

* fix: add to all views

* chore: format

* refactor: return flat value from common.options.ts
This commit is contained in:
Alex Christoffer Rasmussen
2026-05-03 18:43:58 +02:00
committed by GitHub
parent 1a08b39b8b
commit a5e733246d
14 changed files with 84 additions and 37 deletions
@@ -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');
}
+5 -4
View File
@@ -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;
+13 -8
View File
@@ -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 }:
<div className='project-header'>
{projectData?.logo && <ViewLogo name={projectData.logo} className='logo' />}
<div className='title'>{projectData.title}</div>
<BackstageClock />
<BackstageClock timeformat={timeformat} />
</div>
{showProgress && <ProgressBar className='progress-container' current={time.current} duration={time.duration} />}
@@ -131,7 +133,10 @@ function Backstage({ events, customFields, projectData, isMirrored, settings }:
{isOvertime(time.current) ? (
<div className='time-entry__value'>{getLocalizedString('countdown.overtime')}</div>
) : (
<SuperscriptTime time={formatTime(time.expectedFinish)} className='time-entry__value' />
<SuperscriptTime
time={formatTime(time.expectedFinish, { override: timeformat })}
className='time-entry__value'
/>
)}
</div>
<div className='timer-gap' />
@@ -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 (
<div className='clock-container'>
@@ -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),
};
}
+2 -1
View File
@@ -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;
@@ -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 (
<div className='clock-container'>
@@ -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),
};
}
+7 -4
View File
@@ -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 <StudioClockMobile clock={clock} onAir={onAir} />;
return <StudioClockMobile clock={clock} onAir={onAir} timeformat={timeformat} />;
}
const { seconds, display, meridian } = getLargeClockData(clock);
const { seconds, display, meridian } = getLargeClockData(clock, timeformat);
return (
<div className='studio__clock'>
@@ -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 (
<div className='studio__clock studio__clock--small'>
@@ -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),
};
}
@@ -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'];
}
@@ -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
<div className='project-header'>
{projectData?.logo && <ViewLogo name={projectData.logo} className='logo' />}
<div className='title'>{projectData.title}</div>
<TimelineClock />
<TimelineClock timeformat={timeformat} />
</div>
<TimelineSections now={now} next={next} followedBy={followedBy} mainSource={mainSource} />
@@ -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 (
<div className='clock-container'>
@@ -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),
};
}
+6 -4
View File
@@ -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,
</div>
)}
{showClock && <TimerAutoTickingClock />}
{showClock && <TimerAutoTickingClock clockFormat={timeformat} />}
<div className={cx(['timer-container', message.timer.blink && !showOverlay && 'blink'])}>
{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 (
+4 -1
View File
@@ -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),
};
}