mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-12 19:03:47 +00:00
refactor: extract and simplify logic
This commit is contained in:
committed by
Carlos Valente
parent
49cae647d6
commit
3f35b5e297
@@ -1,57 +1,42 @@
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { AnimatePresence } from 'framer-motion';
|
||||
import {
|
||||
CustomFields,
|
||||
MessageState,
|
||||
OntimeEvent,
|
||||
Playback,
|
||||
ProjectData,
|
||||
Settings,
|
||||
SimpleTimerState,
|
||||
TimerPhase,
|
||||
TimerType,
|
||||
ViewSettings,
|
||||
} from 'ontime-types';
|
||||
|
||||
import { FitText } from '../../common/components/fit-text/FitText';
|
||||
import MultiPartProgressBar from '../../common/components/multi-part-progress-bar/MultiPartProgressBar';
|
||||
import TitleCard from '../../common/components/title-card/TitleCard';
|
||||
import ViewLogo from '../../common/components/view-logo/ViewLogo';
|
||||
import ViewParamsEditor from '../../common/components/view-params-editor/ViewParamsEditor';
|
||||
import { useWindowTitle } from '../../common/hooks/useWindowTitle';
|
||||
import { ViewExtendedTimer } from '../../common/models/TimeManager.type';
|
||||
import { cx } from '../../common/utils/styleUtils';
|
||||
import { formatTime, getDefaultFormat } from '../../common/utils/time';
|
||||
import SuperscriptTime from '../../features/viewers/common/superscript-time/SuperscriptTime';
|
||||
import {
|
||||
getFormattedTimer,
|
||||
getPropertyValue,
|
||||
getTimerByType,
|
||||
isStringBoolean,
|
||||
} from '../../features/viewers/common/viewUtils';
|
||||
import { getFormattedTimer, getPropertyValue, getTimerByType } from '../../features/viewers/common/viewUtils';
|
||||
import { useTranslation } from '../../translation/TranslationProvider';
|
||||
|
||||
import { getTimerOptions } from './timer.options';
|
||||
import { MotionTitleCard, titleVariants } from './timer.animations';
|
||||
import { getTimerOptions, useTimerOptions } from './timer.options';
|
||||
import {
|
||||
getEstimatedFontSize,
|
||||
getIsPlaying,
|
||||
getSecondaryDisplay,
|
||||
getShowClock,
|
||||
getShowMessage,
|
||||
getShowModifiers,
|
||||
getShowProgressBar,
|
||||
getTimerColour,
|
||||
getTotalTime,
|
||||
} from './timer.utils';
|
||||
|
||||
import './Timer.scss';
|
||||
|
||||
// motion
|
||||
const titleVariants = {
|
||||
hidden: {
|
||||
x: -2500,
|
||||
},
|
||||
visible: {
|
||||
x: 0,
|
||||
transition: {
|
||||
duration: 1,
|
||||
},
|
||||
},
|
||||
exit: {
|
||||
x: -2500,
|
||||
},
|
||||
};
|
||||
|
||||
export const MotionTitleCard = motion(TitleCard);
|
||||
|
||||
interface TimerProps {
|
||||
auxTimer: SimpleTimerState;
|
||||
customFields: CustomFields;
|
||||
@@ -69,170 +54,126 @@ export default function Timer(props: TimerProps) {
|
||||
const { auxTimer, customFields, eventNow, eventNext, general, isMirrored, message, settings, time, viewSettings } =
|
||||
props;
|
||||
|
||||
const {
|
||||
hideClock,
|
||||
hideCards,
|
||||
hideProgress,
|
||||
hideMessage,
|
||||
hideTimerSeconds,
|
||||
removeLeadingZeros,
|
||||
mainSource,
|
||||
secondarySource,
|
||||
} = useTimerOptions();
|
||||
|
||||
const { getLocalizedString } = useTranslation();
|
||||
const [searchParams] = useSearchParams();
|
||||
|
||||
useWindowTitle('Timer');
|
||||
|
||||
// USER OPTIONS
|
||||
const userOptions = {
|
||||
hideClock: false,
|
||||
hideCards: false,
|
||||
hideProgress: false,
|
||||
hideMessage: false,
|
||||
hideTimerSeconds: false,
|
||||
hideClockSeconds: false,
|
||||
removeLeadingZeros: true,
|
||||
};
|
||||
// gather modifiers
|
||||
const showOverlay = getShowMessage(message.timer);
|
||||
const { showEndMessage, showFinished, showWarning, showDanger } = getShowModifiers(
|
||||
time.timerType,
|
||||
time.countToEnd,
|
||||
time.phase,
|
||||
viewSettings,
|
||||
);
|
||||
const isPlaying = getIsPlaying(time.playback);
|
||||
const showClock = !hideClock && getShowClock(time.timerType);
|
||||
const showProgressBar = !hideProgress && getShowProgressBar(time.timerType);
|
||||
|
||||
const hideClock = searchParams.get('hideClock');
|
||||
userOptions.hideClock = isStringBoolean(hideClock);
|
||||
|
||||
const hideCards = searchParams.get('hideCards');
|
||||
userOptions.hideCards = isStringBoolean(hideCards);
|
||||
|
||||
const hideProgress = searchParams.get('hideProgress');
|
||||
userOptions.hideProgress = isStringBoolean(hideProgress);
|
||||
|
||||
const hideMessage = searchParams.get('hideMessage');
|
||||
userOptions.hideMessage = isStringBoolean(hideMessage);
|
||||
|
||||
const hideClockSeconds = searchParams.get('hideClockSeconds');
|
||||
userOptions.hideClockSeconds = isStringBoolean(hideClockSeconds);
|
||||
const clock = formatTime(time.clock);
|
||||
|
||||
const hideTimerSeconds = searchParams.get('hideTimerSeconds');
|
||||
userOptions.hideTimerSeconds = isStringBoolean(hideTimerSeconds);
|
||||
|
||||
const showLeadingZeros = searchParams.get('showLeadingZeros');
|
||||
userOptions.removeLeadingZeros = !isStringBoolean(showLeadingZeros);
|
||||
|
||||
const secondarySource = searchParams.get('secondary-src');
|
||||
// gather card data
|
||||
const mainFieldNow = getPropertyValue(eventNow, mainSource) ?? eventNow?.title ?? '';
|
||||
const mainFieldNext = getPropertyValue(eventNext, mainSource) ?? eventNext?.title ?? '';
|
||||
const secondaryTextNow = getPropertyValue(eventNow, secondarySource);
|
||||
const secondaryTextNext = getPropertyValue(eventNext, secondarySource);
|
||||
|
||||
const main = searchParams.get('main');
|
||||
const mainFieldNow = (main ? getPropertyValue(eventNow, main) : eventNow?.title) ?? '';
|
||||
const mainFieldNext = (main ? getPropertyValue(eventNext, main) : eventNext?.title) ?? '';
|
||||
|
||||
const showOverlay = message.timer.text !== '' && message.timer.visible;
|
||||
const isPlaying = time.playback !== Playback.Pause;
|
||||
|
||||
const timerIsTimeOfDay = time.timerType === TimerType.Clock;
|
||||
|
||||
const finished = time.phase === TimerPhase.Overtime;
|
||||
const totalTime = (time.duration ?? 0) + (time.addedTime ?? 0);
|
||||
|
||||
const shouldShowModifiers = time.timerType === TimerType.CountDown || time.countToEnd;
|
||||
const showEndMessage = shouldShowModifiers && finished && viewSettings.endMessage;
|
||||
const showProgress =
|
||||
eventNow !== null &&
|
||||
time.timerType !== TimerType.None &&
|
||||
time.timerType !== TimerType.Clock &&
|
||||
time.playback !== Playback.Stop;
|
||||
const showFinished = shouldShowModifiers && finished && (shouldShowModifiers || showEndMessage);
|
||||
const showWarning = shouldShowModifiers && time.phase === TimerPhase.Warning;
|
||||
const showDanger = shouldShowModifiers && time.phase === TimerPhase.Danger;
|
||||
const showClock = time.timerType !== TimerType.Clock;
|
||||
|
||||
const secondaryContent = ((): string | undefined => {
|
||||
if (message.timer.secondarySource === 'aux') {
|
||||
return getFormattedTimer(auxTimer.current, TimerType.CountDown, getLocalizedString('common.minutes'), {
|
||||
removeSeconds: userOptions.hideTimerSeconds,
|
||||
removeLeadingZero: userOptions.removeLeadingZeros,
|
||||
});
|
||||
}
|
||||
if (message.timer.secondarySource === 'external' && message.external) {
|
||||
return message.external;
|
||||
}
|
||||
return;
|
||||
})();
|
||||
|
||||
let timerColor = viewSettings.normalColor;
|
||||
if (!timerIsTimeOfDay && showProgress && showWarning) timerColor = viewSettings.warningColor;
|
||||
if (!timerIsTimeOfDay && showProgress && showDanger) timerColor = viewSettings.dangerColor;
|
||||
|
||||
// gather timer data
|
||||
const totalTime = getTotalTime(time.duration, time.addedTime);
|
||||
const clock = formatTime(time.clock);
|
||||
const stageTimer = getTimerByType(viewSettings.freezeEnd, time);
|
||||
const display = getFormattedTimer(stageTimer, time.timerType, getLocalizedString('common.minutes'), {
|
||||
removeSeconds: userOptions.hideTimerSeconds,
|
||||
removeLeadingZero: userOptions.removeLeadingZeros,
|
||||
removeSeconds: hideTimerSeconds,
|
||||
removeLeadingZero: removeLeadingZeros,
|
||||
});
|
||||
|
||||
const stageTimerCharacters = display.replace('/:/g', '').length;
|
||||
const secondaryContent = getSecondaryDisplay(
|
||||
message,
|
||||
auxTimer.current,
|
||||
getLocalizedString('common.minutes'),
|
||||
hideTimerSeconds,
|
||||
removeLeadingZeros,
|
||||
);
|
||||
|
||||
const baseClasses = `stage-timer ${isMirrored ? 'mirror' : ''}`;
|
||||
|
||||
let timerFontSize = 89 / (stageTimerCharacters - 1);
|
||||
// we need to shrink the timer if the external is going to be there
|
||||
if (secondaryContent) {
|
||||
timerFontSize *= 0.8;
|
||||
}
|
||||
const externalFontSize = timerFontSize * 0.4;
|
||||
const timerContainerClasses = `timer-container ${message.timer.blink ? (showOverlay ? '' : 'blink') : ''}`;
|
||||
const timerClasses = `timer ${!isPlaying ? 'timer--paused' : ''} ${showFinished ? 'timer--finished' : ''}`;
|
||||
// gather presentation styles
|
||||
const timerColour = getTimerColour(viewSettings, showWarning, showDanger);
|
||||
const { timerFontSize, externalFontSize } = getEstimatedFontSize(display, Boolean(secondaryContent));
|
||||
|
||||
// gather option data
|
||||
const defaultFormat = getDefaultFormat(settings?.timeFormat);
|
||||
const timerOptions = getTimerOptions(defaultFormat, customFields);
|
||||
const disableProgress = timerIsTimeOfDay || time.timerType === TimerType.None;
|
||||
|
||||
return (
|
||||
<div className={showFinished ? `${baseClasses} stage-timer--finished` : baseClasses} data-testid='timer-view'>
|
||||
<div
|
||||
className={cx(['stage-timer', isMirrored && 'mirror', showFinished && 'stage-timer--finished'])}
|
||||
data-testid='timer-view'
|
||||
>
|
||||
{general?.projectLogo && <ViewLogo name={general.projectLogo} className='logo' />}
|
||||
|
||||
<ViewParamsEditor viewOptions={timerOptions} />
|
||||
<div className={message.timer.blackout ? 'blackout blackout--active' : 'blackout'} />
|
||||
{!userOptions.hideMessage && (
|
||||
<div className={showOverlay ? 'message-overlay message-overlay--active' : 'message-overlay'}>
|
||||
<FitText mode='multi' min={32} max={256} className={`message ${message.timer.blink ? 'blink' : ''}`}>
|
||||
|
||||
<div className={cx(['blackout', message.timer.blackout && 'blackout--active'])} />
|
||||
|
||||
{!hideMessage && (
|
||||
<div className={cx(['message-overlay', showOverlay && ' message-overlay--active'])}>
|
||||
<FitText mode='multi' min={32} max={256} className={cx(['message', message.timer.blink && 'blink'])}>
|
||||
{message.timer.text}
|
||||
</FitText>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!userOptions.hideClock && (
|
||||
<div className={`clock-container ${showClock ? '' : 'clock-container--hidden'}`}>
|
||||
{showClock && (
|
||||
<div className='clock-container'>
|
||||
<div className='label'>{getLocalizedString('common.time_now')}</div>
|
||||
<SuperscriptTime time={clock} className='clock' />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={timerContainerClasses}>
|
||||
<div className={cx(['timer-container', message.timer.blink && !showOverlay && 'blink'])}>
|
||||
{showEndMessage ? (
|
||||
<div className='end-message'>{viewSettings.endMessage}</div>
|
||||
) : (
|
||||
<div
|
||||
className={timerClasses}
|
||||
className={cx(['timer', !isPlaying && 'timer--paused', showFinished && 'timer--finished'])}
|
||||
style={{
|
||||
fontSize: `${timerFontSize}vw`,
|
||||
'--phase-color': timerColor,
|
||||
'--phase-color': timerColour,
|
||||
}}
|
||||
>
|
||||
{display}
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={`secondary${secondaryContent ? '' : ' secondary--hidden'}`}
|
||||
className={cx(['secondary', !secondaryContent && 'secondary--hidden'])}
|
||||
style={{ fontSize: `${externalFontSize}vw` }}
|
||||
>
|
||||
{secondaryContent}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!userOptions.hideProgress && (
|
||||
{showProgressBar && (
|
||||
<MultiPartProgressBar
|
||||
className={isPlaying ? 'progress-container' : 'progress-container progress-container--paused'}
|
||||
now={disableProgress ? null : time.current}
|
||||
className={cx(['progress-container', !isPlaying && 'progress-container--paused'])}
|
||||
now={time.current}
|
||||
complete={totalTime}
|
||||
normalColor={viewSettings.normalColor}
|
||||
warning={eventNow?.timeWarning}
|
||||
warningColor={viewSettings.warningColor}
|
||||
danger={eventNow?.timeDanger}
|
||||
dangerColor={viewSettings.dangerColor}
|
||||
hidden={!showProgress}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!userOptions.hideCards && (
|
||||
{!hideCards && (
|
||||
<>
|
||||
<AnimatePresence>
|
||||
{eventNow?.title && (
|
||||
@@ -253,6 +194,7 @@ export default function Timer(props: TimerProps) {
|
||||
<AnimatePresence>
|
||||
{eventNext?.title && (
|
||||
<MotionTitleCard
|
||||
className='event next'
|
||||
key='next'
|
||||
variants={titleVariants}
|
||||
initial='hidden'
|
||||
@@ -261,7 +203,6 @@ export default function Timer(props: TimerProps) {
|
||||
label='next'
|
||||
title={mainFieldNext}
|
||||
secondary={secondaryTextNext}
|
||||
className='event next'
|
||||
/>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
import TitleCard from '../../common/components/title-card/TitleCard';
|
||||
|
||||
export const titleVariants = {
|
||||
hidden: {
|
||||
x: -2500,
|
||||
},
|
||||
visible: {
|
||||
x: 0,
|
||||
transition: {
|
||||
duration: 1,
|
||||
},
|
||||
},
|
||||
exit: {
|
||||
x: -2500,
|
||||
},
|
||||
};
|
||||
|
||||
export const MotionTitleCard = motion(TitleCard);
|
||||
@@ -1,4 +1,6 @@
|
||||
import { CustomFields } from 'ontime-types';
|
||||
import { useMemo } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { CustomFields, OntimeEvent } from 'ontime-types';
|
||||
|
||||
import {
|
||||
getTimeOption,
|
||||
@@ -7,6 +9,7 @@ import {
|
||||
showLeadingZeros,
|
||||
} from '../../common/components/view-params-editor/constants';
|
||||
import { ViewOption } from '../../common/components/view-params-editor/types';
|
||||
import { isStringBoolean } from '../../features/viewers/common/viewUtils';
|
||||
|
||||
export const getTimerOptions = (timeFormat: string, customFields: CustomFields): ViewOption[] => {
|
||||
const mainOptions = makeOptionsFromCustomFields(customFields, { title: 'Title', note: 'Note' });
|
||||
@@ -59,17 +62,58 @@ export const getTimerOptions = (timeFormat: string, customFields: CustomFields):
|
||||
},
|
||||
{
|
||||
id: 'hideMessage',
|
||||
title: 'Hide Presenter Message',
|
||||
description: 'Prevents the screen from displaying messages from the presenter',
|
||||
title: 'Hide Timer Message',
|
||||
description: 'Prevents displaying fullscreen messages in the timer',
|
||||
type: 'boolean',
|
||||
defaultValue: false,
|
||||
},
|
||||
{
|
||||
id: 'hideExternal',
|
||||
title: 'Hide External',
|
||||
description: 'Prevents the screen from displaying the external field',
|
||||
title: 'Hide Auxiliary timer / External message',
|
||||
description: 'Prevents the screen from displaying the secondary timer field',
|
||||
type: 'boolean',
|
||||
defaultValue: false,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
type TimerOptions = {
|
||||
hideClock: boolean;
|
||||
hideCards: boolean;
|
||||
hideProgress: boolean;
|
||||
hideMessage: boolean;
|
||||
hideExternal: boolean;
|
||||
hideTimerSeconds: boolean;
|
||||
removeLeadingZeros: boolean;
|
||||
mainSource: keyof OntimeEvent | null;
|
||||
secondarySource: keyof OntimeEvent | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Utility extract the view options from URL Params
|
||||
* the names and fallbacks are manually matched with timerOptions
|
||||
*/
|
||||
function getOptionsFromParams(searchParams: URLSearchParams): TimerOptions {
|
||||
// we manually make an object that matches the key above
|
||||
return {
|
||||
hideClock: isStringBoolean(searchParams.get('hideClock')),
|
||||
hideCards: isStringBoolean(searchParams.get('hideCards')),
|
||||
hideProgress: isStringBoolean(searchParams.get('hideProgress')),
|
||||
hideMessage: isStringBoolean(searchParams.get('hideMessage')),
|
||||
hideExternal: isStringBoolean(searchParams.get('hideExternal')),
|
||||
hideTimerSeconds: isStringBoolean(searchParams.get('hideTimerSeconds')),
|
||||
removeLeadingZeros: !isStringBoolean(searchParams.get('showLeadingZeros')),
|
||||
|
||||
mainSource: searchParams.get('main') as keyof OntimeEvent | null,
|
||||
secondarySource: searchParams.get('secondary-src') as keyof OntimeEvent | null,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook exposes the timer view options
|
||||
*/
|
||||
export function useTimerOptions(): TimerOptions {
|
||||
const [searchParams] = useSearchParams();
|
||||
const options = useMemo(() => getOptionsFromParams(searchParams), [searchParams]);
|
||||
return options;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
import { MaybeNumber, MessageState, Playback, TimerMessage, TimerPhase, TimerType, ViewSettings } from 'ontime-types';
|
||||
|
||||
import { getFormattedTimer } from '../../features/viewers/common/viewUtils';
|
||||
|
||||
/**
|
||||
* Whether a message should be shown
|
||||
*/
|
||||
export function getShowMessage(message: TimerMessage): boolean {
|
||||
return message.text !== '' && message.visible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the playback is playing
|
||||
*/
|
||||
export function getIsPlaying(playback: Playback): boolean {
|
||||
return playback === Playback.Play || playback === Playback.Roll;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the total time from the duration and added time of an event
|
||||
*/
|
||||
export function getTotalTime(duration: MaybeNumber, addedTime: MaybeNumber): number {
|
||||
return (duration ?? 0) + (addedTime ?? 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the progress bar should be shown for this timer type
|
||||
*/
|
||||
export function getShowProgressBar(timerType: TimerType) {
|
||||
return timerType !== TimerType.None && timerType !== TimerType.Clock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the clock should be shown with this timer type
|
||||
*/
|
||||
export function getShowClock(timerType: TimerType) {
|
||||
return timerType !== TimerType.Clock;
|
||||
}
|
||||
|
||||
const fontSizeMap: { [key: number]: number } = {
|
||||
4: 28, // 9:01
|
||||
5: 28, // -9:01, 10:01, 9 min
|
||||
6: 25, // -10:01, 10 min
|
||||
8: 20, // 23:01:01
|
||||
9: 20, // -23:01:01
|
||||
};
|
||||
|
||||
/**
|
||||
* Finds a font size that fits the timer in the screen
|
||||
* Unfortunately hand tweaked
|
||||
*/
|
||||
export function getEstimatedFontSize(stageTimer: string, secondaryContent?: string) {
|
||||
const stageTimerCharacters = stageTimer.length;
|
||||
let timerFontSize = (100 / (stageTimerCharacters - 1)) * 1.25;
|
||||
|
||||
if (fontSizeMap[stageTimerCharacters]) {
|
||||
timerFontSize = fontSizeMap[stageTimerCharacters];
|
||||
}
|
||||
|
||||
let externalFontSize = timerFontSize * 0.2;
|
||||
if (secondaryContent) {
|
||||
// we need to shrink the timer if the external is going to be there
|
||||
// this number has been tweaked to fit in a landscape mobile screen
|
||||
timerFontSize *= 0.6;
|
||||
if (secondaryContent.length > 25) {
|
||||
externalFontSize = (100 / (secondaryContent.length - 1)) * 1.8;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
timerFontSize,
|
||||
externalFontSize,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* which, if any, modifier should be shown at any time
|
||||
*/
|
||||
export function getShowModifiers(
|
||||
timerType: TimerType,
|
||||
countToEnd: boolean,
|
||||
phase: TimerPhase,
|
||||
viewSettings: ViewSettings,
|
||||
) {
|
||||
const showModifiers = timerType === TimerType.CountDown || countToEnd;
|
||||
const finished = phase === TimerPhase.Overtime;
|
||||
return {
|
||||
showEndMessage: showModifiers && finished && viewSettings.endMessage,
|
||||
showFinished: showModifiers && finished, // ????
|
||||
showWarning: showModifiers && phase === TimerPhase.Warning,
|
||||
showDanger: showModifiers && phase === TimerPhase.Danger,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Which colour should the timer have at a given moment
|
||||
*/
|
||||
export function getTimerColour(viewSettings: ViewSettings, showWarning: boolean, showDanger: boolean) {
|
||||
if (showWarning) return viewSettings.warningColor;
|
||||
if (showDanger) return viewSettings.dangerColor;
|
||||
return viewSettings.normalColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* What, if anything, should be displayed in the secondary field
|
||||
*/
|
||||
export function getSecondaryDisplay(
|
||||
message: MessageState,
|
||||
currentAux: MaybeNumber,
|
||||
localisedMinutes: string,
|
||||
removeSeconds: boolean,
|
||||
removeLeadingZero: boolean,
|
||||
hideExternal: boolean,
|
||||
): string | undefined {
|
||||
if (hideExternal) {
|
||||
return;
|
||||
}
|
||||
if (message.timer.secondarySource === 'aux') {
|
||||
return getFormattedTimer(currentAux, TimerType.CountDown, localisedMinutes, {
|
||||
removeSeconds,
|
||||
removeLeadingZero,
|
||||
});
|
||||
}
|
||||
if (message.timer.secondarySource === 'external' && message.external) {
|
||||
return message.external;
|
||||
}
|
||||
return;
|
||||
}
|
||||
Reference in New Issue
Block a user