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
+8 -8
View File
@@ -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>
)}