mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-12 19:03:47 +00:00
Allow time change from api (#1009)
Co-authored-by: Carlos Valente <34649812+cpvalente@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
d8626ff324
commit
3895b37572
@@ -10,6 +10,10 @@ import { eventStore } from '../stores/EventStore.js';
|
||||
import * as assert from '../utils/assert.js';
|
||||
import { isEmptyObject } from '../utils/parserUtils.js';
|
||||
import { parseProperty, updateEvent } from './integration.utils.js';
|
||||
import { throttle } from '../utils/throttle.js';
|
||||
import { willCauseRegeneration } from '../services/rundown-service/rundownCacheUtils.js';
|
||||
|
||||
const throttledUpdateEvent = throttle(updateEvent, 20);
|
||||
|
||||
export function dispatchFromAdapter(type: string, payload: unknown, _source?: 'osc' | 'ws' | 'http') {
|
||||
const action = type.toLowerCase();
|
||||
@@ -43,6 +47,8 @@ const actionHandlers: Record<string, ActionHandler> = {
|
||||
const data = payload[id as keyof typeof payload];
|
||||
const patchEvent: Partial<OntimeEvent> & { id: string } = { id };
|
||||
|
||||
let shouldThrottle = false;
|
||||
|
||||
Object.entries(data).forEach(([property, value]) => {
|
||||
if (typeof property !== 'string' || value === undefined) {
|
||||
throw new Error('Invalid property or value');
|
||||
@@ -50,6 +56,9 @@ const actionHandlers: Record<string, ActionHandler> = {
|
||||
|
||||
const newObjectProperty = parseProperty(property, value);
|
||||
|
||||
const key = Object.keys(newObjectProperty)[0] as keyof OntimeEvent;
|
||||
shouldThrottle = willCauseRegeneration(key) || shouldThrottle;
|
||||
|
||||
if (patchEvent.custom && newObjectProperty.custom) {
|
||||
Object.assign(patchEvent.custom, newObjectProperty.custom);
|
||||
} else {
|
||||
@@ -57,8 +66,13 @@ const actionHandlers: Record<string, ActionHandler> = {
|
||||
}
|
||||
});
|
||||
|
||||
updateEvent(patchEvent);
|
||||
|
||||
if (shouldThrottle) {
|
||||
if (throttledUpdateEvent(patchEvent)) {
|
||||
return { payload: 'throttled' };
|
||||
}
|
||||
} else {
|
||||
updateEvent(patchEvent);
|
||||
}
|
||||
return { payload: 'success' };
|
||||
},
|
||||
/* Message Service */
|
||||
|
||||
@@ -1,24 +1,45 @@
|
||||
import { OntimeEvent, isKeyOfType, isOntimeEvent } from 'ontime-types';
|
||||
import { EndAction, OntimeEvent, TimerType, isKeyOfType, isOntimeEvent } from 'ontime-types';
|
||||
import { MILLIS_PER_SECOND, maxDuration } from 'ontime-utils';
|
||||
|
||||
import { DataProvider } from '../classes/data-provider/DataProvider.js';
|
||||
import { editEvent } from '../services/rundown-service/RundownService.js';
|
||||
import { getEventWithId } from '../services/rundown-service/rundownUtils.js';
|
||||
import { coerceBoolean, coerceColour, coerceNumber, coerceString } from '../utils/coerceType.js';
|
||||
import { coerceBoolean, coerceColour, coerceEnum, coerceNumber, coerceString } from '../utils/coerceType.js';
|
||||
|
||||
const whitelistedPayload = {
|
||||
/**
|
||||
*
|
||||
* @param {number} value time amount in seconds
|
||||
* @returns {number} time in milliseconds clamped to 0 and max duration
|
||||
*/
|
||||
function clampDuration(value: number) {
|
||||
const valueInMillis = value * MILLIS_PER_SECOND;
|
||||
if (valueInMillis > maxDuration || valueInMillis < 0) {
|
||||
throw new Error('Times should be from 0 to 23:59:59');
|
||||
}
|
||||
return valueInMillis;
|
||||
}
|
||||
|
||||
const propertyConversion = {
|
||||
title: coerceString,
|
||||
note: coerceString,
|
||||
cue: coerceString,
|
||||
|
||||
duration: (value: unknown) => Math.max(coerceNumber(value) * MILLIS_PER_SECOND, maxDuration),
|
||||
|
||||
isPublic: coerceBoolean,
|
||||
skip: coerceBoolean,
|
||||
|
||||
colour: coerceColour,
|
||||
|
||||
custom: coerceString,
|
||||
|
||||
timeWarning: (value: unknown) => clampDuration(coerceNumber(value)),
|
||||
timeDanger: (value: unknown) => clampDuration(coerceNumber(value)),
|
||||
|
||||
endAction: (value: unknown) => coerceEnum<EndAction>(value, EndAction),
|
||||
timerType: (value: unknown) => coerceEnum<TimerType>(value, TimerType),
|
||||
|
||||
duration: (value: unknown) => clampDuration(coerceNumber(value)),
|
||||
timeStart: (value: unknown) => clampDuration(coerceNumber(value)),
|
||||
timeEnd: (value: unknown) => clampDuration(coerceNumber(value)),
|
||||
};
|
||||
|
||||
export function parseProperty(property: string, value: unknown) {
|
||||
@@ -27,13 +48,13 @@ export function parseProperty(property: string, value: unknown) {
|
||||
if (!(customKey in DataProvider.getCustomFields())) {
|
||||
throw new Error(`Custom field ${customKey} not found`);
|
||||
}
|
||||
const parserFn = whitelistedPayload.custom;
|
||||
const parserFn = propertyConversion.custom;
|
||||
return { custom: { [customKey]: parserFn(value) } };
|
||||
}
|
||||
if (!isKeyOfType(property, whitelistedPayload)) {
|
||||
if (!isKeyOfType(property, propertyConversion)) {
|
||||
throw new Error(`Property ${property} not permitted`);
|
||||
}
|
||||
const parserFn = whitelistedPayload[property];
|
||||
const parserFn = propertyConversion[property];
|
||||
return { [property]: parserFn(value) };
|
||||
}
|
||||
|
||||
@@ -50,6 +71,5 @@ export function updateEvent(patchEvent: Partial<OntimeEvent> & { id: string }) {
|
||||
if (!isOntimeEvent(event)) {
|
||||
throw new Error('Can only update events');
|
||||
}
|
||||
|
||||
editEvent(patchEvent);
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ export function handleCustomField(
|
||||
}
|
||||
|
||||
/** List of event properties which do not need the rundown to be regenerated */
|
||||
enum regenerateWhitelist {
|
||||
export enum regenerateWhitelist {
|
||||
'id',
|
||||
'cue',
|
||||
'title',
|
||||
@@ -122,6 +122,14 @@ export function isDataStale(patch: Partial<OntimeRundownEntry>): boolean {
|
||||
return Object.keys(patch).some((key) => !(key in regenerateWhitelist));
|
||||
}
|
||||
|
||||
/**
|
||||
* given a key, returns whether it is whitelisted
|
||||
* @param path
|
||||
*/
|
||||
export function willCauseRegeneration(key: keyof OntimeEvent): boolean {
|
||||
return !(key in regenerateWhitelist);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an event and a patch to that event checks whether there are actual changes to the dataset
|
||||
* @param existingEvent
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { coerceColour } from '../coerceType.js';
|
||||
import { coerceColour, coerceEnum } from '../coerceType.js';
|
||||
|
||||
describe('parses a colour string that is', () => {
|
||||
it('valid hex', () => {
|
||||
@@ -19,3 +19,21 @@ describe('parses a colour string that is', () => {
|
||||
expect(() => coerceColour(5)).toThrowError(Error('Invalid colour value received'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('match a string to an enum that is', () => {
|
||||
enum testEnum {
|
||||
'abc',
|
||||
'def',
|
||||
'ghi',
|
||||
}
|
||||
it('valid key', () => {
|
||||
const key = coerceEnum<testEnum>('abc', testEnum);
|
||||
expect(key).toBe('abc');
|
||||
});
|
||||
it('invalid key', () => {
|
||||
expect(() => coerceEnum('123', testEnum)).toThrowError(Error('Invalid value received'));
|
||||
});
|
||||
it('invalid type', () => {
|
||||
expect(() => coerceEnum(123, testEnum)).toThrowError(Error('Invalid value received'));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
import { isColourHex } from 'ontime-utils';
|
||||
|
||||
/**
|
||||
* @description Converts a value to an item in the provided enume.
|
||||
* @param {unknown} value - Value to be converted.
|
||||
* @returns {T} - The converted value as key of the enum.
|
||||
* @throws {Error} Throws an error value is not found in the enum.
|
||||
*/
|
||||
export function coerceEnum<T>(value: unknown, list: object): T {
|
||||
if (typeof value !== 'string' || !(value in list)) {
|
||||
throw new Error('Invalid value received');
|
||||
}
|
||||
return value as T;
|
||||
}
|
||||
|
||||
//TODO: write tests
|
||||
/**
|
||||
* @description Converts a value to a string if possible, throws otherwise
|
||||
|
||||
@@ -22,11 +22,12 @@ export function throttle<T extends any[], U>(cb: (...args: T) => U, delay: numbe
|
||||
return (...args: T) => {
|
||||
if (shouldWait) {
|
||||
waitingArgs = args;
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
cb(...args);
|
||||
shouldWait = true;
|
||||
setTimeout(timeoutFunc, delay);
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user