add relevantBlock function (#1100)

* feat: current block

* pass full rundown to functions and use filter utils
This commit is contained in:
Alex Christoffer Rasmussen
2024-07-23 12:22:47 +02:00
committed by GitHub
parent 20838c038a
commit 158ef05ff0
18 changed files with 246 additions and 49 deletions
@@ -115,6 +115,7 @@ export const setAuxTimer = {
export const useCuesheet = () => { export const useCuesheet = () => {
const featureSelector = (state: RuntimeStore) => ({ const featureSelector = (state: RuntimeStore) => ({
playback: state.timer.playback, playback: state.timer.playback,
currentBlockId: state.currentBlock.block?.id ?? null,
selectedEventId: state.eventNow?.id ?? null, selectedEventId: state.eventNow?.id ?? null,
selectedEventIndex: state.runtime.selectedEventIndex, selectedEventIndex: state.runtime.selectedEventIndex,
numEvents: state.runtime.numEvents, numEvents: state.runtime.numEvents,
+4
View File
@@ -38,6 +38,10 @@ export const runtimeStorePlaceholder: RuntimeStore = {
actualStart: null, actualStart: null,
expectedEnd: null, expectedEnd: null,
}, },
currentBlock: {
block: null,
startedAt: null,
},
eventNow: null, eventNow: null,
eventNext: null, eventNext: null,
publicEventNow: null, publicEventNow: null,
+5
View File
@@ -150,6 +150,11 @@ export const connectSocket = () => {
updateDevTools({ eventNow: payload }); updateDevTools({ eventNow: payload });
break; break;
} }
case 'ontime-currentBlock': {
patchRuntime('currentBlock', payload);
updateDevTools({ currentBlock: payload });
break;
}
case 'ontime-publicEventNow': { case 'ontime-publicEventNow': {
patchRuntime('publicEventNow', payload); patchRuntime('publicEventNow', payload);
updateDevTools({ publicEventNow: payload }); updateDevTools({ publicEventNow: payload });
@@ -21,11 +21,11 @@ interface CuesheetProps {
columns: ColumnDef<OntimeRundownEntry>[]; columns: ColumnDef<OntimeRundownEntry>[];
handleUpdate: (rowIndex: number, accessor: keyof OntimeRundownEntry, payload: unknown) => void; handleUpdate: (rowIndex: number, accessor: keyof OntimeRundownEntry, payload: unknown) => void;
selectedId: string | null; selectedId: string | null;
currentBlockId: string | null;
} }
export default function Cuesheet({ data, columns, handleUpdate, selectedId }: CuesheetProps) { export default function Cuesheet({ data, columns, handleUpdate, selectedId, currentBlockId }: CuesheetProps) {
const { followSelected, showSettings, showDelayBlock, showPrevious, showIndexColumn } = useCuesheetSettings(); const { followSelected, showSettings, showDelayBlock, showPrevious, showIndexColumn } = useCuesheetSettings();
const { const {
columnVisibility, columnVisibility,
columnOrder, columnOrder,
@@ -114,11 +114,16 @@ export default function Cuesheet({ data, columns, handleUpdate, selectedId }: Cu
} }
if (isOntimeBlock(row.original)) { if (isOntimeBlock(row.original)) {
if (isPast && !showPrevious && key !== currentBlockId) {
return null;
}
return <BlockRow key={key} title={row.original.title} />; return <BlockRow key={key} title={row.original.title} />;
} }
if (isOntimeDelay(row.original)) { if (isOntimeDelay(row.original)) {
if (isPast && !showPrevious) {
return null;
}
const delayVal = row.original.duration; const delayVal = row.original.duration;
if (!showDelayBlock || delayVal === 0) { if (!showDelayBlock || delayVal === 0) {
return null; return null;
} }
@@ -128,9 +133,6 @@ export default function Cuesheet({ data, columns, handleUpdate, selectedId }: Cu
if (isOntimeEvent(row.original)) { if (isOntimeEvent(row.original)) {
eventIndex++; eventIndex++;
const isSelected = key === selectedId; const isSelected = key === selectedId;
if (isSelected) {
isPast = false;
}
if (isPast && !showPrevious) { if (isPast && !showPrevious) {
return null; return null;
@@ -107,6 +107,7 @@ export default function CuesheetWrapper() {
columns={columns} columns={columns}
handleUpdate={handleUpdate} handleUpdate={handleUpdate}
selectedId={featureData.selectedEventId} selectedId={featureData.selectedEventId}
currentBlockId={featureData.currentBlockId}
/> />
</div> </div>
); );
+4
View File
@@ -179,6 +179,10 @@ export const startServer = async (
message: messageService.getState(), message: messageService.getState(),
runtime: state.runtime, runtime: state.runtime,
eventNow: state.eventNow, eventNow: state.eventNow,
currentBlock: {
block: null,
startedAt: null,
},
publicEventNow: state.publicEventNow, publicEventNow: state.publicEventNow,
eventNext: state.eventNext, eventNext: state.eventNext,
publicEventNext: state.publicEventNext, publicEventNext: state.publicEventNext,
+2 -2
View File
@@ -1,4 +1,4 @@
import { OntimeEvent } from 'ontime-types'; import { OntimeRundown } from 'ontime-types';
import * as runtimeState from '../stores/runtimeState.js'; import * as runtimeState from '../stores/runtimeState.js';
import type { UpdateResult } from '../stores/runtimeState.js'; import type { UpdateResult } from '../stores/runtimeState.js';
@@ -106,7 +106,7 @@ export class TimerService {
* Loads roll information into timer service * Loads roll information into timer service
* @param {OntimeEvent[]} rundown -- list of events to run * @param {OntimeEvent[]} rundown -- list of events to run
*/ */
roll(rundown: OntimeEvent[]) { roll(rundown: OntimeRundown) {
runtimeState.roll(rundown); runtimeState.roll(rundown);
} }
@@ -25,13 +25,14 @@ import { eventStore } from '../../stores/EventStore.js';
type PatchWithId = (Partial<OntimeEvent> | Partial<OntimeBlock> | Partial<OntimeDelay>) & { id: string }; type PatchWithId = (Partial<OntimeEvent> | Partial<OntimeBlock> | Partial<OntimeDelay>) & { id: string };
type CompleteEntry<T> = T extends Partial<OntimeEvent> type CompleteEntry<T> =
? OntimeEvent T extends Partial<OntimeEvent>
: T extends Partial<OntimeDelay> ? OntimeEvent
? OntimeDelay : T extends Partial<OntimeDelay>
: T extends Partial<OntimeBlock> ? OntimeDelay
? OntimeBlock : T extends Partial<OntimeBlock>
: never; ? OntimeBlock
: never;
function generateEvent<T extends Partial<OntimeEvent> | Partial<OntimeDelay> | Partial<OntimeBlock>>( function generateEvent<T extends Partial<OntimeEvent> | Partial<OntimeDelay> | Partial<OntimeBlock>>(
eventData: T, eventData: T,
@@ -240,7 +241,7 @@ function notifyChanges(options: { timer?: boolean | string[]; external?: boolean
// notify timer service of changed events // notify timer service of changed events
// timer can be true or an array of changed IDs // timer can be true or an array of changed IDs
const affected = Array.isArray(options.timer) ? options.timer : undefined; const affected = Array.isArray(options.timer) ? options.timer : undefined;
runtimeService.maybeUpdate(playableEvents, affected); runtimeService.maybeUpdate(affected);
} }
} }
@@ -9,7 +9,7 @@ import {
TimerLifeCycle, TimerLifeCycle,
TimerPhase, TimerPhase,
} from 'ontime-types'; } from 'ontime-types';
import { millisToString, validatePlayback } from 'ontime-utils'; import { filterPlayable, millisToString, validatePlayback } from 'ontime-utils';
import { deepEqual } from 'fast-equals'; import { deepEqual } from 'fast-equals';
@@ -28,6 +28,7 @@ import {
getNextEventWithCue, getNextEventWithCue,
getEventWithId, getEventWithId,
getPlayableEvents, getPlayableEvents,
getRundown,
} from '../rundown-service/rundownUtils.js'; } from '../rundown-service/rundownUtils.js';
import { skippedOutOfEvent } from '../timerUtils.js'; import { skippedOutOfEvent } from '../timerUtils.js';
import { integrationService } from '../integration-service/IntegrationService.js'; import { integrationService } from '../integration-service/IntegrationService.js';
@@ -95,7 +96,7 @@ class RuntimeService {
} }
// we dont call this.roll because we need to bypass the checks // we dont call this.roll because we need to bypass the checks
const rundown = getPlayableEvents(); const rundown = getRundown();
// TODO: by not calling roll, we dont get the events // TODO: by not calling roll, we dont get the events
this.eventTimer.roll(rundown); this.eventTimer.roll(rundown);
} }
@@ -217,7 +218,7 @@ class RuntimeService {
* Called when the underlying data has changed, * Called when the underlying data has changed,
* we check if the change affects the runtime * we check if the change affects the runtime
*/ */
maybeUpdate(playableEvents: OntimeEvent[], affectedIds?: string[]) { maybeUpdate(affectedIds?: string[]) {
const state = runtimeState.getState(); const state = runtimeState.getState();
const hasLoadedElements = state.eventNow !== null || state.eventNext !== null; const hasLoadedElements = state.eventNow !== null || state.eventNext !== null;
if (!hasLoadedElements) { if (!hasLoadedElements) {
@@ -245,7 +246,8 @@ class RuntimeService {
if (onlyChangedNow) { if (onlyChangedNow) {
runtimeState.reload(eventNow); runtimeState.reload(eventNow);
} else { } else {
runtimeState.reloadAll(eventNow, playableEvents); const rundown = getRundown();
runtimeState.reloadAll(eventNow, rundown);
} }
return; return;
} }
@@ -253,7 +255,8 @@ class RuntimeService {
// Maybe the event will become the next // Maybe the event will become the next
isNext = this.isNewNext(); isNext = this.isNewNext();
if (isNext) { if (isNext) {
runtimeState.loadNext(playableEvents); const rundown = getRundown();
runtimeState.loadNext(rundown);
} }
} }
@@ -269,8 +272,8 @@ class RuntimeService {
return false; return false;
} }
const timedEvents = getPlayableEvents(); const rundown = getRundown();
const success = runtimeState.load(event, timedEvents); const success = runtimeState.load(event, rundown);
if (success) { if (success) {
logger.info(LogOrigin.Playback, `Loaded event with ID ${event.id}`); logger.info(LogOrigin.Playback, `Loaded event with ID ${event.id}`);
@@ -513,13 +516,14 @@ class RuntimeService {
return; return;
} }
const playableEvents = getPlayableEvents(); const rundown = getRundown();
const playableEvents = filterPlayable(rundown);
if (playableEvents.length === 0) { if (playableEvents.length === 0) {
logger.warning(LogOrigin.Server, 'Roll: no events found'); logger.warning(LogOrigin.Server, 'Roll: no events found');
return; return;
} }
this.eventTimer.roll(playableEvents); this.eventTimer.roll(rundown);
const state = runtimeState.getState(); const state = runtimeState.getState();
const newState = state.timer.playback; const newState = state.timer.playback;
@@ -543,14 +547,14 @@ class RuntimeService {
} }
// the db would have to change for the event not to exist // the db would have to change for the event not to exist
// we do not kow the reason for the crash, so we check anyway // we do not know the reason for the crash, so we check anyway
const event = getEventWithId(selectedEventId); const event = getEventWithId(selectedEventId);
if (!event || !isOntimeEvent(event)) { if (!event || !isOntimeEvent(event)) {
return; return;
} }
const timedEvents = getPlayableEvents(); const rundown = getRundown();
runtimeState.resume(restorePoint, event, timedEvents); runtimeState.resume(restorePoint, event, rundown);
logger.info(LogOrigin.Playback, 'Resuming playback'); logger.info(LogOrigin.Playback, 'Resuming playback');
} }
@@ -620,6 +624,11 @@ function broadcastResult(_target: any, _propertyKey: string, descriptor: Propert
updateEventIfChanged('eventNext', state); updateEventIfChanged('eventNext', state);
updateEventIfChanged('publicEventNext', state); updateEventIfChanged('publicEventNext', state);
if (!deepEqual(RuntimeService?.previousState.currentBlock, state.currentBlock)) {
eventStore.set('currentBlock', state.currentBlock);
RuntimeService.previousState.currentBlock = { ...state.currentBlock };
}
if (shouldUpdateClock) { if (shouldUpdateClock) {
RuntimeService.previousClockUpdate = state.clock; RuntimeService.previousClockUpdate = state.clock;
eventStore.set('clock', state.clock); eventStore.set('clock', state.clock);
+8 -4
View File
@@ -127,10 +127,14 @@ type RollTimers = {
/** /**
* Finds loading information given a current rundown and time * Finds loading information given a current rundown and time
* @param {OntimeEvent[]} rundown - List of playable events * @param {OntimeEvent[]} playableEvents - List of playable events
* @param {number} timeNow - time now in ms * @param {number} timeNow - time now in ms
*/ */
export const getRollTimers = (rundown: OntimeEvent[], timeNow: number, currentIndex?: number | null): RollTimers => { export const getRollTimers = (
playableEvents: OntimeEvent[],
timeNow: number,
currentIndex?: number | null,
): RollTimers => {
let nowIndex: MaybeNumber = null; // index of event now let nowIndex: MaybeNumber = null; // index of event now
let nowId: MaybeString = null; // id of event now let nowId: MaybeString = null; // id of event now
let publicIndex: MaybeNumber = null; // index of public event now let publicIndex: MaybeNumber = null; // index of public event now
@@ -140,8 +144,8 @@ export const getRollTimers = (rundown: OntimeEvent[], timeNow: number, currentIn
let publicTimeToNext: MaybeNumber = null; // counter: time for next public event let publicTimeToNext: MaybeNumber = null; // counter: time for next public event
const hasLoaded = currentIndex !== null; const hasLoaded = currentIndex !== null;
const canFilter = hasLoaded && currentIndex === rundown.length - 1; const canFilter = hasLoaded && currentIndex === playableEvents.length - 1;
const filteredRundown = canFilter ? rundown.slice(currentIndex) : rundown; const filteredRundown = canFilter ? playableEvents.slice(currentIndex) : playableEvents;
const lastEvent = filteredRundown.at(-1); const lastEvent = filteredRundown.at(-1);
const lastNormalEnd = normaliseEndTime(lastEvent.timeStart, lastEvent.timeEnd); const lastNormalEnd = normaliseEndTime(lastEvent.timeStart, lastEvent.timeEnd);
@@ -98,6 +98,7 @@ describe('mutation on runtimeState', () => {
expect(newState.eventNow?.id).toBe(mockEvent.id); expect(newState.eventNow?.id).toBe(mockEvent.id);
expect(newState.timer.playback).toBe(Playback.Armed); expect(newState.timer.playback).toBe(Playback.Armed);
expect(newState.clock).not.toBe(666); expect(newState.clock).not.toBe(666);
expect(newState.currentBlock.block).toBeNull();
// 2. Start event // 2. Start event
let success = start(); let success = start();
@@ -171,6 +172,7 @@ describe('mutation on runtimeState', () => {
expect(newState.runtime.actualStart).toBeNull(); expect(newState.runtime.actualStart).toBeNull();
expect(newState.runtime.plannedStart).toBe(0); expect(newState.runtime.plannedStart).toBe(0);
expect(newState.runtime.plannedEnd).toBe(1500); expect(newState.runtime.plannedEnd).toBe(1500);
expect(newState.currentBlock.block).toBeNull();
// 2. Start event // 2. Start event
start(); start();
@@ -202,6 +204,7 @@ describe('mutation on runtimeState', () => {
expect(newState.runtime.offset).toBe(delayBefore); expect(newState.runtime.offset).toBe(delayBefore);
// finish is the difference between the runtime and the schedule // finish is the difference between the runtime and the schedule
expect(newState.runtime.expectedEnd).toBe(event2.timeEnd - newState.runtime.offset); expect(newState.runtime.expectedEnd).toBe(event2.timeEnd - newState.runtime.offset);
expect(newState.currentBlock.block).toBeNull();
// 4. Add time // 4. Add time
addTime(10); addTime(10);
+66 -15
View File
@@ -1,5 +1,14 @@
import { MaybeNumber, OntimeEvent, Playback, Runtime, TimerPhase, TimerState } from 'ontime-types'; import {
import { calculateDuration, dayInMs } from 'ontime-utils'; CurrentBlockState,
MaybeNumber,
OntimeEvent,
OntimeRundown,
Playback,
Runtime,
TimerPhase,
TimerState,
} from 'ontime-types';
import { calculateDuration, dayInMs, filterPlayable, getRelevantBlock } from 'ontime-utils';
import { clock } from '../services/Clock.js'; import { clock } from '../services/Clock.js';
import { RestorePoint } from '../services/RestoreService.js'; import { RestorePoint } from '../services/RestoreService.js';
@@ -41,6 +50,7 @@ const initialTimer: TimerState = {
export type RuntimeState = { export type RuntimeState = {
clock: number; // realtime clock clock: number; // realtime clock
eventNow: OntimeEvent | null; eventNow: OntimeEvent | null;
currentBlock: CurrentBlockState;
publicEventNow: OntimeEvent | null; publicEventNow: OntimeEvent | null;
eventNext: OntimeEvent | null; eventNext: OntimeEvent | null;
publicEventNext: OntimeEvent | null; publicEventNext: OntimeEvent | null;
@@ -52,10 +62,15 @@ export type RuntimeState = {
totalDelay: number; // this value comes from rundown service totalDelay: number; // this value comes from rundown service
pausedAt: MaybeNumber; pausedAt: MaybeNumber;
}; };
_prevCurrentBlock: CurrentBlockState;
}; };
const runtimeState: RuntimeState = { const runtimeState: RuntimeState = {
clock: clock.timeNow(), clock: clock.timeNow(),
currentBlock: {
block: null,
startedAt: null,
},
eventNow: null, eventNow: null,
publicEventNow: null, publicEventNow: null,
eventNext: null, eventNext: null,
@@ -67,6 +82,10 @@ const runtimeState: RuntimeState = {
totalDelay: 0, totalDelay: 0,
pausedAt: null, pausedAt: null,
}, },
_prevCurrentBlock: {
block: null,
startedAt: null,
},
}; };
export function getState(): Readonly<RuntimeState> { export function getState(): Readonly<RuntimeState> {
@@ -77,6 +96,11 @@ export function clear() {
runtimeState.eventNow = null; runtimeState.eventNow = null;
runtimeState.publicEventNow = null; runtimeState.publicEventNow = null;
runtimeState.eventNext = null; runtimeState.eventNext = null;
runtimeState._prevCurrentBlock = { ...runtimeState.currentBlock };
runtimeState.currentBlock.block = null;
runtimeState.currentBlock.startedAt = null;
runtimeState.publicEventNext = null; runtimeState.publicEventNext = null;
runtimeState.runtime.offset = 0; runtimeState.runtime.offset = 0;
@@ -129,12 +153,13 @@ export function updateRundownData(rundownData: RundownData) {
/** /**
* Loads a given event into state * Loads a given event into state
* @param event * @param event
* @param rundown * @param {OntimeEvent[]} playableEvents list of events availebe for playback
* @param initialData * @param {OntimeRundown} rundown the full rundown
* @param initialData potential data from restore point
*/ */
export function load( export function load(
event: OntimeEvent, event: OntimeEvent,
rundown: OntimeEvent[], rundown: OntimeRundown,
initialData?: Partial<TimerState & RestorePoint>, initialData?: Partial<TimerState & RestorePoint>,
): boolean { ): boolean {
clear(); clear();
@@ -165,8 +190,14 @@ export function load(
return event.id === runtimeState.eventNow?.id; return event.id === runtimeState.eventNow?.id;
} }
export function loadNow(event: OntimeEvent, playableEvents: OntimeEvent[]) { export function loadNow(event: OntimeEvent, rundown: OntimeRundown) {
runtimeState.eventNow = event; runtimeState.eventNow = event;
runtimeState.currentBlock.block = getRelevantBlock(rundown, event.id);
//if we are still in the same block keep the startedAt time
if (runtimeState._prevCurrentBlock.block?.id === runtimeState.currentBlock.block?.id) {
runtimeState.currentBlock.startedAt = runtimeState._prevCurrentBlock.startedAt;
}
// check if current is also public // check if current is also public
if (event.isPublic) { if (event.isPublic) {
@@ -180,6 +211,8 @@ export function loadNow(event: OntimeEvent, playableEvents: OntimeEvent[]) {
return; return;
} }
const playableEvents = filterPlayable(rundown);
// iterate backwards to find it // iterate backwards to find it
for (let i = runtimeState.runtime.selectedEventIndex; i >= 0; i--) { for (let i = runtimeState.runtime.selectedEventIndex; i >= 0; i--) {
if (playableEvents[i].isPublic) { if (playableEvents[i].isPublic) {
@@ -190,7 +223,7 @@ export function loadNow(event: OntimeEvent, playableEvents: OntimeEvent[]) {
} }
} }
export function loadNext(playableEvents: OntimeEvent[]) { export function loadNext(rundown: OntimeRundown) {
// assume there are no next events // assume there are no next events
runtimeState.eventNext = null; runtimeState.eventNext = null;
runtimeState.publicEventNext = null; runtimeState.publicEventNext = null;
@@ -199,6 +232,7 @@ export function loadNext(playableEvents: OntimeEvent[]) {
return; return;
} }
const playableEvents = filterPlayable(rundown);
const numEvents = playableEvents.length; const numEvents = playableEvents.length;
if (runtimeState.runtime.selectedEventIndex < numEvents - 1) { if (runtimeState.runtime.selectedEventIndex < numEvents - 1) {
@@ -224,7 +258,14 @@ export function loadNext(playableEvents: OntimeEvent[]) {
} }
} }
export function resume(restorePoint: RestorePoint, event: OntimeEvent, rundown: OntimeEvent[]) { /**
* Resume from restore point
* @param restorePoint
* @param event
* @param playableEvents list of events availebe for playback
* @param rundown the full rundown
*/
export function resume(restorePoint: RestorePoint, event: OntimeEvent, rundown: OntimeRundown) {
load(event, rundown, restorePoint); load(event, rundown, restorePoint);
} }
@@ -255,6 +296,8 @@ export function reload(event?: OntimeEvent) {
runtimeState.timer.addedTime = 0; runtimeState.timer.addedTime = 0;
runtimeState.timer.expectedFinish = getExpectedFinish(runtimeState); runtimeState.timer.expectedFinish = getExpectedFinish(runtimeState);
runtimeState.currentBlock.startedAt = null;
return runtimeState.eventNow.id; return runtimeState.eventNow.id;
} }
@@ -263,10 +306,11 @@ export function reload(event?: OntimeEvent) {
* without interrupting timer * without interrupting timer
* @param eventNow * @param eventNow
* @param playableEvents * @param playableEvents
* @param rundown
*/ */
export function reloadAll(eventNow: OntimeEvent, playableEvents: OntimeEvent[]) { export function reloadAll(eventNow: OntimeEvent, rundown: OntimeRundown) {
loadNow(eventNow, playableEvents); loadNow(eventNow, rundown);
loadNext(playableEvents); loadNext(rundown);
reload(eventNow); reload(eventNow);
} }
@@ -291,6 +335,11 @@ export function start(state: RuntimeState = runtimeState): boolean {
state.timer.startedAt = state.clock; state.timer.startedAt = state.clock;
} }
if (state.currentBlock.startedAt === null) {
console.log('currentBlock.startedAt is null, setting new start');
state.currentBlock.startedAt = state.clock;
}
state.timer.playback = Playback.Play; state.timer.playback = Playback.Play;
state.timer.expectedFinish = getExpectedFinish(state); state.timer.expectedFinish = getExpectedFinish(state);
state.timer.elapsed = 0; state.timer.elapsed = 0;
@@ -427,12 +476,14 @@ export function update(): UpdateResult {
} }
} }
export function roll(rundown: OntimeEvent[]) { export function roll(rundown: OntimeRundown) {
const selectedEventIndex = runtimeState.runtime.selectedEventIndex; const selectedEventIndex = runtimeState.runtime.selectedEventIndex;
clear(); const playableEvents = filterPlayable(rundown);
runtimeState.runtime.numEvents = rundown.length;
const { nextEvent, currentEvent } = getRollTimers(rundown, runtimeState.clock, selectedEventIndex); clear();
runtimeState.runtime.numEvents = playableEvents.length;
const { nextEvent, currentEvent } = getRollTimers(playableEvents, runtimeState.clock, selectedEventIndex);
if (currentEvent) { if (currentEvent) {
// there is something running, load // there is something running, load
@@ -0,0 +1,7 @@
import type { MaybeNumber } from '../../utils/utils.type.js';
import type { OntimeBlock } from '../core/OntimeEvent.type.js';
export type CurrentBlockState = {
block: OntimeBlock | null;
startedAt: MaybeNumber;
};
@@ -1,5 +1,6 @@
import type { OntimeEvent } from '../core/OntimeEvent.type.js'; import type { OntimeEvent } from '../core/OntimeEvent.type.js';
import type { SimpleTimerState } from './AuxTimer.type.js'; import type { SimpleTimerState } from './AuxTimer.type.js';
import type { CurrentBlockState } from './CurrentBlockState.type.js';
import type { MessageState } from './MessageControl.type.js'; import type { MessageState } from './MessageControl.type.js';
import type { Runtime } from './Runtime.type.js'; import type { Runtime } from './Runtime.type.js';
import type { TimerState } from './TimerState.type.js'; import type { TimerState } from './TimerState.type.js';
@@ -15,6 +16,7 @@ export type RuntimeStore = {
// rundown data // rundown data
runtime: Runtime; runtime: Runtime;
currentBlock: CurrentBlockState;
eventNow: OntimeEvent | null; eventNow: OntimeEvent | null;
publicEventNow: OntimeEvent | null; publicEventNow: OntimeEvent | null;
eventNext: OntimeEvent | null; eventNext: OntimeEvent | null;
+1
View File
@@ -62,6 +62,7 @@ export type { Message, TimerMessage, MessageState } from './definitions/runtime/
export type { Runtime } from './definitions/runtime/Runtime.type.js'; export type { Runtime } from './definitions/runtime/Runtime.type.js';
export type { RuntimeStore } from './definitions/runtime/RuntimeStore.type.js'; export type { RuntimeStore } from './definitions/runtime/RuntimeStore.type.js';
export { type TimerState, TimerPhase } from './definitions/runtime/TimerState.type.js'; export { type TimerState, TimerPhase } from './definitions/runtime/TimerState.type.js';
export type { CurrentBlockState } from './definitions/runtime/CurrentBlockState.type.js';
// ---> Extra Timer // ---> Extra Timer
export { type SimpleTimerState, SimplePlayback, SimpleDirection } from './definitions/runtime/AuxTimer.type.js'; export { type SimpleTimerState, SimplePlayback, SimpleDirection } from './definitions/runtime/AuxTimer.type.js';
+3
View File
@@ -8,6 +8,8 @@ export { sanitiseCue } from './src/cue-utils/cueUtils.js';
export { getCueCandidate } from './src/cue-utils/cueUtils.js'; export { getCueCandidate } from './src/cue-utils/cueUtils.js';
export { generateId } from './src/generate-id/generateId.js'; export { generateId } from './src/generate-id/generateId.js';
export { export {
filterPlayable,
filterTimedEvents,
getFirst, getFirst,
getFirstEvent, getFirstEvent,
getFirstEventNormal, getFirstEventNormal,
@@ -23,6 +25,7 @@ export {
getPreviousEvent, getPreviousEvent,
getPreviousEventNormal, getPreviousEventNormal,
getPreviousNormal, getPreviousNormal,
getRelevantBlock,
swapEventData, swapEventData,
} from './src/rundown-utils/rundownUtils.js'; } from './src/rundown-utils/rundownUtils.js';
@@ -2,12 +2,15 @@ import type { NormalisedRundown, OntimeEvent, OntimeRundown } from 'ontime-types
import { SupportedEvent } from 'ontime-types'; import { SupportedEvent } from 'ontime-types';
import { import {
filterPlayable,
filterTimedEvents,
getLastEvent, getLastEvent,
getLastNormal, getLastNormal,
getNext, getNext,
getNextEvent, getNextEvent,
getPrevious, getPrevious,
getPreviousEvent, getPreviousEvent,
getRelevantBlock,
swapEventData, swapEventData,
} from './rundownUtils'; } from './rundownUtils';
@@ -262,4 +265,59 @@ describe('getLastEvent', () => {
expect(lastEntry).toBe(null); expect(lastEntry).toBe(null);
}); });
}); });
describe('relevantBlock', () => {
const testRundown = [
{ id: 'a', type: SupportedEvent.Event },
{ id: 'b', type: SupportedEvent.Event },
{ id: 'c', type: SupportedEvent.Event },
{ id: 'd', type: SupportedEvent.Delay },
{ id: 'e', type: SupportedEvent.Block },
{ id: 'f', type: SupportedEvent.Event },
{ id: 'g', type: SupportedEvent.Block },
{ id: 'h', type: SupportedEvent.Event },
];
it('returns the relevant block', () => {
const block = getRelevantBlock(testRundown as unknown as OntimeRundown, 'h');
expect(block?.id).toBe('g');
});
it('returns the relevant block', () => {
const block = getRelevantBlock(testRundown as unknown as OntimeRundown, 'f');
expect(block?.id).toBe('e');
});
it('returns the relevant block', () => {
const block = getRelevantBlock(testRundown as unknown as OntimeRundown, 'a');
expect(block).toBeNull();
});
it('also works on index 0', () => {
testRundown.unshift({ id: '0', type: SupportedEvent.Block });
const block = getRelevantBlock(testRundown as unknown as OntimeRundown, 'a');
expect(block?.id).toBe('0');
});
});
describe('filter event', () => {
const eventA = { id: 'a', type: SupportedEvent.Event } as OntimeEvent;
const eventB = { id: 'b', skip: true, type: SupportedEvent.Event } as OntimeEvent;
const testRundown = [
eventA,
eventB,
{ id: 'c', type: SupportedEvent.Delay },
{ id: 'd', type: SupportedEvent.Block },
];
test('filterPlayable', () => {
const result = filterPlayable(testRundown as unknown as OntimeRundown);
expect(result).toMatchObject([eventA]);
});
test('filterTimedEvents', () => {
const result = filterTimedEvents(testRundown as unknown as OntimeRundown);
expect(result).toMatchObject([eventA, eventB]);
});
});
}); });
@@ -1,5 +1,5 @@
import type { NormalisedRundown, OntimeEvent, OntimeRundown, OntimeRundownEntry } from 'ontime-types'; import type { NormalisedRundown, OntimeBlock, OntimeEvent, OntimeRundown, OntimeRundownEntry } from 'ontime-types';
import { isOntimeEvent } from 'ontime-types'; import { isOntimeBlock, isOntimeEvent } from 'ontime-types';
type IndexAndEntry = { entry: OntimeRundownEntry | null; index: number | null }; type IndexAndEntry = { entry: OntimeRundownEntry | null; index: number | null };
@@ -326,3 +326,44 @@ export const swapEventData = (eventA: OntimeEvent, eventB: OntimeEvent): { newA:
return { newA, newB }; return { newA, newB };
}; };
/**
* Gets relevant block element for a given ID
* @param rundown
* @param order
* @param {string} currentId
* @return {OntimeBlock | null}
*/
export function getRelevantBlock(rundown: OntimeRundown, currentId: string): OntimeBlock | null {
let inBlock = 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 (entry.id === currentId) {
//set the flag when the current event is found
inBlock = true;
}
//the first block before the current event is the relevant one
if (inBlock && isOntimeBlock(entry)) {
return entry;
}
}
//no blocks exist before current event
return null;
}
/**
* returns all events that can be loaded
* @return {array}
*/
export function filterPlayable(rundown: OntimeRundown): OntimeEvent[] {
return rundown.filter((event) => isOntimeEvent(event) && !event.skip) as OntimeEvent[];
}
/**
* returns all events of type OntimeEvent
* @return {array}
*/
export function filterTimedEvents(rundown: OntimeRundown): OntimeEvent[] {
return rundown.filter((event) => isOntimeEvent(event)) as OntimeEvent[];
}