mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-03 06:28:01 +00:00
722e045b20
* Revert "refactor: add version to session endpoint" This reverts commitcd40a6e55e. * Revert "Remove public event feature (#1645)" This reverts commit08d9e24871. * Revert "Fix: rearrange playing event (#1640)" This reverts commit0649678dca. * Revert "refactor: style tweaks to edit css modal" This reverts commita00ec2d02a. * Revert "refactor: remove usages of framer-motion" This reverts commit54a74ccc2a. * Revert "Upgrade expressjs (#1633)" This reverts commit6f3ab274bd. * Revert "Refactor: require trigger in all events objects (#1636)" This reverts commit90870ecfb6. * Revert "Fix: Correct boundary condition in applyDelay" This reverts commitb640e0e181. * Revert "let vite be the proxy to the dev server (#1630)" This reverts commitc41fe824cf. * Revert "refactor: migrate custom fields to transactions" This reverts commit62c8319d70. * Revert "refactor: simplify validations" This reverts commitb1d23467a2. * Revert "refactor: create transaction system and apply to adding entry (#1620)" This reverts commit9a62daf047. * Revert "Refactor: better rounding (#1594)" This reverts commitb9ab1c6fd7. * Revert "refactor: improve reorder logic" This reverts commitc1054711b0. * Revert "chore: simplify URLs" This reverts commita4d4f29a37. * Revert "refactor: order is single source of truth" This reverts commit2793aadea0. * Revert "refactor: refetch targets is enum" This reverts commite7cfb7d9d9. * Revert "refactor: small ux improvements" This reverts commit256a851c9b. * Revert "refactor: remove trivially inferred numEvents" This reverts commit4ab9c81cb8. * Revert "feat: duplicate groups" This reverts commit2f13d6c89e. * Revert "feat: create group from entry selection" This reverts commitf1f7bad25e. * Revert "feat: create block from rundown empty" This reverts commita8b52a48f7. * Revert "fix: collapsed blocks dont render children" This reverts commitcd0999b2ab. * Revert "refactor: type cleanup and test improvements" This reverts commitb4c60f3f04. * Revert "feat: allow dissolving a block" This reverts commit5cefad3666. * Revert "fix: uncontrolled prop on controlled component" This reverts commit7a6ecd8c34. * Revert "refactor: improve return of reorder" This reverts commitba96ecfd91. * Revert "refactor: mutations on batch elements must have IDs" This reverts commit7bed3757f2. * Revert "chore: upgrade dependencies" This reverts commite2e755b1d2. * Revert "refactor: extract utility to merge two arrays" This reverts commita77d23109d. * Revert "refactor: change network mode defaults" This reverts commit0eb3b8d382. * Revert "fix: delete nested events" This reverts commit0021185288. * Revert "fix: add event at end of block" This reverts commit94c72ff4f6. * Revert "refactor: make finder available in exported rundown" This reverts commite4c08dc9b2. * Revert "assert non null and update test (#1604)" This reverts commitec74af0d62. * Revert "Fix project renumber (#1597)" This reverts commitb6d72dd082. * Revert "Refactor: WebSocket from flush queue to one patch (#1595)" This reverts commit31c311daf0. * Revert "Refactor: ms for api calls (#1593)" This reverts commite9b3cc6090. * Revert "fix test (#1601)" This reverts commit543b04a097. * Revert "fix: rebase master" This reverts commitd39b85b6e6. * Revert "chore: correct test path" This reverts commitbbe107bb2b. * Revert "refactor: extract rundown parsing" This reverts commitc616240db1. * Revert "chore: improve convention entry <> event" This reverts commit166be66ce3. * Revert "refactor: maintain flat orders" This reverts commit4180d0a337. * Revert "refactor: implement operations on nested events" This reverts commit78108e316c. * Revert "refactor: process events in rundown" This reverts commit3bb8b70915. * Revert "chore: improve convention entry <> event" This reverts commit1c4f13a0ed. * Revert "chore: rename currentBlock > parent" This reverts commit3ca0abad53. * Revert "refactor: fix delay positioning in gaps" This reverts commit030c8f897f. * Revert "refactor(e2e): skip flaky test" This reverts commit68175cfa3b. * Revert "refactor: improve project loading" This reverts commitfd8f757851. * Revert "refactor: gather group metadata" This reverts commit876d111c61. * Revert "refactor: swap maintains schedule" This reverts commit730cb95c04. * Revert "chore: rename files" This reverts commit3c388d4fb5. * Revert "refactor: restructure model to contain an object of rundowns" This reverts commit2e23718d73. * Revert "refactor: clearer relationship on rundown elements" This reverts commit89ea8c470b. * Revert "refactor: use strict typing" This reverts commit178640bfc4. * Revert "refactor: remove stop as a possible end action" This reverts commit4ed38340e0. * Revert "refactor: restructure model to contain an object of rundowns" This reverts commit69eb9a5eff. * Revert "chore: remove IDE files" This reverts commit351425127a. * Revert "refactor: remove unused and legacy code" This reverts commit95f2ba37cc.
269 lines
7.8 KiB
TypeScript
269 lines
7.8 KiB
TypeScript
import {
|
|
CustomField,
|
|
CustomFields,
|
|
DatabaseModel,
|
|
OntimeBlock,
|
|
OntimeDelay,
|
|
OntimeEvent,
|
|
OntimeRundown,
|
|
ProjectData,
|
|
Settings,
|
|
TimerType,
|
|
URLPreset,
|
|
ViewSettings,
|
|
isOntimeBlock,
|
|
isOntimeDelay,
|
|
isOntimeEvent,
|
|
} from 'ontime-types';
|
|
import { customFieldLabelToKey, generateId, isAlphanumericWithSpace } from 'ontime-utils';
|
|
|
|
import { dbModel } from '../models/dataModel.js';
|
|
import { block as blockDef, delay as delayDef } from '../models/eventsDefinition.js';
|
|
import { createEvent, type ErrorEmitter } from './parser.js';
|
|
|
|
/**
|
|
* Parse rundown array of an entry
|
|
*/
|
|
export function parseRundown(
|
|
data: Partial<DatabaseModel>,
|
|
emitError?: ErrorEmitter,
|
|
): { customFields: CustomFields; rundown: OntimeRundown } {
|
|
// check custom fields first
|
|
const parsedCustomFields = parseCustomFields(data, emitError);
|
|
|
|
if (!data.rundown) {
|
|
emitError?.('No data found to import');
|
|
return { customFields: parsedCustomFields, rundown: [] };
|
|
}
|
|
|
|
console.log('Found rundown, importing...');
|
|
|
|
const rundown: OntimeRundown = [];
|
|
let eventIndex = 0;
|
|
let previousId: string | null = null;
|
|
const ids: string[] = [];
|
|
|
|
for (const event of data.rundown) {
|
|
if (ids.includes(event.id)) {
|
|
emitError?.('ID collision on event import, skipping');
|
|
continue;
|
|
}
|
|
|
|
const id = event.id || generateId();
|
|
let newEvent: OntimeEvent | OntimeDelay | OntimeBlock | null;
|
|
|
|
if (isOntimeEvent(event)) {
|
|
const maybeEvent = runEventMigrations({ ...event, id });
|
|
|
|
if (event.linkStart) {
|
|
maybeEvent.linkStart = previousId;
|
|
}
|
|
|
|
newEvent = createEvent(maybeEvent, eventIndex);
|
|
// skip if event is invalid
|
|
if (newEvent == null) {
|
|
emitError?.('Skipping event without payload');
|
|
continue;
|
|
}
|
|
|
|
// for every field in custom, check that a key exists in customfields
|
|
for (const field in newEvent.custom) {
|
|
if (!Object.hasOwn(parsedCustomFields, field)) {
|
|
emitError?.(`Custom field ${field} not found`);
|
|
delete newEvent.custom[field];
|
|
}
|
|
}
|
|
|
|
previousId = id;
|
|
eventIndex += 1;
|
|
} else if (isOntimeDelay(event)) {
|
|
newEvent = { ...delayDef, duration: event.duration, id };
|
|
} else if (isOntimeBlock(event)) {
|
|
newEvent = { ...blockDef, title: event.title, id };
|
|
} else {
|
|
emitError?.('Unknown event type, skipping');
|
|
continue;
|
|
}
|
|
|
|
if (newEvent) {
|
|
rundown.push(newEvent);
|
|
ids.push(id);
|
|
}
|
|
}
|
|
|
|
console.log(`Uploaded rundown with ${rundown.length} entries`);
|
|
return { customFields: parsedCustomFields, rundown };
|
|
}
|
|
|
|
/**
|
|
* Parse event portion of an entry
|
|
*/
|
|
export function parseProject(data: Partial<DatabaseModel>, emitError?: ErrorEmitter): ProjectData {
|
|
if (!data.project) {
|
|
emitError?.('No data found to import');
|
|
return { ...dbModel.project };
|
|
}
|
|
|
|
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,
|
|
projectLogo: data.project.projectLogo ?? dbModel.project.projectLogo,
|
|
custom: data.project.custom ?? dbModel.project.custom,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Parse settings portion of an entry
|
|
*/
|
|
export function parseSettings(data: Partial<DatabaseModel>): Settings {
|
|
// skip if file definition is missing
|
|
if (!data.settings || 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 view settings portion of an entry
|
|
*/
|
|
export function parseViewSettings(data: Partial<DatabaseModel>, emitError?: ErrorEmitter): ViewSettings {
|
|
if (!data.viewSettings) {
|
|
emitError?.('No data found to import');
|
|
return { ...dbModel.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,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Parse URL preset portion of an entry
|
|
*/
|
|
export function parseUrlPresets(data: Partial<DatabaseModel>, emitError?: ErrorEmitter): URLPreset[] {
|
|
if (!data.urlPresets) {
|
|
emitError?.('No data found to import');
|
|
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
|
|
*/
|
|
export function parseCustomFields(data: Partial<DatabaseModel>, emitError?: ErrorEmitter): CustomFields {
|
|
if (typeof data.customFields !== 'object') {
|
|
emitError?.('No data found to import');
|
|
return {};
|
|
}
|
|
console.log('Found Custom Fields, importing...');
|
|
|
|
const customFields = sanitiseCustomFields(data.customFields);
|
|
if (Object.keys(customFields).length !== Object.keys(data.customFields).length) {
|
|
emitError?.('Skipped invalid custom fields');
|
|
}
|
|
return customFields;
|
|
}
|
|
|
|
export function sanitiseCustomFields(data: object): CustomFields {
|
|
const newCustomFields: CustomFields = {};
|
|
|
|
for (const [originalKey, field] of Object.entries(data)) {
|
|
if (!isValidField(field)) {
|
|
continue;
|
|
}
|
|
|
|
if (!isAlphanumericWithSpace(field.label)) {
|
|
continue;
|
|
}
|
|
|
|
const keyFromLabel = customFieldLabelToKey(field.label);
|
|
// Test label and key cohesion, but allow old lowercased keys to stay
|
|
// TODO: the `toLocaleLowerCase` part here is to conserve keys from old projects and could be removed at some point (okt. 2024)
|
|
const key = originalKey.toLocaleLowerCase() === keyFromLabel.toLocaleLowerCase() ? originalKey : keyFromLabel;
|
|
if (key in newCustomFields) {
|
|
continue;
|
|
}
|
|
|
|
newCustomFields[key] = {
|
|
type: field.type,
|
|
colour: field.colour,
|
|
label: field.label,
|
|
};
|
|
}
|
|
|
|
function isValidField(data: unknown): data is CustomField {
|
|
return (
|
|
typeof data === 'object' &&
|
|
data !== null &&
|
|
'label' in data &&
|
|
data.label !== '' &&
|
|
'colour' in data &&
|
|
typeof data.colour === 'string' &&
|
|
'type' in data &&
|
|
(data.type === 'string' || data.type === 'image')
|
|
);
|
|
}
|
|
|
|
return newCustomFields;
|
|
}
|
|
|
|
/**
|
|
* Time to end was moved from a TimerType to a standalone boolean named count to end
|
|
* Released as part of v3.10.0
|
|
*/
|
|
function migrateTimeToEnd(event: any): OntimeEvent {
|
|
if (event.timerType === 'time-to-end') {
|
|
event.timerType = TimerType.CountDown;
|
|
event.countToEnd = true;
|
|
}
|
|
return event;
|
|
}
|
|
|
|
/**
|
|
* Mutating function migrates event data entries
|
|
*/
|
|
function runEventMigrations(event: any): OntimeEvent {
|
|
return migrateTimeToEnd(event);
|
|
}
|