improvement: hide seconds (#675)

* feat: optional timer seconds
This commit is contained in:
Carlos Valente
2023-12-31 12:13:05 +01:00
committed by GitHub
parent a5d8b91f34
commit 9a4d659b04
29 changed files with 223 additions and 125 deletions
+12 -22
View File
@@ -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 @@
}
]
}
}
}
@@ -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,
];
@@ -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() {
@@ -14,4 +14,5 @@ export type OverridableOptions = {
hideEndMessage?: boolean;
language?: string;
showProgressBar?: boolean;
hideTimerSeconds?: boolean;
};
@@ -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');
});
});
+11 -2
View File
@@ -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}`;
};
@@ -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);
};
@@ -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;
@@ -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);
});
});
@@ -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;
@@ -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<TimeManagerType, 'timerType' | 'current' | 'elapsed' | 'clock'>;
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;
}
};
@@ -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 (
<div className={`countdown ${isMirrored ? 'mirror' : ''}`} data-testid='countdown-view'>
<NavigationMenu />
<ViewParamsEditor paramFields={[timeOption]} />
<ViewParamsEditor paramFields={timeOption} />
{follow === null ? (
<CountdownSelect events={backstageEvents} />
) : (
@@ -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;
@@ -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);
@@ -107,7 +107,7 @@ $orange-active: #f60;
}
}
.ampmIndicator {
.clock__ampm {
font-family: monospace;
font-size: calc(var(--clock-size) / 20);
position: relative;
@@ -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 (
<div className={`studio-clock ${isMirrored ? 'mirror' : ''}`} data-testid='studio-view'>
<NavigationMenu />
<ViewParamsEditor paramFields={studioClockOptions} />
<div className='clock-container'>
{timeFormat == '12' && <div className='ampmIndicator'>{isAm ? 'am' : 'pm'}</div>}
<div className={`studio-timer ${showSeconds ? 'studio-timer--with-seconds' : ''}`}>{clock}</div>
{timeFormat == '12' && <div className='clock__ampm'>{isAm ? 'am' : 'pm'}</div>}
<div className={`studio-timer ${formatOptions.showSeconds ? 'studio-timer--with-seconds' : ''}`}>{clock}</div>
<div
ref={titleRef}
className='next-title'
@@ -15,15 +15,10 @@ import { formatTime } from '../../../common/utils/time';
import { isStringBoolean } from '../../../common/utils/viewUtils';
import { useTranslation } from '../../../translation/TranslationProvider';
import SuperscriptTime from '../common/superscript-time/SuperscriptTime';
import { formatTimerDisplay, getTimerByType } from '../common/viewerUtils';
import { getTimerByType, removePrependedZero } from '../common/viewerUtils';
import './Timer.scss';
const formatOptions = {
showSeconds: true,
format: 'hh:mm:ss a',
};
// motion
const titleVariants = {
hidden: {
@@ -72,6 +67,8 @@ export default function Timer(props: TimerProps) {
hideCards: false,
hideProgress: false,
hideMessage: false,
hideTimerSeconds: false,
hideClockSeconds: false,
};
const hideClock = searchParams.get('hideClock');
@@ -86,12 +83,19 @@ export default function Timer(props: TimerProps) {
const hideMessage = searchParams.get('hideMessage');
userOptions.hideMessage = isStringBoolean(hideMessage);
const clock = formatTime(time.clock, formatOptions);
const hideClockSeconds = searchParams.get('hideClockSeconds');
userOptions.hideClockSeconds = isStringBoolean(hideClockSeconds);
const clock = formatTime(time.clock, {
showSeconds: !userOptions.hideClockSeconds,
format: 'hh:mm:ss a',
});
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 finished = time.playback === Playback.Play && (time.current ?? 0) < 0 && time.startedAt;
const totalTime = (time.duration ?? 0) + (time.addedTime ?? 0);
@@ -113,11 +117,18 @@ export default function Timer(props: TimerProps) {
: viewSettings.normalColor;
const stageTimer = getTimerByType(time);
let display = formatTimerDisplay(stageTimer);
const stageTimerCharacters = display.replace('/:/g', '').length;
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;
const baseClasses = `stage-timer ${isMirrored ? 'mirror' : ''} ${showBlackout ? 'blackout' : ''}`;
let timerFontSize = 89 / (stageTimerCharacters - 1);
@@ -3,6 +3,7 @@ import { TranslationObject } from './en';
export const langDe: TranslationObject = {
'common.end_time': 'Endzeit',
'common.expected_finish': 'Voraussichtliches Ende',
'common.minutes': 'min',
'common.now': 'Jetzt',
'common.next': 'Nächste',
'common.public_message': 'Öffentliche Nachricht',
@@ -1,6 +1,7 @@
export const langEn = {
'common.end_time': 'End Time',
'common.expected_finish': 'Expected Finish',
'common.minutes': 'min',
'common.now': 'Now',
'common.next': 'Next',
'common.public_message': 'Public message',
@@ -3,6 +3,7 @@ import { TranslationObject } from './en';
export const langEs: TranslationObject = {
'common.end_time': 'Hora de finalización',
'common.expected_finish': 'Finalización esperada',
'common.minutes': 'min',
'common.now': 'Ahora',
'common.next': 'Siguiente',
'common.public_message': 'Mensaje público',
@@ -3,6 +3,7 @@ import { TranslationObject } from './en';
export const langFr: TranslationObject = {
'common.end_time': 'Termine à',
'common.expected_finish': 'Fin estimée à',
'common.minutes': 'min',
'common.now': 'Maintenant',
'common.next': 'A suivre',
'common.public_message': 'Message public',
+2 -1
View File
@@ -3,6 +3,7 @@ import { TranslationObject } from './en';
export const langIt: TranslationObject = {
'common.end_time': 'Ora di Fine',
'common.expected_finish': 'Fine Prevista',
'common.minutes': 'min',
'common.now': 'Adesso',
'common.next': 'Prossimo',
'common.public_message': 'Messaggio pubblico',
@@ -14,6 +15,6 @@ export const langIt: TranslationObject = {
'countdown.running': 'Evento in corso',
'countdown.select_event': 'Seleziona un evento da seguire',
'countdown.to_start': 'Tempo alla partenza',
'countdown.waiting': 'In attesa dell\'inizio dell\'evento',
'countdown.waiting': "In attesa dell'inizio dell'evento",
'countdown.overtime': 'in ritardo',
};
@@ -3,6 +3,7 @@ import { TranslationObject } from './en';
export const langNo: TranslationObject = {
'common.end_time': 'Sluttid',
'common.expected_finish': 'Forventet slutt',
'common.minutes': 'min',
'common.now': 'Nå',
'common.next': 'Neste',
'common.public_message': 'Offentlig beskjed',
@@ -3,6 +3,7 @@ import { TranslationObject } from './en';
export const langPt: TranslationObject = {
'common.end_time': 'Hora de término',
'common.expected_finish': 'Término esperado',
'common.minutes': 'min',
'common.now': 'Agora',
'common.next': 'Próximo',
'common.public_message': 'Mensagem pública',
@@ -3,6 +3,7 @@ import { TranslationObject } from './en';
export const langSv: TranslationObject = {
'common.end_time': 'Sluttid',
'common.expected_finish': 'Förväntat slut',
'common.minutes': 'min',
'common.now': 'Nu',
'common.next': 'Nästa',
'common.public_message': 'Offentligt meddelande',
@@ -1,9 +1,8 @@
import { LogOrigin, OntimeEvent } from 'ontime-types';
import { LogOrigin, OntimeEvent, isKeyOfType, isOntimeEvent } from 'ontime-types';
import { EventLoader } from '../classes/event-loader/EventLoader.js';
import { editEvent } from '../services/rundown-service/RundownService.js';
import { coerceString, coerceNumber, coerceBoolean, coerceColour } from '../utils/coerceType.js';
import { logger } from '../classes/Logger.js';
import { isKeyOfType, isOntimeEvent } from 'ontime-types/src/utils/guards.js';
const whitelistedPayload = {
title: coerceString,
@@ -1,4 +1,5 @@
import { Message, TimerMessage } from 'ontime-types';
import { throttle } from '../../utils/throttle.js';
import type { PublishFn } from '../../stores/EventStore.js';
+2 -1
View File
@@ -21,6 +21,7 @@ export type { Settings } from './definitions/core/Settings.type.js';
// ---> 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';
@@ -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;
}