mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-18 13:44:12 +00:00
Refactor/time formatting (#696)
* chore: upgrade related dependencies * fix: skip sentry on dev * refactor: unify time formatting * refactor: display default format for page
This commit is contained in:
@@ -1,9 +1,8 @@
|
||||
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';
|
||||
import { millisToString, removeLeadingZero } from 'ontime-utils';
|
||||
|
||||
import { overrideStylesURL } from '../../../common/api/apiConstants';
|
||||
import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu';
|
||||
@@ -16,8 +15,7 @@ import { getBackstageOptions } from '../../../common/components/view-params-edit
|
||||
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 { formatTime, getDefaultFormat } from '../../../common/utils/time';
|
||||
import { useTranslation } from '../../../translation/TranslationProvider';
|
||||
import { titleVariants } from '../common/animation';
|
||||
import SuperscriptTime from '../common/superscript-time/SuperscriptTime';
|
||||
@@ -41,8 +39,6 @@ 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,36 +64,22 @@ 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 clock = formatTime(time.clock);
|
||||
const startedAt = formatTime(time.startedAt);
|
||||
const isNegative = (time.current ?? 0) < 0;
|
||||
const expectedFinish = isNegative
|
||||
? getLocalizedString('countdown.overtime')
|
||||
: formatTime(time.expectedFinish, formatOptions);
|
||||
const expectedFinish = isNegative ? getLocalizedString('countdown.overtime') : formatTime(time.expectedFinish);
|
||||
|
||||
const qrSize = Math.max(window.innerWidth / 15, 128);
|
||||
const filteredEvents = backstageEvents.filter((event) => event.type === SupportedEvent.Event);
|
||||
const showPublicMessage = publ.text && publ.visible;
|
||||
const showProgress = time.playback !== 'stop';
|
||||
|
||||
let stageTimer;
|
||||
if (time.current === null) {
|
||||
stageTimer = '- - : - -';
|
||||
} else {
|
||||
stageTimer = formatDisplay(Math.abs(time.current), true);
|
||||
if (isNegative) {
|
||||
stageTimer = `-${stageTimer}`;
|
||||
}
|
||||
}
|
||||
let stageTimer = millisToString(time.current, { fallback: '- - : - -' });
|
||||
stageTimer = removeLeadingZero(stageTimer);
|
||||
|
||||
const totalTime = (time.duration ?? 0) + (time.addedTime ?? 0);
|
||||
const backstageOptions = getBackstageOptions(settings?.timeFormat ?? '24');
|
||||
const defaultFormat = getDefaultFormat(settings?.timeFormat);
|
||||
const backstageOptions = getBackstageOptions(defaultFormat);
|
||||
|
||||
return (
|
||||
<div className={`backstage ${isMirrored ? 'mirror' : ''}`} data-testid='backstage-view'>
|
||||
|
||||
@@ -9,7 +9,7 @@ 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 { formatTime, getDefaultFormat } from '../../../common/utils/time';
|
||||
import SuperscriptTime from '../common/superscript-time/SuperscriptTime';
|
||||
|
||||
import './Clock.scss';
|
||||
@@ -21,11 +21,6 @@ interface ClockProps {
|
||||
settings: Settings | undefined;
|
||||
}
|
||||
|
||||
const formatOptions = {
|
||||
showSeconds: true,
|
||||
format: 'hh:mm:ss a',
|
||||
};
|
||||
|
||||
export default function Clock(props: ClockProps) {
|
||||
const { isMirrored, time, viewSettings, settings } = props;
|
||||
const { shouldRender } = useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL);
|
||||
@@ -122,10 +117,11 @@ export default function Clock(props: ClockProps) {
|
||||
}
|
||||
}
|
||||
|
||||
const clock = formatTime(time.clock, formatOptions);
|
||||
const clock = formatTime(time.clock);
|
||||
const clean = clock.replace('/:/g', '');
|
||||
|
||||
const clockOptions = getClockOptions(settings?.timeFormat ?? '24');
|
||||
const defaultFormat = getDefaultFormat(settings?.timeFormat);
|
||||
const clockOptions = getClockOptions(defaultFormat);
|
||||
|
||||
return (
|
||||
<div
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* encapsulate logic related to showing a clock timer
|
||||
*/
|
||||
|
||||
import { MaybeNumber } from 'ontime-types';
|
||||
|
||||
import { formatTime } from '../../../../common/utils/time';
|
||||
import { FORMAT_12, FORMAT_24 } from '../../../../viewerConfig';
|
||||
import SuperscriptTime from '../superscript-time/SuperscriptTime';
|
||||
|
||||
interface ClockTimeProps {
|
||||
value: MaybeNumber;
|
||||
preferredFormat12?: string;
|
||||
preferredFormat24?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function ClockTime(props: ClockTimeProps) {
|
||||
const { value, preferredFormat12 = FORMAT_12, preferredFormat24 = FORMAT_24, className } = props;
|
||||
|
||||
// TODO: should we get the params from URL here to see if the user is overriding the default?
|
||||
const formattedTime = formatTime(value, { format12: preferredFormat12, format24: preferredFormat24 });
|
||||
|
||||
return <SuperscriptTime className={className} time={formattedTime} />;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* encapsulate logic related to showing a running timer
|
||||
*/
|
||||
|
||||
import { MaybeNumber } from 'ontime-types';
|
||||
import { millisToString, removeLeadingZero, removeSeconds } from 'ontime-utils';
|
||||
|
||||
interface RunningTimeProps {
|
||||
value: MaybeNumber;
|
||||
hideSeconds?: boolean;
|
||||
hideLeadingZero?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default function RunningTime(props: RunningTimeProps) {
|
||||
const { value, hideSeconds, hideLeadingZero, className } = props;
|
||||
let formattedTime = millisToString(value);
|
||||
|
||||
if (hideLeadingZero) {
|
||||
formattedTime = removeLeadingZero(formattedTime);
|
||||
}
|
||||
|
||||
if (hideSeconds) {
|
||||
formattedTime = removeSeconds(formattedTime);
|
||||
}
|
||||
|
||||
return <div className={className}>{formattedTime}</div>;
|
||||
}
|
||||
@@ -23,17 +23,3 @@ export function getTimerByType(timerObject?: TimerTypeParams): number | null {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { OntimeEvent, OntimeRundownEntry, Playback, Settings, SupportedEvent, ViewSettings } from 'ontime-types';
|
||||
import { formatDisplay } from 'ontime-utils';
|
||||
import { millisToString, removeLeadingZero } from 'ontime-utils';
|
||||
|
||||
import { overrideStylesURL } from '../../../common/api/apiConstants';
|
||||
import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu';
|
||||
@@ -9,8 +9,7 @@ import { getCountdownOptions } from '../../../common/components/view-params-edit
|
||||
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 { formatTime, getDefaultFormat } from '../../../common/utils/time';
|
||||
import { useTranslation } from '../../../translation/TranslationProvider';
|
||||
import SuperscriptTime from '../common/superscript-time/SuperscriptTime';
|
||||
|
||||
@@ -19,11 +18,6 @@ import CountdownSelect from './CountdownSelect';
|
||||
|
||||
import './Countdown.scss';
|
||||
|
||||
const formatOptionsFinished = {
|
||||
showSeconds: false,
|
||||
format: 'hh:mm a',
|
||||
};
|
||||
|
||||
interface CountdownProps {
|
||||
isMirrored: boolean;
|
||||
backstageEvents: OntimeEvent[];
|
||||
@@ -95,24 +89,24 @@ 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);
|
||||
const startTime = follow === null ? '...' : formatTime(follow.timeStart + delay);
|
||||
const endTime = follow === null ? '...' : formatTime(follow.timeEnd + delay);
|
||||
|
||||
const formatTimer = (): string => {
|
||||
if (runningMessage === TimerMessage.ended) {
|
||||
return formatTime(runningTimer, { format12: 'hh:mm a', format24: 'HH:mm' });
|
||||
}
|
||||
let formattedTime = millisToString(isSelected ? runningTimer : runningTimer + delay);
|
||||
if (isSelected || runningMessage === TimerMessage.waiting) {
|
||||
formattedTime = removeLeadingZero(formattedTime);
|
||||
}
|
||||
return formattedTime;
|
||||
};
|
||||
const formattedTimer = formatTimer();
|
||||
|
||||
const clock = formatTime(time.clock, formatOptions);
|
||||
const startTime = follow === null ? '...' : formatTime(follow.timeStart + delay, formatOptions);
|
||||
const endTime = follow === null ? '...' : formatTime(follow.timeEnd + delay, formatOptions);
|
||||
const formattedTimer =
|
||||
runningMessage === TimerMessage.ended
|
||||
? formatTime(runningTimer, formatOptionsFinished)
|
||||
: formatDisplay(
|
||||
isSelected ? runningTimer : runningTimer + delay,
|
||||
isSelected || runningMessage === TimerMessage.waiting,
|
||||
);
|
||||
|
||||
const timeOption = getCountdownOptions(settings?.timeFormat ?? '24');
|
||||
const defaultFormat = getDefaultFormat(settings?.timeFormat);
|
||||
const timeOption = getCountdownOptions(defaultFormat);
|
||||
|
||||
return (
|
||||
<div className={`countdown ${isMirrored ? 'mirror' : ''}`} data-testid='countdown-view'>
|
||||
@@ -135,7 +129,7 @@ export default function Countdown(props: CountdownProps) {
|
||||
time={formattedTimer}
|
||||
className={`timer ${standby ? 'timer--paused' : ''} ${isRunningFinished ? 'timer--finished' : ''}`}
|
||||
/>
|
||||
<div className='title'>{follow?.title || 'Untitled Event'}</div>
|
||||
{follow?.title && <div className='title'>{follow.title}</div>}
|
||||
|
||||
<div className='timer-group'>
|
||||
<div className='aux-timers'>
|
||||
|
||||
@@ -9,14 +9,12 @@ import { sanitiseTitle } from './countdown.helpers';
|
||||
|
||||
import './Countdown.scss';
|
||||
|
||||
const formatOptions = {
|
||||
format: 'hh:mm a',
|
||||
};
|
||||
|
||||
interface CountdownSelectProps {
|
||||
events: OntimeRundownEntry[];
|
||||
}
|
||||
|
||||
const scheduleFormat = { format12: 'hh:mm a', format24: 'HH:mm' };
|
||||
|
||||
export default function CountdownSelect(props: CountdownSelectProps) {
|
||||
const { events } = props;
|
||||
const { getLocalizedString } = useTranslation();
|
||||
@@ -35,8 +33,8 @@ export default function CountdownSelect(props: CountdownSelectProps) {
|
||||
filteredEvents.map((event: OntimeEvent, counter: number) => {
|
||||
const index = counter + 1;
|
||||
const title = sanitiseTitle(event.title);
|
||||
const start = formatTime(event.timeStart, formatOptions);
|
||||
const end = formatTime(event.timeEnd, formatOptions);
|
||||
const start = formatTime(event.timeStart, scheduleFormat);
|
||||
const end = formatTime(event.timeEnd, scheduleFormat);
|
||||
|
||||
return (
|
||||
<li key={event.id}>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { Playback, TimerMessage, TimerType, ViewSettings } from 'ontime-types';
|
||||
import { millisToString, removeLeadingZero, removeSeconds } from 'ontime-utils';
|
||||
|
||||
import { overrideStylesURL } from '../../../common/api/apiConstants';
|
||||
import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu';
|
||||
@@ -9,10 +10,9 @@ 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 { useTranslation } from '../../../translation/TranslationProvider';
|
||||
import { getTimerByType, removePrependedZero } from '../common/viewerUtils';
|
||||
import { getTimerByType } from '../common/viewerUtils';
|
||||
|
||||
import './MinimalTimer.scss';
|
||||
|
||||
@@ -153,13 +153,17 @@ export default function MinimalTimer(props: MinimalTimerProps) {
|
||||
: viewSettings.normalColor;
|
||||
|
||||
const stageTimer = getTimerByType(time);
|
||||
let display = '-- : -- : --';
|
||||
let display = millisToString(stageTimer, { fallback: '-- : -- : --' });
|
||||
if (stageTimer !== null) {
|
||||
display = formatTime(stageTimer, {
|
||||
showSeconds: !userOptions.hideTimerSeconds,
|
||||
format: 'hh:mm:ss a',
|
||||
});
|
||||
display = removePrependedZero(display);
|
||||
if (hideTimerSeconds) {
|
||||
display = removeSeconds(display);
|
||||
}
|
||||
display = removeLeadingZero(display);
|
||||
// last unit rounds up in negative timers
|
||||
const isNegative = stageTimer ?? 0 < 0;
|
||||
if (isNegative && display === '0') {
|
||||
display = '-1';
|
||||
}
|
||||
if (display.length < 3) {
|
||||
display = `${display} ${getLocalizedString('common.minutes')}`;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
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,8 +13,7 @@ import { getPublicOptions } from '../../../common/components/view-params-editor/
|
||||
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 { formatTime, getDefaultFormat } from '../../../common/utils/time';
|
||||
import { useTranslation } from '../../../translation/TranslationProvider';
|
||||
import { titleVariants } from '../common/animation';
|
||||
import SuperscriptTime from '../common/superscript-time/SuperscriptTime';
|
||||
@@ -49,8 +47,6 @@ export default function Public(props: BackstageProps) {
|
||||
settings,
|
||||
} = props;
|
||||
|
||||
const [searchParams] = useSearchParams();
|
||||
|
||||
const { shouldRender } = useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL);
|
||||
const { getLocalizedString } = useTranslation();
|
||||
|
||||
@@ -64,17 +60,11 @@ 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 clock = formatTime(time.clock);
|
||||
const qrSize = Math.max(window.innerWidth / 15, 128);
|
||||
|
||||
const publicOptions = getPublicOptions(settings?.timeFormat ?? '24');
|
||||
const defaultFormat = getDefaultFormat(settings?.timeFormat);
|
||||
const publicOptions = getPublicOptions(defaultFormat);
|
||||
|
||||
return (
|
||||
<div className={`public-screen ${isMirrored ? 'mirror' : ''}`} data-testid='public-view'>
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useEffect } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import type { OntimeEvent, OntimeRundown, Settings, ViewSettings } from 'ontime-types';
|
||||
import { isOntimeEvent, Playback } from 'ontime-types';
|
||||
import { formatDisplay } from 'ontime-utils';
|
||||
import { millisToString, removeSeconds } from 'ontime-utils';
|
||||
|
||||
import { overrideStylesURL } from '../../../common/api/apiConstants';
|
||||
import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu';
|
||||
@@ -11,13 +11,11 @@ import ViewParamsEditor from '../../../common/components/view-params-editor/View
|
||||
import useFitText from '../../../common/hooks/useFitText';
|
||||
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
||||
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 { formatTime, getDefaultFormat } from '../../../common/utils/time';
|
||||
import { isStringBoolean } from '../../../common/utils/viewUtils';
|
||||
import SuperscriptTime from '../common/superscript-time/SuperscriptTime';
|
||||
|
||||
import { trimRundown } from './studioClock.utils';
|
||||
import { secondsInMillis, trimRundown } from './studioClock.utils';
|
||||
|
||||
import './StudioClock.scss';
|
||||
|
||||
@@ -36,6 +34,7 @@ interface StudioClockProps {
|
||||
export default function StudioClock(props: StudioClockProps) {
|
||||
const { isMirrored, eventNext, time, backstageEvents, selectedId, nextId, onAir, viewSettings, settings } = props;
|
||||
|
||||
// TODO: can we prevent the Flash of Unstyled Content on the 7segment fonts?
|
||||
// deferring rendering seems to affect styling (font and useFitText)
|
||||
useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL);
|
||||
const { fontSize: titleFontSize, ref: titleRef } = useFitText({ maxFontSize: 500 });
|
||||
@@ -52,45 +51,51 @@ export default function StudioClock(props: StudioClockProps) {
|
||||
document.title = 'ontime - Studio Clock';
|
||||
}, []);
|
||||
|
||||
const hideSeconds = isStringBoolean(searchParams.get('hideSeconds'));
|
||||
const formatOptions = {
|
||||
showSeconds: !hideSeconds,
|
||||
format: 'hh:mm:ss',
|
||||
};
|
||||
let clock = formatTime(time.clock);
|
||||
let hasAmPm = '';
|
||||
if (clock.includes('AM')) {
|
||||
clock = clock.replace('PM', '');
|
||||
hasAmPm = 'AM';
|
||||
} else if (clock.includes('PM')) {
|
||||
clock = clock.replace('PM', '');
|
||||
hasAmPm = 'PM';
|
||||
}
|
||||
|
||||
const clock = formatTime(time.clock, formatOptions);
|
||||
const secondsNow = secondsInMillis(time.clock);
|
||||
const isNegative = (time.current ?? 0) < 0;
|
||||
const isPaused = time.playback === Playback.Pause;
|
||||
|
||||
const studioClockOptions = getStudioClockOptions(settings?.timeFormat ?? '24');
|
||||
const defaultFormat = getDefaultFormat(settings?.timeFormat);
|
||||
const studioClockOptions = getStudioClockOptions(defaultFormat);
|
||||
|
||||
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();
|
||||
let timer = millisToString(time.current, { fallback: '---' });
|
||||
const hideSeconds = isStringBoolean(searchParams.get('hideTimerSeconds'));
|
||||
if (time.current != null && hideSeconds) {
|
||||
timer = removeSeconds(timer);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`studio-clock ${isMirrored ? 'mirror' : ''}`} data-testid='studio-view'>
|
||||
<NavigationMenu />
|
||||
<ViewParamsEditor paramFields={studioClockOptions} />
|
||||
<div className='clock-container'>
|
||||
{timeFormat == '12' && <div className='clock__ampm'>{isAm ? 'am' : 'pm'}</div>}
|
||||
<div className={`studio-timer ${formatOptions.showSeconds ? 'studio-timer--with-seconds' : ''}`}>{clock}</div>
|
||||
{hasAmPm && <div className='clock__ampm'>{hasAmPm}</div>}
|
||||
<div className={`studio-timer ${!hideSeconds ? 'studio-timer--with-seconds' : ''}`}>{clock}</div>
|
||||
<div
|
||||
ref={titleRef}
|
||||
className='next-title'
|
||||
style={{ fontSize: titleFontSize, height: '12.5vh', width: '100%', maxWidth: '80%' }}
|
||||
>
|
||||
{eventNext?.title ?? '---'}
|
||||
{eventNext?.title}
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
next-countdown ${isNegative ? ' next-countdown--overtime' : ''} ${isPaused ? ' next-countdown--paused' : ''}
|
||||
`}
|
||||
>
|
||||
{isNegative ? '-' : ''}
|
||||
{formatDisplay(time.current)}
|
||||
{timer}
|
||||
</div>
|
||||
<div className='clock-indicators'>
|
||||
{activeIndicators.map((i) => (
|
||||
@@ -122,7 +127,7 @@ export default function StudioClock(props: StudioClockProps) {
|
||||
</div>
|
||||
<ul className='schedule'>
|
||||
{trimmedRundown.map((event) => {
|
||||
const start = formatTime(event.timeStart + (event?.delay ?? 0));
|
||||
const start = formatTime(event.timeStart + (event?.delay ?? 0), { format12: 'h:mm a', format24: 'HH:mm' });
|
||||
const isSelected = event.id === selectedId;
|
||||
const isNext = event.id === nextId;
|
||||
const classes = `schedule__item schedule__item${isSelected ? '--now' : isNext ? '--next' : '--future'}`;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { OntimeEvent } from 'ontime-types';
|
||||
|
||||
import { trimRundown } from '../studioClock.utils';
|
||||
import { secondsInMillis, trimRundown } from '../studioClock.utils';
|
||||
|
||||
describe('test trimEventlist function', () => {
|
||||
const limit = 8;
|
||||
@@ -118,3 +118,14 @@ describe('test trimEventlist function', () => {
|
||||
expect(l).toStrictEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('secondsInMillis()', () => {
|
||||
it('return 0 if value is null', () => {
|
||||
expect(secondsInMillis(null)).toBe(0);
|
||||
});
|
||||
it('returns the seconds value of a millis date', () => {
|
||||
const date = 1686255053619; // Thu Jun 08 2023 20:10:53
|
||||
const seconds = secondsInMillis(date);
|
||||
expect(seconds).toBe(53);
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,5 @@
|
||||
import { OntimeEvent } from 'ontime-types';
|
||||
import { MaybeNumber, OntimeEvent } from 'ontime-types';
|
||||
import { MILLIS_PER_MINUTE, MILLIS_PER_SECOND } from 'ontime-utils';
|
||||
|
||||
/**
|
||||
* @description Returns trimmed event list array
|
||||
@@ -19,3 +20,15 @@ export function trimRundown(rundown: OntimeEvent[], selectedId: string | null, l
|
||||
const trimmedRundown = rundown.slice(startIndex, endIndex);
|
||||
return trimmedRundown;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Returns amount of seconds in a date given in milliseconds. For studio clock second indicator
|
||||
* @param {MaybeNumber} millis time to format
|
||||
* @returns amount of elapsed seconds
|
||||
*/
|
||||
export function secondsInMillis(millis: MaybeNumber): number {
|
||||
if (!millis) {
|
||||
return 0;
|
||||
}
|
||||
return Math.floor((millis % MILLIS_PER_MINUTE) / MILLIS_PER_SECOND);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useEffect } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { Message, OntimeEvent, Playback, Settings, TimerMessage, TimerType, ViewSettings } from 'ontime-types';
|
||||
import { millisToString, removeLeadingZero, removeSeconds } from 'ontime-utils';
|
||||
|
||||
import { overrideStylesURL } from '../../../common/api/apiConstants';
|
||||
import MultiPartProgressBar from '../../../common/components/multi-part-progress-bar/MultiPartProgressBar';
|
||||
@@ -11,11 +12,11 @@ import { getTimerOptions } from '../../../common/components/view-params-editor/c
|
||||
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 { formatTime, getDefaultFormat } from '../../../common/utils/time';
|
||||
import { isStringBoolean } from '../../../common/utils/viewUtils';
|
||||
import { useTranslation } from '../../../translation/TranslationProvider';
|
||||
import SuperscriptTime from '../common/superscript-time/SuperscriptTime';
|
||||
import { getTimerByType, removePrependedZero } from '../common/viewerUtils';
|
||||
import { getTimerByType } from '../common/viewerUtils';
|
||||
|
||||
import './Timer.scss';
|
||||
|
||||
@@ -85,10 +86,7 @@ export default function Timer(props: TimerProps) {
|
||||
|
||||
const hideClockSeconds = searchParams.get('hideClockSeconds');
|
||||
userOptions.hideClockSeconds = isStringBoolean(hideClockSeconds);
|
||||
const clock = formatTime(time.clock, {
|
||||
showSeconds: !userOptions.hideClockSeconds,
|
||||
format: 'hh:mm:ss a',
|
||||
});
|
||||
const clock = formatTime(time.clock);
|
||||
|
||||
const hideTimerSeconds = searchParams.get('hideTimerSeconds');
|
||||
userOptions.hideTimerSeconds = isStringBoolean(hideTimerSeconds);
|
||||
@@ -117,13 +115,17 @@ export default function Timer(props: TimerProps) {
|
||||
: viewSettings.normalColor;
|
||||
|
||||
const stageTimer = getTimerByType(time);
|
||||
let display = '-- : -- : --';
|
||||
let display = millisToString(stageTimer, { fallback: '-- : -- : --' });
|
||||
if (stageTimer !== null) {
|
||||
display = formatTime(stageTimer, {
|
||||
showSeconds: !userOptions.hideTimerSeconds,
|
||||
format: 'hh:mm:ss a',
|
||||
});
|
||||
display = removePrependedZero(display);
|
||||
if (hideTimerSeconds) {
|
||||
display = removeSeconds(display);
|
||||
}
|
||||
display = removeLeadingZero(display);
|
||||
// last unit rounds up in negative timers
|
||||
const isNegative = stageTimer ?? 0 < 0;
|
||||
if (isNegative && display === '0') {
|
||||
display = '-1';
|
||||
}
|
||||
if (display.length < 3) {
|
||||
display = `${display} ${getLocalizedString('common.minutes')}`;
|
||||
}
|
||||
@@ -140,7 +142,8 @@ export default function Timer(props: TimerProps) {
|
||||
const timerContainerClasses = `timer-container ${showBlinking ? (showOverlay ? '' : 'blink') : ''}`;
|
||||
const timerClasses = `timer ${!isPlaying ? 'timer--paused' : ''} ${showFinished ? 'timer--finished' : ''}`;
|
||||
|
||||
const timerOptions = getTimerOptions(settings?.timeFormat ?? '24');
|
||||
const defaultFormat = getDefaultFormat(settings?.timeFormat);
|
||||
const timerOptions = getTimerOptions(defaultFormat);
|
||||
|
||||
return (
|
||||
<div className={showFinished ? `${baseClasses} stage-timer--finished` : baseClasses} data-testid='timer-view'>
|
||||
|
||||
Reference in New Issue
Block a user