diff --git a/.eslintrc b/.eslintrc index 23d1da5c3..94aadac2d 100644 --- a/.eslintrc +++ b/.eslintrc @@ -8,25 +8,12 @@ "jest": true }, "parser": "@typescript-eslint/parser", - "extends": [ - "eslint:recommended", - "plugin:@typescript-eslint/recommended", - "prettier", - "eslint-config-prettier" - ], - "plugins": [ - "@typescript-eslint", - "prettier" - ], + "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier", "eslint-config-prettier"], + "plugins": ["@typescript-eslint", "prettier"], "overrides": [ { - "files": [ - "e2e/**/**.spec.ts", - "e2e/**/**.test.ts" - ], - "extends": [ - "plugin:playwright/playwright-test" - ] + "files": ["e2e/**/**.spec.ts", "e2e/**/**.test.ts"], + "extends": ["plugin:playwright/playwright-test"] } ], "rules": { @@ -35,10 +22,13 @@ "no-console": [ "warn", { - "allow": [ - "warn", - "error" - ] + "allow": ["warn", "error"] + } + ], + "no-restricted-imports": [ + "error", + { + "patterns": ["ontime-types/src/*", "ontime-utils/src/*"] } ], "@typescript-eslint/no-non-null-assertion": "warn", @@ -59,4 +49,4 @@ } ] } -} \ No newline at end of file +} diff --git a/apps/client/src/common/components/view-params-editor/constants.ts b/apps/client/src/common/components/view-params-editor/constants.ts index ec1805e75..b8d5967c0 100644 --- a/apps/client/src/common/components/view-params-editor/constants.ts +++ b/apps/client/src/common/components/view-params-editor/constants.ts @@ -1,9 +1,8 @@ -import { UserFields } from 'ontime-types'; -import { TimeFormat } from 'ontime-types/src/definitions/core/TimeFormat.type'; +import { TimeFormat, UserFields } from 'ontime-types'; import { ParamField } from './types'; -export const getTimeOption = (timeFormat: TimeFormat): ParamField => ({ +const getTimeOption = (timeFormat: TimeFormat): ParamField => ({ id: 'format', title: '12 / 24 hour timer', description: 'Whether to show the time in 12 or 24 hour mode. Overrides the global setting from preferences', @@ -12,6 +11,22 @@ export const getTimeOption = (timeFormat: TimeFormat): ParamField => ({ defaultValue: timeFormat, }); +const hideClockSeconds: ParamField = { + id: 'hideClockSeconds', + title: 'Hide seconds in clocks', + description: 'Whether to hide seconds in auxiliar time fields (not the running timer)', + type: 'boolean', + defaultValue: false, +}; + +const hideTimerSeconds: ParamField = { + id: 'hideTimerSeconds', + title: 'Hide seconds in timer', + description: 'Whether to hide seconds in the running timer', + type: 'boolean', + defaultValue: false, +}; + export const getClockOptions = (timeFormat: TimeFormat): ParamField[] => [ getTimeOption(timeFormat), { @@ -86,6 +101,8 @@ export const getClockOptions = (timeFormat: TimeFormat): ParamField[] => [ export const getTimerOptions = (timeFormat: TimeFormat): ParamField[] => [ getTimeOption(timeFormat), + hideTimerSeconds, + hideClockSeconds, { id: 'hideClock', title: 'Hide Time Now', @@ -124,6 +141,7 @@ export const getTimerOptions = (timeFormat: TimeFormat): ParamField[] => [ ]; export const MINIMAL_TIMER_OPTIONS: ParamField[] = [ + hideTimerSeconds, { id: 'key', title: 'Key Colour', @@ -265,6 +283,7 @@ export const LOWER_THIRDS_OPTIONS: ParamField[] = [ export const getBackstageOptions = (timeFormat: TimeFormat): ParamField[] => [ getTimeOption(timeFormat), + hideClockSeconds, { id: 'hidePast', title: 'Hide past events', @@ -290,6 +309,7 @@ export const getBackstageOptions = (timeFormat: TimeFormat): ParamField[] => [ export const getPublicOptions = (timeFormat: TimeFormat): ParamField[] => [ getTimeOption(timeFormat), + hideClockSeconds, { id: 'hidePast', title: 'Hide past events', @@ -314,6 +334,7 @@ export const getPublicOptions = (timeFormat: TimeFormat): ParamField[] => [ ]; export const getStudioClockOptions = (timeFormat: TimeFormat): ParamField[] => [ getTimeOption(timeFormat), + hideClockSeconds, { id: 'seconds', title: 'Show Seconds', @@ -389,3 +410,9 @@ export const getOperatorOptions = (userFields: UserFields, timeFormat: TimeForma }, ]; }; + +export const getCountdownOptions = (timeFormat: TimeFormat): ParamField[] => [ + getTimeOption(timeFormat), + hideTimerSeconds, + hideClockSeconds, +]; diff --git a/apps/client/src/common/hooks-query/useOscSettings.ts b/apps/client/src/common/hooks-query/useOscSettings.ts index 09e84f0da..703a64edf 100644 --- a/apps/client/src/common/hooks-query/useOscSettings.ts +++ b/apps/client/src/common/hooks-query/useOscSettings.ts @@ -1,7 +1,4 @@ -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -//@ts-nocheck -- working on it import { useMutation, useQuery } from '@tanstack/react-query'; -import { OSCSettings } from 'ontime-types'; import { queryRefetchIntervalSlow } from '../../ontimeConfig'; import { OSC_SETTINGS } from '../api/apiConstants'; @@ -13,7 +10,10 @@ import { ontimeQueryClient } from '../queryClient'; export default function useOscSettings() { const { data, status, isFetching, isError, refetch } = useQuery({ queryKey: OSC_SETTINGS, - queryFn: getOSC, + queryFn: async () => { + const oscData = await getOSC(); + return { ...oscData, portIn: String(oscData.portIn), portOut: String(oscData.portOut) }; + }, placeholderData: oscPlaceholderSettings, retry: 5, retryDelay: (attempt: number) => attempt * 2500, @@ -21,8 +21,7 @@ export default function useOscSettings() { networkMode: 'always', }); - // we need to jump through some hoops because of the type op port - return { data: data! as unknown as OSCSettings, status, isFetching, isError, refetch }; + return { data: data ?? oscPlaceholderSettings, status, isFetching, isError, refetch }; } export function useOscSettingsMutation() { diff --git a/apps/client/src/common/models/View.types.ts b/apps/client/src/common/models/View.types.ts index 8085a93d0..91592ce6a 100644 --- a/apps/client/src/common/models/View.types.ts +++ b/apps/client/src/common/models/View.types.ts @@ -14,4 +14,5 @@ export type OverridableOptions = { hideEndMessage?: boolean; language?: string; showProgressBar?: boolean; + hideTimerSeconds?: boolean; }; diff --git a/apps/client/src/common/utils/__tests__/time.test.ts b/apps/client/src/common/utils/__tests__/time.test.ts index e96d432a7..88dce5ade 100644 --- a/apps/client/src/common/utils/__tests__/time.test.ts +++ b/apps/client/src/common/utils/__tests__/time.test.ts @@ -36,4 +36,14 @@ describe('formatTime()', () => { const time = formatTime(ms, options, () => '12'); expect(time).toStrictEqual('01:00 PM'); }); + + it('handles negative times', () => { + const ms = 1 * 60 * 60 * 1000; + const options = { + showSeconds: false, + format: 'hh:mm:ss', + }; + const time = formatTime(ms * -1, options, () => '24'); + expect(time).toStrictEqual('-01:00'); + }); }); diff --git a/apps/client/src/common/utils/time.ts b/apps/client/src/common/utils/time.ts index dc0945f7b..946f183e0 100644 --- a/apps/client/src/common/utils/time.ts +++ b/apps/client/src/common/utils/time.ts @@ -46,12 +46,21 @@ type FormatOptions = { * @param {function} resolver * @return {string} */ -export const formatTime = (milliseconds: number | null, options?: FormatOptions, resolver = resolveTimeFormat) => { +export const formatTime = ( + milliseconds: number | null, + options?: FormatOptions, + resolver = resolveTimeFormat, +): string => { if (milliseconds === null) { return '...'; } const timeFormat = resolver(); const fallback = options?.showSeconds ? 'hh:mm:ss a' : 'hh:mm a'; const { showSeconds = false, format: formatString = fallback } = options || {}; - return timeFormat === '12' ? formatFromMillis(milliseconds, formatString) : millisToString(milliseconds, showSeconds); + const isNegative = (milliseconds ?? 0) < 0; + const display = + timeFormat === '12' + ? formatFromMillis(Math.abs(milliseconds), formatString) + : millisToString(Math.abs(milliseconds), showSeconds); + return `${isNegative ? '-' : ''}${display}`; }; diff --git a/apps/client/src/features/modals/integration-modal/osc/OscSettings.tsx b/apps/client/src/features/modals/integration-modal/osc/OscSettings.tsx index f454a3e77..220b77b31 100644 --- a/apps/client/src/features/modals/integration-modal/osc/OscSettings.tsx +++ b/apps/client/src/features/modals/integration-modal/osc/OscSettings.tsx @@ -1,5 +1,3 @@ -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -//@ts-nocheck -- working on it import { useEffect } from 'react'; import { useForm } from 'react-hook-form'; import { FormControl, Input, Switch } from '@chakra-ui/react'; @@ -59,7 +57,6 @@ export default function OscSettings() { }; const resetForm = () => { - // @ts-expect-error -- we know the types dont match reset(data); }; diff --git a/apps/client/src/features/viewers/backstage/Backstage.tsx b/apps/client/src/features/viewers/backstage/Backstage.tsx index 74a83c737..87b0c1ed8 100644 --- a/apps/client/src/features/viewers/backstage/Backstage.tsx +++ b/apps/client/src/features/viewers/backstage/Backstage.tsx @@ -1,5 +1,6 @@ import { useEffect, useState } from 'react'; import QRCode from 'react-qr-code'; +import { useSearchParams } from 'react-router-dom'; import { AnimatePresence, motion } from 'framer-motion'; import { Message, OntimeEvent, ProjectData, Settings, SupportedEvent, ViewSettings } from 'ontime-types'; import { formatDisplay } from 'ontime-utils'; @@ -16,17 +17,13 @@ import ViewParamsEditor from '../../../common/components/view-params-editor/View import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet'; import { TimeManagerType } from '../../../common/models/TimeManager.type'; import { formatTime } from '../../../common/utils/time'; +import { isStringBoolean } from '../../../common/utils/viewUtils'; import { useTranslation } from '../../../translation/TranslationProvider'; import { titleVariants } from '../common/animation'; import SuperscriptTime from '../common/superscript-time/SuperscriptTime'; import './Backstage.scss'; -const formatOptions = { - showSeconds: true, - format: 'hh:mm:ss a', -}; - interface BackstageProps { isMirrored: boolean; publ: Message; @@ -43,6 +40,9 @@ interface BackstageProps { export default function Backstage(props: BackstageProps) { const { isMirrored, publ, eventNow, eventNext, time, backstageEvents, selectedId, general, viewSettings, settings } = props; + + const [searchParams] = useSearchParams(); + const { shouldRender } = useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL); const { getLocalizedString } = useTranslation(); const [blinkClass, setBlinkClass] = useState(false); @@ -68,6 +68,12 @@ export default function Backstage(props: BackstageProps) { return null; } + const hideSeconds = isStringBoolean(searchParams.get('hideSeconds')); + const formatOptions = { + showSeconds: !hideSeconds, + format: 'hh:mm:ss a', + }; + const clock = formatTime(time.clock, formatOptions); const startedAt = formatTime(time.startedAt, formatOptions); const isNegative = (time.current ?? 0) < 0; diff --git a/apps/client/src/features/viewers/common/__tests__/viewerUtils.test.ts b/apps/client/src/features/viewers/common/__tests__/viewerUtils.test.ts new file mode 100644 index 000000000..3bc08e277 --- /dev/null +++ b/apps/client/src/features/viewers/common/__tests__/viewerUtils.test.ts @@ -0,0 +1,21 @@ +import { removePrependedZero } from '../viewerUtils.js'; + +describe('removePrependedZero', () => { + it('should remove "00:" from the start of the string', () => { + const timer = '00:10:10'; + const result = removePrependedZero(timer); + expect(result).toBe('10:10'); + }); + + it('should remove "00:0:" from the start of the string', () => { + const timer = '00:01:10'; + const result = removePrependedZero(timer); + expect(result).toBe('1:10'); + }); + + it('should not modify the string if it does not start with "00:" or "00:0:"', () => { + const timer = '10:10:10'; + const result = removePrependedZero(timer); + expect(result).toBe(timer); + }); +}); diff --git a/apps/client/src/features/viewers/common/superscript-time/SuperscriptTime.tsx b/apps/client/src/features/viewers/common/superscript-time/SuperscriptTime.tsx index 6a1b99054..57aa5dfc8 100644 --- a/apps/client/src/features/viewers/common/superscript-time/SuperscriptTime.tsx +++ b/apps/client/src/features/viewers/common/superscript-time/SuperscriptTime.tsx @@ -8,6 +8,9 @@ interface SuperscriptTimeProps { style?: CSSProperties; } +/** + * @description receives a string like 12:00 AM and adds the period part to the superscript + */ export default function SuperscriptTime(props: SuperscriptTimeProps) { const { time, className, style } = props; diff --git a/apps/client/src/features/viewers/common/viewerUtils.ts b/apps/client/src/features/viewers/common/viewerUtils.ts index 103ce81e2..cc9b133fa 100644 --- a/apps/client/src/features/viewers/common/viewerUtils.ts +++ b/apps/client/src/features/viewers/common/viewerUtils.ts @@ -1,43 +1,39 @@ import { TimerType } from 'ontime-types'; -import { formatDisplay } from 'ontime-utils'; -import { TimeManagerType } from '../../../common/models/TimeManager.type'; -import { formatTime } from '../../../common/utils/time'; - -const formatOptions = { - showSeconds: true, - format: 'hh:mm:ss a', -}; +import type { TimeManagerType } from '../../../common/models/TimeManager.type'; type TimerTypeParams = Pick; -export function getTimerByType(timerObject?: TimerTypeParams): string | number | null { - let timer = null; +export function getTimerByType(timerObject?: TimerTypeParams): number | null { if (!timerObject) { - return timer; + return null; } - if (timerObject.timerType === TimerType.CountDown || timerObject.timerType === TimerType.TimeToEnd) { - timer = timerObject.current; - } else if (timerObject.timerType === TimerType.CountUp) { - timer = timerObject.elapsed; - } else if (timerObject.timerType === TimerType.Clock) { - timer = formatTime(timerObject.clock, formatOptions); + switch (timerObject.timerType) { + case TimerType.CountDown: + case TimerType.TimeToEnd: + return timerObject.current; + case TimerType.CountUp: + return Math.abs(timerObject.elapsed ?? 0); + case TimerType.Clock: + return timerObject.clock; + default: { + const exhaustiveCheck: never = timerObject.timerType; + return exhaustiveCheck; + } } +} +/** + * Receives a string such as 00:10:10 and removes the hours field if it is 00 + * @param timer + */ +export const removePrependedZero = (timer: string): string => { + if (timer.startsWith('00:0')) { + return timer.slice(4); + } + if (timer.startsWith('00:')) { + return timer.slice(3); + } return timer; -} - -export function formatTimerDisplay(timer?: string | number | null): string { - let display = ''; - - if (typeof timer === 'string') { - display = timer; - } else if (timer === null || typeof timer === 'undefined' || isNaN(timer)) { - display = '-- : -- : --'; - } else { - display = formatDisplay(timer, true); - } - - return display; -} +}; diff --git a/apps/client/src/features/viewers/countdown/Countdown.tsx b/apps/client/src/features/viewers/countdown/Countdown.tsx index 9b34877f4..e4525750f 100644 --- a/apps/client/src/features/viewers/countdown/Countdown.tsx +++ b/apps/client/src/features/viewers/countdown/Countdown.tsx @@ -5,11 +5,12 @@ import { formatDisplay } from 'ontime-utils'; import { overrideStylesURL } from '../../../common/api/apiConstants'; import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu'; -import { getTimeOption } from '../../../common/components/view-params-editor/constants'; +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 { formatTime } from '../../../common/utils/time'; +import { isStringBoolean } from '../../../common/utils/viewUtils'; import { useTranslation } from '../../../translation/TranslationProvider'; import SuperscriptTime from '../common/superscript-time/SuperscriptTime'; @@ -18,11 +19,6 @@ import CountdownSelect from './CountdownSelect'; import './Countdown.scss'; -const formatOptions = { - showSeconds: true, - format: 'hh:mm:ss a', -}; - const formatOptionsFinished = { showSeconds: false, format: 'hh:mm a', @@ -99,6 +95,12 @@ export default function Countdown(props: CountdownProps) { const isSelected = runningMessage === TimerMessage.running; const delayedTimerStyles = delay > 0 ? 'aux-timers__value--delayed' : ''; + const hideSeconds = isStringBoolean(searchParams.get('hideSeconds')); + const formatOptions = { + showSeconds: !hideSeconds, + format: 'hh:mm:ss a', + }; + const clock = formatTime(time.clock, formatOptions); const startTime = follow === null ? '...' : formatTime(follow.timeStart + delay, formatOptions); const endTime = follow === null ? '...' : formatTime(follow.timeEnd + delay, formatOptions); @@ -110,12 +112,12 @@ export default function Countdown(props: CountdownProps) { isSelected || runningMessage === TimerMessage.waiting, ); - const timeOption = getTimeOption(settings?.timeFormat ?? '24'); + const timeOption = getCountdownOptions(settings?.timeFormat ?? '24'); return (
- + {follow === null ? ( ) : ( diff --git a/apps/client/src/features/viewers/minimal-timer/MinimalTimer.tsx b/apps/client/src/features/viewers/minimal-timer/MinimalTimer.tsx index 863194991..e4ebd924e 100644 --- a/apps/client/src/features/viewers/minimal-timer/MinimalTimer.tsx +++ b/apps/client/src/features/viewers/minimal-timer/MinimalTimer.tsx @@ -9,8 +9,10 @@ import ViewParamsEditor from '../../../common/components/view-params-editor/View import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet'; import { TimeManagerType } from '../../../common/models/TimeManager.type'; import { OverridableOptions } from '../../../common/models/View.types'; +import { formatTime } from '../../../common/utils/time'; import { isStringBoolean } from '../../../common/utils/viewUtils'; -import { formatTimerDisplay, getTimerByType } from '../common/viewerUtils'; +import { useTranslation } from '../../../translation/TranslationProvider'; +import { getTimerByType, removePrependedZero } from '../common/viewerUtils'; import './MinimalTimer.scss'; @@ -24,6 +26,7 @@ interface MinimalTimerProps { export default function MinimalTimer(props: MinimalTimerProps) { const { isMirrored, pres, time, viewSettings } = props; const { shouldRender } = useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL); + const { getLocalizedString } = useTranslation(); const [searchParams] = useSearchParams(); useEffect(() => { @@ -35,9 +38,8 @@ export default function MinimalTimer(props: MinimalTimerProps) { return null; } - // get config from url: key, text, font, size, hideovertime - // eg. http://localhost:3000/minimal?key=f00&text=fff - // Check for user options + // TODO: this should be tied to the params + // USER OPTIONS const userOptions: OverridableOptions = { size: 1, }; @@ -126,10 +128,12 @@ export default function MinimalTimer(props: MinimalTimerProps) { const hideEndMessage = searchParams.get('hideendmessage'); userOptions.hideEndMessage = isStringBoolean(hideEndMessage); + const hideTimerSeconds = searchParams.get('hideTimerSeconds'); + userOptions.hideTimerSeconds = isStringBoolean(hideTimerSeconds); + const showOverlay = pres.text !== '' && pres.visible; const isPlaying = time.playback !== Playback.Pause; - const isNegative = - (time.current ?? 0) < 0 && time.timerType !== TimerType.Clock && time.timerType !== TimerType.CountUp; + const showEndMessage = (time.current ?? 0) < 0 && viewSettings.endMessage && !hideEndMessage; const finished = time.playback === Playback.Play && (time.current ?? 0) < 0 && time.startedAt; const showFinished = finished && !userOptions?.hideOvertime && (time.timerType !== TimerType.Clock || showEndMessage); @@ -149,9 +153,16 @@ export default function MinimalTimer(props: MinimalTimerProps) { : viewSettings.normalColor; const stageTimer = getTimerByType(time); - let display = formatTimerDisplay(stageTimer); - if (isNegative) { - display = `-${display}`; + let display = '-- : -- : --'; + if (stageTimer !== null) { + display = formatTime(stageTimer, { + showSeconds: !userOptions.hideTimerSeconds, + format: 'hh:mm:ss a', + }); + display = removePrependedZero(display); + if (display.length < 3) { + display = `${display} ${getLocalizedString('common.minutes')}`; + } } const stageTimerCharacters = display.replace('/:/g', '').length; diff --git a/apps/client/src/features/viewers/public/Public.tsx b/apps/client/src/features/viewers/public/Public.tsx index 372206b12..efd6bf14b 100644 --- a/apps/client/src/features/viewers/public/Public.tsx +++ b/apps/client/src/features/viewers/public/Public.tsx @@ -1,5 +1,6 @@ import { useEffect } from 'react'; import QRCode from 'react-qr-code'; +import { useSearchParams } from 'react-router-dom'; import { AnimatePresence, motion } from 'framer-motion'; import { Message, OntimeEvent, ProjectData, Settings, ViewSettings } from 'ontime-types'; @@ -14,17 +15,13 @@ import ViewParamsEditor from '../../../common/components/view-params-editor/View import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet'; import { TimeManagerType } from '../../../common/models/TimeManager.type'; import { formatTime } from '../../../common/utils/time'; +import { isStringBoolean } from '../../../common/utils/viewUtils'; import { useTranslation } from '../../../translation/TranslationProvider'; import { titleVariants } from '../common/animation'; import SuperscriptTime from '../common/superscript-time/SuperscriptTime'; import './Public.scss'; -const formatOptions = { - showSeconds: true, - format: 'hh:mm:ss a', -}; - interface BackstageProps { isMirrored: boolean; publ: Message; @@ -51,6 +48,9 @@ export default function Public(props: BackstageProps) { viewSettings, settings, } = props; + + const [searchParams] = useSearchParams(); + const { shouldRender } = useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL); const { getLocalizedString } = useTranslation(); @@ -64,6 +64,13 @@ export default function Public(props: BackstageProps) { } const showPublicMessage = publ.text && publ.visible; + + const hideSeconds = isStringBoolean(searchParams.get('hideSeconds')); + const formatOptions = { + showSeconds: !hideSeconds, + format: 'hh:mm:ss a', + }; + const clock = formatTime(time.clock, formatOptions); const qrSize = Math.max(window.innerWidth / 15, 128); diff --git a/apps/client/src/features/viewers/studio/StudioClock.scss b/apps/client/src/features/viewers/studio/StudioClock.scss index 0f230c2fe..f0b95dcb9 100644 --- a/apps/client/src/features/viewers/studio/StudioClock.scss +++ b/apps/client/src/features/viewers/studio/StudioClock.scss @@ -107,7 +107,7 @@ $orange-active: #f60; } } -.ampmIndicator { +.clock__ampm { font-family: monospace; font-size: calc(var(--clock-size) / 20); position: relative; diff --git a/apps/client/src/features/viewers/studio/StudioClock.tsx b/apps/client/src/features/viewers/studio/StudioClock.tsx index c97e287c5..0b52f4f5f 100644 --- a/apps/client/src/features/viewers/studio/StudioClock.tsx +++ b/apps/client/src/features/viewers/studio/StudioClock.tsx @@ -14,17 +14,13 @@ import { TimeManagerType } from '../../../common/models/TimeManager.type'; import { secondsInMillis } from '../../../common/utils/dateConfig'; import { formatTime, resolveTimeFormat } from '../../../common/utils/time'; import { mth } from '../../../common/utils/timeConstants'; +import { isStringBoolean } from '../../../common/utils/viewUtils'; import SuperscriptTime from '../common/superscript-time/SuperscriptTime'; import { trimRundown } from './studioClock.utils'; import './StudioClock.scss'; -const formatOptions = { - showSeconds: false, - format: 'hh:mm', -}; - interface StudioClockProps { isMirrored: boolean; eventNext: OntimeEvent | null; @@ -51,15 +47,17 @@ export default function StudioClock(props: StudioClockProps) { const MAX_TITLES = 11; const [searchParams] = useSearchParams(); - const showSeconds = searchParams.get('seconds'); - const timeFormat = resolveTimeFormat(); - formatOptions.showSeconds = Boolean(showSeconds); - formatOptions.format = `hh:mm${formatOptions.showSeconds ? ':ss' : ''}`; useEffect(() => { document.title = 'ontime - Studio Clock'; }, []); + const hideSeconds = isStringBoolean(searchParams.get('hideSeconds')); + const formatOptions = { + showSeconds: !hideSeconds, + format: 'hh:mm:ss', + }; + const clock = formatTime(time.clock, formatOptions); const secondsNow = secondsInMillis(time.clock); const isNegative = (time.current ?? 0) < 0; @@ -70,14 +68,15 @@ export default function StudioClock(props: StudioClockProps) { const delayed = backstageEvents.filter((event) => isOntimeEvent(event)) as OntimeEvent[]; const trimmedRundown = trimRundown(delayed, selectedId, MAX_TITLES); const isAm = time.clock / (mth * 12) > 12; + const timeFormat = resolveTimeFormat(); return (
- {timeFormat == '12' &&
{isAm ? 'am' : 'pm'}
} -
{clock}
+ {timeFormat == '12' &&
{isAm ? 'am' : 'pm'}
} +
{clock}
Views export type { ViewSettings } from './definitions/core/Views.type.js'; +export type { TimeFormat } from './definitions/core/TimeFormat.type.js'; // ---> Aliases export type { Alias } from './definitions/core/Alias.type.js'; @@ -61,5 +62,5 @@ export type { TimerState } from './definitions/runtime/TimerState.type.js'; // CLIENT // TYPE UTILITIES -export { isOntimeBlock, isOntimeDelay, isOntimeEvent } from './utils/guards.js'; +export { isOntimeBlock, isOntimeDelay, isOntimeEvent, isKeyOfType } from './utils/guards.js'; export type { MaybeNumber } from './utils/utils.type.js'; diff --git a/packages/utils/src/date-utils/formatDisplay.ts b/packages/utils/src/date-utils/formatDisplay.ts index ce9f1f759..48bf70edf 100644 --- a/packages/utils/src/date-utils/formatDisplay.ts +++ b/packages/utils/src/date-utils/formatDisplay.ts @@ -23,7 +23,7 @@ export function formatDisplay(milliseconds: number | null, hideZero = false): st return [hours, minutes, s % 60].map(format).join(':'); } -export const millisToSeconds = (millis: number | null): number => { +const millisToSeconds = (millis: number | null): number => { if (millis === null) { return 0; }