mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-09 17:33:55 +00:00
Merge branch 'master' into port-colision
This commit is contained in:
@@ -3,6 +3,8 @@ import { Request, Response, NextFunction } from 'express';
|
||||
|
||||
export const rundownPostValidator = [
|
||||
body('type').isString().exists().isIn(['event', 'delay', 'block']),
|
||||
body('after').optional().isString(),
|
||||
body('before').optional().isString(),
|
||||
|
||||
(req: Request, res: Response, next: NextFunction) => {
|
||||
const errors = validationResult(req);
|
||||
|
||||
@@ -10,6 +10,7 @@ export const demoDb: DatabaseModel = {
|
||||
note: 'SF1.01',
|
||||
endAction: EndAction.None,
|
||||
timerType: TimerType.CountDown,
|
||||
isTimeToEnd: false,
|
||||
linkStart: null,
|
||||
timeStrategy: TimeStrategy.LockEnd,
|
||||
timeStart: 36000000,
|
||||
@@ -34,6 +35,7 @@ export const demoDb: DatabaseModel = {
|
||||
note: 'SF1.02',
|
||||
endAction: EndAction.None,
|
||||
timerType: TimerType.CountDown,
|
||||
isTimeToEnd: false,
|
||||
linkStart: null,
|
||||
timeStrategy: TimeStrategy.LockEnd,
|
||||
timeStart: 37500000,
|
||||
@@ -58,6 +60,7 @@ export const demoDb: DatabaseModel = {
|
||||
note: 'SF1.03',
|
||||
endAction: EndAction.None,
|
||||
timerType: TimerType.CountDown,
|
||||
isTimeToEnd: false,
|
||||
linkStart: null,
|
||||
timeStrategy: TimeStrategy.LockEnd,
|
||||
timeStart: 39000000,
|
||||
@@ -82,6 +85,7 @@ export const demoDb: DatabaseModel = {
|
||||
note: 'SF1.04',
|
||||
endAction: EndAction.None,
|
||||
timerType: TimerType.CountDown,
|
||||
isTimeToEnd: false,
|
||||
linkStart: null,
|
||||
timeStrategy: TimeStrategy.LockEnd,
|
||||
timeStart: 40500000,
|
||||
@@ -106,6 +110,7 @@ export const demoDb: DatabaseModel = {
|
||||
note: 'SF1.05',
|
||||
endAction: EndAction.None,
|
||||
timerType: TimerType.CountDown,
|
||||
isTimeToEnd: false,
|
||||
linkStart: null,
|
||||
timeStrategy: TimeStrategy.LockEnd,
|
||||
timeStart: 42000000,
|
||||
@@ -135,6 +140,7 @@ export const demoDb: DatabaseModel = {
|
||||
note: 'SF1.06',
|
||||
endAction: EndAction.None,
|
||||
timerType: TimerType.CountDown,
|
||||
isTimeToEnd: false,
|
||||
linkStart: null,
|
||||
timeStrategy: TimeStrategy.LockEnd,
|
||||
timeStart: 47100000,
|
||||
@@ -159,6 +165,7 @@ export const demoDb: DatabaseModel = {
|
||||
note: 'SF1.07',
|
||||
endAction: EndAction.None,
|
||||
timerType: TimerType.CountDown,
|
||||
isTimeToEnd: false,
|
||||
linkStart: null,
|
||||
timeStrategy: TimeStrategy.LockEnd,
|
||||
timeStart: 48600000,
|
||||
@@ -183,6 +190,7 @@ export const demoDb: DatabaseModel = {
|
||||
note: 'SF1.08',
|
||||
endAction: EndAction.None,
|
||||
timerType: TimerType.CountDown,
|
||||
isTimeToEnd: false,
|
||||
linkStart: null,
|
||||
timeStrategy: TimeStrategy.LockEnd,
|
||||
timeStart: 50100000,
|
||||
@@ -207,6 +215,7 @@ export const demoDb: DatabaseModel = {
|
||||
note: 'SF1.09',
|
||||
endAction: EndAction.None,
|
||||
timerType: TimerType.CountDown,
|
||||
isTimeToEnd: false,
|
||||
linkStart: null,
|
||||
timeStrategy: TimeStrategy.LockEnd,
|
||||
timeStart: 51600000,
|
||||
@@ -231,6 +240,7 @@ export const demoDb: DatabaseModel = {
|
||||
note: 'SF1.10',
|
||||
endAction: EndAction.None,
|
||||
timerType: TimerType.CountDown,
|
||||
isTimeToEnd: false,
|
||||
linkStart: null,
|
||||
timeStrategy: TimeStrategy.LockEnd,
|
||||
timeStart: 53100000,
|
||||
@@ -260,6 +270,7 @@ export const demoDb: DatabaseModel = {
|
||||
note: 'SF1.11',
|
||||
endAction: EndAction.None,
|
||||
timerType: TimerType.CountDown,
|
||||
isTimeToEnd: false,
|
||||
linkStart: null,
|
||||
timeStrategy: TimeStrategy.LockEnd,
|
||||
timeStart: 56100000,
|
||||
@@ -284,9 +295,9 @@ export const demoDb: DatabaseModel = {
|
||||
note: 'SF1.12',
|
||||
endAction: EndAction.None,
|
||||
timerType: TimerType.CountDown,
|
||||
isTimeToEnd: false,
|
||||
linkStart: null,
|
||||
timeStrategy: TimeStrategy.LockEnd,
|
||||
|
||||
timeStart: 57600000,
|
||||
timeEnd: 58800000,
|
||||
duration: 1200000,
|
||||
@@ -309,6 +320,7 @@ export const demoDb: DatabaseModel = {
|
||||
note: 'SF1.13',
|
||||
endAction: EndAction.None,
|
||||
timerType: TimerType.CountDown,
|
||||
isTimeToEnd: false,
|
||||
linkStart: null,
|
||||
timeStrategy: TimeStrategy.LockEnd,
|
||||
timeStart: 59100000,
|
||||
@@ -333,6 +345,7 @@ export const demoDb: DatabaseModel = {
|
||||
note: 'SF1.14',
|
||||
endAction: EndAction.None,
|
||||
timerType: TimerType.CountDown,
|
||||
isTimeToEnd: false,
|
||||
linkStart: null,
|
||||
timeStrategy: TimeStrategy.LockEnd,
|
||||
timeStart: 60600000,
|
||||
|
||||
@@ -15,6 +15,7 @@ export const event: Omit<OntimeEvent, 'id' | 'delay' | 'cue'> = {
|
||||
timerType: TimerType.CountDown,
|
||||
timeStrategy: TimeStrategy.LockDuration,
|
||||
linkStart: null,
|
||||
isTimeToEnd: false,
|
||||
timeStart: 0,
|
||||
timeEnd: 0,
|
||||
duration: 0,
|
||||
|
||||
@@ -175,7 +175,7 @@ describe('getExpectedFinish()', () => {
|
||||
const state = {
|
||||
eventNow: {
|
||||
timeEnd: 30,
|
||||
timerType: TimerType.TimeToEnd,
|
||||
isTimeToEnd: true,
|
||||
},
|
||||
timer: {
|
||||
addedTime: 10,
|
||||
@@ -195,7 +195,7 @@ describe('getExpectedFinish()', () => {
|
||||
const state = {
|
||||
eventNow: {
|
||||
timeEnd: 600000, // 00:10:00
|
||||
timerType: TimerType.TimeToEnd,
|
||||
isTimeToEnd: true,
|
||||
},
|
||||
timer: {
|
||||
addedTime: 0,
|
||||
@@ -351,7 +351,7 @@ describe('getCurrent()', () => {
|
||||
const state = {
|
||||
eventNow: {
|
||||
timeEnd: 100,
|
||||
timerType: TimerType.TimeToEnd,
|
||||
isTimeToEnd: true,
|
||||
},
|
||||
clock: 30,
|
||||
timer: {
|
||||
@@ -376,7 +376,7 @@ describe('getCurrent()', () => {
|
||||
const state = {
|
||||
eventNow: {
|
||||
timeEnd: 100,
|
||||
timerType: TimerType.TimeToEnd,
|
||||
isTimeToEnd: true,
|
||||
},
|
||||
clock: 30,
|
||||
timer: {
|
||||
@@ -401,7 +401,7 @@ describe('getCurrent()', () => {
|
||||
const state = {
|
||||
eventNow: {
|
||||
timeEnd: 100,
|
||||
timerType: TimerType.TimeToEnd,
|
||||
isTimeToEnd: true,
|
||||
},
|
||||
clock: 30,
|
||||
timer: {
|
||||
@@ -427,7 +427,7 @@ describe('getCurrent()', () => {
|
||||
eventNow: {
|
||||
timeStart: 79200000, // 22:00:00
|
||||
timeEnd: 600000, // 00:10:00
|
||||
timerType: TimerType.TimeToEnd,
|
||||
isTimeToEnd: true,
|
||||
},
|
||||
clock: 79500000, // 22:05:00
|
||||
timer: {
|
||||
@@ -456,7 +456,7 @@ describe('getCurrent()', () => {
|
||||
timeStart: 77400000, // 21:30:00
|
||||
timeEnd: 81000000, // 22:30:00
|
||||
duration: 3600000, // 01:00:00
|
||||
timerType: TimerType.TimeToEnd,
|
||||
isTimeToEnd: true,
|
||||
},
|
||||
timer: {
|
||||
addedTime: 0,
|
||||
@@ -909,7 +909,8 @@ describe('getRuntimeOffset()', () => {
|
||||
timeStrategy: TimeStrategy.LockEnd,
|
||||
linkStart: null,
|
||||
endAction: EndAction.None,
|
||||
timerType: TimerType.TimeToEnd,
|
||||
timerType: TimerType.CountDown,
|
||||
isTimeToEnd: true,
|
||||
isPublic: true,
|
||||
skip: false,
|
||||
note: '',
|
||||
@@ -961,7 +962,8 @@ describe('getRuntimeOffset()', () => {
|
||||
timeStrategy: TimeStrategy.LockEnd,
|
||||
linkStart: null,
|
||||
endAction: EndAction.None,
|
||||
timerType: TimerType.TimeToEnd,
|
||||
timerType: TimerType.CountDown,
|
||||
isTimeToEnd: true,
|
||||
isPublic: true,
|
||||
skip: false,
|
||||
note: '',
|
||||
@@ -1011,7 +1013,8 @@ describe('getRuntimeOffset()', () => {
|
||||
timeStrategy: TimeStrategy.LockEnd,
|
||||
linkStart: null,
|
||||
endAction: EndAction.None,
|
||||
timerType: TimerType.TimeToEnd, // <--- but this is time to end
|
||||
timerType: TimerType.CountDown,
|
||||
isTimeToEnd: true,
|
||||
},
|
||||
runtime: {
|
||||
selectedEventIndex: 0,
|
||||
|
||||
@@ -9,6 +9,8 @@ import {
|
||||
isOntimeDelay,
|
||||
isOntimeEvent,
|
||||
OntimeRundown,
|
||||
PatchWithId,
|
||||
EventPostPayload,
|
||||
} from 'ontime-types';
|
||||
import { getCueCandidate } from 'ontime-utils';
|
||||
|
||||
@@ -22,8 +24,6 @@ import { runtimeService } from '../runtime-service/RuntimeService.js';
|
||||
import * as cache from './rundownCache.js';
|
||||
import { getPlayableEvents, getTimedEvents } from './rundownUtils.js';
|
||||
|
||||
type PatchWithId = (Partial<OntimeEvent> | Partial<OntimeBlock> | Partial<OntimeDelay>) & { id: string };
|
||||
|
||||
type CompleteEntry<T> =
|
||||
T extends Partial<OntimeEvent>
|
||||
? OntimeEvent
|
||||
@@ -35,12 +35,13 @@ type CompleteEntry<T> =
|
||||
|
||||
function generateEvent<T extends Partial<OntimeEvent> | Partial<OntimeDelay> | Partial<OntimeBlock>>(
|
||||
eventData: T,
|
||||
afterId?: string,
|
||||
): CompleteEntry<T> {
|
||||
// we discard any UI provided IDs and add our own
|
||||
const id = cache.getUniqueId();
|
||||
|
||||
if (isOntimeEvent(eventData)) {
|
||||
return createEvent(eventData, getCueCandidate(cache.getPersistedRundown(), eventData?.after)) as CompleteEntry<T>;
|
||||
return createEvent(eventData, getCueCandidate(cache.getPersistedRundown(), afterId)) as CompleteEntry<T>;
|
||||
}
|
||||
|
||||
if (isOntimeDelay(eventData)) {
|
||||
@@ -59,9 +60,11 @@ function generateEvent<T extends Partial<OntimeEvent> | Partial<OntimeDelay> | P
|
||||
* @param {object} eventData
|
||||
* @return {OntimeRundownEntry}
|
||||
*/
|
||||
export async function addEvent(eventData: PatchWithId & { after?: string }): Promise<OntimeRundownEntry> {
|
||||
export async function addEvent(eventData: EventPostPayload): Promise<OntimeRundownEntry> {
|
||||
// if the user didnt provide an index, we add the event to start
|
||||
let atIndex = 0;
|
||||
let afterId: string | undefined = eventData?.after;
|
||||
|
||||
if (eventData?.after !== undefined) {
|
||||
const previousIndex = cache.getIndexOf(eventData.after);
|
||||
if (previousIndex < 0) {
|
||||
@@ -69,10 +72,20 @@ export async function addEvent(eventData: PatchWithId & { after?: string }): Pro
|
||||
} else {
|
||||
atIndex = previousIndex + 1;
|
||||
}
|
||||
} else if (eventData?.before !== undefined) {
|
||||
const previousIndex = cache.getIndexOf(eventData.before);
|
||||
if (previousIndex < 0) {
|
||||
logger.warning(LogOrigin.Server, `Could not find event with id ${eventData.before}`);
|
||||
} else {
|
||||
atIndex = previousIndex;
|
||||
if (previousIndex > 0) {
|
||||
afterId = cache.getPersistedRundown()[atIndex - 1].id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// generate a fully formed event from the patch
|
||||
const eventToAdd = generateEvent(eventData);
|
||||
const eventToAdd = generateEvent(eventData, afterId);
|
||||
|
||||
// modify rundown
|
||||
const scopedMutation = cache.mutateCache(cache.add);
|
||||
|
||||
@@ -565,6 +565,7 @@ describe('calculateRuntimeDelays', () => {
|
||||
note: '',
|
||||
endAction: EndAction.None,
|
||||
timerType: TimerType.CountDown,
|
||||
isTimeToEnd: false,
|
||||
timeStrategy: TimeStrategy.LockEnd,
|
||||
linkStart: null,
|
||||
timeStart: 600000,
|
||||
@@ -591,6 +592,7 @@ describe('calculateRuntimeDelays', () => {
|
||||
note: '',
|
||||
endAction: EndAction.None,
|
||||
timerType: TimerType.CountDown,
|
||||
isTimeToEnd: false,
|
||||
timeStrategy: TimeStrategy.LockEnd,
|
||||
linkStart: null,
|
||||
timeStart: 1200000,
|
||||
@@ -617,6 +619,7 @@ describe('calculateRuntimeDelays', () => {
|
||||
note: '',
|
||||
endAction: EndAction.None,
|
||||
timerType: TimerType.CountDown,
|
||||
isTimeToEnd: false,
|
||||
timeStrategy: TimeStrategy.LockEnd,
|
||||
linkStart: null,
|
||||
timeStart: 600000,
|
||||
@@ -643,6 +646,7 @@ describe('calculateRuntimeDelays', () => {
|
||||
note: '',
|
||||
endAction: EndAction.None,
|
||||
timerType: TimerType.CountDown,
|
||||
isTimeToEnd: false,
|
||||
timeStrategy: TimeStrategy.LockEnd,
|
||||
linkStart: null,
|
||||
timeStart: 1200000,
|
||||
@@ -678,6 +682,7 @@ describe('getDelayAt()', () => {
|
||||
note: '',
|
||||
endAction: EndAction.None,
|
||||
timerType: TimerType.CountDown,
|
||||
isTimeToEnd: false,
|
||||
timeStrategy: TimeStrategy.LockEnd,
|
||||
linkStart: null,
|
||||
timeStart: 600000,
|
||||
@@ -705,6 +710,7 @@ describe('getDelayAt()', () => {
|
||||
note: '',
|
||||
endAction: EndAction.None,
|
||||
timerType: TimerType.CountDown,
|
||||
isTimeToEnd: false,
|
||||
timeStrategy: TimeStrategy.LockEnd,
|
||||
linkStart: null,
|
||||
timeStart: 1200000,
|
||||
@@ -732,6 +738,7 @@ describe('getDelayAt()', () => {
|
||||
note: '',
|
||||
endAction: EndAction.None,
|
||||
timerType: TimerType.CountDown,
|
||||
isTimeToEnd: false,
|
||||
timeStrategy: TimeStrategy.LockEnd,
|
||||
linkStart: null,
|
||||
timeStart: 600000,
|
||||
@@ -759,6 +766,7 @@ describe('getDelayAt()', () => {
|
||||
note: '',
|
||||
endAction: EndAction.None,
|
||||
timerType: TimerType.CountDown,
|
||||
isTimeToEnd: false,
|
||||
timeStrategy: TimeStrategy.LockEnd,
|
||||
linkStart: null,
|
||||
timeStart: 1200000,
|
||||
@@ -812,6 +820,7 @@ describe('calculateRuntimeDelaysFrom()', () => {
|
||||
note: '',
|
||||
endAction: EndAction.None,
|
||||
timerType: TimerType.CountDown,
|
||||
isTimeToEnd: false,
|
||||
timeStrategy: TimeStrategy.LockEnd,
|
||||
linkStart: null,
|
||||
timeStart: 600000,
|
||||
@@ -839,6 +848,7 @@ describe('calculateRuntimeDelaysFrom()', () => {
|
||||
note: '',
|
||||
endAction: EndAction.None,
|
||||
timerType: TimerType.CountDown,
|
||||
isTimeToEnd: false,
|
||||
timeStrategy: TimeStrategy.LockEnd,
|
||||
linkStart: null,
|
||||
timeStart: 1200000,
|
||||
@@ -866,6 +876,7 @@ describe('calculateRuntimeDelaysFrom()', () => {
|
||||
note: '',
|
||||
endAction: EndAction.None,
|
||||
timerType: TimerType.CountDown,
|
||||
isTimeToEnd: false,
|
||||
timeStrategy: TimeStrategy.LockEnd,
|
||||
linkStart: null,
|
||||
timeStart: 600000,
|
||||
@@ -893,6 +904,7 @@ describe('calculateRuntimeDelaysFrom()', () => {
|
||||
note: '',
|
||||
endAction: EndAction.None,
|
||||
timerType: TimerType.CountDown,
|
||||
isTimeToEnd: false,
|
||||
timeStrategy: TimeStrategy.LockEnd,
|
||||
linkStart: null,
|
||||
timeStart: 1200000,
|
||||
|
||||
@@ -31,6 +31,7 @@ describe('cellRequestFromEvent()', () => {
|
||||
linkStart: null,
|
||||
endAction: EndAction.None,
|
||||
timerType: TimerType.CountDown,
|
||||
isTimeToEnd: false,
|
||||
duration: 10800000,
|
||||
isPublic: false,
|
||||
skip: false,
|
||||
@@ -73,6 +74,7 @@ describe('cellRequestFromEvent()', () => {
|
||||
timeEnd: 57600000,
|
||||
endAction: EndAction.None,
|
||||
timerType: TimerType.CountDown,
|
||||
isTimeToEnd: false,
|
||||
duration: 10800000,
|
||||
timeStrategy: TimeStrategy.LockEnd,
|
||||
linkStart: null,
|
||||
@@ -119,6 +121,7 @@ describe('cellRequestFromEvent()', () => {
|
||||
timeEnd: 57600000,
|
||||
endAction: EndAction.None,
|
||||
timerType: TimerType.CountDown,
|
||||
isTimeToEnd: false,
|
||||
duration: 10800000,
|
||||
timeStrategy: TimeStrategy.LockEnd,
|
||||
linkStart: null,
|
||||
@@ -164,6 +167,7 @@ describe('cellRequestFromEvent()', () => {
|
||||
timeEnd: 57600000,
|
||||
endAction: EndAction.None,
|
||||
timerType: TimerType.CountDown,
|
||||
isTimeToEnd: false,
|
||||
timeStrategy: TimeStrategy.LockEnd,
|
||||
linkStart: null,
|
||||
duration: 10800000,
|
||||
@@ -195,6 +199,7 @@ describe('cellRequestFromEvent()', () => {
|
||||
timeEnd: 57600000,
|
||||
endAction: EndAction.None,
|
||||
timerType: TimerType.CountDown,
|
||||
isTimeToEnd: false,
|
||||
duration: 10800000,
|
||||
timeStrategy: TimeStrategy.LockEnd,
|
||||
linkStart: null,
|
||||
@@ -227,6 +232,7 @@ describe('cellRequestFromEvent()', () => {
|
||||
timeEnd: 57600000,
|
||||
endAction: EndAction.None,
|
||||
timerType: TimerType.CountDown,
|
||||
isTimeToEnd: false,
|
||||
duration: 10800000,
|
||||
timeStrategy: TimeStrategy.LockEnd,
|
||||
linkStart: null,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { MaybeNumber, Playback, TimerPhase, TimerType } from 'ontime-types';
|
||||
import { dayInMs } from 'ontime-utils';
|
||||
import { MaybeNumber, TimerPhase } from 'ontime-types';
|
||||
import { dayInMs, isPlaybackActive } from 'ontime-utils';
|
||||
import { RuntimeState } from '../stores/runtimeState.js';
|
||||
|
||||
/**
|
||||
@@ -19,7 +19,7 @@ export function getExpectedFinish(state: RuntimeState): MaybeNumber {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { timerType, timeEnd } = state.eventNow;
|
||||
const { isTimeToEnd, timeEnd } = state.eventNow;
|
||||
const { pausedAt } = state._timer;
|
||||
const { clock } = state;
|
||||
|
||||
@@ -33,7 +33,7 @@ export function getExpectedFinish(state: RuntimeState): MaybeNumber {
|
||||
|
||||
const pausedTime = pausedAt != null ? clock - pausedAt : 0;
|
||||
|
||||
if (timerType === TimerType.TimeToEnd) {
|
||||
if (isTimeToEnd) {
|
||||
return timeEnd + addedTime + pausedTime;
|
||||
}
|
||||
|
||||
@@ -62,11 +62,11 @@ export function getCurrent(state: RuntimeState): number {
|
||||
}
|
||||
}
|
||||
const { startedAt, duration, addedTime } = state.timer;
|
||||
const { timerType, timeStart, timeEnd } = state.eventNow;
|
||||
const { isTimeToEnd, timeStart, timeEnd } = state.eventNow;
|
||||
const { pausedAt } = state._timer;
|
||||
const { clock } = state;
|
||||
|
||||
if (timerType === TimerType.TimeToEnd) {
|
||||
if (isTimeToEnd) {
|
||||
const isEventOverMidnight = timeStart > timeEnd;
|
||||
const correctDay = isEventOverMidnight ? dayInMs : 0;
|
||||
return correctDay - clock + timeEnd + addedTime;
|
||||
@@ -131,7 +131,7 @@ export function getRuntimeOffset(state: RuntimeState): number {
|
||||
}
|
||||
|
||||
const { clock } = state;
|
||||
const { timeStart, timerType } = state.eventNow;
|
||||
const { isTimeToEnd, timeStart } = state.eventNow;
|
||||
const { addedTime, current, startedAt } = state.timer;
|
||||
|
||||
// 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);
|
||||
// in time-to-end, offset is overtime
|
||||
if (timerType === TimerType.TimeToEnd) {
|
||||
if (isTimeToEnd) {
|
||||
return overtime;
|
||||
}
|
||||
|
||||
@@ -187,25 +187,12 @@ export function getExpectedEnd(state: RuntimeState): MaybeNumber {
|
||||
return state.runtime.plannedEnd - state.runtime.offset + state._timer.totalDelay;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility checks whether the playback is considered to be active
|
||||
* @param state
|
||||
* @returns
|
||||
*/
|
||||
export function isPlaybackActive(state: RuntimeState): boolean {
|
||||
return (
|
||||
state.timer.playback === Playback.Play ||
|
||||
state.timer.playback === Playback.Pause ||
|
||||
state.timer.playback === Playback.Roll
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks running timer to see which phase it currently is in
|
||||
* @param state
|
||||
*/
|
||||
export function getTimerPhase(state: RuntimeState): TimerPhase {
|
||||
if (!isPlaybackActive(state)) {
|
||||
if (!isPlaybackActive(state.timer.playback)) {
|
||||
return TimerPhase.None;
|
||||
}
|
||||
|
||||
|
||||
@@ -175,6 +175,7 @@ describe('mutation on runtimeState', () => {
|
||||
expect(newState.runtime.plannedStart).toBe(0);
|
||||
expect(newState.runtime.plannedEnd).toBe(1500);
|
||||
expect(newState.currentBlock.block).toBeNull();
|
||||
expect(newState.runtime.offset).toBe(0);
|
||||
|
||||
// 2. Start event
|
||||
start();
|
||||
|
||||
@@ -11,7 +11,14 @@ import {
|
||||
TimerPhase,
|
||||
TimerState,
|
||||
} from 'ontime-types';
|
||||
import { calculateDuration, checkIsNow, dayInMs, filterTimedEvents, getPreviousBlock } from 'ontime-utils';
|
||||
import {
|
||||
calculateDuration,
|
||||
checkIsNow,
|
||||
dayInMs,
|
||||
filterTimedEvents,
|
||||
getPreviousBlock,
|
||||
isPlaybackActive,
|
||||
} from 'ontime-utils';
|
||||
|
||||
import { clock } from '../services/Clock.js';
|
||||
import { RestorePoint } from '../services/RestoreService.js';
|
||||
@@ -21,7 +28,6 @@ import {
|
||||
getExpectedFinish,
|
||||
getRuntimeOffset,
|
||||
getTimerPhase,
|
||||
isPlaybackActive,
|
||||
} from '../services/timerUtils.js';
|
||||
import { timerConfig } from '../config/config.js';
|
||||
import { loadRoll, normaliseRollStart } from '../services/rollUtils.js';
|
||||
@@ -485,7 +491,7 @@ export function update(): UpdateResult {
|
||||
runtimeState.clock = clock.timeNow(); // we update the clock on every update call
|
||||
|
||||
// 1. is playback idle?
|
||||
if (!isPlaybackActive(runtimeState)) {
|
||||
if (!isPlaybackActive(runtimeState.timer.playback)) {
|
||||
return updateIfIdle();
|
||||
}
|
||||
|
||||
|
||||
@@ -463,7 +463,7 @@ describe('test event validator', () => {
|
||||
const event = {
|
||||
title: 'test',
|
||||
};
|
||||
const validated = createEvent(event, 'test');
|
||||
const validated = createEvent(event, 1);
|
||||
|
||||
expect(validated).toEqual(
|
||||
expect.objectContaining({
|
||||
@@ -471,12 +471,13 @@ describe('test event validator', () => {
|
||||
note: expect.any(String),
|
||||
timeStart: expect.any(Number),
|
||||
timeEnd: expect.any(Number),
|
||||
isTimeToEnd: expect.any(Boolean),
|
||||
isPublic: expect.any(Boolean),
|
||||
skip: expect.any(Boolean),
|
||||
revision: expect.any(Number),
|
||||
type: expect.any(String),
|
||||
id: expect.any(String),
|
||||
cue: 'test',
|
||||
cue: '2',
|
||||
colour: expect.any(String),
|
||||
custom: expect.any(Object),
|
||||
}),
|
||||
@@ -485,7 +486,7 @@ describe('test event validator', () => {
|
||||
|
||||
it('fails an empty object', () => {
|
||||
const event = {};
|
||||
const validated = createEvent(event, 'none');
|
||||
const validated = createEvent(event, 1);
|
||||
expect(validated).toEqual(null);
|
||||
});
|
||||
|
||||
@@ -495,7 +496,7 @@ describe('test event validator', () => {
|
||||
note: '1899-12-30T08:00:10.000Z',
|
||||
};
|
||||
// @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) {
|
||||
throw new Error('unexpected value');
|
||||
}
|
||||
@@ -774,6 +775,7 @@ describe('getCustomFieldData()', () => {
|
||||
duration: 'duration',
|
||||
cue: 'cue',
|
||||
title: 'title',
|
||||
isTimeToEnd: 'time to end',
|
||||
isPublic: 'public',
|
||||
skip: 'skip',
|
||||
note: 'notes',
|
||||
@@ -824,6 +826,7 @@ describe('getCustomFieldData()', () => {
|
||||
duration: 'duration',
|
||||
cue: 'cue',
|
||||
title: 'title',
|
||||
isTimeToEnd: 'time to end',
|
||||
isPublic: 'public',
|
||||
skip: 'skip',
|
||||
note: 'notes',
|
||||
@@ -883,6 +886,7 @@ describe('parseExcel()', () => {
|
||||
'Title',
|
||||
'End Action',
|
||||
'Timer type',
|
||||
'Time to end',
|
||||
'Public',
|
||||
'Skip',
|
||||
'Notes',
|
||||
@@ -905,7 +909,8 @@ describe('parseExcel()', () => {
|
||||
'Guest Welcome',
|
||||
'',
|
||||
'',
|
||||
'x',
|
||||
'x', // <-- time to end
|
||||
'x', // <-- public
|
||||
'',
|
||||
'Ballyhoo',
|
||||
'a0',
|
||||
@@ -927,7 +932,8 @@ describe('parseExcel()', () => {
|
||||
'A song from the hearth',
|
||||
'load-next',
|
||||
'clock',
|
||||
'',
|
||||
'x', // <-- time to end
|
||||
'', // <-- public
|
||||
'x',
|
||||
'Rainbow chase',
|
||||
'b0',
|
||||
@@ -971,6 +977,7 @@ describe('parseExcel()', () => {
|
||||
timerType: 'count-down',
|
||||
endAction: 'none',
|
||||
isPublic: true,
|
||||
isTimeToEnd: true,
|
||||
skip: false,
|
||||
note: 'Ballyhoo',
|
||||
custom: {
|
||||
@@ -994,6 +1001,7 @@ describe('parseExcel()', () => {
|
||||
timeEnd: 30600000,
|
||||
title: 'A song from the hearth',
|
||||
timerType: 'clock',
|
||||
isTimeToEnd: true,
|
||||
endAction: 'load-next',
|
||||
isPublic: false,
|
||||
skip: true,
|
||||
@@ -1454,12 +1462,17 @@ describe('parseExcel()', () => {
|
||||
timeDanger: 'danger time',
|
||||
custom: {},
|
||||
};
|
||||
|
||||
const result = parseExcel(testdata, {}, importMap);
|
||||
expect(result.rundown.length).toBe(2);
|
||||
expect((result.rundown.at(0) as OntimeEvent).type).toBe(SupportedEvent.Event);
|
||||
expect((result.rundown.at(0) as OntimeEvent).timerType).toBe(TimerType.CountDown);
|
||||
expect((result.rundown.at(1) as OntimeEvent).type).toBe(SupportedEvent.Event);
|
||||
expect((result.rundown.at(1) as OntimeEvent).timerType).toBe(TimerType.CountDown);
|
||||
expect(result.rundown[0]).toMatchObject({
|
||||
type: SupportedEvent.Event,
|
||||
timerType: 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', () => {
|
||||
@@ -1659,7 +1672,7 @@ describe('parseExcel()', () => {
|
||||
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 = [
|
||||
[
|
||||
'Time Start',
|
||||
@@ -1675,11 +1688,11 @@ describe('parseExcel()', () => {
|
||||
'Timer type',
|
||||
],
|
||||
['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'],
|
||||
['10:00:00', '16:36:00', 'D', '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'],
|
||||
['9:45:00', '10:56:00', 'B', 'load-next', 'x', '', 'Rainbow chase', '#0F0', 103, 'x', 'count-down'],
|
||||
['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', 'D', 'load-next', 'x', '', 'Rainbow chase', '#0F0', 103, '', 'count-down'],
|
||||
['', '', '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 firstId = order.at(0); // A
|
||||
const secondId = order.at(1); // C
|
||||
const thirdId = order.at(2); // D
|
||||
const fourthId = order.at(3); // E
|
||||
const fifhtId = order.at(4); // Block
|
||||
const sixthId = order.at(5); // G
|
||||
const secondId = order.at(1); // B
|
||||
const thirdId = order.at(2); // C
|
||||
const fourthId = order.at(3); // D
|
||||
const fifthId = order.at(4); // Block
|
||||
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');
|
||||
}
|
||||
|
||||
expect((rundown[firstId] as OntimeEvent).timeStart).toEqual(16200000);
|
||||
|
||||
expect((rundown[secondId] as OntimeEvent).timeStart).toEqual((rundown[firstId] as OntimeEvent).timeEnd);
|
||||
expect((rundown[secondId] as OntimeEvent).linkStart).toEqual((rundown[firstId] as OntimeEvent).id);
|
||||
|
||||
expect((rundown[thirdId] as OntimeEvent).timeStart).toEqual((rundown[secondId] as OntimeEvent).timeEnd);
|
||||
expect((rundown[thirdId] as OntimeEvent).linkStart).toEqual((rundown[secondId] as OntimeEvent).id);
|
||||
|
||||
expect((rundown[fourthId] as OntimeEvent).timeStart).toEqual(78300000);
|
||||
|
||||
expect((rundown[fifhtId] as OntimeEvent).type).toEqual(SupportedEvent.Block);
|
||||
|
||||
expect((rundown[sixthId] as OntimeEvent).timeStart).toEqual((rundown[fourthId] as OntimeEvent).timeEnd);
|
||||
expect((rundown[sixthId] as OntimeEvent).linkStart).toEqual((rundown[fourthId] as OntimeEvent).id);
|
||||
expect(rundown).toMatchObject({
|
||||
[firstId]: {
|
||||
title: 'A',
|
||||
timeStart: 16200000,
|
||||
},
|
||||
[secondId]: {
|
||||
title: 'B',
|
||||
timeStart: (rundown[firstId] as OntimeEvent).timeEnd,
|
||||
linkStart: (rundown[firstId] as OntimeEvent).id,
|
||||
},
|
||||
[thirdId]: {
|
||||
title: 'C',
|
||||
timeStart: (rundown[secondId] as OntimeEvent).timeEnd,
|
||||
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', () => {
|
||||
@@ -1762,7 +1791,7 @@ describe('parseExcel()', () => {
|
||||
'false',
|
||||
'Setup',
|
||||
'',
|
||||
'time-to-end',
|
||||
'count-down',
|
||||
'none',
|
||||
'15',
|
||||
'00:05:00',
|
||||
@@ -1778,7 +1807,7 @@ describe('parseExcel()', () => {
|
||||
'false',
|
||||
'Meeting 1',
|
||||
'',
|
||||
'time-to-end',
|
||||
'count-down',
|
||||
'none',
|
||||
15,
|
||||
'00:05:00',
|
||||
@@ -1794,7 +1823,7 @@ describe('parseExcel()', () => {
|
||||
'false',
|
||||
'Meeting 2',
|
||||
'',
|
||||
'time-to-end',
|
||||
'count-down',
|
||||
'none',
|
||||
'13',
|
||||
'5',
|
||||
@@ -1810,7 +1839,7 @@ describe('parseExcel()', () => {
|
||||
'true',
|
||||
'Lunch',
|
||||
'',
|
||||
'time-to-end',
|
||||
'count-down',
|
||||
'none',
|
||||
13,
|
||||
5,
|
||||
@@ -1839,26 +1868,46 @@ describe('parseExcel()', () => {
|
||||
const parsedData = parseExcel(testData, {});
|
||||
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
|
||||
// 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
|
||||
expect((rundown.at(2) as OntimeEvent).duration).toBe(60 * MILLIS_PER_MINUTE);
|
||||
expect((rundown.at(2) as OntimeEvent).timeDanger).toBe(5 * MILLIS_PER_MINUTE);
|
||||
expect(rundown[2]).toMatchObject({
|
||||
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.at(3) as OntimeEvent).timeDanger).toBe(5 * MILLIS_PER_MINUTE);
|
||||
expect(rundown[3]).toMatchObject({
|
||||
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.at(4) as OntimeEvent).linkStart).toBe(false);
|
||||
expect((rundown.at(4) as OntimeEvent).timeWarning).toBe(11 * MILLIS_PER_MINUTE);
|
||||
expect((rundown.at(4) as OntimeEvent).timeDanger).toBe(5 * MILLIS_PER_MINUTE);
|
||||
expect(rundown[4]).toMatchObject({
|
||||
cue: 'MEET3',
|
||||
duration: 90 * 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);
|
||||
|
||||
// if we get a boolean, we should just use that
|
||||
expect((rundown.at(5) as OntimeEvent).linkStart).toBe(true);
|
||||
expect((rundown.at(5) as OntimeEvent).timeWarning).toBe(11 * MILLIS_PER_MINUTE);
|
||||
expect(rundown[5]).toMatchObject({
|
||||
cue: 'MEET4',
|
||||
duration: 30 * MILLIS_PER_MINUTE,
|
||||
timeWarning: 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', () => {
|
||||
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', () => {
|
||||
const data: Partial<DatabaseModel> = {
|
||||
rundown: [
|
||||
@@ -445,12 +423,11 @@ describe('parseRundown() linking', () => {
|
||||
customFields: {},
|
||||
};
|
||||
|
||||
const expected: OntimeRundown = [
|
||||
{ ...blankEvent, id: '1', cue: '0' },
|
||||
{ ...blankEvent, id: '2', cue: '1', linkStart: '1' },
|
||||
];
|
||||
const result = parseRundown(data);
|
||||
expect(result.rundown).toEqual(expected);
|
||||
expect(result.rundown[1]).toMatchObject({
|
||||
id: '2',
|
||||
linkStart: '1',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns unlinked if no previous', () => {
|
||||
@@ -466,9 +443,11 @@ describe('parseRundown() linking', () => {
|
||||
customFields: {},
|
||||
};
|
||||
|
||||
const expected: OntimeRundown = [{ ...blankEvent, id: '2', cue: '0' }];
|
||||
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', () => {
|
||||
@@ -505,14 +484,65 @@ describe('parseRundown() linking', () => {
|
||||
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);
|
||||
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,
|
||||
DatabaseModel,
|
||||
EventCustomFields,
|
||||
isOntimeBlock,
|
||||
LogOrigin,
|
||||
OntimeBlock,
|
||||
OntimeEvent,
|
||||
OntimeRundown,
|
||||
SupportedEvent,
|
||||
@@ -110,6 +110,7 @@ export const parseExcel = (
|
||||
// options: booleans
|
||||
let isPublicIndex: number | null = null;
|
||||
let skipIndex: number | null = null;
|
||||
let isTimeToEndIndex: number | null = null;
|
||||
|
||||
let linkStartIndex: number | null = null;
|
||||
|
||||
@@ -159,6 +160,10 @@ export const parseExcel = (
|
||||
titleIndex = col;
|
||||
rundownMetadata['title'] = { row, col };
|
||||
},
|
||||
[importMap.isTimeToEnd]: (row: number, col: number) => {
|
||||
isTimeToEndIndex = col;
|
||||
rundownMetadata['isTimeToEnd'] = { row, col };
|
||||
},
|
||||
[importMap.isPublic]: (row: number, col: number) => {
|
||||
isPublicIndex = col;
|
||||
rundownMetadata['isPublic'] = { row, col };
|
||||
@@ -226,6 +231,8 @@ export const parseExcel = (
|
||||
event.duration = parseExcelDate(column);
|
||||
} else if (j === cueIndex) {
|
||||
event.cue = makeString(column, '');
|
||||
} else if (j === isTimeToEndIndex) {
|
||||
event.isTimeToEnd = parseBooleanString(column);
|
||||
} else if (j === isPublicIndex) {
|
||||
event.isPublic = parseBooleanString(column);
|
||||
} else if (j === skipIndex) {
|
||||
@@ -273,8 +280,8 @@ export const parseExcel = (
|
||||
const keysFound = Object.keys(event).length + Object.keys(eventCustomFields).length;
|
||||
if (keysFound > 0) {
|
||||
// if it is a Block type drop all other filed
|
||||
if (event.type === SupportedEvent.Block) {
|
||||
rundown.push({ type: event.type, id: event.id, title: event.title } as OntimeBlock);
|
||||
if (isOntimeBlock(event)) {
|
||||
rundown.push({ type: event.type, id: event.id, title: event.title });
|
||||
} else {
|
||||
if (timerTypeIndex === null) {
|
||||
event.timerType = TimerType.CountDown;
|
||||
@@ -360,7 +367,6 @@ export function createPatch(originalEvent: OntimeEvent, patchEvent: Partial<Onti
|
||||
patchEvent?.duration ?? originalEvent.duration,
|
||||
patchEvent?.timeStrategy ?? inferStrategy(patchEvent?.timeEnd, patchEvent?.duration, originalEvent.timeStrategy),
|
||||
);
|
||||
const maybeLinkStart = patchEvent.linkStart !== undefined ? patchEvent.linkStart : originalEvent.linkStart;
|
||||
|
||||
return {
|
||||
id: originalEvent.id,
|
||||
@@ -370,9 +376,10 @@ export function createPatch(originalEvent: OntimeEvent, patchEvent: Partial<Onti
|
||||
timeEnd,
|
||||
duration,
|
||||
timeStrategy,
|
||||
linkStart: validateLinkStart(maybeLinkStart),
|
||||
linkStart: validateLinkStart(patchEvent.linkStart),
|
||||
endAction: validateEndAction(patchEvent.endAction, originalEvent.endAction),
|
||||
timerType: validateTimerType(patchEvent.timerType, originalEvent.timerType),
|
||||
isTimeToEnd: typeof patchEvent.isTimeToEnd === 'boolean' ? patchEvent.isTimeToEnd : originalEvent.isTimeToEnd,
|
||||
isPublic: typeof patchEvent.isPublic === 'boolean' ? patchEvent.isPublic : originalEvent.isPublic,
|
||||
skip: typeof patchEvent.skip === 'boolean' ? patchEvent.skip : originalEvent.skip,
|
||||
note: makeString(patchEvent.note, originalEvent.note),
|
||||
@@ -389,17 +396,19 @@ export function createPatch(originalEvent: OntimeEvent, patchEvent: Partial<Onti
|
||||
/**
|
||||
* @description Enforces formatting for events
|
||||
* @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
|
||||
*/
|
||||
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) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const cue = typeof eventIndex === 'number' ? String(eventIndex + 1) : eventIndex;
|
||||
|
||||
const baseEvent = {
|
||||
id: eventArgs?.id ?? generateId(),
|
||||
cue: cueFallback,
|
||||
cue,
|
||||
...eventDef,
|
||||
};
|
||||
const event = createPatch(baseEvent, eventArgs);
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
OscSubscription,
|
||||
ProjectData,
|
||||
Settings,
|
||||
TimerType,
|
||||
URLPreset,
|
||||
ViewSettings,
|
||||
isOntimeBlock,
|
||||
@@ -19,13 +20,7 @@ import {
|
||||
isOntimeDelay,
|
||||
isOntimeEvent,
|
||||
} from 'ontime-types';
|
||||
import {
|
||||
customFieldLabelToKey,
|
||||
generateId,
|
||||
getErrorMessage,
|
||||
getLastEvent,
|
||||
isAlphanumericWithSpace,
|
||||
} from 'ontime-utils';
|
||||
import { customFieldLabelToKey, generateId, getErrorMessage, isAlphanumericWithSpace } from 'ontime-utils';
|
||||
|
||||
import { dbModel } from '../models/dataModel.js';
|
||||
import { block as blockDef, delay as delayDef } from '../models/eventsDefinition.js';
|
||||
@@ -52,6 +47,7 @@ export function parseRundown(
|
||||
|
||||
const rundown: OntimeRundown = [];
|
||||
let eventIndex = 0;
|
||||
let previousId: string | null = null;
|
||||
const ids: string[] = [];
|
||||
|
||||
for (const event of data.rundown) {
|
||||
@@ -64,12 +60,13 @@ export function parseRundown(
|
||||
let newEvent: OntimeEvent | OntimeDelay | OntimeBlock | null;
|
||||
|
||||
if (isOntimeEvent(event)) {
|
||||
const maybeEvent = runEventMigrations({ ...event, id });
|
||||
|
||||
if (event.linkStart) {
|
||||
const prevId = getLastEvent(rundown).lastEvent?.id ?? null;
|
||||
event.linkStart = prevId;
|
||||
maybeEvent.linkStart = previousId;
|
||||
}
|
||||
|
||||
newEvent = createEvent(event, eventIndex.toString());
|
||||
newEvent = createEvent(maybeEvent, eventIndex);
|
||||
// skip if event is invalid
|
||||
if (newEvent == null) {
|
||||
emitError?.('Skipping event without payload');
|
||||
@@ -84,6 +81,7 @@ export function parseRundown(
|
||||
}
|
||||
}
|
||||
|
||||
previousId = id;
|
||||
eventIndex += 1;
|
||||
} else if (isOntimeDelay(event)) {
|
||||
newEvent = { ...delayDef, duration: event.duration, id };
|
||||
@@ -349,3 +347,22 @@ export function sanitiseCustomFields(data: object): CustomFields {
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user