mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-21 15:09:10 +00:00
fix: apply delay
This commit is contained in:
committed by
Carlos Valente
parent
086bd5b644
commit
7877af571f
@@ -1,249 +1,243 @@
|
|||||||
import { OntimeBlock, OntimeDelay, OntimeEvent, OntimeRundown, SupportedEvent } from 'ontime-types';
|
import { OntimeBlock, OntimeDelay, OntimeEvent, OntimeRundown, SupportedEvent } from 'ontime-types';
|
||||||
import { apply } from '../delayUtils.js';
|
import { apply } from '../delayUtils.js';
|
||||||
|
import { MILLIS_PER_HOUR } from 'ontime-utils';
|
||||||
|
|
||||||
describe('apply() ', () => {
|
/**
|
||||||
describe('in a rundown without the delay field, persisted rundown', () => {
|
* Small utility to fill in the necessary data for the test
|
||||||
it('applies delays', () => {
|
*/
|
||||||
const delayId = '1';
|
function makeOntimeEvent(event: Partial<OntimeEvent>): OntimeEvent {
|
||||||
const testRundown: OntimeRundown = [
|
return { ...event, type: SupportedEvent.Event, revision: 1 } as OntimeEvent;
|
||||||
{ id: delayId, type: SupportedEvent.Delay, duration: 10 } as OntimeDelay,
|
}
|
||||||
{ id: '2', type: SupportedEvent.Event, timeStart: 0, timeEnd: 10, duration: 10, revision: 1 } as OntimeEvent,
|
|
||||||
{ id: '3', type: SupportedEvent.Event, timeStart: 0, timeEnd: 10, duration: 10, revision: 1 } as OntimeEvent,
|
|
||||||
{ id: '4', type: SupportedEvent.Block } as OntimeBlock,
|
|
||||||
{ id: '5', type: SupportedEvent.Event, timeStart: 0, timeEnd: 10, duration: 10, revision: 1 } as OntimeEvent,
|
|
||||||
];
|
|
||||||
|
|
||||||
const expected = [
|
/**
|
||||||
{ id: '2', type: SupportedEvent.Event, timeStart: 10, timeEnd: 20, duration: 10, revision: 2 } as OntimeEvent,
|
* Small utility to make a delay event
|
||||||
{ id: '3', type: SupportedEvent.Event, timeStart: 10, timeEnd: 20, duration: 10, revision: 2 } as OntimeEvent,
|
*/
|
||||||
{ id: '4', type: SupportedEvent.Block } as OntimeBlock,
|
function makeOntimeDelay(duration: number): OntimeDelay {
|
||||||
{ id: '5', type: SupportedEvent.Event, timeStart: 0, timeEnd: 10, duration: 10, revision: 1 } as OntimeEvent,
|
return { id: 'delay', type: SupportedEvent.Delay, duration } as OntimeDelay;
|
||||||
];
|
}
|
||||||
|
|
||||||
const updatedRundown = apply(delayId, testRundown);
|
describe('apply()', () => {
|
||||||
expect(updatedRundown).toStrictEqual(expected);
|
it('applies a positive delay to the rundown', () => {
|
||||||
});
|
const testRundown = [
|
||||||
it('applies negative delays', () => {
|
makeOntimeDelay(10),
|
||||||
const delayId = '1';
|
makeOntimeEvent({ id: '1', timeStart: 0, timeEnd: 10, duration: 10 }),
|
||||||
const testRundown: OntimeRundown = [
|
makeOntimeEvent({ id: '2', timeStart: 10, timeEnd: 20, duration: 10, linkStart: '1' }),
|
||||||
{ id: delayId, type: SupportedEvent.Delay, duration: -10 } as OntimeDelay,
|
{ id: '3', type: SupportedEvent.Block } as OntimeBlock,
|
||||||
{ id: '2', type: SupportedEvent.Event, timeStart: 0, timeEnd: 10, duration: 10, revision: 1 } as OntimeEvent,
|
makeOntimeEvent({ id: '4', timeStart: 20, timeEnd: 30, duration: 10, linkStart: null }),
|
||||||
{ id: '3', type: SupportedEvent.Event, timeStart: 20, timeEnd: 40, duration: 20, revision: 1 } as OntimeEvent,
|
makeOntimeEvent({ id: '5', timeStart: 30, timeEnd: 40, duration: 10, linkStart: '4' }),
|
||||||
{ id: '4', type: SupportedEvent.Block } as OntimeBlock,
|
];
|
||||||
{ id: '5', type: SupportedEvent.Event, timeStart: 0, timeEnd: 10, duration: 10, revision: 1 } as OntimeEvent,
|
|
||||||
];
|
|
||||||
|
|
||||||
const expected = [
|
const updatedRundown = apply('delay', testRundown);
|
||||||
{ id: '2', type: SupportedEvent.Event, timeStart: 0, timeEnd: 10, duration: 10, revision: 2 } as OntimeEvent,
|
expect(updatedRundown).not.toBe(testRundown);
|
||||||
{ id: '3', type: SupportedEvent.Event, timeStart: 10, timeEnd: 30, duration: 20, revision: 2 } as OntimeEvent,
|
expect(updatedRundown).toMatchObject([
|
||||||
{ id: '4', type: SupportedEvent.Block } as OntimeBlock,
|
{ id: '1', timeStart: 10, timeEnd: 20, duration: 10, revision: 2 },
|
||||||
{ id: '5', type: SupportedEvent.Event, timeStart: 0, timeEnd: 10, duration: 10, revision: 1 } as OntimeEvent,
|
{ id: '2', timeStart: 20, timeEnd: 30, duration: 10, revision: 2, linkStart: '1' },
|
||||||
];
|
{ id: '3' },
|
||||||
|
{ id: '4', timeStart: 30, timeEnd: 40, duration: 10, revision: 2, linkStart: null },
|
||||||
const updatedRundown = apply(delayId, testRundown);
|
{ id: '5', timeStart: 40, timeEnd: 50, duration: 10, revision: 2, linkStart: '4' },
|
||||||
expect(updatedRundown).toStrictEqual(expected);
|
]);
|
||||||
});
|
|
||||||
it('maintains constant duration', () => {
|
|
||||||
const delayId = '1';
|
|
||||||
const testRundown: OntimeRundown = [
|
|
||||||
{ id: delayId, type: SupportedEvent.Delay, duration: -30 } as OntimeDelay,
|
|
||||||
{ id: '2', type: SupportedEvent.Event, timeStart: 0, timeEnd: 10, duration: 10, revision: 1 } as OntimeEvent,
|
|
||||||
{ id: '3', type: SupportedEvent.Event, timeStart: 20, timeEnd: 40, duration: 20, revision: 1 } as OntimeEvent,
|
|
||||||
];
|
|
||||||
|
|
||||||
const expected = [
|
|
||||||
{ id: '2', type: SupportedEvent.Event, timeStart: 0, timeEnd: 10, duration: 10, revision: 2 } as OntimeEvent,
|
|
||||||
{ id: '3', type: SupportedEvent.Event, timeStart: 0, timeEnd: 20, duration: 20, revision: 2 } as OntimeEvent,
|
|
||||||
];
|
|
||||||
|
|
||||||
const updatedRundown = apply(delayId, testRundown);
|
|
||||||
expect(updatedRundown).toStrictEqual(expected);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
describe('in a rundown with the delay field, cached rundown', () => {
|
|
||||||
it('applies delays', () => {
|
|
||||||
const delayId = '1';
|
|
||||||
const testRundown: OntimeRundown = [
|
|
||||||
{ id: delayId, type: SupportedEvent.Delay, duration: 10 } as OntimeDelay,
|
|
||||||
{
|
|
||||||
id: '2',
|
|
||||||
type: SupportedEvent.Event,
|
|
||||||
timeStart: 0,
|
|
||||||
timeEnd: 10,
|
|
||||||
duration: 10,
|
|
||||||
revision: 1,
|
|
||||||
delay: 10,
|
|
||||||
} as OntimeEvent,
|
|
||||||
{
|
|
||||||
id: '3',
|
|
||||||
type: SupportedEvent.Event,
|
|
||||||
timeStart: 0,
|
|
||||||
timeEnd: 10,
|
|
||||||
duration: 10,
|
|
||||||
revision: 1,
|
|
||||||
delay: 10,
|
|
||||||
} as OntimeEvent,
|
|
||||||
{ id: '4', type: SupportedEvent.Block } as OntimeBlock,
|
|
||||||
{
|
|
||||||
id: '5',
|
|
||||||
type: SupportedEvent.Event,
|
|
||||||
timeStart: 0,
|
|
||||||
timeEnd: 10,
|
|
||||||
duration: 10,
|
|
||||||
revision: 1,
|
|
||||||
delay: 0,
|
|
||||||
} as OntimeEvent,
|
|
||||||
];
|
|
||||||
|
|
||||||
const expected = [
|
it('applies negative delays', () => {
|
||||||
{
|
const testRundown = [
|
||||||
id: '2',
|
makeOntimeDelay(-10),
|
||||||
type: SupportedEvent.Event,
|
makeOntimeEvent({ id: '1', timeStart: 0, timeEnd: 10, duration: 10 }),
|
||||||
timeStart: 10,
|
makeOntimeEvent({ id: '2', timeStart: 10, timeEnd: 20, duration: 10, linkStart: '1' }),
|
||||||
timeEnd: 20,
|
{ id: '3', type: SupportedEvent.Block } as OntimeBlock,
|
||||||
duration: 10,
|
makeOntimeEvent({ id: '4', timeStart: 20, timeEnd: 30, duration: 10, linkStart: null }),
|
||||||
revision: 2,
|
makeOntimeEvent({ id: '5', timeStart: 30, timeEnd: 40, duration: 10, linkStart: '4' }),
|
||||||
delay: 0,
|
];
|
||||||
} as OntimeEvent,
|
|
||||||
{
|
|
||||||
id: '3',
|
|
||||||
type: SupportedEvent.Event,
|
|
||||||
timeStart: 10,
|
|
||||||
timeEnd: 20,
|
|
||||||
duration: 10,
|
|
||||||
revision: 2,
|
|
||||||
delay: 0,
|
|
||||||
} as OntimeEvent,
|
|
||||||
{ id: '4', type: SupportedEvent.Block } as OntimeBlock,
|
|
||||||
{
|
|
||||||
id: '5',
|
|
||||||
type: SupportedEvent.Event,
|
|
||||||
timeStart: 0,
|
|
||||||
timeEnd: 10,
|
|
||||||
duration: 10,
|
|
||||||
revision: 1,
|
|
||||||
delay: 0,
|
|
||||||
} as OntimeEvent,
|
|
||||||
];
|
|
||||||
|
|
||||||
const updatedRundown = apply(delayId, testRundown);
|
const updatedRundown = apply('delay', testRundown);
|
||||||
expect(updatedRundown).toStrictEqual(expected);
|
expect(updatedRundown).toMatchObject([
|
||||||
});
|
{ id: '1', timeStart: 0, timeEnd: 10, duration: 10, revision: 2 },
|
||||||
it('applies negative delays', () => {
|
{ id: '2', timeStart: 0, timeEnd: 10, duration: 10, revision: 2, linkStart: null },
|
||||||
const delayId = '1';
|
{ id: '3' },
|
||||||
const testRundown: OntimeRundown = [
|
{ id: '4', timeStart: 10, timeEnd: 20, duration: 10, revision: 2, linkStart: null },
|
||||||
{ id: delayId, type: SupportedEvent.Delay, duration: -10 } as OntimeDelay,
|
{ id: '5', timeStart: 20, timeEnd: 30, duration: 10, revision: 2, linkStart: '4' },
|
||||||
{
|
]);
|
||||||
id: '2',
|
});
|
||||||
type: SupportedEvent.Event,
|
|
||||||
timeStart: 0,
|
|
||||||
timeEnd: 10,
|
|
||||||
duration: 10,
|
|
||||||
revision: 1,
|
|
||||||
delay: -10,
|
|
||||||
} as OntimeEvent,
|
|
||||||
{
|
|
||||||
id: '3',
|
|
||||||
type: SupportedEvent.Event,
|
|
||||||
timeStart: 20,
|
|
||||||
timeEnd: 40,
|
|
||||||
duration: 20,
|
|
||||||
revision: 1,
|
|
||||||
delay: -10,
|
|
||||||
} as OntimeEvent,
|
|
||||||
{ id: '4', type: SupportedEvent.Block } as OntimeBlock,
|
|
||||||
{
|
|
||||||
id: '5',
|
|
||||||
type: SupportedEvent.Event,
|
|
||||||
timeStart: 0,
|
|
||||||
timeEnd: 10,
|
|
||||||
duration: 10,
|
|
||||||
revision: 1,
|
|
||||||
delay: 0,
|
|
||||||
} as OntimeEvent,
|
|
||||||
];
|
|
||||||
|
|
||||||
const expected = [
|
it('should account for minimum duration and start when applying negative delays', () => {
|
||||||
{
|
const testRundown: OntimeRundown = [
|
||||||
id: '2',
|
makeOntimeDelay(-50),
|
||||||
type: SupportedEvent.Event,
|
makeOntimeEvent({ id: '1', timeStart: 0, timeEnd: 100, duration: 100 }),
|
||||||
timeStart: 0,
|
makeOntimeEvent({ id: '2', timeStart: 100, timeEnd: 150, duration: 50, linkStart: '1' }),
|
||||||
timeEnd: 10,
|
];
|
||||||
duration: 10,
|
|
||||||
revision: 2,
|
|
||||||
delay: 0,
|
|
||||||
} as OntimeEvent,
|
|
||||||
{
|
|
||||||
id: '3',
|
|
||||||
type: SupportedEvent.Event,
|
|
||||||
timeStart: 10,
|
|
||||||
timeEnd: 30,
|
|
||||||
duration: 20,
|
|
||||||
revision: 2,
|
|
||||||
delay: 0,
|
|
||||||
} as OntimeEvent,
|
|
||||||
{ id: '4', type: SupportedEvent.Block } as OntimeBlock,
|
|
||||||
{
|
|
||||||
id: '5',
|
|
||||||
type: SupportedEvent.Event,
|
|
||||||
timeStart: 0,
|
|
||||||
timeEnd: 10,
|
|
||||||
duration: 10,
|
|
||||||
revision: 1,
|
|
||||||
delay: 0,
|
|
||||||
} as OntimeEvent,
|
|
||||||
];
|
|
||||||
|
|
||||||
const updatedRundown = apply(delayId, testRundown);
|
const expected = [
|
||||||
expect(updatedRundown).toStrictEqual(expected);
|
{ id: '1', type: SupportedEvent.Event, timeStart: 0, timeEnd: 100, duration: 100, revision: 2 } as OntimeEvent,
|
||||||
});
|
{
|
||||||
it('maintains constant duration', () => {
|
id: '2',
|
||||||
const delayId = '1';
|
type: SupportedEvent.Event,
|
||||||
const testRundown: OntimeRundown = [
|
timeStart: 50,
|
||||||
{ id: delayId, type: SupportedEvent.Delay, duration: -30 } as OntimeDelay,
|
timeEnd: 100,
|
||||||
{
|
duration: 50,
|
||||||
id: '2',
|
linkStart: null,
|
||||||
type: SupportedEvent.Event,
|
revision: 2,
|
||||||
timeStart: 0,
|
} as OntimeEvent,
|
||||||
timeEnd: 10,
|
];
|
||||||
duration: 10,
|
|
||||||
revision: 1,
|
|
||||||
delay: -30,
|
|
||||||
} as OntimeEvent,
|
|
||||||
{
|
|
||||||
id: '3',
|
|
||||||
type: SupportedEvent.Event,
|
|
||||||
timeStart: 20,
|
|
||||||
timeEnd: 40,
|
|
||||||
duration: 20,
|
|
||||||
revision: 1,
|
|
||||||
delay: -30,
|
|
||||||
} as OntimeEvent,
|
|
||||||
];
|
|
||||||
|
|
||||||
const expected = [
|
const updatedRundown = apply('delay', testRundown);
|
||||||
{
|
expect(updatedRundown).toMatchObject(expected);
|
||||||
id: '2',
|
});
|
||||||
type: SupportedEvent.Event,
|
|
||||||
timeStart: 0,
|
|
||||||
timeEnd: 10,
|
|
||||||
duration: 10,
|
|
||||||
revision: 2,
|
|
||||||
delay: 0,
|
|
||||||
} as OntimeEvent,
|
|
||||||
{
|
|
||||||
id: '3',
|
|
||||||
type: SupportedEvent.Event,
|
|
||||||
timeStart: 0,
|
|
||||||
timeEnd: 20,
|
|
||||||
duration: 20,
|
|
||||||
revision: 2,
|
|
||||||
delay: 0,
|
|
||||||
} as OntimeEvent,
|
|
||||||
];
|
|
||||||
|
|
||||||
const updatedRundown = apply(delayId, testRundown);
|
it('unlinks events to maintain gaps when applying positive delays', () => {
|
||||||
expect(updatedRundown).toStrictEqual(expected);
|
const testRundown = [
|
||||||
});
|
makeOntimeEvent({ id: '1', timeStart: 0, timeEnd: 100, duration: 100, revision: 1 }),
|
||||||
|
makeOntimeDelay(50),
|
||||||
|
makeOntimeEvent({ id: '2', timeStart: 100, timeEnd: 150, duration: 50, revision: 1, linkStart: '1' }),
|
||||||
|
];
|
||||||
|
|
||||||
|
expect(apply('delay', testRundown)).toMatchObject([
|
||||||
|
{ id: '1', type: SupportedEvent.Event, timeStart: 0, timeEnd: 100, duration: 100, revision: 1 } as OntimeEvent,
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
type: SupportedEvent.Event,
|
||||||
|
timeStart: 150,
|
||||||
|
timeEnd: 200,
|
||||||
|
duration: 50,
|
||||||
|
linkStart: null,
|
||||||
|
revision: 2,
|
||||||
|
} as OntimeEvent,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('maintains links if there is no gap', () => {
|
||||||
|
const testRundown = [
|
||||||
|
makeOntimeDelay(50),
|
||||||
|
makeOntimeEvent({ id: '1', timeStart: 0, timeEnd: 100, duration: 100, revision: 1 }),
|
||||||
|
makeOntimeEvent({ id: '2', timeStart: 100, timeEnd: 150, duration: 50, revision: 1, linkStart: '1' }),
|
||||||
|
];
|
||||||
|
|
||||||
|
expect(apply('delay', testRundown)).toMatchObject([
|
||||||
|
{ id: '1', type: SupportedEvent.Event, timeStart: 50, timeEnd: 150, duration: 100, revision: 2 } as OntimeEvent,
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
type: SupportedEvent.Event,
|
||||||
|
timeStart: 150,
|
||||||
|
timeEnd: 200,
|
||||||
|
duration: 50,
|
||||||
|
linkStart: '1',
|
||||||
|
revision: 2,
|
||||||
|
} as OntimeEvent,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('unlinks events to maintain gaps when applying negative delays', () => {
|
||||||
|
const testRundown = [
|
||||||
|
makeOntimeEvent({ id: '1', timeStart: 0, timeEnd: 100, duration: 100, revision: 1 }),
|
||||||
|
makeOntimeDelay(-50),
|
||||||
|
makeOntimeEvent({ id: '2', timeStart: 100, timeEnd: 150, duration: 50, revision: 1, linkStart: '1' }),
|
||||||
|
];
|
||||||
|
|
||||||
|
expect(apply('delay', testRundown)).toMatchObject([
|
||||||
|
{ id: '1', type: SupportedEvent.Event, timeStart: 0, timeEnd: 100, duration: 100, revision: 1 } as OntimeEvent,
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
type: SupportedEvent.Event,
|
||||||
|
timeStart: 50,
|
||||||
|
timeEnd: 100,
|
||||||
|
duration: 50,
|
||||||
|
linkStart: null,
|
||||||
|
revision: 2,
|
||||||
|
} as OntimeEvent,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('gaps reduce positive delay', () => {
|
||||||
|
const testRundown: OntimeRundown = [
|
||||||
|
makeOntimeDelay(100),
|
||||||
|
makeOntimeEvent({ id: '1', timeStart: 0, timeEnd: 100, duration: 100 }),
|
||||||
|
// gap 50
|
||||||
|
makeOntimeEvent({ id: '2', timeStart: 150, timeEnd: 200, duration: 50 }),
|
||||||
|
// gap 50
|
||||||
|
makeOntimeEvent({ id: '3', timeStart: 200, timeEnd: 250, duration: 50 }),
|
||||||
|
// gap 50
|
||||||
|
makeOntimeEvent({ id: '4', timeStart: 300, timeEnd: 350, duration: 50 }),
|
||||||
|
// linked
|
||||||
|
makeOntimeEvent({ id: '5', timeStart: 350, timeEnd: 400, duration: 50, linkStart: '4' }),
|
||||||
|
];
|
||||||
|
|
||||||
|
const updatedRundown = apply('delay', testRundown);
|
||||||
|
expect(updatedRundown).toMatchObject([
|
||||||
|
{ id: '1', timeStart: 0 + 100, timeEnd: 100 + 100, duration: 100, revision: 2 },
|
||||||
|
// gap 50 (100 - 50)
|
||||||
|
{ id: '2', timeStart: 150 + 50, timeEnd: 200 + 50, duration: 50, revision: 2 },
|
||||||
|
// gap 50 (50 - 50)
|
||||||
|
{ id: '3', timeStart: 200 + 50, timeEnd: 250 + 50, duration: 50, revision: 2 },
|
||||||
|
// gap (delay is 0)
|
||||||
|
{ id: '4', timeStart: 300, timeEnd: 350, duration: 50, revision: 1 },
|
||||||
|
// linked
|
||||||
|
{ id: '5', timeStart: 350, timeEnd: 400, duration: 50, revision: 1, linkStart: '4' },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('gaps reduce positive delay (2)', () => {
|
||||||
|
const testRundown: OntimeRundown = [
|
||||||
|
makeOntimeDelay(2 * MILLIS_PER_HOUR),
|
||||||
|
makeOntimeEvent({
|
||||||
|
id: '1',
|
||||||
|
timeStart: 46800000, // 13:00:00
|
||||||
|
timeEnd: 50400000, // 14:00:00
|
||||||
|
duration: MILLIS_PER_HOUR,
|
||||||
|
}),
|
||||||
|
// gap 1h
|
||||||
|
makeOntimeEvent({
|
||||||
|
id: '2',
|
||||||
|
timeStart: 54000000, // 15:00:00
|
||||||
|
timeEnd: 57600000, // 16:00:00
|
||||||
|
duration: MILLIS_PER_HOUR,
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
const updatedRundown = apply('delay', testRundown);
|
||||||
|
expect(updatedRundown).toMatchObject([
|
||||||
|
{ id: '1', timeStart: 54000000 /* 16 */, revision: 2 },
|
||||||
|
// gap 1h (2h - 1h)
|
||||||
|
{ id: '2', timeStart: 57600000 /* 16 */, revision: 2 },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('removes empty delays without applying changes', () => {
|
||||||
|
const testRundown: OntimeRundown = [
|
||||||
|
makeOntimeDelay(0),
|
||||||
|
makeOntimeEvent({ id: '1', timeStart: 0, timeEnd: 100, duration: 100 }),
|
||||||
|
];
|
||||||
|
|
||||||
|
const updatedRundown = apply('delay', testRundown);
|
||||||
|
expect(updatedRundown).toMatchObject([{ id: '1', timeStart: 0, timeEnd: 100, duration: 100 }]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('removes delays in last position without applying changes', () => {
|
||||||
|
const testRundown: OntimeRundown = [
|
||||||
|
makeOntimeEvent({ id: '1', timeStart: 0, timeEnd: 100, duration: 100 }),
|
||||||
|
makeOntimeDelay(100),
|
||||||
|
];
|
||||||
|
|
||||||
|
const updatedRundown = apply('delay', testRundown);
|
||||||
|
expect(updatedRundown).toMatchObject([{ id: '1', timeStart: 0, timeEnd: 100, duration: 100 }]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('unlinks events to across blocks is it is the first event after the delay', () => {
|
||||||
|
const testRundown = [
|
||||||
|
makeOntimeEvent({ id: '1', timeStart: 0, timeEnd: 100, duration: 100, revision: 1 }),
|
||||||
|
makeOntimeDelay(50),
|
||||||
|
{ id: 'block', type: SupportedEvent.Block } as OntimeBlock,
|
||||||
|
makeOntimeEvent({ id: '2', timeStart: 100, timeEnd: 150, duration: 50, revision: 1, linkStart: '1' }),
|
||||||
|
];
|
||||||
|
expect(apply('delay', testRundown)).toMatchObject([
|
||||||
|
{ id: '1', type: SupportedEvent.Event, timeStart: 0, timeEnd: 100, duration: 100, revision: 1 } as OntimeEvent,
|
||||||
|
{ id: 'block', type: SupportedEvent.Block },
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
type: SupportedEvent.Event,
|
||||||
|
timeStart: 150,
|
||||||
|
timeEnd: 200,
|
||||||
|
duration: 50,
|
||||||
|
linkStart: null,
|
||||||
|
revision: 2,
|
||||||
|
} as OntimeEvent,
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { OntimeRundown, isOntimeDelay, isOntimeBlock, isOntimeEvent } from 'ontime-types';
|
import { OntimeRundown, isOntimeDelay, isOntimeBlock, isOntimeEvent, OntimeEvent } from 'ontime-types';
|
||||||
|
import { getTimeFromPrevious, deleteAtIndex } from 'ontime-utils';
|
||||||
import { deleteAtIndex } from '../../../../../packages/utils/src/array-utils/arrayUtils.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates all delays in a given rundown
|
* Calculates all delays in a given rundown
|
||||||
@@ -92,10 +91,7 @@ export function getDelayAt(eventIndex: number, rundown: OntimeRundown): number {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies delay from given event ID, deletes the delay event after
|
* Applies delay from given event ID, deletes the delay event after
|
||||||
* @param eventId
|
|
||||||
* @param rundown
|
|
||||||
* @throws {Error} if event ID not found or is not a delay
|
* @throws {Error} if event ID not found or is not a delay
|
||||||
* @returns
|
|
||||||
*/
|
*/
|
||||||
export function apply(eventId: string, rundown: OntimeRundown): OntimeRundown {
|
export function apply(eventId: string, rundown: OntimeRundown): OntimeRundown {
|
||||||
const delayIndex = rundown.findIndex((event) => event.id === eventId);
|
const delayIndex = rundown.findIndex((event) => event.id === eventId);
|
||||||
@@ -109,27 +105,71 @@ export function apply(eventId: string, rundown: OntimeRundown): OntimeRundown {
|
|||||||
throw new Error('Given event ID is not a delay');
|
throw new Error('Given event ID is not a delay');
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedRundown = [...rundown];
|
// if the delay is empty, or the last element, we can just delete it
|
||||||
const delayValue = delayEvent.duration;
|
if (delayEvent.duration === 0 || delayIndex === rundown.length - 1) {
|
||||||
|
return deleteAtIndex(delayIndex, rundown);
|
||||||
if (delayValue === 0 || delayIndex === rundown.length - 1) {
|
|
||||||
// nothing to apply
|
|
||||||
return updatedRundown;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = delayIndex + 1; i < rundown.length; i++) {
|
/**
|
||||||
const currentEvent = updatedRundown[i];
|
* We apply the delay to the rundown
|
||||||
|
* This logic is mostly in sync with rundownCache.generate
|
||||||
|
*/
|
||||||
|
const updatedRundown = structuredClone(rundown);
|
||||||
|
let delayValue = delayEvent.duration;
|
||||||
|
let lastEntry: OntimeEvent | null = null;
|
||||||
|
let isFirstEvent = true;
|
||||||
|
|
||||||
if (isOntimeBlock(currentEvent)) {
|
for (let i = delayIndex + 1; i < updatedRundown.length; i++) {
|
||||||
break;
|
const currentEntry = updatedRundown[i];
|
||||||
} else if (isOntimeEvent(currentEvent)) {
|
|
||||||
currentEvent.timeStart = Math.max(0, currentEvent.timeStart + delayValue);
|
// we don't do operation on other event types
|
||||||
currentEvent.timeEnd = Math.max(currentEvent.duration, currentEvent.timeEnd + delayValue);
|
if (!isOntimeEvent(currentEntry)) {
|
||||||
if (currentEvent.delay) {
|
continue;
|
||||||
currentEvent.delay = currentEvent.delay - delayValue;
|
|
||||||
}
|
|
||||||
currentEvent.revision += 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// we need to remove the link in the first event to maintain the gap
|
||||||
|
let shouldUnlink = isFirstEvent;
|
||||||
|
isFirstEvent = false;
|
||||||
|
|
||||||
|
// if the event is not linked, we try and maintain gaps
|
||||||
|
if (lastEntry !== null) {
|
||||||
|
const timeFromPrevious: number = getTimeFromPrevious(
|
||||||
|
currentEntry.timeStart,
|
||||||
|
lastEntry.timeStart,
|
||||||
|
lastEntry.timeEnd,
|
||||||
|
lastEntry.duration,
|
||||||
|
);
|
||||||
|
|
||||||
|
// when applying negative delays, we need to unlink the event
|
||||||
|
// if the previous event was fully consumed by the delay
|
||||||
|
if (currentEntry.linkStart && delayValue < 0 && lastEntry.timeStart + delayValue < 0) {
|
||||||
|
shouldUnlink = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timeFromPrevious > 0) {
|
||||||
|
delayValue = Math.max(delayValue - timeFromPrevious, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (delayValue === 0) {
|
||||||
|
// we can bail from continuing if there are no further delays to apply
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// save the current entry before making mutations on its values
|
||||||
|
lastEntry = { ...currentEntry };
|
||||||
|
|
||||||
|
if (shouldUnlink) {
|
||||||
|
currentEntry.linkStart = null;
|
||||||
|
shouldUnlink = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// event times move up by the delay value
|
||||||
|
// we dont update the delay value since we would need to iterate through the entire dataset
|
||||||
|
// this is handled by the rundownCache.generate function
|
||||||
|
currentEntry.timeStart = Math.max(0, currentEntry.timeStart + delayValue);
|
||||||
|
currentEntry.timeEnd = Math.max(currentEntry.duration, currentEntry.timeEnd + delayValue);
|
||||||
|
currentEntry.revision += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return deleteAtIndex(delayIndex, updatedRundown);
|
return deleteAtIndex(delayIndex, updatedRundown);
|
||||||
|
|||||||
Reference in New Issue
Block a user