mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-10 09:53:48 +00:00
refactor: cuesheet design review
fix: parsing of custom fields for blocks refactor: extract cuesheet settings refactor: cuesheet actions refactor: improve cuesheet performance on resizing
This commit is contained in:
committed by
Carlos Valente
parent
0726c04f20
commit
8ad260d28a
@@ -12,6 +12,7 @@ interface LegacyData extends Partial<DatabaseModel> {
|
||||
}
|
||||
|
||||
export function parseAutomationSettings(data: LegacyData, emitError?: ErrorEmitter): AutomationSettings {
|
||||
// TODO(v4): move to migration script
|
||||
/**
|
||||
* Leaving a path for migrating users to the new automations
|
||||
* This should be removed after a few releases
|
||||
|
||||
@@ -213,6 +213,64 @@ describe('parseRundown()', () => {
|
||||
expect((parsedRundown.entries['2'] as OntimeEvent).custom).toStrictEqual({ sound: 'loud' });
|
||||
});
|
||||
|
||||
it('removes empty custom fields', () => {
|
||||
const rundown = {
|
||||
id: 'test',
|
||||
title: '',
|
||||
order: ['1', '2'],
|
||||
flatOrder: ['1', '2', '21'],
|
||||
entries: {
|
||||
'1': makeOntimeEvent({ id: '1', custom: { lighting: 'yes' } }),
|
||||
'2': makeOntimeBlock({ id: '2', entries: ['21'], custom: { lighting: '' } }),
|
||||
'21': makeOntimeEvent({ id: '21', custom: { lighting: '' } }),
|
||||
},
|
||||
revision: 1,
|
||||
} as Rundown;
|
||||
|
||||
const customFields: CustomFields = {
|
||||
lighting: {
|
||||
type: 'string',
|
||||
colour: 'red',
|
||||
label: 'lighting',
|
||||
},
|
||||
};
|
||||
|
||||
const parsedRundown = parseRundown(rundown, customFields);
|
||||
expect((parsedRundown.entries['1'] as OntimeEvent).custom).toStrictEqual({ lighting: 'yes' });
|
||||
expect((parsedRundown.entries['2'] as OntimeBlock).custom).not.toHaveProperty('lighting');
|
||||
expect((parsedRundown.entries['21'] as OntimeEvent).custom).not.toHaveProperty('lighting');
|
||||
});
|
||||
|
||||
it('parses data in blocks', () => {
|
||||
const rundown = {
|
||||
id: 'test',
|
||||
title: '',
|
||||
order: ['block'],
|
||||
flatOrder: ['block'],
|
||||
isNextDay: false,
|
||||
entries: {
|
||||
block: makeOntimeBlock({
|
||||
id: 'block',
|
||||
title: 'block-title',
|
||||
note: 'block-note',
|
||||
colour: 'red',
|
||||
entries: ['1', '2'],
|
||||
}),
|
||||
'1': makeOntimeEvent({ id: '1' }),
|
||||
},
|
||||
revision: 1,
|
||||
} as Rundown;
|
||||
|
||||
const parsedRundown = parseRundown(rundown, {});
|
||||
expect(parsedRundown.order.length).toEqual(1);
|
||||
expect(parsedRundown.entries.block).toMatchObject({
|
||||
title: 'block-title',
|
||||
note: 'block-note',
|
||||
colour: 'red',
|
||||
entries: ['1'],
|
||||
});
|
||||
});
|
||||
|
||||
it('parses events nested in blocks', () => {
|
||||
const rundown = {
|
||||
id: 'test',
|
||||
|
||||
@@ -19,10 +19,10 @@ import {
|
||||
import { isObjectEmpty, generateId, getLinkedTimes, getTimeFrom, isNewLatest } from 'ontime-utils';
|
||||
|
||||
import { defaultRundown } from '../../models/dataModel.js';
|
||||
import { delay as delayDef, block as blockDef } from '../../models/eventsDefinition.js';
|
||||
import { delay as delayDef } from '../../models/eventsDefinition.js';
|
||||
import type { ErrorEmitter } from '../../utils/parserUtils.js';
|
||||
|
||||
import { calculateDayOffset, createEvent } from './rundown.utils.js';
|
||||
import { calculateDayOffset, cleanupCustomFields, createBlock, createEvent } from './rundown.utils.js';
|
||||
import { RundownMetadata } from './rundown.types.js';
|
||||
|
||||
/**
|
||||
@@ -104,14 +104,7 @@ export function parseRundown(
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
cleanupCustomFields(newEvent.custom, parsedCustomFields);
|
||||
eventIndex += 1;
|
||||
} else if (isOntimeDelay(event)) {
|
||||
newEvent = { ...delayDef, duration: event.duration, id };
|
||||
@@ -128,14 +121,7 @@ export function parseRundown(
|
||||
continue;
|
||||
}
|
||||
|
||||
// for every field in custom, check that a key exists in customfields
|
||||
for (const field in newNestedEvent.custom) {
|
||||
if (!Object.hasOwn(parsedCustomFields, field)) {
|
||||
emitError?.(`Custom field ${field} not found`);
|
||||
delete newNestedEvent.custom[field];
|
||||
}
|
||||
}
|
||||
|
||||
cleanupCustomFields(newNestedEvent.custom, parsedCustomFields);
|
||||
eventIndex += 1;
|
||||
|
||||
if (newNestedEvent) {
|
||||
@@ -145,16 +131,13 @@ export function parseRundown(
|
||||
}
|
||||
}
|
||||
|
||||
newEvent = {
|
||||
...blockDef,
|
||||
title: event.title,
|
||||
note: event.note,
|
||||
entries: event.entries?.filter((eventId) => Object.hasOwn(rundown.entries, eventId)) ?? [],
|
||||
isNextDay: event.isNextDay,
|
||||
colour: event.colour,
|
||||
custom: { ...event.custom },
|
||||
id,
|
||||
};
|
||||
newEvent = createBlock({ ...structuredClone(event), id });
|
||||
// ensure entries exist
|
||||
if (event.entries?.length > 0) {
|
||||
newEvent.entries = event.entries.filter((eventId) => Object.hasOwn(rundown.entries, eventId));
|
||||
}
|
||||
// ensure custom fields are valid
|
||||
cleanupCustomFields(newEvent.custom, parsedCustomFields);
|
||||
} else {
|
||||
emitError?.('Unknown event type, skipping');
|
||||
continue;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import {
|
||||
CustomFields,
|
||||
EntryCustomFields,
|
||||
EntryId,
|
||||
isOntimeBlock,
|
||||
isOntimeDelay,
|
||||
@@ -60,7 +62,7 @@ export function generateEvent<T extends Partial<OntimeEvent> | Partial<OntimeDel
|
||||
throw new Error('Invalid event type');
|
||||
}
|
||||
|
||||
export function createPatch(originalEvent: OntimeEvent, patchEvent: Partial<OntimeEvent>): OntimeEvent {
|
||||
export function createEventPatch(originalEvent: OntimeEvent, patchEvent: Partial<OntimeEvent>): OntimeEvent {
|
||||
if (Object.keys(patchEvent).length === 0) {
|
||||
return originalEvent;
|
||||
}
|
||||
@@ -101,18 +103,52 @@ export function createPatch(originalEvent: OntimeEvent, patchEvent: Partial<Onti
|
||||
};
|
||||
}
|
||||
|
||||
export function createBlockPatch(originalBlock: OntimeBlock, patchBlock: Partial<OntimeBlock>): OntimeBlock {
|
||||
if (Object.keys(patchBlock).length === 0) {
|
||||
return originalBlock;
|
||||
}
|
||||
|
||||
const maybeTargetDuration = () => {
|
||||
if (typeof patchBlock.targetDuration === 'number') {
|
||||
return patchBlock.targetDuration;
|
||||
}
|
||||
if (patchBlock.targetDuration === null || patchBlock.targetDuration === '') {
|
||||
return null;
|
||||
}
|
||||
return originalBlock.targetDuration;
|
||||
};
|
||||
|
||||
return {
|
||||
id: originalBlock.id,
|
||||
type: SupportedEntry.Block,
|
||||
title: makeString(patchBlock.title, originalBlock.title),
|
||||
note: makeString(patchBlock.note, originalBlock.note),
|
||||
entries: patchBlock.entries ?? originalBlock.entries,
|
||||
isNextDay: typeof patchBlock.isNextDay === 'boolean' ? patchBlock.isNextDay : originalBlock.isNextDay,
|
||||
targetDuration: maybeTargetDuration(),
|
||||
colour: makeString(patchBlock.colour, originalBlock.colour),
|
||||
revision: originalBlock.revision,
|
||||
timeStart: originalBlock.timeStart,
|
||||
timeEnd: originalBlock.timeEnd,
|
||||
duration: originalBlock.duration,
|
||||
isFirstLinked: originalBlock.isFirstLinked,
|
||||
custom: { ...originalBlock.custom, ...patchBlock.custom },
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function for patching an existing event with new data
|
||||
* Increments the revision of the event when applying the patch
|
||||
*/
|
||||
export function applyPatchToEntry<T extends OntimeEntry>(eventFromRundown: T, patch: Partial<T>): T {
|
||||
if (isOntimeEvent(eventFromRundown)) {
|
||||
const newEvent = createPatch(eventFromRundown, patch as Partial<OntimeEvent>);
|
||||
const newEvent = createEventPatch(eventFromRundown, patch as Partial<OntimeEvent>);
|
||||
newEvent.revision++;
|
||||
return newEvent as T;
|
||||
}
|
||||
|
||||
if (isOntimeBlock(eventFromRundown)) {
|
||||
const newBlock: OntimeBlock = { ...eventFromRundown, ...patch };
|
||||
const newBlock: OntimeBlock = createBlockPatch(eventFromRundown, patch as Partial<OntimeBlock>);
|
||||
newBlock.revision++;
|
||||
return newBlock as T;
|
||||
}
|
||||
@@ -139,7 +175,7 @@ export const createEvent = (eventArgs: Partial<OntimeEvent>, eventIndex: number
|
||||
cue,
|
||||
...eventDef,
|
||||
};
|
||||
const event = createPatch(baseEvent, eventArgs);
|
||||
const event = createEventPatch(baseEvent, eventArgs);
|
||||
return event;
|
||||
};
|
||||
|
||||
@@ -359,6 +395,22 @@ export function getInsertAfterId(rundown: Rundown, afterId?: EntryId, beforeId?:
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitises custom fields in an entry by removing fields
|
||||
* - if it does not exist in the project
|
||||
* - if the value is empty string
|
||||
* Mutates the entryCustomFields object
|
||||
*/
|
||||
export function cleanupCustomFields(entryCustomFields: EntryCustomFields, projectCustomFields: CustomFields) {
|
||||
for (const field in entryCustomFields) {
|
||||
if (!Object.hasOwn(projectCustomFields, field)) {
|
||||
delete entryCustomFields[field];
|
||||
} else if (entryCustomFields[field] === '') {
|
||||
delete entryCustomFields[field];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* converts an index from the timedEventOrder to an index in the playableEventOrder
|
||||
* or returns null if it can not be found
|
||||
|
||||
Reference in New Issue
Block a user