refactor: restructure model to contain an object of rundowns

This commit is contained in:
Carlos Valente
2025-03-15 09:04:33 +01:00
committed by arc-alex
parent 0a80d6db31
commit abd9b127db
111 changed files with 4357 additions and 4515 deletions
+70 -47
View File
@@ -13,11 +13,12 @@ import {
import {
CustomFields,
DatabaseModel,
EventCustomFields,
EntryCustomFields,
isOntimeBlock,
LogOrigin,
OntimeBlock,
OntimeEvent,
OntimeRundown,
Rundown,
SupportedEvent,
TimerType,
TimeStrategy,
@@ -28,17 +29,14 @@ import { logger } from '../classes/Logger.js';
import { event as eventDef } from '../models/eventsDefinition.js';
import { makeString } from './parserUtils.js';
import { parseProject, parseRundown, parseSettings, parseUrlPresets, parseViewSettings } from './parserFunctions.js';
import { parseProject, parseRundowns, parseSettings, parseUrlPresets, parseViewSettings } from './parserFunctions.js';
import { parseExcelDate } from './time.js';
import { Merge } from 'ts-essentials';
export type ErrorEmitter = (message: string) => void;
export const EXCEL_MIME = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
export const JSON_MIME = 'application/json';
type ExcelData = Pick<DatabaseModel, 'rundown' | 'customFields'> & {
rundownMetadata: Record<string, { row: number; col: number }>;
};
function parseBooleanString(value: unknown): boolean {
if (typeof value === 'boolean') {
return value;
@@ -83,9 +81,14 @@ export function getCustomFieldData(
export const parseExcel = (
excelData: unknown[][],
existingCustomFields: CustomFields,
sheetName: string = 'Rundown from excel',
options?: Partial<ImportMap>,
): ExcelData => {
const rundownMetadata = {};
): {
rundown: Rundown;
customFields: CustomFields;
rundownMetadata: Record<string, { row: number; col: number }>;
} => {
const rundownMetadata: Record<string, { row: number; col: number }> = {};
const importMap: ImportMap = { ...defaultImportMap, ...options };
for (const [key, value] of Object.entries(importMap)) {
@@ -95,7 +98,13 @@ export const parseExcel = (
}
const { customFields, customFieldImportKeys } = getCustomFieldData(importMap, existingCustomFields);
const rundown: OntimeRundown = [];
const rundown: Rundown = {
id: generateId(),
title: sheetName,
order: [],
entries: {},
revision: 0,
};
// title stuff: strings
let titleIndex: number | null = null;
@@ -205,8 +214,8 @@ export const parseExcel = (
},
} as const;
const event: any = {};
const eventCustomFields: EventCustomFields = {};
const entry: Partial<Merge<OntimeEvent, OntimeBlock>> = {};
const entryCustomFields: EntryCustomFields = {};
for (let j = 0; j < row.length; j++) {
const column = row[j];
@@ -214,48 +223,50 @@ export const parseExcel = (
if (j === timerTypeIndex) {
const maybeTimeType = makeString(column, '');
if (maybeTimeType === 'block') {
event.type = SupportedEvent.Block;
// we leave this as a clue for the object filtering later on
entry.type = SupportedEvent.Block;
} else if (maybeTimeType === '' || maybeTimeType === 'event' || isKnownTimerType(maybeTimeType)) {
event.type = SupportedEvent.Event;
event.timerType = validateTimerType(maybeTimeType);
// @ts-expect-error -- we leave this as a clue for the object filtering later on
entry.type = SupportedEvent.Event;
entry.timerType = validateTimerType(maybeTimeType);
} else {
// if it is not a block or a known type, we dont import it
return;
}
} else if (j === titleIndex) {
event.title = makeString(column, '');
entry.title = makeString(column, '');
} else if (j === timeStartIndex) {
event.timeStart = parseExcelDate(column);
entry.timeStart = parseExcelDate(column);
} else if (j === linkStartIndex) {
event.linkStart = parseBooleanString(column);
entry.linkStart = parseBooleanString(column) ? 'true' : null;
} else if (j === timeEndIndex) {
event.timeEnd = parseExcelDate(column);
entry.timeEnd = parseExcelDate(column);
} else if (j === durationIndex) {
event.duration = parseExcelDate(column);
entry.duration = parseExcelDate(column);
} else if (j === cueIndex) {
event.cue = makeString(column, '');
entry.cue = makeString(column, '');
} else if (j === countToEndIndex) {
event.countToEnd = parseBooleanString(column);
entry.countToEnd = parseBooleanString(column);
} else if (j === isPublicIndex) {
event.isPublic = parseBooleanString(column);
entry.isPublic = parseBooleanString(column);
} else if (j === skipIndex) {
event.skip = parseBooleanString(column);
entry.skip = parseBooleanString(column);
} else if (j === notesIndex) {
event.note = makeString(column, '');
entry.note = makeString(column, '');
} else if (j === endActionIndex) {
event.endAction = validateEndAction(column);
entry.endAction = validateEndAction(column);
} else if (j === timeWarningIndex) {
event.timeWarning = parseExcelDate(column);
entry.timeWarning = parseExcelDate(column);
} else if (j === timeDangerIndex) {
event.timeDanger = parseExcelDate(column);
entry.timeDanger = parseExcelDate(column);
} else if (j === colourIndex) {
event.colour = makeString(column, '');
entry.colour = makeString(column, '');
} else if (j === entryIdIndex) {
event.id = encodeURIComponent(makeString(column, undefined));
entry.id = encodeURIComponent(makeString(column, undefined));
} else if (j in customFieldIndexes) {
const importKey = customFieldIndexes[j];
const ontimeKey = customFieldImportKeys[importKey];
eventCustomFields[ontimeKey] = makeString(column, '');
entryCustomFields[ontimeKey] = makeString(column, '');
} else {
// 2. if there is no flag, lets see if we know the field type
if (typeof column === 'string') {
@@ -282,20 +293,32 @@ export const parseExcel = (
}
}
// if any data was found in row, push to array
const keysFound = Object.keys(event).length + Object.keys(eventCustomFields).length;
if (keysFound > 0) {
// if it is a Block type drop all other filed
if (isOntimeBlock(event)) {
rundown.push({ type: event.type, id: event.id, title: event.title });
} else {
if (timerTypeIndex === null) {
event.timerType = TimerType.CountDown;
event.type = SupportedEvent.Event;
}
rundown.push({ ...event, custom: { ...eventCustomFields } });
}
// if we didnt find any keys (empty row, or some other data), skip making an event
const keysFound = Object.keys(entry).length + Object.keys(entryCustomFields).length;
if (keysFound === 0) {
return;
}
const id = entry.id || generateId();
// from excel, we can only get blocks and events
if (isOntimeBlock(entry)) {
const block: OntimeBlock = { ...entry, custom: { ...entryCustomFields } };
rundown.order.push(id);
rundown.entries[id] = block;
return;
}
const event = {
...entry,
custom: { ...entryCustomFields },
type: SupportedEvent.Event,
} as OntimeEvent;
if (timerTypeIndex === null) {
event.timerType = TimerType.CountDown;
}
rundown.order.push(id);
rundown.entries[id] = event;
});
return {
@@ -327,11 +350,10 @@ export function parseDatabaseModel(jsonData: Partial<DatabaseModel>): { data: Da
};
// we need to parse the custom fields first so they can be used in validating events
// TODO: can we improve the readability of the error?
const { rundown, customFields } = parseRundown(jsonData, makeEmitError('Rundown'));
const { rundowns, customFields } = parseRundowns(jsonData, makeEmitError('Rundown'));
const data: DatabaseModel = {
rundown,
rundowns,
project: parseProject(jsonData, makeEmitError('Project')),
settings,
viewSettings: parseViewSettings(jsonData, makeEmitError('View Settings')),
@@ -394,6 +416,7 @@ export function createPatch(originalEvent: OntimeEvent, patchEvent: Partial<Onti
gap: originalEvent.gap, // is regenerated if timer related data is changed
// short circuit empty string
cue: makeString(patchEvent.cue ?? null, originalEvent.cue),
currentBlock: originalEvent.currentBlock,
revision: originalEvent.revision,
timeWarning: patchEvent.timeWarning ?? originalEvent.timeWarning,
timeDanger: patchEvent.timeDanger ?? originalEvent.timeDanger,