mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-17 21:24:11 +00:00
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:
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import QRCode from 'react-qr-code';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { EventData, Message, OntimeEvent, ViewSettings } from 'ontime-types';
|
||||
import { EventData, Message, OntimeEvent, SupportedEvent, ViewSettings } from 'ontime-types';
|
||||
import { formatDisplay } from 'ontime-utils';
|
||||
|
||||
import { overrideStylesURL } from '../../../common/api/apiConstants';
|
||||
@@ -15,7 +15,6 @@ import { TIME_FORMAT_OPTION } from '../../../common/components/view-params-edito
|
||||
import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor';
|
||||
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
||||
import { TimeManagerType } from '../../../common/models/TimeManager.type';
|
||||
import { getEventsWithDelay } from '../../../common/utils/eventsManager';
|
||||
import { formatTime } from '../../../common/utils/time';
|
||||
import { useTranslation } from '../../../translation/TranslationProvider';
|
||||
import { titleVariants } from '../common/animation';
|
||||
@@ -74,7 +73,7 @@ export default function Backstage(props: BackstageProps) {
|
||||
: formatTime(time.expectedFinish, formatOptions);
|
||||
|
||||
const qrSize = Math.max(window.innerWidth / 15, 128);
|
||||
const filteredEvents = getEventsWithDelay(backstageEvents);
|
||||
const filteredEvents = backstageEvents.filter((event) => event.type === SupportedEvent.Event);
|
||||
const showPublicMessage = publ.text && publ.visible;
|
||||
const showProgress = time.playback !== 'stop';
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ import { TIME_FORMAT_OPTION } from '../../../common/components/view-params-edito
|
||||
import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor';
|
||||
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
||||
import { TimeManagerType } from '../../../common/models/TimeManager.type';
|
||||
import getDelayTo from '../../../common/utils/getDelayTo';
|
||||
import { formatTime } from '../../../common/utils/time';
|
||||
import { useTranslation } from '../../../translation/TranslationProvider';
|
||||
|
||||
@@ -72,7 +71,7 @@ export default function Countdown(props: CountdownProps) {
|
||||
if (followThis !== null) {
|
||||
setFollow(followThis);
|
||||
const idx: number = backstageEvents.findIndex((event: OntimeRundownEntry) => event.id === followThis?.id);
|
||||
const delayToEvent = getDelayTo(backstageEvents, idx);
|
||||
const delayToEvent = backstageEvents[idx]?.delay ?? 0;
|
||||
setDelay(delayToEvent);
|
||||
}
|
||||
}, [backstageEvents, searchParams]);
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user