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]),
+ };
+}