mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-13 11:23:50 +00:00
Revert "Session version" (#1649)
* Revert "refactor: add version to session endpoint" This reverts commitcd40a6e55e. * Revert "Remove public event feature (#1645)" This reverts commit08d9e24871. * Revert "Fix: rearrange playing event (#1640)" This reverts commit0649678dca. * Revert "refactor: style tweaks to edit css modal" This reverts commita00ec2d02a. * Revert "refactor: remove usages of framer-motion" This reverts commit54a74ccc2a. * Revert "Upgrade expressjs (#1633)" This reverts commit6f3ab274bd. * Revert "Refactor: require trigger in all events objects (#1636)" This reverts commit90870ecfb6. * Revert "Fix: Correct boundary condition in applyDelay" This reverts commitb640e0e181. * Revert "let vite be the proxy to the dev server (#1630)" This reverts commitc41fe824cf. * Revert "refactor: migrate custom fields to transactions" This reverts commit62c8319d70. * Revert "refactor: simplify validations" This reverts commitb1d23467a2. * Revert "refactor: create transaction system and apply to adding entry (#1620)" This reverts commit9a62daf047. * Revert "Refactor: better rounding (#1594)" This reverts commitb9ab1c6fd7. * Revert "refactor: improve reorder logic" This reverts commitc1054711b0. * Revert "chore: simplify URLs" This reverts commita4d4f29a37. * Revert "refactor: order is single source of truth" This reverts commit2793aadea0. * Revert "refactor: refetch targets is enum" This reverts commite7cfb7d9d9. * Revert "refactor: small ux improvements" This reverts commit256a851c9b. * Revert "refactor: remove trivially inferred numEvents" This reverts commit4ab9c81cb8. * Revert "feat: duplicate groups" This reverts commit2f13d6c89e. * Revert "feat: create group from entry selection" This reverts commitf1f7bad25e. * Revert "feat: create block from rundown empty" This reverts commita8b52a48f7. * Revert "fix: collapsed blocks dont render children" This reverts commitcd0999b2ab. * Revert "refactor: type cleanup and test improvements" This reverts commitb4c60f3f04. * Revert "feat: allow dissolving a block" This reverts commit5cefad3666. * Revert "fix: uncontrolled prop on controlled component" This reverts commit7a6ecd8c34. * Revert "refactor: improve return of reorder" This reverts commitba96ecfd91. * Revert "refactor: mutations on batch elements must have IDs" This reverts commit7bed3757f2. * Revert "chore: upgrade dependencies" This reverts commite2e755b1d2. * Revert "refactor: extract utility to merge two arrays" This reverts commita77d23109d. * Revert "refactor: change network mode defaults" This reverts commit0eb3b8d382. * Revert "fix: delete nested events" This reverts commit0021185288. * Revert "fix: add event at end of block" This reverts commit94c72ff4f6. * Revert "refactor: make finder available in exported rundown" This reverts commite4c08dc9b2. * Revert "assert non null and update test (#1604)" This reverts commitec74af0d62. * Revert "Fix project renumber (#1597)" This reverts commitb6d72dd082. * Revert "Refactor: WebSocket from flush queue to one patch (#1595)" This reverts commit31c311daf0. * Revert "Refactor: ms for api calls (#1593)" This reverts commite9b3cc6090. * Revert "fix test (#1601)" This reverts commit543b04a097. * Revert "fix: rebase master" This reverts commitd39b85b6e6. * Revert "chore: correct test path" This reverts commitbbe107bb2b. * Revert "refactor: extract rundown parsing" This reverts commitc616240db1. * Revert "chore: improve convention entry <> event" This reverts commit166be66ce3. * Revert "refactor: maintain flat orders" This reverts commit4180d0a337. * Revert "refactor: implement operations on nested events" This reverts commit78108e316c. * Revert "refactor: process events in rundown" This reverts commit3bb8b70915. * Revert "chore: improve convention entry <> event" This reverts commit1c4f13a0ed. * Revert "chore: rename currentBlock > parent" This reverts commit3ca0abad53. * Revert "refactor: fix delay positioning in gaps" This reverts commit030c8f897f. * Revert "refactor(e2e): skip flaky test" This reverts commit68175cfa3b. * Revert "refactor: improve project loading" This reverts commitfd8f757851. * Revert "refactor: gather group metadata" This reverts commit876d111c61. * Revert "refactor: swap maintains schedule" This reverts commit730cb95c04. * Revert "chore: rename files" This reverts commit3c388d4fb5. * Revert "refactor: restructure model to contain an object of rundowns" This reverts commit2e23718d73. * Revert "refactor: clearer relationship on rundown elements" This reverts commit89ea8c470b. * Revert "refactor: use strict typing" This reverts commit178640bfc4. * Revert "refactor: remove stop as a possible end action" This reverts commit4ed38340e0. * Revert "refactor: restructure model to contain an object of rundowns" This reverts commit69eb9a5eff. * Revert "chore: remove IDE files" This reverts commit351425127a. * Revert "refactor: remove unused and legacy code" This reverts commit95f2ba37cc.
This commit is contained in:
committed by
GitHub
parent
ad6804019b
commit
722e045b20
@@ -1,4 +1,4 @@
|
||||
import { deleteAtIndex, insertAtIndex, mergeAtIndex, reorderArray } from './arrayUtils.js';
|
||||
import { deleteAtIndex, insertAtIndex, reorderArray } from './arrayUtils.js';
|
||||
|
||||
describe('insertAtIndex', () => {
|
||||
it('should insert an item at the beginning of the array', () => {
|
||||
@@ -23,34 +23,7 @@ describe('insertAtIndex', () => {
|
||||
const array = [1, 2, 3];
|
||||
const result = insertAtIndex(1, 5, array);
|
||||
expect(result).toEqual([1, 5, 2, 3]);
|
||||
expect(array).toEqual([1, 2, 3]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('mergeAtIndex', () => {
|
||||
it('should insert an item at the beginning of the array', () => {
|
||||
const array = ['1', '2', '3'];
|
||||
const result = mergeAtIndex(0, ['a', 'b'], array);
|
||||
expect(result).toEqual(['a', 'b', '1', '2', '3']);
|
||||
});
|
||||
|
||||
it('should insert an item at the end of the array', () => {
|
||||
const array = ['1', '2', '3'];
|
||||
const result = mergeAtIndex(3, ['a', 'b'], array);
|
||||
expect(result).toEqual(['1', '2', '3', 'a', 'b']);
|
||||
});
|
||||
|
||||
it('should insert an item in the middle of the array', () => {
|
||||
const array = ['1', '2', '3'];
|
||||
const result = mergeAtIndex(2, ['a', 'b'], array);
|
||||
expect(result).toEqual(['1', '2', 'a', 'b', '3']);
|
||||
});
|
||||
|
||||
it('should return a new array and not modify the original array', () => {
|
||||
const array = ['1', '2', '3'];
|
||||
const result = mergeAtIndex(5, ['a', 'b'], array);
|
||||
expect(result).toEqual(['1', '2', '3', 'a', 'b']);
|
||||
expect(array).toEqual(['1', '2', '3']);
|
||||
expect(array).toEqual([1, 2, 3]); // Original array should remain unchanged
|
||||
});
|
||||
});
|
||||
|
||||
@@ -88,18 +61,12 @@ describe('deleteAtIndex', () => {
|
||||
});
|
||||
|
||||
describe('reorderArray', () => {
|
||||
it('should reorder an item in the array (up)', () => {
|
||||
it('should reorder an item in the array', () => {
|
||||
const array = ['a', 'b', 'c', 'd'];
|
||||
const result = reorderArray(array, 1, 3);
|
||||
expect(result).toEqual(['a', 'c', 'd', 'b']);
|
||||
});
|
||||
|
||||
it('should reorder an item in the array (down)', () => {
|
||||
const array = ['a', 'b', 'c', 'd'];
|
||||
const result = reorderArray(array, 3, 1);
|
||||
expect(result).toEqual(['a', 'd', 'b', 'c']);
|
||||
});
|
||||
|
||||
it('should return the original array if fromIndex and toIndex are the same', () => {
|
||||
const array = ['a', 'b', 'c'];
|
||||
const result = reorderArray(array, 1, 1);
|
||||
|
||||
@@ -25,24 +25,6 @@ export function insertAtIndex<T>(index: number, item: T, array: T[]): T[] {
|
||||
return modifiedArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts an array into another one of the same type at a given index
|
||||
*/
|
||||
export function mergeAtIndex<T>(index: number, newArray: T[], currentArray: T[]): T[] {
|
||||
// Insert at beginning
|
||||
if (index === 0) {
|
||||
return [...newArray, ...currentArray];
|
||||
}
|
||||
|
||||
// insert at end
|
||||
else if (index >= currentArray.length) {
|
||||
return [...currentArray, ...newArray];
|
||||
}
|
||||
|
||||
// insert in the middle
|
||||
return currentArray.toSpliced(index, 0, ...newArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes array element at a given index
|
||||
* @param index
|
||||
@@ -54,8 +36,11 @@ export function deleteAtIndex<T>(index: number, array: T[]) {
|
||||
|
||||
/**
|
||||
* Reorders two objects in an array
|
||||
* @param array
|
||||
* @param fromIndex
|
||||
* @param toIndex
|
||||
*/
|
||||
export function reorderArray<T>(array: T[], fromIndex: number, toIndex: number): T[] {
|
||||
export function reorderArray<T>(array: T[], fromIndex: number, toIndex: number) {
|
||||
if (fromIndex === toIndex) {
|
||||
return array; // No change needed, return the original array
|
||||
}
|
||||
|
||||
@@ -16,7 +16,3 @@ export function getPropertyFromPath<T extends object>(path: string, obj: T): unk
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function isObjectEmpty(obj: object): boolean {
|
||||
return Object.keys(obj).length === 0;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { OntimeDelay, OntimeEntry, OntimeEvent, RundownEntries } from 'ontime-types';
|
||||
import { SupportedEntry } from 'ontime-types';
|
||||
import type { OntimeDelay, OntimeEvent, OntimeRundown } from 'ontime-types';
|
||||
import { SupportedEvent } from 'ontime-types';
|
||||
|
||||
import { getCueCandidate, getIncrement, sanitiseCue } from './cueUtils.js';
|
||||
|
||||
@@ -36,83 +36,76 @@ describe('getIncrement()', () => {
|
||||
describe('getCueCandidate()', () => {
|
||||
describe('in the beginning of the rundown', () => {
|
||||
it('names cue as 1 if next event does not collide', () => {
|
||||
const entries: RundownEntries = {
|
||||
'1': { id: '1', cue: '10', type: SupportedEntry.Event } as OntimeEvent,
|
||||
'2': { id: '2', cue: '11', type: SupportedEntry.Event } as OntimeEvent,
|
||||
};
|
||||
const cue = getCueCandidate(entries, ['1', '2'], null);
|
||||
const testRundown = [
|
||||
{ id: '1', cue: '10', type: SupportedEvent.Event },
|
||||
{ id: '2', cue: '11', type: SupportedEvent.Event },
|
||||
] as OntimeRundown;
|
||||
const cue = getCueCandidate(testRundown);
|
||||
expect(cue).toBe('1');
|
||||
});
|
||||
|
||||
it('creates decimal stem if next cue is 1', () => {
|
||||
const entries: RundownEntries = {
|
||||
'1': { id: '1', cue: '1', type: SupportedEntry.Event } as OntimeEvent,
|
||||
'2': { id: '2', cue: '10', type: SupportedEntry.Event } as OntimeEvent,
|
||||
};
|
||||
const cue = getCueCandidate(entries, ['1', '2'], null);
|
||||
const testRundown = [
|
||||
{ id: '1', cue: '1', type: SupportedEvent.Event },
|
||||
{ id: '2', cue: '10', type: SupportedEvent.Event },
|
||||
] as OntimeRundown;
|
||||
const cue = getCueCandidate(testRundown);
|
||||
expect(cue).toBe('0.1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('in the middle of the rundown', () => {
|
||||
it('names cue as an increment if next event has different stem (case of numbers)', () => {
|
||||
const entries: RundownEntries = {
|
||||
'1': { id: '1', cue: '1', type: SupportedEntry.Event } as OntimeEvent,
|
||||
'2': { id: '2', cue: '10', type: SupportedEntry.Event } as OntimeEvent,
|
||||
};
|
||||
const cue = getCueCandidate(entries, ['1', '2'], '1');
|
||||
const testRundown = [
|
||||
{ id: '1', cue: '1', type: SupportedEvent.Event },
|
||||
{ id: '2', cue: '10', type: SupportedEvent.Event },
|
||||
] as OntimeRundown;
|
||||
const cue = getCueCandidate(testRundown, '1');
|
||||
expect(cue).toBe('2');
|
||||
});
|
||||
|
||||
it('names cue as an increment if next event has different stem (case of letters)', () => {
|
||||
const entries: RundownEntries = {
|
||||
'1': { id: '1', cue: 'Presenter', type: SupportedEntry.Event } as OntimeEvent,
|
||||
'2': {
|
||||
const testRundown = [
|
||||
{ id: '1', cue: 'Presenter', type: SupportedEvent.Event },
|
||||
{
|
||||
id: '2',
|
||||
cue: 'Interval',
|
||||
type: SupportedEntry.Event,
|
||||
} as OntimeEntry,
|
||||
};
|
||||
const cue = getCueCandidate(entries, ['1', '2'], '1');
|
||||
type: SupportedEvent.Event,
|
||||
},
|
||||
] as OntimeRundown;
|
||||
const cue = getCueCandidate(testRundown, '1');
|
||||
expect(cue).toBe('Presenter2');
|
||||
});
|
||||
|
||||
it('creates decimal stem if next cue has same stem (case of numbers)', () => {
|
||||
const entries: RundownEntries = {
|
||||
'1': { id: '1', cue: '1', type: SupportedEntry.Event } as OntimeEvent,
|
||||
'2': { id: '2', cue: '2', type: SupportedEntry.Event } as OntimeEvent,
|
||||
};
|
||||
const cue = getCueCandidate(entries, ['1', '2'], '1');
|
||||
const testRundown = [
|
||||
{ id: '1', cue: '1', type: SupportedEvent.Event },
|
||||
{ id: '2', cue: '2', type: SupportedEvent.Event },
|
||||
] as OntimeRundown;
|
||||
const cue = getCueCandidate(testRundown, '1');
|
||||
expect(cue).toBe('1.1');
|
||||
});
|
||||
|
||||
it('creates decimal stem if next cue has same stem (case of letters)', () => {
|
||||
const entries: RundownEntries = {
|
||||
'1': { id: '1', cue: 'Presenter1', type: SupportedEntry.Event } as OntimeEvent,
|
||||
'2': { id: '2', cue: 'Presenter2', type: SupportedEntry.Event } as OntimeEvent,
|
||||
};
|
||||
const cue = getCueCandidate(entries, ['1', '2'], '1');
|
||||
const testRundown = [
|
||||
{ id: '1', cue: 'Presenter1', type: SupportedEvent.Event },
|
||||
{ id: '2', cue: 'Presenter2', type: SupportedEvent.Event },
|
||||
] as OntimeRundown;
|
||||
const cue = getCueCandidate(testRundown, '1');
|
||||
expect(cue).toBe('Presenter1.1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('considers edge cases', () => {
|
||||
it('previousEvent might not be a cue', () => {
|
||||
const entries: RundownEntries = {
|
||||
'1': { id: '1', cue: '10', type: SupportedEntry.Event } as OntimeEvent,
|
||||
'2': { id: '2', type: SupportedEntry.Delay } as OntimeDelay,
|
||||
};
|
||||
const cue = getCueCandidate(entries, ['1', '2'], '2');
|
||||
const testRundown = [
|
||||
{ id: '1', cue: '10', type: SupportedEvent.Event } as OntimeEvent,
|
||||
{ id: '2', type: SupportedEvent.Delay } as OntimeDelay,
|
||||
];
|
||||
const cue = getCueCandidate(testRundown, '2');
|
||||
expect(cue).toBe('11');
|
||||
});
|
||||
});
|
||||
|
||||
it('there might not be events before', () => {
|
||||
const entries: RundownEntries = {
|
||||
'1': { id: '1', type: SupportedEntry.Delay } as OntimeDelay,
|
||||
'2': { id: '2', type: SupportedEntry.Delay } as OntimeDelay,
|
||||
};
|
||||
const cue = getCueCandidate(entries, ['1', '2'], '2');
|
||||
const testRundown = [
|
||||
{ id: '1', type: SupportedEvent.Delay } as OntimeDelay,
|
||||
{ id: '2', type: SupportedEvent.Delay } as OntimeDelay,
|
||||
];
|
||||
const cue = getCueCandidate(testRundown, '2');
|
||||
expect(cue).toBe('1');
|
||||
});
|
||||
});
|
||||
@@ -120,58 +113,53 @@ describe('getCueCandidate()', () => {
|
||||
describe('findCueName() with mixed events', () => {
|
||||
describe('in the beginning of the rundown', () => {
|
||||
it('names cue as 1 if next event does not collide', () => {
|
||||
const entries: RundownEntries = {
|
||||
'1': { id: '1', cue: '10', type: SupportedEntry.Event } as OntimeEvent,
|
||||
'2': { id: '2', cue: '11', type: SupportedEntry.Event } as OntimeEvent,
|
||||
};
|
||||
const cue = getCueCandidate(entries, ['1', '2'], null);
|
||||
const testRundown = [
|
||||
{ id: '1', cue: '10', type: SupportedEvent.Event },
|
||||
{ id: '2', cue: '11', type: SupportedEvent.Event },
|
||||
] as OntimeRundown;
|
||||
const cue = getCueCandidate(testRundown);
|
||||
expect(cue).toBe('1');
|
||||
});
|
||||
|
||||
it('creates decimal stem if next cue is 1', () => {
|
||||
const entries: RundownEntries = {
|
||||
'1': { id: '1', cue: '1', type: SupportedEntry.Event } as OntimeEvent,
|
||||
'2': { id: '2', cue: '10', type: SupportedEntry.Event } as OntimeEvent,
|
||||
};
|
||||
const cue = getCueCandidate(entries, ['1', '2'], null);
|
||||
const testRundown = [
|
||||
{ id: '1', cue: '1', type: SupportedEvent.Event },
|
||||
{ id: '2', cue: '10', type: SupportedEvent.Event },
|
||||
] as OntimeRundown;
|
||||
const cue = getCueCandidate(testRundown);
|
||||
expect(cue).toBe('0.1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('in the middle of the rundown', () => {
|
||||
it('names cue as an increment if next event has different stem (case of numbers)', () => {
|
||||
const entries: RundownEntries = {
|
||||
'1': { id: '1', cue: '1', type: SupportedEntry.Event } as OntimeEvent,
|
||||
'2': { id: '2', cue: '10', type: SupportedEntry.Event } as OntimeEvent,
|
||||
};
|
||||
const cue = getCueCandidate(entries, ['1', '2'], '1');
|
||||
const testRundown = [
|
||||
{ id: '1', cue: '1', type: SupportedEvent.Event },
|
||||
{ id: '2', cue: '10', type: SupportedEvent.Event },
|
||||
] as OntimeRundown;
|
||||
const cue = getCueCandidate(testRundown, '1');
|
||||
expect(cue).toBe('2');
|
||||
});
|
||||
|
||||
it('names cue as an increment if next event has different stem (case of letters)', () => {
|
||||
const entries: RundownEntries = {
|
||||
'1': { id: '1', cue: 'Presenter', type: SupportedEntry.Event } as OntimeEvent,
|
||||
'2': { id: '2', cue: 'Interval', type: SupportedEntry.Event } as OntimeEvent,
|
||||
};
|
||||
const cue = getCueCandidate(entries, ['1', '2'], '1');
|
||||
const testRundown = [
|
||||
{ id: '1', cue: 'Presenter', type: SupportedEvent.Event },
|
||||
{ id: '2', cue: 'Interval', type: SupportedEvent.Event },
|
||||
] as OntimeRundown;
|
||||
const cue = getCueCandidate(testRundown, '1');
|
||||
expect(cue).toBe('Presenter2');
|
||||
});
|
||||
|
||||
it('creates decimal stem if next cue has same stem (case of numbers)', () => {
|
||||
const entries: RundownEntries = {
|
||||
'1': { id: '1', cue: '1', type: SupportedEntry.Event } as OntimeEvent,
|
||||
'2': { id: '2', cue: '2', type: SupportedEntry.Event } as OntimeEvent,
|
||||
};
|
||||
const cue = getCueCandidate(entries, ['1', '2'], '1');
|
||||
const testRundown = [
|
||||
{ id: '1', cue: '1', type: SupportedEvent.Event },
|
||||
{ id: '2', cue: '2', type: SupportedEvent.Event },
|
||||
] as OntimeRundown;
|
||||
const cue = getCueCandidate(testRundown, '1');
|
||||
expect(cue).toBe('1.1');
|
||||
});
|
||||
|
||||
it('creates decimal stem if next cue has same stem (case of letters)', () => {
|
||||
const entries: RundownEntries = {
|
||||
'1': { id: '1', cue: 'Presenter1', type: SupportedEntry.Event } as OntimeEvent,
|
||||
'2': { id: '2', cue: 'Presenter2', type: SupportedEntry.Event } as OntimeEvent,
|
||||
};
|
||||
const cue = getCueCandidate(entries, ['1', '2'], '1');
|
||||
const testRundown = [
|
||||
{ id: '1', cue: 'Presenter1', type: SupportedEvent.Event },
|
||||
{ id: '2', cue: 'Presenter2', type: SupportedEvent.Event },
|
||||
] as OntimeRundown;
|
||||
const cue = getCueCandidate(testRundown, '1');
|
||||
expect(cue).toBe('Presenter1.1');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { EntryId, OntimeEntry, RundownEntries } from 'ontime-types';
|
||||
import type { OntimeEvent, OntimeRundown, OntimeRundownEntry } from 'ontime-types';
|
||||
import { isOntimeEvent } from 'ontime-types';
|
||||
|
||||
import { getFirstEventNormal, getNextEventNormal, getPreviousEventNormal } from '../rundown-utils/rundownUtils.js';
|
||||
import { getFirstEvent, getNextEvent, getPreviousEvent } from '../rundown-utils/rundownUtils.js';
|
||||
import { isNumeric } from '../types/types.js';
|
||||
|
||||
// Zero or more non-digit characters at the beginning ((\D*)).
|
||||
@@ -11,6 +11,7 @@ const regex = /^(\D*)(\d+)(\.\d+)?$/;
|
||||
|
||||
/**
|
||||
* Finds if last characters in input are a number and increments
|
||||
* @param input {string}
|
||||
*/
|
||||
export function getIncrement(input: string): string {
|
||||
// Check if the input string contains a number at the end
|
||||
@@ -40,57 +41,55 @@ export function getIncrement(input: string): string {
|
||||
|
||||
/**
|
||||
* Gets suitable name for a new event cue
|
||||
* @param rundown {OntimeRundown}
|
||||
* @param insertAfterId {string}
|
||||
*/
|
||||
export function getCueCandidate(entries: RundownEntries, order: EntryId[], insertAfterId: EntryId | null): string {
|
||||
// we did not provide a element to go after, we attempt to go first so only need to check for a cue with value 1
|
||||
if (insertAfterId === null || order.length === 0) {
|
||||
return addAtTop();
|
||||
}
|
||||
|
||||
// get the given event, or any before that
|
||||
let previousEvent: OntimeEntry | null | undefined = entries[insertAfterId];
|
||||
|
||||
if (!isOntimeEvent(previousEvent)) {
|
||||
previousEvent = getPreviousEventNormal(entries, order, insertAfterId).previousEvent;
|
||||
if (!isOntimeEvent(previousEvent)) {
|
||||
return addAtTop();
|
||||
}
|
||||
}
|
||||
|
||||
// the cue is based on the previous event cue
|
||||
const cue = getIncrement(previousEvent.cue);
|
||||
const { nextEvent } = getNextEventNormal(entries, order, insertAfterId);
|
||||
|
||||
// if increment is clashing with next, we add a decimal instead
|
||||
if (cue !== nextEvent?.cue) {
|
||||
return cue;
|
||||
}
|
||||
|
||||
// there is a clash, bt the cue is a pure number
|
||||
if (isNumeric(cue)) {
|
||||
return incrementDecimal(previousEvent.cue);
|
||||
}
|
||||
|
||||
/**
|
||||
* at this point, we know the cue is not numeric
|
||||
* but the increment failed, so we have a numeric ending
|
||||
* eg. Presenter 1 .... Presenter 2 -> Presenter1.1
|
||||
* eg. Presenter 1.1 .... Presenter 1.2 -> Presenter1.1.1
|
||||
*/
|
||||
return `${previousEvent.cue}.1`;
|
||||
|
||||
function incrementDecimal(cue: string) {
|
||||
const n = Number(cue);
|
||||
return (n + 0.1).toString();
|
||||
}
|
||||
|
||||
export function getCueCandidate(rundown: OntimeRundown, insertAfterId?: string): string {
|
||||
function addAtTop() {
|
||||
const firstEventCue = getFirstEventNormal(entries, order).firstEvent?.cue;
|
||||
if (firstEventCue === '1') {
|
||||
return '0.1';
|
||||
const firstEventCue = getFirstEvent(rundown).firstEvent?.cue;
|
||||
|
||||
if (isNumeric(firstEventCue)) {
|
||||
return (Number(firstEventCue) / 10).toString();
|
||||
}
|
||||
return '1';
|
||||
}
|
||||
|
||||
// we did not provide a element to go after, we attempt to go first so only need to check for a cue with value 1
|
||||
if (typeof insertAfterId === 'undefined' || rundown.length === 0) {
|
||||
return addAtTop();
|
||||
}
|
||||
|
||||
const afterIndex = rundown.findIndex((event) => event.id === insertAfterId);
|
||||
|
||||
// we did not find the previous element, insert at top
|
||||
if (afterIndex === -1) {
|
||||
return addAtTop();
|
||||
}
|
||||
|
||||
// get elements around
|
||||
let previousEvent: OntimeRundownEntry | undefined | null | OntimeEvent = rundown.at(afterIndex);
|
||||
if (!isOntimeEvent(previousEvent)) {
|
||||
previousEvent = getPreviousEvent(rundown, insertAfterId).previousEvent as null | OntimeEvent;
|
||||
}
|
||||
|
||||
let cue = '1';
|
||||
const { nextEvent } = getNextEvent(rundown, insertAfterId);
|
||||
|
||||
// try and increment the cue
|
||||
if (isOntimeEvent(previousEvent)) {
|
||||
cue = getIncrement(previousEvent.cue);
|
||||
}
|
||||
|
||||
// if increment is clashing with next, we add a decimal instead
|
||||
if (cue === nextEvent?.cue) {
|
||||
if (previousEvent === null) {
|
||||
cue = '0.1';
|
||||
} else {
|
||||
cue = `${previousEvent.cue}.1`;
|
||||
}
|
||||
}
|
||||
|
||||
return cue;
|
||||
}
|
||||
|
||||
export function sanitiseCue(cue: string) {
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import type { CustomFields } from 'ontime-types';
|
||||
|
||||
import { isAlphanumericWithSpace } from '../regex-utils/isAlphanumeric.js';
|
||||
|
||||
/**
|
||||
* @description Transforms a Custom field label into a valid key or returns null if not possible
|
||||
*/
|
||||
export const customFieldLabelToKey = (label: string): string | null => {
|
||||
if (isAlphanumericWithSpace(label)) {
|
||||
return label.trim().replaceAll(' ', '_');
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export const customKeyFromLabel = (label: string, fields: CustomFields): string | null => {
|
||||
const maybeMatchingKey = Object.keys(fields).find((key) => fields[key].label === label);
|
||||
if (maybeMatchingKey) {
|
||||
return maybeMatchingKey;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
@@ -1,19 +0,0 @@
|
||||
import type { CustomFields } from 'ontime-types';
|
||||
|
||||
/**
|
||||
* Transforms an alphanumeric label with spaces into a valid key
|
||||
*/
|
||||
export function customFieldLabelToKey(label: string): string {
|
||||
return label.trim().replaceAll(' ', '_');
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds an object key in the CustomFields object that matches the given label
|
||||
*/
|
||||
export function customKeyFromLabel(label: string, fields: CustomFields): string | null {
|
||||
const maybeMatchingKey = Object.keys(fields).find((key) => fields[key].label === label);
|
||||
if (maybeMatchingKey) {
|
||||
return maybeMatchingKey;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import { MILLIS_PER_HOUR } from './conversionUtils';
|
||||
describe('checkIsNextDay', () => {
|
||||
it('returns false if there is no previous event', () => {
|
||||
const current = { timeStart: 0, dayOffset: 0 };
|
||||
const previous = null;
|
||||
const previous = undefined;
|
||||
expect(checkIsNextDay(current, previous)).toBeFalsy();
|
||||
});
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import { dayInMs } from './conversionUtils.js';
|
||||
*/
|
||||
export function checkIsNextDay(
|
||||
current: Pick<OntimeEvent, 'timeStart' | 'dayOffset'>,
|
||||
previous: Pick<OntimeEvent, 'timeStart' | 'duration' | 'dayOffset'> | null,
|
||||
previous?: Pick<OntimeEvent, 'timeStart' | 'duration' | 'dayOffset'>,
|
||||
): boolean {
|
||||
if (!previous) {
|
||||
return false;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { millisToSeconds, secondsInMillis } from './conversionUtils';
|
||||
import { millisToHours, millisToMinutes, millisToSeconds, secondsInMillis } from './conversionUtils';
|
||||
|
||||
describe('millisToSecond()', () => {
|
||||
test('null values', () => {
|
||||
@@ -37,6 +37,82 @@ describe('millisToSecond()', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('millisToMinutes()', () => {
|
||||
test('null values', () => {
|
||||
const t = { val: null, result: 0 };
|
||||
expect(millisToMinutes(t.val)).toBe(t.result);
|
||||
});
|
||||
|
||||
test('valid millis', () => {
|
||||
const t = { val: 3600000, result: 60 };
|
||||
expect(millisToMinutes(t.val)).toBe(t.result);
|
||||
});
|
||||
|
||||
test('negative millis', () => {
|
||||
const t = { val: -3600000, result: -60 };
|
||||
expect(millisToMinutes(t.val)).toBe(t.result);
|
||||
});
|
||||
|
||||
test('0', () => {
|
||||
const t = { val: 0, result: 0 };
|
||||
expect(millisToMinutes(t.val)).toBe(t.result);
|
||||
});
|
||||
|
||||
test('-0', () => {
|
||||
const t = { val: -0, result: 0 };
|
||||
expect(millisToMinutes(t.val)).toBe(t.result);
|
||||
});
|
||||
|
||||
test('86401000 (24 hours and 1 second)', () => {
|
||||
const t = { val: 86401000, result: 1440 };
|
||||
expect(millisToMinutes(t.val)).toBe(t.result);
|
||||
});
|
||||
|
||||
test('-86401000 (-24 hours and 1 second)', () => {
|
||||
// negative numbers are rounded up
|
||||
const t = { val: -86401000, result: -1441 };
|
||||
expect(millisToMinutes(t.val)).toBe(t.result);
|
||||
});
|
||||
});
|
||||
|
||||
describe('millisToHours()', () => {
|
||||
test('null values', () => {
|
||||
const t = { val: null, result: 0 };
|
||||
expect(millisToHours(t.val)).toBe(t.result);
|
||||
});
|
||||
|
||||
test('valid millis', () => {
|
||||
const t = { val: 3600000, result: 1 };
|
||||
expect(millisToHours(t.val)).toBe(t.result);
|
||||
});
|
||||
|
||||
test('negative millis', () => {
|
||||
const t = { val: -3600000, result: -1 };
|
||||
expect(millisToHours(t.val)).toBe(t.result);
|
||||
});
|
||||
|
||||
test('0', () => {
|
||||
const t = { val: 0, result: 0 };
|
||||
expect(millisToHours(t.val)).toBe(t.result);
|
||||
});
|
||||
|
||||
test('-0', () => {
|
||||
const t = { val: -0, result: 0 };
|
||||
expect(millisToHours(t.val)).toBe(t.result);
|
||||
});
|
||||
|
||||
test('86401000 (24 hours and 1 second)', () => {
|
||||
const t = { val: 86401000, result: 24 };
|
||||
expect(millisToHours(t.val)).toBe(t.result);
|
||||
});
|
||||
|
||||
test('-86401000 (-24 hours and 1 second)', () => {
|
||||
// negative numbers are rounded up
|
||||
const t = { val: -86401000, result: -25 };
|
||||
expect(millisToHours(t.val)).toBe(t.result);
|
||||
});
|
||||
});
|
||||
|
||||
describe('secondsInMillis()', () => {
|
||||
it('return 0 if value is null', () => {
|
||||
expect(secondsInMillis(null)).toBe(0);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { MaybeNumber } from 'ontime-types';
|
||||
import { TimerType } from 'ontime-types';
|
||||
|
||||
export const MILLIS_PER_SECOND = 1000;
|
||||
export const MILLIS_PER_MINUTE = 1000 * 60;
|
||||
@@ -8,28 +7,44 @@ export const MILLIS_PER_HOUR = 1000 * 60 * 60;
|
||||
export const dayInMs = 86400000;
|
||||
export const maxDuration = dayInMs - MILLIS_PER_SECOND;
|
||||
|
||||
/**
|
||||
* Utility converts milliseconds to a specific unit
|
||||
* @param millis
|
||||
* @param conversion
|
||||
* @returns
|
||||
*/
|
||||
function convertMillis(millis: MaybeNumber, conversion: number): number {
|
||||
if (!millis) {
|
||||
return 0;
|
||||
}
|
||||
return Math.floor(millis / conversion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts value in milliseconds to seconds
|
||||
* @param millis
|
||||
* @returns
|
||||
*/
|
||||
export function millisToSeconds(
|
||||
millis: MaybeNumber,
|
||||
direction: TimerType.CountDown | TimerType.CountUp = TimerType.CountDown,
|
||||
) {
|
||||
if (millis === null) return 0;
|
||||
export function millisToSeconds(millis: MaybeNumber): number {
|
||||
return convertMillis(millis, MILLIS_PER_SECOND);
|
||||
}
|
||||
|
||||
let seconds = 0;
|
||||
if (direction === TimerType.CountDown) {
|
||||
seconds = Math.ceil(millis / MILLIS_PER_SECOND);
|
||||
}
|
||||
/**
|
||||
* Converts value in milliseconds to minutes
|
||||
* @param millis
|
||||
* @returns
|
||||
*/
|
||||
export function millisToMinutes(millis: MaybeNumber): number {
|
||||
return convertMillis(millis, MILLIS_PER_MINUTE);
|
||||
}
|
||||
|
||||
if (direction === TimerType.CountUp) {
|
||||
seconds = Math.floor(millis / MILLIS_PER_SECOND);
|
||||
}
|
||||
|
||||
// this is there to avoid result giving -0
|
||||
return seconds === 0 ? 0 : seconds;
|
||||
/**
|
||||
* Converts value in milliseconds to hours
|
||||
* @param millis
|
||||
* @returns
|
||||
*/
|
||||
export function millisToHours(millis: MaybeNumber): number {
|
||||
return convertMillis(millis, MILLIS_PER_HOUR);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+12
-8
@@ -1,11 +1,11 @@
|
||||
import { dayInMs, MILLIS_PER_HOUR, MILLIS_PER_MINUTE } from './conversionUtils';
|
||||
import { getTimeFrom } from './getTimeFrom';
|
||||
import { getTimeFromPrevious } from './getTimeFromPrevious';
|
||||
|
||||
describe('getTimeFrom', () => {
|
||||
describe('getTimeFromPrevious', () => {
|
||||
it('returns the time elapsed (gap or overlap) from the previous', () => {
|
||||
const expected = 75600000 - 71700000; // current start - previousEnd
|
||||
expect(
|
||||
getTimeFrom(
|
||||
getTimeFromPrevious(
|
||||
{ timeStart: 21 * MILLIS_PER_HOUR, dayOffset: 0 },
|
||||
{ timeStart: 19 * MILLIS_PER_HOUR + 20 * MILLIS_PER_MINUTE, duration: 35 * MILLIS_PER_MINUTE, dayOffset: 0 },
|
||||
),
|
||||
@@ -14,18 +14,22 @@ describe('getTimeFrom', () => {
|
||||
|
||||
it('accounts for partially overlapping events', () => {
|
||||
const expected = -1;
|
||||
expect(getTimeFrom({ timeStart: 11, dayOffset: 0 }, { timeStart: 10, duration: 2, dayOffset: 0 })).toBe(expected);
|
||||
expect(getTimeFromPrevious({ timeStart: 11, dayOffset: 0 }, { timeStart: 10, duration: 2, dayOffset: 0 })).toBe(
|
||||
expected,
|
||||
);
|
||||
});
|
||||
|
||||
it('accounts for events that are fully contained', () => {
|
||||
const expected = -6;
|
||||
expect(getTimeFrom({ timeStart: 10, dayOffset: 0 }, { timeStart: 8, duration: 8, dayOffset: 0 })).toBe(expected);
|
||||
expect(getTimeFromPrevious({ timeStart: 10, dayOffset: 0 }, { timeStart: 8, duration: 8, dayOffset: 0 })).toBe(
|
||||
expected,
|
||||
);
|
||||
});
|
||||
|
||||
it('fully overlapping events are the next day', () => {
|
||||
const expected = dayInMs - 2 * MILLIS_PER_HOUR;
|
||||
expect(
|
||||
getTimeFrom(
|
||||
getTimeFromPrevious(
|
||||
{ timeStart: 10 * MILLIS_PER_HOUR, dayOffset: 1 },
|
||||
{ timeStart: 10 * MILLIS_PER_HOUR, duration: 2 * MILLIS_PER_HOUR, dayOffset: 0 },
|
||||
),
|
||||
@@ -35,7 +39,7 @@ describe('getTimeFrom', () => {
|
||||
it('accounts for events that are the day after', () => {
|
||||
const expected = -MILLIS_PER_HOUR; // (previousEnd - currentStart);
|
||||
expect(
|
||||
getTimeFrom(
|
||||
getTimeFromPrevious(
|
||||
{ timeStart: 22 * MILLIS_PER_HOUR, dayOffset: 0 },
|
||||
{ timeStart: 20 * MILLIS_PER_HOUR, duration: 3 * MILLIS_PER_HOUR, dayOffset: 0 },
|
||||
),
|
||||
@@ -45,7 +49,7 @@ describe('getTimeFrom', () => {
|
||||
it('accounts for events that cross midnight', () => {
|
||||
const expected = -MILLIS_PER_HOUR; // (previousEnd - currentStart);
|
||||
expect(
|
||||
getTimeFrom(
|
||||
getTimeFromPrevious(
|
||||
{ timeStart: 1 * MILLIS_PER_HOUR, dayOffset: 1 },
|
||||
{ timeStart: 20 * MILLIS_PER_HOUR, duration: 6 * MILLIS_PER_HOUR, dayOffset: 0 },
|
||||
),
|
||||
+3
-3
@@ -3,11 +3,11 @@ import type { OntimeEvent } from 'ontime-types';
|
||||
import { dayInMs } from './conversionUtils.js';
|
||||
|
||||
/**
|
||||
* Utility returns the gap from a previous given event
|
||||
* Utility returns the gap from previous event
|
||||
*/
|
||||
export function getTimeFrom(
|
||||
export function getTimeFromPrevious(
|
||||
current: Pick<OntimeEvent, 'timeStart' | 'dayOffset'>,
|
||||
previous: Pick<OntimeEvent, 'timeStart' | 'duration' | 'dayOffset'> | null,
|
||||
previous?: Pick<OntimeEvent, 'timeStart' | 'duration' | 'dayOffset'>,
|
||||
): number {
|
||||
// there is no previous event
|
||||
if (!previous) {
|
||||
@@ -4,7 +4,7 @@ import { isNewLatest } from './isNewLatest';
|
||||
describe('isNewLatest', () => {
|
||||
it('should be true if there is no previous', () => {
|
||||
const current = { timeStart: 0, duration: MILLIS_PER_HOUR, dayOffset: 0 };
|
||||
const previous = null;
|
||||
const previous = undefined;
|
||||
expect(isNewLatest(current, previous)).toBe(true);
|
||||
});
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import { dayInMs } from './conversionUtils.js';
|
||||
*/
|
||||
export function isNewLatest(
|
||||
currentEvent: Pick<OntimeEvent, 'timeStart' | 'duration' | 'dayOffset'>,
|
||||
previousEvent: Pick<OntimeEvent, 'timeStart' | 'duration' | 'dayOffset'> | null,
|
||||
previousEvent?: Pick<OntimeEvent, 'timeStart' | 'duration' | 'dayOffset'>,
|
||||
) {
|
||||
// true if there is no previous
|
||||
if (!previousEvent) {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { TimerType } from 'ontime-types';
|
||||
|
||||
import { dayInMs, MILLIS_PER_HOUR } from './conversionUtils';
|
||||
import { formatFromMillis, millisToString, removeLeadingZero } from './timeFormatting';
|
||||
|
||||
@@ -15,33 +13,32 @@ describe('millisToString()', () => {
|
||||
|
||||
test('negative times are rounded up', () => {
|
||||
const testScenarios = [
|
||||
{ millis: 300, expected_down: '00:00:01', expected_up: '00:00:00' },
|
||||
{ millis: -300, expected_down: '-00:00:00', expected_up: '-00:00:01' },
|
||||
{ millis: 1000, expected_down: '00:00:01', expected_up: '00:00:01' },
|
||||
{ millis: -1000, expected_down: '-00:00:01', expected_up: '-00:00:01' },
|
||||
{ millis: 1500, expected_down: '00:00:02', expected_up: '00:00:01' },
|
||||
{ millis: -1500, expected_down: '-00:00:01', expected_up: '-00:00:02' },
|
||||
{ millis: 60000 - 1, expected_down: '00:01:00', expected_up: '00:00:59' },
|
||||
{ millis: -(60000 - 1), expected_down: '-00:00:59', expected_up: '-00:01:00' },
|
||||
{ millis: 60000, expected_down: '00:01:00', expected_up: '00:01:00' },
|
||||
{ millis: -60000, expected_down: '-00:01:00', expected_up: '-00:01:00' },
|
||||
{ millis: 600000, expected_down: '00:10:00', expected_up: '00:10:00' },
|
||||
{ millis: -600000, expected_down: '-00:10:00', expected_up: '-00:10:00' },
|
||||
{ millis: 3600000, expected_down: '01:00:00', expected_up: '01:00:00' },
|
||||
{ millis: -3600000, expected_down: '-01:00:00', expected_up: '-01:00:00' },
|
||||
{ millis: 36000000, expected_down: '10:00:00', expected_up: '10:00:00' },
|
||||
{ millis: -36000000, expected_down: '-10:00:00', expected_up: '-10:00:00' },
|
||||
{ millis: 86399000, expected_down: '23:59:59', expected_up: '23:59:59' },
|
||||
{ millis: -86399000, expected_down: '-23:59:59', expected_up: '-23:59:59' },
|
||||
{ millis: 86400000, expected_down: '24:00:00', expected_up: '24:00:00' },
|
||||
{ millis: -86400000, expected_down: '-24:00:00', expected_up: '-24:00:00' },
|
||||
{ millis: 86401000, expected_down: '24:00:01', expected_up: '24:00:01' },
|
||||
{ millis: -86401000, expected_down: '-24:00:01', expected_up: '-24:00:01' },
|
||||
{ millis: 300, expected: '00:00:00' },
|
||||
{ millis: -300, expected: '-00:00:01' },
|
||||
{ millis: 1000, expected: '00:00:01' },
|
||||
{ millis: -1000, expected: '-00:00:01' },
|
||||
{ millis: 1500, expected: '00:00:01' },
|
||||
{ millis: -1500, expected: '-00:00:02' },
|
||||
{ millis: 60000 - 1, expected: '00:00:59' },
|
||||
{ millis: -(60000 - 1), expected: '-00:01:00' },
|
||||
{ millis: 60000, expected: '00:01:00' },
|
||||
{ millis: -60000, expected: '-00:01:00' },
|
||||
{ millis: 600000, expected: '00:10:00' },
|
||||
{ millis: -600000, expected: '-00:10:00' },
|
||||
{ millis: 3600000, expected: '01:00:00' },
|
||||
{ millis: -3600000, expected: '-01:00:00' },
|
||||
{ millis: 36000000, expected: '10:00:00' },
|
||||
{ millis: -36000000, expected: '-10:00:00' },
|
||||
{ millis: 86399000, expected: '23:59:59' },
|
||||
{ millis: -86399000, expected: '-23:59:59' },
|
||||
{ millis: 86400000, expected: '24:00:00' },
|
||||
{ millis: -86400000, expected: '-24:00:00' },
|
||||
{ millis: 86401000, expected: '24:00:01' },
|
||||
{ millis: -86401000, expected: '-24:00:01' },
|
||||
];
|
||||
|
||||
testScenarios.forEach((scenario) => {
|
||||
expect(millisToString(scenario.millis, { direction: TimerType.CountDown })).toBe(scenario.expected_down);
|
||||
expect(millisToString(scenario.millis, { direction: TimerType.CountUp })).toBe(scenario.expected_up);
|
||||
expect(millisToString(scenario.millis)).toBe(scenario.expected);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -51,21 +48,20 @@ describe('millisToString()', () => {
|
||||
|
||||
test('random properties', () => {
|
||||
const testScenarios = [
|
||||
{ millis: 300, expected_down: '00:00:01', expected_up: '00:00:00' },
|
||||
{ millis: 1000, expected_down: '00:00:01', expected_up: '00:00:01' },
|
||||
{ millis: 1500, expected_down: '00:00:02', expected_up: '00:00:01' },
|
||||
{ millis: 60000, expected_down: '00:01:00', expected_up: '00:01:00' },
|
||||
{ millis: 600000, expected_down: '00:10:00', expected_up: '00:10:00' },
|
||||
{ millis: 3600000, expected_down: '01:00:00', expected_up: '01:00:00' },
|
||||
{ millis: 36000000, expected_down: '10:00:00', expected_up: '10:00:00' },
|
||||
{ millis: 86399000, expected_down: '23:59:59', expected_up: '23:59:59' },
|
||||
{ millis: 86400000, expected_down: '24:00:00', expected_up: '24:00:00' },
|
||||
{ millis: 86401000, expected_down: '24:00:01', expected_up: '24:00:01' },
|
||||
{ millis: 300, expected: '00:00:00' },
|
||||
{ millis: 1000, expected: '00:00:01' },
|
||||
{ millis: 1500, expected: '00:00:01' },
|
||||
{ millis: 60000, expected: '00:01:00' },
|
||||
{ millis: 600000, expected: '00:10:00' },
|
||||
{ millis: 3600000, expected: '01:00:00' },
|
||||
{ millis: 36000000, expected: '10:00:00' },
|
||||
{ millis: 86399000, expected: '23:59:59' },
|
||||
{ millis: 86400000, expected: '24:00:00' },
|
||||
{ millis: 86401000, expected: '24:00:01' },
|
||||
];
|
||||
|
||||
testScenarios.forEach((scenario) => {
|
||||
expect(millisToString(scenario.millis, { direction: TimerType.CountDown })).toBe(scenario.expected_down);
|
||||
expect(millisToString(scenario.millis, { direction: TimerType.CountUp })).toBe(scenario.expected_up);
|
||||
expect(millisToString(scenario.millis)).toBe(scenario.expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { MaybeNumber, TimerType } from 'ontime-types';
|
||||
import type { MaybeNumber } from 'ontime-types';
|
||||
|
||||
import { millisToSeconds, secondsToHours, secondsToMinutes } from './conversionUtils.js';
|
||||
|
||||
@@ -8,7 +8,6 @@ export function pad(val: number): string {
|
||||
|
||||
type FormatOptions = {
|
||||
fallback?: string;
|
||||
direction?: TimerType.CountDown | TimerType.CountUp;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -24,7 +23,7 @@ export function millisToString(millis?: MaybeNumber, options?: FormatOptions): s
|
||||
|
||||
const isNegative = millis < 0;
|
||||
|
||||
const totalSeconds = Math.abs(millisToSeconds(millis, options?.direction));
|
||||
const totalSeconds = Math.abs(millisToSeconds(millis));
|
||||
const seconds = totalSeconds % 60;
|
||||
const minutes = secondsToMinutes(totalSeconds) % 60;
|
||||
const hours = secondsToHours(totalSeconds);
|
||||
|
||||
@@ -12,6 +12,7 @@ describe('isImportMap()', () => {
|
||||
cue: 'cue',
|
||||
title: 'title',
|
||||
countToEnd: 'count to end',
|
||||
isPublic: 'public',
|
||||
skip: 'skip',
|
||||
note: 'notes',
|
||||
colour: 'colour',
|
||||
@@ -36,6 +37,7 @@ describe('isImportMap()', () => {
|
||||
cue: 'cue',
|
||||
title: 'title',
|
||||
countToEnd: 'count to end',
|
||||
isPublic: 'public',
|
||||
skip: 'skip',
|
||||
note: 'notes',
|
||||
colour: 'colour',
|
||||
@@ -59,6 +61,7 @@ describe('isImportMap()', () => {
|
||||
cue: 'cue',
|
||||
title: 'title',
|
||||
countToEnd: 'count to end',
|
||||
isPublic: 'public',
|
||||
skip: 'skip',
|
||||
note: 'notes',
|
||||
colour: 'colour',
|
||||
|
||||
@@ -12,6 +12,7 @@ export const defaultImportMap = {
|
||||
cue: 'cue',
|
||||
title: 'title',
|
||||
countToEnd: 'count to end',
|
||||
isPublic: 'public',
|
||||
skip: 'skip',
|
||||
note: 'notes',
|
||||
colour: 'colour',
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type { OntimeBlock, OntimeDelay, OntimeEntry, OntimeEvent } from 'ontime-types';
|
||||
import { SupportedEntry } from 'ontime-types';
|
||||
import type { NormalisedRundown, OntimeEvent, OntimeRundown } from 'ontime-types';
|
||||
import { SupportedEvent } from 'ontime-types';
|
||||
|
||||
import {
|
||||
filterPlayable,
|
||||
getLastEvent,
|
||||
getLastNormal,
|
||||
getNext,
|
||||
@@ -14,46 +15,36 @@ import {
|
||||
|
||||
describe('getNext()', () => {
|
||||
it('returns the next event of type event', () => {
|
||||
const testRundown = {
|
||||
entries: {
|
||||
'1': { id: '1', type: SupportedEntry.Event } as OntimeEvent,
|
||||
'2': { id: '2', type: SupportedEntry.Event } as OntimeEvent,
|
||||
'3': { id: '3', type: SupportedEntry.Event } as OntimeEvent,
|
||||
},
|
||||
order: ['1', '2', '3'],
|
||||
};
|
||||
const testRundown = [
|
||||
{ id: '1', type: SupportedEvent.Event },
|
||||
{ id: '2', type: SupportedEvent.Event },
|
||||
{ id: '3', type: SupportedEvent.Event },
|
||||
];
|
||||
|
||||
const { nextEvent, nextIndex } = getNext(testRundown, '1');
|
||||
const { nextEvent, nextIndex } = getNext(testRundown as OntimeRundown, '1');
|
||||
expect(nextEvent?.id).toBe('2');
|
||||
expect(nextIndex).toBe(1);
|
||||
});
|
||||
it('alows other event types', () => {
|
||||
const testRundown = [
|
||||
{ id: '1', type: SupportedEvent.Event },
|
||||
{ id: '2', type: SupportedEvent.Delay },
|
||||
{ id: '3', type: SupportedEvent.Block },
|
||||
{ id: '4', type: SupportedEvent.Event },
|
||||
];
|
||||
|
||||
it('returns any type of OntimeEntry ', () => {
|
||||
const testRundown = {
|
||||
entries: {
|
||||
'1': { id: '1', type: SupportedEntry.Event } as OntimeEvent,
|
||||
'2': { id: '2', type: SupportedEntry.Delay } as OntimeDelay,
|
||||
'3': { id: '3', type: SupportedEntry.Block } as OntimeBlock,
|
||||
'4': { id: '4', type: SupportedEntry.Event } as OntimeEvent,
|
||||
},
|
||||
order: ['1', '2', '3', '4'],
|
||||
};
|
||||
|
||||
const { nextEvent, nextIndex } = getNext(testRundown, '1');
|
||||
const { nextEvent, nextIndex } = getNext(testRundown as OntimeRundown, '1');
|
||||
expect(nextEvent?.id).toBe('2');
|
||||
expect(nextIndex).toBe(1);
|
||||
});
|
||||
|
||||
it('returns null if none found', () => {
|
||||
const testRundown = {
|
||||
entries: {
|
||||
'1': { id: '1', type: SupportedEntry.Event } as OntimeEvent,
|
||||
'2': { id: '2', type: SupportedEntry.Event } as OntimeEvent,
|
||||
'3': { id: '3', type: SupportedEntry.Event } as OntimeEvent,
|
||||
},
|
||||
order: ['1', '2', '3'],
|
||||
};
|
||||
const { nextEvent, nextIndex } = getNext(testRundown, '3');
|
||||
const testRundown = [
|
||||
{ id: '1', type: SupportedEvent.Event },
|
||||
{ id: '2', type: SupportedEvent.Delay },
|
||||
{ id: '3', type: SupportedEvent.Block },
|
||||
];
|
||||
|
||||
const { nextEvent, nextIndex } = getNext(testRundown as OntimeRundown, '3');
|
||||
expect(nextEvent).toBe(null);
|
||||
expect(nextIndex).toBe(null);
|
||||
});
|
||||
@@ -62,37 +53,35 @@ describe('getNext()', () => {
|
||||
describe('getNextEvent()', () => {
|
||||
it('returns the next event of type event', () => {
|
||||
const testRundown = [
|
||||
{ id: '1', type: SupportedEntry.Event } as OntimeEvent,
|
||||
{ id: '2', type: SupportedEntry.Event } as OntimeEvent,
|
||||
{ id: '3', type: SupportedEntry.Event } as OntimeEvent,
|
||||
{ id: '1', type: SupportedEvent.Event },
|
||||
{ id: '2', type: SupportedEvent.Event },
|
||||
{ id: '3', type: SupportedEvent.Event },
|
||||
];
|
||||
|
||||
const { nextEvent, nextIndex } = getNextEvent(testRundown, '1');
|
||||
const { nextEvent, nextIndex } = getNextEvent(testRundown as OntimeRundown, '1');
|
||||
expect(nextEvent?.id).toBe('2');
|
||||
expect(nextIndex).toBe(1);
|
||||
});
|
||||
|
||||
it('ignores other event types', () => {
|
||||
const testRundown = [
|
||||
{ id: '1', type: SupportedEntry.Event } as OntimeEvent,
|
||||
{ id: '2', type: SupportedEntry.Delay } as OntimeDelay,
|
||||
{ id: '3', type: SupportedEntry.Block } as OntimeBlock,
|
||||
{ id: '4', type: SupportedEntry.Event } as OntimeEvent,
|
||||
{ id: '1', type: SupportedEvent.Event },
|
||||
{ id: '2', type: SupportedEvent.Delay },
|
||||
{ id: '3', type: SupportedEvent.Block },
|
||||
{ id: '4', type: SupportedEvent.Event },
|
||||
];
|
||||
|
||||
const { nextEvent, nextIndex } = getNextEvent(testRundown, '1');
|
||||
const { nextEvent, nextIndex } = getNextEvent(testRundown as OntimeRundown, '1');
|
||||
expect(nextEvent?.id).toBe('4');
|
||||
expect(nextIndex).toBe(3);
|
||||
});
|
||||
|
||||
it('returns null if none found', () => {
|
||||
const testRundown = [
|
||||
{ id: '1', type: SupportedEntry.Event } as OntimeEvent,
|
||||
{ id: '2', type: SupportedEntry.Delay } as OntimeDelay,
|
||||
{ id: '3', type: SupportedEntry.Block } as OntimeBlock,
|
||||
{ id: '1', type: SupportedEvent.Event },
|
||||
{ id: '2', type: SupportedEvent.Delay },
|
||||
{ id: '3', type: SupportedEvent.Block },
|
||||
];
|
||||
|
||||
const { nextEvent, nextIndex } = getNextEvent(testRundown, '1');
|
||||
const { nextEvent, nextIndex } = getNextEvent(testRundown as OntimeRundown, '1');
|
||||
expect(nextEvent).toBe(null);
|
||||
expect(nextIndex).toBe(null);
|
||||
});
|
||||
@@ -100,46 +89,36 @@ describe('getNextEvent()', () => {
|
||||
|
||||
describe('getPrevious()', () => {
|
||||
it('returns the previous event of type event', () => {
|
||||
const testRundown = {
|
||||
entries: {
|
||||
'1': { id: '1', type: SupportedEntry.Event } as OntimeEvent,
|
||||
'2': { id: '2', type: SupportedEntry.Event } as OntimeEvent,
|
||||
'3': { id: '3', type: SupportedEntry.Event } as OntimeEvent,
|
||||
},
|
||||
order: ['1', '2', '3'],
|
||||
};
|
||||
const testRundown = [
|
||||
{ id: '1', type: SupportedEvent.Event },
|
||||
{ id: '2', type: SupportedEvent.Event },
|
||||
{ id: '3', type: SupportedEvent.Event },
|
||||
];
|
||||
|
||||
const { entry, index } = getPrevious(testRundown, '3');
|
||||
const { entry, index } = getPrevious(testRundown as OntimeRundown, '3');
|
||||
expect(entry?.id).toBe('2');
|
||||
expect(index).toBe(1);
|
||||
});
|
||||
|
||||
it('allow other event types', () => {
|
||||
const testRundown = {
|
||||
entries: {
|
||||
'1': { id: '1', type: SupportedEntry.Event } as OntimeEvent,
|
||||
'2': { id: '2', type: SupportedEntry.Delay } as OntimeDelay,
|
||||
'3': { id: '3', type: SupportedEntry.Block } as OntimeBlock,
|
||||
'4': { id: '4', type: SupportedEntry.Event } as OntimeEvent,
|
||||
},
|
||||
order: ['1', '2', '3', '4'],
|
||||
};
|
||||
const testRundown = [
|
||||
{ id: '1', type: SupportedEvent.Event },
|
||||
{ id: '2', type: SupportedEvent.Delay },
|
||||
{ id: '3', type: SupportedEvent.Block },
|
||||
{ id: '4', type: SupportedEvent.Event },
|
||||
];
|
||||
|
||||
const { entry, index } = getPrevious(testRundown, '3');
|
||||
const { entry, index } = getPrevious(testRundown as OntimeRundown, '3');
|
||||
expect(entry?.id).toBe('2');
|
||||
expect(index).toBe(1);
|
||||
});
|
||||
|
||||
it('returns null if none found', () => {
|
||||
const testRundown = {
|
||||
entries: {
|
||||
'2': { id: '2', type: SupportedEntry.Event } as OntimeEvent,
|
||||
'3': { id: '3', type: SupportedEntry.Event } as OntimeEvent,
|
||||
},
|
||||
order: ['1', '2', '3'],
|
||||
};
|
||||
const testRundown = [
|
||||
{ id: '2', type: SupportedEvent.Delay },
|
||||
{ id: '3', type: SupportedEvent.Block },
|
||||
{ id: '4', type: SupportedEvent.Event },
|
||||
];
|
||||
|
||||
const { entry, index } = getPrevious(testRundown, '2');
|
||||
const { entry, index } = getPrevious(testRundown as OntimeRundown, '2');
|
||||
expect(entry).toBe(null);
|
||||
expect(index).toBe(null);
|
||||
});
|
||||
@@ -147,47 +126,36 @@ describe('getPrevious()', () => {
|
||||
|
||||
describe('getPreviousEvent()', () => {
|
||||
it('returns the previous event of type event', () => {
|
||||
const testRundown = {
|
||||
entries: {
|
||||
'1': { id: '1', type: SupportedEntry.Event } as OntimeEvent,
|
||||
'2': { id: '2', type: SupportedEntry.Event } as OntimeEvent,
|
||||
'3': { id: '3', type: SupportedEntry.Event } as OntimeEvent,
|
||||
},
|
||||
order: ['1', '2', '3'],
|
||||
};
|
||||
const testRundown = [
|
||||
{ id: '1', type: SupportedEvent.Event },
|
||||
{ id: '2', type: SupportedEvent.Event },
|
||||
{ id: '3', type: SupportedEvent.Event },
|
||||
];
|
||||
|
||||
const { previousEvent, previousIndex } = getPreviousEvent(testRundown, '3');
|
||||
const { previousEvent, previousIndex } = getPreviousEvent(testRundown as OntimeRundown, '3');
|
||||
expect(previousEvent?.id).toBe('2');
|
||||
expect(previousIndex).toBe(1);
|
||||
});
|
||||
|
||||
it('ignores other event types', () => {
|
||||
const testRundown = {
|
||||
entries: {
|
||||
'1': { id: '1', type: SupportedEntry.Event } as OntimeEvent,
|
||||
'2': { id: '2', type: SupportedEntry.Delay } as OntimeDelay,
|
||||
'3': { id: '3', type: SupportedEntry.Block } as OntimeBlock,
|
||||
'4': { id: '4', type: SupportedEntry.Event } as OntimeEvent,
|
||||
},
|
||||
order: ['1', '2', '3', '4'],
|
||||
};
|
||||
const testRundown = [
|
||||
{ id: '1', type: SupportedEvent.Event },
|
||||
{ id: '2', type: SupportedEvent.Delay },
|
||||
{ id: '3', type: SupportedEvent.Block },
|
||||
{ id: '4', type: SupportedEvent.Event },
|
||||
];
|
||||
|
||||
const { previousEvent, previousIndex } = getPreviousEvent(testRundown, '4');
|
||||
const { previousEvent, previousIndex } = getPreviousEvent(testRundown as OntimeRundown, '4');
|
||||
expect(previousEvent?.id).toBe('1');
|
||||
expect(previousIndex).toBe(0);
|
||||
});
|
||||
|
||||
it('returns null if none found', () => {
|
||||
const testRundown = {
|
||||
entries: {
|
||||
'2': { id: '2', type: SupportedEntry.Delay } as OntimeDelay,
|
||||
'3': { id: '3', type: SupportedEntry.Block } as OntimeBlock,
|
||||
'4': { id: '4', type: SupportedEntry.Event } as OntimeEvent,
|
||||
},
|
||||
order: ['2', '3', '4'],
|
||||
};
|
||||
const testRundown = [
|
||||
{ id: '2', type: SupportedEvent.Delay },
|
||||
{ id: '3', type: SupportedEvent.Block },
|
||||
{ id: '4', type: SupportedEvent.Event },
|
||||
];
|
||||
|
||||
const { previousEvent, previousIndex } = getPreviousEvent(testRundown, '2');
|
||||
const { previousEvent, previousIndex } = getPreviousEvent(testRundown as OntimeRundown, '2');
|
||||
expect(previousEvent).toBe(null);
|
||||
expect(previousIndex).toBe(null);
|
||||
});
|
||||
@@ -202,8 +170,6 @@ describe('swapEventData', () => {
|
||||
timeEnd: 1,
|
||||
duration: 1,
|
||||
delay: 1,
|
||||
revision: 3,
|
||||
parent: null,
|
||||
} as OntimeEvent;
|
||||
const eventB = {
|
||||
id: '2',
|
||||
@@ -212,11 +178,9 @@ describe('swapEventData', () => {
|
||||
timeEnd: 2,
|
||||
duration: 2,
|
||||
delay: 2,
|
||||
revision: 7,
|
||||
parent: 'testing',
|
||||
} as OntimeEvent;
|
||||
|
||||
const [newA, newB] = swapEventData(eventA, eventB);
|
||||
const { newA, newB } = swapEventData(eventA, eventB);
|
||||
|
||||
expect(newA).toMatchObject({
|
||||
id: '1',
|
||||
@@ -225,8 +189,6 @@ describe('swapEventData', () => {
|
||||
timeEnd: 1,
|
||||
duration: 1,
|
||||
delay: 1,
|
||||
revision: 3,
|
||||
parent: null,
|
||||
});
|
||||
expect(newB).toMatchObject({
|
||||
id: '2',
|
||||
@@ -235,120 +197,121 @@ describe('swapEventData', () => {
|
||||
timeEnd: 2,
|
||||
duration: 2,
|
||||
delay: 2,
|
||||
revision: 7,
|
||||
parent: 'testing',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLastEvent', () => {
|
||||
it('returns the last event of type event', () => {
|
||||
const testRundown: OntimeEntry[] = [
|
||||
{ id: '1', type: SupportedEntry.Event } as OntimeEvent,
|
||||
{ id: '2', type: SupportedEntry.Delay } as OntimeDelay,
|
||||
{ id: '3', type: SupportedEntry.Event } as OntimeEvent,
|
||||
{ id: '4', type: SupportedEntry.Block } as OntimeBlock,
|
||||
const testRundown = [
|
||||
{ id: '1', type: SupportedEvent.Event },
|
||||
{ id: '2', type: SupportedEvent.Delay },
|
||||
{ id: '3', type: SupportedEvent.Event },
|
||||
{ id: '4', type: SupportedEvent.Block },
|
||||
];
|
||||
|
||||
const { lastEvent } = getLastEvent(testRundown);
|
||||
const { lastEvent } = getLastEvent(testRundown as OntimeRundown);
|
||||
expect(lastEvent?.id).toBe('3');
|
||||
});
|
||||
|
||||
it('handles rundowns with a single event', () => {
|
||||
const testRundown: OntimeEntry[] = [{ id: '1', type: SupportedEntry.Event } as OntimeEvent];
|
||||
const { lastEvent } = getLastEvent(testRundown);
|
||||
const testRundown = [{ id: '1', type: SupportedEvent.Event }];
|
||||
|
||||
const { lastEvent } = getLastEvent(testRundown as OntimeRundown);
|
||||
expect(lastEvent?.id).toBe('1');
|
||||
});
|
||||
|
||||
describe('getLastNormal', () => {
|
||||
it('returns the last entry', () => {
|
||||
const entries = {
|
||||
4: { id: '4', type: SupportedEntry.Block } as OntimeBlock,
|
||||
1: { id: '1', type: SupportedEntry.Event } as OntimeEvent,
|
||||
3: { id: '3', type: SupportedEntry.Event } as OntimeEvent,
|
||||
2: { id: '2', type: SupportedEntry.Delay } as OntimeDelay,
|
||||
const testRundown = {
|
||||
4: { id: '4', type: SupportedEvent.Block },
|
||||
1: { id: '1', type: SupportedEvent.Event },
|
||||
3: { id: '3', type: SupportedEvent.Event },
|
||||
2: { id: '2', type: SupportedEvent.Delay },
|
||||
};
|
||||
|
||||
const order = ['1', '2', '3', '4'];
|
||||
|
||||
const lastEntry = getLastNormal(entries, order);
|
||||
const lastEntry = getLastNormal(testRundown as unknown as NormalisedRundown, order);
|
||||
expect(lastEntry?.id).toBe('4');
|
||||
});
|
||||
|
||||
it('handles rundowns with a single event', () => {
|
||||
const entries = {
|
||||
1: { id: '1', type: SupportedEntry.Event } as OntimeEvent,
|
||||
};
|
||||
const testRundown = [{ id: '1', type: SupportedEvent.Event }];
|
||||
|
||||
const lastEvent = getLastNormal(entries, ['1']);
|
||||
const { lastEvent } = getLastEvent(testRundown as OntimeRundown);
|
||||
expect(lastEvent?.id).toBe('1');
|
||||
});
|
||||
|
||||
it('handles empty order', () => {
|
||||
const testRundown = {
|
||||
1: { id: '1', type: SupportedEntry.Event } as OntimeEvent,
|
||||
2: { id: '2', type: SupportedEntry.Event } as OntimeEvent,
|
||||
4: { id: '4', type: SupportedEvent.Block },
|
||||
1: { id: '1', type: SupportedEvent.Event },
|
||||
3: { id: '3', type: SupportedEvent.Event },
|
||||
2: { id: '2', type: SupportedEvent.Delay },
|
||||
};
|
||||
|
||||
const lastEntry = getLastNormal(testRundown, []);
|
||||
const order: string[] = [];
|
||||
|
||||
const lastEntry = getLastNormal(testRundown as unknown as NormalisedRundown, order);
|
||||
expect(lastEntry).toBe(null);
|
||||
});
|
||||
|
||||
it('handles empty rundown', () => {
|
||||
const lastEntry = getLastNormal({}, ['1', '2', '3', '4']);
|
||||
const testRundown = {};
|
||||
|
||||
const order = ['1', '2', '3', '4'];
|
||||
|
||||
const lastEntry = getLastNormal(testRundown as unknown as NormalisedRundown, order);
|
||||
expect(lastEntry).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPreviousBlock()', () => {
|
||||
const testRundown = {
|
||||
entries: {
|
||||
a: { id: 'a', type: SupportedEntry.Event } as OntimeEvent,
|
||||
b: { id: 'b', type: SupportedEntry.Event } as OntimeEvent,
|
||||
c: { id: 'c', type: SupportedEntry.Event } as OntimeEvent,
|
||||
d: { id: 'd', type: SupportedEntry.Delay } as OntimeDelay,
|
||||
e: { id: 'e', type: SupportedEntry.Block } as OntimeBlock,
|
||||
f: { id: 'f', type: SupportedEntry.Event } as OntimeEvent,
|
||||
g: { id: 'g', type: SupportedEntry.Block } as OntimeBlock,
|
||||
h: { id: 'h', type: SupportedEntry.Event } as OntimeEvent,
|
||||
},
|
||||
order: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'],
|
||||
};
|
||||
describe('relevantBlock', () => {
|
||||
const testRundown = [
|
||||
{ id: 'a', type: SupportedEvent.Event },
|
||||
{ id: 'b', type: SupportedEvent.Event },
|
||||
{ id: 'c', type: SupportedEvent.Event },
|
||||
{ id: 'd', type: SupportedEvent.Delay },
|
||||
{ id: 'e', type: SupportedEvent.Block },
|
||||
{ id: 'f', type: SupportedEvent.Event },
|
||||
{ id: 'g', type: SupportedEvent.Block },
|
||||
{ id: 'h', type: SupportedEvent.Event },
|
||||
];
|
||||
|
||||
test.each([
|
||||
['h', 'g'],
|
||||
['f', 'e'],
|
||||
])('returns the relevant block', (id, expected) => {
|
||||
const block = getPreviousBlock(testRundown, id);
|
||||
expect(block?.id).toBe(expected);
|
||||
it('returns the relevant block', () => {
|
||||
const block = getPreviousBlock(testRundown as unknown as OntimeRundown, 'h');
|
||||
|
||||
expect(block?.id).toBe('g');
|
||||
});
|
||||
it('returns the relevant block', () => {
|
||||
const block = getPreviousBlock(testRundown as unknown as OntimeRundown, 'f');
|
||||
|
||||
it('returns null if there is no parent block relevant block', () => {
|
||||
const block = getPreviousBlock(testRundown, 'a');
|
||||
expect(block).toBe(null);
|
||||
expect(block?.id).toBe('e');
|
||||
});
|
||||
it('returns the relevant block', () => {
|
||||
const block = getPreviousBlock(testRundown as unknown as OntimeRundown, 'a');
|
||||
|
||||
expect(block).toBeNull();
|
||||
});
|
||||
it('also works on index 0', () => {
|
||||
testRundown.order.unshift('0');
|
||||
// @ts-expect-error -- we are adding an event to the rundown
|
||||
testRundown.entries['0'] = { id: '0', type: SupportedEntry.Block } as OntimeBlock;
|
||||
const block = getPreviousBlock(testRundown, 'a');
|
||||
testRundown.unshift({ id: '0', type: SupportedEvent.Block });
|
||||
const block = getPreviousBlock(testRundown as unknown as OntimeRundown, 'a');
|
||||
expect(block?.id).toBe('0');
|
||||
});
|
||||
});
|
||||
|
||||
it('returns the parent block if nested event', () => {
|
||||
const testRundown = {
|
||||
entries: {
|
||||
1: { id: '1', type: SupportedEntry.Event } as OntimeEvent,
|
||||
block: { id: 'block', type: SupportedEntry.Block, events: ['21', '22', '23'] } as OntimeBlock,
|
||||
21: { id: '21', type: SupportedEntry.Event, parent: 'block' } as OntimeEvent,
|
||||
22: { id: '22', type: SupportedEntry.Event, parent: 'block' } as OntimeEvent,
|
||||
23: { id: '23', type: SupportedEntry.Event, parent: 'block' } as OntimeEvent,
|
||||
},
|
||||
order: ['1', 'block'],
|
||||
};
|
||||
const block = getPreviousBlock(testRundown, '21');
|
||||
expect(block?.id).toBe('block');
|
||||
describe('filterPlayable()', () => {
|
||||
test('should return an array with only playable events', () => {
|
||||
const eventA = { id: 'a', type: SupportedEvent.Event } as OntimeEvent;
|
||||
const eventB = { id: 'b', skip: true, type: SupportedEvent.Event } as OntimeEvent;
|
||||
const testRundown = [
|
||||
eventA,
|
||||
eventB,
|
||||
{ id: 'c', type: SupportedEvent.Delay },
|
||||
{ id: 'd', type: SupportedEvent.Block },
|
||||
];
|
||||
|
||||
const result = filterPlayable(testRundown as unknown as OntimeRundown);
|
||||
expect(result).toMatchObject([eventA]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,31 +1,37 @@
|
||||
import type {
|
||||
EntryId,
|
||||
NormalisedRundown,
|
||||
OntimeBlock,
|
||||
OntimeEntry,
|
||||
OntimeEvent,
|
||||
OntimeRundown,
|
||||
OntimeRundownEntry,
|
||||
PlayableEvent,
|
||||
Rundown,
|
||||
RundownEntries,
|
||||
} from 'ontime-types';
|
||||
import { isOntimeBlock, isOntimeEvent, isPlayableEvent } from 'ontime-types';
|
||||
|
||||
type IndexAndEntry = { entry: OntimeEntry | null; index: number | null };
|
||||
type IndexAndEntry = { entry: OntimeRundownEntry | null; index: number | null };
|
||||
|
||||
/**
|
||||
* Gets first event in rundown, if it exists
|
||||
*/
|
||||
export function getFirst(rundown: OntimeRundown) {
|
||||
return rundown.length ? rundown[0] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets first event in a normalised rundown, if it exists
|
||||
* @param rundown
|
||||
* @param order
|
||||
* @returns
|
||||
*/
|
||||
export function getFirstNormal(entries: RundownEntries, order: EntryId[]): OntimeEntry | null {
|
||||
if (!order.length) {
|
||||
return null;
|
||||
}
|
||||
const eventId = order[0];
|
||||
return entries[eventId] ?? null;
|
||||
export function getFirstNormal(rundown: NormalisedRundown, order: string[]) {
|
||||
const firstId = order[0];
|
||||
return rundown[firstId] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets first scheduled event in rundown, if it exists
|
||||
*/
|
||||
export function getFirstEvent(rundown: OntimeEntry[]): {
|
||||
export function getFirstEvent(rundown: OntimeRundown): {
|
||||
firstEvent: PlayableEvent | null;
|
||||
firstIndex: number | null;
|
||||
} {
|
||||
@@ -42,8 +48,8 @@ export function getFirstEvent(rundown: OntimeEntry[]): {
|
||||
* Gets first scheduled event in a normalised rundown, if it exists
|
||||
*/
|
||||
export function getFirstEventNormal(
|
||||
rundown: RundownEntries,
|
||||
order: EntryId[],
|
||||
rundown: NormalisedRundown,
|
||||
order: string[],
|
||||
): {
|
||||
firstEvent: PlayableEvent | null;
|
||||
firstIndex: number | null;
|
||||
@@ -61,18 +67,18 @@ export function getFirstEventNormal(
|
||||
/**
|
||||
* Gets last event in a normalised rundown, if it exists
|
||||
*/
|
||||
export function getLastNormal(entries: RundownEntries, order: EntryId[]): OntimeEntry | null {
|
||||
export function getLastNormal(rundown: NormalisedRundown, order: string[]): OntimeRundownEntry | null {
|
||||
const lastId = order.at(-1);
|
||||
if (lastId === undefined) {
|
||||
return null;
|
||||
}
|
||||
return entries[lastId] ?? null;
|
||||
return rundown[lastId] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets last scheduled event in rundown, if it exists
|
||||
*/
|
||||
export function getLastEvent(rundown: OntimeEntry[]): {
|
||||
export function getLastEvent(rundown: OntimeRundown): {
|
||||
lastEvent: PlayableEvent | null;
|
||||
lastIndex: number | null;
|
||||
} {
|
||||
@@ -81,7 +87,7 @@ export function getLastEvent(rundown: OntimeEntry[]): {
|
||||
}
|
||||
|
||||
for (let i = rundown.length - 1; i >= 0; i--) {
|
||||
const lastEvent = rundown[i];
|
||||
const lastEvent = rundown.at(i);
|
||||
if (isOntimeEvent(lastEvent) && isPlayableEvent(lastEvent)) {
|
||||
return { lastEvent, lastIndex: i };
|
||||
}
|
||||
@@ -93,7 +99,7 @@ export function getLastEvent(rundown: OntimeEntry[]): {
|
||||
* Gets last scheduled event in a normalised rundown, if it exists
|
||||
*/
|
||||
export function getLastEventNormal(
|
||||
rundown: RundownEntries,
|
||||
rundown: NormalisedRundown,
|
||||
order: string[],
|
||||
): {
|
||||
lastEvent: OntimeEvent | null;
|
||||
@@ -117,14 +123,13 @@ export function getLastEventNormal(
|
||||
* Gets next entry in rundown, if it exists
|
||||
*/
|
||||
export function getNext(
|
||||
rundown: Pick<Rundown, 'entries' | 'order'>,
|
||||
rundown: OntimeRundown,
|
||||
currentId: string,
|
||||
): { nextEvent: OntimeEntry | null; nextIndex: number | null } {
|
||||
const index = rundown.order.findIndex((entryId) => entryId === currentId);
|
||||
if (index !== -1 && index + 1 < rundown.order.length) {
|
||||
): { nextEvent: OntimeRundownEntry | null; nextIndex: number | null } {
|
||||
const index = rundown.findIndex((event) => event.id === currentId);
|
||||
if (index !== -1 && index + 1 < rundown.length) {
|
||||
const nextIndex = index + 1;
|
||||
const nextId = rundown.order[nextIndex];
|
||||
const nextEvent = rundown.entries[nextId];
|
||||
const nextEvent = rundown[nextIndex];
|
||||
return { nextEvent, nextIndex };
|
||||
} else {
|
||||
return { nextEvent: null, nextIndex: null };
|
||||
@@ -134,7 +139,7 @@ export function getNext(
|
||||
/**
|
||||
* Gets next entry in rundown, if it exists
|
||||
*/
|
||||
export function getNextNormal(rundown: RundownEntries, order: string[], currentId: string): IndexAndEntry {
|
||||
export function getNextNormal(rundown: NormalisedRundown, order: string[], currentId: string): IndexAndEntry {
|
||||
const currentIndex = order.findIndex((id) => id === currentId);
|
||||
if (currentIndex !== -1 && currentIndex + 1 < order.length) {
|
||||
const index = currentIndex + 1;
|
||||
@@ -150,10 +155,10 @@ export function getNextNormal(rundown: RundownEntries, order: string[], currentI
|
||||
* Gets next scheduled event in rundown, if it exists
|
||||
*/
|
||||
export function getNextEvent(
|
||||
rundown: OntimeEntry[],
|
||||
rundown: OntimeRundown,
|
||||
currentId: string,
|
||||
): { nextEvent: OntimeEvent | null; nextIndex: number | null } {
|
||||
const index = rundown.findIndex((entry) => entry.id === currentId);
|
||||
const index = rundown.findIndex((event) => event.id === currentId);
|
||||
if (index < 0) {
|
||||
return { nextEvent: null, nextIndex: null };
|
||||
}
|
||||
@@ -171,18 +176,18 @@ export function getNextEvent(
|
||||
* Gets next scheduled event in a normalised rundown, if it exists
|
||||
*/
|
||||
export function getNextEventNormal(
|
||||
entries: RundownEntries,
|
||||
order: EntryId[],
|
||||
rundown: NormalisedRundown,
|
||||
order: string[],
|
||||
currentId: string,
|
||||
): { nextEvent: OntimeEvent | null; nextIndex: number | null } {
|
||||
const index = order.findIndex((entryId) => entryId === currentId);
|
||||
const index = order.findIndex((id) => id === currentId);
|
||||
if (index < 0) {
|
||||
return { nextEvent: null, nextIndex: null };
|
||||
}
|
||||
|
||||
for (let i = index + 1; i < order.length; i++) {
|
||||
const nextId = order[i];
|
||||
const nextEvent = entries[nextId];
|
||||
const nextEvent = rundown[nextId];
|
||||
if (isOntimeEvent(nextEvent)) {
|
||||
return { nextEvent, nextIndex: i };
|
||||
}
|
||||
@@ -193,13 +198,11 @@ export function getNextEventNormal(
|
||||
/**
|
||||
* Gets previous entry in rundown, if it exists
|
||||
*/
|
||||
export function getPrevious(rundown: Pick<Rundown, 'entries' | 'order'>, currentId: string): IndexAndEntry {
|
||||
const currentIndex = rundown.order.findIndex((entryId) => entryId === currentId);
|
||||
|
||||
if (currentIndex > 1) {
|
||||
export function getPrevious(rundown: OntimeRundown, currentId: string): IndexAndEntry {
|
||||
const currentIndex = rundown.findIndex((event) => event.id === currentId);
|
||||
if (currentIndex !== -1 && currentIndex - 1 >= 0) {
|
||||
const index = currentIndex - 1;
|
||||
const previousId = rundown.order[index];
|
||||
const entry = rundown.entries[previousId];
|
||||
const entry = rundown[index];
|
||||
return { entry, index };
|
||||
} else {
|
||||
return { entry: null, index: null };
|
||||
@@ -207,15 +210,14 @@ export function getPrevious(rundown: Pick<Rundown, 'entries' | 'order'>, current
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets previous entry in a normalised rundown, if it exists
|
||||
* Gets previous entry in a nornalised rundown, if it exists
|
||||
*/
|
||||
export function getPreviousNormal(entries: RundownEntries, order: string[], currentId: string): IndexAndEntry {
|
||||
export function getPreviousNormal(rundown: NormalisedRundown, order: string[], currentId: string): IndexAndEntry {
|
||||
const currentIndex = order.findIndex((id) => id === currentId);
|
||||
|
||||
if (currentIndex !== -1 && currentIndex - 1 >= 0) {
|
||||
const index = currentIndex - 1;
|
||||
const previousId = order[index];
|
||||
const entry = entries[previousId];
|
||||
const entry = rundown[previousId];
|
||||
return { entry, index };
|
||||
} else {
|
||||
return { entry: null, index: null };
|
||||
@@ -226,16 +228,15 @@ export function getPreviousNormal(entries: RundownEntries, order: string[], curr
|
||||
* Gets previous scheduled event in rundown, if it exists
|
||||
*/
|
||||
export function getPreviousEvent(
|
||||
rundown: Pick<Rundown, 'entries' | 'order'>,
|
||||
rundown: OntimeRundown,
|
||||
currentId: string,
|
||||
): { previousEvent: OntimeEvent | null; previousIndex: number | null } {
|
||||
const index = rundown.order.findIndex((entryId) => entryId === currentId);
|
||||
const index = rundown.findIndex((event) => event.id === currentId);
|
||||
if (index < 0) {
|
||||
return { previousEvent: null, previousIndex: null };
|
||||
}
|
||||
for (let i = index - 1; i >= 0; i--) {
|
||||
const previousId = rundown.order[i];
|
||||
const previousEvent = rundown.entries[previousId];
|
||||
const previousEvent = rundown[i];
|
||||
if (isOntimeEvent(previousEvent)) {
|
||||
return { previousEvent, previousIndex: i };
|
||||
}
|
||||
@@ -245,19 +246,23 @@ export function getPreviousEvent(
|
||||
|
||||
/**
|
||||
* Gets previous scheduled event in a normalised rundown, if it exists
|
||||
* @param rundown
|
||||
* @param order
|
||||
* @param {string} currentId
|
||||
* @return {{ previousEvent: OntimeRundownEntry | null; previousIndex: number | null } }
|
||||
*/
|
||||
export function getPreviousEventNormal(
|
||||
entries: RundownEntries,
|
||||
order: EntryId[],
|
||||
rundown: NormalisedRundown,
|
||||
order: string[],
|
||||
currentId: string,
|
||||
): { previousEvent: OntimeEvent | null; previousIndex: number | null } {
|
||||
const index = order.findIndex((entryId) => entryId === currentId);
|
||||
const index = order.findIndex((id) => id === currentId);
|
||||
if (index < 0) {
|
||||
return { previousEvent: null, previousIndex: null };
|
||||
}
|
||||
for (let i = index - 1; i >= 0; i--) {
|
||||
const previousId = order[i];
|
||||
const previousEvent = entries[previousId];
|
||||
const previousEvent = rundown[previousId];
|
||||
if (isOntimeEvent(previousEvent)) {
|
||||
return { previousEvent, previousIndex: i };
|
||||
}
|
||||
@@ -268,7 +273,7 @@ export function getPreviousEventNormal(
|
||||
/**
|
||||
* @description swaps two OntimeEvents in the rundown
|
||||
*/
|
||||
export const swapEventData = (eventA: OntimeEvent, eventB: OntimeEvent): [newA: OntimeEvent, newB: OntimeEvent] => {
|
||||
export const swapEventData = (eventA: OntimeEvent, eventB: OntimeEvent): { newA: OntimeEvent; newB: OntimeEvent } => {
|
||||
const newA = {
|
||||
...eventB,
|
||||
// events keep the ID
|
||||
@@ -278,13 +283,10 @@ export const swapEventData = (eventA: OntimeEvent, eventB: OntimeEvent): [newA:
|
||||
timeEnd: eventA.timeEnd,
|
||||
duration: eventA.duration,
|
||||
linkStart: eventA.linkStart,
|
||||
parent: eventA.parent,
|
||||
// keep schedule metadata
|
||||
delay: eventA.delay,
|
||||
gap: eventA.gap,
|
||||
dayOffset: eventA.dayOffset,
|
||||
// keep revision number
|
||||
revision: eventA.revision,
|
||||
};
|
||||
|
||||
const newB = {
|
||||
@@ -296,26 +298,23 @@ export const swapEventData = (eventA: OntimeEvent, eventB: OntimeEvent): [newA:
|
||||
timeEnd: eventB.timeEnd,
|
||||
duration: eventB.duration,
|
||||
linkStart: eventB.linkStart,
|
||||
parent: eventB.parent,
|
||||
// keep schedule metadata
|
||||
delay: eventB.delay,
|
||||
gap: eventB.gap,
|
||||
dayOffset: eventB.dayOffset,
|
||||
// keep revision number
|
||||
revision: eventB.revision,
|
||||
};
|
||||
|
||||
return [newA, newB];
|
||||
return { newA, newB };
|
||||
};
|
||||
|
||||
export function getEventWithId(rundown: OntimeEntry[], id: string): OntimeEntry | undefined {
|
||||
export function getEventWithId(rundown: OntimeRundown, id: string): OntimeRundownEntry | undefined {
|
||||
return rundown.find((event) => event.id === id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets relevant block element for a given ID
|
||||
*/
|
||||
export function getPreviousBlockNormal(rundown: RundownEntries, order: string[], currentId: string): IndexAndEntry {
|
||||
export function getPreviousBlockNormal(rundown: NormalisedRundown, order: string[], currentId: string): IndexAndEntry {
|
||||
let foundCurrentEvent = false;
|
||||
// Iterate backwards through the rundown to find the current event
|
||||
for (let index = order.length - 1; index >= 0; index--) {
|
||||
@@ -338,7 +337,7 @@ export function getPreviousBlockNormal(rundown: RundownEntries, order: string[],
|
||||
/**
|
||||
* Gets next block element for a given ID
|
||||
*/
|
||||
export function getNextBlockNormal(rundown: RundownEntries, order: string[], currentId: string): IndexAndEntry {
|
||||
export function getNextBlockNormal(rundown: NormalisedRundown, order: string[], currentId: string): IndexAndEntry {
|
||||
let foundCurrentEvent = false;
|
||||
// Iterate backwards through the rundown to find the current event
|
||||
for (let index = 0; index < order.length; index++) {
|
||||
@@ -361,19 +360,11 @@ export function getNextBlockNormal(rundown: RundownEntries, order: string[], cur
|
||||
/**
|
||||
* Gets relevant block element for a given ID
|
||||
*/
|
||||
export function getPreviousBlock(rundown: Pick<Rundown, 'entries' | 'order'>, currentId: EntryId): OntimeBlock | null {
|
||||
const currentEvent = rundown.entries[currentId];
|
||||
|
||||
// check if event is inside a block
|
||||
if (isOntimeEvent(currentEvent) && currentEvent.parent) {
|
||||
return rundown.entries[currentEvent.parent] as OntimeBlock;
|
||||
}
|
||||
|
||||
export function getPreviousBlock(rundown: OntimeRundown, currentId: string): OntimeBlock | null {
|
||||
let foundCurrentEvent = false;
|
||||
// Iterate backwards through the rundown to find the current event
|
||||
for (let i = rundown.order.length - 1; i >= 0; i--) {
|
||||
const entryId = rundown.order[i];
|
||||
const entry = rundown.entries[entryId];
|
||||
for (let i = rundown.length - 1; i >= 0; i--) {
|
||||
const entry = rundown[i];
|
||||
if (!foundCurrentEvent && entry.id === currentId) {
|
||||
// set the flag when the current event is found
|
||||
foundCurrentEvent = true;
|
||||
@@ -384,6 +375,20 @@ export function getPreviousBlock(rundown: Pick<Rundown, 'entries' | 'order'>, cu
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
// no blocks exist before null event
|
||||
// no blocks exist before current event
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* filters a rundown to timed events
|
||||
*/
|
||||
export function filterPlayable(rundown: OntimeRundown): PlayableEvent[] {
|
||||
return rundown.filter((event) => isOntimeEvent(event) && !event.skip) as PlayableEvent[];
|
||||
}
|
||||
|
||||
/**
|
||||
* filters a rundown to events that can be played
|
||||
*/
|
||||
export function filterTimedEvents(rundown: OntimeRundown): OntimeEvent[] {
|
||||
return rundown.filter((event) => isOntimeEvent(event)) as OntimeEvent[];
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import type { ProjectRundowns, Rundown } from 'ontime-types';
|
||||
|
||||
/**
|
||||
* Gets the first rundown in the project
|
||||
* We know that the project has at least one rundown
|
||||
*/
|
||||
export function getFirstRundown(rundowns: ProjectRundowns): Rundown {
|
||||
const keys = Object.keys(rundowns);
|
||||
if (keys.length === 0) {
|
||||
throw new Error('No rundowns found in project');
|
||||
}
|
||||
|
||||
const firstKey = keys[0];
|
||||
return rundowns[firstKey];
|
||||
}
|
||||
@@ -9,9 +9,9 @@ describe('validateEndAction()', () => {
|
||||
expect(endAction).toBe(EndAction.LoadNext);
|
||||
});
|
||||
it('returns fallback otherwise', () => {
|
||||
const emptyAction = validateEndAction('', EndAction.LoadNext);
|
||||
const emptyAction = validateEndAction('', EndAction.Stop);
|
||||
const invalidAction = validateEndAction('this-does-not-exist', EndAction.PlayNext);
|
||||
expect(emptyAction).toBe(EndAction.LoadNext);
|
||||
expect(emptyAction).toBe(EndAction.Stop);
|
||||
expect(invalidAction).toBe(EndAction.PlayNext);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
import type { MaybeString } from 'ontime-types';
|
||||
import { EndAction, TimerType, TimeStrategy } from 'ontime-types';
|
||||
|
||||
/**
|
||||
* Check if a given value is a valid type linkStart, returns the fallback otherwise
|
||||
* linkStart can be a string (id of an event to link) or null (unlinked)
|
||||
* @param {MaybeString} maybeLinkStart
|
||||
* @returns {MaybeString}
|
||||
*/
|
||||
export function validateLinkStart(maybeLinkStart: unknown, fallback: MaybeString = null): MaybeString {
|
||||
if (typeof maybeLinkStart === 'string' || maybeLinkStart === null) {
|
||||
return maybeLinkStart as MaybeString;
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given value is a valid time strategy, returns the fallback otherwise
|
||||
* @param {TimeStrategy} maybeTimeStrategy
|
||||
|
||||
Reference in New Issue
Block a user