mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-10 18:03:47 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9813643c96 | |||
| b88c2a6bd8 |
@@ -65,6 +65,7 @@ export const useMessagePreview = () => {
|
|||||||
showExternalMessage: state.message.timer.secondarySource === 'external' && Boolean(state.message.external),
|
showExternalMessage: state.message.timer.secondarySource === 'external' && Boolean(state.message.external),
|
||||||
showTimerMessage: state.message.timer.visible && Boolean(state.message.timer.text),
|
showTimerMessage: state.message.timer.visible && Boolean(state.message.timer.text),
|
||||||
timerType: state.eventNow?.timerType ?? null,
|
timerType: state.eventNow?.timerType ?? null,
|
||||||
|
isTimeToEnd: state.eventNow?.isTimeToEnd ?? false,
|
||||||
});
|
});
|
||||||
|
|
||||||
return useRuntimeStore(featureSelector);
|
return useRuntimeStore(featureSelector);
|
||||||
|
|||||||
@@ -15,4 +15,5 @@ export type ViewExtendedTimer = {
|
|||||||
|
|
||||||
clock: number;
|
clock: number;
|
||||||
timerType: TimerType;
|
timerType: TimerType;
|
||||||
|
isTimeToEnd: boolean;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ export const cloneEvent = (event: OntimeEvent, after?: string): ClonedEvent => {
|
|||||||
timeEnd: event.timeEnd,
|
timeEnd: event.timeEnd,
|
||||||
timerType: event.timerType,
|
timerType: event.timerType,
|
||||||
timeStrategy: event.timeStrategy,
|
timeStrategy: event.timeStrategy,
|
||||||
|
isTimeToEnd: event.isTimeToEnd,
|
||||||
linkStart: event.linkStart,
|
linkStart: event.linkStart,
|
||||||
endAction: event.endAction,
|
endAction: event.endAction,
|
||||||
isPublic: event.isPublic,
|
isPublic: event.isPublic,
|
||||||
|
|||||||
@@ -88,7 +88,6 @@ export default function EditorSettingsForm() {
|
|||||||
>
|
>
|
||||||
<option value={TimerType.CountDown}>Count down</option>
|
<option value={TimerType.CountDown}>Count down</option>
|
||||||
<option value={TimerType.CountUp}>Count up</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.Clock}>Clock</option>
|
||||||
<option value={TimerType.None}>None</option>
|
<option value={TimerType.None}>None</option>
|
||||||
</Select>
|
</Select>
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export const namedImportMap = {
|
|||||||
Duration: 'duration',
|
Duration: 'duration',
|
||||||
Cue: 'cue',
|
Cue: 'cue',
|
||||||
Title: 'title',
|
Title: 'title',
|
||||||
|
'Time to end': 'time to end',
|
||||||
'Is Public': 'public',
|
'Is Public': 'public',
|
||||||
Skip: 'skip',
|
Skip: 'skip',
|
||||||
Note: 'notes',
|
Note: 'notes',
|
||||||
@@ -47,6 +48,7 @@ export function convertToImportMap(namedImportMap: NamedImportMap): ImportMap {
|
|||||||
duration: namedImportMap.Duration,
|
duration: namedImportMap.Duration,
|
||||||
cue: namedImportMap.Cue,
|
cue: namedImportMap.Cue,
|
||||||
title: namedImportMap.Title,
|
title: namedImportMap.Title,
|
||||||
|
isTimeToEnd: namedImportMap['Time to end'],
|
||||||
isPublic: namedImportMap['Is Public'],
|
isPublic: namedImportMap['Is Public'],
|
||||||
skip: namedImportMap.Skip,
|
skip: namedImportMap.Skip,
|
||||||
note: namedImportMap.Note,
|
note: namedImportMap.Note,
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ export default function PreviewRundown(props: PreviewRundownProps) {
|
|||||||
<th>Duration</th>
|
<th>Duration</th>
|
||||||
<th>Warning Time</th>
|
<th>Warning Time</th>
|
||||||
<th>Danger Time</th>
|
<th>Danger Time</th>
|
||||||
|
<th>Is Time to End</th>
|
||||||
<th>Is Public</th>
|
<th>Is Public</th>
|
||||||
<th>Skip</th>
|
<th>Skip</th>
|
||||||
<th>Colour</th>
|
<th>Colour</th>
|
||||||
@@ -71,6 +72,7 @@ export default function PreviewRundown(props: PreviewRundownProps) {
|
|||||||
}
|
}
|
||||||
eventIndex += 1;
|
eventIndex += 1;
|
||||||
const colour = event.colour ? getAccessibleColour(event.colour) : {};
|
const colour = event.colour ? getAccessibleColour(event.colour) : {};
|
||||||
|
const isTimeToEnd = booleanToText(event.isTimeToEnd);
|
||||||
const isPublic = booleanToText(event.isPublic);
|
const isPublic = booleanToText(event.isPublic);
|
||||||
const skip = booleanToText(event.skip);
|
const skip = booleanToText(event.skip);
|
||||||
|
|
||||||
@@ -93,6 +95,7 @@ export default function PreviewRundown(props: PreviewRundownProps) {
|
|||||||
<td>{millisToString(event.duration)}</td>
|
<td>{millisToString(event.duration)}</td>
|
||||||
<td>{millisToString(event.timeWarning)}</td>
|
<td>{millisToString(event.timeWarning)}</td>
|
||||||
<td>{millisToString(event.timeDanger)}</td>
|
<td>{millisToString(event.timeDanger)}</td>
|
||||||
|
<td className={style.center}>{isTimeToEnd && <Tag>{isTimeToEnd}</Tag>}</td>
|
||||||
<td className={style.center}>{isPublic && <Tag>{isPublic}</Tag>}</td>
|
<td className={style.center}>{isPublic && <Tag>{isPublic}</Tag>}</td>
|
||||||
<td>{skip && <Tag>{skip}</Tag>}</td>
|
<td>{skip && <Tag>{skip}</Tag>}</td>
|
||||||
<td style={{ ...colour }}>{event.colour}</td>
|
<td style={{ ...colour }}>{event.colour}</td>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import { Corner } from '../../editors/editor-utils/EditorUtils';
|
|||||||
import style from './MessageControl.module.scss';
|
import style from './MessageControl.module.scss';
|
||||||
|
|
||||||
export default function TimerPreview() {
|
export default function TimerPreview() {
|
||||||
const { blink, blackout, phase, showAuxTimer, showExternalMessage, showTimerMessage, timerType } =
|
const { blink, blackout, isTimeToEnd, phase, showAuxTimer, showExternalMessage, showTimerMessage, timerType } =
|
||||||
useMessagePreview();
|
useMessagePreview();
|
||||||
const { data } = useViewSettings();
|
const { data } = useViewSettings();
|
||||||
|
|
||||||
@@ -24,11 +24,11 @@ export default function TimerPreview() {
|
|||||||
|
|
||||||
const main = (() => {
|
const main = (() => {
|
||||||
if (showTimerMessage) return 'Message';
|
if (showTimerMessage) return 'Message';
|
||||||
|
if (timerType === TimerType.None) return timerPlaceholder;
|
||||||
if (phase === TimerPhase.Pending) return 'Standby to start';
|
if (phase === TimerPhase.Pending) return 'Standby to start';
|
||||||
if (phase === TimerPhase.Overtime && data.endMessage) return 'Custom end message';
|
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.Clock) return 'Clock';
|
||||||
if (timerType === TimerType.None) return timerPlaceholder;
|
if (isTimeToEnd) return 'Target event scheduled end';
|
||||||
return 'Timer';
|
return 'Timer';
|
||||||
})();
|
})();
|
||||||
|
|
||||||
@@ -74,12 +74,16 @@ export default function TimerPreview() {
|
|||||||
<Tooltip label='Time type: Clock' openDelay={tooltipDelayMid} shouldWrapChildren>
|
<Tooltip label='Time type: Clock' openDelay={tooltipDelayMid} shouldWrapChildren>
|
||||||
<IoTime className={style.statusIcon} data-active={timerType === TimerType.Clock} />
|
<IoTime className={style.statusIcon} data-active={timerType === TimerType.Clock} />
|
||||||
</Tooltip>
|
</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>
|
<Tooltip label='Time type: None' openDelay={tooltipDelayMid} shouldWrapChildren>
|
||||||
<IoBan className={style.statusIcon} data-active={timerType === TimerType.None} />
|
<IoBan className={style.statusIcon} data-active={timerType === TimerType.None} />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
<Tooltip
|
||||||
|
label={isTimeToEnd ? 'Count to schedule' : 'Count from start'}
|
||||||
|
openDelay={tooltipDelayMid}
|
||||||
|
shouldWrapChildren
|
||||||
|
>
|
||||||
|
<IoFlag className={style.statusIcon} data-active={isTimeToEnd} />
|
||||||
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
|
|
||||||
.title {
|
.title {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
color: $label-gray;
|
color: $ui-white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
|
|||||||
@@ -150,6 +150,7 @@ export default function RundownEntry(props: RundownEntryProps) {
|
|||||||
if (data.type === SupportedEvent.Event) {
|
if (data.type === SupportedEvent.Event) {
|
||||||
return (
|
return (
|
||||||
<EventBlock
|
<EventBlock
|
||||||
|
eventId={data.id}
|
||||||
eventIndex={eventIndex}
|
eventIndex={eventIndex}
|
||||||
cue={data.cue}
|
cue={data.cue}
|
||||||
timeStart={data.timeStart}
|
timeStart={data.timeStart}
|
||||||
@@ -157,7 +158,7 @@ export default function RundownEntry(props: RundownEntryProps) {
|
|||||||
duration={data.duration}
|
duration={data.duration}
|
||||||
timeStrategy={data.timeStrategy}
|
timeStrategy={data.timeStrategy}
|
||||||
linkStart={data.linkStart}
|
linkStart={data.linkStart}
|
||||||
eventId={data.id}
|
isTimeToEnd={data.isTimeToEnd}
|
||||||
isPublic={data.isPublic}
|
isPublic={data.isPublic}
|
||||||
endAction={data.endAction}
|
endAction={data.endAction}
|
||||||
timerType={data.timerType}
|
timerType={data.timerType}
|
||||||
|
|||||||
@@ -24,13 +24,14 @@ import RundownIndicators from './RundownIndicators';
|
|||||||
import style from './EventBlock.module.scss';
|
import style from './EventBlock.module.scss';
|
||||||
|
|
||||||
interface EventBlockProps {
|
interface EventBlockProps {
|
||||||
|
eventId: string;
|
||||||
cue: string;
|
cue: string;
|
||||||
timeStart: number;
|
timeStart: number;
|
||||||
timeEnd: number;
|
timeEnd: number;
|
||||||
duration: number;
|
duration: number;
|
||||||
timeStrategy: TimeStrategy;
|
timeStrategy: TimeStrategy;
|
||||||
linkStart: MaybeString;
|
linkStart: MaybeString;
|
||||||
eventId: string;
|
isTimeToEnd: boolean;
|
||||||
eventIndex: number;
|
eventIndex: number;
|
||||||
isPublic: boolean;
|
isPublic: boolean;
|
||||||
endAction: EndAction;
|
endAction: EndAction;
|
||||||
@@ -68,6 +69,7 @@ export default function EventBlock(props: EventBlockProps) {
|
|||||||
duration,
|
duration,
|
||||||
timeStrategy,
|
timeStrategy,
|
||||||
linkStart,
|
linkStart,
|
||||||
|
isTimeToEnd,
|
||||||
isPublic = true,
|
isPublic = true,
|
||||||
eventIndex,
|
eventIndex,
|
||||||
endAction,
|
endAction,
|
||||||
@@ -286,6 +288,7 @@ export default function EventBlock(props: EventBlockProps) {
|
|||||||
timeEnd={timeEnd}
|
timeEnd={timeEnd}
|
||||||
duration={duration}
|
duration={duration}
|
||||||
linkStart={linkStart}
|
linkStart={linkStart}
|
||||||
|
isTimeToEnd={isTimeToEnd}
|
||||||
timeStrategy={timeStrategy}
|
timeStrategy={timeStrategy}
|
||||||
eventId={eventId}
|
eventId={eventId}
|
||||||
eventIndex={eventIndex}
|
eventIndex={eventIndex}
|
||||||
|
|||||||
@@ -23,12 +23,13 @@ import EventBlockProgressBar from './composite/EventBlockProgressBar';
|
|||||||
import style from './EventBlock.module.scss';
|
import style from './EventBlock.module.scss';
|
||||||
|
|
||||||
interface EventBlockInnerProps {
|
interface EventBlockInnerProps {
|
||||||
|
eventId: string;
|
||||||
timeStart: number;
|
timeStart: number;
|
||||||
timeEnd: number;
|
timeEnd: number;
|
||||||
duration: number;
|
duration: number;
|
||||||
timeStrategy: TimeStrategy;
|
timeStrategy: TimeStrategy;
|
||||||
linkStart: MaybeString;
|
linkStart: MaybeString;
|
||||||
eventId: string;
|
isTimeToEnd: boolean;
|
||||||
eventIndex: number;
|
eventIndex: number;
|
||||||
isPublic: boolean;
|
isPublic: boolean;
|
||||||
endAction: EndAction;
|
endAction: EndAction;
|
||||||
@@ -43,14 +44,15 @@ interface EventBlockInnerProps {
|
|||||||
isRolling: boolean;
|
isRolling: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const EventBlockInner = (props: EventBlockInnerProps) => {
|
function EventBlockInner(props: EventBlockInnerProps) {
|
||||||
const {
|
const {
|
||||||
|
eventId,
|
||||||
timeStart,
|
timeStart,
|
||||||
timeEnd,
|
timeEnd,
|
||||||
duration,
|
duration,
|
||||||
timeStrategy,
|
timeStrategy,
|
||||||
linkStart,
|
linkStart,
|
||||||
eventId,
|
isTimeToEnd,
|
||||||
isPublic = true,
|
isPublic = true,
|
||||||
endAction,
|
endAction,
|
||||||
timerType,
|
timerType,
|
||||||
@@ -91,7 +93,7 @@ const EventBlockInner = (props: EventBlockInnerProps) => {
|
|||||||
delay={delay}
|
delay={delay}
|
||||||
timeStrategy={timeStrategy}
|
timeStrategy={timeStrategy}
|
||||||
linkStart={linkStart}
|
linkStart={linkStart}
|
||||||
timerType={timerType}
|
isTimeToEnd={isTimeToEnd}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={style.titleSection}>
|
<div className={style.titleSection}>
|
||||||
@@ -122,6 +124,11 @@ const EventBlockInner = (props: EventBlockInnerProps) => {
|
|||||||
<EndActionIcon action={endAction} className={style.statusIcon} />
|
<EndActionIcon action={endAction} className={style.statusIcon} />
|
||||||
</span>
|
</span>
|
||||||
</Tooltip>
|
</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}>
|
<Tooltip label={`${isPublic ? 'Event is public' : 'Event is private'}`} openDelay={tooltipDelayMid}>
|
||||||
<span>
|
<span>
|
||||||
<IoPeople className={`${style.statusIcon} ${isPublic ? style.active : style.disabled}`} />
|
<IoPeople className={`${style.statusIcon} ${isPublic ? style.active : style.disabled}`} />
|
||||||
@@ -131,7 +138,7 @@ const EventBlockInner = (props: EventBlockInnerProps) => {
|
|||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default memo(EventBlockInner);
|
export default memo(EventBlockInner);
|
||||||
|
|
||||||
@@ -162,9 +169,5 @@ function TimerIcon(props: { type: TimerType; className: string }) {
|
|||||||
if (type === TimerType.None) {
|
if (type === TimerType.None) {
|
||||||
return <IoBan className={className} />;
|
return <IoBan className={className} />;
|
||||||
}
|
}
|
||||||
if (type === TimerType.TimeToEnd) {
|
|
||||||
const classes = cx([style.active, className]);
|
|
||||||
return <IoFlag className={classes} />;
|
|
||||||
}
|
|
||||||
return <IoArrowDown className={className} />;
|
return <IoArrowDown className={className} />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 2rem;
|
gap: 1rem;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,6 +38,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
|
margin-top: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.decorated {
|
.decorated {
|
||||||
@@ -63,6 +64,7 @@
|
|||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
max-width: max-content;
|
max-width: max-content;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
height: 30px; // manually match the height of a text input
|
||||||
}
|
}
|
||||||
|
|
||||||
.inline {
|
.inline {
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ export default function EventEditor() {
|
|||||||
duration={event.duration}
|
duration={event.duration}
|
||||||
timeStrategy={event.timeStrategy}
|
timeStrategy={event.timeStrategy}
|
||||||
linkStart={event.linkStart}
|
linkStart={event.linkStart}
|
||||||
|
isTimeToEnd={event.isTimeToEnd}
|
||||||
delay={event.delay ?? 0}
|
delay={event.delay ?? 0}
|
||||||
isPublic={event.isPublic}
|
isPublic={event.isPublic}
|
||||||
endAction={event.endAction}
|
endAction={event.endAction}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ interface EventEditorTimesProps {
|
|||||||
duration: number;
|
duration: number;
|
||||||
timeStrategy: TimeStrategy;
|
timeStrategy: TimeStrategy;
|
||||||
linkStart: MaybeString;
|
linkStart: MaybeString;
|
||||||
|
isTimeToEnd: boolean;
|
||||||
delay: number;
|
delay: number;
|
||||||
isPublic: boolean;
|
isPublic: boolean;
|
||||||
endAction: EndAction;
|
endAction: EndAction;
|
||||||
@@ -26,9 +27,9 @@ interface EventEditorTimesProps {
|
|||||||
timeDanger: number;
|
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 {
|
const {
|
||||||
eventId,
|
eventId,
|
||||||
timeStart,
|
timeStart,
|
||||||
@@ -36,6 +37,7 @@ const EventEditorTimes = (props: EventEditorTimesProps) => {
|
|||||||
duration,
|
duration,
|
||||||
timeStrategy,
|
timeStrategy,
|
||||||
linkStart,
|
linkStart,
|
||||||
|
isTimeToEnd,
|
||||||
delay,
|
delay,
|
||||||
isPublic,
|
isPublic,
|
||||||
endAction,
|
endAction,
|
||||||
@@ -51,6 +53,11 @@ const EventEditorTimes = (props: EventEditorTimesProps) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (field === 'isTimeToEnd') {
|
||||||
|
updateEvent({ id: eventId, isTimeToEnd: !(value as boolean) });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (field === 'timeWarning' || field === 'timeDanger') {
|
if (field === 'timeWarning' || field === 'timeDanger') {
|
||||||
const newTime = parseUserTime(value as string);
|
const newTime = parseUserTime(value as string);
|
||||||
updateEvent({ id: eventId, [field]: newTime });
|
updateEvent({ id: eventId, [field]: newTime });
|
||||||
@@ -71,95 +78,115 @@ const EventEditorTimes = (props: EventEditorTimesProps) => {
|
|||||||
: '';
|
: '';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={style.column}>
|
<>
|
||||||
<div>
|
<div className={style.column}>
|
||||||
<Editor.Label>Event schedule</Editor.Label>
|
<div>
|
||||||
<div className={style.inline}>
|
<Editor.Label>Event schedule</Editor.Label>
|
||||||
<TimeInputFlow
|
<div className={style.inline}>
|
||||||
eventId={eventId}
|
<TimeInputFlow
|
||||||
timeStart={timeStart}
|
eventId={eventId}
|
||||||
timeEnd={timeEnd}
|
timeStart={timeStart}
|
||||||
duration={duration}
|
timeEnd={timeEnd}
|
||||||
timeStrategy={timeStrategy}
|
duration={duration}
|
||||||
linkStart={linkStart}
|
timeStrategy={timeStrategy}
|
||||||
delay={delay}
|
linkStart={linkStart}
|
||||||
timerType={timerType}
|
delay={delay}
|
||||||
/>
|
isTimeToEnd={isTimeToEnd}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={style.delayLabel}>{delayLabel}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={style.delayLabel}>{delayLabel}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={style.splitTwo}>
|
<Editor.Title>Event behaviour</Editor.Title>
|
||||||
<div>
|
<div className={style.splitTwo}>
|
||||||
<Editor.Label htmlFor='timeWarning'>Warning Time</Editor.Label>
|
<div>
|
||||||
<TimeInput
|
<Editor.Label htmlFor='endAction'>End Action</Editor.Label>
|
||||||
id='timeWarning'
|
<Select
|
||||||
name='timeWarning'
|
id='endAction'
|
||||||
submitHandler={handleSubmit}
|
size='sm'
|
||||||
time={timeWarning}
|
name='endAction'
|
||||||
placeholder='Duration'
|
value={endAction}
|
||||||
/>
|
onChange={(event) => handleSubmit('endAction', event.target.value)}
|
||||||
</div>
|
variant='ontime'
|
||||||
<div>
|
>
|
||||||
<Editor.Label htmlFor='timerType'>Timer Type</Editor.Label>
|
<option value={EndAction.None}>None</option>
|
||||||
<Select
|
<option value={EndAction.Stop}>Stop rundown</option>
|
||||||
size='sm'
|
<option value={EndAction.LoadNext}>Load next event</option>
|
||||||
id='timerType'
|
<option value={EndAction.PlayNext}>Play next event</option>
|
||||||
name='timerType'
|
</Select>
|
||||||
value={timerType}
|
</div>
|
||||||
onChange={(event) => handleSubmit('timerType', event.target.value)}
|
<div>
|
||||||
variant='ontime'
|
<Editor.Label htmlFor='timeToEnd'>Target Event Scheduled End</Editor.Label>
|
||||||
>
|
<Editor.Label className={style.switchLabel}>
|
||||||
<option value={TimerType.CountDown}>Count down</option>
|
<Switch
|
||||||
<option value={TimerType.CountUp}>Count up</option>
|
id='timeToEnd'
|
||||||
<option value={TimerType.TimeToEnd}>Time to end</option>
|
size='md'
|
||||||
<option value={TimerType.Clock}>Clock</option>
|
isChecked={isTimeToEnd}
|
||||||
<option value={TimerType.None}>None</option>
|
onChange={() => handleSubmit('isTimeToEnd', isTimeToEnd)}
|
||||||
</Select>
|
variant='ontime'
|
||||||
</div>
|
/>
|
||||||
<div>
|
{isTimeToEnd ? 'On' : 'Off'}
|
||||||
<Editor.Label htmlFor='timeDanger'>Danger Time</Editor.Label>
|
</Editor.Label>
|
||||||
<TimeInput
|
</div>
|
||||||
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>
|
|
||||||
</div>
|
</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>
|
<div>
|
||||||
<Editor.Label htmlFor='isPublic'>Event Visibility</Editor.Label>
|
<Editor.Label htmlFor='isPublic'>Event Visibility</Editor.Label>
|
||||||
<Editor.Label className={style.switchLabel}>
|
<Editor.Label className={style.switchLabel}>
|
||||||
<Switch
|
<Switch
|
||||||
id='isPublic'
|
id='isPublic'
|
||||||
size='md'
|
size='md'
|
||||||
isChecked={isPublic}
|
isChecked={isPublic}
|
||||||
onChange={() => handleSubmit('isPublic', isPublic)}
|
onChange={() => handleSubmit('isPublic', isPublic)}
|
||||||
variant='ontime'
|
variant='ontime'
|
||||||
/>
|
/>
|
||||||
{isPublic ? 'Public' : 'Private'}
|
{isPublic ? 'Public' : 'Private'}
|
||||||
</Editor.Label>
|
</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>
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default memo(EventEditorTimes);
|
export default memo(EventEditorTimes);
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ const EventEditorTitles = (props: EventEditorTitlesProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={style.column}>
|
<div className={style.column}>
|
||||||
|
<Editor.Title>Event data</Editor.Title>
|
||||||
<div className={style.splitTwo}>
|
<div className={style.splitTwo}>
|
||||||
<div>
|
<div>
|
||||||
<Editor.Label htmlFor='eventId'>Event ID (read only)</Editor.Label>
|
<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 { IoLockClosed } from '@react-icons/all-files/io5/IoLockClosed';
|
||||||
import { IoLockOpenOutline } from '@react-icons/all-files/io5/IoLockOpenOutline';
|
import { IoLockOpenOutline } from '@react-icons/all-files/io5/IoLockOpenOutline';
|
||||||
import { IoUnlink } from '@react-icons/all-files/io5/IoUnlink';
|
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 TimeInputWithButton from '../../../common/components/input/time-input/TimeInputWithButton';
|
||||||
import { useEventAction } from '../../../common/hooks/useEventAction';
|
import { useEventAction } from '../../../common/hooks/useEventAction';
|
||||||
@@ -16,19 +16,19 @@ import style from './TimeInputFlow.module.scss';
|
|||||||
|
|
||||||
interface EventBlockTimerProps {
|
interface EventBlockTimerProps {
|
||||||
eventId: string;
|
eventId: string;
|
||||||
|
isTimeToEnd: boolean;
|
||||||
timeStart: number;
|
timeStart: number;
|
||||||
timeEnd: number;
|
timeEnd: number;
|
||||||
duration: number;
|
duration: number;
|
||||||
timeStrategy: TimeStrategy;
|
timeStrategy: TimeStrategy;
|
||||||
linkStart: MaybeString;
|
linkStart: MaybeString;
|
||||||
delay: number;
|
delay: number;
|
||||||
timerType: TimerType;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type TimeActions = 'timeStart' | 'timeEnd' | 'duration';
|
type TimeActions = 'timeStart' | 'timeEnd' | 'duration';
|
||||||
|
|
||||||
const TimeInputFlow = (props: EventBlockTimerProps) => {
|
function TimeInputFlow(props: EventBlockTimerProps) {
|
||||||
const { eventId, timeStart, timeEnd, duration, timeStrategy, linkStart, delay, timerType } = props;
|
const { eventId, isTimeToEnd, timeStart, timeEnd, duration, timeStrategy, linkStart, delay } = props;
|
||||||
const { updateEvent, updateTimer } = useEventAction();
|
const { updateEvent, updateTimer } = useEventAction();
|
||||||
|
|
||||||
// In sync with EventEditorTimes
|
// In sync with EventEditorTimes
|
||||||
@@ -49,8 +49,8 @@ const TimeInputFlow = (props: EventBlockTimerProps) => {
|
|||||||
warnings.push('Over midnight');
|
warnings.push('Over midnight');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (timerType === TimerType.TimeToEnd) {
|
if (isTimeToEnd) {
|
||||||
warnings.push('Time to end');
|
warnings.push('Target event scheduled end');
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasDelay = delay !== 0;
|
const hasDelay = delay !== 0;
|
||||||
@@ -128,6 +128,6 @@ const TimeInputFlow = (props: EventBlockTimerProps) => {
|
|||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default memo(TimeInputFlow);
|
export default memo(TimeInputFlow);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
Settings,
|
Settings,
|
||||||
SimpleTimerState,
|
SimpleTimerState,
|
||||||
SupportedEvent,
|
SupportedEvent,
|
||||||
|
TimerType,
|
||||||
ViewSettings,
|
ViewSettings,
|
||||||
} from 'ontime-types';
|
} from 'ontime-types';
|
||||||
import { useStore } from 'zustand';
|
import { useStore } from 'zustand';
|
||||||
@@ -74,16 +75,14 @@ const withData = <P extends WithDataProps>(Component: ComponentType<P>) => {
|
|||||||
const selectedId = eventNow?.id ?? null;
|
const selectedId = eventNow?.id ?? null;
|
||||||
const nextId = eventNext?.id ?? null;
|
const nextId = eventNext?.id ?? null;
|
||||||
|
|
||||||
/******************************************/
|
/**
|
||||||
/*** + TimeManagerType ***/
|
* Contains an extended timer object with properties from the current event
|
||||||
/*** WRAP INFORMATION RELATED TO TIME ***/
|
*/
|
||||||
/*** -------------------------------- ***/
|
const timeManagerType: ViewExtendedTimer = {
|
||||||
/******************************************/
|
|
||||||
|
|
||||||
const TimeManagerType = {
|
|
||||||
...timer,
|
...timer,
|
||||||
clock,
|
clock,
|
||||||
timerType: eventNow?.timerType ?? null,
|
timerType: eventNow?.timerType ?? TimerType.CountDown,
|
||||||
|
isTimeToEnd: eventNow?.isTimeToEnd ?? false,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -108,7 +107,7 @@ const withData = <P extends WithDataProps>(Component: ComponentType<P>) => {
|
|||||||
runtime={runtime}
|
runtime={runtime}
|
||||||
selectedId={selectedId}
|
selectedId={selectedId}
|
||||||
settings={settings}
|
settings={settings}
|
||||||
time={TimeManagerType}
|
time={timeManagerType}
|
||||||
viewSettings={viewSettings}
|
viewSettings={viewSettings}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -5,16 +5,22 @@ import type { ViewExtendedTimer } from '../../../common/models/TimeManager.type'
|
|||||||
import { timerPlaceholder, timerPlaceholderMin } from '../../../common/utils/styleUtils';
|
import { timerPlaceholder, timerPlaceholderMin } from '../../../common/utils/styleUtils';
|
||||||
import { formatTime } from '../../../common/utils/time';
|
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 {
|
export function getTimerByType(freezeEnd: boolean, timerObject?: TimerTypeParams): number | null {
|
||||||
if (!timerObject) {
|
if (!timerObject) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (timerObject.isTimeToEnd) {
|
||||||
|
if (timerObject.current === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return freezeEnd ? Math.max(timerObject.current, 0) : timerObject.current;
|
||||||
|
}
|
||||||
|
|
||||||
switch (timerObject.timerType) {
|
switch (timerObject.timerType) {
|
||||||
case TimerType.CountDown:
|
case TimerType.CountDown:
|
||||||
case TimerType.TimeToEnd:
|
|
||||||
if (timerObject.current === null) {
|
if (timerObject.current === null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ export default function MinimalTimer(props: MinimalTimerProps) {
|
|||||||
|
|
||||||
const isPlaying = time.playback !== Playback.Pause;
|
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 finished = time.phase === TimerPhase.Overtime;
|
||||||
const showEndMessage = shouldShowModifiers && finished && viewSettings.endMessage && !hideEndMessage;
|
const showEndMessage = shouldShowModifiers && finished && viewSettings.endMessage && !hideEndMessage;
|
||||||
const showFinished =
|
const showFinished =
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ export default function Timer(props: TimerProps) {
|
|||||||
const finished = time.phase === TimerPhase.Overtime;
|
const finished = time.phase === TimerPhase.Overtime;
|
||||||
const totalTime = (time.duration ?? 0) + (time.addedTime ?? 0);
|
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 showEndMessage = shouldShowModifiers && finished && viewSettings.endMessage;
|
||||||
const showProgress =
|
const showProgress =
|
||||||
eventNow !== null &&
|
eventNow !== null &&
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export const demoDb: DatabaseModel = {
|
|||||||
note: 'SF1.01',
|
note: 'SF1.01',
|
||||||
endAction: EndAction.None,
|
endAction: EndAction.None,
|
||||||
timerType: TimerType.CountDown,
|
timerType: TimerType.CountDown,
|
||||||
|
isTimeToEnd: false,
|
||||||
linkStart: null,
|
linkStart: null,
|
||||||
timeStrategy: TimeStrategy.LockEnd,
|
timeStrategy: TimeStrategy.LockEnd,
|
||||||
timeStart: 36000000,
|
timeStart: 36000000,
|
||||||
@@ -34,6 +35,7 @@ export const demoDb: DatabaseModel = {
|
|||||||
note: 'SF1.02',
|
note: 'SF1.02',
|
||||||
endAction: EndAction.None,
|
endAction: EndAction.None,
|
||||||
timerType: TimerType.CountDown,
|
timerType: TimerType.CountDown,
|
||||||
|
isTimeToEnd: false,
|
||||||
linkStart: null,
|
linkStart: null,
|
||||||
timeStrategy: TimeStrategy.LockEnd,
|
timeStrategy: TimeStrategy.LockEnd,
|
||||||
timeStart: 37500000,
|
timeStart: 37500000,
|
||||||
@@ -58,6 +60,7 @@ export const demoDb: DatabaseModel = {
|
|||||||
note: 'SF1.03',
|
note: 'SF1.03',
|
||||||
endAction: EndAction.None,
|
endAction: EndAction.None,
|
||||||
timerType: TimerType.CountDown,
|
timerType: TimerType.CountDown,
|
||||||
|
isTimeToEnd: false,
|
||||||
linkStart: null,
|
linkStart: null,
|
||||||
timeStrategy: TimeStrategy.LockEnd,
|
timeStrategy: TimeStrategy.LockEnd,
|
||||||
timeStart: 39000000,
|
timeStart: 39000000,
|
||||||
@@ -82,6 +85,7 @@ export const demoDb: DatabaseModel = {
|
|||||||
note: 'SF1.04',
|
note: 'SF1.04',
|
||||||
endAction: EndAction.None,
|
endAction: EndAction.None,
|
||||||
timerType: TimerType.CountDown,
|
timerType: TimerType.CountDown,
|
||||||
|
isTimeToEnd: false,
|
||||||
linkStart: null,
|
linkStart: null,
|
||||||
timeStrategy: TimeStrategy.LockEnd,
|
timeStrategy: TimeStrategy.LockEnd,
|
||||||
timeStart: 40500000,
|
timeStart: 40500000,
|
||||||
@@ -106,6 +110,7 @@ export const demoDb: DatabaseModel = {
|
|||||||
note: 'SF1.05',
|
note: 'SF1.05',
|
||||||
endAction: EndAction.None,
|
endAction: EndAction.None,
|
||||||
timerType: TimerType.CountDown,
|
timerType: TimerType.CountDown,
|
||||||
|
isTimeToEnd: false,
|
||||||
linkStart: null,
|
linkStart: null,
|
||||||
timeStrategy: TimeStrategy.LockEnd,
|
timeStrategy: TimeStrategy.LockEnd,
|
||||||
timeStart: 42000000,
|
timeStart: 42000000,
|
||||||
@@ -135,6 +140,7 @@ export const demoDb: DatabaseModel = {
|
|||||||
note: 'SF1.06',
|
note: 'SF1.06',
|
||||||
endAction: EndAction.None,
|
endAction: EndAction.None,
|
||||||
timerType: TimerType.CountDown,
|
timerType: TimerType.CountDown,
|
||||||
|
isTimeToEnd: false,
|
||||||
linkStart: null,
|
linkStart: null,
|
||||||
timeStrategy: TimeStrategy.LockEnd,
|
timeStrategy: TimeStrategy.LockEnd,
|
||||||
timeStart: 47100000,
|
timeStart: 47100000,
|
||||||
@@ -159,6 +165,7 @@ export const demoDb: DatabaseModel = {
|
|||||||
note: 'SF1.07',
|
note: 'SF1.07',
|
||||||
endAction: EndAction.None,
|
endAction: EndAction.None,
|
||||||
timerType: TimerType.CountDown,
|
timerType: TimerType.CountDown,
|
||||||
|
isTimeToEnd: false,
|
||||||
linkStart: null,
|
linkStart: null,
|
||||||
timeStrategy: TimeStrategy.LockEnd,
|
timeStrategy: TimeStrategy.LockEnd,
|
||||||
timeStart: 48600000,
|
timeStart: 48600000,
|
||||||
@@ -183,6 +190,7 @@ export const demoDb: DatabaseModel = {
|
|||||||
note: 'SF1.08',
|
note: 'SF1.08',
|
||||||
endAction: EndAction.None,
|
endAction: EndAction.None,
|
||||||
timerType: TimerType.CountDown,
|
timerType: TimerType.CountDown,
|
||||||
|
isTimeToEnd: false,
|
||||||
linkStart: null,
|
linkStart: null,
|
||||||
timeStrategy: TimeStrategy.LockEnd,
|
timeStrategy: TimeStrategy.LockEnd,
|
||||||
timeStart: 50100000,
|
timeStart: 50100000,
|
||||||
@@ -207,6 +215,7 @@ export const demoDb: DatabaseModel = {
|
|||||||
note: 'SF1.09',
|
note: 'SF1.09',
|
||||||
endAction: EndAction.None,
|
endAction: EndAction.None,
|
||||||
timerType: TimerType.CountDown,
|
timerType: TimerType.CountDown,
|
||||||
|
isTimeToEnd: false,
|
||||||
linkStart: null,
|
linkStart: null,
|
||||||
timeStrategy: TimeStrategy.LockEnd,
|
timeStrategy: TimeStrategy.LockEnd,
|
||||||
timeStart: 51600000,
|
timeStart: 51600000,
|
||||||
@@ -231,6 +240,7 @@ export const demoDb: DatabaseModel = {
|
|||||||
note: 'SF1.10',
|
note: 'SF1.10',
|
||||||
endAction: EndAction.None,
|
endAction: EndAction.None,
|
||||||
timerType: TimerType.CountDown,
|
timerType: TimerType.CountDown,
|
||||||
|
isTimeToEnd: false,
|
||||||
linkStart: null,
|
linkStart: null,
|
||||||
timeStrategy: TimeStrategy.LockEnd,
|
timeStrategy: TimeStrategy.LockEnd,
|
||||||
timeStart: 53100000,
|
timeStart: 53100000,
|
||||||
@@ -260,6 +270,7 @@ export const demoDb: DatabaseModel = {
|
|||||||
note: 'SF1.11',
|
note: 'SF1.11',
|
||||||
endAction: EndAction.None,
|
endAction: EndAction.None,
|
||||||
timerType: TimerType.CountDown,
|
timerType: TimerType.CountDown,
|
||||||
|
isTimeToEnd: false,
|
||||||
linkStart: null,
|
linkStart: null,
|
||||||
timeStrategy: TimeStrategy.LockEnd,
|
timeStrategy: TimeStrategy.LockEnd,
|
||||||
timeStart: 56100000,
|
timeStart: 56100000,
|
||||||
@@ -284,9 +295,9 @@ export const demoDb: DatabaseModel = {
|
|||||||
note: 'SF1.12',
|
note: 'SF1.12',
|
||||||
endAction: EndAction.None,
|
endAction: EndAction.None,
|
||||||
timerType: TimerType.CountDown,
|
timerType: TimerType.CountDown,
|
||||||
|
isTimeToEnd: false,
|
||||||
linkStart: null,
|
linkStart: null,
|
||||||
timeStrategy: TimeStrategy.LockEnd,
|
timeStrategy: TimeStrategy.LockEnd,
|
||||||
|
|
||||||
timeStart: 57600000,
|
timeStart: 57600000,
|
||||||
timeEnd: 58800000,
|
timeEnd: 58800000,
|
||||||
duration: 1200000,
|
duration: 1200000,
|
||||||
@@ -309,6 +320,7 @@ export const demoDb: DatabaseModel = {
|
|||||||
note: 'SF1.13',
|
note: 'SF1.13',
|
||||||
endAction: EndAction.None,
|
endAction: EndAction.None,
|
||||||
timerType: TimerType.CountDown,
|
timerType: TimerType.CountDown,
|
||||||
|
isTimeToEnd: false,
|
||||||
linkStart: null,
|
linkStart: null,
|
||||||
timeStrategy: TimeStrategy.LockEnd,
|
timeStrategy: TimeStrategy.LockEnd,
|
||||||
timeStart: 59100000,
|
timeStart: 59100000,
|
||||||
@@ -333,6 +345,7 @@ export const demoDb: DatabaseModel = {
|
|||||||
note: 'SF1.14',
|
note: 'SF1.14',
|
||||||
endAction: EndAction.None,
|
endAction: EndAction.None,
|
||||||
timerType: TimerType.CountDown,
|
timerType: TimerType.CountDown,
|
||||||
|
isTimeToEnd: false,
|
||||||
linkStart: null,
|
linkStart: null,
|
||||||
timeStrategy: TimeStrategy.LockEnd,
|
timeStrategy: TimeStrategy.LockEnd,
|
||||||
timeStart: 60600000,
|
timeStart: 60600000,
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export const event: Omit<OntimeEvent, 'id' | 'delay' | 'cue'> = {
|
|||||||
timerType: TimerType.CountDown,
|
timerType: TimerType.CountDown,
|
||||||
timeStrategy: TimeStrategy.LockDuration,
|
timeStrategy: TimeStrategy.LockDuration,
|
||||||
linkStart: null,
|
linkStart: null,
|
||||||
|
isTimeToEnd: false,
|
||||||
timeStart: 0,
|
timeStart: 0,
|
||||||
timeEnd: 0,
|
timeEnd: 0,
|
||||||
duration: 0,
|
duration: 0,
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ describe('getExpectedFinish()', () => {
|
|||||||
const state = {
|
const state = {
|
||||||
eventNow: {
|
eventNow: {
|
||||||
timeEnd: 30,
|
timeEnd: 30,
|
||||||
timerType: TimerType.TimeToEnd,
|
isTimeToEnd: true,
|
||||||
},
|
},
|
||||||
timer: {
|
timer: {
|
||||||
addedTime: 10,
|
addedTime: 10,
|
||||||
@@ -195,7 +195,7 @@ describe('getExpectedFinish()', () => {
|
|||||||
const state = {
|
const state = {
|
||||||
eventNow: {
|
eventNow: {
|
||||||
timeEnd: 600000, // 00:10:00
|
timeEnd: 600000, // 00:10:00
|
||||||
timerType: TimerType.TimeToEnd,
|
isTimeToEnd: true,
|
||||||
},
|
},
|
||||||
timer: {
|
timer: {
|
||||||
addedTime: 0,
|
addedTime: 0,
|
||||||
@@ -351,7 +351,7 @@ describe('getCurrent()', () => {
|
|||||||
const state = {
|
const state = {
|
||||||
eventNow: {
|
eventNow: {
|
||||||
timeEnd: 100,
|
timeEnd: 100,
|
||||||
timerType: TimerType.TimeToEnd,
|
isTimeToEnd: true,
|
||||||
},
|
},
|
||||||
clock: 30,
|
clock: 30,
|
||||||
timer: {
|
timer: {
|
||||||
@@ -376,7 +376,7 @@ describe('getCurrent()', () => {
|
|||||||
const state = {
|
const state = {
|
||||||
eventNow: {
|
eventNow: {
|
||||||
timeEnd: 100,
|
timeEnd: 100,
|
||||||
timerType: TimerType.TimeToEnd,
|
isTimeToEnd: true,
|
||||||
},
|
},
|
||||||
clock: 30,
|
clock: 30,
|
||||||
timer: {
|
timer: {
|
||||||
@@ -401,7 +401,7 @@ describe('getCurrent()', () => {
|
|||||||
const state = {
|
const state = {
|
||||||
eventNow: {
|
eventNow: {
|
||||||
timeEnd: 100,
|
timeEnd: 100,
|
||||||
timerType: TimerType.TimeToEnd,
|
isTimeToEnd: true,
|
||||||
},
|
},
|
||||||
clock: 30,
|
clock: 30,
|
||||||
timer: {
|
timer: {
|
||||||
@@ -427,7 +427,7 @@ describe('getCurrent()', () => {
|
|||||||
eventNow: {
|
eventNow: {
|
||||||
timeStart: 79200000, // 22:00:00
|
timeStart: 79200000, // 22:00:00
|
||||||
timeEnd: 600000, // 00:10:00
|
timeEnd: 600000, // 00:10:00
|
||||||
timerType: TimerType.TimeToEnd,
|
isTimeToEnd: true,
|
||||||
},
|
},
|
||||||
clock: 79500000, // 22:05:00
|
clock: 79500000, // 22:05:00
|
||||||
timer: {
|
timer: {
|
||||||
@@ -456,7 +456,7 @@ describe('getCurrent()', () => {
|
|||||||
timeStart: 77400000, // 21:30:00
|
timeStart: 77400000, // 21:30:00
|
||||||
timeEnd: 81000000, // 22:30:00
|
timeEnd: 81000000, // 22:30:00
|
||||||
duration: 3600000, // 01:00:00
|
duration: 3600000, // 01:00:00
|
||||||
timerType: TimerType.TimeToEnd,
|
isTimeToEnd: true,
|
||||||
},
|
},
|
||||||
timer: {
|
timer: {
|
||||||
addedTime: 0,
|
addedTime: 0,
|
||||||
@@ -909,7 +909,8 @@ describe('getRuntimeOffset()', () => {
|
|||||||
timeStrategy: TimeStrategy.LockEnd,
|
timeStrategy: TimeStrategy.LockEnd,
|
||||||
linkStart: null,
|
linkStart: null,
|
||||||
endAction: EndAction.None,
|
endAction: EndAction.None,
|
||||||
timerType: TimerType.TimeToEnd,
|
timerType: TimerType.CountDown,
|
||||||
|
isTimeToEnd: true,
|
||||||
isPublic: true,
|
isPublic: true,
|
||||||
skip: false,
|
skip: false,
|
||||||
note: '',
|
note: '',
|
||||||
@@ -961,7 +962,8 @@ describe('getRuntimeOffset()', () => {
|
|||||||
timeStrategy: TimeStrategy.LockEnd,
|
timeStrategy: TimeStrategy.LockEnd,
|
||||||
linkStart: null,
|
linkStart: null,
|
||||||
endAction: EndAction.None,
|
endAction: EndAction.None,
|
||||||
timerType: TimerType.TimeToEnd,
|
timerType: TimerType.CountDown,
|
||||||
|
isTimeToEnd: true,
|
||||||
isPublic: true,
|
isPublic: true,
|
||||||
skip: false,
|
skip: false,
|
||||||
note: '',
|
note: '',
|
||||||
@@ -1011,7 +1013,8 @@ describe('getRuntimeOffset()', () => {
|
|||||||
timeStrategy: TimeStrategy.LockEnd,
|
timeStrategy: TimeStrategy.LockEnd,
|
||||||
linkStart: null,
|
linkStart: null,
|
||||||
endAction: EndAction.None,
|
endAction: EndAction.None,
|
||||||
timerType: TimerType.TimeToEnd, // <--- but this is time to end
|
timerType: TimerType.CountDown,
|
||||||
|
isTimeToEnd: true,
|
||||||
},
|
},
|
||||||
runtime: {
|
runtime: {
|
||||||
selectedEventIndex: 0,
|
selectedEventIndex: 0,
|
||||||
|
|||||||
@@ -565,6 +565,7 @@ describe('calculateRuntimeDelays', () => {
|
|||||||
note: '',
|
note: '',
|
||||||
endAction: EndAction.None,
|
endAction: EndAction.None,
|
||||||
timerType: TimerType.CountDown,
|
timerType: TimerType.CountDown,
|
||||||
|
isTimeToEnd: false,
|
||||||
timeStrategy: TimeStrategy.LockEnd,
|
timeStrategy: TimeStrategy.LockEnd,
|
||||||
linkStart: null,
|
linkStart: null,
|
||||||
timeStart: 600000,
|
timeStart: 600000,
|
||||||
@@ -591,6 +592,7 @@ describe('calculateRuntimeDelays', () => {
|
|||||||
note: '',
|
note: '',
|
||||||
endAction: EndAction.None,
|
endAction: EndAction.None,
|
||||||
timerType: TimerType.CountDown,
|
timerType: TimerType.CountDown,
|
||||||
|
isTimeToEnd: false,
|
||||||
timeStrategy: TimeStrategy.LockEnd,
|
timeStrategy: TimeStrategy.LockEnd,
|
||||||
linkStart: null,
|
linkStart: null,
|
||||||
timeStart: 1200000,
|
timeStart: 1200000,
|
||||||
@@ -617,6 +619,7 @@ describe('calculateRuntimeDelays', () => {
|
|||||||
note: '',
|
note: '',
|
||||||
endAction: EndAction.None,
|
endAction: EndAction.None,
|
||||||
timerType: TimerType.CountDown,
|
timerType: TimerType.CountDown,
|
||||||
|
isTimeToEnd: false,
|
||||||
timeStrategy: TimeStrategy.LockEnd,
|
timeStrategy: TimeStrategy.LockEnd,
|
||||||
linkStart: null,
|
linkStart: null,
|
||||||
timeStart: 600000,
|
timeStart: 600000,
|
||||||
@@ -643,6 +646,7 @@ describe('calculateRuntimeDelays', () => {
|
|||||||
note: '',
|
note: '',
|
||||||
endAction: EndAction.None,
|
endAction: EndAction.None,
|
||||||
timerType: TimerType.CountDown,
|
timerType: TimerType.CountDown,
|
||||||
|
isTimeToEnd: false,
|
||||||
timeStrategy: TimeStrategy.LockEnd,
|
timeStrategy: TimeStrategy.LockEnd,
|
||||||
linkStart: null,
|
linkStart: null,
|
||||||
timeStart: 1200000,
|
timeStart: 1200000,
|
||||||
@@ -678,6 +682,7 @@ describe('getDelayAt()', () => {
|
|||||||
note: '',
|
note: '',
|
||||||
endAction: EndAction.None,
|
endAction: EndAction.None,
|
||||||
timerType: TimerType.CountDown,
|
timerType: TimerType.CountDown,
|
||||||
|
isTimeToEnd: false,
|
||||||
timeStrategy: TimeStrategy.LockEnd,
|
timeStrategy: TimeStrategy.LockEnd,
|
||||||
linkStart: null,
|
linkStart: null,
|
||||||
timeStart: 600000,
|
timeStart: 600000,
|
||||||
@@ -705,6 +710,7 @@ describe('getDelayAt()', () => {
|
|||||||
note: '',
|
note: '',
|
||||||
endAction: EndAction.None,
|
endAction: EndAction.None,
|
||||||
timerType: TimerType.CountDown,
|
timerType: TimerType.CountDown,
|
||||||
|
isTimeToEnd: false,
|
||||||
timeStrategy: TimeStrategy.LockEnd,
|
timeStrategy: TimeStrategy.LockEnd,
|
||||||
linkStart: null,
|
linkStart: null,
|
||||||
timeStart: 1200000,
|
timeStart: 1200000,
|
||||||
@@ -732,6 +738,7 @@ describe('getDelayAt()', () => {
|
|||||||
note: '',
|
note: '',
|
||||||
endAction: EndAction.None,
|
endAction: EndAction.None,
|
||||||
timerType: TimerType.CountDown,
|
timerType: TimerType.CountDown,
|
||||||
|
isTimeToEnd: false,
|
||||||
timeStrategy: TimeStrategy.LockEnd,
|
timeStrategy: TimeStrategy.LockEnd,
|
||||||
linkStart: null,
|
linkStart: null,
|
||||||
timeStart: 600000,
|
timeStart: 600000,
|
||||||
@@ -759,6 +766,7 @@ describe('getDelayAt()', () => {
|
|||||||
note: '',
|
note: '',
|
||||||
endAction: EndAction.None,
|
endAction: EndAction.None,
|
||||||
timerType: TimerType.CountDown,
|
timerType: TimerType.CountDown,
|
||||||
|
isTimeToEnd: false,
|
||||||
timeStrategy: TimeStrategy.LockEnd,
|
timeStrategy: TimeStrategy.LockEnd,
|
||||||
linkStart: null,
|
linkStart: null,
|
||||||
timeStart: 1200000,
|
timeStart: 1200000,
|
||||||
@@ -812,6 +820,7 @@ describe('calculateRuntimeDelaysFrom()', () => {
|
|||||||
note: '',
|
note: '',
|
||||||
endAction: EndAction.None,
|
endAction: EndAction.None,
|
||||||
timerType: TimerType.CountDown,
|
timerType: TimerType.CountDown,
|
||||||
|
isTimeToEnd: false,
|
||||||
timeStrategy: TimeStrategy.LockEnd,
|
timeStrategy: TimeStrategy.LockEnd,
|
||||||
linkStart: null,
|
linkStart: null,
|
||||||
timeStart: 600000,
|
timeStart: 600000,
|
||||||
@@ -839,6 +848,7 @@ describe('calculateRuntimeDelaysFrom()', () => {
|
|||||||
note: '',
|
note: '',
|
||||||
endAction: EndAction.None,
|
endAction: EndAction.None,
|
||||||
timerType: TimerType.CountDown,
|
timerType: TimerType.CountDown,
|
||||||
|
isTimeToEnd: false,
|
||||||
timeStrategy: TimeStrategy.LockEnd,
|
timeStrategy: TimeStrategy.LockEnd,
|
||||||
linkStart: null,
|
linkStart: null,
|
||||||
timeStart: 1200000,
|
timeStart: 1200000,
|
||||||
@@ -866,6 +876,7 @@ describe('calculateRuntimeDelaysFrom()', () => {
|
|||||||
note: '',
|
note: '',
|
||||||
endAction: EndAction.None,
|
endAction: EndAction.None,
|
||||||
timerType: TimerType.CountDown,
|
timerType: TimerType.CountDown,
|
||||||
|
isTimeToEnd: false,
|
||||||
timeStrategy: TimeStrategy.LockEnd,
|
timeStrategy: TimeStrategy.LockEnd,
|
||||||
linkStart: null,
|
linkStart: null,
|
||||||
timeStart: 600000,
|
timeStart: 600000,
|
||||||
@@ -893,6 +904,7 @@ describe('calculateRuntimeDelaysFrom()', () => {
|
|||||||
note: '',
|
note: '',
|
||||||
endAction: EndAction.None,
|
endAction: EndAction.None,
|
||||||
timerType: TimerType.CountDown,
|
timerType: TimerType.CountDown,
|
||||||
|
isTimeToEnd: false,
|
||||||
timeStrategy: TimeStrategy.LockEnd,
|
timeStrategy: TimeStrategy.LockEnd,
|
||||||
linkStart: null,
|
linkStart: null,
|
||||||
timeStart: 1200000,
|
timeStart: 1200000,
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ describe('cellRequestFromEvent()', () => {
|
|||||||
linkStart: null,
|
linkStart: null,
|
||||||
endAction: EndAction.None,
|
endAction: EndAction.None,
|
||||||
timerType: TimerType.CountDown,
|
timerType: TimerType.CountDown,
|
||||||
|
isTimeToEnd: false,
|
||||||
duration: 10800000,
|
duration: 10800000,
|
||||||
isPublic: false,
|
isPublic: false,
|
||||||
skip: false,
|
skip: false,
|
||||||
@@ -73,6 +74,7 @@ describe('cellRequestFromEvent()', () => {
|
|||||||
timeEnd: 57600000,
|
timeEnd: 57600000,
|
||||||
endAction: EndAction.None,
|
endAction: EndAction.None,
|
||||||
timerType: TimerType.CountDown,
|
timerType: TimerType.CountDown,
|
||||||
|
isTimeToEnd: false,
|
||||||
duration: 10800000,
|
duration: 10800000,
|
||||||
timeStrategy: TimeStrategy.LockEnd,
|
timeStrategy: TimeStrategy.LockEnd,
|
||||||
linkStart: null,
|
linkStart: null,
|
||||||
@@ -119,6 +121,7 @@ describe('cellRequestFromEvent()', () => {
|
|||||||
timeEnd: 57600000,
|
timeEnd: 57600000,
|
||||||
endAction: EndAction.None,
|
endAction: EndAction.None,
|
||||||
timerType: TimerType.CountDown,
|
timerType: TimerType.CountDown,
|
||||||
|
isTimeToEnd: false,
|
||||||
duration: 10800000,
|
duration: 10800000,
|
||||||
timeStrategy: TimeStrategy.LockEnd,
|
timeStrategy: TimeStrategy.LockEnd,
|
||||||
linkStart: null,
|
linkStart: null,
|
||||||
@@ -164,6 +167,7 @@ describe('cellRequestFromEvent()', () => {
|
|||||||
timeEnd: 57600000,
|
timeEnd: 57600000,
|
||||||
endAction: EndAction.None,
|
endAction: EndAction.None,
|
||||||
timerType: TimerType.CountDown,
|
timerType: TimerType.CountDown,
|
||||||
|
isTimeToEnd: false,
|
||||||
timeStrategy: TimeStrategy.LockEnd,
|
timeStrategy: TimeStrategy.LockEnd,
|
||||||
linkStart: null,
|
linkStart: null,
|
||||||
duration: 10800000,
|
duration: 10800000,
|
||||||
@@ -195,6 +199,7 @@ describe('cellRequestFromEvent()', () => {
|
|||||||
timeEnd: 57600000,
|
timeEnd: 57600000,
|
||||||
endAction: EndAction.None,
|
endAction: EndAction.None,
|
||||||
timerType: TimerType.CountDown,
|
timerType: TimerType.CountDown,
|
||||||
|
isTimeToEnd: false,
|
||||||
duration: 10800000,
|
duration: 10800000,
|
||||||
timeStrategy: TimeStrategy.LockEnd,
|
timeStrategy: TimeStrategy.LockEnd,
|
||||||
linkStart: null,
|
linkStart: null,
|
||||||
@@ -227,6 +232,7 @@ describe('cellRequestFromEvent()', () => {
|
|||||||
timeEnd: 57600000,
|
timeEnd: 57600000,
|
||||||
endAction: EndAction.None,
|
endAction: EndAction.None,
|
||||||
timerType: TimerType.CountDown,
|
timerType: TimerType.CountDown,
|
||||||
|
isTimeToEnd: false,
|
||||||
duration: 10800000,
|
duration: 10800000,
|
||||||
timeStrategy: TimeStrategy.LockEnd,
|
timeStrategy: TimeStrategy.LockEnd,
|
||||||
linkStart: null,
|
linkStart: null,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { MaybeNumber, Playback, TimerPhase, TimerType } from 'ontime-types';
|
import { MaybeNumber, Playback, TimerPhase } from 'ontime-types';
|
||||||
import { dayInMs } from 'ontime-utils';
|
import { dayInMs } from 'ontime-utils';
|
||||||
import { RuntimeState } from '../stores/runtimeState.js';
|
import { RuntimeState } from '../stores/runtimeState.js';
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ export function getExpectedFinish(state: RuntimeState): MaybeNumber {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { timerType, timeEnd } = state.eventNow;
|
const { isTimeToEnd, timeEnd } = state.eventNow;
|
||||||
const { pausedAt } = state._timer;
|
const { pausedAt } = state._timer;
|
||||||
const { clock } = state;
|
const { clock } = state;
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ export function getExpectedFinish(state: RuntimeState): MaybeNumber {
|
|||||||
|
|
||||||
const pausedTime = pausedAt != null ? clock - pausedAt : 0;
|
const pausedTime = pausedAt != null ? clock - pausedAt : 0;
|
||||||
|
|
||||||
if (timerType === TimerType.TimeToEnd) {
|
if (isTimeToEnd) {
|
||||||
return timeEnd + addedTime + pausedTime;
|
return timeEnd + addedTime + pausedTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,11 +62,11 @@ export function getCurrent(state: RuntimeState): number {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const { startedAt, duration, addedTime } = state.timer;
|
const { startedAt, duration, addedTime } = state.timer;
|
||||||
const { timerType, timeStart, timeEnd } = state.eventNow;
|
const { isTimeToEnd, timeStart, timeEnd } = state.eventNow;
|
||||||
const { pausedAt } = state._timer;
|
const { pausedAt } = state._timer;
|
||||||
const { clock } = state;
|
const { clock } = state;
|
||||||
|
|
||||||
if (timerType === TimerType.TimeToEnd) {
|
if (isTimeToEnd) {
|
||||||
const isEventOverMidnight = timeStart > timeEnd;
|
const isEventOverMidnight = timeStart > timeEnd;
|
||||||
const correctDay = isEventOverMidnight ? dayInMs : 0;
|
const correctDay = isEventOverMidnight ? dayInMs : 0;
|
||||||
return correctDay - clock + timeEnd + addedTime;
|
return correctDay - clock + timeEnd + addedTime;
|
||||||
@@ -131,7 +131,7 @@ export function getRuntimeOffset(state: RuntimeState): number {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { clock } = state;
|
const { clock } = state;
|
||||||
const { timeStart, timerType } = state.eventNow;
|
const { isTimeToEnd, timeStart } = state.eventNow;
|
||||||
const { addedTime, current, startedAt } = state.timer;
|
const { addedTime, current, startedAt } = state.timer;
|
||||||
|
|
||||||
// if we havent started, but the timer is armed
|
// if we havent started, but the timer is armed
|
||||||
@@ -142,7 +142,7 @@ export function getRuntimeOffset(state: RuntimeState): number {
|
|||||||
|
|
||||||
const overtime = Math.min(current, 0);
|
const overtime = Math.min(current, 0);
|
||||||
// in time-to-end, offset is overtime
|
// in time-to-end, offset is overtime
|
||||||
if (timerType === TimerType.TimeToEnd) {
|
if (isTimeToEnd) {
|
||||||
return overtime;
|
return overtime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -463,7 +463,7 @@ describe('test event validator', () => {
|
|||||||
const event = {
|
const event = {
|
||||||
title: 'test',
|
title: 'test',
|
||||||
};
|
};
|
||||||
const validated = createEvent(event, 'test');
|
const validated = createEvent(event, 1);
|
||||||
|
|
||||||
expect(validated).toEqual(
|
expect(validated).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
@@ -471,12 +471,13 @@ describe('test event validator', () => {
|
|||||||
note: expect.any(String),
|
note: expect.any(String),
|
||||||
timeStart: expect.any(Number),
|
timeStart: expect.any(Number),
|
||||||
timeEnd: expect.any(Number),
|
timeEnd: expect.any(Number),
|
||||||
|
isTimeToEnd: expect.any(Boolean),
|
||||||
isPublic: expect.any(Boolean),
|
isPublic: expect.any(Boolean),
|
||||||
skip: expect.any(Boolean),
|
skip: expect.any(Boolean),
|
||||||
revision: expect.any(Number),
|
revision: expect.any(Number),
|
||||||
type: expect.any(String),
|
type: expect.any(String),
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
cue: 'test',
|
cue: '2',
|
||||||
colour: expect.any(String),
|
colour: expect.any(String),
|
||||||
custom: expect.any(Object),
|
custom: expect.any(Object),
|
||||||
}),
|
}),
|
||||||
@@ -485,7 +486,7 @@ describe('test event validator', () => {
|
|||||||
|
|
||||||
it('fails an empty object', () => {
|
it('fails an empty object', () => {
|
||||||
const event = {};
|
const event = {};
|
||||||
const validated = createEvent(event, 'none');
|
const validated = createEvent(event, 1);
|
||||||
expect(validated).toEqual(null);
|
expect(validated).toEqual(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -495,7 +496,7 @@ describe('test event validator', () => {
|
|||||||
note: '1899-12-30T08:00:10.000Z',
|
note: '1899-12-30T08:00:10.000Z',
|
||||||
};
|
};
|
||||||
// @ts-expect-error -- we know this is wrong, testing imports outside domain
|
// @ts-expect-error -- we know this is wrong, testing imports outside domain
|
||||||
const validated = createEvent(event, 'not-used');
|
const validated = createEvent(event, 1);
|
||||||
if (validated === null) {
|
if (validated === null) {
|
||||||
throw new Error('unexpected value');
|
throw new Error('unexpected value');
|
||||||
}
|
}
|
||||||
@@ -774,6 +775,7 @@ describe('getCustomFieldData()', () => {
|
|||||||
duration: 'duration',
|
duration: 'duration',
|
||||||
cue: 'cue',
|
cue: 'cue',
|
||||||
title: 'title',
|
title: 'title',
|
||||||
|
isTimeToEnd: 'time to end',
|
||||||
isPublic: 'public',
|
isPublic: 'public',
|
||||||
skip: 'skip',
|
skip: 'skip',
|
||||||
note: 'notes',
|
note: 'notes',
|
||||||
@@ -824,6 +826,7 @@ describe('getCustomFieldData()', () => {
|
|||||||
duration: 'duration',
|
duration: 'duration',
|
||||||
cue: 'cue',
|
cue: 'cue',
|
||||||
title: 'title',
|
title: 'title',
|
||||||
|
isTimeToEnd: 'time to end',
|
||||||
isPublic: 'public',
|
isPublic: 'public',
|
||||||
skip: 'skip',
|
skip: 'skip',
|
||||||
note: 'notes',
|
note: 'notes',
|
||||||
@@ -883,6 +886,7 @@ describe('parseExcel()', () => {
|
|||||||
'Title',
|
'Title',
|
||||||
'End Action',
|
'End Action',
|
||||||
'Timer type',
|
'Timer type',
|
||||||
|
'Time to end',
|
||||||
'Public',
|
'Public',
|
||||||
'Skip',
|
'Skip',
|
||||||
'Notes',
|
'Notes',
|
||||||
@@ -905,7 +909,8 @@ describe('parseExcel()', () => {
|
|||||||
'Guest Welcome',
|
'Guest Welcome',
|
||||||
'',
|
'',
|
||||||
'',
|
'',
|
||||||
'x',
|
'x', // <-- time to end
|
||||||
|
'x', // <-- public
|
||||||
'',
|
'',
|
||||||
'Ballyhoo',
|
'Ballyhoo',
|
||||||
'a0',
|
'a0',
|
||||||
@@ -927,7 +932,8 @@ describe('parseExcel()', () => {
|
|||||||
'A song from the hearth',
|
'A song from the hearth',
|
||||||
'load-next',
|
'load-next',
|
||||||
'clock',
|
'clock',
|
||||||
'',
|
'x', // <-- time to end
|
||||||
|
'', // <-- public
|
||||||
'x',
|
'x',
|
||||||
'Rainbow chase',
|
'Rainbow chase',
|
||||||
'b0',
|
'b0',
|
||||||
@@ -971,6 +977,7 @@ describe('parseExcel()', () => {
|
|||||||
timerType: 'count-down',
|
timerType: 'count-down',
|
||||||
endAction: 'none',
|
endAction: 'none',
|
||||||
isPublic: true,
|
isPublic: true,
|
||||||
|
isTimeToEnd: true,
|
||||||
skip: false,
|
skip: false,
|
||||||
note: 'Ballyhoo',
|
note: 'Ballyhoo',
|
||||||
custom: {
|
custom: {
|
||||||
@@ -994,6 +1001,7 @@ describe('parseExcel()', () => {
|
|||||||
timeEnd: 30600000,
|
timeEnd: 30600000,
|
||||||
title: 'A song from the hearth',
|
title: 'A song from the hearth',
|
||||||
timerType: 'clock',
|
timerType: 'clock',
|
||||||
|
isTimeToEnd: true,
|
||||||
endAction: 'load-next',
|
endAction: 'load-next',
|
||||||
isPublic: false,
|
isPublic: false,
|
||||||
skip: true,
|
skip: true,
|
||||||
@@ -1454,12 +1462,17 @@ describe('parseExcel()', () => {
|
|||||||
timeDanger: 'danger time',
|
timeDanger: 'danger time',
|
||||||
custom: {},
|
custom: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = parseExcel(testdata, {}, importMap);
|
const result = parseExcel(testdata, {}, importMap);
|
||||||
expect(result.rundown.length).toBe(2);
|
expect(result.rundown.length).toBe(2);
|
||||||
expect((result.rundown.at(0) as OntimeEvent).type).toBe(SupportedEvent.Event);
|
expect(result.rundown[0]).toMatchObject({
|
||||||
expect((result.rundown.at(0) as OntimeEvent).timerType).toBe(TimerType.CountDown);
|
type: SupportedEvent.Event,
|
||||||
expect((result.rundown.at(1) as OntimeEvent).type).toBe(SupportedEvent.Event);
|
timerType: TimerType.CountDown,
|
||||||
expect((result.rundown.at(1) as OntimeEvent).timerType).toBe(TimerType.CountDown);
|
});
|
||||||
|
expect(result.rundown[1]).toMatchObject({
|
||||||
|
type: SupportedEvent.Event,
|
||||||
|
timerType: TimerType.CountDown,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('imports as events if timer type is empty or has whitespace', () => {
|
it('imports as events if timer type is empty or has whitespace', () => {
|
||||||
@@ -1659,7 +1672,7 @@ describe('parseExcel()', () => {
|
|||||||
expect((events.at(0) as OntimeEvent).colour).toEqual('#F00'); //<--trailing white space in Excel data
|
expect((events.at(0) as OntimeEvent).colour).toEqual('#F00'); //<--trailing white space in Excel data
|
||||||
});
|
});
|
||||||
|
|
||||||
it('link start', () => {
|
it('parses link start and checks that is applicable', () => {
|
||||||
const testData = [
|
const testData = [
|
||||||
[
|
[
|
||||||
'Time Start',
|
'Time Start',
|
||||||
@@ -1675,11 +1688,11 @@ describe('parseExcel()', () => {
|
|||||||
'Timer type',
|
'Timer type',
|
||||||
],
|
],
|
||||||
['4:30:00', '9:45:00', 'A', 'load-next', '', '', 'Rainbow chase', '#F00', 102, '', 'count-down'],
|
['4:30:00', '9:45:00', 'A', 'load-next', '', '', 'Rainbow chase', '#F00', 102, '', 'count-down'],
|
||||||
['9:45:00', '10:56:00', 'C', 'load-next', 'x', '', 'Rainbow chase', '#0F0', 103, 'x', 'count-down'],
|
['9:45:00', '10:56:00', 'B', 'load-next', 'x', '', 'Rainbow chase', '#0F0', 103, 'x', 'count-down'],
|
||||||
['10:00:00', '16:36:00', 'D', 'load-next', 'x', '', 'Rainbow chase', '#F00', 102, 'x', 'count-down'], //<-- incorrect start times are overridden
|
['10:00:00', '16:36:00', 'C', 'load-next', 'x', '', 'Rainbow chase', '#F00', 102, 'x', 'count-down'], // <-- incorrect start times are overridden
|
||||||
['21:45:00', '22:56:00', 'E', 'load-next', 'x', '', 'Rainbow chase', '#0F0', 103, '', 'count-down'],
|
['21:45:00', '22:56:00', 'D', 'load-next', 'x', '', 'Rainbow chase', '#0F0', 103, '', 'count-down'],
|
||||||
['', '', 'BLOCK', '', '', '', '', '', '', '', 'block'],
|
['', '', 'BLOCK', '', '', '', '', '', '', '', 'block'],
|
||||||
['00:0:00', '23:56:00', 'G', 'load-next', 'x', '', 'Rainbow chase', '#0F0', 103, 'x', 'count-down'], //<-- link past blocks
|
['00:0:00', '23:56:00', 'E', 'load-next', 'x', '', 'Rainbow chase', '#0F0', 103, 'x', 'count-down'], // <-- link past blocks
|
||||||
[],
|
[],
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -1709,30 +1722,46 @@ describe('parseExcel()', () => {
|
|||||||
const { rundown, order } = cache.get();
|
const { rundown, order } = cache.get();
|
||||||
|
|
||||||
const firstId = order.at(0); // A
|
const firstId = order.at(0); // A
|
||||||
const secondId = order.at(1); // C
|
const secondId = order.at(1); // B
|
||||||
const thirdId = order.at(2); // D
|
const thirdId = order.at(2); // C
|
||||||
const fourthId = order.at(3); // E
|
const fourthId = order.at(3); // D
|
||||||
const fifhtId = order.at(4); // Block
|
const fifthId = order.at(4); // Block
|
||||||
const sixthId = order.at(5); // G
|
const sixthId = order.at(5); // E
|
||||||
|
|
||||||
if (!firstId || !secondId || !thirdId || !fourthId || !fifhtId || !sixthId) {
|
if (!firstId || !secondId || !thirdId || !fourthId || !fifthId || !sixthId) {
|
||||||
throw new Error('Unexpected value');
|
throw new Error('Unexpected value');
|
||||||
}
|
}
|
||||||
|
|
||||||
expect((rundown[firstId] as OntimeEvent).timeStart).toEqual(16200000);
|
expect(rundown).toMatchObject({
|
||||||
|
[firstId]: {
|
||||||
expect((rundown[secondId] as OntimeEvent).timeStart).toEqual((rundown[firstId] as OntimeEvent).timeEnd);
|
title: 'A',
|
||||||
expect((rundown[secondId] as OntimeEvent).linkStart).toEqual((rundown[firstId] as OntimeEvent).id);
|
timeStart: 16200000,
|
||||||
|
},
|
||||||
expect((rundown[thirdId] as OntimeEvent).timeStart).toEqual((rundown[secondId] as OntimeEvent).timeEnd);
|
[secondId]: {
|
||||||
expect((rundown[thirdId] as OntimeEvent).linkStart).toEqual((rundown[secondId] as OntimeEvent).id);
|
title: 'B',
|
||||||
|
timeStart: (rundown[firstId] as OntimeEvent).timeEnd,
|
||||||
expect((rundown[fourthId] as OntimeEvent).timeStart).toEqual(78300000);
|
linkStart: (rundown[firstId] as OntimeEvent).id,
|
||||||
|
},
|
||||||
expect((rundown[fifhtId] as OntimeEvent).type).toEqual(SupportedEvent.Block);
|
[thirdId]: {
|
||||||
|
title: 'C',
|
||||||
expect((rundown[sixthId] as OntimeEvent).timeStart).toEqual((rundown[fourthId] as OntimeEvent).timeEnd);
|
timeStart: (rundown[secondId] as OntimeEvent).timeEnd,
|
||||||
expect((rundown[sixthId] as OntimeEvent).linkStart).toEqual((rundown[fourthId] as OntimeEvent).id);
|
linkStart: (rundown[secondId] as OntimeEvent).id,
|
||||||
|
},
|
||||||
|
[fourthId]: {
|
||||||
|
title: 'D',
|
||||||
|
timeStart: 78300000,
|
||||||
|
linkStart: null,
|
||||||
|
},
|
||||||
|
[fifthId]: {
|
||||||
|
title: 'BLOCK',
|
||||||
|
type: SupportedEvent.Block,
|
||||||
|
},
|
||||||
|
[sixthId]: {
|
||||||
|
title: 'E',
|
||||||
|
timeStart: (rundown[fourthId] as OntimeEvent).timeEnd,
|
||||||
|
linkStart: (rundown[fourthId] as OntimeEvent).id,
|
||||||
|
},
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('#971 BUG: parses time fields and booleans', () => {
|
it('#971 BUG: parses time fields and booleans', () => {
|
||||||
@@ -1762,7 +1791,7 @@ describe('parseExcel()', () => {
|
|||||||
'false',
|
'false',
|
||||||
'Setup',
|
'Setup',
|
||||||
'',
|
'',
|
||||||
'time-to-end',
|
'count-down',
|
||||||
'none',
|
'none',
|
||||||
'15',
|
'15',
|
||||||
'00:05:00',
|
'00:05:00',
|
||||||
@@ -1778,7 +1807,7 @@ describe('parseExcel()', () => {
|
|||||||
'false',
|
'false',
|
||||||
'Meeting 1',
|
'Meeting 1',
|
||||||
'',
|
'',
|
||||||
'time-to-end',
|
'count-down',
|
||||||
'none',
|
'none',
|
||||||
15,
|
15,
|
||||||
'00:05:00',
|
'00:05:00',
|
||||||
@@ -1794,7 +1823,7 @@ describe('parseExcel()', () => {
|
|||||||
'false',
|
'false',
|
||||||
'Meeting 2',
|
'Meeting 2',
|
||||||
'',
|
'',
|
||||||
'time-to-end',
|
'count-down',
|
||||||
'none',
|
'none',
|
||||||
'13',
|
'13',
|
||||||
'5',
|
'5',
|
||||||
@@ -1810,7 +1839,7 @@ describe('parseExcel()', () => {
|
|||||||
'true',
|
'true',
|
||||||
'Lunch',
|
'Lunch',
|
||||||
'',
|
'',
|
||||||
'time-to-end',
|
'count-down',
|
||||||
'none',
|
'none',
|
||||||
13,
|
13,
|
||||||
5,
|
5,
|
||||||
@@ -1839,26 +1868,46 @@ describe('parseExcel()', () => {
|
|||||||
const parsedData = parseExcel(testData, {});
|
const parsedData = parseExcel(testData, {});
|
||||||
const { rundown } = parsedData;
|
const { rundown } = parsedData;
|
||||||
|
|
||||||
|
// '15' as a string is parsed by smart time entry as minutes
|
||||||
|
expect(rundown[0]).toMatchObject({
|
||||||
|
cue: 'SETUP',
|
||||||
|
timeWarning: 15 * MILLIS_PER_MINUTE,
|
||||||
|
});
|
||||||
|
|
||||||
// elements in bug report
|
// elements in bug report
|
||||||
// 15 is a number, in which case we parse it as a minutes value
|
// 15 is a number, in which case we parse it as a minutes value
|
||||||
expect((rundown.at(1) as OntimeEvent).timeWarning).toBe(15 * MILLIS_PER_MINUTE);
|
expect(rundown[1]).toMatchObject({
|
||||||
|
cue: 'MEET1',
|
||||||
|
timeWarning: 15 * MILLIS_PER_MINUTE,
|
||||||
|
});
|
||||||
|
|
||||||
// in the case where a string is passed, we need to check whether it is an ISO 8601 date
|
// in the case where a string is passed, we need to check whether it is an ISO 8601 date
|
||||||
expect((rundown.at(2) as OntimeEvent).duration).toBe(60 * MILLIS_PER_MINUTE);
|
expect(rundown[2]).toMatchObject({
|
||||||
expect((rundown.at(2) as OntimeEvent).timeDanger).toBe(5 * MILLIS_PER_MINUTE);
|
cue: 'MEET2',
|
||||||
|
duration: 60 * MILLIS_PER_MINUTE,
|
||||||
|
timeDanger: 5 * MILLIS_PER_MINUTE,
|
||||||
|
});
|
||||||
|
|
||||||
expect((rundown.at(3) as OntimeEvent).timeWarning).toBe(13 * MILLIS_PER_MINUTE);
|
expect(rundown[3]).toMatchObject({
|
||||||
expect((rundown.at(3) as OntimeEvent).timeDanger).toBe(5 * MILLIS_PER_MINUTE);
|
cue: 'lunch',
|
||||||
|
timeWarning: 13 * MILLIS_PER_MINUTE,
|
||||||
|
timeDanger: 5 * MILLIS_PER_MINUTE,
|
||||||
|
});
|
||||||
|
|
||||||
expect((rundown.at(4) as OntimeEvent).duration).toBe(90 * MILLIS_PER_MINUTE);
|
expect(rundown[4]).toMatchObject({
|
||||||
expect((rundown.at(4) as OntimeEvent).linkStart).toBe(false);
|
cue: 'MEET3',
|
||||||
expect((rundown.at(4) as OntimeEvent).timeWarning).toBe(11 * MILLIS_PER_MINUTE);
|
duration: 90 * MILLIS_PER_MINUTE,
|
||||||
expect((rundown.at(4) as OntimeEvent).timeDanger).toBe(5 * MILLIS_PER_MINUTE);
|
linkStart: false,
|
||||||
|
timeWarning: 11 * MILLIS_PER_MINUTE,
|
||||||
|
timeDanger: 5 * MILLIS_PER_MINUTE,
|
||||||
|
});
|
||||||
|
|
||||||
expect((rundown.at(5) as OntimeEvent).duration).toBe(30 * MILLIS_PER_MINUTE);
|
expect(rundown[5]).toMatchObject({
|
||||||
|
cue: 'MEET4',
|
||||||
// if we get a boolean, we should just use that
|
duration: 30 * MILLIS_PER_MINUTE,
|
||||||
expect((rundown.at(5) as OntimeEvent).linkStart).toBe(true);
|
timeWarning: 11 * MILLIS_PER_MINUTE,
|
||||||
expect((rundown.at(5) as OntimeEvent).timeWarning).toBe(11 * MILLIS_PER_MINUTE);
|
// if we get a boolean, we should just use that
|
||||||
|
linkStart: true,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -405,28 +405,6 @@ describe('sanitiseCustomFields()', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('parseRundown() linking', () => {
|
describe('parseRundown() linking', () => {
|
||||||
const blankEvent: OntimeEvent = {
|
|
||||||
id: '',
|
|
||||||
type: SupportedEvent.Event,
|
|
||||||
cue: '',
|
|
||||||
title: '',
|
|
||||||
note: '',
|
|
||||||
endAction: EndAction.None,
|
|
||||||
timerType: TimerType.CountDown,
|
|
||||||
linkStart: null,
|
|
||||||
timeStrategy: TimeStrategy.LockDuration,
|
|
||||||
timeStart: 0,
|
|
||||||
timeEnd: 0,
|
|
||||||
duration: 0,
|
|
||||||
isPublic: false,
|
|
||||||
skip: false,
|
|
||||||
colour: '',
|
|
||||||
revision: 0,
|
|
||||||
timeWarning: 120000,
|
|
||||||
timeDanger: 60000,
|
|
||||||
custom: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
it('returns linked events', () => {
|
it('returns linked events', () => {
|
||||||
const data: Partial<DatabaseModel> = {
|
const data: Partial<DatabaseModel> = {
|
||||||
rundown: [
|
rundown: [
|
||||||
@@ -445,12 +423,11 @@ describe('parseRundown() linking', () => {
|
|||||||
customFields: {},
|
customFields: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
const expected: OntimeRundown = [
|
|
||||||
{ ...blankEvent, id: '1', cue: '0' },
|
|
||||||
{ ...blankEvent, id: '2', cue: '1', linkStart: '1' },
|
|
||||||
];
|
|
||||||
const result = parseRundown(data);
|
const result = parseRundown(data);
|
||||||
expect(result.rundown).toEqual(expected);
|
expect(result.rundown[1]).toMatchObject({
|
||||||
|
id: '2',
|
||||||
|
linkStart: '1',
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns unlinked if no previous', () => {
|
it('returns unlinked if no previous', () => {
|
||||||
@@ -466,9 +443,11 @@ describe('parseRundown() linking', () => {
|
|||||||
customFields: {},
|
customFields: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
const expected: OntimeRundown = [{ ...blankEvent, id: '2', cue: '0' }];
|
|
||||||
const result = parseRundown(data);
|
const result = parseRundown(data);
|
||||||
expect(result.rundown).toEqual(expected);
|
expect(result.rundown[0]).toMatchObject({
|
||||||
|
id: '2',
|
||||||
|
linkStart: null,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns linked events past blocks and delays', () => {
|
it('returns linked events past blocks and delays', () => {
|
||||||
@@ -505,14 +484,65 @@ describe('parseRundown() linking', () => {
|
|||||||
customFields: {},
|
customFields: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
const expected: OntimeRundown = [
|
|
||||||
{ ...blankEvent, id: '1', cue: '0' },
|
|
||||||
{ id: 'delay1', type: SupportedEvent.Delay, duration: 0 },
|
|
||||||
{ ...blankEvent, id: '2', cue: '1', linkStart: '1' },
|
|
||||||
{ id: 'block1', type: SupportedEvent.Block, title: '' },
|
|
||||||
{ ...blankEvent, id: '3', cue: '2', linkStart: '2' },
|
|
||||||
];
|
|
||||||
const result = parseRundown(data);
|
const result = parseRundown(data);
|
||||||
expect(result.rundown).toEqual(expected);
|
expect(result.rundown[0]).toMatchObject({
|
||||||
|
id: '1',
|
||||||
|
cue: '1',
|
||||||
|
});
|
||||||
|
// skip delay
|
||||||
|
expect(result.rundown[2]).toMatchObject({
|
||||||
|
id: '2',
|
||||||
|
cue: '2',
|
||||||
|
linkStart: '1',
|
||||||
|
});
|
||||||
|
// skip block
|
||||||
|
expect(result.rundown[4]).toMatchObject({
|
||||||
|
id: '3',
|
||||||
|
cue: '3',
|
||||||
|
linkStart: '2',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('parseRundown() migrations', () => {
|
||||||
|
const legacyEvent = {
|
||||||
|
id: '1',
|
||||||
|
type: SupportedEvent.Event,
|
||||||
|
cue: '',
|
||||||
|
title: '',
|
||||||
|
note: '',
|
||||||
|
endAction: EndAction.None,
|
||||||
|
timerType: 'time-to-end',
|
||||||
|
linkStart: null,
|
||||||
|
timeStrategy: TimeStrategy.LockDuration,
|
||||||
|
timeStart: 0,
|
||||||
|
timeEnd: 0,
|
||||||
|
duration: 0,
|
||||||
|
isPublic: false,
|
||||||
|
skip: false,
|
||||||
|
colour: '',
|
||||||
|
revision: 0,
|
||||||
|
timeWarning: 120000,
|
||||||
|
timeDanger: 60000,
|
||||||
|
custom: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
it('migrates an event with time-to-end', () => {
|
||||||
|
const result = parseRundown({ rundown: [legacyEvent] as OntimeRundown });
|
||||||
|
expect(result.rundown[0]).toMatchObject({
|
||||||
|
id: '1',
|
||||||
|
timerType: TimerType.CountDown,
|
||||||
|
isTimeToEnd: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('migrates an event without time-to-end', () => {
|
||||||
|
const countdownEvent = { ...legacyEvent, timerType: TimerType.CountDown };
|
||||||
|
const result = parseRundown({ rundown: [countdownEvent] as OntimeRundown });
|
||||||
|
expect(result.rundown[0]).toMatchObject({
|
||||||
|
id: '1',
|
||||||
|
timerType: TimerType.CountDown,
|
||||||
|
isTimeToEnd: false,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ import {
|
|||||||
CustomFields,
|
CustomFields,
|
||||||
DatabaseModel,
|
DatabaseModel,
|
||||||
EventCustomFields,
|
EventCustomFields,
|
||||||
|
isOntimeBlock,
|
||||||
LogOrigin,
|
LogOrigin,
|
||||||
OntimeBlock,
|
|
||||||
OntimeEvent,
|
OntimeEvent,
|
||||||
OntimeRundown,
|
OntimeRundown,
|
||||||
SupportedEvent,
|
SupportedEvent,
|
||||||
@@ -110,6 +110,7 @@ export const parseExcel = (
|
|||||||
// options: booleans
|
// options: booleans
|
||||||
let isPublicIndex: number | null = null;
|
let isPublicIndex: number | null = null;
|
||||||
let skipIndex: number | null = null;
|
let skipIndex: number | null = null;
|
||||||
|
let isTimeToEndIndex: number | null = null;
|
||||||
|
|
||||||
let linkStartIndex: number | null = null;
|
let linkStartIndex: number | null = null;
|
||||||
|
|
||||||
@@ -159,6 +160,10 @@ export const parseExcel = (
|
|||||||
titleIndex = col;
|
titleIndex = col;
|
||||||
rundownMetadata['title'] = { row, col };
|
rundownMetadata['title'] = { row, col };
|
||||||
},
|
},
|
||||||
|
[importMap.isTimeToEnd]: (row: number, col: number) => {
|
||||||
|
isTimeToEndIndex = col;
|
||||||
|
rundownMetadata['isTimeToEnd'] = { row, col };
|
||||||
|
},
|
||||||
[importMap.isPublic]: (row: number, col: number) => {
|
[importMap.isPublic]: (row: number, col: number) => {
|
||||||
isPublicIndex = col;
|
isPublicIndex = col;
|
||||||
rundownMetadata['isPublic'] = { row, col };
|
rundownMetadata['isPublic'] = { row, col };
|
||||||
@@ -226,6 +231,8 @@ export const parseExcel = (
|
|||||||
event.duration = parseExcelDate(column);
|
event.duration = parseExcelDate(column);
|
||||||
} else if (j === cueIndex) {
|
} else if (j === cueIndex) {
|
||||||
event.cue = makeString(column, '');
|
event.cue = makeString(column, '');
|
||||||
|
} else if (j === isTimeToEndIndex) {
|
||||||
|
event.isTimeToEnd = parseBooleanString(column);
|
||||||
} else if (j === isPublicIndex) {
|
} else if (j === isPublicIndex) {
|
||||||
event.isPublic = parseBooleanString(column);
|
event.isPublic = parseBooleanString(column);
|
||||||
} else if (j === skipIndex) {
|
} else if (j === skipIndex) {
|
||||||
@@ -273,8 +280,8 @@ export const parseExcel = (
|
|||||||
const keysFound = Object.keys(event).length + Object.keys(eventCustomFields).length;
|
const keysFound = Object.keys(event).length + Object.keys(eventCustomFields).length;
|
||||||
if (keysFound > 0) {
|
if (keysFound > 0) {
|
||||||
// if it is a Block type drop all other filed
|
// if it is a Block type drop all other filed
|
||||||
if (event.type === SupportedEvent.Block) {
|
if (isOntimeBlock(event)) {
|
||||||
rundown.push({ type: event.type, id: event.id, title: event.title } as OntimeBlock);
|
rundown.push({ type: event.type, id: event.id, title: event.title });
|
||||||
} else {
|
} else {
|
||||||
if (timerTypeIndex === null) {
|
if (timerTypeIndex === null) {
|
||||||
event.timerType = TimerType.CountDown;
|
event.timerType = TimerType.CountDown;
|
||||||
@@ -360,7 +367,6 @@ export function createPatch(originalEvent: OntimeEvent, patchEvent: Partial<Onti
|
|||||||
patchEvent?.duration ?? originalEvent.duration,
|
patchEvent?.duration ?? originalEvent.duration,
|
||||||
patchEvent?.timeStrategy ?? inferStrategy(patchEvent?.timeEnd, patchEvent?.duration, originalEvent.timeStrategy),
|
patchEvent?.timeStrategy ?? inferStrategy(patchEvent?.timeEnd, patchEvent?.duration, originalEvent.timeStrategy),
|
||||||
);
|
);
|
||||||
const maybeLinkStart = patchEvent.linkStart !== undefined ? patchEvent.linkStart : originalEvent.linkStart;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: originalEvent.id,
|
id: originalEvent.id,
|
||||||
@@ -370,9 +376,10 @@ export function createPatch(originalEvent: OntimeEvent, patchEvent: Partial<Onti
|
|||||||
timeEnd,
|
timeEnd,
|
||||||
duration,
|
duration,
|
||||||
timeStrategy,
|
timeStrategy,
|
||||||
linkStart: validateLinkStart(maybeLinkStart),
|
linkStart: validateLinkStart(patchEvent.linkStart),
|
||||||
endAction: validateEndAction(patchEvent.endAction, originalEvent.endAction),
|
endAction: validateEndAction(patchEvent.endAction, originalEvent.endAction),
|
||||||
timerType: validateTimerType(patchEvent.timerType, originalEvent.timerType),
|
timerType: validateTimerType(patchEvent.timerType, originalEvent.timerType),
|
||||||
|
isTimeToEnd: typeof patchEvent.isTimeToEnd === 'boolean' ? patchEvent.isTimeToEnd : originalEvent.isTimeToEnd,
|
||||||
isPublic: typeof patchEvent.isPublic === 'boolean' ? patchEvent.isPublic : originalEvent.isPublic,
|
isPublic: typeof patchEvent.isPublic === 'boolean' ? patchEvent.isPublic : originalEvent.isPublic,
|
||||||
skip: typeof patchEvent.skip === 'boolean' ? patchEvent.skip : originalEvent.skip,
|
skip: typeof patchEvent.skip === 'boolean' ? patchEvent.skip : originalEvent.skip,
|
||||||
note: makeString(patchEvent.note, originalEvent.note),
|
note: makeString(patchEvent.note, originalEvent.note),
|
||||||
@@ -389,17 +396,19 @@ export function createPatch(originalEvent: OntimeEvent, patchEvent: Partial<Onti
|
|||||||
/**
|
/**
|
||||||
* @description Enforces formatting for events
|
* @description Enforces formatting for events
|
||||||
* @param {object} eventArgs - attributes of event
|
* @param {object} eventArgs - attributes of event
|
||||||
* @param cueFallback
|
* @param {number} eventIndex - can be a string when we pass the a suggested cue name
|
||||||
* @returns {object|null} - formatted object or null in case is invalid
|
* @returns {object|null} - formatted object or null in case is invalid
|
||||||
*/
|
*/
|
||||||
export const createEvent = (eventArgs: Partial<OntimeEvent>, cueFallback: string): OntimeEvent | null => {
|
export const createEvent = (eventArgs: Partial<OntimeEvent>, eventIndex: number | string): OntimeEvent | null => {
|
||||||
if (Object.keys(eventArgs).length === 0) {
|
if (Object.keys(eventArgs).length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const cue = typeof eventIndex === 'number' ? String(eventIndex + 1) : eventIndex;
|
||||||
|
|
||||||
const baseEvent = {
|
const baseEvent = {
|
||||||
id: eventArgs?.id ?? generateId(),
|
id: eventArgs?.id ?? generateId(),
|
||||||
cue: cueFallback,
|
cue,
|
||||||
...eventDef,
|
...eventDef,
|
||||||
};
|
};
|
||||||
const event = createPatch(baseEvent, eventArgs);
|
const event = createPatch(baseEvent, eventArgs);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
OscSubscription,
|
OscSubscription,
|
||||||
ProjectData,
|
ProjectData,
|
||||||
Settings,
|
Settings,
|
||||||
|
TimerType,
|
||||||
URLPreset,
|
URLPreset,
|
||||||
ViewSettings,
|
ViewSettings,
|
||||||
isOntimeBlock,
|
isOntimeBlock,
|
||||||
@@ -19,13 +20,7 @@ import {
|
|||||||
isOntimeDelay,
|
isOntimeDelay,
|
||||||
isOntimeEvent,
|
isOntimeEvent,
|
||||||
} from 'ontime-types';
|
} from 'ontime-types';
|
||||||
import {
|
import { customFieldLabelToKey, generateId, getErrorMessage, isAlphanumericWithSpace } from 'ontime-utils';
|
||||||
customFieldLabelToKey,
|
|
||||||
generateId,
|
|
||||||
getErrorMessage,
|
|
||||||
getLastEvent,
|
|
||||||
isAlphanumericWithSpace,
|
|
||||||
} from 'ontime-utils';
|
|
||||||
|
|
||||||
import { dbModel } from '../models/dataModel.js';
|
import { dbModel } from '../models/dataModel.js';
|
||||||
import { block as blockDef, delay as delayDef } from '../models/eventsDefinition.js';
|
import { block as blockDef, delay as delayDef } from '../models/eventsDefinition.js';
|
||||||
@@ -52,6 +47,7 @@ export function parseRundown(
|
|||||||
|
|
||||||
const rundown: OntimeRundown = [];
|
const rundown: OntimeRundown = [];
|
||||||
let eventIndex = 0;
|
let eventIndex = 0;
|
||||||
|
let previousId: string | null = null;
|
||||||
const ids: string[] = [];
|
const ids: string[] = [];
|
||||||
|
|
||||||
for (const event of data.rundown) {
|
for (const event of data.rundown) {
|
||||||
@@ -64,12 +60,13 @@ export function parseRundown(
|
|||||||
let newEvent: OntimeEvent | OntimeDelay | OntimeBlock | null;
|
let newEvent: OntimeEvent | OntimeDelay | OntimeBlock | null;
|
||||||
|
|
||||||
if (isOntimeEvent(event)) {
|
if (isOntimeEvent(event)) {
|
||||||
|
const maybeEvent = runEventMigrations({ ...event, id });
|
||||||
|
|
||||||
if (event.linkStart) {
|
if (event.linkStart) {
|
||||||
const prevId = getLastEvent(rundown).lastEvent?.id ?? null;
|
maybeEvent.linkStart = previousId;
|
||||||
event.linkStart = prevId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
newEvent = createEvent(event, eventIndex.toString());
|
newEvent = createEvent(maybeEvent, eventIndex);
|
||||||
// skip if event is invalid
|
// skip if event is invalid
|
||||||
if (newEvent == null) {
|
if (newEvent == null) {
|
||||||
emitError?.('Skipping event without payload');
|
emitError?.('Skipping event without payload');
|
||||||
@@ -84,6 +81,7 @@ export function parseRundown(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
previousId = id;
|
||||||
eventIndex += 1;
|
eventIndex += 1;
|
||||||
} else if (isOntimeDelay(event)) {
|
} else if (isOntimeDelay(event)) {
|
||||||
newEvent = { ...delayDef, duration: event.duration, id };
|
newEvent = { ...delayDef, duration: event.duration, id };
|
||||||
@@ -349,3 +347,22 @@ export function sanitiseCustomFields(data: object): CustomFields {
|
|||||||
|
|
||||||
return newCustomFields;
|
return newCustomFields;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Time to end was moved from a TimerType to a standalone boolean
|
||||||
|
* Released as part of v3.10.0
|
||||||
|
*/
|
||||||
|
function migrateTimeToEnd(event: any): OntimeEvent {
|
||||||
|
if (event.timerType === 'time-to-end') {
|
||||||
|
event.timerType = TimerType.CountDown;
|
||||||
|
event.isTimeToEnd = true;
|
||||||
|
}
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mutating function migrates event data entries
|
||||||
|
*/
|
||||||
|
function runEventMigrations(event: any): OntimeEvent {
|
||||||
|
return migrateTimeToEnd(event);
|
||||||
|
}
|
||||||
|
|||||||
Vendored
BIN
Binary file not shown.
@@ -1,7 +1,6 @@
|
|||||||
export enum TimerType {
|
export enum TimerType {
|
||||||
CountDown = 'count-down',
|
CountDown = 'count-down',
|
||||||
CountUp = 'count-up',
|
CountUp = 'count-up',
|
||||||
TimeToEnd = 'time-to-end',
|
|
||||||
Clock = 'clock',
|
Clock = 'clock',
|
||||||
None = 'none',
|
None = 'none',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export type OntimeEvent = OntimeBaseEvent & {
|
|||||||
note: string;
|
note: string;
|
||||||
endAction: EndAction;
|
endAction: EndAction;
|
||||||
timerType: TimerType;
|
timerType: TimerType;
|
||||||
|
isTimeToEnd: boolean;
|
||||||
linkStart: MaybeString; // ID of event to link to
|
linkStart: MaybeString; // ID of event to link to
|
||||||
timeStrategy: TimeStrategy;
|
timeStrategy: TimeStrategy;
|
||||||
timeStart: number;
|
timeStart: number;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ describe('isImportMap()', () => {
|
|||||||
duration: 'duration',
|
duration: 'duration',
|
||||||
cue: 'cue',
|
cue: 'cue',
|
||||||
title: 'title',
|
title: 'title',
|
||||||
|
isTimeToEnd: 'time to end',
|
||||||
isPublic: 'public',
|
isPublic: 'public',
|
||||||
skip: 'skip',
|
skip: 'skip',
|
||||||
note: 'notes',
|
note: 'notes',
|
||||||
@@ -34,6 +35,7 @@ describe('isImportMap()', () => {
|
|||||||
duration: 'duration',
|
duration: 'duration',
|
||||||
cue: 'cue',
|
cue: 'cue',
|
||||||
title: 'title',
|
title: 'title',
|
||||||
|
isTimeToEnd: 'time to end',
|
||||||
isPublic: 'public',
|
isPublic: 'public',
|
||||||
skip: 'skip',
|
skip: 'skip',
|
||||||
note: 'notes',
|
note: 'notes',
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export const defaultImportMap = {
|
|||||||
duration: 'duration',
|
duration: 'duration',
|
||||||
cue: 'cue',
|
cue: 'cue',
|
||||||
title: 'title',
|
title: 'title',
|
||||||
|
isTimeToEnd: 'time to end',
|
||||||
isPublic: 'public',
|
isPublic: 'public',
|
||||||
skip: 'skip',
|
skip: 'skip',
|
||||||
note: 'notes',
|
note: 'notes',
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ describe('validateEndAction()', () => {
|
|||||||
|
|
||||||
describe('validateTimerType()', () => {
|
describe('validateTimerType()', () => {
|
||||||
it('recognises a string representation of an action', () => {
|
it('recognises a string representation of an action', () => {
|
||||||
const timerType = validateTimerType('time-to-end');
|
const timerType = validateTimerType('count-up');
|
||||||
expect(timerType).toBe(TimerType.TimeToEnd);
|
expect(timerType).toBe(TimerType.CountUp);
|
||||||
});
|
});
|
||||||
it('returns fallback otherwise', () => {
|
it('returns fallback otherwise', () => {
|
||||||
const emptyType = validateTimerType('', TimerType.Clock);
|
const emptyType = validateTimerType('', TimerType.Clock);
|
||||||
|
|||||||
Reference in New Issue
Block a user