mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-06 07:53:54 +00:00
a5e733246d
* fix: format clock from preset * fix: add to all views * chore: format * refactor: return flat value from common.options.ts
229 lines
7.6 KiB
TypeScript
229 lines
7.6 KiB
TypeScript
import { MaybeString, OntimeView, TimerType } from 'ontime-types';
|
|
import { useMemo } from 'react';
|
|
|
|
import { FitText } from '../../common/components/fit-text/FitText';
|
|
import MultiPartProgressBar from '../../common/components/multi-part-progress-bar/MultiPartProgressBar';
|
|
import EmptyPage from '../../common/components/state/EmptyPage';
|
|
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 { useAutoTickingClock } from '../../common/hooks/useAutoTickingClock';
|
|
import { useTimerSocket } from '../../common/hooks/useSocket';
|
|
import { useWindowTitle } from '../../common/hooks/useWindowTitle';
|
|
import { cx } from '../../common/utils/styleUtils';
|
|
import { formatTime, getDefaultFormat } from '../../common/utils/time';
|
|
import { useTranslation } from '../../translation/TranslationProvider';
|
|
import Loader from '../common/loader/Loader';
|
|
import SuperscriptTime from '../common/superscript-time/SuperscriptTime';
|
|
import { getFormattedTimer, getTimerByType } from '../common/viewUtils';
|
|
import { getTimerColour } from '../utils/presentation.utils';
|
|
import { getTimerOptions, useTimerOptions } from './timer.options';
|
|
import {
|
|
getCardData,
|
|
getEstimatedFontSize,
|
|
getIsPlaying,
|
|
getSecondaryDisplay,
|
|
getShowClock,
|
|
getShowMessage,
|
|
getShowModifiers,
|
|
getShowProgressBar,
|
|
getTotalTime,
|
|
} from './timer.utils';
|
|
import { TimerData, useTimerData } from './useTimerData';
|
|
|
|
import './Timer.scss';
|
|
|
|
export default function TimerLoader() {
|
|
const { data, status } = useTimerData();
|
|
|
|
useWindowTitle('Timer');
|
|
|
|
if (status === 'pending') {
|
|
return <Loader />;
|
|
}
|
|
|
|
if (status === 'error') {
|
|
return <EmptyPage text='There was an error fetching data, please refresh the page.' />;
|
|
}
|
|
|
|
return <Timer {...data} />;
|
|
}
|
|
|
|
function Timer({ customFields, projectData, isMirrored, settings, viewSettings, entries }: TimerData) {
|
|
const { eventNext, eventNow, message, time, clock, timerTypeNow, countToEndNow, auxTimer } = useTimerSocket();
|
|
const {
|
|
hideClock,
|
|
hideCards,
|
|
hideProgress,
|
|
hideMessage,
|
|
hideSecondary,
|
|
hideLogo,
|
|
hideTimerSeconds,
|
|
removeLeadingZeros,
|
|
mainSource,
|
|
secondarySource,
|
|
timerType,
|
|
freezeOvertime,
|
|
freezeMessage,
|
|
hidePhase,
|
|
font,
|
|
keyColour,
|
|
timerColour,
|
|
timeformat,
|
|
} = useTimerOptions();
|
|
|
|
const { getLocalizedString } = useTranslation();
|
|
const localisedMinutes = getLocalizedString('common.minutes');
|
|
|
|
// gather modifiers
|
|
const viewTimerType = timerType ?? timerTypeNow;
|
|
const showOverlay = getShowMessage(message.timer);
|
|
const { showEndMessage, showFinished, showWarning, showDanger } = getShowModifiers(
|
|
timerTypeNow,
|
|
countToEndNow,
|
|
time.phase,
|
|
freezeOvertime,
|
|
freezeMessage,
|
|
hidePhase,
|
|
);
|
|
const isPlaying = getIsPlaying(time.playback);
|
|
const showClock = !hideClock && getShowClock(viewTimerType);
|
|
const showProgressBar = !hideProgress && getShowProgressBar(viewTimerType);
|
|
|
|
// gather card data
|
|
const { showNow, nowMain, nowSecondary, showNext, nextMain, nextSecondary } = getCardData(
|
|
eventNow,
|
|
eventNext,
|
|
mainSource,
|
|
secondarySource,
|
|
time.playback,
|
|
time.phase,
|
|
entries,
|
|
);
|
|
|
|
// gather timer data
|
|
const totalTime = getTotalTime(time.duration, time.addedTime);
|
|
const stageTimer = getTimerByType(freezeOvertime, timerTypeNow, clock, time, timerType);
|
|
const display = getFormattedTimer(stageTimer, viewTimerType, localisedMinutes, {
|
|
removeSeconds: hideTimerSeconds,
|
|
removeLeadingZero: removeLeadingZeros,
|
|
clockFormat: timeformat,
|
|
});
|
|
|
|
const currentAux = (() => {
|
|
if (message.timer.secondarySource === 'aux1') {
|
|
return auxTimer.aux1;
|
|
}
|
|
if (message.timer.secondarySource === 'aux2') {
|
|
return auxTimer.aux2;
|
|
}
|
|
if (message.timer.secondarySource === 'aux3') {
|
|
return auxTimer.aux3;
|
|
}
|
|
return null;
|
|
})();
|
|
|
|
const secondaryContent = getSecondaryDisplay(
|
|
message,
|
|
currentAux,
|
|
localisedMinutes,
|
|
hideTimerSeconds,
|
|
removeLeadingZeros,
|
|
hideSecondary,
|
|
);
|
|
|
|
// gather presentation styles
|
|
const resolvedTimerColour = getTimerColour(viewSettings, timerColour, showWarning, showDanger);
|
|
const timerFontSize = getEstimatedFontSize(display, secondaryContent);
|
|
const subduePaused = !isPlaying && viewTimerType !== TimerType.Clock;
|
|
const userStyles = {
|
|
...(keyColour && { '--timer-bg': keyColour }),
|
|
...(resolvedTimerColour && { '--timer-colour': resolvedTimerColour }),
|
|
...(font && { '--timer-font': font }),
|
|
};
|
|
|
|
// gather option data
|
|
const defaultFormat = getDefaultFormat(settings?.timeFormat);
|
|
const timerOptions = useMemo(() => getTimerOptions(defaultFormat, customFields), [customFields, defaultFormat]);
|
|
|
|
return (
|
|
<div
|
|
data-testid='timer-view'
|
|
className={cx(['stage-timer', isMirrored && 'mirror', showFinished && 'stage-timer--finished'])}
|
|
style={userStyles}
|
|
>
|
|
{!hideLogo && projectData?.logo && <ViewLogo name={projectData.logo} className='logo' />}
|
|
|
|
<ViewParamsEditor target={OntimeView.Timer} viewOptions={timerOptions} />
|
|
|
|
<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>
|
|
)}
|
|
|
|
{showClock && <TimerAutoTickingClock clockFormat={timeformat} />}
|
|
|
|
<div className={cx(['timer-container', message.timer.blink && !showOverlay && 'blink'])}>
|
|
{showEndMessage ? (
|
|
<FitText mode='multi' min={64} max={256} className='end-message'>
|
|
{freezeMessage}
|
|
</FitText>
|
|
) : (
|
|
<div
|
|
className={cx(['timer', subduePaused && 'timer--paused', showFinished && 'timer--finished'])}
|
|
style={{ fontSize: `${timerFontSize}vw` }}
|
|
data-type={viewTimerType}
|
|
data-phase={time.phase}
|
|
>
|
|
{display}
|
|
</div>
|
|
)}
|
|
<div className={cx(['secondary', !secondaryContent && 'secondary--hidden'])}>
|
|
<FitText mode='multi' min={64} max={256}>
|
|
{secondaryContent}
|
|
</FitText>
|
|
</div>
|
|
</div>
|
|
|
|
{showProgressBar && (
|
|
<MultiPartProgressBar
|
|
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}
|
|
hideOvertime={!showFinished}
|
|
/>
|
|
)}
|
|
|
|
{!hideCards && (
|
|
<>
|
|
{showNow && <TitleCard className='event now' label='now' title={nowMain} secondary={nowSecondary} />}
|
|
{showNext && <TitleCard className='event next' label='next' title={nextMain} secondary={nextSecondary} />}
|
|
</>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function TimerAutoTickingClock({ clockFormat }: { clockFormat: MaybeString }) {
|
|
const autoTickingClock = useAutoTickingClock();
|
|
const formattedClock = formatTime(autoTickingClock, { override: clockFormat });
|
|
const { getLocalizedString } = useTranslation();
|
|
|
|
return (
|
|
<div className='clock-container'>
|
|
<div className='label'>{getLocalizedString('common.time_now')}</div>
|
|
<SuperscriptTime time={formattedClock} className='clock' />
|
|
</div>
|
|
);
|
|
}
|