Files
ontime/apps/client/src/views/editor/pip-timer/PipTimer.tsx
T
Carlos Valente c4ac9df8cf refactor(ui): overlay the status icons and let the preview fill the panel
Three adjustments to the panel layout:

- the timer status icons go back to overlaying the stage, which returns the
  row they were costing. They sit in the same chrome layer as the pop-out
  buttons, so the blackout no longer hides them
- the screen state buttons group with the preview they act on, and the gap
  before the message inputs widens to separate the two
- the stage drops its fixed aspect ratio and fills the panel width, which
  also removes the slack a tall panel used to leave around it

Without a 16:9 frame the width derived font size can overflow a short, wide
stage, so the timer is now capped by container height at the ratio the
estimate assumes. In the pip window, which has no ancestor query container,
both terms resolve against the viewport, so a 16:9 window is unchanged and a
wider one stops clipping its digits.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011CHxSQ6nWHuyar8feieRra
2026-08-26 13:30:28 +00:00

116 lines
4.0 KiB
TypeScript

import { ViewSettings } from 'ontime-types';
import { FitText } from '../../../common/components/fit-text/FitText';
import MultiPartProgressBar from '../../../common/components/multi-part-progress-bar/MultiPartProgressBar';
import { useTimerSocket } from '../../../common/hooks/useSocket';
import { cx } from '../../../common/utils/styleUtils';
import { getFormattedTimer, getTimerByType } from '../../common/viewUtils';
import {
getEstimatedFontSize,
getIsPlaying,
getSecondaryDisplay,
getShowMessage,
getShowModifiers,
getShowProgressBar,
getTotalTime,
} from '../../timer/timer.utils';
import { getTimerColour } from '../../utils/presentation.utils';
import './PipTimer.scss';
interface PipTimerProps {
viewSettings: ViewSettings;
}
export function PipTimer({ viewSettings }: PipTimerProps) {
const { eventNow, message, time, clock, timerTypeNow, countToEndNow, auxTimer } = useTimerSocket();
// gather modifiers
const showOverlay = getShowMessage(message.timer);
const { showFinished, showWarning, showDanger } = getShowModifiers(
timerTypeNow,
countToEndNow,
time.phase,
false,
'',
false,
);
const isPlaying = getIsPlaying(time.playback);
const showProgressBar = getShowProgressBar(timerTypeNow);
// gather timer data
const totalTime = getTotalTime(time.duration, time.addedTime);
const stageTimer = getTimerByType(false, timerTypeNow, clock, time, timerTypeNow);
// match the defaults of the timer view, which is what the preview is standing in for
const display = getFormattedTimer(stageTimer, timerTypeNow, 'min', {
removeSeconds: false,
removeLeadingZero: true,
});
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, 'min', false, true, false);
// gather presentation styles
const resolvedTimerColour = getTimerColour(viewSettings, undefined, showWarning, showDanger);
// the estimate is tuned for a 16:9 screen, so cap it by height for containers wider than that
const timerFontSize = getEstimatedFontSize(display, secondaryContent);
const timerFontRule = `min(${timerFontSize}cqw, ${((timerFontSize * 16) / 9).toFixed(2)}cqh)`;
const userStyles = {
...(resolvedTimerColour && { '--timer-colour': resolvedTimerColour }),
};
return (
<div className={cx(['pip-timer', showFinished && 'pip-timer--finished'])} style={userStyles}>
<div className={cx(['blackout', message.timer.blackout && 'blackout--active'])} />
<div className={cx(['message-overlay', showOverlay && 'message-overlay--active'])}>
<FitText mode='multi' min={12} max={256} className={cx(['message', message.timer.blink && 'blink'])}>
{message.timer.text}
</FitText>
</div>
<div className={cx(['timer-container', message.timer.blink && !showOverlay && 'blink'])}>
<div
className={cx(['timer', !isPlaying && 'timer--paused', showFinished && 'timer--finished'])}
style={{ fontSize: timerFontRule }}
data-phase={time.phase}
>
{display}
</div>
<div className={cx(['secondary', !secondaryContent && 'secondary--hidden'])}>
<FitText mode='multi' min={12} max={256}>
{secondaryContent}
</FitText>
</div>
</div>
{showProgressBar && (
<MultiPartProgressBar
className={cx(['progress-container', !isPlaying && 'progress-container--paused'])}
now={time.current}
complete={totalTime}
eventId={eventNow?.id}
normalColor={viewSettings.normalColor}
warning={eventNow?.timeWarning}
warningColor={viewSettings.warningColor}
danger={eventNow?.timeDanger}
dangerColor={viewSettings.dangerColor}
hideOvertime={!showFinished}
/>
)}
</div>
);
}