mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-09 09:23:51 +00:00
refactor: pass events instead of derived data to titles (#493)
* refactor: pass events instead of derived data to titles
This commit is contained in:
@@ -79,7 +79,8 @@ export const setPlayback = {
|
||||
|
||||
export const useInfoPanel = () => {
|
||||
const featureSelector = (state: RuntimeStore) => ({
|
||||
titles: state.titles,
|
||||
eventNow: state.eventNow,
|
||||
eventNext: state.eventNext,
|
||||
playback: state.playback,
|
||||
selectedEventIndex: state.loaded.selectedEventIndex,
|
||||
numEvents: state.loaded.numEvents,
|
||||
@@ -94,7 +95,7 @@ export const useCuesheet = () => {
|
||||
selectedEventId: state.loaded.selectedEventId,
|
||||
selectedEventIndex: state.loaded.selectedEventIndex,
|
||||
numEvents: state.loaded.numEvents,
|
||||
titleNow: state.titles.titleNow,
|
||||
titleNow: state.eventNow?.title || '',
|
||||
});
|
||||
|
||||
return useRuntimeStore(featureSelector, deepCompare);
|
||||
|
||||
@@ -42,26 +42,10 @@ export const runtimeStorePlaceholder = {
|
||||
nextEventId: null,
|
||||
nextPublicEventId: null,
|
||||
},
|
||||
titles: {
|
||||
titleNow: null,
|
||||
subtitleNow: null,
|
||||
presenterNow: null,
|
||||
noteNow: null,
|
||||
titleNext: null,
|
||||
subtitleNext: null,
|
||||
presenterNext: null,
|
||||
noteNext: null,
|
||||
},
|
||||
titlesPublic: {
|
||||
titleNow: null,
|
||||
subtitleNow: null,
|
||||
presenterNow: null,
|
||||
noteNow: null,
|
||||
titleNext: null,
|
||||
subtitleNext: null,
|
||||
presenterNext: null,
|
||||
noteNext: null,
|
||||
},
|
||||
eventNow: null,
|
||||
eventNext: null,
|
||||
publicEventNow: null,
|
||||
publicEventNext: null,
|
||||
};
|
||||
|
||||
export const runtime = createStore<RuntimeStore>(() => ({
|
||||
|
||||
@@ -87,18 +87,6 @@ export const connectSocket = (preferredClientName?: string) => {
|
||||
runtime.setState(state);
|
||||
break;
|
||||
}
|
||||
case 'ontime-titles': {
|
||||
const state = runtime.getState();
|
||||
state.titles = payload;
|
||||
runtime.setState(state);
|
||||
break;
|
||||
}
|
||||
case 'ontime-titlesPublic': {
|
||||
const state = runtime.getState();
|
||||
state.titlesPublic = payload;
|
||||
runtime.setState(state);
|
||||
break;
|
||||
}
|
||||
case 'ontime-timerMessage': {
|
||||
const state = runtime.getState();
|
||||
state.timerMessage = payload;
|
||||
|
||||
@@ -12,17 +12,17 @@ export default function Info() {
|
||||
const showNif = useEditorSettings((state) => state.eventSettings.showNif);
|
||||
|
||||
const titlesNow = {
|
||||
title: data.titles.titleNow || '',
|
||||
subtitle: data.titles.subtitleNow || '',
|
||||
presenter: data.titles.presenterNow || '',
|
||||
note: data.titles.noteNow || '',
|
||||
title: data.eventNow?.title || '',
|
||||
subtitle: data.eventNow?.subtitle || '',
|
||||
presenter: data.eventNow?.presenter || '',
|
||||
note: data.eventNow?.note || '',
|
||||
};
|
||||
|
||||
const titlesNext = {
|
||||
title: data.titles.titleNext || '',
|
||||
subtitle: data.titles.subtitleNext || '',
|
||||
presenter: data.titles.presenterNext || '',
|
||||
note: data.titles.noteNext || '',
|
||||
title: data.eventNext?.title || '',
|
||||
subtitle: data.eventNext?.subtitle || '',
|
||||
presenter: data.eventNext?.presenter || '',
|
||||
note: data.eventNext?.note || '',
|
||||
};
|
||||
|
||||
const selected = !data.numEvents
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/* eslint-disable react/display-name */
|
||||
import { ComponentType, useMemo } from 'react';
|
||||
import { TitleBlock } from 'ontime-types';
|
||||
import { useStore } from 'zustand';
|
||||
|
||||
import useEventData from '../../common/hooks-query/useEventData';
|
||||
@@ -9,8 +8,6 @@ import useViewSettings from '../../common/hooks-query/useViewSettings';
|
||||
import { runtime } from '../../common/stores/runtime';
|
||||
import { useViewOptionsStore } from '../../common/stores/viewOptions';
|
||||
|
||||
export type TitleManager = TitleBlock & { showNow: boolean; showNext: boolean };
|
||||
|
||||
const withData = <P extends object>(Component: ComponentType<P>) => {
|
||||
return (props: Partial<P>) => {
|
||||
// persisted app state
|
||||
@@ -30,44 +27,22 @@ const withData = <P extends object>(Component: ComponentType<P>) => {
|
||||
|
||||
// websocket data
|
||||
const data = useStore(runtime);
|
||||
const { timer, titles, titlesPublic, publicMessage, timerMessage, lowerMessage, playback, onAir } = data;
|
||||
const publicSelectedId = data.loaded.selectedPublicEventId;
|
||||
const selectedId = data.loaded.selectedEventId;
|
||||
const nextId = data.loaded.nextEventId;
|
||||
|
||||
/********************************************/
|
||||
/*** + titleManager ***/
|
||||
/*** WRAP INFORMATION RELATED TO TITLES ***/
|
||||
/*** ---------------------------------- ***/
|
||||
/********************************************/
|
||||
// is there a now field?
|
||||
let showNow = true;
|
||||
if (!titles.titleNow && !titles.subtitleNow && !titles.presenterNow) showNow = false;
|
||||
|
||||
// is there a next field?
|
||||
let showNext = true;
|
||||
if (!titles.titleNext && !titles.subtitleNext && !titles.presenterNext) showNext = false;
|
||||
|
||||
const titleManager: TitleManager = { ...titles, showNow: showNow, showNext: showNext };
|
||||
|
||||
/********************************************/
|
||||
/*** + publicTitleManager ***/
|
||||
/*** WRAP INFORMATION RELATED TO TITLES ***/
|
||||
/*** ---------------------------------- ***/
|
||||
/********************************************/
|
||||
// is there a now field?
|
||||
let showPublicNow = true;
|
||||
if (!titlesPublic.titleNow && !titlesPublic.subtitleNow && !titlesPublic.presenterNow) showPublicNow = false;
|
||||
|
||||
// is there a next field?
|
||||
let showPublicNext = true;
|
||||
if (!titlesPublic.titleNext && !titlesPublic.subtitleNext && !titlesPublic.presenterNext) showPublicNext = false;
|
||||
|
||||
const publicTitleManager: TitleManager = {
|
||||
...titlesPublic,
|
||||
showNow: showPublicNow,
|
||||
showNext: showPublicNext,
|
||||
};
|
||||
const {
|
||||
timer,
|
||||
publicMessage,
|
||||
timerMessage,
|
||||
lowerMessage,
|
||||
playback,
|
||||
onAir,
|
||||
eventNext,
|
||||
publicEventNext,
|
||||
publicEventNow,
|
||||
eventNow,
|
||||
loaded,
|
||||
} = data;
|
||||
const publicSelectedId = loaded.selectedPublicEventId;
|
||||
const selectedId = loaded.selectedEventId;
|
||||
const nextId = loaded.nextEventId;
|
||||
|
||||
/******************************************/
|
||||
/*** + TimeManagerType ***/
|
||||
@@ -75,9 +50,6 @@ const withData = <P extends object>(Component: ComponentType<P>) => {
|
||||
/*** -------------------------------- ***/
|
||||
/******************************************/
|
||||
|
||||
// inject info:
|
||||
// is timer finished
|
||||
// get clock string
|
||||
const TimeManagerType = {
|
||||
...timer,
|
||||
playback,
|
||||
@@ -95,8 +67,10 @@ const withData = <P extends object>(Component: ComponentType<P>) => {
|
||||
pres={timerMessage}
|
||||
publ={publicMessage}
|
||||
lower={lowerMessage}
|
||||
title={titleManager}
|
||||
publicTitle={publicTitleManager}
|
||||
eventNow={eventNow}
|
||||
publicEventNow={publicEventNow}
|
||||
eventNext={eventNext}
|
||||
publicEventNext={publicEventNext}
|
||||
time={TimeManagerType}
|
||||
events={publicEvents}
|
||||
backstageEvents={rundownData}
|
||||
|
||||
@@ -18,7 +18,6 @@ import { TimeManagerType } from '../../../common/models/TimeManager.type';
|
||||
import { formatTime } from '../../../common/utils/time';
|
||||
import { useTranslation } from '../../../translation/TranslationProvider';
|
||||
import { titleVariants } from '../common/animation';
|
||||
import { TitleManager } from '../ViewWrapper';
|
||||
|
||||
import './Backstage.scss';
|
||||
|
||||
@@ -30,7 +29,8 @@ const formatOptions = {
|
||||
interface BackstageProps {
|
||||
isMirrored: boolean;
|
||||
publ: Message;
|
||||
title: TitleManager;
|
||||
eventNow: OntimeEvent | null;
|
||||
eventNext: OntimeEvent | null;
|
||||
time: TimeManagerType;
|
||||
backstageEvents: OntimeEvent[];
|
||||
selectedId: string | null;
|
||||
@@ -39,7 +39,7 @@ interface BackstageProps {
|
||||
}
|
||||
|
||||
export default function Backstage(props: BackstageProps) {
|
||||
const { isMirrored, publ, title, time, backstageEvents, selectedId, general, viewSettings } = props;
|
||||
const { isMirrored, publ, eventNow, eventNext, time, backstageEvents, selectedId, general, viewSettings } = props;
|
||||
const { shouldRender } = useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL);
|
||||
const { getLocalizedString } = useTranslation();
|
||||
const [blinkClass, setBlinkClass] = useState(false);
|
||||
@@ -110,7 +110,7 @@ export default function Backstage(props: BackstageProps) {
|
||||
|
||||
<div className='now-container'>
|
||||
<AnimatePresence>
|
||||
{title.showNow && (
|
||||
{eventNow && (
|
||||
<motion.div
|
||||
className={`event now ${blinkClass ? 'blink' : ''}`}
|
||||
key='now'
|
||||
@@ -121,9 +121,9 @@ export default function Backstage(props: BackstageProps) {
|
||||
>
|
||||
<TitleCard
|
||||
label='now'
|
||||
title={title.titleNow}
|
||||
subtitle={title.subtitleNow}
|
||||
presenter={title.presenterNow}
|
||||
title={eventNow.title}
|
||||
subtitle={eventNow.subtitle}
|
||||
presenter={eventNow.presenter}
|
||||
/>
|
||||
<div className='timer-group'>
|
||||
<div className='aux-timers'>
|
||||
@@ -144,7 +144,7 @@ export default function Backstage(props: BackstageProps) {
|
||||
</AnimatePresence>
|
||||
|
||||
<AnimatePresence>
|
||||
{title.showNext && (
|
||||
{eventNext && (
|
||||
<motion.div
|
||||
className='event next'
|
||||
key='next'
|
||||
@@ -155,9 +155,9 @@ export default function Backstage(props: BackstageProps) {
|
||||
>
|
||||
<TitleCard
|
||||
label='next'
|
||||
title={title.titleNext}
|
||||
subtitle={title.subtitleNext}
|
||||
presenter={title.presenterNext}
|
||||
title={eventNext.title}
|
||||
subtitle={eventNext.subtitle}
|
||||
presenter={eventNext.presenter}
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
@@ -5,7 +5,6 @@ import { Message } from 'ontime-types';
|
||||
import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu';
|
||||
import { LOWER_THIRDS_OPTIONS } from '../../../common/components/view-params-editor/constants';
|
||||
import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor';
|
||||
import { TitleManager } from '../ViewWrapper';
|
||||
|
||||
import { LowerOptions } from './LowerWrapper';
|
||||
|
||||
@@ -13,12 +12,14 @@ import './LowerLines.scss';
|
||||
|
||||
interface LowerLinesProps {
|
||||
lower: Message;
|
||||
title: TitleManager;
|
||||
heading: string;
|
||||
subheading: string;
|
||||
options: LowerOptions;
|
||||
doShow: boolean;
|
||||
}
|
||||
|
||||
export default function LowerLines(props: LowerLinesProps) {
|
||||
const { lower, title, options } = props;
|
||||
const { lower, heading, subheading, options, doShow } = props;
|
||||
const [showLower, setShowLower] = useState(true);
|
||||
|
||||
// Unmount if fadeOut
|
||||
@@ -36,8 +37,8 @@ export default function LowerLines(props: LowerLinesProps) {
|
||||
}, [options.fadeOut, options.transitionIn]);
|
||||
|
||||
useEffect(() => {
|
||||
setShowLower(title.showNow);
|
||||
}, [title.showNow]);
|
||||
setShowLower(doShow);
|
||||
}, [doShow]);
|
||||
|
||||
// Format messages
|
||||
const showLowerMessage = lower.text !== '' && lower.visible;
|
||||
@@ -146,14 +147,14 @@ export default function LowerLines(props: LowerLinesProps) {
|
||||
>
|
||||
<motion.div className='title-container' variants={titleContainerVariants}>
|
||||
<motion.div className='title' variants={titleVariants}>
|
||||
{title.titleNow}
|
||||
{heading}
|
||||
</motion.div>
|
||||
<div className='title-decor' />
|
||||
</motion.div>
|
||||
<motion.div className='subtitle-container' variants={subtitleContainerVariants}>
|
||||
<div className='sub-decor' />
|
||||
<motion.div className='subtitle' variants={subtitleVariants}>
|
||||
{title.presenterNow}
|
||||
{subheading}
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { memo, useEffect, useState } from 'react';
|
||||
import isEqual from 'react-fast-compare';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { Message, ViewSettings } from 'ontime-types';
|
||||
import { Message, OntimeEvent, ViewSettings } from 'ontime-types';
|
||||
|
||||
import { overrideStylesURL } from '../../../common/api/apiConstants';
|
||||
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
|
||||
import { TitleManager } from '../ViewWrapper';
|
||||
|
||||
import LowerLines from './LowerLines';
|
||||
|
||||
@@ -17,33 +16,26 @@ export type LowerOptions = {
|
||||
keyColour?: string;
|
||||
fadeOut: number;
|
||||
};
|
||||
|
||||
interface LowerProps {
|
||||
title: TitleManager;
|
||||
eventNow: OntimeEvent | null;
|
||||
lower: Message;
|
||||
viewSettings: ViewSettings;
|
||||
}
|
||||
|
||||
// prevent triggering animation without a content change
|
||||
const areEqual = (prevProps: LowerProps, nextProps: LowerProps) => {
|
||||
return isEqual(prevProps.title, nextProps.title) && isEqual(prevProps.lower, nextProps.lower);
|
||||
return isEqual(prevProps.eventNow?.title, nextProps.eventNow?.title) && isEqual(prevProps.lower, nextProps.lower);
|
||||
};
|
||||
|
||||
const Lower = (props: LowerProps) => {
|
||||
const { title, lower, viewSettings } = props;
|
||||
const { eventNow, lower, viewSettings } = props;
|
||||
const { shouldRender } = useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL);
|
||||
const [searchParams] = useSearchParams();
|
||||
const [titles, setTitles] = useState<TitleManager>({
|
||||
titleNow: '',
|
||||
titleNext: '',
|
||||
subtitleNow: '',
|
||||
subtitleNext: '',
|
||||
presenterNow: '',
|
||||
presenterNext: '',
|
||||
noteNow: '',
|
||||
noteNext: '',
|
||||
showNow: false,
|
||||
showNext: false,
|
||||
});
|
||||
|
||||
const [heading, setHeading] = useState('');
|
||||
const [subheading, setSubheading] = useState('');
|
||||
const [showLower, setShowLower] = useState(false);
|
||||
|
||||
// Set window title
|
||||
useEffect(() => {
|
||||
@@ -54,28 +46,32 @@ const Lower = (props: LowerProps) => {
|
||||
useEffect(() => {
|
||||
// clear titles if necessary
|
||||
// will trigger an animation out in the component
|
||||
let timeout: NodeJS.Timeout | null = null;
|
||||
if (
|
||||
title?.titleNow !== titles?.titleNow ||
|
||||
title?.subtitleNow !== titles?.subtitleNow ||
|
||||
title?.presenterNow !== titles?.presenterNow
|
||||
) {
|
||||
setTitles((t) => ({ ...t, showNow: false }));
|
||||
let timeout: NodeJS.Timeout;
|
||||
|
||||
const transitionTime = 2000;
|
||||
const haveTitlesChanged = eventNow?.title !== heading || eventNow?.presenter !== subheading;
|
||||
const areTitlesEmpty = !eventNow?.title && !eventNow?.presenter;
|
||||
|
||||
// we have new titles
|
||||
if (haveTitlesChanged && !areTitlesEmpty) {
|
||||
// show lower
|
||||
setHeading(eventNow?.title ?? '');
|
||||
setSubheading(eventNow?.presenter ?? '');
|
||||
setShowLower(true);
|
||||
|
||||
// schedule transition out
|
||||
const transitionTime = 5000;
|
||||
timeout = setTimeout(() => {
|
||||
setTitles(title);
|
||||
setShowLower(false);
|
||||
}, transitionTime);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (timeout != null) {
|
||||
if (timeout) {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
};
|
||||
// eslint-disable-next-line -- we do this to keep animations
|
||||
}, [title.titleNow, title.subtitleNow, title.presenterNow]);
|
||||
}, [eventNow?.title, eventNow?.presenter]);
|
||||
|
||||
// defer rendering until we load stylesheets
|
||||
if (!shouldRender) {
|
||||
@@ -135,7 +131,7 @@ const Lower = (props: LowerProps) => {
|
||||
}
|
||||
}
|
||||
|
||||
return <LowerLines lower={lower} title={titles} options={options} />;
|
||||
return <LowerLines lower={lower} heading={heading} subheading={subheading} options={options} doShow={showLower} />;
|
||||
};
|
||||
|
||||
export default memo(Lower, areEqual);
|
||||
|
||||
@@ -16,7 +16,6 @@ import { TimeManagerType } from '../../../common/models/TimeManager.type';
|
||||
import { formatTime } from '../../../common/utils/time';
|
||||
import { useTranslation } from '../../../translation/TranslationProvider';
|
||||
import { titleVariants } from '../common/animation';
|
||||
import { TitleManager } from '../ViewWrapper';
|
||||
|
||||
import './Public.scss';
|
||||
|
||||
@@ -28,7 +27,8 @@ const formatOptions = {
|
||||
interface BackstageProps {
|
||||
isMirrored: boolean;
|
||||
publ: Message;
|
||||
publicTitle: TitleManager;
|
||||
publicEventNow: OntimeEvent | null;
|
||||
publicEventNext: OntimeEvent | null;
|
||||
time: TimeManagerType;
|
||||
events: OntimeEvent[];
|
||||
publicSelectedId: string | null;
|
||||
@@ -37,7 +37,8 @@ interface BackstageProps {
|
||||
}
|
||||
|
||||
export default function Public(props: BackstageProps) {
|
||||
const { isMirrored, publ, publicTitle, time, events, publicSelectedId, general, viewSettings } = props;
|
||||
const { isMirrored, publ, publicEventNow, publicEventNext, time, events, publicSelectedId, general, viewSettings } =
|
||||
props;
|
||||
const { shouldRender } = useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL);
|
||||
const { getLocalizedString } = useTranslation();
|
||||
|
||||
@@ -68,7 +69,7 @@ export default function Public(props: BackstageProps) {
|
||||
|
||||
<div className='now-container'>
|
||||
<AnimatePresence>
|
||||
{publicTitle.showNow && (
|
||||
{publicEventNow && (
|
||||
<motion.div
|
||||
className='event now'
|
||||
key='now'
|
||||
@@ -79,16 +80,16 @@ export default function Public(props: BackstageProps) {
|
||||
>
|
||||
<TitleCard
|
||||
label='now'
|
||||
title={publicTitle.titleNow}
|
||||
subtitle={publicTitle.subtitleNow}
|
||||
presenter={publicTitle.presenterNow}
|
||||
title={publicEventNow.title}
|
||||
subtitle={publicEventNow.subtitle}
|
||||
presenter={publicEventNow.presenter}
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
<AnimatePresence>
|
||||
{publicTitle.showNext && (
|
||||
{publicEventNext && (
|
||||
<motion.div
|
||||
className='event next'
|
||||
key='next'
|
||||
@@ -99,9 +100,9 @@ export default function Public(props: BackstageProps) {
|
||||
>
|
||||
<TitleCard
|
||||
label='next'
|
||||
title={publicTitle.titleNext}
|
||||
subtitle={publicTitle.subtitleNext}
|
||||
presenter={publicTitle.presenterNext}
|
||||
title={publicEventNext.title}
|
||||
subtitle={publicEventNext.subtitle}
|
||||
presenter={publicEventNext.presenter}
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
@@ -13,7 +13,6 @@ import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet
|
||||
import { TimeManagerType } from '../../../common/models/TimeManager.type';
|
||||
import { secondsInMillis } from '../../../common/utils/dateConfig';
|
||||
import { formatTime } from '../../../common/utils/time';
|
||||
import { TitleManager } from '../ViewWrapper';
|
||||
|
||||
import { type ScheduleEvent, formatEventList, trimRundown } from './studioClock.utils';
|
||||
|
||||
@@ -26,7 +25,7 @@ const formatOptions = {
|
||||
|
||||
interface StudioClockProps {
|
||||
isMirrored: boolean;
|
||||
title: TitleManager;
|
||||
eventNext: OntimeEvent | null;
|
||||
time: TimeManagerType;
|
||||
backstageEvents: OntimeRundown;
|
||||
selectedId: string | null;
|
||||
@@ -36,7 +35,7 @@ interface StudioClockProps {
|
||||
}
|
||||
|
||||
export default function StudioClock(props: StudioClockProps) {
|
||||
const { isMirrored, title, time, backstageEvents, selectedId, nextId, onAir, viewSettings } = props;
|
||||
const { isMirrored, eventNext, time, backstageEvents, selectedId, nextId, onAir, viewSettings } = props;
|
||||
|
||||
// deferring rendering seems to affect styling (font and useFitText)
|
||||
useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL);
|
||||
@@ -87,7 +86,7 @@ export default function StudioClock(props: StudioClockProps) {
|
||||
className='next-title'
|
||||
style={{ fontSize: titleFontSize, height: '10vh', width: '100%', maxWidth: '75%' }}
|
||||
>
|
||||
{title.titleNext}
|
||||
{eventNext?.title ?? ''}
|
||||
</div>
|
||||
<div className={isNegative ? 'next-countdown' : 'next-countdown next-countdown--overtime'}>
|
||||
{selectedId !== null && formatDisplay(time.current)}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect } from 'react';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { EventData, Playback, TimerMessage, TimerType, ViewSettings } from 'ontime-types';
|
||||
import { EventData, OntimeEvent, Playback, TimerMessage, TimerType, ViewSettings } from 'ontime-types';
|
||||
|
||||
import { overrideStylesURL } from '../../../common/api/apiConstants';
|
||||
import MultiPartProgressBar from '../../../common/components/multi-part-progress-bar/MultiPartProgressBar';
|
||||
@@ -13,7 +13,6 @@ import { TimeManagerType } from '../../../common/models/TimeManager.type';
|
||||
import { formatTime } from '../../../common/utils/time';
|
||||
import { useTranslation } from '../../../translation/TranslationProvider';
|
||||
import { formatTimerDisplay, getTimerByType } from '../common/viewerUtils';
|
||||
import { TitleManager } from '../ViewWrapper';
|
||||
|
||||
import './Timer.scss';
|
||||
|
||||
@@ -42,13 +41,14 @@ interface TimerProps {
|
||||
isMirrored: boolean;
|
||||
general: EventData;
|
||||
pres: TimerMessage;
|
||||
title: TitleManager;
|
||||
eventNow: OntimeEvent | null;
|
||||
eventNext: OntimeEvent | null;
|
||||
time: TimeManagerType;
|
||||
viewSettings: ViewSettings;
|
||||
}
|
||||
|
||||
export default function Timer(props: TimerProps) {
|
||||
const { isMirrored, pres, title, time, viewSettings } = props;
|
||||
const { isMirrored, pres, eventNow, eventNext, time, viewSettings } = props;
|
||||
const { shouldRender } = useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL);
|
||||
const { getLocalizedString } = useTranslation();
|
||||
|
||||
@@ -139,7 +139,7 @@ export default function Timer(props: TimerProps) {
|
||||
/>
|
||||
|
||||
<AnimatePresence>
|
||||
{title.showNow && !finished && (
|
||||
{eventNow && !finished && (
|
||||
<motion.div
|
||||
className='event now'
|
||||
key='now'
|
||||
@@ -148,13 +148,13 @@ export default function Timer(props: TimerProps) {
|
||||
animate='visible'
|
||||
exit='exit'
|
||||
>
|
||||
<TitleCard label='now' title={title.titleNow} subtitle={title.subtitleNow} presenter={title.presenterNow} />
|
||||
<TitleCard label='now' title={eventNow.title} subtitle={eventNow.subtitle} presenter={eventNow.presenter} />
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
<AnimatePresence>
|
||||
{title.showNext && (
|
||||
{eventNext && (
|
||||
<motion.div
|
||||
className='event next'
|
||||
key='next'
|
||||
@@ -165,9 +165,9 @@ export default function Timer(props: TimerProps) {
|
||||
>
|
||||
<TitleCard
|
||||
label='next'
|
||||
title={title.titleNext}
|
||||
subtitle={title.subtitleNext}
|
||||
presenter={title.presenterNext}
|
||||
title={eventNext.title}
|
||||
subtitle={eventNext.subtitle}
|
||||
presenter={eventNext.presenter}
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Loaded, OntimeEvent, SupportedEvent, TitleBlock } from 'ontime-types';
|
||||
import { Loaded, OntimeEvent, SupportedEvent } from 'ontime-types';
|
||||
|
||||
import { DataProvider } from '../data-provider/DataProvider.js';
|
||||
import { getRollTimers } from '../../services/rollUtils.js';
|
||||
@@ -10,10 +10,11 @@ let instance;
|
||||
* Manages business logic around loading events
|
||||
*/
|
||||
export class EventLoader {
|
||||
loadedEvent: OntimeEvent | null;
|
||||
loaded: Loaded;
|
||||
titles: TitleBlock;
|
||||
titlesPublic: TitleBlock;
|
||||
eventNow: OntimeEvent | null;
|
||||
publicEventNow: OntimeEvent | null;
|
||||
eventNext: OntimeEvent | null;
|
||||
publicEventNext: OntimeEvent | null;
|
||||
|
||||
constructor() {
|
||||
if (instance) {
|
||||
@@ -56,7 +57,7 @@ export class EventLoader {
|
||||
}
|
||||
|
||||
/**
|
||||
* returns an event given its index
|
||||
* returns an event given its index after filtering for OntimeEvents
|
||||
* @param {number} eventIndex
|
||||
* @return {OntimeEvent | undefined}
|
||||
*/
|
||||
@@ -65,16 +66,6 @@ export class EventLoader {
|
||||
return timedEvents?.[eventIndex];
|
||||
}
|
||||
|
||||
/**
|
||||
* returns an event given its index
|
||||
* @param {number} eventIndex
|
||||
* @return {object | undefined}
|
||||
*/
|
||||
static getPlayableAtIndex(eventIndex) {
|
||||
const timedEvents = EventLoader.getPlayableEvents();
|
||||
return timedEvents?.[eventIndex];
|
||||
}
|
||||
|
||||
/**
|
||||
* returns an event given its id
|
||||
* @param {string} eventId
|
||||
@@ -167,16 +158,18 @@ export class EventLoader {
|
||||
timeNow,
|
||||
);
|
||||
|
||||
this.loadedEvent = currentEvent;
|
||||
// load events
|
||||
this.eventNow = currentEvent;
|
||||
this.publicEventNow = currentPublicEvent;
|
||||
this.eventNext = nextEvent;
|
||||
this.publicEventNext = nextPublicEvent;
|
||||
|
||||
// loaded data summary
|
||||
this.loaded.selectedEventIndex = nowIndex;
|
||||
this.loaded.selectedEventId = currentEvent?.id || null;
|
||||
this.loaded.numEvents = timedEvents.length;
|
||||
|
||||
// titles
|
||||
this._loadThisTitles(currentEvent, 'now-private');
|
||||
this._loadThisTitles(currentPublicEvent, 'now-public');
|
||||
this._loadThisTitles(nextEvent, 'next-private');
|
||||
this._loadThisTitles(nextPublicEvent, 'next-public');
|
||||
this.loaded.nextEventId = nextEvent.id;
|
||||
this.loaded.nextPublicEventId = nextPublicEvent.id;
|
||||
|
||||
return { currentEvent, nextEvent, timeToNext };
|
||||
}
|
||||
@@ -187,10 +180,11 @@ export class EventLoader {
|
||||
*/
|
||||
getLoaded() {
|
||||
return {
|
||||
loadedEvent: this.loadedEvent,
|
||||
loaded: this.loaded,
|
||||
titles: this.titles,
|
||||
titlesPublic: this.titlesPublic,
|
||||
eventNow: this.eventNow,
|
||||
publicEventNow: this.publicEventNow,
|
||||
eventNext: this.eventNext,
|
||||
publicEventNext: this.publicEventNext,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -206,7 +200,10 @@ export class EventLoader {
|
||||
* Resets instance state
|
||||
*/
|
||||
reset(emit = true) {
|
||||
this.loadedEvent = null;
|
||||
this.eventNow = null;
|
||||
this.publicEventNow = null;
|
||||
this.eventNext = null;
|
||||
this.publicEventNext = null;
|
||||
this.loaded = {
|
||||
selectedEventIndex: null,
|
||||
selectedEventId: null,
|
||||
@@ -215,26 +212,6 @@ export class EventLoader {
|
||||
nextPublicEventId: null,
|
||||
numEvents: EventLoader.getPlayableEvents().length,
|
||||
};
|
||||
this.titles = {
|
||||
titleNow: null,
|
||||
subtitleNow: null,
|
||||
presenterNow: null,
|
||||
noteNow: null,
|
||||
titleNext: null,
|
||||
subtitleNext: null,
|
||||
presenterNext: null,
|
||||
noteNext: null,
|
||||
};
|
||||
this.titlesPublic = {
|
||||
titleNow: null,
|
||||
subtitleNow: null,
|
||||
presenterNow: null,
|
||||
noteNow: null,
|
||||
titleNext: null,
|
||||
subtitleNext: null,
|
||||
presenterNext: null,
|
||||
noteNext: null,
|
||||
};
|
||||
|
||||
// workaround for socket not being ready in constructor
|
||||
if (emit) {
|
||||
@@ -255,13 +232,12 @@ export class EventLoader {
|
||||
const playableEvents = EventLoader.getPlayableEvents();
|
||||
|
||||
// we know some stuff now
|
||||
this.loadedEvent = event;
|
||||
this.loaded.selectedEventIndex = eventIndex;
|
||||
this.loaded.selectedEventId = event.id;
|
||||
this.loaded.numEvents = timedEvents.length;
|
||||
// this.nextEventId = playableEvents[eventIndex + 1].id;
|
||||
this._loadTitlesNow(event, playableEvents);
|
||||
this._loadTitlesNext(playableEvents);
|
||||
this.eventNow = event;
|
||||
this._loadEventNow(event, playableEvents);
|
||||
this._loadEventNext(playableEvents);
|
||||
|
||||
this._loadEvent();
|
||||
|
||||
@@ -274,29 +250,28 @@ export class EventLoader {
|
||||
private _loadEvent() {
|
||||
eventStore.batchSet({
|
||||
loaded: this.loaded,
|
||||
titles: this.titles,
|
||||
titlesPublic: this.titlesPublic,
|
||||
eventNow: this.eventNow,
|
||||
publicEventNow: this.publicEventNow,
|
||||
eventNext: this.eventNext,
|
||||
publicEventNext: this.publicEventNext,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description loads given title (now)
|
||||
* @description loads currently running events
|
||||
* @private
|
||||
* @param {object} event
|
||||
* @param {array} rundown
|
||||
*/
|
||||
private _loadTitlesNow(event, rundown) {
|
||||
// private title is always current
|
||||
private _loadEventNow(event, rundown) {
|
||||
this.eventNow = event;
|
||||
|
||||
// check if current is also public
|
||||
if (event.isPublic) {
|
||||
this._loadThisTitles(event, 'now');
|
||||
this.publicEventNow = event;
|
||||
} else {
|
||||
this._loadThisTitles(event, 'now-private');
|
||||
|
||||
// assume there is no public event
|
||||
this.titlesPublic.titleNow = null;
|
||||
this.titlesPublic.subtitleNow = null;
|
||||
this.titlesPublic.presenterNow = null;
|
||||
this.publicEventNow = null;
|
||||
this.loaded.selectedPublicEventId = null;
|
||||
|
||||
// if there is nothing before, return
|
||||
@@ -305,7 +280,8 @@ export class EventLoader {
|
||||
// iterate backwards to find it
|
||||
for (let i = this.loaded.selectedEventIndex; i >= 0; i--) {
|
||||
if (rundown[i].isPublic) {
|
||||
this._loadThisTitles(rundown[i], 'now-public');
|
||||
this.publicEventNow = rundown[i];
|
||||
this.loaded.selectedPublicEventId = rundown[i].id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -313,180 +289,44 @@ export class EventLoader {
|
||||
}
|
||||
|
||||
/**
|
||||
* @description look for next titles to load
|
||||
* @description look for next events
|
||||
* @private
|
||||
*/
|
||||
private _loadTitlesNext(rundown) {
|
||||
// maybe there is nothing to load
|
||||
if (this.loaded.selectedEventIndex === null) return;
|
||||
|
||||
// assume there is no next event
|
||||
this.titles.titleNext = null;
|
||||
this.titles.subtitleNext = null;
|
||||
this.titles.presenterNext = null;
|
||||
this.titles.noteNext = null;
|
||||
private _loadEventNext(rundown) {
|
||||
// assume there are no next events
|
||||
this.eventNext = null;
|
||||
this.publicEventNext = null;
|
||||
this.loaded.nextEventId = null;
|
||||
|
||||
this.titlesPublic.titleNext = null;
|
||||
this.titlesPublic.subtitleNext = null;
|
||||
this.titlesPublic.presenterNext = null;
|
||||
this.loaded.nextPublicEventId = null;
|
||||
|
||||
if (this.loaded.selectedEventIndex === null) return;
|
||||
|
||||
const numEvents = rundown.length;
|
||||
|
||||
if (this.loaded.selectedEventIndex < numEvents - 1) {
|
||||
let nextPublic = false;
|
||||
let nextPrivate = false;
|
||||
let nextProduction = false;
|
||||
|
||||
for (let i = this.loaded.selectedEventIndex + 1; i < numEvents; i++) {
|
||||
// if we have not set private
|
||||
if (!nextPrivate) {
|
||||
this._loadThisTitles(rundown[i], 'next-private');
|
||||
nextPrivate = true;
|
||||
if (!nextProduction) {
|
||||
this.eventNext = rundown[i];
|
||||
this.loaded.nextEventId = rundown[i].id;
|
||||
nextProduction = true;
|
||||
}
|
||||
|
||||
// if event is public
|
||||
if (rundown[i].isPublic) {
|
||||
this._loadThisTitles(rundown[i], 'next-public');
|
||||
this.publicEventNext = rundown[i];
|
||||
this.loaded.nextPublicEventId = rundown[i].id;
|
||||
nextPublic = true;
|
||||
}
|
||||
|
||||
// Stop if both are set
|
||||
if (nextPublic && nextPrivate) break;
|
||||
if (nextPublic && nextProduction) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description loads given title
|
||||
* @param event
|
||||
* @param type
|
||||
* @private
|
||||
*/
|
||||
private _loadThisTitles(event, type) {
|
||||
if (type === 'now') {
|
||||
if (event === null) {
|
||||
// public
|
||||
this.titlesPublic.titleNow = null;
|
||||
this.titlesPublic.subtitleNow = null;
|
||||
this.titlesPublic.presenterNow = null;
|
||||
this.titlesPublic.noteNow = null;
|
||||
this.loaded.selectedPublicEventId = null;
|
||||
|
||||
// private
|
||||
this.titles.titleNow = null;
|
||||
this.titles.subtitleNow = null;
|
||||
this.titles.presenterNow = null;
|
||||
this.titles.noteNow = null;
|
||||
this.loaded.selectedEventId = null;
|
||||
} else {
|
||||
// public
|
||||
this.titlesPublic.titleNow = event.title;
|
||||
this.titlesPublic.subtitleNow = event.subtitle;
|
||||
this.titlesPublic.presenterNow = event.presenter;
|
||||
this.titlesPublic.noteNow = event.note;
|
||||
this.loaded.selectedPublicEventId = event.id;
|
||||
|
||||
// private
|
||||
this.titles.titleNow = event.title;
|
||||
this.titles.subtitleNow = event.subtitle;
|
||||
this.titles.presenterNow = event.presenter;
|
||||
this.titles.noteNow = event.note;
|
||||
this.loaded.selectedEventId = event.id;
|
||||
}
|
||||
} else if (type === 'now-public') {
|
||||
if (event === null) {
|
||||
this.titlesPublic.titleNow = null;
|
||||
this.titlesPublic.subtitleNow = null;
|
||||
this.titlesPublic.presenterNow = null;
|
||||
this.titlesPublic.noteNow = null;
|
||||
this.loaded.selectedPublicEventId = null;
|
||||
} else {
|
||||
this.titlesPublic.titleNow = event.title;
|
||||
this.titlesPublic.subtitleNow = event.subtitle;
|
||||
this.titlesPublic.presenterNow = event.presenter;
|
||||
this.titlesPublic.noteNow = event.note;
|
||||
this.loaded.selectedPublicEventId = event.id;
|
||||
}
|
||||
} else if (type === 'now-private') {
|
||||
if (event === null) {
|
||||
this.titles.titleNow = null;
|
||||
this.titles.subtitleNow = null;
|
||||
this.titles.presenterNow = null;
|
||||
this.titles.noteNow = null;
|
||||
this.loaded.selectedEventId = null;
|
||||
} else {
|
||||
this.titles.titleNow = event.title;
|
||||
this.titles.subtitleNow = event.subtitle;
|
||||
this.titles.presenterNow = event.presenter;
|
||||
this.titles.noteNow = event.note;
|
||||
this.loaded.selectedEventId = event.id;
|
||||
}
|
||||
}
|
||||
|
||||
// next, load to both public and private
|
||||
else if (type === 'next') {
|
||||
if (event === null) {
|
||||
// public
|
||||
this.titlesPublic.titleNext = null;
|
||||
this.titlesPublic.subtitleNext = null;
|
||||
this.titlesPublic.presenterNext = null;
|
||||
this.titlesPublic.noteNext = null;
|
||||
this.loaded.nextPublicEventId = null;
|
||||
|
||||
// private
|
||||
this.titles.titleNext = null;
|
||||
this.titles.subtitleNext = null;
|
||||
this.titles.presenterNext = null;
|
||||
this.titles.noteNext = null;
|
||||
this.loaded.nextEventId = null;
|
||||
} else {
|
||||
// public
|
||||
this.titlesPublic.titleNext = event.title;
|
||||
this.titlesPublic.subtitleNext = event.subtitle;
|
||||
this.titlesPublic.presenterNext = event.presenter;
|
||||
this.titlesPublic.noteNext = event.note;
|
||||
this.loaded.nextPublicEventId = event.id;
|
||||
|
||||
// private
|
||||
this.titles.titleNext = event.title;
|
||||
this.titles.subtitleNext = event.subtitle;
|
||||
this.titles.presenterNext = event.presenter;
|
||||
this.titles.noteNext = event.note;
|
||||
this.loaded.nextEventId = event.id;
|
||||
}
|
||||
} else if (type === 'next-public') {
|
||||
if (event === null) {
|
||||
this.titlesPublic.titleNext = null;
|
||||
this.titlesPublic.subtitleNext = null;
|
||||
this.titlesPublic.presenterNext = null;
|
||||
this.titlesPublic.noteNext = null;
|
||||
this.loaded.nextPublicEventId = null;
|
||||
} else {
|
||||
this.titlesPublic.titleNext = event.title;
|
||||
this.titlesPublic.subtitleNext = event.subtitle;
|
||||
this.titlesPublic.presenterNext = event.presenter;
|
||||
this.titlesPublic.noteNext = event.note;
|
||||
this.loaded.nextPublicEventId = event.id;
|
||||
}
|
||||
} else if (type === 'next-private') {
|
||||
if (event === null) {
|
||||
this.titles.titleNext = null;
|
||||
this.titles.subtitleNext = null;
|
||||
this.titles.presenterNext = null;
|
||||
this.titles.noteNext = null;
|
||||
this.loaded.nextEventId = null;
|
||||
} else {
|
||||
this.titles.titleNext = event.title;
|
||||
this.titles.subtitleNext = event.subtitle;
|
||||
this.titles.presenterNext = event.presenter;
|
||||
this.titles.noteNext = event.note;
|
||||
this.loaded.nextEventId = event.id;
|
||||
}
|
||||
} else {
|
||||
throw new Error(`Unhandled title type: ${type}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const eventLoader = new EventLoader();
|
||||
|
||||
@@ -116,8 +116,8 @@ export function updateTimer(affectedIds?: string[]) {
|
||||
|
||||
if (safeOption) {
|
||||
eventLoader.reset();
|
||||
const { loadedEvent } = eventLoader.loadById(runningEventId) || {};
|
||||
eventTimer.hotReload(loadedEvent);
|
||||
const { eventNow } = eventLoader.loadById(runningEventId) || {};
|
||||
eventTimer.hotReload(eventNow);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -133,9 +133,9 @@ export function updateTimer(affectedIds?: string[]) {
|
||||
eventTimer.roll(currentEvent, nextEvent);
|
||||
}
|
||||
} else {
|
||||
const { loadedEvent } = eventLoader.loadById(runningEventId) || {};
|
||||
if (loadedEvent) {
|
||||
eventTimer.hotReload(loadedEvent);
|
||||
const { eventNow } = eventLoader.loadById(runningEventId) || {};
|
||||
if (eventNow) {
|
||||
eventTimer.hotReload(eventNow);
|
||||
} else {
|
||||
eventTimer.stop();
|
||||
}
|
||||
@@ -144,8 +144,8 @@ export function updateTimer(affectedIds?: string[]) {
|
||||
}
|
||||
|
||||
if (isNext) {
|
||||
const { loadedEvent } = eventLoader.loadById(runningEventId) || {};
|
||||
eventTimer.hotReload(loadedEvent);
|
||||
const { eventNow } = eventLoader.loadById(runningEventId) || {};
|
||||
eventTimer.hotReload(eventNow);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -56,8 +56,10 @@ export const eventStore = {
|
||||
* - Message Service lowerMessage
|
||||
* - Message Service onAir
|
||||
* - Event Loader loaded
|
||||
* - Event Loader titles
|
||||
* - Event Loader titlesPublic
|
||||
* - Event Loader eventNow
|
||||
* - Event Loader publicEventNow
|
||||
* - Event Loader eventNext
|
||||
* - Event Loader publicEventNext
|
||||
*/
|
||||
|
||||
export const getInitialPayload = () => ({
|
||||
@@ -68,6 +70,8 @@ export const getInitialPayload = () => ({
|
||||
lowerMessage: messageService.lowerMessage,
|
||||
onAir: messageService.onAir,
|
||||
loaded: eventLoader.loaded,
|
||||
titles: eventLoader.titles,
|
||||
titlesPublic: eventLoader.titlesPublic,
|
||||
eventNow: eventLoader.eventNow,
|
||||
publicEventNow: eventLoader.publicEventNow,
|
||||
eventNext: eventLoader.eventNext,
|
||||
publicEventNext: eventLoader.publicEventNext,
|
||||
});
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Playback } from './Playback.type.js';
|
||||
import { Message, TimerMessage } from './MessageControl.type.js';
|
||||
import { TimerState } from './TimerState.type.js';
|
||||
import { TitleBlock } from './TitleBlock.type.js';
|
||||
import { Loaded } from './Playlist.type.js';
|
||||
import { OntimeEvent } from '../core/OntimeEvent.type.js';
|
||||
|
||||
export type RuntimeStore = {
|
||||
// timer service
|
||||
@@ -17,6 +17,8 @@ export type RuntimeStore = {
|
||||
|
||||
// event loader
|
||||
loaded: Loaded;
|
||||
titles: TitleBlock;
|
||||
titlesPublic: TitleBlock;
|
||||
eventNow: OntimeEvent | null;
|
||||
publicEventNow: OntimeEvent | null;
|
||||
eventNext: OntimeEvent | null;
|
||||
publicEventNext: OntimeEvent | null;
|
||||
};
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
export type TitleBlock = {
|
||||
titleNow: string | null;
|
||||
subtitleNow: string | null;
|
||||
presenterNow: string | null;
|
||||
noteNow: string | null;
|
||||
titleNext: string | null;
|
||||
subtitleNext: string | null;
|
||||
presenterNext: string | null;
|
||||
noteNext: string | null;
|
||||
};
|
||||
@@ -5,8 +5,8 @@ export type { DatabaseModel } from './definitions/DataModel.type.js';
|
||||
export { EndAction } from './definitions/EndAction.type.js';
|
||||
export {
|
||||
type OntimeBaseEvent,
|
||||
type OntimeBlock,
|
||||
type OntimeDelay,
|
||||
type OntimeBlock,
|
||||
type OntimeEvent,
|
||||
SupportedEvent,
|
||||
} from './definitions/core/OntimeEvent.type.js';
|
||||
@@ -42,7 +42,6 @@ export type { Message, TimerMessage } from './definitions/runtime/MessageControl
|
||||
export type { Loaded } from './definitions/runtime/Playlist.type.js';
|
||||
export type { RuntimeStore } from './definitions/runtime/RuntimeStore.type.js';
|
||||
export type { TimerState } from './definitions/runtime/TimerState.type.js';
|
||||
export type { TitleBlock } from './definitions/runtime/TitleBlock.type.js';
|
||||
|
||||
// CLIENT
|
||||
|
||||
|
||||
Reference in New Issue
Block a user