diff --git a/apps/client/src/common/api/ontimeApi.ts b/apps/client/src/common/api/ontimeApi.ts index ce18f3e03..8a6319b25 100644 --- a/apps/client/src/common/api/ontimeApi.ts +++ b/apps/client/src/common/api/ontimeApi.ts @@ -4,6 +4,7 @@ import { DatabaseModel, GetInfo, HttpSettings, + MessageResponse, OntimeRundown, OSCSettings, OscSubscription, @@ -13,7 +14,6 @@ import { UserFields, ViewSettings, } from 'ontime-types'; -import { MessageResponse } from 'ontime-types'; import { ExcelImportMap } from 'ontime-utils'; import { apiRepoLatest } from '../../externals'; diff --git a/apps/server/src/app.ts b/apps/server/src/app.ts index 406ae2d97..322ac2071 100644 --- a/apps/server/src/app.ts +++ b/apps/server/src/app.ts @@ -31,7 +31,6 @@ import { DataProvider } from './classes/data-provider/DataProvider.js'; import { dbLoadingProcess } from './modules/loadDb.js'; // Services -import { EventLoader } from './classes/event-loader/EventLoader.js'; import { integrationService } from './services/integration-service/IntegrationService.js'; import { logger } from './classes/Logger.js'; import { oscIntegration } from './services/integration-service/OscIntegration.js'; @@ -43,6 +42,7 @@ import { restoreService } from './services/RestoreService.js'; import { messageService } from './services/message-service/MessageService.js'; import { populateDemo } from './modules/loadDemo.js'; import { getState, updateNumEvents } from './stores/runtimeState.js'; +import { getNumEvents } from './services/rundown-service/RundownService.js'; console.log(`Starting Ontime version ${ONTIME_VERSION}`); @@ -184,7 +184,7 @@ export const startServer = async () => { runtimeService.init(maybeRestorePoint); // TODO: do this on the init of the runtime service - const numEvents = EventLoader.getNumEvents(); + const numEvents = getNumEvents(); updateNumEvents(numEvents); // eventStore set is a dependency of the services that publish to it diff --git a/apps/server/src/classes/event-loader/EventLoader.ts b/apps/server/src/classes/event-loader/EventLoader.ts deleted file mode 100644 index 037b12ef4..000000000 --- a/apps/server/src/classes/event-loader/EventLoader.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { OntimeEvent, isOntimeEvent } from 'ontime-types'; - -import { DataProvider } from '../data-provider/DataProvider.js'; - -/** - * Manages business logic around loading and finding events - */ -export class EventLoader { - // TODO: migrate logic to RundownService - - /** - * returns all events that contain time data - * @return {array} - */ - static getTimedEvents(): OntimeEvent[] { - return DataProvider.getRundown().filter((event) => isOntimeEvent(event)) as OntimeEvent[]; - } - - /** - * returns all events that can be loaded - * @return {array} - */ - static getPlayableEvents(): OntimeEvent[] { - return DataProvider.getRundown().filter((event) => isOntimeEvent(event) && !event.skip) as OntimeEvent[]; - } - - /** - * returns number of events - * @return {number} - */ - static getNumEvents(): number { - return EventLoader.getPlayableEvents().length; - } - - /** - * returns an event given its index after filtering for OntimeEvents - * @param {number} eventIndex - * @return {OntimeEvent | undefined} - */ - static getEventAtIndex(eventIndex: number): OntimeEvent | undefined { - const timedEvents = EventLoader.getTimedEvents(); - return timedEvents.at(eventIndex); - } - - /** - * returns an event given its id - * @param {string} eventId - * @return {object | undefined} - */ - static getEventWithId(eventId: string): OntimeEvent | undefined { - const timedEvents = EventLoader.getTimedEvents(); - return timedEvents.find((event) => event.id === eventId); - } - - /** - * returns first event given its cue - * @param {string} cue - * @return {object | undefined} - */ - static getEventWithCue(cue: string): OntimeEvent | undefined { - const timedEvents = EventLoader.getTimedEvents(); - return timedEvents.find((event) => event.cue.toLowerCase() === cue.toLowerCase()); - } - - /** - * finds the previous event - * @return {object | undefined} - */ - static findPrevious(currentEventId?: string): OntimeEvent | null { - const timedEvents = EventLoader.getPlayableEvents(); - if (!timedEvents || !timedEvents.length) { - return null; - } - - // if there is no event running, go to first - if (!currentEventId) { - return timedEvents.at(0) ?? null; - } - - const currentIndex = timedEvents.findIndex((event) => event.id === currentEventId); - const newIndex = Math.max(currentIndex - 1, 0); - const previousEvent = timedEvents.at(newIndex) ?? null; - return previousEvent; - } - - /** - * finds the next event - * @return {object | undefined} - */ - static findNext(currentEventId?: string): OntimeEvent | null { - const timedEvents = EventLoader.getPlayableEvents(); - if (!timedEvents || !timedEvents.length) { - return null; - } - - // if there is no event running, go to first - if (!currentEventId) { - return timedEvents.at(0) ?? null; - } - - const currentIndex = timedEvents.findIndex((event) => event.id === currentEventId); - const newIndex = (currentIndex + 1) % timedEvents.length; - const nextEvent = timedEvents.at(newIndex); - return nextEvent ?? null; - } -} diff --git a/apps/server/src/controllers/integrationController.config.ts b/apps/server/src/controllers/integrationController.config.ts index d65562dd5..1f50538d8 100644 --- a/apps/server/src/controllers/integrationController.config.ts +++ b/apps/server/src/controllers/integrationController.config.ts @@ -1,6 +1,5 @@ import { LogOrigin, OntimeEvent, isKeyOfType, isOntimeEvent } from 'ontime-types'; -import { EventLoader } from '../classes/event-loader/EventLoader.js'; -import { editEvent } from '../services/rundown-service/RundownService.js'; +import { editEvent, getEventWithId } from '../services/rundown-service/RundownService.js'; import { coerceString, coerceNumber, coerceBoolean, coerceColour } from '../utils/coerceType.js'; import { logger } from '../classes/Logger.js'; @@ -49,7 +48,7 @@ export function updateEvent( propertyName: keyof OntimeEvent, newValue: OntimeEvent[typeof propertyName], ) { - const event = EventLoader.getEventWithId(eventId); + const event = getEventWithId(eventId); if (event) { if (!isOntimeEvent(event)) { throw new Error('Can only update events'); diff --git a/apps/server/src/services/TimerService.ts b/apps/server/src/services/TimerService.ts index ff0af67d7..ea9104f42 100644 --- a/apps/server/src/services/TimerService.ts +++ b/apps/server/src/services/TimerService.ts @@ -5,7 +5,7 @@ import { integrationService } from './integration-service/IntegrationService.js' import { eventStore } from '../stores/EventStore.js'; import { restoreService } from './RestoreService.js'; import { runtimeService } from './runtime-service/RuntimeService.js'; -import { EventLoader } from '../classes/event-loader/EventLoader.js'; +import { getPlayableEvents } from './rundown-service/RundownService.js'; /** * Service manages Ontime's main timer @@ -71,7 +71,8 @@ export class TimerService { } if (doRoll) { - const rundown = EventLoader.getPlayableEvents(); + // TODO: escalate to parent + const rundown = getPlayableEvents(); runtimeState.roll(rundown); } diff --git a/apps/server/src/services/rundown-service/RundownService.ts b/apps/server/src/services/rundown-service/RundownService.ts index 30f8d9763..085057e03 100644 --- a/apps/server/src/services/rundown-service/RundownService.ts +++ b/apps/server/src/services/rundown-service/RundownService.ts @@ -10,7 +10,6 @@ import { import { generateId, getCueCandidate } from 'ontime-utils'; import { DataProvider } from '../../classes/data-provider/DataProvider.js'; import { block as blockDef, delay as delayDef } from '../../models/eventsDefinition.js'; -import { EventLoader } from '../../classes/event-loader/EventLoader.js'; import { sendRefetch } from '../../adapters/websocketAux.js'; import { runtimeCacheStore } from '../../stores/cachingStore.js'; import { @@ -168,7 +167,7 @@ export async function swapEvents(from: string, to: string) { * Called when we make changes to the rundown object */ function updateChangeNumEvents() { - const numEvents = EventLoader.getPlayableEvents().length; + const numEvents = getPlayableEvents().length; updateNumEvents(numEvents); } @@ -195,3 +194,99 @@ export function notifyChanges(options: { timer?: boolean | string[]; external?: sendRefetch(); } } + +/** + * returns all events of type OntimeEvent + * @return {array} + */ +export function getTimedEvents(): OntimeEvent[] { + return DataProvider.getRundown().filter((event) => isOntimeEvent(event)) as OntimeEvent[]; +} + +/** + * returns all events that can be loaded + * @return {array} + */ +export function getPlayableEvents(): OntimeEvent[] { + return DataProvider.getRundown().filter((event) => isOntimeEvent(event) && !event.skip) as OntimeEvent[]; +} + +/** + * returns number of events that can be loaded + * @return {number} + */ +export function getNumEvents(): number { + return getPlayableEvents().length; +} + +/** + * returns an event given its index after filtering for OntimeEvents + * @param {number} eventIndex + * @return {OntimeEvent | undefined} + */ +export function getEventAtIndex(eventIndex: number): OntimeEvent | undefined { + const timedEvents = getTimedEvents(); + return timedEvents.at(eventIndex); +} + +/** + * returns first event that matches a given ID + * @param {string} eventId + * @return {object | undefined} + */ +export function getEventWithId(eventId: string): OntimeEvent | undefined { + const timedEvents = getTimedEvents(); + return timedEvents.find((event) => event.id === eventId); +} + +/** + * returns first event that matches a given cue + * @param {string} cue + * @return {object | undefined} + */ +export function getEventWithCue(cue: string): OntimeEvent | undefined { + const timedEvents = getTimedEvents(); + return timedEvents.find((event) => event.cue.toLowerCase() === cue.toLowerCase()); +} + +/** + * finds the previous event + * @return {object | undefined} + */ +export function findPrevious(currentEventId?: string): OntimeEvent | null { + const timedEvents = getPlayableEvents(); + if (!timedEvents || !timedEvents.length) { + return null; + } + + // if there is no event running, go to first + if (!currentEventId) { + return timedEvents.at(0) ?? null; + } + + const currentIndex = timedEvents.findIndex((event) => event.id === currentEventId); + const newIndex = Math.max(currentIndex - 1, 0); + const previousEvent = timedEvents.at(newIndex) ?? null; + return previousEvent; +} + +/** + * finds the next event + * @return {object | undefined} + */ +export function findNext(currentEventId?: string): OntimeEvent | null { + const timedEvents = getPlayableEvents(); + if (!timedEvents || !timedEvents.length) { + return null; + } + + // if there is no event running, go to first + if (!currentEventId) { + return timedEvents.at(0) ?? null; + } + + const currentIndex = timedEvents.findIndex((event) => event.id === currentEventId); + const newIndex = (currentIndex + 1) % timedEvents.length; + const nextEvent = timedEvents.at(newIndex); + return nextEvent ?? null; +} diff --git a/apps/server/src/services/runtime-service/RuntimeService.ts b/apps/server/src/services/runtime-service/RuntimeService.ts index 1573b41e6..f9b1f80bc 100644 --- a/apps/server/src/services/runtime-service/RuntimeService.ts +++ b/apps/server/src/services/runtime-service/RuntimeService.ts @@ -1,11 +1,11 @@ import { LogOrigin, OntimeEvent, Playback } from 'ontime-types'; import { millisToString, validatePlayback } from 'ontime-utils'; -import { EventLoader } from '../../classes/event-loader/EventLoader.js'; import { TimerService } from '../TimerService.js'; import { logger } from '../../classes/Logger.js'; import { RestorePoint } from '../RestoreService.js'; import * as runtimeState from '../../stores/runtimeState.js'; +import { findNext, findPrevious, getEventAtIndex, getEventWithCue, getEventWithId, getPlayableEvents } from '../rundown-service/RundownService.js'; /** * Service manages runtime status of app @@ -50,7 +50,7 @@ class RuntimeService { } private isNewNext() { - const timedEvents = EventLoader.getPlayableEvents(); + const timedEvents = getPlayableEvents(); const state = runtimeState.getState(); const now = state.eventNow?.id; const next = state.eventNext?.id; @@ -114,7 +114,7 @@ class RuntimeService { this.roll(); } // load stuff again, but keep running if our events still exist - const eventNow = EventLoader.getEventWithId(state.eventNow.id); + const eventNow = getEventWithId(state.eventNow.id); if (eventNow) { runtimeState.reload(eventNow); } @@ -124,7 +124,7 @@ class RuntimeService { isNext = this.isNewNext(); if (isNext) { // TODO: do i need to load here? - const playableEvents = EventLoader.getPlayableEvents(); + const playableEvents = getPlayableEvents(); runtimeState.loadNext(playableEvents); } } @@ -140,7 +140,7 @@ class RuntimeService { return false; } - const timedEvents = EventLoader.getPlayableEvents(); + const timedEvents = getPlayableEvents(); const state = runtimeState.getState(); // TODO: return success boolean from runtimeState runtimeState.load(event, timedEvents); @@ -158,7 +158,7 @@ class RuntimeService { * @return {boolean} success - whether an event was loaded */ startById(eventId: string): boolean { - const event = EventLoader.getEventWithId(eventId); + const event = getEventWithId(eventId); const success = this.loadEvent(event); if (success) { this.start(); @@ -172,7 +172,7 @@ class RuntimeService { * @return {boolean} success - whether an event was loaded */ startByIndex(eventIndex: number): boolean { - const event = EventLoader.getEventAtIndex(eventIndex); + const event = getEventAtIndex(eventIndex); const success = this.loadEvent(event); if (success) { this.start(); @@ -186,7 +186,7 @@ class RuntimeService { * @return {boolean} success - whether an event was loaded */ startByCue(cue: string): boolean { - const event = EventLoader.getEventWithCue(cue); + const event = getEventWithCue(cue); const success = this.loadEvent(event); if (success) { this.start(); @@ -200,7 +200,7 @@ class RuntimeService { * @return {boolean} success - whether an event was loaded */ loadById(eventId: string): boolean { - const event = EventLoader.getEventWithId(eventId); + const event = getEventWithId(eventId); const success = this.loadEvent(event); return success; } @@ -211,7 +211,7 @@ class RuntimeService { * @return {boolean} success - whether an event was loaded */ loadByIndex(eventIndex: number): boolean { - const event = EventLoader.getEventAtIndex(eventIndex); + const event = getEventAtIndex(eventIndex); const success = this.loadEvent(event); return success; } @@ -222,7 +222,7 @@ class RuntimeService { * @return {boolean} success - whether an event was loaded */ loadByCue(cue: string): boolean { - const event = EventLoader.getEventWithCue(cue); + const event = getEventWithCue(cue); const success = this.loadEvent(event); return success; } @@ -233,7 +233,7 @@ class RuntimeService { */ loadPrevious(): boolean { const state = runtimeState.getState(); - const previousEvent = EventLoader.findPrevious(state.eventNow?.id); + const previousEvent = findPrevious(state.eventNow?.id); if (previousEvent) { const success = this.loadEvent(previousEvent); return success; @@ -247,7 +247,7 @@ class RuntimeService { */ loadNext(): boolean { const state = runtimeState.getState(); - const nextEvent = EventLoader.findNext(state.eventNow?.id); + const nextEvent = findNext(state.eventNow?.id); if (nextEvent) { const success = this.loadEvent(nextEvent); return success; @@ -317,7 +317,7 @@ class RuntimeService { * Sets playback to roll */ roll() { - const playableEvents = EventLoader.getPlayableEvents(); + const playableEvents = getPlayableEvents(); try { this.eventTimer.roll(playableEvents); } catch (error) { @@ -346,12 +346,12 @@ class RuntimeService { // 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 - const event = EventLoader.getEventWithId(selectedEventId); + const event = getEventWithId(selectedEventId); if (!event) { return; } - const timedEvents = EventLoader.getPlayableEvents(); + const timedEvents = getPlayableEvents(); runtimeState.resume(restorePoint, event, timedEvents); logger.info(LogOrigin.Playback, 'Resuming playback'); } diff --git a/apps/server/src/stores/__tests__/runtimeState.test.ts b/apps/server/src/stores/__tests__/runtimeState.test.ts index 0cd4a5236..bedbc6003 100644 --- a/apps/server/src/stores/__tests__/runtimeState.test.ts +++ b/apps/server/src/stores/__tests__/runtimeState.test.ts @@ -48,18 +48,16 @@ describe('mutation on runtimeState', () => { beforeEach(() => { clear(); - vi.mock('../../classes/event-loader/EventLoader.js', () => ({ - EventLoader: { - getPlayableEvents: vi.fn().mockReturnValue([ - { - id: 'mock', - cue: 'mock', - timeStart: 0, - timeEnd: 1000, - duration: 1000, - }, - ]), - }, + vi.mock('../../services/rundown-service/RundownService.js', () => ({ + getPlayableEvents: vi.fn().mockReturnValue([ + { + id: 'mock', + cue: 'mock', + timeStart: 0, + timeEnd: 1000, + duration: 1000, + }, + ]), })); }); diff --git a/apps/server/src/stores/runtimeState.ts b/apps/server/src/stores/runtimeState.ts index f1ce6434a..cf27cb3de 100644 --- a/apps/server/src/stores/runtimeState.ts +++ b/apps/server/src/stores/runtimeState.ts @@ -3,8 +3,8 @@ import { calculateDuration, dayInMs } from 'ontime-utils'; import { clock } from '../services/Clock.js'; import { RestorePoint } from '../services/RestoreService.js'; +import { getPlayableEvents } from '../services/rundown-service/RundownService.js'; import { getCurrent, getExpectedFinish, getRollTimers, skippedOutOfEvent, updateRoll } from '../services/timerUtils.js'; -import { EventLoader } from '../classes/event-loader/EventLoader.js'; import { timerConfig } from '../config/config.js'; const initialRuntime: Runtime = { @@ -102,7 +102,7 @@ function patchTimer(newState: Partial) { */ function fetchNumEvents(): number { // TODO: could we avoid having this dependency? - return EventLoader.getPlayableEvents().length; + return getPlayableEvents().length; } /**