mirror of
https://github.com/cpvalente/ontime.git
synced 2026-09-02 12:59:09 +00:00
convert to and from database type (#1410)
* create db event type * dont use it yet * convert to and from database type * type needed in one place in client * use flatMap to filter out unknow stuff * leave revision in save file * move to utils * make test * destructur entry * simple test * throw in rundownToDatabaseRundown * clerify comment * Refactor: avoid shadowing the rundown variable in rundownCache (#1409) --------- Co-authored-by: Carlos Valente <34649812+cpvalente@users.noreply.github.com> * rename to DAO * add simple edit test * only the generate function clears the state state * strict guard for custom fields * fix small strict issues * ensure deep copy * safely set isStale * todo is done * small type error * these functions can work on DAO entries * only reat rundown as DAO on initial load and the cast immediately and esure all fields are present * eslint comment Co-authored-by: Carlos Valente <34649812+cpvalente@users.noreply.github.com> * fix spelling Co-authored-by: Carlos Valente <34649812+cpvalente@users.noreply.github.com> * fix spelling Co-authored-by: Carlos Valente <34649812+cpvalente@users.noreply.github.com> * comment not needed * setIsStale * remove DAO * clean up commits * remove unneeded async/await * revert test --------- Co-authored-by: Carlos Valente <34649812+cpvalente@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
1b62ed5f2d
commit
533f9798e8
@@ -24,6 +24,7 @@ export const cloneEvent = (event: OntimeEvent): ClonedEvent => {
|
|||||||
skip: event.skip,
|
skip: event.skip,
|
||||||
colour: event.colour,
|
colour: event.colour,
|
||||||
revision: 0,
|
revision: 0,
|
||||||
|
delay: 0,
|
||||||
timeWarning: event.timeWarning,
|
timeWarning: event.timeWarning,
|
||||||
timeDanger: event.timeDanger,
|
timeDanger: event.timeDanger,
|
||||||
custom: {},
|
custom: {},
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ function getCustomFields(): Readonly<CustomFields> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function setRundown(newData: OntimeRundown): ReadonlyPromise<OntimeRundown> {
|
async function setRundown(newData: OntimeRundown): ReadonlyPromise<OntimeRundown> {
|
||||||
db.data.rundown = [...newData];
|
db.data.rundown = newData;
|
||||||
await persist();
|
await persist();
|
||||||
return db.data.rundown;
|
return db.data.rundown;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -143,6 +143,7 @@ describe('safeMerge', () => {
|
|||||||
publicInfo: '',
|
publicInfo: '',
|
||||||
backstageUrl: '',
|
backstageUrl: '',
|
||||||
backstageInfo: '',
|
backstageInfo: '',
|
||||||
|
projectLogo: null,
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
app: 'ontime',
|
app: 'ontime',
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export const demoDb: DatabaseModel = {
|
|||||||
skip: false,
|
skip: false,
|
||||||
colour: '',
|
colour: '',
|
||||||
revision: 0,
|
revision: 0,
|
||||||
|
delay: 0,
|
||||||
timeWarning: 500000,
|
timeWarning: 500000,
|
||||||
timeDanger: 100000,
|
timeDanger: 100000,
|
||||||
custom: {
|
custom: {
|
||||||
@@ -45,6 +46,7 @@ export const demoDb: DatabaseModel = {
|
|||||||
skip: false,
|
skip: false,
|
||||||
colour: '',
|
colour: '',
|
||||||
revision: 0,
|
revision: 0,
|
||||||
|
delay: 0,
|
||||||
timeWarning: 500000,
|
timeWarning: 500000,
|
||||||
timeDanger: 100000,
|
timeDanger: 100000,
|
||||||
custom: {
|
custom: {
|
||||||
@@ -70,6 +72,7 @@ export const demoDb: DatabaseModel = {
|
|||||||
skip: false,
|
skip: false,
|
||||||
colour: '',
|
colour: '',
|
||||||
revision: 0,
|
revision: 0,
|
||||||
|
delay: 0,
|
||||||
timeWarning: 500000,
|
timeWarning: 500000,
|
||||||
timeDanger: 100000,
|
timeDanger: 100000,
|
||||||
custom: {
|
custom: {
|
||||||
@@ -95,6 +98,7 @@ export const demoDb: DatabaseModel = {
|
|||||||
skip: false,
|
skip: false,
|
||||||
colour: '',
|
colour: '',
|
||||||
revision: 0,
|
revision: 0,
|
||||||
|
delay: 0,
|
||||||
timeWarning: 500000,
|
timeWarning: 500000,
|
||||||
timeDanger: 100000,
|
timeDanger: 100000,
|
||||||
custom: {
|
custom: {
|
||||||
@@ -120,6 +124,7 @@ export const demoDb: DatabaseModel = {
|
|||||||
skip: false,
|
skip: false,
|
||||||
colour: '',
|
colour: '',
|
||||||
revision: 0,
|
revision: 0,
|
||||||
|
delay: 0,
|
||||||
timeWarning: 500000,
|
timeWarning: 500000,
|
||||||
timeDanger: 100000,
|
timeDanger: 100000,
|
||||||
custom: {
|
custom: {
|
||||||
@@ -150,6 +155,7 @@ export const demoDb: DatabaseModel = {
|
|||||||
skip: false,
|
skip: false,
|
||||||
colour: '',
|
colour: '',
|
||||||
revision: 0,
|
revision: 0,
|
||||||
|
delay: 0,
|
||||||
timeWarning: 500000,
|
timeWarning: 500000,
|
||||||
timeDanger: 100000,
|
timeDanger: 100000,
|
||||||
custom: {
|
custom: {
|
||||||
@@ -175,6 +181,7 @@ export const demoDb: DatabaseModel = {
|
|||||||
skip: false,
|
skip: false,
|
||||||
colour: '',
|
colour: '',
|
||||||
revision: 0,
|
revision: 0,
|
||||||
|
delay: 0,
|
||||||
timeWarning: 500000,
|
timeWarning: 500000,
|
||||||
timeDanger: 100000,
|
timeDanger: 100000,
|
||||||
custom: {
|
custom: {
|
||||||
@@ -200,6 +207,7 @@ export const demoDb: DatabaseModel = {
|
|||||||
skip: false,
|
skip: false,
|
||||||
colour: '',
|
colour: '',
|
||||||
revision: 0,
|
revision: 0,
|
||||||
|
delay: 0,
|
||||||
timeWarning: 500000,
|
timeWarning: 500000,
|
||||||
timeDanger: 100000,
|
timeDanger: 100000,
|
||||||
custom: {
|
custom: {
|
||||||
@@ -225,6 +233,7 @@ export const demoDb: DatabaseModel = {
|
|||||||
skip: false,
|
skip: false,
|
||||||
colour: '',
|
colour: '',
|
||||||
revision: 0,
|
revision: 0,
|
||||||
|
delay: 0,
|
||||||
timeWarning: 500000,
|
timeWarning: 500000,
|
||||||
timeDanger: 100000,
|
timeDanger: 100000,
|
||||||
custom: {
|
custom: {
|
||||||
@@ -250,6 +259,7 @@ export const demoDb: DatabaseModel = {
|
|||||||
skip: false,
|
skip: false,
|
||||||
colour: '',
|
colour: '',
|
||||||
revision: 0,
|
revision: 0,
|
||||||
|
delay: 0,
|
||||||
timeWarning: 500000,
|
timeWarning: 500000,
|
||||||
timeDanger: 100000,
|
timeDanger: 100000,
|
||||||
custom: {
|
custom: {
|
||||||
@@ -280,6 +290,7 @@ export const demoDb: DatabaseModel = {
|
|||||||
skip: false,
|
skip: false,
|
||||||
colour: '',
|
colour: '',
|
||||||
revision: 0,
|
revision: 0,
|
||||||
|
delay: 0,
|
||||||
timeWarning: 500000,
|
timeWarning: 500000,
|
||||||
timeDanger: 100000,
|
timeDanger: 100000,
|
||||||
custom: {
|
custom: {
|
||||||
@@ -305,6 +316,7 @@ export const demoDb: DatabaseModel = {
|
|||||||
skip: false,
|
skip: false,
|
||||||
colour: '',
|
colour: '',
|
||||||
revision: 0,
|
revision: 0,
|
||||||
|
delay: 0,
|
||||||
timeWarning: 500000,
|
timeWarning: 500000,
|
||||||
timeDanger: 100000,
|
timeDanger: 100000,
|
||||||
custom: {
|
custom: {
|
||||||
@@ -330,6 +342,7 @@ export const demoDb: DatabaseModel = {
|
|||||||
skip: false,
|
skip: false,
|
||||||
colour: '',
|
colour: '',
|
||||||
revision: 0,
|
revision: 0,
|
||||||
|
delay: 0,
|
||||||
timeWarning: 500000,
|
timeWarning: 500000,
|
||||||
timeDanger: 100000,
|
timeDanger: 100000,
|
||||||
custom: {
|
custom: {
|
||||||
@@ -355,6 +368,7 @@ export const demoDb: DatabaseModel = {
|
|||||||
skip: false,
|
skip: false,
|
||||||
colour: '',
|
colour: '',
|
||||||
revision: 0,
|
revision: 0,
|
||||||
|
delay: 0,
|
||||||
timeWarning: 500000,
|
timeWarning: 500000,
|
||||||
timeDanger: 100000,
|
timeDanger: 100000,
|
||||||
custom: {
|
custom: {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
TimerType,
|
TimerType,
|
||||||
} from 'ontime-types';
|
} from 'ontime-types';
|
||||||
|
|
||||||
export const event: Omit<OntimeEvent, 'id' | 'delay' | 'cue'> = {
|
export const event: Omit<OntimeEvent, 'id' | 'cue'> = {
|
||||||
title: '',
|
title: '',
|
||||||
note: '',
|
note: '',
|
||||||
endAction: EndAction.None,
|
endAction: EndAction.None,
|
||||||
@@ -24,6 +24,7 @@ export const event: Omit<OntimeEvent, 'id' | 'delay' | 'cue'> = {
|
|||||||
colour: '',
|
colour: '',
|
||||||
type: SupportedEvent.Event,
|
type: SupportedEvent.Event,
|
||||||
revision: 0,
|
revision: 0,
|
||||||
|
delay: 0,
|
||||||
timeWarning: 120000,
|
timeWarning: 120000,
|
||||||
timeDanger: 60000,
|
timeDanger: 60000,
|
||||||
custom: {},
|
custom: {},
|
||||||
|
|||||||
@@ -438,7 +438,7 @@ describe('add() mutation', () => {
|
|||||||
test('adds an event to the rundown', () => {
|
test('adds an event to the rundown', () => {
|
||||||
const mockEvent = { id: 'mock', cue: 'mock', type: SupportedEvent.Event } as OntimeEvent;
|
const mockEvent = { id: 'mock', cue: 'mock', type: SupportedEvent.Event } as OntimeEvent;
|
||||||
const testRundown: OntimeRundown = [];
|
const testRundown: OntimeRundown = [];
|
||||||
const { newRundown } = add({ atIndex: 0, event: mockEvent, persistedRundown: testRundown });
|
const { newRundown } = add({ atIndex: 0, event: mockEvent, rundown: testRundown });
|
||||||
expect(newRundown.length).toBe(1);
|
expect(newRundown.length).toBe(1);
|
||||||
expect(newRundown[0]).toMatchObject(mockEvent);
|
expect(newRundown[0]).toMatchObject(mockEvent);
|
||||||
});
|
});
|
||||||
@@ -448,7 +448,7 @@ describe('remove() mutation', () => {
|
|||||||
test('deletes an event from the rundown', () => {
|
test('deletes an event from the rundown', () => {
|
||||||
const mockEvent = { id: 'mock', cue: 'mock', type: SupportedEvent.Event } as OntimeEvent;
|
const mockEvent = { id: 'mock', cue: 'mock', type: SupportedEvent.Event } as OntimeEvent;
|
||||||
const testRundown: OntimeRundown = [mockEvent];
|
const testRundown: OntimeRundown = [mockEvent];
|
||||||
const { newRundown } = remove({ eventIds: [mockEvent.id], persistedRundown: testRundown });
|
const { newRundown } = remove({ eventIds: [mockEvent.id], rundown: testRundown });
|
||||||
expect(newRundown.length).toBe(0);
|
expect(newRundown.length).toBe(0);
|
||||||
});
|
});
|
||||||
test('deletes multiple events from the rundown', () => {
|
test('deletes multiple events from the rundown', () => {
|
||||||
@@ -460,7 +460,7 @@ describe('remove() mutation', () => {
|
|||||||
{ type: SupportedEvent.Event, id: '5' } as OntimeEvent,
|
{ type: SupportedEvent.Event, id: '5' } as OntimeEvent,
|
||||||
{ type: SupportedEvent.Event, id: '6' } as OntimeEvent,
|
{ type: SupportedEvent.Event, id: '6' } as OntimeEvent,
|
||||||
];
|
];
|
||||||
const { newRundown } = remove({ eventIds: ['1', '2', '3'], persistedRundown: testRundown });
|
const { newRundown } = remove({ eventIds: ['1', '2', '3'], rundown: testRundown });
|
||||||
expect(newRundown.length).toBe(3);
|
expect(newRundown.length).toBe(3);
|
||||||
expect(newRundown.at(0)?.id).toBe('4');
|
expect(newRundown.at(0)?.id).toBe('4');
|
||||||
});
|
});
|
||||||
@@ -474,7 +474,7 @@ describe('edit() mutation', () => {
|
|||||||
const { newRundown, newEvent } = edit({
|
const { newRundown, newEvent } = edit({
|
||||||
eventId: mockEvent.id,
|
eventId: mockEvent.id,
|
||||||
patch: mockEventPatch,
|
patch: mockEventPatch,
|
||||||
persistedRundown: testRundown,
|
rundown: testRundown,
|
||||||
});
|
});
|
||||||
expect(newRundown.length).toBe(1);
|
expect(newRundown.length).toBe(1);
|
||||||
expect(newEvent).toMatchObject({
|
expect(newEvent).toMatchObject({
|
||||||
@@ -487,7 +487,7 @@ describe('edit() mutation', () => {
|
|||||||
|
|
||||||
describe('batchEdit() mutation', () => {
|
describe('batchEdit() mutation', () => {
|
||||||
it('should correctly apply the patch to the events with the given IDs', () => {
|
it('should correctly apply the patch to the events with the given IDs', () => {
|
||||||
const persistedRundown: OntimeRundown = [
|
const testRundown: OntimeRundown = [
|
||||||
{ id: '1', type: SupportedEvent.Event, cue: 'data1' } as OntimeEvent,
|
{ id: '1', type: SupportedEvent.Event, cue: 'data1' } as OntimeEvent,
|
||||||
{ id: '2', type: SupportedEvent.Event, cue: 'data2' } as OntimeEvent,
|
{ id: '2', type: SupportedEvent.Event, cue: 'data2' } as OntimeEvent,
|
||||||
{ id: '3', type: SupportedEvent.Event, cue: 'data3' } as OntimeEvent,
|
{ id: '3', type: SupportedEvent.Event, cue: 'data3' } as OntimeEvent,
|
||||||
@@ -495,7 +495,7 @@ describe('batchEdit() mutation', () => {
|
|||||||
const eventIds = ['1', '3'];
|
const eventIds = ['1', '3'];
|
||||||
const patch = { cue: 'newData' };
|
const patch = { cue: 'newData' };
|
||||||
|
|
||||||
const { newRundown } = batchEdit({ persistedRundown, eventIds, patch });
|
const { newRundown } = batchEdit({ rundown: testRundown, eventIds, patch });
|
||||||
|
|
||||||
expect(newRundown).toMatchObject([
|
expect(newRundown).toMatchObject([
|
||||||
{ id: '1', type: SupportedEvent.Event, cue: 'newData' },
|
{ id: '1', type: SupportedEvent.Event, cue: 'newData' },
|
||||||
@@ -507,16 +507,16 @@ describe('batchEdit() mutation', () => {
|
|||||||
|
|
||||||
describe('reorder() mutation', () => {
|
describe('reorder() mutation', () => {
|
||||||
it('should correctly reorder two events', () => {
|
it('should correctly reorder two events', () => {
|
||||||
const persistedRundown: OntimeRundown = [
|
const testRundown: OntimeRundown = [
|
||||||
{ id: '1', type: SupportedEvent.Event, cue: 'data1', revision: 0 } as OntimeEvent,
|
{ id: '1', type: SupportedEvent.Event, cue: 'data1', revision: 0 } as OntimeEvent,
|
||||||
{ id: '2', type: SupportedEvent.Event, cue: 'data2', revision: 0 } as OntimeEvent,
|
{ id: '2', type: SupportedEvent.Event, cue: 'data2', revision: 0 } as OntimeEvent,
|
||||||
{ id: '3', type: SupportedEvent.Event, cue: 'data3', revision: 0 } as OntimeEvent,
|
{ id: '3', type: SupportedEvent.Event, cue: 'data3', revision: 0 } as OntimeEvent,
|
||||||
];
|
];
|
||||||
const { newRundown } = reorder({
|
const { newRundown } = reorder({
|
||||||
persistedRundown,
|
rundown: testRundown,
|
||||||
eventId: persistedRundown[0].id,
|
eventId: testRundown[0].id,
|
||||||
from: 0,
|
from: 0,
|
||||||
to: persistedRundown.length - 1,
|
to: testRundown.length - 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(newRundown).toMatchObject([
|
expect(newRundown).toMatchObject([
|
||||||
@@ -529,15 +529,15 @@ describe('reorder() mutation', () => {
|
|||||||
|
|
||||||
describe('swap() mutation', () => {
|
describe('swap() mutation', () => {
|
||||||
it('should correctly swap data between events', () => {
|
it('should correctly swap data between events', () => {
|
||||||
const persistedRundown: OntimeRundown = [
|
const testRundown: OntimeRundown = [
|
||||||
{ id: '1', type: SupportedEvent.Event, cue: 'data1', timeStart: 1, revision: 0 } as OntimeEvent,
|
{ id: '1', type: SupportedEvent.Event, cue: 'data1', timeStart: 1, revision: 0 } as OntimeEvent,
|
||||||
{ id: '2', type: SupportedEvent.Event, cue: 'data2', timeStart: 2, revision: 0 } as OntimeEvent,
|
{ id: '2', type: SupportedEvent.Event, cue: 'data2', timeStart: 2, revision: 0 } as OntimeEvent,
|
||||||
{ id: '3', type: SupportedEvent.Event, cue: 'data3', timeStart: 3, revision: 0 } as OntimeEvent,
|
{ id: '3', type: SupportedEvent.Event, cue: 'data3', timeStart: 3, revision: 0 } as OntimeEvent,
|
||||||
];
|
];
|
||||||
const { newRundown } = swap({
|
const { newRundown } = swap({
|
||||||
persistedRundown,
|
rundown: testRundown,
|
||||||
fromId: persistedRundown[0].id,
|
fromId: testRundown[0].id,
|
||||||
toId: persistedRundown[1].id,
|
toId: testRundown[1].id,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect((newRundown[0] as OntimeEvent).id).toBe('1');
|
expect((newRundown[0] as OntimeEvent).id).toBe('1');
|
||||||
@@ -576,6 +576,7 @@ describe('calculateRuntimeDelays', () => {
|
|||||||
colour: '',
|
colour: '',
|
||||||
type: SupportedEvent.Event,
|
type: SupportedEvent.Event,
|
||||||
revision: 0,
|
revision: 0,
|
||||||
|
delay: 0,
|
||||||
timeWarning: 120000,
|
timeWarning: 120000,
|
||||||
timeDanger: 60000,
|
timeDanger: 60000,
|
||||||
id: '659e1',
|
id: '659e1',
|
||||||
@@ -603,6 +604,7 @@ describe('calculateRuntimeDelays', () => {
|
|||||||
colour: '',
|
colour: '',
|
||||||
type: SupportedEvent.Event,
|
type: SupportedEvent.Event,
|
||||||
revision: 0,
|
revision: 0,
|
||||||
|
delay: 0,
|
||||||
timeWarning: 120000,
|
timeWarning: 120000,
|
||||||
timeDanger: 60000,
|
timeDanger: 60000,
|
||||||
id: '1c48f',
|
id: '1c48f',
|
||||||
@@ -630,6 +632,7 @@ describe('calculateRuntimeDelays', () => {
|
|||||||
colour: '',
|
colour: '',
|
||||||
type: SupportedEvent.Event,
|
type: SupportedEvent.Event,
|
||||||
revision: 0,
|
revision: 0,
|
||||||
|
delay: 0,
|
||||||
timeWarning: 120000,
|
timeWarning: 120000,
|
||||||
timeDanger: 60000,
|
timeDanger: 60000,
|
||||||
id: 'd48c2',
|
id: 'd48c2',
|
||||||
@@ -657,6 +660,7 @@ describe('calculateRuntimeDelays', () => {
|
|||||||
colour: '',
|
colour: '',
|
||||||
type: SupportedEvent.Event,
|
type: SupportedEvent.Event,
|
||||||
revision: 0,
|
revision: 0,
|
||||||
|
delay: 0,
|
||||||
timeWarning: 120000,
|
timeWarning: 120000,
|
||||||
timeDanger: 60000,
|
timeDanger: 60000,
|
||||||
id: '2f185',
|
id: '2f185',
|
||||||
@@ -935,7 +939,7 @@ describe('calculateRuntimeDelaysFrom()', () => {
|
|||||||
|
|
||||||
describe('custom fields', () => {
|
describe('custom fields', () => {
|
||||||
describe('createCustomField()', () => {
|
describe('createCustomField()', () => {
|
||||||
it('creates a field from given parameters', async () => {
|
it('creates a field from given parameters', () => {
|
||||||
const expected = {
|
const expected = {
|
||||||
Lighting: {
|
Lighting: {
|
||||||
label: 'Lighting',
|
label: 'Lighting',
|
||||||
@@ -944,14 +948,14 @@ describe('custom fields', () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const customField = await createCustomField({ label: 'Lighting', type: 'string', colour: 'blue' });
|
const customField = createCustomField({ label: 'Lighting', type: 'string', colour: 'blue' });
|
||||||
expect(customField).toStrictEqual(expected);
|
expect(customField).toStrictEqual(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('editCustomField()', () => {
|
describe('editCustomField()', () => {
|
||||||
it('edits a field with a given label', async () => {
|
it('edits a field with a given label', () => {
|
||||||
await createCustomField({ label: 'Sound', type: 'string', colour: 'blue' });
|
createCustomField({ label: 'Sound', type: 'string', colour: 'blue' });
|
||||||
|
|
||||||
const expected = {
|
const expected = {
|
||||||
Lighting: {
|
Lighting: {
|
||||||
@@ -966,14 +970,14 @@ describe('custom fields', () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const customField = await editCustomField('Sound', { label: 'Sound', type: 'string', colour: 'green' });
|
const customField = editCustomField('Sound', { label: 'Sound', type: 'string', colour: 'green' });
|
||||||
expect(customFieldChangelog).toStrictEqual(new Map());
|
expect(customFieldChangelog).toStrictEqual(new Map());
|
||||||
|
|
||||||
expect(customField).toStrictEqual(expected);
|
expect(customField).toStrictEqual(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renames a field to a new label', async () => {
|
it('renames a field to a new label', () => {
|
||||||
const created = await createCustomField({ label: 'Video', type: 'string', colour: 'red' });
|
const created = createCustomField({ label: 'Video', type: 'string', colour: 'red' });
|
||||||
|
|
||||||
const expected = {
|
const expected = {
|
||||||
Lighting: {
|
Lighting: {
|
||||||
@@ -1015,10 +1019,10 @@ describe('custom fields', () => {
|
|||||||
|
|
||||||
// We need to flush all scheduled tasks for the generate function to settle
|
// We need to flush all scheduled tasks for the generate function to settle
|
||||||
vi.useFakeTimers();
|
vi.useFakeTimers();
|
||||||
const customField = await editCustomField('Video', { label: 'AV', type: 'string', colour: 'red' });
|
const customField = editCustomField('Video', { label: 'AV', type: 'string', colour: 'red' });
|
||||||
expect(customField).toStrictEqual(expectedAfter);
|
expect(customField).toStrictEqual(expectedAfter);
|
||||||
expect(customFieldChangelog).toStrictEqual(new Map([['Video', 'AV']]));
|
expect(customFieldChangelog).toStrictEqual(new Map([['Video', 'AV']]));
|
||||||
await editCustomField('AV', { label: 'Video' });
|
editCustomField('AV', { label: 'Video' });
|
||||||
vi.runAllTimers();
|
vi.runAllTimers();
|
||||||
expect(customFieldChangelog).toStrictEqual(new Map());
|
expect(customFieldChangelog).toStrictEqual(new Map());
|
||||||
vi.useRealTimers();
|
vi.useRealTimers();
|
||||||
@@ -1026,7 +1030,7 @@ describe('custom fields', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('removeCustomField()', () => {
|
describe('removeCustomField()', () => {
|
||||||
it('deletes a field with a given label', async () => {
|
it('deletes a field with a given label', () => {
|
||||||
const expected = {
|
const expected = {
|
||||||
Lighting: {
|
Lighting: {
|
||||||
label: 'Lighting',
|
label: 'Lighting',
|
||||||
@@ -1040,7 +1044,7 @@ describe('custom fields', () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const customField = await removeCustomField('Sound');
|
const customField = removeCustomField('Sound');
|
||||||
|
|
||||||
expect(customField).toStrictEqual(expected);
|
expect(customField).toStrictEqual(expected);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import {
|
|||||||
CustomField,
|
CustomField,
|
||||||
CustomFieldLabel,
|
CustomFieldLabel,
|
||||||
CustomFields,
|
CustomFields,
|
||||||
|
isOntimeBlock,
|
||||||
isOntimeDelay,
|
isOntimeDelay,
|
||||||
isOntimeEvent,
|
isOntimeEvent,
|
||||||
isPlayableEvent,
|
isPlayableEvent,
|
||||||
@@ -31,14 +32,27 @@ type NormalisedRundown = Record<EventID, OntimeRundownEntry>;
|
|||||||
let persistedRundown: OntimeRundown = [];
|
let persistedRundown: OntimeRundown = [];
|
||||||
let persistedCustomFields: CustomFields = {};
|
let persistedCustomFields: CustomFields = {};
|
||||||
|
|
||||||
/** Utility function gets to expose data */
|
/**
|
||||||
|
* Get the cached rundown without triggering regeneration
|
||||||
|
*/
|
||||||
export const getPersistedRundown = (): OntimeRundown => persistedRundown;
|
export const getPersistedRundown = (): OntimeRundown => persistedRundown;
|
||||||
export const getCustomFields = (): CustomFields => persistedCustomFields;
|
export const getCustomFields = (): CustomFields => persistedCustomFields;
|
||||||
|
|
||||||
let rundown: NormalisedRundown = {};
|
let normalisedRundown: NormalisedRundown = {};
|
||||||
let order: EventID[] = [];
|
let order: EventID[] = [];
|
||||||
let revision = 0;
|
let revision = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* all mutating functions will set this value if there is a need for re-generation
|
||||||
|
* but will only be cleared by the generate function
|
||||||
|
*/
|
||||||
let isStale = true;
|
let isStale = true;
|
||||||
|
|
||||||
|
/** Allows safely setting the stale state without accidentally clearing it */
|
||||||
|
function setIsStale() {
|
||||||
|
isStale = true;
|
||||||
|
}
|
||||||
|
|
||||||
let totalDelay = 0;
|
let totalDelay = 0;
|
||||||
let totalDuration = 0;
|
let totalDuration = 0;
|
||||||
let firstStart: MaybeNumber = null;
|
let firstStart: MaybeNumber = null;
|
||||||
@@ -72,18 +86,22 @@ export async function init(initialRundown: Readonly<OntimeRundown>, customFields
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility initialises cache
|
* Utility generate cache
|
||||||
* @param rundown
|
* @private should not be called outside of `rundownCache.ts`
|
||||||
*/
|
*/
|
||||||
export function generate(
|
export function generate(
|
||||||
initialRundown: OntimeRundown = persistedRundown,
|
initialRundown: OntimeRundown = persistedRundown,
|
||||||
customFields: CustomFields = persistedCustomFields,
|
customFields: CustomFields = persistedCustomFields,
|
||||||
) {
|
) {
|
||||||
|
function clearIsStale() {
|
||||||
|
isStale = false;
|
||||||
|
}
|
||||||
|
|
||||||
// we decided to re-write this dataset for every change
|
// we decided to re-write this dataset for every change
|
||||||
// instead of maintaining logic to update it
|
// instead of maintaining logic to update it
|
||||||
|
|
||||||
assignedCustomFields = {};
|
assignedCustomFields = {};
|
||||||
rundown = {};
|
normalisedRundown = {};
|
||||||
order = [];
|
order = [];
|
||||||
links = {};
|
links = {};
|
||||||
firstStart = null;
|
firstStart = null;
|
||||||
@@ -94,11 +112,12 @@ export function generate(
|
|||||||
let lastEntry: PlayableEvent | null = null;
|
let lastEntry: PlayableEvent | null = null;
|
||||||
|
|
||||||
for (let i = 0; i < initialRundown.length; i++) {
|
for (let i = 0; i < initialRundown.length; i++) {
|
||||||
// TODO: filter properties that should not be persisted (eg: delay)
|
|
||||||
// we assign a reference to the current entry, this will be mutated in place
|
// we assign a reference to the current entry, this will be mutated in place
|
||||||
const currentEntry = initialRundown[i];
|
const currentEntry = initialRundown[i];
|
||||||
|
|
||||||
if (isOntimeEvent(currentEntry)) {
|
if (isOntimeEvent(currentEntry)) {
|
||||||
|
currentEntry.delay = 0;
|
||||||
|
|
||||||
// 1. handle links - mutates updatedEvent
|
// 1. handle links - mutates updatedEvent
|
||||||
handleLink(i, initialRundown, currentEntry, links);
|
handleLink(i, initialRundown, currentEntry, links);
|
||||||
|
|
||||||
@@ -145,24 +164,30 @@ export function generate(
|
|||||||
lastEntry = currentEntry;
|
lastEntry = currentEntry;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else if (isOntimeDelay(currentEntry)) {
|
||||||
|
// calculate delays
|
||||||
// calculate delays
|
// !!! this must happen after handling the links
|
||||||
// !!! this must happen after handling the links
|
|
||||||
if (isOntimeDelay(currentEntry)) {
|
|
||||||
totalDelay += currentEntry.duration;
|
totalDelay += currentEntry.duration;
|
||||||
|
} else if (isOntimeBlock(currentEntry)) {
|
||||||
|
// calculate block - nothing yet
|
||||||
|
} else {
|
||||||
|
// unknown - type skip it
|
||||||
|
// this is needed to get the type guard working when we assign the entry to the rundown
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// add id to order
|
// add id to order
|
||||||
order.push(currentEntry.id);
|
order.push(currentEntry.id);
|
||||||
// add entry to rundown
|
// add entry to rundown
|
||||||
rundown[currentEntry.id] = currentEntry;
|
normalisedRundown[currentEntry.id] = currentEntry;
|
||||||
}
|
}
|
||||||
|
|
||||||
lastEnd = lastEntry?.timeEnd ?? null;
|
lastEnd = lastEntry?.timeEnd ?? null;
|
||||||
isStale = false;
|
clearIsStale();
|
||||||
customFieldChangelog.clear();
|
customFieldChangelog.clear();
|
||||||
return { rundown, order, links, totalDelay, totalDuration, assignedCustomFields };
|
|
||||||
|
//The return value is used for testing
|
||||||
|
return { rundown: normalisedRundown, order, links, totalDelay, totalDuration, assignedCustomFields };
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns an ID guaranteed to be unique */
|
/** Returns an ID guaranteed to be unique */
|
||||||
@@ -173,7 +198,7 @@ export function getUniqueId(): string {
|
|||||||
let id = '';
|
let id = '';
|
||||||
do {
|
do {
|
||||||
id = generateId();
|
id = generateId();
|
||||||
} while (Object.hasOwn(rundown, id));
|
} while (Object.hasOwn(normalisedRundown, id));
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,15 +219,15 @@ type RundownCache = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns cached data
|
* Returns the full rundown cache.
|
||||||
* @returns {RundownCache}
|
* Will triggering regeneration if data is stale.
|
||||||
*/
|
*/
|
||||||
export function get(): Readonly<RundownCache> {
|
export function get(): Readonly<RundownCache> {
|
||||||
if (isStale) {
|
if (isStale) {
|
||||||
generate();
|
generate();
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
rundown,
|
rundown: normalisedRundown,
|
||||||
order,
|
order,
|
||||||
revision,
|
revision,
|
||||||
totalDelay,
|
totalDelay,
|
||||||
@@ -212,6 +237,7 @@ export function get(): Readonly<RundownCache> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns calculated metadata from rundown
|
* Returns calculated metadata from rundown
|
||||||
|
* Will triggering regeneration if data is stale.
|
||||||
*/
|
*/
|
||||||
export function getMetadata() {
|
export function getMetadata() {
|
||||||
if (isStale) {
|
if (isStale) {
|
||||||
@@ -227,7 +253,7 @@ export function getMetadata() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
type CommonParams = { persistedRundown: OntimeRundown };
|
type CommonParams = { rundown: OntimeRundown };
|
||||||
type MutationParams<T> = T & CommonParams;
|
type MutationParams<T> = T & CommonParams;
|
||||||
type MutatingReturn = {
|
type MutatingReturn = {
|
||||||
newRundown: OntimeRundown;
|
newRundown: OntimeRundown;
|
||||||
@@ -238,23 +264,14 @@ type MutatingFn<T extends object> = (params: MutationParams<T>) => MutatingRetur
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Decorators injects data into mutation
|
* Decorators injects data into mutation
|
||||||
* @param mutation
|
* ensures order of operations when performing mutations
|
||||||
* @returns
|
|
||||||
*/
|
*/
|
||||||
export function mutateCache<T extends object>(mutation: MutatingFn<T>) {
|
export function mutateCache<T extends object>(mutation: MutatingFn<T>) {
|
||||||
async function scopedMutation(params: T) {
|
function scopedMutation(params: T) {
|
||||||
/**
|
const { newEvent, newRundown, didMutate } = mutation({ ...params, rundown: persistedRundown });
|
||||||
* Marking the data set as stale
|
|
||||||
* doing it before calling the mutation, gives the function a chance
|
|
||||||
* to prevent recalculation by setting stale = false
|
|
||||||
*/
|
|
||||||
isStale = true;
|
|
||||||
|
|
||||||
const { newEvent, newRundown, didMutate } = mutation({ ...params, persistedRundown });
|
|
||||||
|
|
||||||
// early return without calling side effects
|
// early return without calling side effects
|
||||||
if (!didMutate) {
|
if (!didMutate) {
|
||||||
isStale = false;
|
|
||||||
return { newEvent, newRundown, didMutate };
|
return { newEvent, newRundown, didMutate };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,31 +295,34 @@ export function mutateCache<T extends object>(mutation: MutatingFn<T>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AddArgs = MutationParams<{ atIndex: number; event: OntimeRundownEntry }>;
|
type AddArgs = MutationParams<{ atIndex: number; event: OntimeRundownEntry }>;
|
||||||
|
/**
|
||||||
export function add({ persistedRundown, atIndex, event }: AddArgs): Required<MutatingReturn> {
|
* Add entry to rundown
|
||||||
|
*/
|
||||||
|
export function add({ rundown, atIndex, event }: AddArgs): Required<MutatingReturn> {
|
||||||
const newEvent: OntimeRundownEntry = { ...event };
|
const newEvent: OntimeRundownEntry = { ...event };
|
||||||
const newRundown = insertAtIndex(atIndex, newEvent, persistedRundown);
|
const newRundown = insertAtIndex(atIndex, newEvent, rundown);
|
||||||
|
setIsStale();
|
||||||
return { newRundown, newEvent, didMutate: true };
|
return { newRundown, newEvent, didMutate: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
type RemoveArgs = MutationParams<{ eventIds: string[] }>;
|
type RemoveArgs = MutationParams<{ eventIds: string[] }>;
|
||||||
|
/**
|
||||||
export function remove({ persistedRundown, eventIds }: RemoveArgs): MutatingReturn {
|
* Remove entry to rundown
|
||||||
const newRundown = persistedRundown.filter((event) => !eventIds.includes(event.id));
|
*/
|
||||||
|
export function remove({ rundown, eventIds }: RemoveArgs): MutatingReturn {
|
||||||
return { newRundown, didMutate: persistedRundown.length !== newRundown.length };
|
const newRundown = rundown.filter((event) => !eventIds.includes(event.id));
|
||||||
|
const didMutate = rundown.length !== newRundown.length;
|
||||||
|
if (didMutate) setIsStale();
|
||||||
|
return { newRundown, didMutate };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeAll(): MutatingReturn {
|
export function removeAll(): MutatingReturn {
|
||||||
|
setIsStale();
|
||||||
return { newRundown: [], didMutate: true };
|
return { newRundown: [], didMutate: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility function for patching events
|
* Utility function for patching an existing event with new data
|
||||||
* @param eventFromRundown
|
|
||||||
* @param patch
|
|
||||||
* @returns
|
|
||||||
*/
|
*/
|
||||||
function makeEvent(eventFromRundown: OntimeRundownEntry, patch: Partial<OntimeRundownEntry>): OntimeRundownEntry {
|
function makeEvent(eventFromRundown: OntimeRundownEntry, patch: Partial<OntimeRundownEntry>): OntimeRundownEntry {
|
||||||
if (isOntimeEvent(eventFromRundown)) {
|
if (isOntimeEvent(eventFromRundown)) {
|
||||||
@@ -315,120 +335,133 @@ function makeEvent(eventFromRundown: OntimeRundownEntry, patch: Partial<OntimeRu
|
|||||||
}
|
}
|
||||||
|
|
||||||
type EditArgs = MutationParams<{ eventId: string; patch: Partial<OntimeRundownEntry> }>;
|
type EditArgs = MutationParams<{ eventId: string; patch: Partial<OntimeRundownEntry> }>;
|
||||||
|
/**
|
||||||
export function edit({ persistedRundown, eventId, patch }: EditArgs): Required<MutatingReturn> {
|
* Apply patch to an entry with given id
|
||||||
const indexAt = persistedRundown.findIndex((event) => event.id === eventId);
|
*/
|
||||||
|
export function edit({ rundown, eventId, patch }: EditArgs): Required<MutatingReturn> {
|
||||||
|
const indexAt = rundown.findIndex((event) => event.id === eventId);
|
||||||
if (indexAt < 0) {
|
if (indexAt < 0) {
|
||||||
throw new Error('Event not found');
|
throw new Error('Event not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (patch?.type && persistedRundown[indexAt].type !== patch.type) {
|
if (patch?.type && rundown[indexAt].type !== patch.type) {
|
||||||
throw new Error('Invalid event type');
|
throw new Error('Invalid event type');
|
||||||
}
|
}
|
||||||
|
|
||||||
const eventInMemory = persistedRundown[indexAt];
|
const eventInMemory = rundown[indexAt];
|
||||||
|
|
||||||
if (!hasChanges(eventInMemory, patch)) {
|
if (!hasChanges(eventInMemory, patch)) {
|
||||||
isStale = false;
|
return { newRundown: rundown, newEvent: eventInMemory, didMutate: false };
|
||||||
return { newRundown: persistedRundown, newEvent: eventInMemory, didMutate: false };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const newEvent = makeEvent(eventInMemory, patch);
|
const newEvent = makeEvent(eventInMemory, patch);
|
||||||
|
|
||||||
const newRundown = [...persistedRundown];
|
const newRundown = [...rundown];
|
||||||
newRundown[indexAt] = newEvent;
|
newRundown[indexAt] = newEvent;
|
||||||
|
|
||||||
// check whether the data warrants recalculation of cache
|
// check whether the data warrants recalculation of cache
|
||||||
const makeStale = isDataStale(patch);
|
const makeStale = isDataStale(patch);
|
||||||
|
|
||||||
if (!makeStale) {
|
if (makeStale) {
|
||||||
rundown[newEvent.id] = newEvent;
|
setIsStale();
|
||||||
|
} else {
|
||||||
|
normalisedRundown[newEvent.id] = newEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
isStale = makeStale;
|
|
||||||
return { newRundown, newEvent, didMutate: true };
|
return { newRundown, newEvent, didMutate: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
type BatchEditArgs = MutationParams<{ eventIds: string[]; patch: Partial<OntimeRundownEntry> }>;
|
type BatchEditArgs = MutationParams<{ eventIds: string[]; patch: Partial<OntimeRundownEntry> }>;
|
||||||
|
/**
|
||||||
export function batchEdit({ persistedRundown, eventIds, patch }: BatchEditArgs): MutatingReturn {
|
* Apply patch to multiple entries
|
||||||
|
*/
|
||||||
|
export function batchEdit({ rundown, eventIds, patch }: BatchEditArgs): MutatingReturn {
|
||||||
const ids = new Set(eventIds);
|
const ids = new Set(eventIds);
|
||||||
|
|
||||||
const newRundown = [];
|
const newRundown = [];
|
||||||
for (let i = 0; i < persistedRundown.length; i++) {
|
for (let i = 0; i < rundown.length; i++) {
|
||||||
if (ids.has(persistedRundown[i].id)) {
|
if (ids.has(rundown[i].id)) {
|
||||||
if (patch?.type && persistedRundown[i].type !== patch.type) {
|
if (patch?.type && rundown[i].type !== patch.type) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const newEvent = makeEvent(persistedRundown[i], patch);
|
const newEvent = makeEvent(rundown[i], patch);
|
||||||
newRundown.push(newEvent);
|
newRundown.push(newEvent);
|
||||||
} else {
|
} else {
|
||||||
newRundown.push(persistedRundown[i]);
|
newRundown.push(rundown[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
setIsStale();
|
||||||
return { newRundown, didMutate: true };
|
return { newRundown, didMutate: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
type ReorderArgs = MutationParams<{ eventId: string; from: number; to: number }>;
|
type ReorderArgs = MutationParams<{ eventId: string; from: number; to: number }>;
|
||||||
|
/**
|
||||||
export function reorder({ persistedRundown, eventId, from, to }: ReorderArgs): Required<MutatingReturn> {
|
* Redorder two entries
|
||||||
const event = persistedRundown[from];
|
*/
|
||||||
|
export function reorder({ rundown, eventId, from, to }: ReorderArgs): Required<MutatingReturn> {
|
||||||
|
const event = rundown[from];
|
||||||
if (!event || eventId !== event.id) {
|
if (!event || eventId !== event.id) {
|
||||||
throw new Error('Event not found');
|
throw new Error('Event not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
const newRundown = reorderArray(persistedRundown, from, to);
|
const newRundown = reorderArray(rundown, from, to);
|
||||||
for (let i = from; i <= to; i++) {
|
for (let i = from; i <= to; i++) {
|
||||||
const event = newRundown.at(i);
|
const event = newRundown.at(i);
|
||||||
if (isOntimeEvent(event)) {
|
if (isOntimeEvent(event)) {
|
||||||
event.revision += 1;
|
event.revision += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
setIsStale();
|
||||||
return { newRundown, newEvent: newRundown.at(from) as OntimeRundownEntry, didMutate: true };
|
return { newRundown, newEvent: newRundown.at(from) as OntimeRundownEntry, didMutate: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
type ApplyDelayArgs = MutationParams<{ eventId: string }>;
|
type ApplyDelayArgs = MutationParams<{ eventId: string }>;
|
||||||
|
/**
|
||||||
export function applyDelay({ persistedRundown, eventId }: ApplyDelayArgs): MutatingReturn {
|
* Apply a delay
|
||||||
const newRundown = apply(eventId, persistedRundown);
|
*/
|
||||||
|
export function applyDelay({ rundown, eventId }: ApplyDelayArgs): MutatingReturn {
|
||||||
|
const newRundown = apply(eventId, rundown);
|
||||||
|
setIsStale();
|
||||||
return { newRundown, didMutate: true };
|
return { newRundown, didMutate: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
type SwapArgs = MutationParams<{ fromId: string; toId: string }>;
|
type SwapArgs = MutationParams<{ fromId: string; toId: string }>;
|
||||||
|
/**
|
||||||
|
* Swap two entries
|
||||||
|
*/
|
||||||
|
export function swap({ rundown, fromId, toId }: SwapArgs): MutatingReturn {
|
||||||
|
const indexA = rundown.findIndex((event) => event.id === fromId);
|
||||||
|
const eventA = rundown.at(indexA);
|
||||||
|
|
||||||
export function swap({ persistedRundown, fromId, toId }: SwapArgs): MutatingReturn {
|
const indexB = rundown.findIndex((event) => event.id === toId);
|
||||||
const indexA = persistedRundown.findIndex((event) => event.id === fromId);
|
const eventB = rundown.at(indexB);
|
||||||
const eventA = persistedRundown.at(indexA);
|
|
||||||
|
|
||||||
const indexB = persistedRundown.findIndex((event) => event.id === toId);
|
|
||||||
const eventB = persistedRundown.at(indexB);
|
|
||||||
|
|
||||||
if (!isOntimeEvent(eventA) || !isOntimeEvent(eventB)) {
|
if (!isOntimeEvent(eventA) || !isOntimeEvent(eventB)) {
|
||||||
throw new Error('Swap only available for OntimeEvents');
|
throw new Error('Swap only available for OntimeEvents');
|
||||||
}
|
}
|
||||||
|
|
||||||
const { newA, newB } = swapEventData(eventA, eventB);
|
const { newA, newB } = swapEventData(eventA, eventB);
|
||||||
const newRundown = [...persistedRundown];
|
const newRundown = [...rundown];
|
||||||
|
|
||||||
newRundown[indexA] = newA;
|
newRundown[indexA] = newA;
|
||||||
(newRundown[indexA] as OntimeEvent).revision += 1;
|
(newRundown[indexA] as OntimeEvent).revision += 1;
|
||||||
newRundown[indexB] = newB;
|
newRundown[indexB] = newB;
|
||||||
(newRundown[indexB] as OntimeEvent).revision += 1;
|
(newRundown[indexB] as OntimeEvent).revision += 1;
|
||||||
|
|
||||||
|
setIsStale();
|
||||||
return { newRundown, didMutate: true };
|
return { newRundown, didMutate: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invalidates service cache if a custom field is used
|
* Utility for invalidating service cache if a custom field is used
|
||||||
* @param label
|
|
||||||
*/
|
*/
|
||||||
function invalidateIfUsed(label: CustomFieldLabel) {
|
function invalidateIfUsed(label: CustomFieldLabel) {
|
||||||
if (label in assignedCustomFields) {
|
if (label in assignedCustomFields) {
|
||||||
isStale = true;
|
setIsStale();
|
||||||
}
|
}
|
||||||
// if the field was in use, we mark the cache as stale
|
// if the field was in use, we mark the cache as stale
|
||||||
if (label in assignedCustomFields) {
|
if (label in assignedCustomFields) {
|
||||||
isStale = true;
|
setIsStale();
|
||||||
}
|
}
|
||||||
// ... and schedule a cache update
|
// ... and schedule a cache update
|
||||||
// schedule a non priority cache update
|
// schedule a non priority cache update
|
||||||
@@ -439,10 +472,9 @@ function invalidateIfUsed(label: CustomFieldLabel) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Schedules a non priority custom field persist
|
* Utility for scheduling a non priority custom field persist
|
||||||
* @param persistedCustomFields
|
|
||||||
*/
|
*/
|
||||||
function scheduleCustomFieldPersist(persistedCustomFields: CustomFields) {
|
function scheduleCustomFieldPersist() {
|
||||||
setImmediate(async () => {
|
setImmediate(async () => {
|
||||||
await getDataProvider().setCustomFields(persistedCustomFields);
|
await getDataProvider().setCustomFields(persistedCustomFields);
|
||||||
});
|
});
|
||||||
@@ -450,12 +482,15 @@ function scheduleCustomFieldPersist(persistedCustomFields: CustomFields) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Sanitises and creates a custom field in the database
|
* Sanitises and creates a custom field in the database
|
||||||
* @param field
|
|
||||||
* @returns
|
|
||||||
*/
|
*/
|
||||||
export const createCustomField = async (field: CustomField) => {
|
export function createCustomField(field: CustomField): CustomFields {
|
||||||
const { label, type, colour } = field;
|
const { label, type, colour } = field;
|
||||||
const key = customFieldLabelToKey(label);
|
const key = customFieldLabelToKey(label);
|
||||||
|
|
||||||
|
if (key === null) {
|
||||||
|
throw new Error('Unable to convert label to a valid key');
|
||||||
|
}
|
||||||
|
|
||||||
// check if label already exists
|
// check if label already exists
|
||||||
const alreadyExists = Object.hasOwn(persistedCustomFields, key);
|
const alreadyExists = Object.hasOwn(persistedCustomFields, key);
|
||||||
|
|
||||||
@@ -466,18 +501,15 @@ export const createCustomField = async (field: CustomField) => {
|
|||||||
// update object and persist
|
// update object and persist
|
||||||
persistedCustomFields[key] = { label, type, colour };
|
persistedCustomFields[key] = { label, type, colour };
|
||||||
|
|
||||||
scheduleCustomFieldPersist(persistedCustomFields);
|
scheduleCustomFieldPersist();
|
||||||
|
|
||||||
return persistedCustomFields;
|
return persistedCustomFields;
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Edits an existing custom field in the database
|
* Edits an existing custom field in the database
|
||||||
* @param key
|
|
||||||
* @param newField
|
|
||||||
* @returns
|
|
||||||
*/
|
*/
|
||||||
export const editCustomField = async (key: string, newField: Partial<CustomField>) => {
|
export function editCustomField(key: string, newField: Partial<CustomField>): CustomFields {
|
||||||
if (!(key in persistedCustomFields)) {
|
if (!(key in persistedCustomFields)) {
|
||||||
throw new Error('Could not find label');
|
throw new Error('Could not find label');
|
||||||
}
|
}
|
||||||
@@ -487,7 +519,14 @@ export const editCustomField = async (key: string, newField: Partial<CustomField
|
|||||||
throw new Error('Change of field type is not allowed');
|
throw new Error('Change of field type is not allowed');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (newField.label === undefined) {
|
||||||
|
throw new Error('Missing label');
|
||||||
|
}
|
||||||
|
|
||||||
const newKey = customFieldLabelToKey(newField.label);
|
const newKey = customFieldLabelToKey(newField.label);
|
||||||
|
if (newKey === null) {
|
||||||
|
throw new Error('Unable to convert label to a valid key');
|
||||||
|
}
|
||||||
persistedCustomFields[newKey] = { ...existingField, ...newField };
|
persistedCustomFields[newKey] = { ...existingField, ...newField };
|
||||||
|
|
||||||
if (key !== newKey) {
|
if (key !== newKey) {
|
||||||
@@ -495,23 +534,22 @@ export const editCustomField = async (key: string, newField: Partial<CustomField
|
|||||||
customFieldChangelog.set(key, newKey);
|
customFieldChangelog.set(key, newKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
scheduleCustomFieldPersist(persistedCustomFields);
|
scheduleCustomFieldPersist();
|
||||||
invalidateIfUsed(key);
|
invalidateIfUsed(key);
|
||||||
|
|
||||||
return persistedCustomFields;
|
return persistedCustomFields;
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes a custom field from the database
|
* Deletes a custom field from the database
|
||||||
* @param label
|
|
||||||
*/
|
*/
|
||||||
export const removeCustomField = async (label: string) => {
|
export function removeCustomField(label: string): CustomFields {
|
||||||
if (label in persistedCustomFields) {
|
if (label in persistedCustomFields) {
|
||||||
delete persistedCustomFields[label];
|
delete persistedCustomFields[label];
|
||||||
}
|
}
|
||||||
|
|
||||||
scheduleCustomFieldPersist(persistedCustomFields);
|
scheduleCustomFieldPersist();
|
||||||
invalidateIfUsed(label);
|
invalidateIfUsed(label);
|
||||||
|
|
||||||
return persistedCustomFields;
|
return persistedCustomFields;
|
||||||
};
|
}
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ export function handleCustomField(
|
|||||||
// rename the property if it is in the changelog
|
// rename the property if it is in the changelog
|
||||||
if (customFieldChangelog.has(field)) {
|
if (customFieldChangelog.has(field)) {
|
||||||
const oldData = mutableEvent.custom[field];
|
const oldData = mutableEvent.custom[field];
|
||||||
const newLabel = customFieldChangelog.get(field);
|
const newLabel = customFieldChangelog.get(field) as string; // it os OK to cast to string here since we already checked that it existed
|
||||||
|
|
||||||
mutableEvent.custom[newLabel] = oldData;
|
mutableEvent.custom[newLabel] = oldData;
|
||||||
delete mutableEvent.custom[field];
|
delete mutableEvent.custom[field];
|
||||||
@@ -143,6 +143,6 @@ export function willCauseRegeneration(key: keyof OntimeEvent): boolean {
|
|||||||
*/
|
*/
|
||||||
export function hasChanges<T extends OntimeBaseEvent>(existingEvent: T, newEvent: Partial<T>): boolean {
|
export function hasChanges<T extends OntimeBaseEvent>(existingEvent: T, newEvent: Partial<T>): boolean {
|
||||||
return Object.keys(newEvent).some(
|
return Object.keys(newEvent).some(
|
||||||
(key) => !Object.hasOwn(existingEvent, key) || existingEvent[key] !== newEvent[key],
|
(key) => !Object.hasOwn(existingEvent, key) || existingEvent[key as keyof T] !== newEvent[key as keyof T],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ describe('cellRequestFromEvent()', () => {
|
|||||||
isPublic: false,
|
isPublic: false,
|
||||||
skip: false,
|
skip: false,
|
||||||
colour: 'red',
|
colour: 'red',
|
||||||
|
delay: 0,
|
||||||
revision: 0,
|
revision: 0,
|
||||||
id: '1358',
|
id: '1358',
|
||||||
timeWarning: 0,
|
timeWarning: 0,
|
||||||
@@ -82,6 +83,7 @@ describe('cellRequestFromEvent()', () => {
|
|||||||
skip: false,
|
skip: false,
|
||||||
colour: 'red',
|
colour: 'red',
|
||||||
revision: 0,
|
revision: 0,
|
||||||
|
delay: 0,
|
||||||
id: '1358',
|
id: '1358',
|
||||||
timeWarning: 0,
|
timeWarning: 0,
|
||||||
timeDanger: 0,
|
timeDanger: 0,
|
||||||
@@ -129,6 +131,7 @@ describe('cellRequestFromEvent()', () => {
|
|||||||
skip: false,
|
skip: false,
|
||||||
colour: 'red',
|
colour: 'red',
|
||||||
revision: 0,
|
revision: 0,
|
||||||
|
delay: 0,
|
||||||
id: '1358',
|
id: '1358',
|
||||||
timeWarning: 0,
|
timeWarning: 0,
|
||||||
timeDanger: 0,
|
timeDanger: 0,
|
||||||
@@ -174,6 +177,7 @@ describe('cellRequestFromEvent()', () => {
|
|||||||
isPublic: true,
|
isPublic: true,
|
||||||
skip: false,
|
skip: false,
|
||||||
colour: 'red',
|
colour: 'red',
|
||||||
|
delay: 0,
|
||||||
revision: 0,
|
revision: 0,
|
||||||
id: '1358',
|
id: '1358',
|
||||||
timeWarning: 0,
|
timeWarning: 0,
|
||||||
@@ -207,6 +211,7 @@ describe('cellRequestFromEvent()', () => {
|
|||||||
skip: false,
|
skip: false,
|
||||||
colour: 'red',
|
colour: 'red',
|
||||||
revision: 0,
|
revision: 0,
|
||||||
|
delay: 0,
|
||||||
id: '1358',
|
id: '1358',
|
||||||
timeWarning: 0,
|
timeWarning: 0,
|
||||||
timeDanger: 0,
|
timeDanger: 0,
|
||||||
@@ -240,6 +245,7 @@ describe('cellRequestFromEvent()', () => {
|
|||||||
skip: false,
|
skip: false,
|
||||||
colour: 'red',
|
colour: 'red',
|
||||||
revision: 0,
|
revision: 0,
|
||||||
|
delay: 0,
|
||||||
id: '1358',
|
id: '1358',
|
||||||
timeWarning: 0,
|
timeWarning: 0,
|
||||||
timeDanger: 0,
|
timeDanger: 0,
|
||||||
|
|||||||
@@ -384,6 +384,7 @@ export function createPatch(originalEvent: OntimeEvent, patchEvent: Partial<Onti
|
|||||||
skip: typeof patchEvent.skip === 'boolean' ? patchEvent.skip : originalEvent.skip,
|
skip: typeof patchEvent.skip === 'boolean' ? patchEvent.skip : originalEvent.skip,
|
||||||
note: makeString(patchEvent.note, originalEvent.note),
|
note: makeString(patchEvent.note, originalEvent.note),
|
||||||
colour: makeString(patchEvent.colour, originalEvent.colour),
|
colour: makeString(patchEvent.colour, originalEvent.colour),
|
||||||
|
delay: 0, //delay is always regenerated by the cache
|
||||||
// short circuit empty string
|
// short circuit empty string
|
||||||
cue: makeString(patchEvent.cue ?? null, originalEvent.cue),
|
cue: makeString(patchEvent.cue ?? null, originalEvent.cue),
|
||||||
revision: originalEvent.revision,
|
revision: originalEvent.revision,
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export type OntimeEvent = OntimeBaseEvent & {
|
|||||||
skip: boolean;
|
skip: boolean;
|
||||||
colour: string;
|
colour: string;
|
||||||
revision: number;
|
revision: number;
|
||||||
delay?: number; // calculated at runtime
|
delay: number; // calculated at runtime
|
||||||
timeWarning: number;
|
timeWarning: number;
|
||||||
timeDanger: number;
|
timeDanger: number;
|
||||||
custom: EventCustomFields;
|
custom: EventCustomFields;
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { isAlphanumericWithSpace } from '../regex-utils/isAlphanumeric.js';
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @description Transforms a Custom field label into a valid key or returns null if not possible
|
* @description Transforms a Custom field label into a valid key or returns null if not possible
|
||||||
* @returns {string | null}
|
|
||||||
*/
|
*/
|
||||||
export const customFieldLabelToKey = (label: string): string | null => {
|
export const customFieldLabelToKey = (label: string): string | null => {
|
||||||
if (isAlphanumericWithSpace(label)) {
|
if (isAlphanumericWithSpace(label)) {
|
||||||
|
|||||||
Reference in New Issue
Block a user