refactor: maintain flat orders

This commit is contained in:
Carlos Valente
2025-04-17 10:12:06 +02:00
parent ed7f8a0aa2
commit a55dfba4ef
13 changed files with 71 additions and 25 deletions
@@ -13,6 +13,7 @@ const cachedRundownPlaceholder: Rundown = {
id: 'default', id: 'default',
title: '', title: '',
order: [], order: [],
flatOrder: [],
entries: {}, entries: {},
revision: -1, revision: -1,
}; };
@@ -22,6 +22,7 @@ describe('aggregateRundowns()', () => {
title: '', title: '',
revision: 0, revision: 0,
order: ['1', '2'], order: ['1', '2'],
flatOrder: ['1', '2'],
entries: { entries: {
'1': { id: '1' } as OntimeEntry, '1': { id: '1' } as OntimeEntry,
'2': { id: '2' } as OntimeEntry, '2': { id: '2' } as OntimeEntry,
@@ -32,6 +33,7 @@ describe('aggregateRundowns()', () => {
title: '', title: '',
revision: 0, revision: 0,
order: ['3', '4'], order: ['3', '4'],
flatOrder: ['3', '4'],
entries: { entries: {
'3': { id: '3' } as OntimeEntry, '3': { id: '3' } as OntimeEntry,
'4': { id: '4' } as OntimeEntry, '4': { id: '4' } as OntimeEntry,
+1
View File
@@ -5,6 +5,7 @@ export const defaultRundown: Rundown = {
id: 'default', id: 'default',
title: 'Default', title: 'Default',
order: [], order: [],
flatOrder: [],
entries: {}, entries: {},
revision: 0, revision: 0,
}; };
+19
View File
@@ -19,6 +19,25 @@ export const demoDb: DatabaseModel = {
'bab4a', 'bab4a',
'd3eb1', 'd3eb1',
], ],
flatOrder: [
'block',
'32d31',
'21cd2',
'0b371',
'3cd28',
'e457f',
'01e85',
'1c420',
'b7737',
'd3a80',
'8276c',
'2340b',
'cb90b',
'503c4',
'5e965',
'bab4a',
'd3eb1',
],
entries: { entries: {
block: { block: {
type: SupportedEvent.Block, type: SupportedEvent.Block,
@@ -71,7 +71,7 @@ export async function addEvent(eventData: EventPostPayload): Promise<OntimeEntry
} }
// 2. if the user provides a parent (inside a group), we make sure it exists // 2. if the user provides a parent (inside a group), we make sure it exists
let parent: EntryId | undefined; let parent: EntryId | null = null;
if ('parent' in eventData && eventData.parent != null) { if ('parent' in eventData && eventData.parent != null) {
if (!cache.hasId(eventData.parent)) { if (!cache.hasId(eventData.parent)) {
throw new Error(`Parent event with ID ${eventData.parent} not found`); throw new Error(`Parent event with ID ${eventData.parent} not found`);
@@ -49,6 +49,19 @@ describe('generate()', () => {
); );
}); });
it('generates metadata from given rundown', () => {
const initResult = generate(demoDb.rundowns.default, demoDb.customFields);
expect(initResult.order.length).toBe(12);
expect(initResult.flatEntryOrder.length).toBe(17);
expect(initResult.timedEventOrder.length).toBe(14);
expect(initResult.playableEventOrder.length).toBe(14);
expect(initResult.firstStart).toBe(36000000);
expect(initResult.lastEnd).toBe(61800000);
expect(initResult.totalDays).toBe(0);
expect(initResult.totalDelay).toBe(0);
});
it('creates normalised versions of a given rundown', () => { it('creates normalised versions of a given rundown', () => {
const rundown = makeRundown({ const rundown = makeRundown({
order: ['1', '2', '3'], order: ['1', '2', '3'],
@@ -613,7 +626,7 @@ describe('add() mutation', () => {
test('adds an event to the rundown', () => { test('adds an event to the rundown', () => {
const mockEvent = makeOntimeEvent({ id: 'mock', cue: 'mock' }); const mockEvent = makeOntimeEvent({ id: 'mock', cue: 'mock' });
const rundown = makeRundown({}); const rundown = makeRundown({});
const { newRundown } = add({ atIndex: 0, event: mockEvent, rundown }); const { newRundown } = add({ atIndex: 0, entry: mockEvent, parent: null, rundown });
expect(newRundown.order.length).toBe(1); expect(newRundown.order.length).toBe(1);
expect(newRundown.entries['mock']).toMatchObject(mockEvent); expect(newRundown.entries['mock']).toMatchObject(mockEvent);
}); });
@@ -7,9 +7,9 @@ export type RundownMetadata = {
firstStart: MaybeNumber; firstStart: MaybeNumber;
lastEnd: MaybeNumber; lastEnd: MaybeNumber;
playableEventOrder: EntryId[]; playableEventOrder: EntryId[]; // flat order of playable events
timedEventOrder: EntryId[]; timedEventOrder: EntryId[]; // flat order of timed events
flatEventOrder: EntryId[]; flatEntryOrder: EntryId[]; // flat order of entries
/** /**
* Keep track of which custom fields are used. * Keep track of which custom fields are used.
@@ -40,7 +40,7 @@ let rundownMetadata: RundownMetadata = {
playableEventOrder: [], playableEventOrder: [],
timedEventOrder: [], timedEventOrder: [],
flatEventOrder: [], flatEntryOrder: [],
assignedCustomFields: {}, assignedCustomFields: {},
}; };
@@ -172,7 +172,7 @@ export function updateCache() {
const { previousEvent, latestEvent, ...metadata } = processedData; const { previousEvent, latestEvent, ...metadata } = processedData;
currentRundown.entries = metadata.entries; currentRundown.entries = metadata.entries;
currentRundown.order = metadata.order; currentRundown.order = metadata.order;
currentRundown.flatOrder = metadata.flatEventOrder; currentRundown.flatOrder = metadata.flatEntryOrder;
rundownMetadata = metadata; rundownMetadata = metadata;
clearIsStale(); clearIsStale();
customFieldChangelog = {}; customFieldChangelog = {};
@@ -320,7 +320,7 @@ export function mutateCache<T extends object>(mutation: MutatingFn<T>) {
return scopedMutation; return scopedMutation;
} }
type AddArgs = MutationParams<{ atIndex: number; parent?: EntryId; entry: OntimeEntry }>; type AddArgs = MutationParams<{ atIndex: number; parent: EntryId | null; entry: OntimeEntry }>;
/** /**
* Add entry to rundown * Add entry to rundown
*/ */
@@ -158,7 +158,7 @@ export function makeRundownMetadata(customFields: CustomFields, customFieldChang
assignedCustomFields: {}, assignedCustomFields: {},
playableEventOrder: [], playableEventOrder: [],
timedEventOrder: [], timedEventOrder: [],
flatEventOrder: [], flatEntryOrder: [],
entries: {}, entries: {},
order: [], order: [],
@@ -192,12 +192,10 @@ function processEntry<T extends OntimeEntry>(
): { processedData: ProcessedRundownMetadata; processedEntry: T } { ): { processedData: ProcessedRundownMetadata; processedEntry: T } {
const processedData = { ...rundownMetadata }; const processedData = { ...rundownMetadata };
const currentEntry = structuredClone(entry); const currentEntry = structuredClone(entry);
processedData.flatEventOrder.push(currentEntry.id); processedData.flatEntryOrder.push(currentEntry.id);
if (isOntimeEvent(currentEntry)) { if (isOntimeEvent(currentEntry)) {
if (!childOfBlock) { processedData.timedEventOrder.push(currentEntry.id);
processedData.timedEventOrder.push(currentEntry.id);
}
/** /**
* 1.Checks that link can be established (ie, events exist and are valid) * 1.Checks that link can be established (ie, events exist and are valid)
@@ -227,9 +225,7 @@ function processEntry<T extends OntimeEntry>(
// update rundown metadata, it only concerns playable events // update rundown metadata, it only concerns playable events
if (isPlayableEvent(currentEntry)) { if (isPlayableEvent(currentEntry)) {
if (!childOfBlock) { processedData.playableEventOrder.push(currentEntry.id);
processedData.playableEventOrder.push(currentEntry.id);
}
// first start is always the first event // first start is always the first event
if (processedData.firstStart === null) { if (processedData.firstStart === null) {
@@ -122,23 +122,23 @@ export function findPrevious(currentEventId?: string): OntimeEvent | undefined {
* finds the next event * finds the next event
*/ */
export function findNext(currentEventId?: string): PlayableEvent | undefined { export function findNext(currentEventId?: string): PlayableEvent | undefined {
const { playableEventsOrder } = cache.getEventOrder(); const { playableEventOrder } = cache.getMetadata();
if (!playableEventsOrder.length) { if (!playableEventOrder.length) {
return; return;
} }
// if there is no event running, go to first // if there is no event running, go to first
if (!currentEventId) { if (!currentEventId) {
return getFirstPlayable(playableEventsOrder); return getFirstPlayable(playableEventOrder);
} }
const currentIndex = playableEventsOrder.findIndex((eventId) => eventId === currentEventId); const currentIndex = playableEventOrder.findIndex((eventId) => eventId === currentEventId);
const newIndex = Math.min(currentIndex + 1, playableEventsOrder.length - 1); const newIndex = Math.min(currentIndex + 1, playableEventOrder.length - 1);
const nextEventId = playableEventsOrder.at(newIndex); const nextEventId = playableEventOrder.at(newIndex);
if (!nextEventId) { if (!nextEventId) {
return getFirstPlayable(playableEventsOrder); return getFirstPlayable(playableEventOrder);
} }
return getEntryWithId(nextEventId) as PlayableEvent | undefined; return getEntryWithId(nextEventId) as PlayableEvent | undefined;
@@ -176,7 +176,7 @@ export function getRundownOrThrow(rundowns: ProjectRundowns, rundownId: string):
} }
export function getInsertionPosition( export function getInsertionPosition(
parentId?: EntryId, parentId: EntryId | null,
afterId?: EntryId, afterId?: EntryId,
beforeId?: EntryId, beforeId?: EntryId,
): { atIndex: number; afterId: EntryId | undefined } { ): { atIndex: number; afterId: EntryId | undefined } {
@@ -202,7 +202,7 @@ export function getInsertionPosition(
afterId: undefined, afterId: undefined,
}; };
function selectOrderList(parentId?: EntryId) { function selectOrderList(parentId: EntryId | null) {
if (parentId) { if (parentId) {
return (getEntryWithId(parentId) as OntimeBlock).events; return (getEntryWithId(parentId) as OntimeBlock).events;
} }
@@ -54,6 +54,7 @@ describe('parseRundown()', () => {
id: '', id: '',
title: '', title: '',
order: ['1', '2', '3', '4'], order: ['1', '2', '3', '4'],
flatOrder: ['1', '2', '3', '4'],
entries: { entries: {
'1': { id: '1', type: SupportedEvent.Event, title: 'test', skip: false } as OntimeEvent, // OK '1': { id: '1', type: SupportedEvent.Event, title: 'test', skip: false } as OntimeEvent, // OK
'2': { id: '1', type: SupportedEvent.Block, title: 'test 2', skip: false } as OntimeBlock, // duplicate ID '2': { id: '1', type: SupportedEvent.Block, title: 'test 2', skip: false } as OntimeBlock, // duplicate ID
@@ -84,6 +85,7 @@ describe('parseRundown()', () => {
id: '', id: '',
title: '', title: '',
order: ['1', '2'], order: ['1', '2'],
flatOrder: ['1', '2'],
entries: { entries: {
// @ts-expect-error -- testing external data which could be incorrect // @ts-expect-error -- testing external data which could be incorrect
'1': { id: '1', type: SupportedEvent.Event, cue: 101 } as OntimeEvent, '1': { id: '1', type: SupportedEvent.Event, cue: 101 } as OntimeEvent,
@@ -110,6 +112,7 @@ describe('parseRundown()', () => {
id: '', id: '',
title: '', title: '',
order: ['1', '1'], order: ['1', '1'],
flatOrder: ['1', '1'],
entries: { entries: {
'1': { id: '1', type: SupportedEvent.Event } as OntimeEvent, '1': { id: '1', type: SupportedEvent.Event } as OntimeEvent,
'2': { id: '2', type: SupportedEvent.Event } as OntimeEvent, '2': { id: '2', type: SupportedEvent.Event } as OntimeEvent,
@@ -127,6 +130,7 @@ describe('parseRundown()', () => {
id: 'test', id: 'test',
title: '', title: '',
order: ['1', '2'], order: ['1', '2'],
flatOrder: ['1', '2'],
entries: { entries: {
'1': { id: '1', type: SupportedEvent.Event } as OntimeEvent, '1': { id: '1', type: SupportedEvent.Event } as OntimeEvent,
'2': { id: '2', type: SupportedEvent.Event } as OntimeEvent, '2': { id: '2', type: SupportedEvent.Event } as OntimeEvent,
@@ -155,6 +159,7 @@ describe('parseRundown()', () => {
id: 'test', id: 'test',
title: '', title: '',
order: ['1', '2', '3', '4'], order: ['1', '2', '3', '4'],
flatOrder: ['1', '2', '3', '4'],
entries: { entries: {
'1': { id: '1', type: SupportedEvent.Event } as OntimeEvent, '1': { id: '1', type: SupportedEvent.Event } as OntimeEvent,
'2': { id: '2', type: SupportedEvent.Event } as OntimeEvent, '2': { id: '2', type: SupportedEvent.Event } as OntimeEvent,
@@ -173,6 +178,7 @@ describe('parseRundown()', () => {
id: 'test', id: 'test',
title: '', title: '',
order: ['1', '2', '3', '4'], order: ['1', '2', '3', '4'],
flatOrder: ['1', '2', '3', '4'],
entries: { entries: {
'1': { id: '1', type: SupportedEvent.Event } as OntimeEvent, '1': { id: '1', type: SupportedEvent.Event } as OntimeEvent,
'2': { id: '2', type: SupportedEvent.Event } as OntimeEvent, '2': { id: '2', type: SupportedEvent.Event } as OntimeEvent,
@@ -191,6 +197,7 @@ describe('parseRundown()', () => {
id: 'test', id: 'test',
title: '', title: '',
order: ['block'], order: ['block'],
flatOrder: ['block'],
entries: { entries: {
block: makeOntimeBlock({ id: 'block', events: ['1', '2'] }), block: makeOntimeBlock({ id: 'block', events: ['1', '2'] }),
'1': makeOntimeEvent({ id: '1' }), '1': makeOntimeEvent({ id: '1' }),
+2
View File
@@ -109,6 +109,7 @@ export const parseExcel = (
id: generateId(), id: generateId(),
title: sheetName, title: sheetName,
order: [], order: [],
flatOrder: [],
entries: {}, entries: {},
revision: 0, revision: 0,
}; };
@@ -326,6 +327,7 @@ export const parseExcel = (
event.timerType = TimerType.CountDown; event.timerType = TimerType.CountDown;
} }
rundown.order.push(id); rundown.order.push(id);
rundown.flatOrder.push(id);
rundown.entries[id] = event; rundown.entries[id] = event;
}); });
+5
View File
@@ -74,6 +74,7 @@ export function parseRundown(
title: rundown.title ?? '', title: rundown.title ?? '',
entries: {}, entries: {},
order: [], order: [],
flatOrder: [],
revision: rundown.revision ?? 1, revision: rundown.revision ?? 1,
}; };
@@ -95,6 +96,7 @@ export function parseRundown(
const id = entryId; const id = entryId;
let newEvent: OntimeEvent | OntimeDelay | OntimeBlock | null; let newEvent: OntimeEvent | OntimeDelay | OntimeBlock | null;
const nestedEntryIds: string[] = [];
if (isOntimeEvent(event)) { if (isOntimeEvent(event)) {
newEvent = createEvent(event, eventIndex); newEvent = createEvent(event, eventIndex);
@@ -139,6 +141,7 @@ export function parseRundown(
eventIndex += 1; eventIndex += 1;
if (newNestedEvent) { if (newNestedEvent) {
nestedEntryIds.push(nestedEventId);
parsedRundown.entries[nestedEventId] = newNestedEvent; parsedRundown.entries[nestedEventId] = newNestedEvent;
} }
} }
@@ -162,6 +165,8 @@ export function parseRundown(
if (newEvent) { if (newEvent) {
parsedRundown.entries[id] = newEvent; parsedRundown.entries[id] = newEvent;
parsedRundown.order.push(id); parsedRundown.order.push(id);
parsedRundown.flatOrder.push(id);
parsedRundown.flatOrder.push(...nestedEntryIds);
} }
} }