import type { NormalisedRundown, OntimeBlock, OntimeEvent, OntimeRundown, OntimeRundownEntry, PlayableEvent, } from 'ontime-types'; import { isOntimeBlock, isOntimeEvent, isPlayableEvent } from 'ontime-types'; type IndexAndEntry = { entry: OntimeRundownEntry | null; index: number | null }; /** * Gets first event in rundown, if it exists */ export function getFirst(rundown: OntimeRundown) { return rundown.length ? rundown[0] : null; } /** * Gets first event in a normalised rundown, if it exists * @param rundown * @param order * @returns */ export function getFirstNormal(rundown: NormalisedRundown, order: string[]) { const firstId = order[0]; return rundown[firstId] ?? null; } /** * Gets first scheduled event in rundown, if it exists */ export function getFirstEvent(rundown: OntimeRundown): { firstEvent: PlayableEvent | null; firstIndex: number | null; } { for (let i = 0; i < rundown.length; i++) { const firstEvent = rundown[i]; if (isOntimeEvent(firstEvent) && isPlayableEvent(firstEvent)) { return { firstEvent, firstIndex: i }; } } return { firstEvent: null, firstIndex: null }; } /** * Gets first scheduled event in a normalised rundown, if it exists */ export function getFirstEventNormal( rundown: NormalisedRundown, order: string[], ): { firstEvent: PlayableEvent | null; firstIndex: number | null; } { for (let i = 0; i < order.length; i++) { const firstId = order[i]; const firstEvent = rundown[firstId]; if (isOntimeEvent(firstEvent) && isPlayableEvent(firstEvent)) { return { firstEvent, firstIndex: i }; } } return { firstEvent: null, firstIndex: null }; } /** * Gets last event in a normalised rundown, if it exists */ export function getLastNormal(rundown: NormalisedRundown, order: string[]): OntimeRundownEntry | null { const lastId = order.at(-1); if (lastId === undefined) { return null; } return rundown[lastId] ?? null; } /** * Gets last scheduled event in rundown, if it exists */ export function getLastEvent(rundown: OntimeRundown): { lastEvent: PlayableEvent | null; lastIndex: number | null; } { if (rundown.length < 1) { return { lastEvent: null, lastIndex: null }; } for (let i = rundown.length - 1; i >= 0; i--) { const lastEvent = rundown.at(i); if (isOntimeEvent(lastEvent) && isPlayableEvent(lastEvent)) { return { lastEvent, lastIndex: i }; } } return { lastEvent: null, lastIndex: null }; } /** * Gets last scheduled event in a normalised rundown, if it exists */ export function getLastEventNormal( rundown: NormalisedRundown, order: string[], ): { lastEvent: OntimeEvent | null; lastIndex: number | null; } { if (order.length < 1) { return { lastEvent: null, lastIndex: null }; } for (let i = order.length - 1; i >= 0; i--) { const lastId = order[i]; const lastEvent = rundown[lastId]; if (isOntimeEvent(lastEvent) && !lastEvent.skip) { return { lastEvent, lastIndex: i }; } } return { lastEvent: null, lastIndex: null }; } /** * Gets next entry in rundown, if it exists */ export function getNext( rundown: OntimeRundown, currentId: string, ): { nextEvent: OntimeRundownEntry | null; nextIndex: number | null } { const index = rundown.findIndex((event) => event.id === currentId); if (index !== -1 && index + 1 < rundown.length) { const nextIndex = index + 1; const nextEvent = rundown[nextIndex]; return { nextEvent, nextIndex }; } else { return { nextEvent: null, nextIndex: null }; } } /** * Gets next entry in rundown, if it exists */ export function getNextNormal(rundown: NormalisedRundown, order: string[], currentId: string): IndexAndEntry { const currentIndex = order.findIndex((id) => id === currentId); if (currentIndex !== -1 && currentIndex + 1 < order.length) { const index = currentIndex + 1; const nextId = order[index]; const entry = rundown[nextId]; return { entry, index }; } else { return { entry: null, index: null }; } } /** * Gets next scheduled event in rundown, if it exists */ export function getNextEvent( rundown: OntimeRundown, currentId: string, ): { nextEvent: OntimeEvent | null; nextIndex: number | null } { const index = rundown.findIndex((event) => event.id === currentId); if (index < 0) { return { nextEvent: null, nextIndex: null }; } for (let i = index + 1; i < rundown.length; i++) { const nextEvent = rundown[i]; if (isOntimeEvent(nextEvent)) { return { nextEvent, nextIndex: i }; } } return { nextEvent: null, nextIndex: null }; } /** * Gets next scheduled event in a normalised rundown, if it exists */ export function getNextEventNormal( rundown: NormalisedRundown, order: string[], currentId: string, ): { nextEvent: OntimeEvent | null; nextIndex: number | null } { const index = order.findIndex((id) => id === currentId); if (index < 0) { return { nextEvent: null, nextIndex: null }; } for (let i = index + 1; i < order.length; i++) { const nextId = order[i]; const nextEvent = rundown[nextId]; if (isOntimeEvent(nextEvent)) { return { nextEvent, nextIndex: i }; } } return { nextEvent: null, nextIndex: null }; } /** * Gets previous entry in rundown, if it exists */ export function getPrevious(rundown: OntimeRundown, currentId: string): IndexAndEntry { const currentIndex = rundown.findIndex((event) => event.id === currentId); if (currentIndex !== -1 && currentIndex - 1 >= 0) { const index = currentIndex - 1; const entry = rundown[index]; return { entry, index }; } else { return { entry: null, index: null }; } } /** * Gets previous entry in a nornalised rundown, if it exists */ export function getPreviousNormal(rundown: NormalisedRundown, order: string[], currentId: string): IndexAndEntry { const currentIndex = order.findIndex((id) => id === currentId); if (currentIndex !== -1 && currentIndex - 1 >= 0) { const index = currentIndex - 1; const previousId = order[index]; const entry = rundown[previousId]; return { entry, index }; } else { return { entry: null, index: null }; } } /** * Gets previous scheduled event in rundown, if it exists */ export function getPreviousEvent( rundown: OntimeRundown, currentId: string, ): { previousEvent: OntimeEvent | null; previousIndex: number | null } { const index = rundown.findIndex((event) => event.id === currentId); if (index < 0) { return { previousEvent: null, previousIndex: null }; } for (let i = index - 1; i >= 0; i--) { const previousEvent = rundown[i]; if (isOntimeEvent(previousEvent)) { return { previousEvent, previousIndex: i }; } } return { previousEvent: null, previousIndex: null }; } /** * Gets previous scheduled event in a normalised rundown, if it exists * @param rundown * @param order * @param {string} currentId * @return {{ previousEvent: OntimeRundownEntry | null; previousIndex: number | null } } */ export function getPreviousEventNormal( rundown: NormalisedRundown, order: string[], currentId: string, ): { previousEvent: OntimeEvent | null; previousIndex: number | null } { const index = order.findIndex((id) => id === currentId); if (index < 0) { return { previousEvent: null, previousIndex: null }; } for (let i = index - 1; i >= 0; i--) { const previousId = order[i]; const previousEvent = rundown[previousId]; if (isOntimeEvent(previousEvent)) { return { previousEvent, previousIndex: i }; } } return { previousEvent: null, previousIndex: null }; } /** * @description swaps two OntimeEvents in the rundown */ export const swapEventData = (eventA: OntimeEvent, eventB: OntimeEvent): { newA: OntimeEvent; newB: OntimeEvent } => { const newA = { ...eventB, id: eventA.id, timeStart: eventA.timeStart, timeEnd: eventA.timeEnd, duration: eventA.duration, delay: eventA.delay, }; const newB = { ...eventA, id: eventB.id, timeStart: eventB.timeStart, timeEnd: eventB.timeEnd, duration: eventB.duration, delay: eventB.delay, }; return { newA, newB }; }; export function getEventWithId(rundown: OntimeRundown, id: string): OntimeRundownEntry | undefined { return rundown.find((event) => event.id === id); } /** * Gets relevant block element for a given ID */ export function getPreviousBlockNormal(rundown: NormalisedRundown, order: string[], currentId: string): IndexAndEntry { let foundCurrentEvent = false; // Iterate backwards through the rundown to find the current event for (let index = order.length - 1; index >= 0; index--) { const id = order[index]; if (!foundCurrentEvent && id === currentId) { // set the flag when the current event is found foundCurrentEvent = true; continue; } // the first block before the current event is the relevant one const entry = rundown[id]; if (foundCurrentEvent && isOntimeBlock(entry)) { return { entry, index }; } } // no blocks exist before current event return { entry: null, index: null }; } /** * Gets next block element for a given ID */ export function getNextBlockNormal(rundown: NormalisedRundown, order: string[], currentId: string): IndexAndEntry { let foundCurrentEvent = false; // Iterate backwards through the rundown to find the current event for (let index = 0; index < order.length; index++) { const id = order[index]; if (!foundCurrentEvent && id === currentId) { // set the flag when the current event is found foundCurrentEvent = true; continue; } // the first block before the current event is the relevant one const entry = rundown[id]; if (foundCurrentEvent && isOntimeBlock(entry)) { return { entry, index }; } } // no blocks exist before current event return { entry: null, index: null }; } /** * Gets relevant block element for a given ID */ export function getPreviousBlock(rundown: OntimeRundown, currentId: string): OntimeBlock | null { let foundCurrentEvent = false; // Iterate backwards through the rundown to find the current event for (let i = rundown.length - 1; i >= 0; i--) { const entry = rundown[i]; if (!foundCurrentEvent && entry.id === currentId) { // set the flag when the current event is found foundCurrentEvent = true; continue; } // the first block before the current event is the relevant one if (foundCurrentEvent && isOntimeBlock(entry)) { return entry; } } // no blocks exist before current event return null; } /** * filters a rundown to timed events */ export function filterPlayable(rundown: OntimeRundown): PlayableEvent[] { return rundown.filter((event) => isOntimeEvent(event) && !event.skip) as PlayableEvent[]; } /** * filters a rundown to events that can be played */ export function filterTimedEvents(rundown: OntimeRundown): OntimeEvent[] { return rundown.filter((event) => isOntimeEvent(event)) as OntimeEvent[]; }