mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-11 18:33:53 +00:00
Typescript null (#869)
* refactor: improve null checking * fix: return result of mutation
This commit is contained in:
@@ -116,16 +116,14 @@ export class SocketServer implements IAdapter {
|
||||
|
||||
// Protocol specific stuff handled above
|
||||
try {
|
||||
const reply = dispatchFromAdapter(
|
||||
type,
|
||||
{
|
||||
payload,
|
||||
},
|
||||
'ws',
|
||||
);
|
||||
const reply = dispatchFromAdapter(type, { payload }, 'ws');
|
||||
if (reply) {
|
||||
const { payload } = reply;
|
||||
ws.send(type, payload);
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
type: 'ontime-change',
|
||||
payload: reply.payload,
|
||||
}),
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(LogOrigin.Rx, `WS IN: ${error}`);
|
||||
|
||||
@@ -3,10 +3,13 @@
|
||||
* Google Sheets
|
||||
*/
|
||||
|
||||
import { CustomFields, OntimeRundown } from 'ontime-types';
|
||||
import { ImportMap } from 'ontime-utils';
|
||||
|
||||
import { extname } from 'path';
|
||||
import { existsSync } from 'fs';
|
||||
import { ImportMap } from 'ontime-utils';
|
||||
import xlsx from 'node-xlsx';
|
||||
|
||||
import { parseExcel } from '../../utils/parser.js';
|
||||
import { parseCustomFields, parseRundown } from '../../utils/parserFunctions.js';
|
||||
import { deleteFile } from '../../utils/parserUtils.js';
|
||||
@@ -29,7 +32,7 @@ export function listWorksheets() {
|
||||
return excelData.map((value) => value.name);
|
||||
}
|
||||
|
||||
export function generateRundownPreview(options: ImportMap) {
|
||||
export function generateRundownPreview(options: ImportMap): { rundown: OntimeRundown; customFields: CustomFields } {
|
||||
const data = excelData.find(({ name }) => name.toLowerCase() === options.worksheet.toLowerCase())?.data;
|
||||
|
||||
if (!data) {
|
||||
@@ -39,15 +42,14 @@ export function generateRundownPreview(options: ImportMap) {
|
||||
const dataFromExcel = parseExcel(data, options);
|
||||
|
||||
// we run the parsed data through an extra step to ensure the objects shape
|
||||
const result = { rundown: [], customFields: {} };
|
||||
result.rundown = parseRundown(dataFromExcel);
|
||||
if (result.rundown.length < 1) {
|
||||
const rundown = parseRundown(dataFromExcel);
|
||||
if (rundown.length === 0) {
|
||||
throw new Error(`Could not find data to import in the worksheet: ${options.worksheet}`);
|
||||
}
|
||||
result.customFields = parseCustomFields(dataFromExcel);
|
||||
const customFields = parseCustomFields(dataFromExcel);
|
||||
|
||||
//clear the data
|
||||
// clear the data
|
||||
excelData = [];
|
||||
|
||||
return result;
|
||||
return { rundown, customFields };
|
||||
}
|
||||
|
||||
@@ -64,6 +64,7 @@ export class SimpleTimer {
|
||||
|
||||
public update(timeNow: number): SimpleTimerState {
|
||||
if (this.state.playback === SimplePlayback.Start) {
|
||||
// we know startedAt is not null since we are in play mode
|
||||
const elapsed = timeNow - this.startedAt;
|
||||
if (this.state.direction === SimpleDirection.CountDown) {
|
||||
this.state.current = this.state.duration - elapsed;
|
||||
|
||||
@@ -64,7 +64,7 @@ export class RestoreService {
|
||||
private readonly filePath: MaybeString;
|
||||
private readonly file: JSONFile<RestorePoint | null>;
|
||||
private failedCreateAttempts: number;
|
||||
private savedState: RestorePoint;
|
||||
private savedState: RestorePoint | null;
|
||||
|
||||
constructor(filePath: string) {
|
||||
this.filePath = filePath;
|
||||
|
||||
@@ -75,7 +75,7 @@ export async function getProjectFiles(): Promise<ProjectFile[]> {
|
||||
const allFiles = await getFilesFromFolder(resolveProjectsDirectory);
|
||||
const filteredFiles = filterProjectFiles(allFiles);
|
||||
|
||||
const projectFiles = [];
|
||||
const projectFiles: ProjectFile[] = [];
|
||||
for (const file of filteredFiles) {
|
||||
const filePath = join(resolveProjectsDirectory, file);
|
||||
const stats = await stat(filePath);
|
||||
|
||||
@@ -22,20 +22,32 @@ import { runtimeService } from '../runtime-service/RuntimeService.js';
|
||||
import * as cache from './rundownCache.js';
|
||||
import { getPlayableEvents } from './rundownUtils.js';
|
||||
|
||||
function generateEvent(eventData: Partial<OntimeEvent> | Partial<OntimeDelay> | Partial<OntimeBlock>) {
|
||||
// we discard any UI provided events and add our own
|
||||
type PatchWithId = (Partial<OntimeEvent> | Partial<OntimeBlock> | Partial<OntimeDelay>) & { id: string };
|
||||
|
||||
type CompleteEntry<T> = T extends Partial<OntimeEvent>
|
||||
? OntimeEvent
|
||||
: T extends Partial<OntimeDelay>
|
||||
? OntimeDelay
|
||||
: T extends Partial<OntimeBlock>
|
||||
? OntimeBlock
|
||||
: never;
|
||||
|
||||
function generateEvent<T extends Partial<OntimeEvent> | Partial<OntimeDelay> | Partial<OntimeBlock>>(
|
||||
eventData: T,
|
||||
): CompleteEntry<T> {
|
||||
// we discard any UI provided IDs and add our own
|
||||
const id = cache.getUniqueId();
|
||||
|
||||
if (isOntimeEvent(eventData)) {
|
||||
return createEvent(eventData, getCueCandidate(cache.getPersistedRundown(), eventData?.after)) as OntimeEvent;
|
||||
return createEvent(eventData, getCueCandidate(cache.getPersistedRundown(), eventData?.after)) as CompleteEntry<T>;
|
||||
}
|
||||
|
||||
if (isOntimeDelay(eventData)) {
|
||||
return { ...delayDef, duration: eventData.duration ?? 0, id } as OntimeDelay;
|
||||
return { ...delayDef, duration: eventData.duration ?? 0, id } as CompleteEntry<T>;
|
||||
}
|
||||
|
||||
if (isOntimeBlock(eventData)) {
|
||||
return { ...blockDef, title: eventData?.title ?? '', id } as OntimeBlock;
|
||||
return { ...blockDef, title: eventData?.title ?? '', id } as CompleteEntry<T>;
|
||||
}
|
||||
|
||||
throw new Error('Invalid event type');
|
||||
@@ -46,9 +58,7 @@ function generateEvent(eventData: Partial<OntimeEvent> | Partial<OntimeDelay> |
|
||||
* @param {object} eventData
|
||||
* @return {OntimeRundownEntry}
|
||||
*/
|
||||
export async function addEvent(
|
||||
eventData: Partial<OntimeEvent> | Partial<OntimeDelay> | Partial<OntimeBlock>,
|
||||
): Promise<OntimeRundownEntry> {
|
||||
export async function addEvent(eventData: PatchWithId & { after?: string }): Promise<OntimeRundownEntry> {
|
||||
// if the user didnt provide an index, we add the event to start
|
||||
let atIndex = 0;
|
||||
if (eventData?.after !== undefined) {
|
||||
@@ -62,15 +72,16 @@ export async function addEvent(
|
||||
|
||||
// generate a fully formed event from the patch
|
||||
const eventToAdd = generateEvent(eventData);
|
||||
|
||||
// modify rundown
|
||||
const scopedMutation = cache.mutateCache(cache.add);
|
||||
const { newEvent } = await scopedMutation({ atIndex, event: eventToAdd as OntimeRundownEntry });
|
||||
const { newEvent } = await scopedMutation({ atIndex, event: eventToAdd });
|
||||
|
||||
// notify runtime that rundown has changed
|
||||
updateRuntimeOnChange();
|
||||
|
||||
// notify timer and external services of change
|
||||
notifyChanges({ timer: [newEvent.id], external: true });
|
||||
notifyChanges({ timer: [eventData.id], external: true });
|
||||
|
||||
return newEvent;
|
||||
}
|
||||
@@ -81,7 +92,11 @@ export async function addEvent(
|
||||
*/
|
||||
export async function deleteEvent(eventId: string) {
|
||||
const scopedMutation = cache.mutateCache(cache.remove);
|
||||
await scopedMutation({ eventId });
|
||||
const { didMutate } = await scopedMutation({ eventId });
|
||||
|
||||
if (didMutate === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
// notify runtime that rundown has changed
|
||||
updateRuntimeOnChange();
|
||||
@@ -108,14 +123,18 @@ export async function deleteAllEvents() {
|
||||
* Apply patch to an element in rundown
|
||||
* @param patch
|
||||
*/
|
||||
export async function editEvent(patch: Partial<OntimeEvent> | Partial<OntimeBlock> | Partial<OntimeDelay>) {
|
||||
export async function editEvent(patch: PatchWithId) {
|
||||
if (isOntimeEvent(patch) && patch?.cue === '') {
|
||||
throw new Error('Cue value invalid');
|
||||
}
|
||||
|
||||
const scopedMutation = cache.mutateCache(cache.edit);
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- we know patch has an id
|
||||
const { newEvent } = await scopedMutation({ patch, eventId: patch.id! });
|
||||
const { newEvent, didMutate } = await scopedMutation({ patch, eventId: patch.id });
|
||||
|
||||
// short circuit if nothing changed
|
||||
if (didMutate === false) {
|
||||
return newEvent;
|
||||
}
|
||||
|
||||
// notify runtime that rundown has changed
|
||||
updateRuntimeOnChange();
|
||||
|
||||
@@ -83,7 +83,7 @@ export function generate(
|
||||
|
||||
let accumulatedDelay = 0;
|
||||
let daySpan = 0;
|
||||
let previousEnd: number;
|
||||
let previousEnd: MaybeNumber = null;
|
||||
|
||||
for (let i = 0; i < initialRundown.length; i++) {
|
||||
const currentEvent = initialRundown[i];
|
||||
@@ -106,7 +106,7 @@ export function generate(
|
||||
lastEnd = updatedEvent.timeEnd;
|
||||
|
||||
// check if we go over midnight, account for eventual gaps
|
||||
const gapOverMidnight = previousEnd > updatedEvent.timeStart;
|
||||
const gapOverMidnight = previousEnd !== null && previousEnd > updatedEvent.timeStart;
|
||||
const durationOverMidnight = updatedEvent.timeStart > updatedEvent.timeEnd;
|
||||
if (gapOverMidnight || durationOverMidnight) {
|
||||
daySpan++;
|
||||
@@ -135,7 +135,9 @@ export function generate(
|
||||
|
||||
isStale = false;
|
||||
totalDelay = accumulatedDelay;
|
||||
totalDuration = getTotalDuration(firstStart, lastEnd, daySpan);
|
||||
if (lastEnd !== null && firstStart !== null) {
|
||||
totalDuration = getTotalDuration(firstStart, lastEnd, daySpan);
|
||||
}
|
||||
|
||||
return { rundown, order, links, totalDelay, totalDuration, assignedCustomProperties: assignedCustomFields };
|
||||
}
|
||||
@@ -210,6 +212,7 @@ type MutationParams<T> = T & CommonParams;
|
||||
type MutatingReturn = {
|
||||
newRundown: OntimeRundown;
|
||||
newEvent?: OntimeRundownEntry;
|
||||
didMutate: boolean;
|
||||
};
|
||||
type MutatingFn<T extends object> = (params: MutationParams<T>) => MutatingReturn;
|
||||
|
||||
@@ -227,7 +230,7 @@ export function mutateCache<T extends object>(mutation: MutatingFn<T>) {
|
||||
*/
|
||||
isStale = true;
|
||||
|
||||
const { newEvent, newRundown } = mutation({ ...params, persistedRundown });
|
||||
const { newEvent, newRundown, didMutate } = mutation({ ...params, persistedRundown });
|
||||
|
||||
revision = revision + 1;
|
||||
persistedRundown = newRundown;
|
||||
@@ -244,7 +247,7 @@ export function mutateCache<T extends object>(mutation: MutatingFn<T>) {
|
||||
DataProvider.setRundown(persistedRundown);
|
||||
});
|
||||
|
||||
return { newEvent };
|
||||
return { newEvent, newRundown, didMutate };
|
||||
}
|
||||
|
||||
return scopedMutation;
|
||||
@@ -256,7 +259,7 @@ export function add({ persistedRundown, atIndex, event }: AddArgs): Required<Mut
|
||||
const newEvent: OntimeRundownEntry = { ...event };
|
||||
const newRundown = insertAtIndex(atIndex, newEvent, persistedRundown);
|
||||
|
||||
return { newRundown, newEvent };
|
||||
return { newRundown, newEvent, didMutate: true };
|
||||
}
|
||||
|
||||
type RemoveArgs = MutationParams<{ eventId: string }>;
|
||||
@@ -265,11 +268,11 @@ export function remove({ persistedRundown, eventId }: RemoveArgs): MutatingRetur
|
||||
const atIndex = persistedRundown.findIndex((event) => event.id === eventId);
|
||||
const newRundown = deleteAtIndex(atIndex, persistedRundown);
|
||||
|
||||
return { newRundown };
|
||||
return { newRundown, didMutate: atIndex !== -1 };
|
||||
}
|
||||
|
||||
export function removeAll(): { newRundown: OntimeRundown } {
|
||||
return { newRundown: [] };
|
||||
export function removeAll(): MutatingReturn {
|
||||
return { newRundown: [], didMutate: true };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -304,7 +307,7 @@ export function edit({ persistedRundown, eventId, patch }: EditArgs): Required<M
|
||||
const eventInMemory = persistedRundown[indexAt];
|
||||
if (!hasChanges(eventInMemory, patch)) {
|
||||
isStale = false;
|
||||
return;
|
||||
return { newRundown: persistedRundown, newEvent: eventInMemory, didMutate: false };
|
||||
}
|
||||
|
||||
const newEvent = makeEvent(eventInMemory, patch);
|
||||
@@ -312,6 +315,7 @@ export function edit({ persistedRundown, eventId, patch }: EditArgs): Required<M
|
||||
const newRundown = [...persistedRundown];
|
||||
newRundown[indexAt] = newEvent;
|
||||
|
||||
// check whether the data warrants recalculation of cache
|
||||
const makeStale = isDataStale(patch);
|
||||
|
||||
if (!makeStale) {
|
||||
@@ -319,7 +323,7 @@ export function edit({ persistedRundown, eventId, patch }: EditArgs): Required<M
|
||||
}
|
||||
|
||||
isStale = makeStale;
|
||||
return { newRundown, newEvent };
|
||||
return { newRundown, newEvent, didMutate: true };
|
||||
}
|
||||
|
||||
type BatchEditArgs = MutationParams<{ eventIds: string[]; patch: Partial<OntimeRundownEntry> }>;
|
||||
@@ -339,7 +343,7 @@ export function batchEdit({ persistedRundown, eventIds, patch }: BatchEditArgs):
|
||||
newRundown.push(persistedRundown[i]);
|
||||
}
|
||||
}
|
||||
return { newRundown };
|
||||
return { newRundown, didMutate: true };
|
||||
}
|
||||
|
||||
type ReorderArgs = MutationParams<{ eventId: string; from: number; to: number }>;
|
||||
@@ -357,14 +361,14 @@ export function reorder({ persistedRundown, eventId, from, to }: ReorderArgs): R
|
||||
event.revision += 1;
|
||||
}
|
||||
}
|
||||
return { newRundown, newEvent: newRundown.at(from) };
|
||||
return { newRundown, newEvent: newRundown.at(from) as OntimeRundownEntry, didMutate: true };
|
||||
}
|
||||
|
||||
type ApplyDelayArgs = MutationParams<{ eventId: string }>;
|
||||
|
||||
export function applyDelay({ persistedRundown, eventId }: ApplyDelayArgs): MutatingReturn {
|
||||
const newRundown = apply(eventId, persistedRundown);
|
||||
return { newRundown };
|
||||
return { newRundown, didMutate: true };
|
||||
}
|
||||
|
||||
type SwapArgs = MutationParams<{ fromId: string; toId: string }>;
|
||||
@@ -388,7 +392,7 @@ export function swap({ persistedRundown, fromId, toId }: SwapArgs): MutatingRetu
|
||||
newRundown[indexB] = newB;
|
||||
(newRundown[indexB] as OntimeEvent).revision += 1;
|
||||
|
||||
return { newRundown };
|
||||
return { newRundown, didMutate: true };
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -25,7 +25,13 @@ const populateDb = (directory: string, filename: string): string => {
|
||||
if (!existsSync(dbPath)) {
|
||||
try {
|
||||
const dbDirectory = resolveDbDirectory;
|
||||
const newFileDirectory = join(dbDirectory, pathToStartDb.split('/').pop());
|
||||
const startDbName = pathToStartDb.split('/').pop();
|
||||
|
||||
if (!startDbName) {
|
||||
throw new Error('Invalid path to start database');
|
||||
}
|
||||
|
||||
const newFileDirectory = join(dbDirectory, startDbName);
|
||||
|
||||
copyFileSync(pathToStartDb, newFileDirectory);
|
||||
dbPath = newFileDirectory;
|
||||
|
||||
@@ -318,7 +318,6 @@ describe('test parser edge cases', () => {
|
||||
|
||||
// @ts-expect-error -- we know this is wrong, testing imports outside domain
|
||||
const parseResponse = await parseJson(testData);
|
||||
expect(console.log).toHaveBeenCalledWith('ERROR: unkown event type, skipping');
|
||||
expect(parseResponse?.rundown.length).toBe(0);
|
||||
});
|
||||
|
||||
@@ -332,7 +331,7 @@ describe('test parser edge cases', () => {
|
||||
|
||||
// @ts-expect-error -- we know this is wrong, testing imports outside domain
|
||||
await parseJson(testData);
|
||||
expect(console.log).toHaveBeenCalledWith('ERROR: unknown app version, skipping');
|
||||
expect(console.log).toHaveBeenCalledWith('ERROR: unable to parse settings, missing app or version');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -564,6 +563,7 @@ describe('test views import', () => {
|
||||
normalColor: '#ffffffcc',
|
||||
warningColor: '#FFAB33',
|
||||
dangerColor: '#ED3333',
|
||||
freezeEnd: false,
|
||||
endMessage: '',
|
||||
overrideStyles: false,
|
||||
};
|
||||
@@ -581,7 +581,7 @@ describe('test views import', () => {
|
||||
},
|
||||
} as DatabaseModel;
|
||||
const parsed = parseViewSettings(testData);
|
||||
expect(parsed).toStrictEqual({});
|
||||
expect(parsed).toStrictEqual(dbModel.viewSettings);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -591,7 +591,7 @@ describe('test import of v2 datamodel', () => {
|
||||
rundown: [
|
||||
{ type: SupportedEvent.Block, title: 'block-title', id: 'block-id' },
|
||||
{ type: SupportedEvent.Delay, duration: 0 },
|
||||
{ type: SupportedEvent.Event, title: 'block-title', id: 'block-id' },
|
||||
{ type: SupportedEvent.Event, title: 'event-title', id: 'event-id' },
|
||||
],
|
||||
project: {
|
||||
title: '',
|
||||
|
||||
@@ -273,15 +273,26 @@ export const parseJson = async (jsonData: Partial<DatabaseModel>): Promise<Datab
|
||||
return null;
|
||||
}
|
||||
|
||||
let settings;
|
||||
|
||||
// check settings first to make sure we can parse it
|
||||
try {
|
||||
settings = parseSettings(jsonData);
|
||||
} catch (error) {
|
||||
// if we cant parse, return an empty project
|
||||
console.log('ERROR: unable to parse settings, missing app or version');
|
||||
return dbModel;
|
||||
}
|
||||
|
||||
const returnData: DatabaseModel = {
|
||||
rundown: parseRundown(jsonData),
|
||||
project: parseProject(jsonData) ?? dbModel.project,
|
||||
settings: parseSettings(jsonData) ?? dbModel.settings,
|
||||
viewSettings: parseViewSettings(jsonData) ?? dbModel.viewSettings,
|
||||
project: parseProject(jsonData),
|
||||
settings,
|
||||
viewSettings: parseViewSettings(jsonData),
|
||||
urlPresets: parseUrlPresets(jsonData),
|
||||
customFields: parseCustomFields(jsonData),
|
||||
osc: parseOsc(jsonData) ?? dbModel.osc,
|
||||
http: parseHttp(jsonData) ?? dbModel.http,
|
||||
osc: parseOsc(jsonData),
|
||||
http: parseHttp(jsonData),
|
||||
};
|
||||
|
||||
return returnData;
|
||||
|
||||
@@ -15,6 +15,9 @@ import {
|
||||
isOntimeCycle,
|
||||
HttpSubscription,
|
||||
URLPreset,
|
||||
OntimeEvent,
|
||||
OntimeBlock,
|
||||
OntimeDelay,
|
||||
} from 'ontime-types';
|
||||
|
||||
import { block as blockDef, delay as delayDef } from '../models/eventsDefinition.js';
|
||||
@@ -22,135 +25,118 @@ import { dbModel } from '../models/dataModel.js';
|
||||
import { createEvent } from './parser.js';
|
||||
|
||||
/**
|
||||
* Parse events array of an entry
|
||||
* @param {object} data - data object
|
||||
* @returns {object} - event object data
|
||||
* Parse rundown array of an entry
|
||||
*/
|
||||
export const parseRundown = (data: Partial<DatabaseModel>): OntimeRundown => {
|
||||
let newRundown: OntimeRundown = [];
|
||||
if ('rundown' in data) {
|
||||
console.log('Found rundown definition, importing...');
|
||||
const rundown = [];
|
||||
try {
|
||||
let eventIndex = 0;
|
||||
const ids = [];
|
||||
for (const event of data.rundown) {
|
||||
// double check unique ids
|
||||
if (ids.includes(event?.id)) {
|
||||
console.log('ERROR: ID collision on import, skipping');
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isOntimeEvent(event)) {
|
||||
eventIndex += 1;
|
||||
const parsedEvent = createEvent(event, eventIndex.toString());
|
||||
if (event != null) {
|
||||
rundown.push(parsedEvent);
|
||||
ids.push(parsedEvent.id);
|
||||
}
|
||||
} else if (isOntimeDelay(event)) {
|
||||
rundown.push({
|
||||
...delayDef,
|
||||
duration: event.duration,
|
||||
id: event.id || generateId(),
|
||||
});
|
||||
} else if (isOntimeBlock(event)) {
|
||||
rundown.push({ ...blockDef, title: event.title, id: event.id || generateId() });
|
||||
} else {
|
||||
console.log('ERROR: unkown event type, skipping');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`Error ${error}`);
|
||||
}
|
||||
// write to db
|
||||
newRundown = rundown;
|
||||
console.log(`Uploaded file with ${newRundown.length} entries`);
|
||||
if (!data.rundown) {
|
||||
return [];
|
||||
}
|
||||
return newRundown;
|
||||
|
||||
console.log('Found rundown, importing...');
|
||||
|
||||
const rundown: OntimeRundown = [];
|
||||
let eventIndex = 0;
|
||||
const ids: string[] = [];
|
||||
|
||||
for (const event of data.rundown) {
|
||||
if (ids.includes(event.id)) {
|
||||
console.log('ERROR: ID collision on import, skipping');
|
||||
continue;
|
||||
}
|
||||
|
||||
const id = event.id || generateId();
|
||||
let newEvent: OntimeEvent | OntimeDelay | OntimeBlock | null;
|
||||
|
||||
if (isOntimeEvent(event)) {
|
||||
newEvent = createEvent(event, eventIndex.toString());
|
||||
// skip if event is invalid
|
||||
if (newEvent == null) {
|
||||
continue;
|
||||
}
|
||||
eventIndex += 1;
|
||||
} else if (isOntimeDelay(event)) {
|
||||
newEvent = { ...delayDef, duration: event.duration, id };
|
||||
} else if (isOntimeBlock(event)) {
|
||||
newEvent = { ...blockDef, title: event.title, id };
|
||||
} else {
|
||||
console.log('ERROR: unknown event type, skipping');
|
||||
continue;
|
||||
}
|
||||
|
||||
if (newEvent) {
|
||||
rundown.push(newEvent);
|
||||
ids.push(id);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Uploaded rundown with ${rundown.length} entries`);
|
||||
return rundown;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse event portion of an entry
|
||||
* @param {object} data - data object
|
||||
* @returns {object} - event object data
|
||||
*/
|
||||
export const parseProject = (data: Partial<DatabaseModel>): ProjectData => {
|
||||
let newProjectData: Partial<ProjectData> = {};
|
||||
// we are adding this here to aid transition, should be removed once enough time has past that users have fully migrated
|
||||
if ('project' in data) {
|
||||
console.log('Found project data, importing...');
|
||||
const project = data.project;
|
||||
|
||||
// filter known properties and write to db
|
||||
newProjectData = {
|
||||
...dbModel.project,
|
||||
title: project.title || dbModel.project.title,
|
||||
description: project.description || dbModel.project.description,
|
||||
publicUrl: project.publicUrl || dbModel.project.publicUrl,
|
||||
publicInfo: project.publicInfo || dbModel.project.publicInfo,
|
||||
backstageUrl: project.backstageUrl || dbModel.project.backstageUrl,
|
||||
backstageInfo: project.backstageInfo || dbModel.project.backstageInfo,
|
||||
};
|
||||
if (!data.project) {
|
||||
return { ...dbModel.project };
|
||||
}
|
||||
return newProjectData as ProjectData;
|
||||
|
||||
console.log('Found project data, importing...');
|
||||
|
||||
return {
|
||||
title: data.project.title ?? dbModel.project.title,
|
||||
description: data.project.description ?? dbModel.project.description,
|
||||
publicUrl: data.project.publicUrl ?? dbModel.project.publicUrl,
|
||||
publicInfo: data.project.publicInfo ?? dbModel.project.publicInfo,
|
||||
backstageUrl: data.project.backstageUrl ?? dbModel.project.backstageUrl,
|
||||
backstageInfo: data.project.backstageInfo ?? dbModel.project.backstageInfo,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse settings portion of an entry
|
||||
* @param {object} data - data object
|
||||
* @returns {object} - event object data
|
||||
*/
|
||||
export const parseSettings = (data): Settings => {
|
||||
let newSettings: Partial<Settings> = {};
|
||||
if ('settings' in data) {
|
||||
console.log('Found settings definition, importing...');
|
||||
const s = data.settings;
|
||||
|
||||
// skip if file definition is missing
|
||||
if (s?.app !== 'ontime' || s?.version == null) {
|
||||
console.log('ERROR: unknown app version, skipping');
|
||||
} else {
|
||||
const settings = {
|
||||
version: dbModel.settings.version,
|
||||
serverPort: s.serverPort ?? dbModel.settings.serverPort,
|
||||
editorKey: s.editorKey ?? null,
|
||||
operatorKey: s.operatorKey ?? null,
|
||||
timeFormat: s.timeFormat ?? '24',
|
||||
language: s.language ?? 'en',
|
||||
};
|
||||
|
||||
// write to db
|
||||
newSettings = {
|
||||
...dbModel.settings,
|
||||
...settings,
|
||||
};
|
||||
}
|
||||
export const parseSettings = (data: Partial<DatabaseModel>): Settings => {
|
||||
if (!data.settings) {
|
||||
return { ...dbModel.settings };
|
||||
}
|
||||
return newSettings as Settings;
|
||||
|
||||
// skip if file definition is missing
|
||||
if (data.settings?.app !== 'ontime' || data.settings?.version == null) {
|
||||
throw new Error('ERROR: unable to parse settings, missing app or version');
|
||||
}
|
||||
|
||||
console.log('Found settings, importing...');
|
||||
|
||||
return {
|
||||
app: dbModel.settings.app,
|
||||
version: dbModel.settings.version,
|
||||
serverPort: data.settings.serverPort ?? dbModel.settings.serverPort,
|
||||
editorKey: data.settings.editorKey ?? null,
|
||||
operatorKey: data.settings.operatorKey ?? null,
|
||||
timeFormat: data.settings.timeFormat ?? '24',
|
||||
language: data.settings.language ?? 'en',
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse settings portion of an entry
|
||||
* @param {object} data - data object
|
||||
* @returns {object} - event object data
|
||||
* Parse view settings portion of an entry
|
||||
*/
|
||||
export const parseViewSettings = (data: Partial<DatabaseModel>): ViewSettings => {
|
||||
let newViews: Partial<ViewSettings> = {};
|
||||
if ('viewSettings' in data) {
|
||||
console.log('Found view definition, importing...');
|
||||
const v = data.viewSettings;
|
||||
|
||||
const viewSettings = {
|
||||
overrideStyles: v.overrideStyles ?? dbModel.viewSettings.overrideStyles,
|
||||
normalColor: v.normalColor ?? dbModel.viewSettings.normalColor,
|
||||
warningColor: v.warningColor ?? dbModel.viewSettings.warningColor,
|
||||
dangerColor: v.dangerColor ?? dbModel.viewSettings.dangerColor,
|
||||
endMessage: v.endMessage ?? dbModel.viewSettings.endMessage,
|
||||
};
|
||||
|
||||
newViews = { ...viewSettings };
|
||||
if (!data.viewSettings) {
|
||||
return { ...dbModel.viewSettings };
|
||||
}
|
||||
return newViews as ViewSettings;
|
||||
|
||||
console.log('Found view settings, importing...');
|
||||
|
||||
return {
|
||||
dangerColor: data.viewSettings.dangerColor ?? dbModel.viewSettings.dangerColor,
|
||||
endMessage: data.viewSettings.endMessage ?? dbModel.viewSettings.endMessage,
|
||||
freezeEnd: data.viewSettings.freezeEnd ?? dbModel.viewSettings.freezeEnd,
|
||||
normalColor: data.viewSettings.normalColor ?? dbModel.viewSettings.normalColor,
|
||||
overrideStyles: data.viewSettings.overrideStyles ?? dbModel.viewSettings.overrideStyles,
|
||||
warningColor: data.viewSettings.warningColor ?? dbModel.viewSettings.warningColor,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -170,20 +156,20 @@ export function sanitiseOscSubscriptions(subscriptions?: OscSubscription[]): Osc
|
||||
/**
|
||||
* Parse osc portion of an entry
|
||||
*/
|
||||
export const parseOsc = (data: { osc?: Partial<OSCSettings> }): OSCSettings => {
|
||||
if ('osc' in data) {
|
||||
console.log('Found OSC definition, importing...');
|
||||
|
||||
const loadedConfig = data.osc || {};
|
||||
return {
|
||||
portIn: loadedConfig.portIn ?? dbModel.osc.portIn,
|
||||
portOut: loadedConfig.portOut ?? dbModel.osc.portOut,
|
||||
targetIP: loadedConfig.targetIP ?? dbModel.osc.targetIP,
|
||||
enabledIn: loadedConfig.enabledIn ?? dbModel.osc.enabledIn,
|
||||
enabledOut: loadedConfig.enabledOut ?? dbModel.osc.enabledOut,
|
||||
subscriptions: sanitiseOscSubscriptions(loadedConfig.subscriptions),
|
||||
};
|
||||
export const parseOsc = (data: Partial<DatabaseModel>): OSCSettings => {
|
||||
if (!data.osc) {
|
||||
return { ...dbModel.osc };
|
||||
}
|
||||
console.log('Found OSC settings, importing...');
|
||||
|
||||
return {
|
||||
portIn: data.osc.portIn ?? dbModel.osc.portIn,
|
||||
portOut: data.osc.portOut ?? dbModel.osc.portOut,
|
||||
targetIP: data.osc.targetIP ?? dbModel.osc.targetIP,
|
||||
enabledIn: data.osc.enabledIn ?? dbModel.osc.enabledIn,
|
||||
enabledOut: data.osc.enabledOut ?? dbModel.osc.enabledOut,
|
||||
subscriptions: sanitiseOscSubscriptions(data.osc.subscriptions),
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -206,66 +192,70 @@ export function sanitiseHttpSubscriptions(subscriptions?: HttpSubscription[]): H
|
||||
|
||||
/**
|
||||
* Parse Http portion of an entry
|
||||
* @param {object} data - data object
|
||||
* @param {boolean} enforce - whether to create a definition if one is missing
|
||||
* @returns {object} - event object data
|
||||
*/
|
||||
export const parseHttp = (data: { http?: Partial<HttpSettings> }): HttpSettings => {
|
||||
if ('http' in data) {
|
||||
console.log('Found HTTP definition, importing...');
|
||||
|
||||
// TODO: this can be improved by only merging known keys
|
||||
const loadedConfig = data?.http || {};
|
||||
|
||||
return {
|
||||
enabledOut: loadedConfig.enabledOut ?? dbModel.http.enabledOut,
|
||||
subscriptions: sanitiseHttpSubscriptions(loadedConfig.subscriptions),
|
||||
};
|
||||
export const parseHttp = (data: Partial<DatabaseModel>): HttpSettings => {
|
||||
if (!data.http) {
|
||||
return { ...dbModel.http };
|
||||
}
|
||||
|
||||
console.log('Found HTTP settings, importing...');
|
||||
|
||||
return {
|
||||
enabledOut: data.http.enabledOut ?? dbModel.http.enabledOut,
|
||||
subscriptions: sanitiseHttpSubscriptions(data.http.subscriptions),
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse URL preset portion of an entry
|
||||
* @param {object} data - data object
|
||||
* @returns {object} - event object data
|
||||
*/
|
||||
export const parseUrlPresets = (data: Partial<DatabaseModel>): URLPreset[] => {
|
||||
const newPresets: URLPreset[] = [];
|
||||
if ('urlPresets' in data) {
|
||||
console.log('Found URL presets definition, importing...');
|
||||
try {
|
||||
for (const preset of data.urlPresets) {
|
||||
const newPreset = {
|
||||
enabled: preset.enabled ?? false,
|
||||
alias: preset.alias ?? '',
|
||||
pathAndParams: preset.pathAndParams ?? '',
|
||||
};
|
||||
newPresets.push(newPreset);
|
||||
}
|
||||
console.log(`Uploaded ${newPresets.length} preset(s)`);
|
||||
} catch (error) {
|
||||
console.log(`Error: ${error}`);
|
||||
}
|
||||
if (!data.urlPresets) {
|
||||
return [];
|
||||
}
|
||||
|
||||
console.log('Found URL presets, importing...');
|
||||
|
||||
const newPresets: URLPreset[] = [];
|
||||
|
||||
for (const preset of data.urlPresets) {
|
||||
const newPreset = {
|
||||
enabled: preset.enabled ?? false,
|
||||
alias: preset.alias ?? '',
|
||||
pathAndParams: preset.pathAndParams ?? '',
|
||||
};
|
||||
newPresets.push(newPreset);
|
||||
}
|
||||
|
||||
console.log(`Uploaded ${newPresets.length} preset(s)`);
|
||||
|
||||
return newPresets;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse customFields entry
|
||||
* @param {object} data - data object
|
||||
* @returns {object} - event object data
|
||||
*/
|
||||
export const parseCustomFields = (data: Partial<DatabaseModel>): CustomFields => {
|
||||
let newCustomFields: CustomFields = { ...dbModel.customFields };
|
||||
|
||||
if ('customFields' in data) {
|
||||
console.log('Found Custom Fields definition, importing...');
|
||||
try {
|
||||
//TODO: validate
|
||||
newCustomFields = { ...dbModel.customFields, ...data.customFields };
|
||||
} catch (error) {
|
||||
console.log(`Error: ${error}`);
|
||||
}
|
||||
if (typeof data.customFields !== 'object') {
|
||||
return { ...dbModel.customFields };
|
||||
}
|
||||
return { ...newCustomFields };
|
||||
|
||||
console.log('Found Custom Fields, importing...');
|
||||
|
||||
const newCustomFields: CustomFields = {};
|
||||
|
||||
for (const fieldLabel in data.customFields) {
|
||||
const field = data.customFields[fieldLabel];
|
||||
if (!field.label || !field.type || !field.colour) {
|
||||
console.log('ERROR: missing required field, skipping');
|
||||
continue;
|
||||
}
|
||||
newCustomFields[field.label] = {
|
||||
type: field.type,
|
||||
colour: field.colour,
|
||||
label: field.label,
|
||||
};
|
||||
}
|
||||
|
||||
return newCustomFields;
|
||||
};
|
||||
|
||||
@@ -15,10 +15,9 @@ export const makeString = (val: unknown, fallback = ''): string => {
|
||||
|
||||
/**
|
||||
* @description Delete file from system
|
||||
* @param {string} file - reference to file
|
||||
*/
|
||||
export const deleteFile = async (file) => {
|
||||
unlink(file, (error) => {
|
||||
export const deleteFile = async (filePath: string) => {
|
||||
unlink(filePath, (error) => {
|
||||
if (error) {
|
||||
console.error('Could not delete file:', error);
|
||||
}
|
||||
@@ -67,11 +66,12 @@ export function mergeObject<T extends object>(a: T, b: Partial<T>): T {
|
||||
* @description Removes undefined
|
||||
* @param {object} obj
|
||||
*/
|
||||
export const removeUndefined = (obj: object) => {
|
||||
export const removeUndefined = <T extends Record<string, unknown>>(obj: T): Partial<T> => {
|
||||
return Object.keys(obj).reduce((patched, key) => {
|
||||
if (typeof obj[key] !== 'undefined') {
|
||||
// @ts-expect-error -- not sure how to type this
|
||||
patched[key] = obj[key];
|
||||
}
|
||||
return patched;
|
||||
}, {});
|
||||
}, {} as Partial<T>);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user