refactor: pass events instead of derived data to titles (#493)

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