chore: rename blocks to groups

This commit is contained in:
Carlos Valente
2025-08-09 06:47:13 +02:00
committed by Carlos Valente
parent 486d89ecf4
commit e5d2457717
83 changed files with 987 additions and 981 deletions
@@ -46,7 +46,7 @@ describe('test parseDatabaseModel() with demo project (valid)', () => {
// remove time-related fields from the comparison
// these are not calculated in the parser
Object.values(filteredDemoProject.rundowns.default.entries).forEach((entry: any) => {
if (entry.type === SupportedEntry.Block) {
if (entry.type === SupportedEntry.Group) {
delete entry.timeStart;
delete entry.timeEnd;
delete entry.duration;
@@ -54,7 +54,7 @@ describe('test parseDatabaseModel() with demo project (valid)', () => {
}
});
Object.values(data.rundowns.default.entries).forEach((entry: any) => {
if (entry.type === SupportedEntry.Block) {
if (entry.type === SupportedEntry.Group) {
delete entry.timeStart;
delete entry.timeEnd;
delete entry.duration;
@@ -5,7 +5,7 @@ import {
EndAction,
EntryCustomFields,
NormalisedAutomation,
OntimeBlock,
OntimeGroup,
OntimeEntry,
ProjectData,
ProjectRundowns,
@@ -317,7 +317,8 @@ export function migrateAutomations(jsonData: object): AutomationSettings | undef
* - add parent
*
* - block:
* - add all the new blocks of the block that is now a group
* - rename to group
* - create group data
*/
export function migrateRundown(
jsonData: object,
@@ -392,13 +393,13 @@ export function migrateRundown(
});
} else if (entry.type === 'block') {
if (parent) {
(newRundown.entries[parent] as OntimeBlock).entries = [...children];
(newRundown.entries[parent] as OntimeGroup).entries = [...children];
children = [];
}
parent = entry.id;
append({
id: entry.id,
type: SupportedEntry.Block,
type: SupportedEntry.Group,
title: entry.title,
note: '', // leave blank
entries: [], // leave empty
@@ -418,7 +419,7 @@ export function migrateRundown(
}
if (parent) {
(newRundown.entries[parent] as OntimeBlock).entries = [...children];
(newRundown.entries[parent] as OntimeGroup).entries = [...children];
children = [];
}
@@ -48,7 +48,7 @@ describe('v3 to v4', () => {
dayOffset: 0,
gap: 0,
},
{ id: 'block0', type: 'block', title: 'BLOCK 0' },
{ id: 'group0', type: 'block', title: 'GROUP 0' },
{
id: 'event2',
type: SupportedEntry.Event,
@@ -107,7 +107,7 @@ describe('v3 to v4', () => {
dayOffset: 0,
gap: 0,
},
{ id: 'block1', type: 'block', title: 'BLOCK 1' },
{ id: 'group1', type: 'block', title: 'GROUP 1' },
{ id: 'delay', type: 'delay', duration: 1000 },
],
project: {
@@ -268,8 +268,8 @@ describe('v3 to v4', () => {
const expectedRundown: Rundown = {
id: 'default',
title: 'Default',
order: ['event1', 'block0', 'block1'],
flatOrder: ['event1', 'block0', 'event2', 'event3', 'block1', 'delay'],
order: ['event1', 'group0', 'group1'],
flatOrder: ['event1', 'group0', 'event2', 'event3', 'group1', 'delay'],
entries: {
event1: {
id: 'event1',
@@ -298,10 +298,10 @@ describe('v3 to v4', () => {
dayOffset: 0,
gap: 0,
},
block0: {
id: 'block0',
type: SupportedEntry.Block,
title: 'BLOCK 0',
group0: {
id: 'group0',
type: SupportedEntry.Group,
title: 'GROUP 0',
colour: '',
custom: {},
duration: 0,
@@ -337,7 +337,7 @@ describe('v3 to v4', () => {
},
triggers: [{ id: 'testTrig', title: 'Test trigger', trigger: TimerLifeCycle.onStart, automationId: '1' }],
flag: false,
parent: 'block0',
parent: 'group0',
revision: -1,
delay: 0,
dayOffset: 0,
@@ -368,16 +368,16 @@ describe('v3 to v4', () => {
},
triggers: [{ id: 'testTrig', title: 'Test trigger', trigger: TimerLifeCycle.onStart, automationId: '1' }],
flag: false,
parent: 'block0',
parent: 'group0',
revision: -1,
delay: 0,
dayOffset: 0,
gap: 0,
},
block1: {
id: 'block1',
type: SupportedEntry.Block,
title: 'BLOCK 1',
group1: {
id: 'group1',
type: SupportedEntry.Group,
title: 'GROUP 1',
colour: '',
custom: {},
duration: 0,
@@ -393,7 +393,7 @@ describe('v3 to v4', () => {
type: SupportedEntry.Delay,
id: 'delay',
duration: 1000,
parent: 'block1',
parent: 'group1',
},
},
@@ -156,10 +156,10 @@ describe('parseExcel()', () => {
expect((firstEvent as OntimeEvent).title).toBe('A song from the hearth');
});
it('imports blocks', () => {
it('imports groups', () => {
const testdata = [
['Title', 'Timer type'],
['a block', 'block'],
['a group', 'group'],
['an event', 'clock'],
];
@@ -171,7 +171,7 @@ describe('parseExcel()', () => {
const firstEvent = result.rundown.entries[result.rundown.order[0]];
expect(result.rundown.order.length).toBe(2);
expect((firstEvent as OntimeEvent).type).toBe(SupportedEntry.Block);
expect((firstEvent as OntimeEvent).type).toBe(SupportedEntry.Group);
});
it('imports as events if there is no timer type column', () => {
@@ -301,8 +301,8 @@ describe('parseExcel()', () => {
['9:45:00', '10:56:00', 'B', 'x', 'count-down'],
['10:00:00', '16:36:00', 'C', 'x', 'count-down'],
['21:45:00', '22:56:00', 'D', '', 'count-down'],
['', '', 'BLOCK', 'x', 'block'], // <-- block with link
['00:0:00', '23:56:00', 'E', 'x', 'count-down'], // <-- link past blocks
['', '', 'GROUP', 'x', 'group'], // <-- group with link
['00:0:00', '23:56:00', 'E', 'x', 'count-down'], // <-- must link past previous group
];
const importMap = {
@@ -315,7 +315,7 @@ describe('parseExcel()', () => {
const result = parseExcel(testData, {}, 'testSheet', importMap);
expect(result.rundown.order.length).toBe(6);
expect(result.rundown.order).toMatchObject(['A', 'B', 'C', 'D', 'BLOCK', 'E']);
expect(result.rundown.order).toMatchObject(['A', 'B', 'C', 'D', 'GROUP', 'E']);
expect(result.rundown.entries).toMatchObject({
A: {
@@ -330,8 +330,8 @@ describe('parseExcel()', () => {
D: {
linkStart: false,
},
BLOCK: {
type: SupportedEntry.Block,
GROUP: {
type: SupportedEntry.Group,
},
E: {
linkStart: true,
+11 -10
View File
@@ -2,10 +2,10 @@ import {
CustomFields,
Rundown,
OntimeEvent,
OntimeBlock,
OntimeGroup,
EntryCustomFields,
SupportedEntry,
isOntimeBlock,
isOntimeGroup,
TimerType,
CustomFieldKey,
} from 'ontime-types';
@@ -31,6 +31,7 @@ import { parseExcelDate } from '../../utils/time.js';
* @param {array} excelData - array with excel sheet
* @param {ImportOptions} options - an object that contains the import map
* @returns {object} - parsed object
* TODO: import milestones
*/
export const parseExcel = (
excelData: unknown[][],
@@ -170,7 +171,7 @@ export const parseExcel = (
},
} as const;
const entry: Partial<Merge<OntimeEvent, OntimeBlock>> = {};
const entry: Partial<Merge<OntimeEvent, OntimeGroup>> = {};
const entryCustomFields: EntryCustomFields = {};
for (let j = 0; j < row.length; j++) {
@@ -178,16 +179,16 @@ export const parseExcel = (
// 1. we check if we have set a flag for a known field
if (j === timerTypeIndex) {
const maybeTimeType = makeString(column, '');
if (maybeTimeType === 'block') {
if (maybeTimeType === 'group') {
// we leave this as a clue for the object filtering later on
entry.type = SupportedEntry.Block;
entry.type = SupportedEntry.Group;
entry.entries = [];
} else if (maybeTimeType === '' || maybeTimeType === 'event' || isKnownTimerType(maybeTimeType)) {
// @ts-expect-error -- we leave this as a clue for the object filtering later on
entry.type = SupportedEntry.Event;
entry.timerType = validateTimerType(maybeTimeType);
} else {
// if it is not a block or a known type, we dont import it
// if it is not a group or a known type, we dont import it
return;
}
} else if (j === titleIndex) {
@@ -258,11 +259,11 @@ export const parseExcel = (
}
const id = entry.id || generateId();
// from excel, we can only get blocks and events
if (isOntimeBlock(entry)) {
const block: OntimeBlock = { ...entry, custom: { ...entryCustomFields } };
// from excel, we can only get groups, milestones and events
if (isOntimeGroup(entry)) {
const group: OntimeGroup = { ...entry, custom: { ...entryCustomFields } };
rundown.order.push(id);
rundown.entries[id] = block;
rundown.entries[id] = group;
return;
}
@@ -2,7 +2,7 @@ import {
SupportedEntry,
OntimeEvent,
OntimeDelay,
OntimeBlock,
OntimeGroup,
Rundown,
CustomField,
OntimeMilestone,
@@ -16,8 +16,8 @@ const baseEvent = {
revision: 1,
};
const baseBlock = {
type: SupportedEntry.Block,
const baseGroup = {
type: SupportedEntry.Group,
entries: [],
};
@@ -28,7 +28,7 @@ const baseMilestone = {
};
/**
* Utility to create a Ontime event
* Utility to create an Ontime event
*/
export function makeOntimeEvent(patch: Partial<OntimeEvent>): OntimeEvent {
return {
@@ -38,21 +38,21 @@ export function makeOntimeEvent(patch: Partial<OntimeEvent>): OntimeEvent {
}
/**
* Utility to create a delay event
* Utility to create a delay entry
*/
export function makeOntimeDelay(patch: Partial<OntimeDelay>): OntimeDelay {
return { id: 'delay', type: SupportedEntry.Delay, duration: 0, ...patch } as OntimeDelay;
}
/**
* Utility to create a block event
* Utility to create a group entry
*/
export function makeOntimeBlock(patch: Partial<OntimeBlock>): OntimeBlock {
return { id: 'block', ...baseBlock, ...patch } as OntimeBlock;
export function makeOntimeGroup(patch: Partial<OntimeGroup>): OntimeGroup {
return { id: 'group', ...baseGroup, ...patch } as OntimeGroup;
}
/**
* Utility to create a block event
* Utility to create a milestone entry
*/
export function makeOntimeMilestone(patch: Partial<OntimeMilestone>): OntimeMilestone {
return { id: 'milestone', ...baseMilestone, ...patch } as OntimeMilestone;
@@ -1,10 +1,10 @@
import { CustomFields, OntimeBlock, OntimeDelay, OntimeEvent, SupportedEntry, TimeStrategy } from 'ontime-types';
import { CustomFields, OntimeGroup, OntimeDelay, OntimeEvent, SupportedEntry, TimeStrategy } from 'ontime-types';
import { dayInMs, MILLIS_PER_HOUR, MILLIS_PER_MINUTE } from 'ontime-utils';
import {
makeOntimeEvent,
makeRundown,
makeOntimeBlock,
makeOntimeGroup,
makeOntimeDelay,
makeCustomField,
} from '../__mocks__/rundown.mocks.js';
@@ -112,7 +112,7 @@ describe('processRundown()', () => {
order: ['1', '2', '3'],
entries: {
'1': makeOntimeEvent({ id: '1' }),
'2': makeOntimeBlock({ id: '2' }),
'2': makeOntimeGroup({ id: '2' }),
'3': makeOntimeDelay({ id: '3' }),
},
});
@@ -121,7 +121,7 @@ describe('processRundown()', () => {
expect(initResult.order.length).toBe(3);
expect(initResult.order).toStrictEqual(['1', '2', '3']);
expect(initResult.entries['1'].type).toBe(SupportedEntry.Event);
expect(initResult.entries['2'].type).toBe(SupportedEntry.Block);
expect(initResult.entries['2'].type).toBe(SupportedEntry.Group);
expect(initResult.entries['3'].type).toBe(SupportedEntry.Delay);
});
@@ -142,14 +142,14 @@ describe('processRundown()', () => {
it('accounts for gaps in rundown when calculating delays', () => {
const rundown = makeRundown({
order: ['1', 'delay', '2', 'block', '3', 'another-block', '4'],
order: ['1', 'delay', '2', 'group', '3', 'another-group', '4'],
entries: {
'1': makeOntimeEvent({ id: '1', timeStart: 100, timeEnd: 200, duration: 100 }),
delay: makeOntimeDelay({ id: 'delay', duration: 200 }),
'2': makeOntimeEvent({ id: '2', timeStart: 200, timeEnd: 300, duration: 100 }),
block: makeOntimeBlock({ id: 'block', title: 'break' }),
group: makeOntimeGroup({ id: 'group', title: 'break' }),
'3': makeOntimeEvent({ id: '3', timeStart: 400, timeEnd: 500, duration: 100 }),
'another-block': makeOntimeBlock({ id: 'another-block', title: 'another-break' }),
'another-group': makeOntimeGroup({ id: 'another-group', title: 'another-break' }),
'4': makeOntimeEvent({ id: '4', timeStart: 600, timeEnd: 700, duration: 100 }),
},
});
@@ -297,14 +297,14 @@ describe('processRundown()', () => {
it('handles negative delays', () => {
const rundown = makeRundown({
order: ['1', 'delay', '2', 'block', '3', 'another-block', '4'],
order: ['1', 'delay', '2', 'group', '3', 'another-group', '4'],
entries: {
'1': makeOntimeEvent({ id: '1', timeStart: 100, timeEnd: 200, duration: 100 }),
delay: makeOntimeDelay({ id: 'delay', duration: -200 }),
'2': makeOntimeEvent({ id: '2', timeStart: 200, timeEnd: 300, duration: 100 }),
block: makeOntimeBlock({ id: 'block', title: 'break' }),
group: makeOntimeGroup({ id: 'group', title: 'break' }),
'3': makeOntimeEvent({ id: '3', timeStart: 400, timeEnd: 500, duration: 100 }),
'another-block': makeOntimeBlock({ id: 'another-block', title: 'another-break' }),
'another-group': makeOntimeGroup({ id: 'another-group', title: 'another-break' }),
'4': makeOntimeEvent({ id: '4', timeStart: 600, timeEnd: 700, duration: 100 }),
},
});
@@ -321,7 +321,7 @@ describe('processRundown()', () => {
it('links times across events', () => {
const rundown = makeRundown({
order: ['1', '2', 'block', 'delay', '3'],
order: ['1', '2', 'group', 'delay', '3'],
entries: {
'1': makeOntimeEvent({
id: '1',
@@ -338,7 +338,7 @@ describe('processRundown()', () => {
linkStart: true,
timeStrategy: TimeStrategy.LockEnd,
}),
block: makeOntimeBlock({ id: 'block' }),
group: makeOntimeGroup({ id: 'group' }),
delay: makeOntimeDelay({ id: 'delay' }),
'3': makeOntimeEvent({
id: '3',
@@ -570,7 +570,7 @@ describe('processRundown()', () => {
const rundown = makeRundown({
order: ['1'],
entries: {
'1': makeOntimeBlock({ id: '1', entries: ['100', '200', '300'] }),
'1': makeOntimeGroup({ id: '1', entries: ['100', '200', '300'] }),
'100': makeOntimeEvent({ id: '100', timeStart: 100, timeEnd: 200, duration: 100, linkStart: false }),
'200': makeOntimeEvent({ id: '200', timeStart: 200, timeEnd: 300, duration: 100 }),
'300': makeOntimeEvent({ id: '300', timeStart: 300, timeEnd: 400, duration: 100 }),
@@ -583,7 +583,7 @@ describe('processRundown()', () => {
expect(generatedRundown.totalDelay).toBe(0);
expect(generatedRundown.entries).toMatchObject({
'1': {
type: SupportedEntry.Block,
type: SupportedEntry.Group,
entries: ['100', '200', '300'],
timeStart: 100,
timeEnd: 400,
@@ -601,15 +601,15 @@ describe('processRundown()', () => {
order: ['0', '1', '2', '3'],
entries: {
'0': makeOntimeEvent({ id: '0', timeStart: 0, timeEnd: 10, duration: 10, linkStart: false }),
'1': makeOntimeBlock({ id: '1', entries: ['101', '102', '103'] }),
'1': makeOntimeGroup({ id: '1', entries: ['101', '102', '103'] }),
'101': makeOntimeEvent({ id: '101', timeStart: 100, timeEnd: 200, duration: 100, linkStart: false }),
'102': makeOntimeEvent({ id: '102', timeStart: 200, timeEnd: 300, duration: 100, linkStart: true }),
'103': makeOntimeEvent({ id: '103', timeStart: 300, timeEnd: 400, duration: 100, linkStart: true }),
'2': makeOntimeBlock({ id: '2', entries: ['201', '202', '203'] }),
'2': makeOntimeGroup({ id: '2', entries: ['201', '202', '203'] }),
'201': makeOntimeEvent({ id: '201', timeStart: 500, timeEnd: 600, duration: 100, linkStart: false }),
'202': makeOntimeEvent({ id: '202', timeStart: 600, timeEnd: 700, duration: 100, linkStart: true }),
'203': makeOntimeEvent({ id: '203', timeStart: 700, timeEnd: 800, duration: 100, linkStart: true }),
'3': makeOntimeBlock({ id: '3', entries: ['301', '302', '303'] }),
'3': makeOntimeGroup({ id: '3', entries: ['301', '302', '303'] }),
'301': makeOntimeEvent({ id: '301', timeStart: 900, timeEnd: 1000, duration: 100, linkStart: false }),
'302': makeOntimeEvent({ id: '302', timeStart: 1000, timeEnd: 1100, duration: 100, linkStart: true }),
'303': makeOntimeEvent({ id: '303', timeStart: 1100, timeEnd: 1200, duration: 100, linkStart: true }),
@@ -623,7 +623,7 @@ describe('processRundown()', () => {
expect(generatedRundown.entries).toMatchObject({
'0': { type: SupportedEntry.Event, parent: null },
'1': {
type: SupportedEntry.Block,
type: SupportedEntry.Group,
entries: ['101', '102', '103'],
timeStart: 100,
timeEnd: 400,
@@ -634,7 +634,7 @@ describe('processRundown()', () => {
'102': { parent: '1' },
'103': { parent: '1' },
'2': {
type: SupportedEntry.Block,
type: SupportedEntry.Group,
entries: ['201', '202', '203'],
timeStart: 500,
timeEnd: 800,
@@ -645,7 +645,7 @@ describe('processRundown()', () => {
'202': { id: '202', timeStart: 600, timeEnd: 700, duration: 100 },
'203': { id: '203', timeStart: 700, timeEnd: 800, duration: 100 },
'3': {
type: SupportedEntry.Block,
type: SupportedEntry.Group,
entries: ['301', '302', '303'],
timeStart: 900,
timeEnd: 1200,
@@ -688,36 +688,36 @@ describe('rundownMutation.add()', () => {
expect(rundown.entries['mock']).toMatchObject(mockEvent);
});
test('adds an event at the top of the block if no after is given', () => {
test('adds an event at the top of the group if no after is given', () => {
const mockEvent = makeOntimeEvent({ id: 'mock', cue: 'mock' });
const rundown = makeRundown({
flatOrder: ['1', '1a'],
order: ['1'],
entries: {
'1': makeOntimeBlock({ id: '1' }),
'1': makeOntimeGroup({ id: '1' }),
'1a': makeOntimeEvent({ id: '1a', parent: '1' }),
},
});
rundownMutation.add(rundown, mockEvent, null, rundown.entries['1'] as OntimeBlock);
rundownMutation.add(rundown, mockEvent, null, rundown.entries['1'] as OntimeGroup);
expect(rundown.order).toStrictEqual(['1']);
expect(rundown.flatOrder).toStrictEqual(['1', 'mock', '1a']);
expect(rundown.entries['mock']).toMatchObject(mockEvent);
});
test('adds an event at the a given location inside a block', () => {
test('adds an event at the a given location inside a group', () => {
const mockEvent = makeOntimeEvent({ id: 'mock', cue: 'mock' });
const rundown = makeRundown({
flatOrder: ['1', '1a'],
order: ['1'],
entries: {
'1': makeOntimeBlock({ id: '1' }),
'1': makeOntimeGroup({ id: '1' }),
'1a': makeOntimeEvent({ id: '1a', parent: '1' }),
},
});
rundownMutation.add(rundown, mockEvent, '1a', rundown.entries['1'] as OntimeBlock);
rundownMutation.add(rundown, mockEvent, '1a', rundown.entries['1'] as OntimeGroup);
expect(rundown.order).toStrictEqual(['1']);
expect(rundown.flatOrder).toStrictEqual(['1', '1a', 'mock']);
@@ -785,11 +785,11 @@ describe('rundownMutation.remove()', () => {
expect(rundown.entries['3']).not.toBeUndefined();
});
it('deletes a block and its children', () => {
it('deletes a group and its children', () => {
const rundown = makeRundown({
order: ['1', '4'],
entries: {
'1': makeOntimeBlock({ id: '1', entries: ['2', '3'] }),
'1': makeOntimeGroup({ id: '1', entries: ['2', '3'] }),
'2': makeOntimeEvent({ id: '2', parent: '1' }),
'3': makeOntimeDelay({ id: '3', parent: '1' }),
'4': makeOntimeEvent({ id: '4', parent: null }),
@@ -811,7 +811,7 @@ describe('rundownMutation.remove()', () => {
const rundown = makeRundown({
order: ['1', '4'],
entries: {
'1': makeOntimeBlock({ id: '1', entries: ['2', '3'] }),
'1': makeOntimeGroup({ id: '1', entries: ['2', '3'] }),
'2': makeOntimeEvent({ id: '2', parent: '1' }),
'3': makeOntimeDelay({ id: '3', parent: '1' }),
'4': makeOntimeEvent({ id: '4', parent: null }),
@@ -849,11 +849,11 @@ describe('rundownMutation.removeAll()', () => {
});
describe('rundownMutation.reorder()', () => {
it('moves an event into a block', () => {
it('moves an event into a group', () => {
const rundown = makeRundown({
order: ['1', '2', '3'],
entries: {
'1': makeOntimeBlock({ id: '1', entries: [] }),
'1': makeOntimeGroup({ id: '1', entries: [] }),
'2': makeOntimeEvent({ id: '2', parent: null }),
'3': makeOntimeEvent({ id: '3', parent: null }),
},
@@ -870,12 +870,12 @@ describe('rundownMutation.reorder()', () => {
});
});
it('adds an event into a block', () => {
it('adds an event into a group', () => {
const rundown = makeRundown({
order: ['1', '2'],
flatOrder: ['1', '11', '2'],
entries: {
'1': makeOntimeBlock({ id: '1', entries: ['11'] }),
'1': makeOntimeGroup({ id: '1', entries: ['11'] }),
'11': makeOntimeEvent({ id: '11', parent: '1' }),
'2': makeOntimeEvent({ id: '2', parent: null }),
},
@@ -926,12 +926,12 @@ describe('rundownMutation.reorder()', () => {
expect(rundown.order).toStrictEqual(['3', '1', '2']);
});
it('moves an event out and before a block', () => {
it('moves an event out and before a group', () => {
const rundown = makeRundown({
order: ['1', '2'],
flatOrder: ['1', '11', '2'],
entries: {
'1': makeOntimeBlock({ id: '1', entries: ['11'] }),
'1': makeOntimeGroup({ id: '1', entries: ['11'] }),
'11': makeOntimeEvent({ id: '11', parent: '1' }),
'2': makeOntimeEvent({ id: '2', parent: null }),
},
@@ -951,22 +951,22 @@ describe('rundownMutation.reorder()', () => {
});
});
it('moves an event out and after a block', () => {
it('moves an event out and after a group', () => {
const rundown = makeRundown({
order: ['1', 'block', '2'],
flatOrder: ['1', 'block', '11', '2'],
order: ['1', 'group', '2'],
flatOrder: ['1', 'group', '11', '2'],
entries: {
'1': makeOntimeEvent({ id: '1', parent: null }),
block: makeOntimeBlock({ id: 'block', entries: ['11'] }),
'11': makeOntimeEvent({ id: '11', parent: 'block' }),
group: makeOntimeGroup({ id: 'group', entries: ['11'] }),
'11': makeOntimeEvent({ id: '11', parent: 'group' }),
'2': makeOntimeEvent({ id: '2', parent: null }),
},
});
rundownMutation.reorder(rundown, rundown.entries['11'], rundown.entries['block'], 'after');
rundownMutation.reorder(rundown, rundown.entries['11'], rundown.entries['group'], 'after');
expect(rundown.order).toStrictEqual(['1', 'block', '11', '2']);
expect(rundown.entries['block']).toMatchObject({
expect(rundown.order).toStrictEqual(['1', 'group', '11', '2']);
expect(rundown.entries['group']).toMatchObject({
entries: [],
});
expect(rundown.entries['11']).toMatchObject({
@@ -977,14 +977,14 @@ describe('rundownMutation.reorder()', () => {
});
});
it('moves an event between blocks', () => {
it('moves an event between groups', () => {
const rundown = makeRundown({
order: ['1', '2'],
flatOrder: ['1', '11', '2', '22'],
entries: {
'1': makeOntimeBlock({ id: '1', entries: ['11'] }),
'1': makeOntimeGroup({ id: '1', entries: ['11'] }),
'11': makeOntimeEvent({ id: '11', parent: '1' }),
'2': makeOntimeBlock({ id: '2', entries: ['22'] }),
'2': makeOntimeGroup({ id: '2', entries: ['22'] }),
'22': makeOntimeEvent({ id: '22', parent: '2' }),
},
});
@@ -1003,13 +1003,13 @@ describe('rundownMutation.reorder()', () => {
});
});
it('moves an event into an empty block', () => {
it('moves an event into an empty group', () => {
const rundown = makeRundown({
order: ['1', '2'],
flatOrder: ['1', '2', '22'],
entries: {
'1': makeOntimeBlock({ id: '1', entries: [] }),
'2': makeOntimeBlock({ id: '2', entries: ['22'] }),
'1': makeOntimeGroup({ id: '1', entries: [] }),
'2': makeOntimeGroup({ id: '2', entries: ['22'] }),
'22': makeOntimeEvent({ id: '22', parent: '2' }),
},
});
@@ -1028,14 +1028,14 @@ describe('rundownMutation.reorder()', () => {
});
});
it('moves an event out of a block (up)', () => {
it('moves an event out of a group (up)', () => {
const rundown = makeRundown({
order: ['1', '2'],
flatOrder: ['1', '11', '2', '22'],
entries: {
'1': makeOntimeBlock({ id: '1', entries: ['11'] }),
'1': makeOntimeGroup({ id: '1', entries: ['11'] }),
'11': makeOntimeEvent({ id: '11', parent: '1' }),
'2': makeOntimeBlock({ id: '2', entries: ['22'] }),
'2': makeOntimeGroup({ id: '2', entries: ['22'] }),
'22': makeOntimeEvent({ id: '22', parent: '2' }),
},
});
@@ -1059,14 +1059,14 @@ describe('rundownMutation.reorder()', () => {
});
});
it('moves an event out of a block (down)', () => {
it('moves an event out of a group (down)', () => {
const rundown = makeRundown({
order: ['1', '2'],
flatOrder: ['1', '11', '2', '22'],
entries: {
'1': makeOntimeBlock({ id: '1', entries: ['11'] }),
'1': makeOntimeGroup({ id: '1', entries: ['11'] }),
'11': makeOntimeEvent({ id: '11', parent: '1' }),
'2': makeOntimeBlock({ id: '2', entries: ['22'] }),
'2': makeOntimeGroup({ id: '2', entries: ['22'] }),
'22': makeOntimeEvent({ id: '22', parent: '2' }),
},
});
@@ -1088,14 +1088,14 @@ describe('rundownMutation.reorder()', () => {
});
});
it('moves a block (up)', () => {
it('moves a group (up)', () => {
const rundown = makeRundown({
order: ['1', '2'],
flatOrder: ['1', '11', '2', '22'],
entries: {
'1': makeOntimeBlock({ id: '1', entries: ['11'] }),
'1': makeOntimeGroup({ id: '1', entries: ['11'] }),
'11': makeOntimeEvent({ id: '11', parent: '1' }),
'2': makeOntimeBlock({ id: '2', entries: ['22'] }),
'2': makeOntimeGroup({ id: '2', entries: ['22'] }),
'22': makeOntimeEvent({ id: '22', parent: '2' }),
},
});
@@ -1105,14 +1105,14 @@ describe('rundownMutation.reorder()', () => {
expect(rundown.order).toStrictEqual(['2', '1']);
});
it('moves a block (down)', () => {
it('moves a group (down)', () => {
const rundown = makeRundown({
order: ['1', '2'],
flatOrder: ['1', '11', '2', '22'],
entries: {
'1': makeOntimeBlock({ id: '1', entries: ['11'] }),
'1': makeOntimeGroup({ id: '1', entries: ['11'] }),
'11': makeOntimeEvent({ id: '11', parent: '1' }),
'2': makeOntimeBlock({ id: '2', entries: ['22'] }),
'2': makeOntimeGroup({ id: '2', entries: ['22'] }),
'22': makeOntimeEvent({ id: '22', parent: '2' }),
},
});
@@ -1132,7 +1132,7 @@ describe('rundownMutation.applyDelay()', () => {
delay: makeOntimeDelay({ id: 'delay', duration: 10 }),
'1': makeOntimeEvent({ id: '1', timeStart: 0, timeEnd: 10, duration: 10 }),
'2': makeOntimeEvent({ id: '2', timeStart: 10, timeEnd: 20, duration: 10, linkStart: true }),
'3': makeOntimeBlock({ id: '3' }),
'3': makeOntimeGroup({ id: '3' }),
'4': makeOntimeEvent({ id: '4', timeStart: 20, timeEnd: 30, duration: 10, linkStart: false }),
'5': makeOntimeEvent({ id: '5', timeStart: 30, timeEnd: 40, duration: 10, linkStart: true }),
},
@@ -1158,7 +1158,7 @@ describe('rundownMutation.applyDelay()', () => {
delay: makeOntimeDelay({ id: 'delay', duration: -10 }),
'1': makeOntimeEvent({ id: '1', timeStart: 0, timeEnd: 10, duration: 10 }),
'2': makeOntimeEvent({ id: '2', timeStart: 10, timeEnd: 20, duration: 10, linkStart: true }),
'3': makeOntimeBlock({ id: '3' }),
'3': makeOntimeGroup({ id: '3' }),
'4': makeOntimeEvent({ id: '4', timeStart: 20, timeEnd: 30, duration: 10, linkStart: false }),
'5': makeOntimeEvent({ id: '5', timeStart: 30, timeEnd: 40, duration: 10, linkStart: true }),
},
@@ -1398,13 +1398,13 @@ describe('rundownMutation.applyDelay()', () => {
expect(testRundown.entries).toMatchObject({ '1': { id: '1', timeStart: 0, timeEnd: 100, duration: 100 } });
});
it('unlinks events to across blocks is it is the first event after the delay', () => {
it('unlinks events to across groups is it is the first event after the delay', () => {
const testRundown = makeRundown({
order: ['1', 'delay', 'block', '2'],
order: ['1', 'delay', 'group', '2'],
entries: {
'1': makeOntimeEvent({ id: '1', timeStart: 0, timeEnd: 100, duration: 100, revision: 1 }),
delay: makeOntimeDelay({ id: 'delay', duration: 50 }),
block: makeOntimeBlock({ id: 'block' }),
group: makeOntimeGroup({ id: 'group' }),
'2': makeOntimeEvent({ id: '2', timeStart: 100, timeEnd: 150, duration: 50, revision: 1, linkStart: true }),
},
});
@@ -1420,7 +1420,7 @@ describe('rundownMutation.applyDelay()', () => {
duration: 100,
revision: 1,
},
block: { id: 'block' },
group: { id: 'group' },
'2': {
id: '2',
timeStart: 150,
@@ -1432,13 +1432,13 @@ describe('rundownMutation.applyDelay()', () => {
});
});
it('applies a delay from inside a block', () => {
it('applies a delay from inside a group', () => {
const testRundown = makeRundown({
order: ['1', 'block', '2', '3'],
order: ['1', 'group', '2', '3'],
entries: {
'1': makeOntimeEvent({ id: '1', timeStart: 0, timeEnd: 100, duration: 100 }),
block: makeOntimeBlock({ id: 'block', entries: ['delay'] }),
delay: makeOntimeDelay({ id: 'delay', duration: 100, parent: 'block' }),
group: makeOntimeGroup({ id: 'group', entries: ['delay'] }),
delay: makeOntimeDelay({ id: 'delay', duration: 100, parent: 'group' }),
'2': makeOntimeEvent({ id: '2', timeStart: 100, timeEnd: 200, duration: 100, linkStart: true }),
'3': makeOntimeEvent({ id: '3', timeStart: 200, timeEnd: 300, duration: 100, linkStart: true }),
},
@@ -1476,18 +1476,18 @@ describe('rundownMutation.applyDelay()', () => {
it('applies a delay from across nested orders', () => {
const testRundown = makeRundown({
order: ['1', 'delay', 'block', '2', '3'],
order: ['1', 'delay', 'group', '2', '3'],
entries: {
'1': makeOntimeEvent({ id: '1', timeStart: 0, timeEnd: 100, duration: 100 }),
delay: makeOntimeDelay({ id: 'delay', duration: 100 }),
block: makeOntimeBlock({ id: 'block', entries: ['block-1'] }),
'block-1': makeOntimeEvent({
id: 'block-1',
group: makeOntimeGroup({ id: 'group', entries: ['group-1'] }),
'group-1': makeOntimeEvent({
id: 'group-1',
timeStart: 100,
timeEnd: 200,
duration: 100,
linkStart: true,
parent: 'block',
parent: 'group',
}),
'2': makeOntimeEvent({ id: '2', timeStart: 200, timeEnd: 300, duration: 100, linkStart: true }),
'3': makeOntimeEvent({ id: '3', timeStart: 300, timeEnd: 400, duration: 100, linkStart: true }),
@@ -1504,7 +1504,7 @@ describe('rundownMutation.applyDelay()', () => {
duration: 100,
revision: 1,
},
'block-1': {
'group-1': {
timeStart: 200,
timeEnd: 300,
duration: 100,
@@ -1579,11 +1579,11 @@ describe('rundownMutation.clone()', () => {
});
});
it('clones an event inside a block and adds it to the rundown', () => {
it('clones an event inside a group and adds it to the rundown', () => {
const testRundown = makeRundown({
order: ['1'],
entries: {
'1': makeOntimeBlock({ id: '1', entries: ['1a'] }),
'1': makeOntimeGroup({ id: '1', entries: ['1a'] }),
'1a': makeOntimeEvent({ id: '1a', cue: 'nested', parent: '1' }),
},
});
@@ -1599,11 +1599,11 @@ describe('rundownMutation.clone()', () => {
});
});
it('clones a block and its nested elements', () => {
it('clones a group and its nested elements', () => {
const testRundown = makeRundown({
order: ['1'],
entries: {
'1': makeOntimeBlock({ id: '1', title: 'top', entries: ['1a'] }),
'1': makeOntimeGroup({ id: '1', title: 'top', entries: ['1a'] }),
'1a': makeOntimeEvent({ id: '1a', cue: 'nested', parent: '1' }),
},
});
@@ -1612,10 +1612,10 @@ describe('rundownMutation.clone()', () => {
expect(testRundown.order).toStrictEqual(['1', newEntry.id]);
expect(testRundown.entries[newEntry.id]).toMatchObject({
type: SupportedEntry.Block,
type: SupportedEntry.Group,
entries: [expect.any(String)],
});
expect((testRundown.entries[newEntry.id] as OntimeBlock).entries[0]).not.toBe('1a');
expect((testRundown.entries[newEntry.id] as OntimeGroup).entries[0]).not.toBe('1a');
});
});
@@ -1632,34 +1632,34 @@ describe('rundownMutation.group()', () => {
rundownMutation.group(rundown, ['1', '2']);
const blockId = rundown.order[0];
expect(blockId).toStrictEqual(expect.any(String));
const groupId = rundown.order[0];
expect(groupId).toStrictEqual(expect.any(String));
expect(rundown.order).toStrictEqual([expect.any(String), '3']);
expect(rundown.entries).toMatchObject({
[blockId]: {
type: SupportedEntry.Block,
[groupId]: {
type: SupportedEntry.Group,
entries: ['1', '2'],
},
'1': { id: '1', type: SupportedEntry.Event, parent: blockId },
'2': { id: '2', type: SupportedEntry.Event, parent: blockId },
'1': { id: '1', type: SupportedEntry.Event, parent: groupId },
'2': { id: '2', type: SupportedEntry.Event, parent: groupId },
'3': { id: '3', type: SupportedEntry.Event, parent: null },
});
});
});
describe('rundownMutation.ungroup()', () => {
it('should correctly dissolve a block into its events', () => {
it('should correctly dissolve a group into its events', () => {
const testRundown = makeRundown({
order: ['1', '2'],
entries: {
'1': makeOntimeEvent({ id: '1', cue: 'data1', parent: null }),
'2': makeOntimeBlock({ id: '2', entries: ['21', '22'] }),
'2': makeOntimeGroup({ id: '2', entries: ['21', '22'] }),
'21': makeOntimeEvent({ id: '21', cue: 'data21', parent: '2' }),
'22': makeOntimeEvent({ id: '22', cue: 'data22', parent: '2' }),
},
});
rundownMutation.ungroup(testRundown, testRundown.entries['2'] as OntimeBlock);
rundownMutation.ungroup(testRundown, testRundown.entries['2'] as OntimeGroup);
expect(testRundown.order).toStrictEqual(['1', '21', '22']);
expect(testRundown.entries['2']).toBeUndefined();
@@ -1,7 +1,7 @@
import { SupportedEntry, OntimeEvent, OntimeBlock, Rundown, CustomFields } from 'ontime-types';
import { SupportedEntry, OntimeEvent, OntimeGroup, Rundown, CustomFields } from 'ontime-types';
import { defaultRundown } from '../../../models/dataModel.js';
import { makeOntimeBlock, makeOntimeEvent, makeOntimeMilestone } from '../__mocks__/rundown.mocks.js';
import { makeOntimeGroup, makeOntimeEvent, makeOntimeMilestone } from '../__mocks__/rundown.mocks.js';
import { parseRundowns, parseRundown, handleCustomField, addToCustomAssignment } from '../rundown.parser.js';
@@ -47,7 +47,7 @@ describe('parseRundown()', () => {
flatOrder: ['1', '2', '3', '4'],
entries: {
'1': { id: '1', type: SupportedEntry.Event, title: 'test', skip: false } as OntimeEvent, // OK
'2': { id: '1', type: SupportedEntry.Block, title: 'test 2' } as OntimeBlock, // duplicate ID
'2': { id: '1', type: SupportedEntry.Group, title: 'test 2' } as OntimeGroup, // duplicate ID
'3': {} as OntimeEvent, // no data
'4': { id: '4', title: 'test 2', skip: false } as OntimeEvent, // no type
},
@@ -221,7 +221,7 @@ describe('parseRundown()', () => {
flatOrder: ['1', '2', '21'],
entries: {
'1': makeOntimeEvent({ id: '1', custom: { lighting: 'yes' } }),
'2': makeOntimeBlock({ id: '2', entries: ['21'], custom: { lighting: '' } }),
'2': makeOntimeGroup({ id: '2', entries: ['21'], custom: { lighting: '' } }),
'21': makeOntimeEvent({ id: '21', custom: { lighting: '' } }),
},
revision: 1,
@@ -237,7 +237,7 @@ describe('parseRundown()', () => {
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['2'] as OntimeGroup).custom).not.toHaveProperty('lighting');
expect((parsedRundown.entries['21'] as OntimeEvent).custom).not.toHaveProperty('lighting');
});
@@ -245,41 +245,41 @@ describe('parseRundown()', () => {
const rundown = {
id: 'test',
title: '',
order: ['block'],
flatOrder: ['block'],
order: ['group'],
flatOrder: ['group'],
isNextDay: false,
entries: {
block: makeOntimeBlock({
id: 'block',
title: 'block-title',
note: 'block-note',
group: makeOntimeGroup({
id: 'group',
title: 'group-title',
note: 'group-note',
colour: 'red',
entries: ['1', '2', '3'],
}),
'1': makeOntimeEvent({ id: '1', parent: 'block' }),
'2': makeOntimeMilestone({ id: '2', parent: 'block' }),
'1': makeOntimeEvent({ id: '1', parent: 'group' }),
'2': makeOntimeMilestone({ id: '2', parent: 'group' }),
},
revision: 1,
} as Rundown;
const parsedRundown = parseRundown(rundown, {});
expect(parsedRundown.order).toStrictEqual(['block']);
expect(parsedRundown.flatOrder).toStrictEqual(['block', '1', '2']);
expect(parsedRundown.order).toStrictEqual(['group']);
expect(parsedRundown.flatOrder).toStrictEqual(['group', '1', '2']);
expect(parsedRundown.entries).toMatchObject({
block: { id: 'block', type: SupportedEntry.Block, entries: ['1', '2'] },
group: { id: 'group', type: SupportedEntry.Group, entries: ['1', '2'] },
'1': { id: '1', type: SupportedEntry.Event },
'2': { id: '2', type: SupportedEntry.Milestone },
});
});
it('parses events nested in blocks', () => {
it('parses events nested in groups', () => {
const rundown = {
id: 'test',
title: '',
order: ['block'],
flatOrder: ['block'],
order: ['group'],
flatOrder: ['group'],
entries: {
block: makeOntimeBlock({ id: 'block', entries: ['1', '2'] }),
group: makeOntimeGroup({ id: 'group', entries: ['1', '2'] }),
'1': makeOntimeEvent({ id: '1' }),
'2': makeOntimeEvent({ id: '2' }),
},
@@ -288,7 +288,7 @@ describe('parseRundown()', () => {
const parsedRundown = parseRundown(rundown, {});
expect(parsedRundown.order.length).toEqual(1);
expect(parsedRundown.entries.block).toMatchObject({ entries: ['1', '2'] });
expect(parsedRundown.entries.group).toMatchObject({ entries: ['1', '2'] });
expect(Object.keys(parsedRundown.entries).length).toEqual(3);
});
});
@@ -1,4 +1,4 @@
import { TimeStrategy, EndAction, TimerType, OntimeEvent, OntimeBlock } from 'ontime-types';
import { TimeStrategy, EndAction, TimerType, OntimeEvent, OntimeGroup } from 'ontime-types';
import { MILLIS_PER_HOUR } from 'ontime-utils';
import { assertType } from 'vitest';
@@ -11,7 +11,7 @@ import {
getInsertAfterId,
hasChanges,
} from '../rundown.utils.js';
import { makeOntimeBlock, makeOntimeEvent, makeRundown } from '../__mocks__/rundown.mocks.js';
import { makeOntimeGroup, makeOntimeEvent, makeRundown } from '../__mocks__/rundown.mocks.js';
describe('test event validator', () => {
it('validates a good object', () => {
@@ -227,13 +227,13 @@ describe('getInsertAfterId()', () => {
entries: {
'1': makeOntimeEvent({ id: '1', parent: null }),
'2': makeOntimeEvent({ id: '2', parent: null }),
block: makeOntimeBlock({ id: 'block', entries: ['31', '32'] }),
'31': makeOntimeEvent({ id: '31', parent: 'block' }),
'32': makeOntimeEvent({ id: '32', parent: 'block' }),
'4': makeOntimeEvent({ id: '31', parent: null }),
group: makeOntimeGroup({ id: 'group', entries: ['31', '32'] }),
'31': makeOntimeEvent({ id: '31', parent: 'group' }),
'32': makeOntimeEvent({ id: '32', parent: 'group' }),
'4': makeOntimeEvent({ id: '4', parent: null }),
},
order: ['1', '2', 'block', '4'],
flatOrder: ['1', '2', 'block', '31', '32', '4'],
order: ['1', '2', 'group', '4'],
flatOrder: ['1', '2', 'group', '31', '32', '4'],
});
it('returns afterId if provided', () => {
@@ -251,12 +251,12 @@ describe('getInsertAfterId()', () => {
it('returns the previous id of an entry in the rundown', () => {
expect(getInsertAfterId(rundown, null, undefined, '2')).toBe('1');
expect(getInsertAfterId(rundown, null, undefined, '4')).toBe('block');
expect(getInsertAfterId(rundown, null, undefined, 'block')).toBe('2');
expect(getInsertAfterId(rundown, null, undefined, '4')).toBe('group');
expect(getInsertAfterId(rundown, null, undefined, 'group')).toBe('2');
});
it('returns the previous id of an event in a block', () => {
expect(getInsertAfterId(rundown, rundown.entries.block as OntimeBlock, undefined, '31')).toBeNull();
expect(getInsertAfterId(rundown, rundown.entries.block as OntimeBlock, undefined, '32')).toBe('31');
it('returns the previous id of an event in a group', () => {
expect(getInsertAfterId(rundown, rundown.entries.group as OntimeGroup, undefined, '31')).toBeNull();
expect(getInsertAfterId(rundown, rundown.entries.group as OntimeGroup, undefined, '32')).toBe('31');
});
});
+73 -73
View File
@@ -15,10 +15,10 @@ import {
CustomFieldKey,
CustomFields,
EntryId,
isOntimeBlock,
isOntimeGroup,
isOntimeEvent,
isPlayableEvent,
OntimeBlock,
OntimeGroup,
OntimeDelay,
OntimeEntry,
OntimeEvent,
@@ -32,9 +32,9 @@ import { getDataProvider } from '../../classes/data-provider/DataProvider.js';
import type { AssignedMap, CustomFieldsMetadata, RundownMetadata } from './rundown.types.js';
import {
applyPatchToEntry,
cloneBlock,
cloneGroup,
cloneEntry,
createBlock,
createGroup,
deleteById,
doesInvalidateMetadata,
getUniqueId,
@@ -176,14 +176,14 @@ export function createTransaction(options: TransactionOptions): Transaction {
/**
* Add entry to rundown, handles the following cases:
* - 1a. add entry in block, after a given entry
* - 1b. add entry in block, at the beginning
* - 1a. add entry in group, after a given entry
* - 1b. add entry in group, at the beginning
* - 2a. add entry to the rundown, after a given entry
* - 2b. add entry to the rundown, at the beginning
*/
function add(rundown: Rundown, entry: OntimeEntry, afterId: EntryId | null, parent: OntimeBlock | null): OntimeEntry {
function add(rundown: Rundown, entry: OntimeEntry, afterId: EntryId | null, parent: OntimeGroup | null): OntimeEntry {
if (parent) {
// 1. inserting an entry inside a block
// 1. inserting an entry inside a group
if (afterId) {
const atEventsIndex = parent.entries.indexOf(afterId) + 1;
const atFlatIndex = rundown.flatOrder.indexOf(afterId) + 1;
@@ -232,31 +232,31 @@ function edit(rundown: Rundown, patch: PatchWithId): { entry: OntimeEntry; didIn
/**
* Deletes an entry from the rundown
* - if the entry is an ontime block, we delete it along with its children
* - if the entry is inside a block, we delete it and remove the reference from the parent block
* - if the entry is an ontime group, we delete it along with its children
* - if the entry is inside a group, we delete it and remove the reference from the parent group
*/
function remove(rundown: Rundown, entry: OntimeEntry) {
if (isOntimeBlock(entry)) {
// for ontime blocks, we need to iterate through the children and delete them
if (isOntimeGroup(entry)) {
// for ontime groups, we need to iterate through the children and delete them
for (let i = 0; i < entry.entries.length; i++) {
const nestedEntryId = entry.entries[i];
deleteEntry(nestedEntryId);
}
} else if (entry.parent) {
// at this point, we are handling entries inside a block, so we need to remove the reference
const parentBlock = rundown.entries[entry.parent];
// at this point, we are handling entries inside a group, so we need to remove the reference
const parentGroup = rundown.entries[entry.parent];
// eslint-disable-next-line no-unused-labels -- dev code path
DEV: {
if (parentBlock && !isOntimeBlock(parentBlock)) {
consoleError(`Parent block with ID ${entry.parent} is not a valid OntimeBlock`);
if (parentGroup && !isOntimeGroup(parentGroup)) {
consoleError(`Parent group with ID ${entry.parent} is not a valid Group`);
}
}
if (parentBlock && isOntimeBlock(parentBlock)) {
if (parentGroup && isOntimeGroup(parentGroup)) {
// we call a mutation to the parent event to remove the entry from the events
const filteredEvents = deleteById(parentBlock.entries, entry.id);
edit(rundown, { id: parentBlock.id, entries: filteredEvents });
const filteredEvents = deleteById(parentGroup.entries, entry.id);
edit(rundown, { id: parentGroup.id, entries: filteredEvents });
}
}
deleteEntry(entry.id);
@@ -281,22 +281,22 @@ function removeAll(rundown: Rundown): Rundown {
/**
* Reorders an entry in the rundown
* Handle moving across order lists
* @param order - 'before' | 'after' | 'insert' - where to add the entry, insert serves to add the entry into an empty block
* @throws if we insert a block inside another
* @param order - 'before' | 'after' | 'insert' - where to add the entry, insert serves to add the entry into an empty group
* @throws if we insert a group inside another
*/
function reorder(rundown: Rundown, eventFrom: OntimeEntry, eventTo: OntimeEntry, order: 'before' | 'after' | 'insert') {
// handle moving across parents
const fromParent: EntryId | null = (eventFrom as { parent?: EntryId })?.parent ?? null;
const toParent = (() => {
if (isOntimeBlock(eventTo)) {
// Special case: if we're moving relative to our own parent block, remove from block
if (isOntimeGroup(eventTo)) {
// Special case: if we're moving relative to our own parent group, remove from group
if ('parent' in eventFrom && eventFrom.parent === eventTo.id) {
return null;
}
if (order === 'insert') {
// prevent blocks from being inserted into other blocks
if (isOntimeBlock(eventFrom)) {
throw new Error('Cannot insert a block into another block');
// prevent groups from being inserted into other groups
if (isOntimeGroup(eventFrom)) {
throw new Error('Cannot insert a group into another group');
}
return eventTo.id;
}
@@ -310,8 +310,8 @@ function reorder(rundown: Rundown, eventFrom: OntimeEntry, eventTo: OntimeEntry,
eventFrom.parent = toParent;
}
const sourceArray = fromParent === null ? rundown.order : (rundown.entries[fromParent] as OntimeBlock).entries;
const destinationArray = toParent === null ? rundown.order : (rundown.entries[toParent] as OntimeBlock).entries;
const sourceArray = fromParent === null ? rundown.order : (rundown.entries[fromParent] as OntimeGroup).entries;
const destinationArray = toParent === null ? rundown.order : (rundown.entries[toParent] as OntimeGroup).entries;
const fromIndex = sourceArray.indexOf(eventFrom.id);
const toIndex = (() => {
@@ -450,11 +450,11 @@ function swap(rundown: Rundown, eventFrom: OntimeEvent, eventTo: OntimeEvent) {
/**
* Inserts a clone of the given entry into the rundown
* Handles cloning children if the entry is a block
* Handles cloning children if the entry is a group
*/
function clone(rundown: Rundown, entry: OntimeEntry): OntimeEntry {
if (isOntimeBlock(entry)) {
const newBlock = cloneBlock(entry, getUniqueId(rundown));
if (isOntimeGroup(entry)) {
const newGroup = cloneGroup(entry, getUniqueId(rundown));
const nestedIds: EntryId[] = [];
for (let i = 0; i < entry.entries.length; i++) {
@@ -464,83 +464,83 @@ function clone(rundown: Rundown, entry: OntimeEntry): OntimeEntry {
continue;
}
// clone the event and assign it to the new block
// clone the event and assign it to the new group
const newNestedEntry = cloneEntry(nestedEntry, getUniqueId(rundown));
(newNestedEntry as OntimeEvent | OntimeDelay).parent = newBlock.id;
(newNestedEntry as OntimeEvent | OntimeDelay).parent = newGroup.id;
nestedIds.push(newNestedEntry.id);
// we immediately insert the nested entries into the rundown
rundown.entries[newNestedEntry.id] = newNestedEntry;
}
// indexes + 1 since we are inserting after the cloned block
// indexes + 1 since we are inserting after the cloned group
const atIndex = rundown.order.indexOf(entry.id) + 1;
newBlock.entries = nestedIds;
newBlock.title = `${entry.title || 'Untitled'} (copy)`;
newGroup.entries = nestedIds;
newGroup.title = `${entry.title || 'Untitled'} (copy)`;
rundown.entries[newBlock.id] = newBlock;
rundown.order = insertAtIndex(atIndex, newBlock.id, rundown.order);
rundown.entries[newGroup.id] = newGroup;
rundown.order = insertAtIndex(atIndex, newGroup.id, rundown.order);
return newBlock;
return newGroup;
} else {
const parent: OntimeBlock | null = entry.parent ? (rundown.entries[entry.parent] as OntimeBlock) : null;
const parent: OntimeGroup | null = entry.parent ? (rundown.entries[entry.parent] as OntimeGroup) : null;
return add(rundown, cloneEntry(entry, getUniqueId(rundown)), entry.id, parent);
}
}
/**
* Groups a list of entries
* It ensures that the entries get reassigned parent and the block gets a list of events
* It ensures that the entries get reassigned parent and the group gets a list of events
* The group will be created at the index of the first event in the order, not at the lowest index
* Mutates the given rundown
*/
function group(rundown: Rundown, entryIds: EntryId[]): OntimeBlock {
const newBlock = createBlock({ id: getUniqueId(rundown) });
function group(rundown: Rundown, entryIds: EntryId[]): OntimeGroup {
const newGroup = createGroup({ id: getUniqueId(rundown) });
const nestedEvents: EntryId[] = [];
let firstIndex = -1;
for (let i = 0; i < entryIds.length; i++) {
const entryId = entryIds[i];
const entry = rundown.entries[entryId];
if (!entry || isOntimeBlock(entry)) {
if (!entry || isOntimeGroup(entry)) {
// invalid operation, we skip this entry
continue;
}
// the block will be created at the first selected event position
// the group will be created at the first selected event position
// note that this is not the lowest index
if (firstIndex === -1) {
firstIndex = rundown.flatOrder.indexOf(entryId);
}
nestedEvents.push(entryId);
entry.parent = newBlock.id;
entry.parent = newGroup.id;
rundown.flatOrder = rundown.flatOrder.filter((id) => id !== entryId);
rundown.order = rundown.order.filter((id) => id !== entryId);
}
newBlock.entries = nestedEvents;
newGroup.entries = nestedEvents;
const insertIndex = Math.max(0, firstIndex);
// we have filtered the items from the order
// we will insert them now, with only the block at top level ...
rundown.order = insertAtIndex(insertIndex, newBlock.id, rundown.order);
rundown.entries[newBlock.id] = newBlock;
// we will insert them now, with only the group at top level ...
rundown.order = insertAtIndex(insertIndex, newGroup.id, rundown.order);
rundown.entries[newGroup.id] = newGroup;
return newBlock;
return newGroup;
}
/**
* Deletes a block and moves all its children to the top level order
* Deletes a group and moves all its children to the top level order
*/
function ungroup(rundown: Rundown, block: OntimeBlock) {
// get the events from the block and merge them into the order where the block was
const nestedEvents = block.entries;
const blockIndex = rundown.order.indexOf(block.id);
rundown.order.splice(blockIndex, 1, ...nestedEvents);
function ungroup(rundown: Rundown, group: OntimeGroup) {
// get the events from the group and merge them into the order where the group was
const nestedEvents = group.entries;
const groupIndex = rundown.order.indexOf(group.id);
rundown.order.splice(groupIndex, 1, ...nestedEvents);
// delete block from entries and remove its reference from the child events
delete rundown.entries[block.id];
// delete the group from entries and remove its reference from the child events
delete rundown.entries[group.id];
for (let i = 0; i < nestedEvents.length; i++) {
const eventId = nestedEvents[i];
const entry = rundown.entries[eventId];
@@ -720,16 +720,16 @@ export function processRundown(
}
const { processedEntry } = process(currentEntry, null);
// if the event is a block, we process the nested entries
// if the event is a group, we process the nested entries
// the code here is a copy of the processing of top level events
if (isOntimeBlock(processedEntry)) {
let blockStartTime = null;
let blockEndTime = null;
if (isOntimeGroup(processedEntry)) {
let groupStartTime = null;
let groupEndTime = null;
let isFirstLinked = false;
const blockEvents: EntryId[] = [];
const groupEvents: EntryId[] = [];
processedEntry.duration = 0;
// check if the block contains nested entries
// check if the group contains nested entries
for (let j = 0; j < processedEntry.entries.length; j++) {
const nestedEntryId = processedEntry.entries[j];
const nestedEntry = initialRundown.entries[nestedEntryId];
@@ -738,7 +738,7 @@ export function processRundown(
continue;
}
blockEvents.push(nestedEntry.id);
groupEvents.push(nestedEntry.id);
const { processedEntry: processedNestedEntry } = process(nestedEntry, processedEntry.id);
// we dont extract metadata of skipped events,
@@ -748,24 +748,24 @@ export function processRundown(
}
// first start is always the first event
if (blockStartTime === null) {
blockStartTime = processedNestedEntry.timeStart;
if (groupStartTime === null) {
groupStartTime = processedNestedEntry.timeStart;
isFirstLinked = Boolean(processedNestedEntry.linkStart);
}
// lastEntry is the event with the latest end time
blockEndTime = processedNestedEntry.timeEnd;
groupEndTime = processedNestedEntry.timeEnd;
if (j > 0) {
processedEntry.duration += processedNestedEntry.gap;
}
processedEntry.duration = processedEntry.duration + processedNestedEntry.duration;
}
// update block metadata
processedEntry.timeStart = blockStartTime;
processedEntry.timeEnd = blockEndTime;
// update group metadata
processedEntry.timeStart = groupStartTime;
processedEntry.timeEnd = groupEndTime;
processedEntry.isFirstLinked = isFirstLinked;
processedEntry.entries = blockEvents;
processedEntry.entries = groupEvents;
}
}
@@ -6,7 +6,7 @@ import {
OntimeEvent,
isOntimeEvent,
isOntimeDelay,
isOntimeBlock,
isOntimeGroup,
CustomFieldKey,
EntryId,
OntimeEntry,
@@ -21,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, createMilestone } from './rundown.utils.js';
import { calculateDayOffset, cleanupCustomFields, createGroup, createEvent, createMilestone } from './rundown.utils.js';
import { RundownMetadata } from './rundown.types.js';
/**
@@ -110,7 +110,11 @@ export function parseRundown(
} else if (isOntimeMilestone(event)) {
newEvent = createMilestone({ ...event, id });
cleanupCustomFields(newEvent.custom, parsedCustomFields);
} else if (isOntimeBlock(event)) {
/**
* We leave here an entry point for blocks for the alpha testers, should remove this after a while
*/
// @ts-expect-error -- we are checking a legacy type
} else if (event.type === 'block' || isOntimeGroup(event)) {
for (let i = 0; i < event.entries.length; i++) {
const nestedEventId = event.entries[i];
const nestedEvent = rundown.entries[nestedEventId];
@@ -143,7 +147,7 @@ export function parseRundown(
}
}
newEvent = createBlock({ ...structuredClone(event), id });
newEvent = createGroup({ ...structuredClone(event), id });
// ensure entries exist
if (event.entries?.length > 0) {
newEvent.entries = event.entries.filter((eventId) => Object.hasOwn(rundown.entries, eventId));
@@ -240,9 +244,9 @@ export function makeRundownMetadata(customFields: CustomFields) {
function process<T extends OntimeEntry>(
entry: T,
childOfBlock: EntryId | null,
childOfGroup: EntryId | null,
): { processedData: ProcessedRundownMetadata; processedEntry: T } {
const data = processEntry(rundownMeta, customFields, entry, childOfBlock);
const data = processEntry(rundownMeta, customFields, entry, childOfGroup);
rundownMeta = data.processedData;
return data;
}
@@ -261,7 +265,7 @@ function processEntry<T extends OntimeEntry>(
rundownMetadata: ProcessedRundownMetadata,
customFields: CustomFields,
entry: T,
childOfBlock: EntryId | null,
childOfGroup: EntryId | null,
): { processedData: ProcessedRundownMetadata; processedEntry: T } {
const processedData = { ...rundownMetadata };
const currentEntry = structuredClone(entry);
@@ -294,7 +298,7 @@ function processEntry<T extends OntimeEntry>(
currentEntry.dayOffset = processedData.totalDays;
currentEntry.delay = 0; // this means we dont calculate delays or gaps for skipped events
currentEntry.gap = 0; // this means we dont calculate delays or gaps for skipped events
currentEntry.parent = childOfBlock;
currentEntry.parent = childOfGroup;
// update rundown metadata, it only concerns playable events
if (isPlayableEvent(currentEntry)) {
@@ -353,10 +357,10 @@ function processEntry<T extends OntimeEntry>(
} else if (isOntimeDelay(currentEntry)) {
// !!! this must happen after handling the links
processedData.totalDelay += currentEntry.duration;
currentEntry.parent = childOfBlock;
currentEntry.parent = childOfGroup;
}
if (!childOfBlock) {
if (!childOfGroup) {
processedData.order.push(currentEntry.id);
}
processedData.entries[currentEntry.id] = currentEntry;
@@ -4,10 +4,10 @@ import {
CustomFields,
EntryId,
EventPostPayload,
isOntimeBlock,
isOntimeGroup,
isOntimeDelay,
isOntimeEvent,
OntimeBlock,
OntimeGroup,
OntimeEntry,
PatchWithId,
RefetchKey,
@@ -35,12 +35,12 @@ export async function addEntry(eventData: EventPostPayload): Promise<OntimeEntry
}
// the parent can be provided or inferred from position
let parent: OntimeBlock | null = null;
let parent: OntimeGroup | null = null;
if ('parent' in eventData && eventData.parent != null) {
// if the user provides a parent (inside a group), we make sure it exists and it is a group
const maybeParent = rundown.entries[eventData.parent];
if (!maybeParent || !isOntimeBlock(maybeParent)) {
if (!maybeParent || !isOntimeGroup(maybeParent)) {
throw new Error(`Invalid parent event with ID ${eventData.parent}`);
}
parent = maybeParent;
@@ -51,7 +51,7 @@ export async function addEntry(eventData: EventPostPayload): Promise<OntimeEntry
const maybeSibling = rundown.entries[referenceId];
if (maybeSibling && 'parent' in maybeSibling && maybeSibling.parent) {
const maybeParent = rundown.entries[maybeSibling.parent];
if (maybeParent && isOntimeBlock(maybeParent)) {
if (maybeParent && isOntimeGroup(maybeParent)) {
parent = maybeParent;
}
}
@@ -241,7 +241,7 @@ export async function deleteAllEntries(): Promise<Rundown> {
/**
* Moves an event to a new position in the rundown
* Handles moving across root orders (a block order and top level order)
* Handles moving across root orders (a group order and top level order)
* @throws if entryId or destinationId not found
*/
export function reorderEntry(entryId: EntryId, destinationId: EntryId, order: 'before' | 'after' | 'insert') {
@@ -356,7 +356,7 @@ export async function cloneEntry(entryId: EntryId): Promise<Rundown> {
updateRuntimeOnChange(rundownMetadata);
// notify timer and external services of change
if (isOntimeBlock(newEntry)) {
if (isOntimeGroup(newEntry)) {
notifyChanges(rundownMetadata, revision, { timer: newEntry.entries, external: true });
} else if (isOntimeEvent(newEntry)) {
notifyChanges(rundownMetadata, revision, { timer: [newEntry.id], external: true });
@@ -370,7 +370,7 @@ export async function cloneEntry(entryId: EntryId): Promise<Rundown> {
}
/**
* Groups a list of entries into a new block
* Groups a list of entries into a new group
*/
export async function groupEntries(entryIds: EntryId[]): Promise<Rundown> {
const { rundown, commit } = createTransaction({ mutableRundown: true, mutableCustomFields: false });
@@ -391,17 +391,17 @@ export async function groupEntries(entryIds: EntryId[]): Promise<Rundown> {
}
/**
* Deletes a block and moves all its children to the top level
* Deletes a group and moves all its children to the top level
*/
export async function ungroupEntries(blockId: EntryId): Promise<Rundown> {
export async function ungroupEntries(groupId: EntryId): Promise<Rundown> {
const { rundown, commit } = createTransaction({ mutableRundown: true, mutableCustomFields: false });
const block = rundown.entries[blockId];
if (!block || !isOntimeBlock(block)) {
throw new Error(`Block with ID ${blockId} not found or is not a block`);
const group = rundown.entries[groupId];
if (!group || !isOntimeGroup(group)) {
throw new Error(`Group with ID ${groupId} not found or is not a group`);
}
rundownMutation.ungroup(rundown, block);
rundownMutation.ungroup(rundown, group);
const { rundown: rundownResult, rundownMetadata, revision } = commit();
// schedule the side effects
@@ -2,12 +2,12 @@ import {
CustomFields,
EntryCustomFields,
EntryId,
isOntimeBlock,
isOntimeGroup,
isOntimeDelay,
isOntimeEvent,
isOntimeMilestone,
OntimeBaseEvent,
OntimeBlock,
OntimeGroup,
OntimeDelay,
OntimeEntry,
OntimeEvent,
@@ -27,7 +27,7 @@ import {
import {
event as eventDef,
block as blockDef,
group as groupDef,
delay as delayDef,
milestone as milestoneDef,
} from '../../models/eventsDefinition.js';
@@ -39,8 +39,8 @@ type CompleteEntry<T> =
? OntimeEvent
: T extends Partial<OntimeDelay>
? OntimeDelay
: T extends Partial<OntimeBlock>
? OntimeBlock
: T extends Partial<OntimeGroup>
? OntimeGroup
: T extends Partial<OntimeMilestone>
? OntimeMilestone
: never;
@@ -49,7 +49,7 @@ type CompleteEntry<T> =
* Generates a fully formed RundownEntry of the patch type
*/
export function generateEvent<
T extends Partial<OntimeEvent> | Partial<OntimeDelay> | Partial<OntimeBlock> | Partial<OntimeMilestone>,
T extends Partial<OntimeEvent> | Partial<OntimeDelay> | Partial<OntimeGroup> | 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>;
@@ -61,9 +61,9 @@ export function generateEvent<
return { ...delayDef, duration: eventData.duration ?? 0, id } as CompleteEntry<T>;
}
// TODO(v4): allow user to provide a larger patch of the block entry
if (isOntimeBlock(eventData)) {
return createBlock({ id, title: eventData.title ?? '' }) as CompleteEntry<T>;
// TODO(v4): allow user to provide a larger patch of the group entry
if (isOntimeGroup(eventData)) {
return createGroup({ id, title: eventData.title ?? '' }) as CompleteEntry<T>;
}
if (isOntimeMilestone(eventData)) {
@@ -115,35 +115,35 @@ export function createEventPatch(originalEvent: OntimeEvent, patchEvent: Partial
};
}
export function createBlockPatch(originalBlock: OntimeBlock, patchBlock: Partial<OntimeBlock>): OntimeBlock {
if (Object.keys(patchBlock).length === 0) {
return originalBlock;
export function createGroupPatch(originalGroup: OntimeGroup, patchGroup: Partial<OntimeGroup>): OntimeGroup {
if (Object.keys(patchGroup).length === 0) {
return originalGroup;
}
const maybeTargetDuration = () => {
if (typeof patchBlock.targetDuration === 'number') {
return patchBlock.targetDuration;
if (typeof patchGroup.targetDuration === 'number') {
return patchGroup.targetDuration;
}
if (patchBlock.targetDuration === null || patchBlock.targetDuration === '') {
if (patchGroup.targetDuration === null || patchGroup.targetDuration === '') {
return null;
}
return originalBlock.targetDuration;
return originalGroup.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,
id: originalGroup.id,
type: SupportedEntry.Group,
title: makeString(patchGroup.title, originalGroup.title),
note: makeString(patchGroup.note, originalGroup.note),
entries: patchGroup.entries ?? originalGroup.entries,
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 },
colour: makeString(patchGroup.colour, originalGroup.colour),
revision: originalGroup.revision,
timeStart: originalGroup.timeStart,
timeEnd: originalGroup.timeEnd,
duration: originalGroup.duration,
isFirstLinked: originalGroup.isFirstLinked,
custom: { ...originalGroup.custom, ...patchGroup.custom },
};
}
@@ -179,10 +179,10 @@ export function applyPatchToEntry(eventFromRundown: OntimeEntry, patch: Partial<
return newEvent;
}
if (isOntimeBlock(eventFromRundown)) {
const newBlock = createBlockPatch(eventFromRundown as OntimeBlock, patch as Partial<OntimeBlock>);
newBlock.revision++;
return newBlock;
if (isOntimeGroup(eventFromRundown)) {
const newGroup = createGroupPatch(eventFromRundown as OntimeGroup, patch as Partial<OntimeGroup>);
newGroup.revision++;
return newGroup;
}
if (isOntimeMilestone(eventFromRundown)) {
@@ -218,16 +218,16 @@ export const createEvent = (eventArgs: Partial<OntimeEvent>, eventIndex: number
};
/**
* Creates a new block from an optional patch
* Creates a new group from an optional patch
*/
export function createBlock(patch?: Partial<OntimeBlock>): OntimeBlock {
export function createGroup(patch?: Partial<OntimeGroup>): OntimeGroup {
if (!patch) {
return { ...blockDef, id: generateId() };
return { ...groupDef, id: generateId() };
}
return {
id: patch.id ?? generateId(),
type: SupportedEntry.Block,
type: SupportedEntry.Group,
title: patch.title ?? '',
note: patch.note ?? '',
entries: patch.entries ?? [],
@@ -385,13 +385,13 @@ export function cloneMilestone(entry: OntimeMilestone, newId: EntryId): OntimeMi
}
/**
* Gathers business logic for how to clone an OntimeBlock
* Gathers business logic for how to clone an OntimeGroup
*/
export function cloneBlock(entry: OntimeBlock, newId: EntryId): OntimeBlock {
export function cloneGroup(entry: OntimeGroup, newId: EntryId): OntimeGroup {
const newEntry = structuredClone(entry);
newEntry.id = newId;
// in blocks, we need to remove the events references
// in groups, we need to remove the events references
newEntry.entries = [];
newEntry.revision = 0;
return newEntry;
@@ -405,8 +405,8 @@ export function cloneEntry(entry: OntimeEntry, newId: EntryId): OntimeEntry {
return cloneEvent(entry, newId);
} else if (isOntimeDelay(entry)) {
return cloneDelay(entry, newId);
} else if (isOntimeBlock(entry)) {
return cloneBlock(entry, newId);
} else if (isOntimeGroup(entry)) {
return cloneGroup(entry, newId);
} else if (isOntimeMilestone(entry)) {
return cloneMilestone(entry, newId);
}
@@ -452,7 +452,7 @@ export function calculateDayOffset(
*/
export function getInsertAfterId(
rundown: Rundown,
parent: OntimeBlock | null,
parent: OntimeGroup | null,
afterId?: EntryId,
beforeId?: EntryId,
): EntryId | null {
@@ -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', 'milestone']),
body('type').isString().isIn(['event', 'delay', 'group', 'milestone']),
body('after').optional().isString(),
body('before').optional().isString(),