refactor: extract time-to-end

This commit is contained in:
Carlos Valente
2024-12-15 21:25:41 +01:00
parent ed65a4db67
commit b88c2a6bd8
32 changed files with 419 additions and 248 deletions
@@ -65,6 +65,7 @@ export const useMessagePreview = () => {
showExternalMessage: state.message.timer.secondarySource === 'external' && Boolean(state.message.external),
showTimerMessage: state.message.timer.visible && Boolean(state.message.timer.text),
timerType: state.eventNow?.timerType ?? null,
isTimeToEnd: state.eventNow?.isTimeToEnd ?? false,
});
return useRuntimeStore(featureSelector);
@@ -15,4 +15,5 @@ export type ViewExtendedTimer = {
clock: number;
timerType: TimerType;
isTimeToEnd: boolean;
};
@@ -17,6 +17,7 @@ export const cloneEvent = (event: OntimeEvent, after?: string): ClonedEvent => {
timeEnd: event.timeEnd,
timerType: event.timerType,
timeStrategy: event.timeStrategy,
isTimeToEnd: event.isTimeToEnd,
linkStart: event.linkStart,
endAction: event.endAction,
isPublic: event.isPublic,
@@ -88,7 +88,6 @@ export default function EditorSettingsForm() {
>
<option value={TimerType.CountDown}>Count down</option>
<option value={TimerType.CountUp}>Count up</option>
<option value={TimerType.TimeToEnd}>Time to end</option>
<option value={TimerType.Clock}>Clock</option>
<option value={TimerType.None}>None</option>
</Select>
@@ -16,7 +16,7 @@ import { Corner } from '../../editors/editor-utils/EditorUtils';
import style from './MessageControl.module.scss';
export default function TimerPreview() {
const { blink, blackout, phase, showAuxTimer, showExternalMessage, showTimerMessage, timerType } =
const { blink, blackout, isTimeToEnd, phase, showAuxTimer, showExternalMessage, showTimerMessage, timerType } =
useMessagePreview();
const { data } = useViewSettings();
@@ -24,11 +24,11 @@ export default function TimerPreview() {
const main = (() => {
if (showTimerMessage) return 'Message';
if (timerType === TimerType.None) return timerPlaceholder;
if (phase === TimerPhase.Pending) return 'Standby to start';
if (phase === TimerPhase.Overtime && data.endMessage) return 'Custom end message';
if (timerType === TimerType.TimeToEnd) return 'Time to end';
if (timerType === TimerType.Clock) return 'Clock';
if (timerType === TimerType.None) return timerPlaceholder;
if (isTimeToEnd) return 'Target event scheduled end';
return 'Timer';
})();
@@ -74,12 +74,16 @@ export default function TimerPreview() {
<Tooltip label='Time type: Clock' openDelay={tooltipDelayMid} shouldWrapChildren>
<IoTime className={style.statusIcon} data-active={timerType === TimerType.Clock} />
</Tooltip>
<Tooltip label='Time type: Time to end' openDelay={tooltipDelayMid} shouldWrapChildren>
<IoFlag className={style.statusIcon} data-active={timerType === TimerType.TimeToEnd} />
</Tooltip>
<Tooltip label='Time type: None' openDelay={tooltipDelayMid} shouldWrapChildren>
<IoBan className={style.statusIcon} data-active={timerType === TimerType.None} />
</Tooltip>
<Tooltip
label={isTimeToEnd ? 'Count to schedule' : 'Count from start'}
openDelay={tooltipDelayMid}
shouldWrapChildren
>
<IoFlag className={style.statusIcon} data-active={isTimeToEnd} />
</Tooltip>
</div>
</div>
);
@@ -27,7 +27,7 @@
.title {
font-size: 1rem;
color: $label-gray;
color: $ui-white;
}
.label {
@@ -150,6 +150,7 @@ export default function RundownEntry(props: RundownEntryProps) {
if (data.type === SupportedEvent.Event) {
return (
<EventBlock
eventId={data.id}
eventIndex={eventIndex}
cue={data.cue}
timeStart={data.timeStart}
@@ -157,7 +158,7 @@ export default function RundownEntry(props: RundownEntryProps) {
duration={data.duration}
timeStrategy={data.timeStrategy}
linkStart={data.linkStart}
eventId={data.id}
isTimeToEnd={data.isTimeToEnd}
isPublic={data.isPublic}
endAction={data.endAction}
timerType={data.timerType}
@@ -24,13 +24,14 @@ import RundownIndicators from './RundownIndicators';
import style from './EventBlock.module.scss';
interface EventBlockProps {
eventId: string;
cue: string;
timeStart: number;
timeEnd: number;
duration: number;
timeStrategy: TimeStrategy;
linkStart: MaybeString;
eventId: string;
isTimeToEnd: boolean;
eventIndex: number;
isPublic: boolean;
endAction: EndAction;
@@ -68,6 +69,7 @@ export default function EventBlock(props: EventBlockProps) {
duration,
timeStrategy,
linkStart,
isTimeToEnd,
isPublic = true,
eventIndex,
endAction,
@@ -286,6 +288,7 @@ export default function EventBlock(props: EventBlockProps) {
timeEnd={timeEnd}
duration={duration}
linkStart={linkStart}
isTimeToEnd={isTimeToEnd}
timeStrategy={timeStrategy}
eventId={eventId}
eventIndex={eventIndex}
@@ -23,12 +23,13 @@ import EventBlockProgressBar from './composite/EventBlockProgressBar';
import style from './EventBlock.module.scss';
interface EventBlockInnerProps {
eventId: string;
timeStart: number;
timeEnd: number;
duration: number;
timeStrategy: TimeStrategy;
linkStart: MaybeString;
eventId: string;
isTimeToEnd: boolean;
eventIndex: number;
isPublic: boolean;
endAction: EndAction;
@@ -43,14 +44,15 @@ interface EventBlockInnerProps {
isRolling: boolean;
}
const EventBlockInner = (props: EventBlockInnerProps) => {
function EventBlockInner(props: EventBlockInnerProps) {
const {
eventId,
timeStart,
timeEnd,
duration,
timeStrategy,
linkStart,
eventId,
isTimeToEnd,
isPublic = true,
endAction,
timerType,
@@ -91,7 +93,7 @@ const EventBlockInner = (props: EventBlockInnerProps) => {
delay={delay}
timeStrategy={timeStrategy}
linkStart={linkStart}
timerType={timerType}
isTimeToEnd={isTimeToEnd}
/>
</div>
<div className={style.titleSection}>
@@ -122,6 +124,11 @@ const EventBlockInner = (props: EventBlockInnerProps) => {
<EndActionIcon action={endAction} className={style.statusIcon} />
</span>
</Tooltip>
<Tooltip label={`${isTimeToEnd ? 'Time to End' : 'Count from start'}`} openDelay={tooltipDelayMid}>
<span>
<IoFlag className={`${style.statusIcon} ${isTimeToEnd ? style.active : style.disabled}`} />
</span>
</Tooltip>
<Tooltip label={`${isPublic ? 'Event is public' : 'Event is private'}`} openDelay={tooltipDelayMid}>
<span>
<IoPeople className={`${style.statusIcon} ${isPublic ? style.active : style.disabled}`} />
@@ -131,7 +138,7 @@ const EventBlockInner = (props: EventBlockInnerProps) => {
</div>
</>
);
};
}
export default memo(EventBlockInner);
@@ -162,9 +169,5 @@ function TimerIcon(props: { type: TimerType; className: string }) {
if (type === TimerType.None) {
return <IoBan className={className} />;
}
if (type === TimerType.TimeToEnd) {
const classes = cx([style.active, className]);
return <IoFlag className={classes} />;
}
return <IoArrowDown className={className} />;
}
@@ -16,7 +16,7 @@
flex: 1;
display: flex;
flex-direction: column;
gap: 2rem;
gap: 1rem;
overflow-y: auto;
}
@@ -38,6 +38,7 @@
display: flex;
flex-direction: column;
gap: 1rem;
margin-top: 0.5rem;
}
.decorated {
@@ -63,6 +64,7 @@
gap: 0.5rem;
max-width: max-content;
cursor: pointer;
height: 30px; // manually match the height of a text input
}
.inline {
@@ -83,6 +83,7 @@ export default function EventEditor() {
duration={event.duration}
timeStrategy={event.timeStrategy}
linkStart={event.linkStart}
isTimeToEnd={event.isTimeToEnd}
delay={event.delay ?? 0}
isPublic={event.isPublic}
endAction={event.endAction}
@@ -18,6 +18,7 @@ interface EventEditorTimesProps {
duration: number;
timeStrategy: TimeStrategy;
linkStart: MaybeString;
isTimeToEnd: boolean;
delay: number;
isPublic: boolean;
endAction: EndAction;
@@ -26,9 +27,9 @@ interface EventEditorTimesProps {
timeDanger: number;
}
type HandledActions = 'timerType' | 'endAction' | 'isPublic' | 'timeWarning' | 'timeDanger';
type HandledActions = 'isTimeToEnd' | 'timerType' | 'endAction' | 'isPublic' | 'timeWarning' | 'timeDanger';
const EventEditorTimes = (props: EventEditorTimesProps) => {
function EventEditorTimes(props: EventEditorTimesProps) {
const {
eventId,
timeStart,
@@ -36,6 +37,7 @@ const EventEditorTimes = (props: EventEditorTimesProps) => {
duration,
timeStrategy,
linkStart,
isTimeToEnd,
delay,
isPublic,
endAction,
@@ -51,6 +53,11 @@ const EventEditorTimes = (props: EventEditorTimesProps) => {
return;
}
if (field === 'isTimeToEnd') {
updateEvent({ id: eventId, isTimeToEnd: !(value as boolean) });
return;
}
if (field === 'timeWarning' || field === 'timeDanger') {
const newTime = parseUserTime(value as string);
updateEvent({ id: eventId, [field]: newTime });
@@ -71,95 +78,115 @@ const EventEditorTimes = (props: EventEditorTimesProps) => {
: '';
return (
<div className={style.column}>
<div>
<Editor.Label>Event schedule</Editor.Label>
<div className={style.inline}>
<TimeInputFlow
eventId={eventId}
timeStart={timeStart}
timeEnd={timeEnd}
duration={duration}
timeStrategy={timeStrategy}
linkStart={linkStart}
delay={delay}
timerType={timerType}
/>
<>
<div className={style.column}>
<div>
<Editor.Label>Event schedule</Editor.Label>
<div className={style.inline}>
<TimeInputFlow
eventId={eventId}
timeStart={timeStart}
timeEnd={timeEnd}
duration={duration}
timeStrategy={timeStrategy}
linkStart={linkStart}
delay={delay}
isTimeToEnd={isTimeToEnd}
/>
</div>
<div className={style.delayLabel}>{delayLabel}</div>
</div>
<div className={style.delayLabel}>{delayLabel}</div>
</div>
<div className={style.splitTwo}>
<div>
<Editor.Label htmlFor='timeWarning'>Warning Time</Editor.Label>
<TimeInput
id='timeWarning'
name='timeWarning'
submitHandler={handleSubmit}
time={timeWarning}
placeholder='Duration'
/>
</div>
<div>
<Editor.Label htmlFor='timerType'>Timer Type</Editor.Label>
<Select
size='sm'
id='timerType'
name='timerType'
value={timerType}
onChange={(event) => handleSubmit('timerType', event.target.value)}
variant='ontime'
>
<option value={TimerType.CountDown}>Count down</option>
<option value={TimerType.CountUp}>Count up</option>
<option value={TimerType.TimeToEnd}>Time to end</option>
<option value={TimerType.Clock}>Clock</option>
<option value={TimerType.None}>None</option>
</Select>
</div>
<div>
<Editor.Label htmlFor='timeDanger'>Danger Time</Editor.Label>
<TimeInput
id='timeDanger'
name='timeDanger'
submitHandler={handleSubmit}
time={timeDanger}
placeholder='Duration'
/>
</div>
<div>
<Editor.Label htmlFor='endAction'>End Action</Editor.Label>
<Select
id='endAction'
size='sm'
name='endAction'
value={endAction}
onChange={(event) => handleSubmit('endAction', event.target.value)}
variant='ontime'
>
<option value={EndAction.None}>None</option>
<option value={EndAction.Stop}>Stop</option>
<option value={EndAction.LoadNext}>Load Next</option>
<option value={EndAction.PlayNext}>Play Next</option>
</Select>
<Editor.Title>Event behaviour</Editor.Title>
<div className={style.splitTwo}>
<div>
<Editor.Label htmlFor='endAction'>End Action</Editor.Label>
<Select
id='endAction'
size='sm'
name='endAction'
value={endAction}
onChange={(event) => handleSubmit('endAction', event.target.value)}
variant='ontime'
>
<option value={EndAction.None}>None</option>
<option value={EndAction.Stop}>Stop rundown</option>
<option value={EndAction.LoadNext}>Load next event</option>
<option value={EndAction.PlayNext}>Play next event</option>
</Select>
</div>
<div>
<Editor.Label htmlFor='timeToEnd'>Target Event Scheduled End</Editor.Label>
<Editor.Label className={style.switchLabel}>
<Switch
id='timeToEnd'
size='md'
isChecked={isTimeToEnd}
onChange={() => handleSubmit('isTimeToEnd', isTimeToEnd)}
variant='ontime'
/>
{isTimeToEnd ? 'On' : 'Off'}
</Editor.Label>
</div>
</div>
</div>
<div className={style.column}>
<Editor.Title>Display options</Editor.Title>
<div className={style.splitTwo}>
<div>
<Editor.Label htmlFor='timerType'>Timer Type</Editor.Label>
<Select
size='sm'
id='timerType'
name='timerType'
value={timerType}
onChange={(event) => handleSubmit('timerType', event.target.value)}
variant='ontime'
>
<option value={TimerType.CountDown}>Count down</option>
<option value={TimerType.CountUp}>Count up</option>
<option value={TimerType.Clock}>Clock</option>
<option value={TimerType.None}>None</option>
</Select>
</div>
<div>
<Editor.Label htmlFor='timeWarning'>Warning Time</Editor.Label>
<TimeInput
id='timeWarning'
name='timeWarning'
submitHandler={handleSubmit}
time={timeWarning}
placeholder='Duration'
/>
</div>
<div>
<Editor.Label htmlFor='isPublic'>Event Visibility</Editor.Label>
<Editor.Label className={style.switchLabel}>
<Switch
id='isPublic'
size='md'
isChecked={isPublic}
onChange={() => handleSubmit('isPublic', isPublic)}
variant='ontime'
/>
{isPublic ? 'Public' : 'Private'}
</Editor.Label>
<div>
<Editor.Label htmlFor='isPublic'>Event Visibility</Editor.Label>
<Editor.Label className={style.switchLabel}>
<Switch
id='isPublic'
size='md'
isChecked={isPublic}
onChange={() => handleSubmit('isPublic', isPublic)}
variant='ontime'
/>
{isPublic ? 'Public' : 'Private'}
</Editor.Label>
</div>
<div>
<Editor.Label htmlFor='timeDanger'>Danger Time</Editor.Label>
<TimeInput
id='timeDanger'
name='timeDanger'
submitHandler={handleSubmit}
time={timeDanger}
placeholder='Duration'
/>
</div>
</div>
</div>
</div>
</>
);
};
}
export default memo(EventEditorTimes);
@@ -29,6 +29,7 @@ const EventEditorTitles = (props: EventEditorTitlesProps) => {
return (
<div className={style.column}>
<Editor.Title>Event data</Editor.Title>
<div className={style.splitTwo}>
<div>
<Editor.Label htmlFor='eventId'>Event ID (read only)</Editor.Label>
@@ -5,7 +5,7 @@ import { IoLink } from '@react-icons/all-files/io5/IoLink';
import { IoLockClosed } from '@react-icons/all-files/io5/IoLockClosed';
import { IoLockOpenOutline } from '@react-icons/all-files/io5/IoLockOpenOutline';
import { IoUnlink } from '@react-icons/all-files/io5/IoUnlink';
import { MaybeString, TimerType, TimeStrategy } from 'ontime-types';
import { MaybeString, TimeStrategy } from 'ontime-types';
import TimeInputWithButton from '../../../common/components/input/time-input/TimeInputWithButton';
import { useEventAction } from '../../../common/hooks/useEventAction';
@@ -16,19 +16,19 @@ import style from './TimeInputFlow.module.scss';
interface EventBlockTimerProps {
eventId: string;
isTimeToEnd: boolean;
timeStart: number;
timeEnd: number;
duration: number;
timeStrategy: TimeStrategy;
linkStart: MaybeString;
delay: number;
timerType: TimerType;
}
type TimeActions = 'timeStart' | 'timeEnd' | 'duration';
const TimeInputFlow = (props: EventBlockTimerProps) => {
const { eventId, timeStart, timeEnd, duration, timeStrategy, linkStart, delay, timerType } = props;
function TimeInputFlow(props: EventBlockTimerProps) {
const { eventId, isTimeToEnd, timeStart, timeEnd, duration, timeStrategy, linkStart, delay } = props;
const { updateEvent, updateTimer } = useEventAction();
// In sync with EventEditorTimes
@@ -49,8 +49,8 @@ const TimeInputFlow = (props: EventBlockTimerProps) => {
warnings.push('Over midnight');
}
if (timerType === TimerType.TimeToEnd) {
warnings.push('Time to end');
if (isTimeToEnd) {
warnings.push('Target event scheduled end');
}
const hasDelay = delay !== 0;
@@ -128,6 +128,6 @@ const TimeInputFlow = (props: EventBlockTimerProps) => {
)}
</>
);
};
}
export default memo(TimeInputFlow);
@@ -9,6 +9,7 @@ import {
Settings,
SimpleTimerState,
SupportedEvent,
TimerType,
ViewSettings,
} from 'ontime-types';
import { useStore } from 'zustand';
@@ -74,16 +75,14 @@ const withData = <P extends WithDataProps>(Component: ComponentType<P>) => {
const selectedId = eventNow?.id ?? null;
const nextId = eventNext?.id ?? null;
/******************************************/
/*** + TimeManagerType ***/
/*** WRAP INFORMATION RELATED TO TIME ***/
/*** -------------------------------- ***/
/******************************************/
const TimeManagerType = {
/**
* Contains an extended timer object with properties from the current event
*/
const timeManagerType: ViewExtendedTimer = {
...timer,
clock,
timerType: eventNow?.timerType ?? null,
timerType: eventNow?.timerType ?? TimerType.CountDown,
isTimeToEnd: eventNow?.isTimeToEnd ?? false,
};
return (
@@ -108,7 +107,7 @@ const withData = <P extends WithDataProps>(Component: ComponentType<P>) => {
runtime={runtime}
selectedId={selectedId}
settings={settings}
time={TimeManagerType}
time={timeManagerType}
viewSettings={viewSettings}
/>
</>
@@ -5,16 +5,22 @@ import type { ViewExtendedTimer } from '../../../common/models/TimeManager.type'
import { timerPlaceholder, timerPlaceholderMin } from '../../../common/utils/styleUtils';
import { formatTime } from '../../../common/utils/time';
type TimerTypeParams = Pick<ViewExtendedTimer, 'timerType' | 'current' | 'elapsed' | 'clock'>;
type TimerTypeParams = Pick<ViewExtendedTimer, 'isTimeToEnd' | 'timerType' | 'current' | 'elapsed' | 'clock'>;
export function getTimerByType(freezeEnd: boolean, timerObject?: TimerTypeParams): number | null {
if (!timerObject) {
return null;
}
if (timerObject.isTimeToEnd) {
if (timerObject.current === null) {
return null;
}
return freezeEnd ? Math.max(timerObject.current, 0) : timerObject.current;
}
switch (timerObject.timerType) {
case TimerType.CountDown:
case TimerType.TimeToEnd:
if (timerObject.current === null) {
return null;
}
@@ -124,7 +124,7 @@ export default function MinimalTimer(props: MinimalTimerProps) {
const isPlaying = time.playback !== Playback.Pause;
const shouldShowModifiers = time.timerType === TimerType.CountDown || time.timerType === TimerType.TimeToEnd;
const shouldShowModifiers = time.timerType === TimerType.CountDown || time.isTimeToEnd;
const finished = time.phase === TimerPhase.Overtime;
const showEndMessage = shouldShowModifiers && finished && viewSettings.endMessage && !hideEndMessage;
const showFinished =
@@ -118,7 +118,7 @@ export default function Timer(props: TimerProps) {
const finished = time.phase === TimerPhase.Overtime;
const totalTime = (time.duration ?? 0) + (time.addedTime ?? 0);
const shouldShowModifiers = time.timerType === TimerType.CountDown || time.timerType === TimerType.TimeToEnd;
const shouldShowModifiers = time.timerType === TimerType.CountDown || time.isTimeToEnd;
const showEndMessage = shouldShowModifiers && finished && viewSettings.endMessage;
const showProgress =
eventNow !== null &&