Files
ontime/apps/client/src/views/studio/StudioTimers.tsx
T
Claude 2f29060fa4 refactor: expose aux timer names via runtime store, index in automations
Addresses review feedback on the aux timer naming feature:

- Automation action labels now reference the timers by index
  ("Aux timer 1: start") rather than the custom names, keeping the
  automation config stable regardless of naming.
- Expose the custom names through the consumer-facing runtime interface:
  the aux timer objects broadcast over the websocket now carry a `name`
  field. Names are seeded from the persisted settings at bootstrap and
  kept in sync whenever the settings change, so every consumer (views and
  integrations) reads them from the same runtime data.
- The client control and view consumers now read the name from the
  runtime store instead of querying settings directly; the settings form
  remains the persisted editing source.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WCZejVTzuAY3tHTE6nB3JH
2026-07-25 08:49:51 +00:00

148 lines
5.3 KiB
TypeScript

import { Playback, TimerPhase, ViewSettings } from 'ontime-types';
import { millisToString } from 'ontime-utils';
import { useAuxTimersName, useAuxTimersTime, useStudioTimersSocket } from '../../common/hooks/useSocket';
import { getAuxTimerLabel } from '../../common/utils/auxTimerUtils';
import { getOffsetState } from '../../common/utils/offset';
import { cx } from '../../common/utils/styleUtils';
import { useTranslation } from '../../translation/TranslationProvider';
import { getPropertyValue } from '../common/viewUtils';
import { getTimerColour } from '../utils/presentation.utils';
import { useStudioOptions } from './studio.options';
import { getFormattedEventData, getFormattedScheduleTimes } from './studioTimers.utils';
import './StudioTimers.scss';
interface StudioTimersProps {
viewSettings: ViewSettings;
}
export default function StudioTimers({ viewSettings }: StudioTimersProps) {
const { getLocalizedString } = useTranslation();
const { mainSource } = useStudioOptions();
const { eventNow, eventNext, message, time, offset, rundown, expectedRundownEnd } = useStudioTimersSocket();
const schedule = getFormattedScheduleTimes({
offset: offset,
actualStart: rundown.actualStart,
expectedEnd: expectedRundownEnd,
});
const event = getFormattedEventData(eventNow, time, mainSource);
const eventNextTitle = getPropertyValue(eventNext, mainSource ?? 'title') || '-';
const formattedTimerMessage = (message.timer.visible && message.timer.text) || '-';
const formattedSecondaryMessage = message.timer.secondarySource === 'secondary' ? message.secondary || '-' : '-';
// gather presentation styles
const timerColour = getTimerColour(
viewSettings,
undefined,
time.phase === TimerPhase.Warning,
time.phase === TimerPhase.Danger,
);
const offsetState = getOffsetState(offset);
return (
<div className='studio__timers'>
<div className='card' id='card-schedule'>
<div className='card__row'>
<div>
<div className='label'>{getLocalizedString('common.started_at')}</div>
<div className='runtime-timer'>{schedule.actualStart}</div>
</div>
<div>
<div className='label center'>Over / under</div>
<div className={cx(['runtime-timer', 'center', !eventNow && 'muted', offsetState])}>{schedule.offset}</div>
</div>
<div>
<div className='label right'>{getLocalizedString('common.expected_end')}</div>
<div className='runtime-timer right'>{schedule.expectedEnd}</div>
</div>
</div>
</div>
<div className='card' id='card-event-now'>
<div className='card__row'>
<div>
<div className='label'>{getLocalizedString('common.now')}</div>
<div className='title'>{event.title}</div>
</div>
<div>
<div className='label right'>{getLocalizedString('common.next')}</div>
<div className='title right'>{eventNextTitle}</div>
</div>
</div>
<div className='card__row'>
<div>
<div className='label'>{getLocalizedString('common.started_at')}</div>
<div className='runtime-timer'>{event.startedAt}</div>
</div>
<div>
<div className='label' />
<div
className={cx([
'event-timer',
time.phase === TimerPhase.Overtime && 'event-timer--finished',
time.playback === Playback.Pause && 'event-timer--paused',
])}
style={{
'--phase-color': timerColour,
}}
data-phase={time.phase}
>
{event.timer}
</div>
</div>
<div>
<div className='label right'>{getLocalizedString('common.expected_end')}</div>
<div className='runtime-timer right'>{event.expectedEnd}</div>
</div>
</div>
</div>
<StudioTimersAux />
<div className='card' id='card-timer-message'>
<div>
<div className='label'>{getLocalizedString('common.timer_message')}</div>
<div className={cx(['extra', !formattedTimerMessage && 'muted'])}>{formattedTimerMessage}</div>
</div>
</div>
<div className='card' id='card-secondary-message'>
<div>
<div className='label'>{getLocalizedString('common.secondary_message')}</div>
<div className={cx(['extra', !formattedSecondaryMessage && 'muted'])}>{formattedSecondaryMessage}</div>
</div>
</div>
</div>
);
}
function StudioTimersAux() {
const auxTimer = useAuxTimersTime();
const auxName = useAuxTimersName();
return (
<div className='card' id='card-aux'>
<div className='card__row'>
<div>
<div className='label'>{getAuxTimerLabel(auxName.aux1, 'Aux 1')}</div>
<div className='extra'>{millisToString(auxTimer.aux1)}</div>
</div>
<div>
<div className='label center'>{getAuxTimerLabel(auxName.aux2, 'Aux 2')}</div>
<div className='extra center'>{millisToString(auxTimer.aux2)}</div>
</div>
<div>
<div className='label right'>{getAuxTimerLabel(auxName.aux3, 'Aux 3')}</div>
<div className='extra right'>{millisToString(auxTimer.aux3)}</div>
</div>
</div>
</div>
);
}