Skip to content

Commit

Permalink
refactor: extract and simplify logic
Browse files Browse the repository at this point in the history
  • Loading branch information
cpvalente committed Jan 21, 2025
1 parent 133e40c commit 45d9a47
Show file tree
Hide file tree
Showing 4 changed files with 281 additions and 142 deletions.
215 changes: 78 additions & 137 deletions apps/client/src/views/timer/Timer.tsx
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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,
};

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);
// 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 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 baseClasses = `stage-timer ${isMirrored ? 'mirror' : ''}`;
const secondaryContent = getSecondaryDisplay(
message,
auxTimer.current,
getLocalizedString('common.minutes'),
hideTimerSeconds,
removeLeadingZeros,
);

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 && (
Expand All @@ -253,6 +194,7 @@ export default function Timer(props: TimerProps) {
<AnimatePresence>
{eventNext?.title && (
<MotionTitleCard
className='event next'
key='next'
variants={titleVariants}
initial='hidden'
Expand All @@ -261,7 +203,6 @@ export default function Timer(props: TimerProps) {
label='next'
title={mainFieldNext}
secondary={secondaryTextNext}
className='event next'
/>
)}
</AnimatePresence>
Expand Down
20 changes: 20 additions & 0 deletions apps/client/src/views/timer/timer.animations.ts
Original file line number Diff line number Diff line change
@@ -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);
Loading

0 comments on commit 45d9a47

Please sign in to comment.