feat: event cue (#473)

* feat: parse cue

* refactor: get delay from backend

* feat: add cue to UI

* refactor: remove deprecated delay logic

* refactor: extract studio clock specific logic

* refactor: extract utilities

* refactor: fix issue with missing key

* style: prevent cue overflow

* style: prevent whitespace wrap

* feat: add support for cues in integrations
This commit is contained in:
Carlos Valente
2023-08-17 21:43:11 +02:00
committed by GitHub
parent 51c31adaf1
commit 657cc22b44
62 changed files with 1363 additions and 1290 deletions
@@ -1,6 +1,7 @@
import { useEffect, useState } from 'react';
import { useSearchParams } from 'react-router-dom';
import type { OntimeRundown, ViewSettings } from 'ontime-types';
import type { OntimeEvent, OntimeRundown, ViewSettings } from 'ontime-types';
import { SupportedEvent } from 'ontime-types';
import { formatDisplay } from 'ontime-utils';
import { overrideStylesURL } from '../../../common/api/apiConstants';
@@ -11,15 +12,11 @@ import useFitText from '../../../common/hooks/useFitText';
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
import { TimeManagerType } from '../../../common/models/TimeManager.type';
import { secondsInMillis } from '../../../common/utils/dateConfig';
import {
formatEventList,
getEventsWithDelay,
type ScheduleEvent,
trimRundown,
} from '../../../common/utils/eventsManager';
import { formatTime } from '../../../common/utils/time';
import { TitleManager } from '../ViewWrapper';
import { type ScheduleEvent, formatEventList, trimRundown } from './studioClock.utils';
import './StudioClock.scss';
const formatOptions = {
@@ -66,8 +63,8 @@ export default function StudioClock(props: StudioClockProps) {
return;
}
const delayed = getEventsWithDelay(backstageEvents);
const trimmed = trimRundown(delayed, selectedId || '', MAX_TITLES);
const delayed = backstageEvents.filter((event) => event.type === SupportedEvent.Event);
const trimmed = trimRundown(delayed as OntimeEvent[], selectedId || '', MAX_TITLES);
const formatted = formatEventList(trimmed, selectedId || '', nextId || '', {
showEnd: false,
@@ -0,0 +1,203 @@
import { OntimeEvent, SupportedEvent } from 'ontime-types';
import { formatEventList, trimRundown } from '../studioClock.utils';
describe('test trimEventlist function', () => {
const limit = 8;
const testData = [
{ id: '1' },
{ id: '2' },
{ id: '3' },
{ id: '4' },
{ id: '5' },
{ id: '6' },
{ id: '7' },
{ id: '8' },
{ id: '9' },
{ id: '10' },
{ id: '11' },
{ id: '12' },
];
it('when we use the first item', () => {
const selectedId = '1';
const expected = [
{ id: '1' },
{ id: '2' },
{ id: '3' },
{ id: '4' },
{ id: '5' },
{ id: '6' },
{ id: '7' },
{ id: '8' },
];
const l = trimRundown(testData as OntimeEvent[], selectedId, limit);
expect(l.length).toBe(limit);
expect(l).toStrictEqual(expected);
});
it('when we use the third item', () => {
const selectedId = '3';
const expected = [
{ id: '1' },
{ id: '2' },
{ id: '3' },
{ id: '4' },
{ id: '5' },
{ id: '6' },
{ id: '7' },
{ id: '8' },
];
const l = trimRundown(testData as OntimeEvent[], selectedId, limit);
expect(l.length).toBe(limit);
expect(l).toStrictEqual(expected);
});
it('when we use the fourth item', () => {
const selectedId = '4';
const expected = [
{ id: '2' },
{ id: '3' },
{ id: '4' },
{ id: '5' },
{ id: '6' },
{ id: '7' },
{ id: '8' },
{ id: '9' },
];
const l = trimRundown(testData as OntimeEvent[], selectedId, limit);
expect(l.length).toBe(limit);
expect(l).toStrictEqual(expected);
});
it('if selected is not found', () => {
const selectedId = '15';
const expected = [
{ id: '1' },
{ id: '2' },
{ id: '3' },
{ id: '4' },
{ id: '5' },
{ id: '6' },
{ id: '7' },
{ id: '8' },
];
const l = trimRundown(testData as OntimeEvent[], selectedId, limit);
expect(l.length).toBe(limit);
expect(l).toStrictEqual(expected);
});
});
describe('test formatEvents function', () => {
const testEvent = [
{
title: 'Welcome to Ontime',
subtitle: 'Subtitles are useful',
presenter: 'cpvalente',
note: 'Maybe a running note for the operator?',
timeStart: 28800000,
timeEnd: 30600000,
isPublic: false,
colour: '',
type: SupportedEvent.Event,
revision: 0,
id: '5946',
},
{
title: 'Unless recalled by the OSC address',
subtitle: '',
presenter: '',
note: 'In green, below',
timeStart: 34800000,
timeEnd: 35400000,
isPublic: false,
colour: '',
type: SupportedEvent.Event,
revision: 0,
id: '8ee5',
},
];
it('it parses correctly', () => {
const selectedId = 'otherEvent';
const nextId = 'notHere';
const expected = [
{
id: '5946',
time: '08:00 - 08:30',
title: 'Welcome to Ontime',
isNow: false,
isNext: false,
colour: '',
},
{
id: '8ee5',
time: '09:40 - 09:50',
title: 'Unless recalled by the OSC address',
isNow: false,
isNext: false,
colour: '',
},
];
const parsed = formatEventList(testEvent as OntimeEvent[], selectedId, nextId, { showEnd: true });
expect(parsed).toStrictEqual(expected);
});
it('it handles selected correctly', () => {
const selectedId = '5946';
const nextId = '8ee5';
const expected = [
{
id: '5946',
time: '08:00 - 08:30',
title: 'Welcome to Ontime',
isNow: true,
isNext: false,
colour: '',
},
{
id: '8ee5',
time: '09:40 - 09:50',
title: 'Unless recalled by the OSC address',
isNow: false,
isNext: true,
colour: '',
},
];
const parsed = formatEventList(testEvent as OntimeEvent[], selectedId, nextId, { showEnd: true });
expect(parsed).toStrictEqual(expected);
});
it('it handles next correctly', () => {
const selectedId = '8ee5';
const nextId = 'notHere';
const expected = [
{
id: '5946',
time: '08:00 - 08:30',
title: 'Welcome to Ontime',
isNow: false,
isNext: false,
colour: '',
},
{
id: '8ee5',
time: '09:40 - 09:50',
title: 'Unless recalled by the OSC address',
isNow: true,
isNext: false,
colour: '',
},
];
const parsed = formatEventList(testEvent as OntimeEvent[], selectedId, nextId, { showEnd: true });
expect(parsed).toStrictEqual(expected);
});
});
@@ -0,0 +1,76 @@
import { OntimeEvent } from 'ontime-types';
import { formatTime } from '../../../common/utils/time';
export type ScheduleEvent = {
id: string;
time: string;
title: string;
isNow: boolean;
isNext: boolean;
colour: string;
};
/**
* @description Returns trimmed event list array
* @param {Object[]} rundown - given rundown
* @param {string} selectedId - id of currently selected event
* @param {number} limit - max number of events to return
* @returns {Object[]} Event list with maximum <limit> objects
*/
export const trimRundown = (rundown: OntimeEvent[], selectedId: string, limit: number): OntimeEvent[] => {
if (rundown == null) return [];
const BEFORE = 2;
const trimmedRundown = [...rundown];
// limit events length if necessary
if (limit != null) {
while (trimmedRundown.length > limit) {
const idx = trimmedRundown.findIndex((e) => e.id === selectedId);
if (idx <= BEFORE) {
trimmedRundown.pop();
} else {
trimmedRundown.shift();
}
}
}
return trimmedRundown;
};
type FormatEventListOptionsProp = {
showEnd?: boolean;
};
/**
* @description Returns list of events formatted to be displayed
* @param {Object[]} rundown - given rundown
* @param {string} selectedId - id of currently selected event
* @param {string} nextId - id of next event
* @param {object} [options]
* @param {boolean} [options.showEnd] - whether to show the end time
* @returns {Object[]} Formatted list of events [{time: -, title: -, isNow, isNext}]
*/
export const formatEventList = (
rundown: OntimeEvent[],
selectedId: string,
nextId: string,
options: FormatEventListOptionsProp,
): ScheduleEvent[] => {
if (rundown == null) return [];
const { showEnd = false } = options;
// format list
return rundown.map((event) => {
const start = formatTime(event.timeStart + (event.delay || 0));
const end = formatTime(event.timeEnd + (event.delay || 0));
return {
id: event.id,
time: showEnd ? `${start} - ${end}` : start,
title: event.title,
isNow: event.id === selectedId,
isNext: event.id === nextId,
colour: event.colour,
};
});
};