mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-08 17:03:53 +00:00
feat: milestones
This commit is contained in:
committed by
Carlos Valente
parent
a7ae8598db
commit
f2913971db
@@ -4,8 +4,6 @@ import {
|
||||
ProjectRundowns,
|
||||
Rundown,
|
||||
OntimeEvent,
|
||||
OntimeDelay,
|
||||
OntimeBlock,
|
||||
isOntimeEvent,
|
||||
isOntimeDelay,
|
||||
isOntimeBlock,
|
||||
@@ -15,6 +13,7 @@ import {
|
||||
PlayableEvent,
|
||||
RundownEntries,
|
||||
isPlayableEvent,
|
||||
isOntimeMilestone,
|
||||
} from 'ontime-types';
|
||||
import { isObjectEmpty, generateId, getLinkedTimes, getTimeFrom, isNewLatest } from 'ontime-utils';
|
||||
|
||||
@@ -22,7 +21,7 @@ import { defaultRundown } from '../../models/dataModel.js';
|
||||
import { delay as delayDef } from '../../models/eventsDefinition.js';
|
||||
import type { ErrorEmitter } from '../../utils/parserUtils.js';
|
||||
|
||||
import { calculateDayOffset, cleanupCustomFields, createBlock, createEvent } from './rundown.utils.js';
|
||||
import { calculateDayOffset, cleanupCustomFields, createBlock, createEvent, createMilestone } from './rundown.utils.js';
|
||||
import { RundownMetadata } from './rundown.types.js';
|
||||
|
||||
/**
|
||||
@@ -93,7 +92,7 @@ export function parseRundown(
|
||||
}
|
||||
|
||||
const id = entryId;
|
||||
let newEvent: OntimeEvent | OntimeDelay | OntimeBlock | null;
|
||||
let newEvent: OntimeEntry | null;
|
||||
const nestedEntryIds: string[] = [];
|
||||
|
||||
if (isOntimeEvent(event)) {
|
||||
@@ -108,13 +107,17 @@ export function parseRundown(
|
||||
eventIndex += 1;
|
||||
} else if (isOntimeDelay(event)) {
|
||||
newEvent = { ...delayDef, duration: event.duration, id };
|
||||
} else if (isOntimeMilestone(event)) {
|
||||
newEvent = createMilestone({ ...event, id });
|
||||
cleanupCustomFields(newEvent.custom, parsedCustomFields);
|
||||
} else if (isOntimeBlock(event)) {
|
||||
for (let i = 0; i < event.entries.length; i++) {
|
||||
const nestedEventId = event.entries[i];
|
||||
const nestedEvent = rundown.entries[nestedEventId];
|
||||
let newNestedEvent: OntimeEntry | null = null;
|
||||
|
||||
if (isOntimeEvent(nestedEvent)) {
|
||||
const newNestedEvent = createEvent(nestedEvent, eventIndex);
|
||||
newNestedEvent = createEvent(nestedEvent, eventIndex);
|
||||
// skip if event is invalid
|
||||
if (newNestedEvent == null) {
|
||||
emitError?.('Skipping event without payload');
|
||||
@@ -123,11 +126,16 @@ export function parseRundown(
|
||||
|
||||
cleanupCustomFields(newNestedEvent.custom, parsedCustomFields);
|
||||
eventIndex += 1;
|
||||
} else if (isOntimeDelay(nestedEvent)) {
|
||||
newNestedEvent = { ...delayDef, duration: nestedEvent.duration, id };
|
||||
} else if (isOntimeMilestone(nestedEvent)) {
|
||||
newNestedEvent = createMilestone({ ...nestedEvent, id });
|
||||
cleanupCustomFields(newNestedEvent.custom, parsedCustomFields);
|
||||
}
|
||||
|
||||
if (newNestedEvent) {
|
||||
nestedEntryIds.push(nestedEventId);
|
||||
parsedRundown.entries[nestedEventId] = newNestedEvent;
|
||||
}
|
||||
if (newNestedEvent) {
|
||||
nestedEntryIds.push(nestedEventId);
|
||||
parsedRundown.entries[nestedEventId] = newNestedEvent;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,7 +159,7 @@ export function parseRundown(
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Imported rundown ${parsedRundown.title} with ${parsedRundown.order.length} entries`);
|
||||
console.log(`Imported rundown ${parsedRundown.title} with ${parsedRundown.flatOrder.length} entries`);
|
||||
return parsedRundown;
|
||||
}
|
||||
|
||||
|
||||
@@ -247,7 +247,6 @@ export function reorderEntry(entryId: EntryId, destinationId: EntryId, order: 'b
|
||||
throw new Error('Event not found');
|
||||
}
|
||||
|
||||
console.log('reorder', eventFrom.id, eventTo.id, order);
|
||||
rundownMutation.reorder(rundown, eventFrom, eventTo, order);
|
||||
|
||||
const { rundown: rundownResult, rundownMetadata, revision } = commit();
|
||||
|
||||
@@ -5,11 +5,13 @@ import {
|
||||
isOntimeBlock,
|
||||
isOntimeDelay,
|
||||
isOntimeEvent,
|
||||
isOntimeMilestone,
|
||||
OntimeBaseEvent,
|
||||
OntimeBlock,
|
||||
OntimeDelay,
|
||||
OntimeEntry,
|
||||
OntimeEvent,
|
||||
OntimeMilestone,
|
||||
Rundown,
|
||||
SupportedEntry,
|
||||
TimeStrategy,
|
||||
@@ -23,7 +25,12 @@ import {
|
||||
validateTimes,
|
||||
} from 'ontime-utils';
|
||||
|
||||
import { event as eventDef, block as blockDef, delay as delayDef } from '../../models/eventsDefinition.js';
|
||||
import {
|
||||
event as eventDef,
|
||||
block as blockDef,
|
||||
delay as delayDef,
|
||||
milestone as milestoneDef,
|
||||
} from '../../models/eventsDefinition.js';
|
||||
import { makeString } from '../../utils/parserUtils.js';
|
||||
import { RundownMetadata } from './rundown.types.js';
|
||||
|
||||
@@ -34,16 +41,16 @@ type CompleteEntry<T> =
|
||||
? OntimeDelay
|
||||
: T extends Partial<OntimeBlock>
|
||||
? OntimeBlock
|
||||
: never;
|
||||
: T extends Partial<OntimeMilestone>
|
||||
? OntimeMilestone
|
||||
: never;
|
||||
|
||||
/**
|
||||
* Generates a fully formed RundownEntry of the patch type
|
||||
*/
|
||||
export function generateEvent<T extends Partial<OntimeEvent> | Partial<OntimeDelay> | Partial<OntimeBlock>>(
|
||||
rundown: Rundown,
|
||||
eventData: T,
|
||||
afterId: EntryId | null,
|
||||
): CompleteEntry<T> {
|
||||
export function generateEvent<
|
||||
T extends Partial<OntimeEvent> | Partial<OntimeDelay> | Partial<OntimeBlock> | Partial<OntimeMilestone>,
|
||||
>(rundown: Rundown, eventData: T, afterId: EntryId | null): CompleteEntry<T> {
|
||||
if (isOntimeEvent(eventData)) {
|
||||
return createEvent(eventData, getCueCandidate(rundown.entries, rundown.order, afterId)) as CompleteEntry<T>;
|
||||
}
|
||||
@@ -59,6 +66,10 @@ export function generateEvent<T extends Partial<OntimeEvent> | Partial<OntimeDel
|
||||
return createBlock({ id, title: eventData.title ?? '' }) as CompleteEntry<T>;
|
||||
}
|
||||
|
||||
if (isOntimeMilestone(eventData)) {
|
||||
return createMilestone({ ...eventData, id }) as CompleteEntry<T>;
|
||||
}
|
||||
|
||||
throw new Error('Invalid event type');
|
||||
}
|
||||
|
||||
@@ -136,25 +147,52 @@ export function createBlockPatch(originalBlock: OntimeBlock, patchBlock: Partial
|
||||
};
|
||||
}
|
||||
|
||||
export function createMilestonePatch(
|
||||
originalMilestone: OntimeMilestone,
|
||||
patchMilestone: Partial<OntimeMilestone>,
|
||||
): OntimeMilestone {
|
||||
if (Object.keys(patchMilestone).length === 0) {
|
||||
return originalMilestone;
|
||||
}
|
||||
|
||||
return {
|
||||
id: originalMilestone.id,
|
||||
type: SupportedEntry.Milestone,
|
||||
cue: makeString(patchMilestone.cue ?? null, originalMilestone.cue),
|
||||
title: makeString(patchMilestone.title, originalMilestone.title),
|
||||
note: makeString(patchMilestone.note, originalMilestone.note),
|
||||
colour: makeString(patchMilestone.colour, originalMilestone.colour),
|
||||
revision: originalMilestone.revision,
|
||||
custom: { ...originalMilestone.custom, ...patchMilestone.custom },
|
||||
parent: originalMilestone.parent,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
export function applyPatchToEntry(eventFromRundown: OntimeEntry, patch: Partial<OntimeEntry>): OntimeEntry {
|
||||
if (isOntimeEvent(eventFromRundown)) {
|
||||
const newEvent = createEventPatch(eventFromRundown, patch as Partial<OntimeEvent>);
|
||||
const newEvent = createEventPatch(eventFromRundown as OntimeEvent, patch as Partial<OntimeEvent>);
|
||||
newEvent.revision++;
|
||||
return newEvent as T;
|
||||
return newEvent;
|
||||
}
|
||||
|
||||
if (isOntimeBlock(eventFromRundown)) {
|
||||
const newBlock: OntimeBlock = createBlockPatch(eventFromRundown, patch as Partial<OntimeBlock>);
|
||||
const newBlock = createBlockPatch(eventFromRundown as OntimeBlock, patch as Partial<OntimeBlock>);
|
||||
newBlock.revision++;
|
||||
return newBlock as T;
|
||||
return newBlock;
|
||||
}
|
||||
|
||||
if (isOntimeMilestone(eventFromRundown)) {
|
||||
const newMilestone = createMilestonePatch(eventFromRundown as OntimeMilestone, patch as Partial<OntimeMilestone>);
|
||||
newMilestone.revision++;
|
||||
return newMilestone;
|
||||
}
|
||||
|
||||
// only delay is left
|
||||
return { ...eventFromRundown, ...patch } as T;
|
||||
return { ...eventFromRundown, ...patch } as OntimeDelay;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -205,6 +243,27 @@ export function createBlock(patch?: Partial<OntimeBlock>): OntimeBlock {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new milestone from an optional patch
|
||||
*/
|
||||
export function createMilestone(patch?: Partial<OntimeMilestone>): OntimeMilestone {
|
||||
if (!patch) {
|
||||
return { ...milestoneDef, id: generateId() };
|
||||
}
|
||||
|
||||
return {
|
||||
id: patch.id ?? generateId(),
|
||||
type: SupportedEntry.Milestone,
|
||||
cue: patch.cue ?? '',
|
||||
title: patch.title ?? '',
|
||||
note: patch.note ?? '',
|
||||
colour: makeString(patch.colour, ''),
|
||||
custom: patch.custom ?? {},
|
||||
parent: patch.parent ?? null,
|
||||
revision: 0,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Function infers strategy for a patch with only partial timer data
|
||||
* @param end
|
||||
@@ -317,6 +376,15 @@ export function cloneDelay(entry: OntimeDelay, newId: EntryId): OntimeDelay {
|
||||
return newEntry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gathers business logic for how to clone an OntimeMilestone
|
||||
*/
|
||||
export function cloneMilestone(entry: OntimeMilestone, newId: EntryId): OntimeMilestone {
|
||||
const newEntry = structuredClone(entry);
|
||||
newEntry.id = newId;
|
||||
return newEntry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gathers business logic for how to clone an OntimeBlock
|
||||
*/
|
||||
@@ -333,13 +401,15 @@ export function cloneBlock(entry: OntimeBlock, newId: EntryId): OntimeBlock {
|
||||
/**
|
||||
* Receives an entry and chooses the correct cloning strategy
|
||||
*/
|
||||
export function cloneEntry<T extends OntimeEntry>(entry: T, newId: EntryId): T {
|
||||
export function cloneEntry(entry: OntimeEntry, newId: EntryId): OntimeEntry {
|
||||
if (isOntimeEvent(entry)) {
|
||||
return cloneEvent(entry, newId) as T;
|
||||
return cloneEvent(entry, newId);
|
||||
} else if (isOntimeDelay(entry)) {
|
||||
return cloneDelay(entry, newId) as T;
|
||||
} else if (entry.type === 'block') {
|
||||
return cloneBlock(entry as OntimeBlock, newId) as T;
|
||||
return cloneDelay(entry, newId);
|
||||
} else if (isOntimeBlock(entry)) {
|
||||
return cloneBlock(entry, newId);
|
||||
} else if (isOntimeMilestone(entry)) {
|
||||
return cloneMilestone(entry, newId);
|
||||
}
|
||||
throw new Error(`Unsupported entry type for cloning: ${entry}`);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { body, param } from 'express-validator';
|
||||
import { requestValidationFunction } from '../validation-utils/validationFunction.js';
|
||||
|
||||
export const rundownPostValidator = [
|
||||
body('type').isString().isIn(['event', 'delay', 'block']),
|
||||
body('type').isString().isIn(['event', 'delay', 'block', 'milestone']),
|
||||
body('after').optional().isString(),
|
||||
body('before').optional().isString(),
|
||||
|
||||
|
||||
Reference in New Issue
Block a user