mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-08 08:53:51 +00:00
023aac4ead
* refactor: extract utilities and types * refactor: extract and simplify progress bar logic * refactor: duration validation handles midnight * fix: prevent stale event loader * refactor: consider next event in timer invalidation * refactor: reload changes on changed roll target * refactor: timer load accounts for midnight * fix: issues with midnight on roll * refactor: prevent stale secondary event
203 lines
6.6 KiB
TypeScript
203 lines
6.6 KiB
TypeScript
import { OntimeEvent } from 'ontime-types';
|
|
import { dayInMs } from 'ontime-utils';
|
|
|
|
/**
|
|
* handle events that span over midnight
|
|
*/
|
|
export const normaliseEndTime = (start: number, end: number) => (end < start ? end + dayInMs : end);
|
|
|
|
/**
|
|
* @description Sorts an array of objects by given property
|
|
* @param {array} arr - array to be sorted
|
|
* @param {string} property - property to compare
|
|
* @returns {array} copy of array sorted in ascending order
|
|
*/
|
|
|
|
export const sortArrayByProperty = <T>(arr: T[], property: string): T[] => {
|
|
return [...arr].sort((a, b) => {
|
|
return a[property] - b[property];
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Finds loading information given a current rundown and time
|
|
* @param {OntimeEvent[]} rundown - List of playable events
|
|
* @param {number} timeNow - time now in ms
|
|
* @returns {{}}
|
|
*/
|
|
export const getRollTimers = (rundown: OntimeEvent[], timeNow: number) => {
|
|
let nowIndex: number | null = null; // index of event now
|
|
let nowId: string | null = null; // id of event now
|
|
let publicIndex: number | null = null; // index of public event now
|
|
let nextIndex: number | null = null; // index of next event
|
|
let publicNextIndex: number | null = null; // index of next public event
|
|
let timeToNext: number | null = null; // counter: time for next event
|
|
let publicTimeToNext: number | null = null; // counter: time for next public event
|
|
|
|
const orderedEvents = sortArrayByProperty(rundown, 'timeStart');
|
|
const lastEvent = orderedEvents[orderedEvents.length - 1];
|
|
const lastNormalEnd = normaliseEndTime(lastEvent.timeStart, lastEvent.timeEnd);
|
|
|
|
let nextEvent: OntimeEvent | null = null;
|
|
let nextPublicEvent: OntimeEvent | null = null;
|
|
let currentEvent: OntimeEvent | null = null;
|
|
let currentPublicEvent: OntimeEvent | null = null;
|
|
|
|
if (timeNow > lastNormalEnd) {
|
|
// we are past last end
|
|
// preload first and find next
|
|
|
|
const firstEvent = orderedEvents[0];
|
|
nextIndex = 0;
|
|
nextEvent = firstEvent;
|
|
timeToNext = firstEvent.timeStart + dayInMs - timeNow;
|
|
|
|
if (firstEvent.isPublic) {
|
|
nextPublicEvent = firstEvent;
|
|
publicNextIndex = 0;
|
|
} else {
|
|
// look for next public
|
|
// dev note: we feel that this is more efficient than filtering
|
|
// since the next event will likely be close to the one playing
|
|
for (const event of orderedEvents) {
|
|
if (event.isPublic) {
|
|
nextPublicEvent = event;
|
|
// we need the index before this was sorted
|
|
publicNextIndex = rundown.findIndex((rundownEvent) => rundownEvent.id === event.id);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// flags: select first event if several overlapping
|
|
let nowFound = false;
|
|
// keep track of the end times when looking for public
|
|
let publicTime = -1;
|
|
|
|
for (const event of orderedEvents) {
|
|
// When does the event end (handle midnight)
|
|
const normalEnd = normaliseEndTime(event.timeStart, event.timeEnd);
|
|
|
|
const hasNotEnded = normalEnd > timeNow;
|
|
const isFromDayBefore = normalEnd > dayInMs && timeNow < event.timeEnd;
|
|
const hasStarted = isFromDayBefore || timeNow >= event.timeStart;
|
|
|
|
if (normalEnd <= timeNow) {
|
|
// event ran already
|
|
|
|
if (event.isPublic && normalEnd > publicTime) {
|
|
// public event might not be the one running
|
|
publicTime = normalEnd;
|
|
currentPublicEvent = event;
|
|
publicIndex = rundown.findIndex((rundownEvent) => rundownEvent.id === event.id);
|
|
}
|
|
} else if (hasNotEnded && hasStarted && !nowFound) {
|
|
// event is running
|
|
currentEvent = event;
|
|
nowIndex = rundown.findIndex((rundownEvent) => rundownEvent.id === event.id);
|
|
nowId = event.id;
|
|
nowFound = true;
|
|
|
|
// it could also be public
|
|
if (event.isPublic) {
|
|
publicTime = normalEnd;
|
|
currentPublicEvent = event;
|
|
publicIndex = rundown.findIndex((rundownEvent) => rundownEvent.id === event.id);
|
|
}
|
|
} else if (normalEnd > timeNow) {
|
|
// event will run
|
|
|
|
// we already know whats next and next-public
|
|
if (nextIndex !== null && publicNextIndex !== null) {
|
|
continue;
|
|
}
|
|
|
|
// look for next events
|
|
// check how far the start is from now
|
|
const timeToEventStart = event.timeStart - timeNow;
|
|
|
|
// we don't have a next or this one starts sooner than current next
|
|
if (nextIndex === null || timeToEventStart < timeToNext) {
|
|
timeToNext = timeToEventStart;
|
|
nextEvent = event;
|
|
nextIndex = rundown.findIndex((rundownEvent) => rundownEvent.id === event.id);
|
|
}
|
|
|
|
if (event.isPublic) {
|
|
// if we don't have a public next or this one start sooner than assigned next
|
|
if (publicNextIndex === null || timeToEventStart < publicTimeToNext) {
|
|
publicTimeToNext = timeToEventStart;
|
|
nextPublicEvent = event;
|
|
publicNextIndex = rundown.findIndex((rundownEvent) => rundownEvent.id === event.id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return {
|
|
nowIndex,
|
|
nowId,
|
|
publicIndex,
|
|
nextIndex,
|
|
publicNextIndex,
|
|
timeToNext,
|
|
nextEvent,
|
|
nextPublicEvent,
|
|
currentEvent,
|
|
currentPublicEvent,
|
|
};
|
|
};
|
|
|
|
type CurrentTimers = {
|
|
selectedEventId: string | null;
|
|
current: number | null;
|
|
_finishAt: number | null;
|
|
clock: number | null;
|
|
secondaryTimer: number | null;
|
|
secondaryTarget: number | null;
|
|
};
|
|
|
|
/**
|
|
* @description Implements update functions for roll mode
|
|
* @param {CurrentTimers} currentTimers
|
|
* @returns {object} object with selection variables
|
|
*/
|
|
export const updateRoll = (currentTimers: CurrentTimers) => {
|
|
const { selectedEventId, current, _finishAt, clock, secondaryTimer, secondaryTarget } = currentTimers;
|
|
|
|
// timers
|
|
let updatedTimer = current;
|
|
let updatedSecondaryTimer = secondaryTimer;
|
|
// whether rollLoad should be called: force reload of events
|
|
let doRollLoad = false;
|
|
// whether finished event should trigger
|
|
let isPrimaryFinished = false;
|
|
|
|
if (selectedEventId && current !== null) {
|
|
// if we have something selected and a timer, we are running
|
|
|
|
updatedTimer = _finishAt - clock;
|
|
if (updatedTimer > dayInMs) {
|
|
updatedTimer -= dayInMs;
|
|
}
|
|
|
|
if (updatedTimer < 0) {
|
|
isPrimaryFinished = true;
|
|
// we need a new event
|
|
doRollLoad = true;
|
|
}
|
|
} else if (secondaryTimer >= 0) {
|
|
// if secondaryTimer is running we are in waiting to roll
|
|
|
|
updatedSecondaryTimer = secondaryTarget - clock;
|
|
|
|
if (updatedSecondaryTimer <= 0) {
|
|
// we need a new event
|
|
doRollLoad = true;
|
|
}
|
|
}
|
|
|
|
return { updatedTimer, updatedSecondaryTimer, doRollLoad, isFinished: isPrimaryFinished };
|
|
};
|