mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-21 23:19:09 +00:00
add relevantBlock function (#1100)
* feat: current block * pass full rundown to functions and use filter utils
This commit is contained in:
committed by
GitHub
parent
20838c038a
commit
158ef05ff0
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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[];
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user