diff --git a/apps/client/src/AppRouter.tsx b/apps/client/src/AppRouter.tsx index de9aa56c2..e8879e96a 100644 --- a/apps/client/src/AppRouter.tsx +++ b/apps/client/src/AppRouter.tsx @@ -60,6 +60,7 @@ export default function AppRouter() { path='backstage' element={ + } diff --git a/apps/client/src/common/hooks/useSocket.ts b/apps/client/src/common/hooks/useSocket.ts index ccbc5bc56..698949387 100644 --- a/apps/client/src/common/hooks/useSocket.ts +++ b/apps/client/src/common/hooks/useSocket.ts @@ -248,3 +248,11 @@ export const useCountdownSocket = createSelector((state: RuntimeStore) => ({ current: state.timer.current, clock: state.clock, })); + +export const useBackstageSocket = createSelector((state: RuntimeStore) => ({ + eventNext: state.eventNext, + eventNow: state.eventNow, + runtime: state.runtime, + selectedEventId: state.eventNow?.id ?? null, + time: state.timer, +})); diff --git a/apps/client/src/views/backstage/Backstage.tsx b/apps/client/src/views/backstage/Backstage.tsx index fe64eeaa5..66d1cfcb5 100644 --- a/apps/client/src/views/backstage/Backstage.tsx +++ b/apps/client/src/views/backstage/Backstage.tsx @@ -1,59 +1,53 @@ import { useEffect, useMemo, useState } from 'react'; import QRCode from 'react-qr-code'; import { useViewportSize } from '@mantine/hooks'; -import { CustomFields, OntimeEvent, OntimeView, ProjectData, Runtime, Settings } from 'ontime-types'; +import { OntimeView, ProjectData } from 'ontime-types'; import { millisToString, removeLeadingZero } from 'ontime-utils'; import ProgressBar from '../../common/components/progress-bar/ProgressBar'; import Empty from '../../common/components/state/Empty'; +import EmptyPage from '../../common/components/state/EmptyPage'; import TitleCard from '../../common/components/title-card/TitleCard'; import ViewLogo from '../../common/components/view-logo/ViewLogo'; import ViewParamsEditor from '../../common/components/view-params-editor/ViewParamsEditor'; +import { useBackstageSocket, useClock } from '../../common/hooks/useSocket'; import { useWindowTitle } from '../../common/hooks/useWindowTitle'; -import { ViewExtendedTimer } from '../../common/models/TimeManager.type'; import { cx, timerPlaceholderMin } from '../../common/utils/styleUtils'; import { formatTime, getDefaultFormat } from '../../common/utils/time'; import SuperscriptTime from '../../features/viewers/common/superscript-time/SuperscriptTime'; import { useTranslation } from '../../translation/TranslationProvider'; +import Loader from '../common/loader/Loader'; import ScheduleExport from '../common/schedule/ScheduleExport'; import { getBackstageOptions, useBackstageOptions } from './backstage.options'; import { getCardData, getIsPendingStart, getShowProgressBar, isOvertime } from './backstage.utils'; +import { BackstageData, useBackstageData } from './useBackstageData'; import './Backstage.scss'; -interface BackstageProps { - events: OntimeEvent[]; - customFields: CustomFields; - eventNext: OntimeEvent | null; - eventNow: OntimeEvent | null; - general: ProjectData; - isMirrored: boolean; - time: ViewExtendedTimer; - runtime: Runtime; - selectedId: string | null; - settings: Settings | undefined; -} - -export default function Backstage({ - events, - customFields, - eventNext, - eventNow, - general, - time, - isMirrored, - runtime, - selectedId, - settings, -}: BackstageProps) { - const { getLocalizedString } = useTranslation(); - const { secondarySource, extraInfo } = useBackstageOptions(); - const [blinkClass, setBlinkClass] = useState(false); - const { height: screenHeight } = useViewportSize(); +export default function BackstageLoader() { + const { data, status } = useBackstageData(); useWindowTitle('Backstage'); + if (status === 'pending') { + return ; + } + + if (status === 'error') { + return ; + } + + return ; +} + +function Backstage({ events, customFields, projectData, isMirrored, settings }: BackstageData) { + const { getLocalizedString } = useTranslation(); + const { secondarySource, extraInfo } = useBackstageOptions(); + const { eventNext, eventNow, runtime, selectedEventId, time } = useBackstageSocket(); + const [blinkClass, setBlinkClass] = useState(false); + const { height: screenHeight } = useViewportSize(); + // blink on change useEffect(() => { setBlinkClass(false); @@ -63,7 +57,7 @@ export default function Backstage({ }, 10); return () => clearTimeout(timer); - }, [selectedId]); + }, [selectedEventId]); // gather card data const hasEvents = events.length > 0; @@ -76,7 +70,6 @@ export default function Backstage({ ); // gather timer data - const clock = formatTime(time.clock); const isPendingStart = getIsPendingStart(time.playback, time.phase); const startedAt = isPendingStart ? formatTime(time.secondaryTimer) : formatTime(time.startedAt); @@ -104,20 +97,17 @@ export default function Backstage({ // gather option data const defaultFormat = getDefaultFormat(settings?.timeFormat); const backstageOptions = useMemo( - () => getBackstageOptions(defaultFormat, customFields, general), - [defaultFormat, customFields, general], + () => getBackstageOptions(defaultFormat, customFields, projectData), + [defaultFormat, customFields, projectData], ); return (
- {general?.logo && } -
{general.title}
-
-
{getLocalizedString('common.time_now')}
- -
+ {projectData?.logo && } +
{projectData.title}
+
{showProgress && } @@ -177,13 +167,13 @@ export default function Backstage({ )}
- {showSchedule && } + {showSchedule && }
- {extraInfo && } + {extraInfo && }
- {general.url && } - {general.info &&
{general.info}
} + {projectData.url && } + {projectData.info &&
{projectData.info}
}
@@ -222,3 +212,18 @@ function ExtraInfo({ projectData, size, source }: ExtraInfoProps) { ); } + +function BackstageClock() { + const { getLocalizedString } = useTranslation(); + const { clock } = useClock(); + + // gather timer data + const formattedClock = formatTime(clock); + + return ( +
+
{getLocalizedString('common.time_now')}
+ +
+ ); +} diff --git a/apps/client/src/views/backstage/useBackstageData.ts b/apps/client/src/views/backstage/useBackstageData.ts new file mode 100644 index 000000000..330f1fcb3 --- /dev/null +++ b/apps/client/src/views/backstage/useBackstageData.ts @@ -0,0 +1,38 @@ +import { CustomFields, OntimeEntry, ProjectData, Settings } from 'ontime-types'; + +import useCustomFields from '../../common/hooks-query/useCustomFields'; +import useProjectData from '../../common/hooks-query/useProjectData'; +import { useFlatRundown } from '../../common/hooks-query/useRundown'; +import useSettings from '../../common/hooks-query/useSettings'; +import { useViewOptionsStore } from '../../common/stores/viewOptions'; +import { aggregateQueryStatus, ViewData } from '../utils/viewLoader.utils'; + +export interface BackstageData { + events: OntimeEntry[]; + customFields: CustomFields; + projectData: ProjectData; + isMirrored: boolean; + settings: Settings; +} + +export function useBackstageData(): ViewData { + // persisted app state + const isMirrored = useViewOptionsStore((state) => state.mirror); + + // HTTP API data + const { data: rundownData, status: rundownStatus } = useFlatRundown(); + const { data: projectData, status: projectDataStatus } = useProjectData(); + const { data: settings, status: settingsStatus } = useSettings(); + const { data: customFields, status: customFieldsStatus } = useCustomFields(); + + return { + data: { + events: rundownData, + customFields, + projectData, + isMirrored, + settings, + }, + status: aggregateQueryStatus([rundownStatus, projectDataStatus, settingsStatus, customFieldsStatus]), + }; +}