mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-11 02:13:48 +00:00
refactor: small tweaks and fixes
This commit is contained in:
@@ -85,13 +85,13 @@ const actionHandlers: Record<string, ActionHandler> = {
|
||||
return { payload: 'success' };
|
||||
}
|
||||
if ('id' in payload) {
|
||||
assert.isString(payload);
|
||||
runtimeService.startById(payload);
|
||||
assert.isString(payload.id);
|
||||
runtimeService.startById(payload.id);
|
||||
return { payload: 'success' };
|
||||
}
|
||||
if ('cue' in payload) {
|
||||
assert.isString(payload);
|
||||
runtimeService.startByCue(payload);
|
||||
assert.isString(payload.cue);
|
||||
runtimeService.startByCue(payload.cue);
|
||||
return { payload: 'success' };
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -48,7 +48,7 @@ const connectSocket = () => {
|
||||
// we only need to read message type of ontime
|
||||
if (type === 'ontime') {
|
||||
// destructure known data from ontime
|
||||
// see https://cpvalente.gitbook.io/ontime/control-and-feedback/websocket-api
|
||||
// see https://docs.getontime.no/api/osc-and-ws/
|
||||
const { timer, playback } = payload;
|
||||
const timerElement = document.getElementById('timer');
|
||||
if (playback == 'stop') {
|
||||
|
||||
+2
-2
@@ -10,5 +10,5 @@
|
||||
|
||||
<body>
|
||||
<div id="timer"></div>
|
||||
<script src="./app.js" type="module"></script>
|
||||
</html>
|
||||
<script src="./app.js" type="text/javascript"></script>
|
||||
</html>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable no-console */
|
||||
import { initAssets, startIntegrations, startOSCServer, startServer } from './app.js';
|
||||
|
||||
async function startOntime() {
|
||||
|
||||
@@ -142,17 +142,18 @@ function broadcastResult(_target: any, _propertyKey: string, descriptor: Propert
|
||||
const isTimeToUpdate = state.clock - TimerService.previousUpdate >= TimerService._updateInterval;
|
||||
|
||||
// some changes need an immediate update
|
||||
const hasNewLoaded = state.eventNow?.id !== TimerService.previousState?.eventNow?.id;
|
||||
const hasSkippedBack = state.clock < TimerService.previousUpdate;
|
||||
const justStarted = !TimerService.previousState?.timer;
|
||||
const hasChangedPlayback = TimerService.previousState.timer?.playback !== state.timer.playback;
|
||||
const hasImmediateChanges = hasSkippedBack || justStarted || hasChangedPlayback;
|
||||
const hasImmediateChanges = hasNewLoaded || hasSkippedBack || justStarted || hasChangedPlayback;
|
||||
|
||||
if (hasImmediateChanges || (isTimeToUpdate && !deepEqual(TimerService.previousState?.timer, state.timer))) {
|
||||
eventStore.set('timer', state.timer);
|
||||
TimerService.previousState.timer = { ...state.timer };
|
||||
}
|
||||
|
||||
if (isTimeToUpdate && !deepEqual(TimerService.previousState?.runtime, state.runtime)) {
|
||||
if (hasChangedPlayback || (isTimeToUpdate && !deepEqual(TimerService.previousState?.runtime, state.runtime))) {
|
||||
eventStore.set('runtime', state.runtime);
|
||||
TimerService.previousState.runtime = { ...state.runtime };
|
||||
}
|
||||
|
||||
@@ -1387,6 +1387,9 @@ describe('getRuntimeOffset()', () => {
|
||||
_timer: {
|
||||
pausedAt: null,
|
||||
},
|
||||
runtime: {
|
||||
actualStart: 150,
|
||||
},
|
||||
} as RuntimeState;
|
||||
|
||||
const offset = getRuntimeOffset(state);
|
||||
@@ -1408,6 +1411,9 @@ describe('getRuntimeOffset()', () => {
|
||||
_timer: {
|
||||
pausedAt: null,
|
||||
},
|
||||
runtime: {
|
||||
actualStart: 100,
|
||||
},
|
||||
} as RuntimeState;
|
||||
|
||||
const offset = getRuntimeOffset(state);
|
||||
@@ -1430,9 +1436,116 @@ describe('getRuntimeOffset()', () => {
|
||||
_timer: {
|
||||
pausedAt: 125,
|
||||
},
|
||||
runtime: {
|
||||
actualStart: 100,
|
||||
},
|
||||
} as RuntimeState;
|
||||
|
||||
const offset = getRuntimeOffset(state);
|
||||
expect(offset).toBe(25);
|
||||
});
|
||||
|
||||
it('can only count once started', () => {
|
||||
const state = {
|
||||
clock: 78480789,
|
||||
eventNow: {
|
||||
id: 'd6a2ce',
|
||||
type: 'event',
|
||||
title: '',
|
||||
timeStart: 77400000,
|
||||
timeEnd: 81000000,
|
||||
duration: 3600000,
|
||||
timeStrategy: 'lock-duration',
|
||||
linkStart: null,
|
||||
endAction: 'none',
|
||||
timerType: 'count-down',
|
||||
isPublic: true,
|
||||
skip: false,
|
||||
note: '',
|
||||
colour: '',
|
||||
cue: '1',
|
||||
revision: 0,
|
||||
timeWarning: 120000,
|
||||
timeDanger: 60000,
|
||||
custom: {},
|
||||
delay: 0,
|
||||
},
|
||||
runtime: {
|
||||
selectedEventIndex: 0,
|
||||
numEvents: 2,
|
||||
offset: -77400000,
|
||||
plannedStart: 77400000,
|
||||
plannedEnd: 84600000,
|
||||
actualStart: null,
|
||||
expectedEnd: null,
|
||||
},
|
||||
timer: {
|
||||
addedTime: 0,
|
||||
current: 3600000,
|
||||
duration: 3600000,
|
||||
elapsed: null,
|
||||
expectedFinish: null,
|
||||
finishedAt: null,
|
||||
playback: 'armed',
|
||||
secondaryTimer: null,
|
||||
startedAt: null,
|
||||
},
|
||||
_timer: { pausedAt: null, secondaryTarget: null, finishedNow: false },
|
||||
} as RuntimeState;
|
||||
|
||||
const offset = getRuntimeOffset(state);
|
||||
expect(offset).toBe(null);
|
||||
});
|
||||
|
||||
it('handles loaded event', () => {
|
||||
const state = {
|
||||
clock: 79521653,
|
||||
eventNow: {
|
||||
id: '835242',
|
||||
type: 'event',
|
||||
title: '',
|
||||
timeStart: 81000000,
|
||||
timeEnd: 84600000,
|
||||
duration: 3600000,
|
||||
timeStrategy: 'lock-duration',
|
||||
linkStart: null,
|
||||
endAction: 'none',
|
||||
timerType: 'count-down',
|
||||
isPublic: true,
|
||||
skip: false,
|
||||
note: '',
|
||||
colour: '',
|
||||
cue: '2',
|
||||
revision: 0,
|
||||
timeWarning: 120000,
|
||||
timeDanger: 60000,
|
||||
custom: {},
|
||||
delay: 0,
|
||||
},
|
||||
runtime: {
|
||||
selectedEventIndex: 1,
|
||||
numEvents: 2,
|
||||
offset: -81000000,
|
||||
plannedStart: 77400000,
|
||||
plannedEnd: 84600000,
|
||||
actualStart: 79443403,
|
||||
expectedEnd: null,
|
||||
},
|
||||
timer: {
|
||||
addedTime: 0,
|
||||
current: 3600000,
|
||||
duration: 3600000,
|
||||
elapsed: null,
|
||||
expectedFinish: null,
|
||||
finishedAt: null,
|
||||
playback: 'armed',
|
||||
secondaryTimer: null,
|
||||
startedAt: null,
|
||||
},
|
||||
_timer: { pausedAt: null, secondaryTarget: null, finishedNow: false },
|
||||
} as RuntimeState;
|
||||
|
||||
const offset = getRuntimeOffset(state);
|
||||
expect(offset).toBe(79521653 - 81000000);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,6 +14,7 @@ import { appStateService } from '../app-state-service/AppStateService.js';
|
||||
import { ensureDirectory, getFilesFromFolder, removeFileExtension } from '../../utils/fileManagement.js';
|
||||
import { dbModel } from '../../models/dataModel.js';
|
||||
import { deleteFile } from '../../utils/parserUtils.js';
|
||||
import { switchDb } from '../../setup/loadDb.js';
|
||||
|
||||
// init dependencies
|
||||
init();
|
||||
@@ -40,6 +41,9 @@ export async function applyProjectFile(filePath: string, options?: Options) {
|
||||
const newFilePath = join(resolveProjectsDirectory, filename);
|
||||
await rename(filePath, newFilePath);
|
||||
|
||||
// change LowDB to point to new file
|
||||
await switchDb(filename);
|
||||
|
||||
// apply data model
|
||||
await applyDataModel(data, options);
|
||||
|
||||
@@ -135,6 +139,9 @@ export async function createProjectFile(filename: string, projectData: ProjectDa
|
||||
const newFile = join(resolveProjectsDirectory, filename);
|
||||
await writeFile(newFile, JSON.stringify(data));
|
||||
|
||||
// change LowDB to point to new file
|
||||
await switchDb(filename);
|
||||
|
||||
// apply its data
|
||||
await applyDataModel(data);
|
||||
|
||||
|
||||
@@ -191,10 +191,7 @@ class RuntimeService {
|
||||
}
|
||||
|
||||
const timedEvents = getPlayableEvents();
|
||||
const state = runtimeState.getState();
|
||||
// TODO: return success boolean from runtimeState, when we work with optimising integrations
|
||||
runtimeState.load(event, timedEvents);
|
||||
const success = event.id === state.eventNow?.id;
|
||||
const success = runtimeState.load(event, timedEvents);
|
||||
|
||||
if (success) {
|
||||
integrationService.dispatch(TimerLifeCycle.onLoad);
|
||||
|
||||
@@ -292,20 +292,27 @@ export const updateRoll = (state: RuntimeState) => {
|
||||
|
||||
/**
|
||||
* Calculates difference between the runtime and the schedule of an event
|
||||
* Positive offset is a delay
|
||||
* Negative offset is time ahead
|
||||
* @param state
|
||||
* @returns
|
||||
*/
|
||||
export function getRuntimeOffset(state: RuntimeState): number {
|
||||
if (state.eventNow === null) {
|
||||
return 0;
|
||||
export function getRuntimeOffset(state: RuntimeState): MaybeNumber {
|
||||
if (state.runtime.actualStart === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { clock } = state;
|
||||
const { timeStart } = state.eventNow;
|
||||
const { addedTime, current, startedAt } = state.timer;
|
||||
|
||||
// if we havent started, the offset is the difference to the schedule
|
||||
if (startedAt === null) {
|
||||
return clock - timeStart;
|
||||
}
|
||||
|
||||
const overtime = Math.min(current, 0);
|
||||
const startOffset = startedAt - timeStart;
|
||||
const pausedTime = state._timer.pausedAt === null ? 0 : state.clock - state._timer.pausedAt;
|
||||
const pausedTime = state._timer.pausedAt === null ? 0 : clock - state._timer.pausedAt;
|
||||
|
||||
return startOffset + addedTime + pausedTime + Math.abs(overtime);
|
||||
}
|
||||
|
||||
@@ -97,7 +97,8 @@ const lastLoadedProject = isTest ? 'db.json' : getLastLoadedProject();
|
||||
|
||||
// path to public db
|
||||
export const resolveDbDirectory = join(testDbStartDirectory, isTest ? `../${config.database.testdb}` : config.projects);
|
||||
export const resolveDbPath = join(resolveDbDirectory, lastLoadedProject ? lastLoadedProject : config.database.filename);
|
||||
export const resolveDbName = lastLoadedProject ? lastLoadedProject : config.database.filename;
|
||||
export const resolveDbPath = join(resolveDbDirectory, resolveDbName);
|
||||
|
||||
export const pathToStartDb = isTest
|
||||
? join(srcDirectory, '..', config.database.testdb, config.database.filename)
|
||||
|
||||
@@ -8,7 +8,7 @@ import { join } from 'path';
|
||||
import { ensureDirectory } from '../utils/fileManagement.js';
|
||||
import { dbModel } from '../models/dataModel.js';
|
||||
|
||||
import { pathToStartDb, resolveDbDirectory, resolveDbPath } from './index.js';
|
||||
import { pathToStartDb, resolveDbDirectory, resolveDbName } from './index.js';
|
||||
import { parseProjectFile } from '../services/project-service/projectFileUtils.js';
|
||||
import { parseJson } from '../utils/parser.js';
|
||||
|
||||
@@ -16,25 +16,25 @@ import { parseJson } from '../utils/parser.js';
|
||||
* @description ensures directories exist and populates database
|
||||
* @return {string} - path to db file
|
||||
*/
|
||||
const populateDb = (): string => {
|
||||
// if everything goes well, the DB in disk is the one loaded
|
||||
let dbInDisk = resolveDbPath;
|
||||
ensureDirectory(resolveDbDirectory);
|
||||
const populateDb = (directory: string, filename: string): string => {
|
||||
ensureDirectory(directory);
|
||||
let dbPath = join(directory, filename);
|
||||
|
||||
// if everything goes well, the DB in disk is the one loaded
|
||||
// if dbInDisk doesn't exist we want to use startup db
|
||||
if (!existsSync(dbInDisk)) {
|
||||
if (!existsSync(dbPath)) {
|
||||
try {
|
||||
const dbDirectory = resolveDbDirectory;
|
||||
const newFileDirectory = join(dbDirectory, pathToStartDb.split('/').pop());
|
||||
|
||||
copyFileSync(pathToStartDb, newFileDirectory);
|
||||
dbInDisk = newFileDirectory;
|
||||
dbPath = newFileDirectory;
|
||||
} catch (_) {
|
||||
/* we do not handle this */
|
||||
}
|
||||
}
|
||||
|
||||
return dbInDisk;
|
||||
return dbPath;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -55,10 +55,9 @@ const parseDatabase = async (fileToRead: string, adapterToUse: Low<DatabaseModel
|
||||
|
||||
/**
|
||||
* @description loads ontime db
|
||||
* @return {Promise<{data: (*), db: Low<unknown>}>}
|
||||
*/
|
||||
async function loadDb() {
|
||||
const dbInDisk = populateDb();
|
||||
async function loadDb(directory: string, filename: string) {
|
||||
const dbInDisk = populateDb(directory, filename);
|
||||
|
||||
const adapter = new JSONFile<DatabaseModel>(dbInDisk);
|
||||
const db = new Low(adapter, dbModel);
|
||||
@@ -72,12 +71,24 @@ async function loadDb() {
|
||||
|
||||
export let db = {} as Low<DatabaseModel>;
|
||||
export let data = {} as DatabaseModel;
|
||||
export const dbLoadingProcess = loadDb();
|
||||
export const dbLoadingProcess = loadDb(resolveDbDirectory, resolveDbName);
|
||||
|
||||
/**
|
||||
* Initialises database at known location
|
||||
*/
|
||||
const init = async () => {
|
||||
const dbProvider = await dbLoadingProcess;
|
||||
db = dbProvider.db;
|
||||
data = dbProvider.data;
|
||||
};
|
||||
|
||||
/**
|
||||
* Allows to switch the database to a new file
|
||||
*/
|
||||
export const switchDb = async (newFileName: string) => {
|
||||
const { db: newDb, data: newData } = await loadDb(resolveDbDirectory, newFileName);
|
||||
db = newDb;
|
||||
data = newData;
|
||||
};
|
||||
|
||||
init();
|
||||
|
||||
@@ -177,7 +177,7 @@ describe('mutation on runtimeState', () => {
|
||||
stop();
|
||||
newState = getState();
|
||||
expect(newState.runtime.actualStart).toBeNull();
|
||||
expect(newState.runtime.offset).toBe(0);
|
||||
expect(newState.runtime.offset).toBeNull();
|
||||
expect(newState.runtime.expectedEnd).toBeNull();
|
||||
});
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import { timerConfig } from '../config/config.js';
|
||||
const initialRuntime: Runtime = {
|
||||
selectedEventIndex: null,
|
||||
numEvents: 0,
|
||||
offset: 0,
|
||||
offset: null,
|
||||
plannedStart: 0,
|
||||
plannedEnd: 0,
|
||||
actualStart: null,
|
||||
@@ -128,7 +128,11 @@ export function updateRundownData(playableRundown: OntimeEvent[]) {
|
||||
* @param rundown
|
||||
* @param initialData
|
||||
*/
|
||||
export function load(event: OntimeEvent, rundown: OntimeEvent[], initialData?: Partial<TimerState & RestorePoint>) {
|
||||
export function load(
|
||||
event: OntimeEvent,
|
||||
rundown: OntimeEvent[],
|
||||
initialData?: Partial<TimerState & RestorePoint>,
|
||||
): boolean {
|
||||
clear();
|
||||
|
||||
updateRundownData(rundown);
|
||||
@@ -153,9 +157,11 @@ export function load(event: OntimeEvent, rundown: OntimeEvent[], initialData?: P
|
||||
if (firstStart === null || typeof firstStart === 'number') {
|
||||
runtimeState.runtime.actualStart = firstStart;
|
||||
runtimeState.runtime.offset = getRuntimeOffset(runtimeState);
|
||||
runtimeState.runtime.expectedEnd = runtimeState.runtime.plannedEnd + runtimeState.runtime.offset;
|
||||
runtimeState.runtime.expectedEnd = (runtimeState.runtime.plannedEnd + runtimeState.runtime.offset) % dayInMs;
|
||||
}
|
||||
}
|
||||
|
||||
return event.id === runtimeState.eventNow?.id;
|
||||
}
|
||||
|
||||
export function loadNow(event: OntimeEvent, playableEvents: OntimeEvent[]) {
|
||||
@@ -329,7 +335,9 @@ export function addTime(amount: number) {
|
||||
|
||||
// update runtime delays: over - under
|
||||
runtimeState.runtime.offset = getRuntimeOffset(runtimeState);
|
||||
runtimeState.runtime.expectedEnd = runtimeState.runtime.plannedEnd + runtimeState.runtime.offset;
|
||||
if (runtimeState.runtime.offset !== null) {
|
||||
runtimeState.runtime.expectedEnd = (runtimeState.runtime.plannedEnd + runtimeState.runtime.offset) % dayInMs;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,8 +15,6 @@ import {
|
||||
OntimeEvent,
|
||||
OntimeRundown,
|
||||
SupportedEvent,
|
||||
EndAction,
|
||||
TimerType,
|
||||
TimeStrategy,
|
||||
CustomFields,
|
||||
EventCustomFields,
|
||||
@@ -325,8 +323,8 @@ export function createPatch(originalEvent: OntimeEvent, patchEvent: Partial<Onti
|
||||
duration,
|
||||
timeStrategy,
|
||||
linkStart: validateLinkStart(maybeLinkStart),
|
||||
endAction: validateEndAction(patchEvent.endAction, EndAction.None),
|
||||
timerType: validateTimerType(patchEvent.timerType, TimerType.CountDown),
|
||||
endAction: validateEndAction(patchEvent.endAction, originalEvent.endAction),
|
||||
timerType: validateTimerType(patchEvent.timerType, originalEvent.timerType),
|
||||
isPublic: typeof patchEvent.isPublic === 'boolean' ? patchEvent.isPublic : originalEvent.isPublic,
|
||||
skip: typeof patchEvent.skip === 'boolean' ? patchEvent.skip : originalEvent.skip,
|
||||
note: makeString(patchEvent.note, originalEvent.note),
|
||||
|
||||
Reference in New Issue
Block a user