* refactor: cleanup routes

* style: smaller base font

* chore: upgrade dependencies

* chore: lock node version to electron

* refactor: pass HTTP to integration controller (#652)

* refactor: deprecate onair control

* refactor: remove playback router

* Several project files user folder (#617)

* chore: automated screenshots (#667)

* feat: app settings (#658)

* refactor: remove deprecated event data (#674)

* Studio clock (#663)

---------

Co-authored-by: Carlos Valente <carlosvalente@pm.me>

* Feat: reorder events with alt+ctrl + arrow up/down (#645)

* Warning and danger per event (#677)

---------

Co-authored-by: Fabian Posenau <fabian@fphome.de>

* refactor: stabilise actionHandler (#683)

Co-authored-by: Fabian Posenau <fabian@fphome.de>

* improvement: hide seconds (#675)

* wip: overview (#688)

* fix: focus cursor (#695)

* refactor: update lower third (#665)

* Refactor/time formatting (#696)

---------

Co-authored-by: Carlos Valente <34649812+cpvalente@users.noreply.github.com>
Co-authored-by: Carlos Valente <carlosvalente@pm.me>

* feat: multiple selection (#703)

---------

Co-authored-by: asharonbaltazar <asharonbaltazar@outlook.com>
Co-authored-by: Alex <ac@omnivox.dk>

* fix: test - go to `Edit mode` befor tying to click `Event options` button (#708)

* refactor: runtime service (#715)

* fix: issue with loosing cursor position on message (#719)

* remove info panel (#721)

* Event editor continue (#722)

* update API - part  (#709)

---------

Co-authored-by: Carlos Valente <34649812+cpvalente@users.noreply.github.com>
Co-authored-by: Carlos Valente <carlosvalente@pm.me>

* refactor: update timers (#729)

* feat: many timers (#706)

---------

Co-authored-by: arc-alex <ac@omnivox.dk>

* refactor: excel cleanup (#734)

* refactor: allow import of blocks and skip import (#735)

* Project manager (#697)

* refactor: UI for linking events (#763)

* upgraded pipeline actions (#777)

* Over under (#771)

* custom fields (#744)


---------

Co-authored-by: Carlos Valente <carlosvalente@pm.me>

* Sheets settings (#774)

---------

Co-authored-by: arc-alex <ac@omnivox.dk>

* style: tweaks to lower thirds (#785)

* refactor: delays account for gaps (#784)

* refactor: partial state updates (#780)

* feat: generate crash report (#787)

* Sheet use limited input device auth flow (#782)

---------

Co-authored-by: cv <34649812+cpvalente@users.noreply.github.com>
Co-authored-by: Carlos Valente <carlosvalente@pm.me>

* Custom fields views (#789)

* refactor: deprecate presenter and subtitle (#795)

* refactor: organise API around resources (#798)

---------

Co-authored-by: Bianca Procopio <biancahprocopio@gmail.com>

* Time to end (#804)

* Skip fixes (#805)

* fix: onair derives from playback

* Param nav (#822)

---------

Co-authored-by: Alex Christoffer Rasmussen <ac@omnivox.dk>

* refactor: download files from interface (#831)

* Quick options (#814)

* End pause (#832)

* chore: bump node version in docker (#834)

* refactor: follow in run mode (#840)

* fix: uncaught error in http integration (#837)

* Apply project (#843)

Co-authored-by: Matteo Gheza <matteo.gheza07@gmail.com>
Co-authored-by: Ary <arylmoraesn@gmail.com>
Co-authored-by: Alex Christoffer Rasmussen <ac@omnivox.dk>
Co-authored-by: Fabian Posenau <19673098+kellhogs@users.noreply.github.com>
Co-authored-by: Fabian Posenau <fabian@fphome.de>
Co-authored-by: Alex Rohleder <alexrohleder96@gmail.com>
Co-authored-by: asharonbaltazar <asharonbaltazar@outlook.com>
Co-authored-by: Bianca Procopio <biancahprocopio@gmail.com>
Co-authored-by: Fabian Posenau <fabianpos99+github@gmail.com>
This commit is contained in:
Carlos Valente
2024-04-13 10:06:46 +02:00
committed by GitHub
parent debdbd1c5a
commit 55d1aca9b6
640 changed files with 26702 additions and 21623 deletions
@@ -1,6 +1,44 @@
import { OntimeRundown, SupportedEvent } from 'ontime-types';
import type { OntimeEvent, OntimeRundown } from 'ontime-types';
import { SupportedEvent } from 'ontime-types';
import { getNextEvent, getPreviousEvent } from './rundownUtils';
import { getLastEvent, getNext, getNextEvent, getPrevious, getPreviousEvent, swapEventData } from './rundownUtils';
describe('getNext()', () => {
it('returns the next event of type event', () => {
const testRundown = [
{ id: '1', type: SupportedEvent.Event },
{ id: '2', type: SupportedEvent.Event },
{ id: '3', type: SupportedEvent.Event },
];
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 },
];
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 = [
{ 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);
});
});
describe('getNextEvent()', () => {
it('returns the next event of type event', () => {
@@ -10,8 +48,9 @@ describe('getNextEvent()', () => {
{ id: '3', type: SupportedEvent.Event },
];
const next = getNextEvent(testRundown as OntimeRundown, '1');
expect(next?.id).toBe('2');
const { nextEvent, nextIndex } = getNextEvent(testRundown as OntimeRundown, '1');
expect(nextEvent?.id).toBe('2');
expect(nextIndex).toBe(1);
});
it('ignores other event types', () => {
const testRundown = [
@@ -21,8 +60,9 @@ describe('getNextEvent()', () => {
{ id: '4', type: SupportedEvent.Event },
];
const next = getNextEvent(testRundown as OntimeRundown, '1');
expect(next?.id).toBe('4');
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 = [
@@ -31,8 +71,46 @@ describe('getNextEvent()', () => {
{ id: '3', type: SupportedEvent.Block },
];
const next = getNextEvent(testRundown as OntimeRundown, '1');
expect(next).toBe(null);
const { nextEvent, nextIndex } = getNextEvent(testRundown as OntimeRundown, '1');
expect(nextEvent).toBe(null);
expect(nextIndex).toBe(null);
});
});
describe('getPrevious()', () => {
it('returns the previous event of type event', () => {
const testRundown = [
{ id: '1', type: SupportedEvent.Event },
{ id: '2', type: SupportedEvent.Event },
{ id: '3', type: SupportedEvent.Event },
];
const { previousEvent, previousIndex } = getPrevious(testRundown as OntimeRundown, '3');
expect(previousEvent?.id).toBe('2');
expect(previousIndex).toBe(1);
});
it('allow 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 },
];
const { previousEvent, previousIndex } = getPrevious(testRundown as OntimeRundown, '3');
expect(previousEvent?.id).toBe('2');
expect(previousIndex).toBe(1);
});
it('returns null if none found', () => {
const testRundown = [
{ id: '2', type: SupportedEvent.Delay },
{ id: '3', type: SupportedEvent.Block },
{ id: '4', type: SupportedEvent.Event },
];
const { previousEvent, previousIndex } = getPrevious(testRundown as OntimeRundown, '2');
expect(previousEvent).toBe(null);
expect(previousIndex).toBe(null);
});
});
@@ -44,8 +122,9 @@ describe('getPreviousEvent()', () => {
{ id: '3', type: SupportedEvent.Event },
];
const previous = getPreviousEvent(testRundown as OntimeRundown, '3');
expect(previous?.id).toBe('2');
const { previousEvent, previousIndex } = getPreviousEvent(testRundown as OntimeRundown, '3');
expect(previousEvent?.id).toBe('2');
expect(previousIndex).toBe(1);
});
it('ignores other event types', () => {
const testRundown = [
@@ -55,8 +134,9 @@ describe('getPreviousEvent()', () => {
{ id: '4', type: SupportedEvent.Event },
];
const previous = getPreviousEvent(testRundown as OntimeRundown, '4');
expect(previous?.id).toBe('1');
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 = [
@@ -65,7 +145,68 @@ describe('getPreviousEvent()', () => {
{ id: '4', type: SupportedEvent.Event },
];
const previous = getNextEvent(testRundown as OntimeRundown, '1');
expect(previous).toBe(null);
const { previousEvent, previousIndex } = getPreviousEvent(testRundown as OntimeRundown, '2');
expect(previousEvent).toBe(null);
expect(previousIndex).toBe(null);
});
});
describe('swapEventData', () => {
it('swaps some data between two events', () => {
const eventA = {
id: '1',
cue: 'A',
timeStart: 1,
timeEnd: 1,
duration: 1,
delay: 1,
} as OntimeEvent;
const eventB = {
id: '2',
cue: 'B',
timeStart: 2,
timeEnd: 2,
duration: 2,
delay: 2,
} as OntimeEvent;
const { newA, newB } = swapEventData(eventA, eventB);
expect(newA).toMatchObject({
id: '1',
cue: 'B',
timeStart: 1,
timeEnd: 1,
duration: 1,
delay: 1,
});
expect(newB).toMatchObject({
id: '2',
cue: 'A',
timeStart: 2,
timeEnd: 2,
duration: 2,
delay: 2,
});
});
});
describe('getLastEvent', () => {
it('returns the last event of type event', () => {
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 as OntimeRundown);
expect(lastEvent?.id).toBe('3');
});
it('handles rundowns with a single event', () => {
const testRundown = [{ id: '1', type: SupportedEvent.Event }];
const { lastEvent } = getLastEvent(testRundown as OntimeRundown);
expect(lastEvent?.id).toBe('1');
});
});
+256 -83
View File
@@ -1,4 +1,5 @@
import { isOntimeEvent, OntimeEvent, OntimeRundown, OntimeRundownEntry } from 'ontime-types';
import type { NormalisedRundown, OntimeEvent, OntimeRundown, OntimeRundownEntry } from 'ontime-types';
import { isOntimeEvent } from 'ontime-types';
/**
* Gets first event in rundown, if it exists
@@ -10,47 +11,146 @@ export function getFirst(rundown: OntimeRundownEntry[]) {
}
/**
* Gets first scheduled event in rundown, if it exists
* @param {OntimeRundownEntry[]} rundown
* @return {OntimeEvent | null}
* Gets first event in a normalised rundown, if it exists
* @param rundown
* @param order
* @returns
*/
export function getFirstEvent(rundown: OntimeRundownEntry[]) {
for (let i = 0; i < rundown.length; i++) {
const event = rundown[i];
if (isOntimeEvent(event)) {
return event;
}
}
return null;
}
export function getLastEvent(rundown: OntimeRundown): OntimeEvent | null {
if (rundown.length < 1) {
return null;
}
for (let i = rundown.length - 1; i > 0; i--) {
const event = rundown.at(i);
if (isOntimeEvent(event)) {
return event;
}
}
return null;
export function getFirstNormal(rundown: NormalisedRundown, order: string[]) {
const firstId = order[0];
return rundown[firstId] ?? null;
}
/**
* Gets next event in rundown, if it exists
* Gets first scheduled event in rundown, if it exists
* @param {OntimeRundownEntry[]} rundown
* @return {{ firstEvent: OntimeEvent | null; firstIndex: number | null } }
*/
export function getFirstEvent(rundown: OntimeRundownEntry[]): {
firstEvent: OntimeEvent | null;
firstIndex: number | null;
} {
for (let i = 0; i < rundown.length; i++) {
const firstEvent = rundown[i];
if (isOntimeEvent(firstEvent) && !firstEvent.skip) {
return { firstEvent, firstIndex: i };
}
}
return { firstEvent: null, firstIndex: null };
}
/**
* Gets first scheduled event in a normalised rundown, if it exists
* @param rundown
* @param order
* @returns
*/
export function getFirstEventNormal(
rundown: NormalisedRundown,
order: string[],
): {
firstEvent: OntimeEvent | null;
firstIndex: number | null;
} {
for (let i = 0; i < order.length; i++) {
const firstId = order[i];
const firstEvent = rundown[firstId];
if (isOntimeEvent(firstEvent) && !firstEvent.skip) {
return { firstEvent, firstIndex: i };
}
}
return { firstEvent: null, firstIndex: null };
}
/**
* Gets last scheduled event in rundown, if it exists
* @param {OntimeRundownEntry[]} rundown
* @return {{ firstEvent: OntimeEvent | null; firstIndex: number | null } }
*/
export function getLastEvent(rundown: OntimeRundown): {
lastEvent: OntimeEvent | null;
lastIndex: number | null;
} {
if (rundown.length < 1) {
return { lastEvent: null, lastIndex: null };
}
for (let i = rundown.length - 1; i >= 0; i--) {
const lastEvent = rundown.at(i);
if (isOntimeEvent(lastEvent) && !lastEvent.skip) {
return { lastEvent, lastIndex: i };
}
}
return { lastEvent: null, lastIndex: null };
}
/**
* Gets last scheduled event in a normalised rundown, if it exists
* @param rundown
* @param order
* @return {{ firstEvent: OntimeEvent | null; firstIndex: number | null } }
*/
export function getLastEventNormal(
rundown: NormalisedRundown,
order: string[],
): {
lastEvent: OntimeEvent | null;
lastIndex: number | null;
} {
if (order.length < 1) {
return { lastEvent: null, lastIndex: null };
}
for (let i = order.length - 1; i >= 0; i--) {
const lastId = order[i];
const lastEvent = rundown[lastId];
if (isOntimeEvent(lastEvent) && !lastEvent.skip) {
return { lastEvent, lastIndex: i };
}
}
return { lastEvent: null, lastIndex: null };
}
/**
* Gets next entry in rundown, if it exists
* @param {OntimeRundownEntry[]} rundown
* @param {string} currentId
* @return {OntimeRundownEntry | null}
* @return {{ nextEvent: OntimeRundownEntry | null; nextIndex: number | null } }
*/
export function getNext(rundown: OntimeRundownEntry[], currentId: string): OntimeRundownEntry | null {
export function getNext(
rundown: OntimeRundownEntry[],
currentId: string,
): { nextEvent: OntimeRundownEntry | null; nextIndex: number | null } {
const index = rundown.findIndex((event) => event.id === currentId);
if (index !== -1 && index + 1 < rundown.length) {
return rundown[index + 1];
const nextIndex = index + 1;
const nextEvent = rundown[nextIndex];
return { nextEvent, nextIndex };
} else {
return null;
return { nextEvent: null, nextIndex: null };
}
}
/**
* Gets next entry in rundown, if it exists
* @param rundown
* @param order
* @param currentId
* @returns
*/
export function getNextNormal(
rundown: NormalisedRundown,
order: string[],
currentId: string,
): { nextEvent: OntimeRundownEntry | null; nextIndex: number | null } {
const index = order.findIndex((id) => id === currentId);
if (index !== -1 && index + 1 < order.length) {
const nextIndex = index + 1;
const nextId = order[nextIndex];
const nextEvent = rundown[nextId];
return { nextEvent, nextIndex };
} else {
return { nextEvent: null, nextIndex: null };
}
}
@@ -58,35 +158,93 @@ export function getNext(rundown: OntimeRundownEntry[], currentId: string): Ontim
* Gets next scheduled event in rundown, if it exists
* @param {OntimeRundownEntry[]} rundown
* @param {string} currentId
* @return {OntimeEvent | null}
* @return {{ nextEvent: OntimeEvent | null; nextIndex: number | null } }
*/
export function getNextEvent(rundown: OntimeRundownEntry[], currentId: string): OntimeEvent | null {
export function getNextEvent(
rundown: OntimeRundownEntry[],
currentId: string,
): { nextEvent: OntimeEvent | null; nextIndex: number | null } {
const index = rundown.findIndex((event) => event.id === currentId);
if (index < 0) {
return null;
return { nextEvent: null, nextIndex: null };
}
for (let i = index + 1; i < rundown.length; i++) {
const event = rundown[i];
if (isOntimeEvent(event)) {
return event;
const nextEvent = rundown[i];
if (isOntimeEvent(nextEvent)) {
return { nextEvent, nextIndex: i };
}
}
return null;
return { nextEvent: null, nextIndex: null };
}
/**
* Gets previous event in rundown, if it exists
* Gets next scheduled event in a normalised rundown, if it exists
* @param rundown
* @param order
* @param {string} currentId
* @return {{ nextEvent: OntimeEvent | null; nextIndex: number | null } }
*/
export function getNextEventNormal(
rundown: NormalisedRundown,
order: string[],
currentId: string,
): { nextEvent: OntimeEvent | null; nextIndex: number | null } {
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 = rundown[nextId];
if (isOntimeEvent(nextEvent)) {
return { nextEvent, nextIndex: i };
}
}
return { nextEvent: null, nextIndex: null };
}
/**
* Gets previous entry in rundown, if it exists
* @param {OntimeRundownEntry[]} rundown
* @param {string} currentId
* @return {OntimeRundownEntry | null}
* @return {{ previousEvent: OntimeRundownEntry | null; previousIndex: number | null } }
*/
export function getPrevious(rundown: OntimeRundownEntry[], currentId: string) {
export function getPrevious(
rundown: OntimeRundownEntry[],
currentId: string,
): { previousEvent: OntimeRundownEntry | null; previousIndex: number | null } {
const index = rundown.findIndex((event) => event.id === currentId);
if (index !== -1 && index - 1 >= 0) {
return rundown[index - 1];
const previousIndex = index - 1;
const previousEvent = rundown[previousIndex];
return { previousEvent, previousIndex };
} else {
return null;
return { previousEvent: null, previousIndex: null };
}
}
/**
* Gets previous entry in a nornalised rundown, if it exists
* @param rundown
* @param order
* @param {string} currentId
* @return {{ previousEvent: OntimeRundownEntry | null; previousIndex: number | null } }
*/
export function getPreviousNormal(
rundown: NormalisedRundown,
order: string[],
currentId: string,
): { previousEvent: OntimeRundownEntry | null; previousIndex: number | null } {
const index = order.findIndex((id) => id === currentId);
if (index !== -1 && index - 1 >= 0) {
const previousIndex = index - 1;
const previousId = order[previousIndex];
const previousEvent = rundown[previousId];
return { previousEvent, previousIndex };
} else {
return { previousEvent: null, previousIndex: null };
}
}
@@ -94,59 +252,74 @@ export function getPrevious(rundown: OntimeRundownEntry[], currentId: string) {
* Gets previous scheduled event in rundown, if it exists
* @param {OntimeRundownEntry[]} rundown
* @param {string} currentId
* @return {OntimeEvent | null}
* @return {{ previousEvent: OntimeRundownEntry | null; previousIndex: number | null } }
*/
export function getPreviousEvent(rundown: OntimeRundownEntry[], currentId: string): OntimeEvent | null {
export function getPreviousEvent(
rundown: OntimeRundownEntry[],
currentId: string,
): { previousEvent: OntimeEvent | null; previousIndex: number | null } {
const index = rundown.findIndex((event) => event.id === currentId);
if (index < 0) {
return null;
return { previousEvent: null, previousIndex: null };
}
for (let i = index - 1; i >= 0; i--) {
const event = rundown[i];
if (isOntimeEvent(event)) {
return event;
const previousEvent = rundown[i];
if (isOntimeEvent(previousEvent)) {
return { previousEvent, previousIndex: i };
}
}
return null;
return { previousEvent: null, previousIndex: null };
}
/**
* 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(
rundown: NormalisedRundown,
order: string[],
currentId: string,
): { previousEvent: OntimeEvent | null; previousIndex: number | null } {
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 = rundown[previousId];
if (isOntimeEvent(previousEvent)) {
return { previousEvent, previousIndex: i };
}
}
return { previousEvent: null, previousIndex: null };
}
/**
* @description swaps two OntimeEvents in the rundown
* @param {OntimeRundown} rundown
* @param {number} fromEventIndex
* @param {number} toEventIndex
* @returns {OntimeRundown}
* @param {OntimeEvent} eventA
* @param {OntimeEvent} eventB
*/
export const swapOntimeEvents = (
rundown: OntimeRundown,
fromEventIndex: number,
toEventIndex: number,
): OntimeRundown => {
const updatedRundown = [...rundown];
if (fromEventIndex < 0 || toEventIndex < 0) {
throw new Error('ID not found at index');
}
const fromEvent = updatedRundown.at(fromEventIndex) as OntimeEvent;
const toEvent = updatedRundown.at(toEventIndex) as OntimeEvent;
updatedRundown[fromEventIndex] = {
...toEvent,
timeStart: fromEvent.timeStart,
timeEnd: fromEvent.timeEnd,
duration: fromEvent.duration,
delay: fromEvent.delay,
export const swapEventData = (eventA: OntimeEvent, eventB: OntimeEvent): { newA: OntimeEvent; newB: OntimeEvent } => {
const newA = {
...eventB,
id: eventA.id,
timeStart: eventA.timeStart,
timeEnd: eventA.timeEnd,
duration: eventA.duration,
delay: eventA.delay,
};
updatedRundown[toEventIndex] = {
...fromEvent,
timeStart: toEvent.timeStart,
timeEnd: toEvent.timeEnd,
duration: toEvent.duration,
delay: toEvent.delay,
const newB = {
...eventA,
id: eventB.id,
timeStart: eventB.timeStart,
timeEnd: eventB.timeEnd,
duration: eventB.duration,
delay: eventB.delay,
};
return updatedRundown;
return { newA, newB };
};