import { useEffect } from 'react'; import { useSearchParams } from 'react-router-dom'; import { AnimatePresence, motion } from 'framer-motion'; import { CustomFields, 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'; import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu'; import TitleCard from '../../../common/components/title-card/TitleCard'; import { getTimerOptions } from '../../../common/components/view-params-editor/constants'; import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor'; import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet'; import { ViewExtendedTimer } from '../../../common/models/TimeManager.type'; import { timerPlaceholder } from '../../../common/utils/styleUtils'; import { formatTime, getDefaultFormat } from '../../../common/utils/time'; import { useTranslation } from '../../../translation/TranslationProvider'; import SuperscriptTime from '../common/superscript-time/SuperscriptTime'; import { getPropertyValue, getTimerByType, isStringBoolean } from '../common/viewUtils'; import './Timer.scss'; // motion const titleVariants = { hidden: { x: -2500, }, visible: { x: 0, transition: { duration: 1, }, }, exit: { x: -2500, }, }; interface TimerProps { customFields: CustomFields; eventNext: OntimeEvent | null; eventNow: OntimeEvent | null; external: Message; isMirrored: boolean; pres: TimerMessage; settings: Settings | undefined; time: ViewExtendedTimer; viewSettings: ViewSettings; } export default function Timer(props: TimerProps) { const { customFields, isMirrored, pres, eventNow, eventNext, time, viewSettings, external, settings } = props; const { shouldRender } = useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL); const { getLocalizedString } = useTranslation(); const [searchParams] = useSearchParams(); useEffect(() => { document.title = 'ontime - Timer'; }, []); // defer rendering until we load stylesheets if (!shouldRender) { return null; } // USER OPTIONS const userOptions = { hideClock: false, hideCards: false, hideProgress: false, hideMessage: false, hideTimerSeconds: false, hideClockSeconds: false, }; 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 secondarySource = searchParams.get('secondary-src'); const secondaryTextNow = getPropertyValue(eventNow, secondarySource); const secondaryTextNext = getPropertyValue(eventNext, secondarySource); const showOverlay = pres.text !== '' && pres.visible; const isPlaying = time.playback !== Playback.Pause; const timerIsTimeOfDay = time.timerType === TimerType.Clock; const finished = time.playback === Playback.Play && (time.current ?? 0) < 0 && time.startedAt; const totalTime = (time.duration ?? 0) + (time.addedTime ?? 0); const showEndMessage = (time.current ?? 1) < 0 && viewSettings.endMessage; const showProgress = time.playback !== Playback.Stop; const showFinished = finished && (time.timerType !== TimerType.Clock || showEndMessage); const showWarning = (time.current ?? 1) < (eventNow?.timeWarning ?? 0); const showDanger = (time.current ?? 1) < (eventNow?.timeDanger ?? 0); const showBlinking = pres.blink; const showBlackout = pres.blackout; const showClock = time.timerType !== TimerType.Clock; const showExternal = external.visible && external.text; let timerColor = viewSettings.normalColor; if (!timerIsTimeOfDay && showProgress && showWarning) timerColor = viewSettings.warningColor; if (!timerIsTimeOfDay && showProgress && showDanger) timerColor = viewSettings.dangerColor; const stageTimer = getTimerByType(time); let display = millisToString(stageTimer, { fallback: timerPlaceholder }); if (stageTimer !== null) { if (hideTimerSeconds) { display = removeSeconds(display); } display = removeLeadingZero(display); // last unit rounds up in negative timers const isNegative = (stageTimer ?? 0 < 0) && !timerIsTimeOfDay && time.timerType !== TimerType.CountUp; if (isNegative && display === '0') { display = '-1'; } 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); // we need to shrink the timer if the external is going to be there if (showExternal) { timerFontSize *= 0.8; } const externalFontSize = timerFontSize * 0.4; const timerContainerClasses = `timer-container ${showBlinking ? (showOverlay ? '' : 'blink') : ''}`; const timerClasses = `timer ${!isPlaying ? 'timer--paused' : ''} ${showFinished ? 'timer--finished' : ''}`; const defaultFormat = getDefaultFormat(settings?.timeFormat); const timerOptions = getTimerOptions(defaultFormat, customFields); return (
{!userOptions.hideMessage && (
{pres.text}
)} {!userOptions.hideClock && (
{getLocalizedString('common.time_now')}
)}
{showEndMessage ? (
{viewSettings.endMessage}
) : (
{display}
)}
{external.text}
{!userOptions.hideProgress && (
); }