diff --git a/apps/client/src/AppRouter.tsx b/apps/client/src/AppRouter.tsx
index 7d4eeab62..de9aa56c2 100644
--- a/apps/client/src/AppRouter.tsx
+++ b/apps/client/src/AppRouter.tsx
@@ -51,6 +51,7 @@ export default function AppRouter() {
path='countdown'
element={
+
}
diff --git a/apps/client/src/common/hooks/useSocket.ts b/apps/client/src/common/hooks/useSocket.ts
index 8a23ee623..ccbc5bc56 100644
--- a/apps/client/src/common/hooks/useSocket.ts
+++ b/apps/client/src/common/hooks/useSocket.ts
@@ -242,3 +242,9 @@ export const useTimerSocket = createSelector((state: RuntimeStore) => ({
aux3: state.auxtimer3.current,
},
}));
+
+export const useCountdownSocket = createSelector((state: RuntimeStore) => ({
+ playback: state.timer.playback,
+ current: state.timer.current,
+ clock: state.clock,
+}));
diff --git a/apps/client/src/views/countdown/Countdown.tsx b/apps/client/src/views/countdown/Countdown.tsx
index a1b83410b..873216a78 100644
--- a/apps/client/src/views/countdown/Countdown.tsx
+++ b/apps/client/src/views/countdown/Countdown.tsx
@@ -1,64 +1,52 @@
import { useMemo, useState } from 'react';
import { IoAdd } from 'react-icons/io5';
-import {
- CustomFields,
- EntryId,
- isOntimeEvent,
- isPlayableEvent,
- OntimeEvent,
- OntimeView,
- ProjectData,
- Settings,
-} from 'ontime-types';
+import { EntryId, isOntimeEvent, isPlayableEvent, OntimeEvent, OntimeView } from 'ontime-types';
import Button from '../../common/components/buttons/Button';
import Empty from '../../common/components/state/Empty';
+import EmptyPage from '../../common/components/state/EmptyPage';
import ViewLogo from '../../common/components/view-logo/ViewLogo';
import ViewParamsEditor from '../../common/components/view-params-editor/ViewParamsEditor';
+import { useClock } from '../../common/hooks/useSocket';
import { useWindowTitle } from '../../common/hooks/useWindowTitle';
-import { ViewExtendedTimer } from '../../common/models/TimeManager.type';
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 { getCountdownOptions, useCountdownOptions } from './countdown.options';
import { getOrderedSubscriptions } from './countdown.utils';
import CountdownSelect from './CountdownSelect';
import CountdownSubscriptions from './CountdownSubscriptions';
+import { CountdownData, useCountdownData } from './useCountdownData';
import './Countdown.scss';
-interface CountdownProps {
- customFields: CustomFields;
- events: OntimeEvent[];
- general: ProjectData;
- time: ViewExtendedTimer;
- isMirrored: boolean;
- selectedId: EntryId | null;
- settings: Settings | undefined;
-}
-
-export default function Countdown({
- customFields,
- events,
- general,
- time,
- isMirrored,
- selectedId,
- settings,
-}: CountdownProps) {
- const { getLocalizedString } = useTranslation();
- const { subscriptions } = useCountdownOptions();
- const [editMode, setEditMode] = useState(false);
+export default function CountdownLoader() {
+ const { data, status } = useCountdownData();
useWindowTitle('Countdown');
+ if (status === 'pending') {
+ return ;
+ }
+
+ if (status === 'error') {
+ return ;
+ }
+
+ return ;
+}
+
+function Countdown({ customFields, events, projectData, isMirrored, settings }: CountdownData) {
+ const { getLocalizedString } = useTranslation();
+ const { subscriptions } = useCountdownOptions();
+
+ const [editMode, setEditMode] = useState(false);
+
// gather rundown data
const playableEvents = events.filter((event) => isOntimeEvent(event) && isPlayableEvent(event));
- // gather timer data
- const clock = formatTime(time.clock);
-
// gather presentation data
const hasEvents = playableEvents.length > 0;
@@ -73,12 +61,9 @@ export default function Countdown({
- {general?.logo &&
}
-
{general.title}
-
-
{getLocalizedString('common.time_now')}
-
-
+ {projectData?.logo &&
}
+
{projectData.title}
+
{!hasEvents &&
}
@@ -91,9 +76,7 @@ export default function Countdown({
setEditMode(true)}
- selectedId={selectedId}
/>
)}
@@ -102,13 +85,11 @@ export default function Countdown({
interface CountdownContentsProps {
playableEvents: OntimeEvent[];
- selectedId: EntryId | null;
subscriptions: EntryId[];
- time: ViewExtendedTimer;
goToEditMode: () => void;
}
-function CountdownContents({ playableEvents, selectedId, subscriptions, time, goToEditMode }: CountdownContentsProps) {
+function CountdownContents({ playableEvents, subscriptions, goToEditMode }: CountdownContentsProps) {
const { getLocalizedString } = useTranslation();
if (subscriptions.length === 0) {
@@ -135,12 +116,20 @@ function CountdownContents({ playableEvents, selectedId, subscriptions, time, go
);
}
+ return ;
+}
+
+function CountdownClock() {
+ 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/countdown/CountdownSubscriptions.tsx b/apps/client/src/views/countdown/CountdownSubscriptions.tsx
index 49cc22239..1c956af7d 100644
--- a/apps/client/src/views/countdown/CountdownSubscriptions.tsx
+++ b/apps/client/src/views/countdown/CountdownSubscriptions.tsx
@@ -5,8 +5,13 @@ import { EntryId, OntimeEvent } from 'ontime-types';
import Button from '../../common/components/buttons/Button';
import { useFadeOutOnInactivity } from '../../common/hooks/useFadeOutOnInactivity';
import useFollowComponent from '../../common/hooks/useFollowComponent';
-import { useCurrentDay, useRuntimeOffset } from '../../common/hooks/useSocket';
-import { ViewExtendedTimer } from '../../common/models/TimeManager.type';
+import {
+ useCountdownSocket,
+ useCurrentDay,
+ usePlayback,
+ useRuntimeOffset,
+ useSelectedEventId,
+} from '../../common/hooks/useSocket';
import { getOffsetState } from '../../common/utils/offset';
import { cx } from '../../common/utils/styleUtils';
import { throttle } from '../../common/utils/throttle';
@@ -22,18 +27,13 @@ import './Countdown.scss';
interface CountdownSubscriptionsProps {
subscribedEvents: OntimeEvent[];
- selectedId: EntryId | null;
- time: ViewExtendedTimer;
goToEditMode: () => void;
}
-export default function CountdownSubscriptions({
- time,
- subscribedEvents,
- selectedId,
- goToEditMode,
-}: CountdownSubscriptionsProps) {
+export default function CountdownSubscriptions({ subscribedEvents, goToEditMode }: CountdownSubscriptionsProps) {
const { secondarySource, showExpected } = useCountdownOptions();
+ const { playback } = usePlayback();
+ const { selectedEventId } = useSelectedEventId();
const showFab = useFadeOutOnInactivity(true);
const timeoutId = useRef(null);
@@ -49,16 +49,16 @@ export default function CountdownSubscriptions({
// reset scroll if nothing is selected
useEffect(() => {
- if (!selectedId) {
+ if (!selectedEventId) {
if (!lockAutoScroll) {
scrollRef.current?.scrollTo(0, 0);
}
}
- }, [selectedId, lockAutoScroll, scrollRef]);
+ }, [selectedEventId, lockAutoScroll, scrollRef]);
// scroll to component if user clicks the Follow button
const handleOffset = () => {
- if (selectedId) {
+ if (selectedEventId) {
scrollToComponent();
}
setLockAutoScroll(false);
@@ -91,7 +91,7 @@ export default function CountdownSubscriptions({
{subscribedEvents.map((event) => {
const secondaryData = getPropertyValue(event, secondarySource);
- const isLive = getIsLive(event.id, selectedId, time.playback);
+ const isLive = getIsLive(event.id, selectedEventId, playback);
return (
@@ -107,7 +107,7 @@ export default function CountdownSubscriptions({
>
)}
-
+
{sanitiseTitle(event.title)}
{secondaryData &&
{secondaryData}
}
@@ -152,22 +152,24 @@ function ExpectedSchedule(props: ExpectedScheduleProps) {
}
interface SubscriptionStatusProps {
- time: ViewExtendedTimer;
event: OntimeEvent;
- selectedId: EntryId | null;
+ selectedEventId: EntryId | null;
}
-function SubscriptionStatus({ time, event, selectedId }: SubscriptionStatusProps) {
+function SubscriptionStatus({ event, selectedEventId }: SubscriptionStatusProps) {
const { getLocalizedString } = useTranslation();
const { currentDay } = useCurrentDay();
const { offset } = useRuntimeOffset();
const { showExpected } = useCountdownOptions();
+ const { playback, current, clock } = useCountdownSocket();
// TODO: use reporter values as in the event block chip
const { status, timer } = getSubscriptionDisplayData(
- time,
+ current,
+ playback,
+ clock,
event,
- selectedId,
+ selectedEventId,
offset,
currentDay,
getLocalizedString('common.minutes'),
diff --git a/apps/client/src/views/countdown/countdown.utils.ts b/apps/client/src/views/countdown/countdown.utils.ts
index 618bc3355..b755cae8d 100644
--- a/apps/client/src/views/countdown/countdown.utils.ts
+++ b/apps/client/src/views/countdown/countdown.utils.ts
@@ -1,7 +1,6 @@
-import { EntryId, OntimeEvent, Playback, TimerType } from 'ontime-types';
+import { EntryId, MaybeNumber, OntimeEvent, Playback, TimerType } from 'ontime-types';
import { dayInMs } from 'ontime-utils';
-import { ViewExtendedTimer } from '../../common/models/TimeManager.type';
import { getFormattedTimer } from '../../features/viewers/common/viewUtils';
import type { TranslationKey } from '../../translation/TranslationProvider';
@@ -46,7 +45,9 @@ export const timerProgress: TimerMessage = {
* TODO: get timer data granularly
*/
export function getSubscriptionDisplayData(
- time: ViewExtendedTimer,
+ current: MaybeNumber,
+ playback: Playback,
+ clock: number,
subscribedEvent: OntimeEvent,
selectedId: EntryId | null,
offset: number,
@@ -58,7 +59,7 @@ export function getSubscriptionDisplayData(
if (selectedId === subscribedEvent.id) {
// 1. An event that is loaded but not running is {'due': }
- if (time.playback === Playback.Armed) {
+ if (playback === Playback.Armed) {
// if we are following the event, but it is not running, we show the scheduled start
return {
status: 'due',
@@ -74,7 +75,7 @@ export function getSubscriptionDisplayData(
// 2. An event with a time-to-start lower than 0 is {'due': }, show the running timer
return {
status: 'live',
- timer: getFormattedTimer(time.current, TimerType.CountDown, minutesString, subscriptionTimerDisplayOptions),
+ timer: getFormattedTimer(current, TimerType.CountDown, minutesString, subscriptionTimerDisplayOptions),
};
}
@@ -89,7 +90,7 @@ export function getSubscriptionDisplayData(
return {
status: 'future',
timer: getFormattedTimer(
- subscribedEvent.timeStart + dayOffset - time.clock - offsetAndDelay,
+ subscribedEvent.timeStart + dayOffset - clock - offsetAndDelay,
TimerType.CountDown,
minutesString,
subscriptionTimerDisplayOptions,
@@ -113,11 +114,11 @@ export function getSubscriptionDisplayData(
// 5. if event is in future, we count to the scheduled start
// TODO: get time until
- if (time.clock < subscribedEvent.timeStart) {
+ if (clock < subscribedEvent.timeStart) {
return {
status: 'future',
timer: getFormattedTimer(
- subscribedEvent.timeStart - time.clock - offsetAndDelay,
+ subscribedEvent.timeStart - clock - offsetAndDelay,
TimerType.CountDown,
minutesString,
subscriptionTimerDisplayOptions,
@@ -127,7 +128,7 @@ export function getSubscriptionDisplayData(
// 6. if event has ended, we show the scheduled end
// TODO: get the time from the reporter
- if (time.clock > subscribedEvent.timeEnd) {
+ if (clock > subscribedEvent.timeEnd) {
return {
status: 'done',
timer: getFormattedTimer(
diff --git a/apps/client/src/views/countdown/useCountdownData.ts b/apps/client/src/views/countdown/useCountdownData.ts
new file mode 100644
index 000000000..84c56b251
--- /dev/null
+++ b/apps/client/src/views/countdown/useCountdownData.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 CountdownData {
+ customFields: CustomFields;
+ events: OntimeEntry[];
+ projectData: ProjectData;
+ isMirrored: boolean;
+ settings: Settings;
+}
+
+export function useCountdownData(): 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: {
+ customFields,
+ events: rundownData,
+ projectData,
+ isMirrored,
+ settings,
+ },
+ status: aggregateQueryStatus([rundownStatus, projectDataStatus, settingsStatus, customFieldsStatus]),
+ };
+}