From 48c76947c58679f7c48ce85eec27493d145f5e88 Mon Sep 17 00:00:00 2001 From: Carlos Valente Date: Sun, 13 Sep 2026 20:20:16 +0200 Subject: [PATCH] refactor(rundown): bind each surface to its data source Editable panels now share a rundown scope for data, selection, and mutations; runtime-only views resolve the loaded rundown explicitly. This also prevents stale data while switching scopes and provides the Operator editor with its mutation scope. --- apps/client/src/AppRouter.tsx | 5 +- .../__tests__/useRundownSelection.test.ts} | 12 +- .../hooks/useRundownSelection.ts} | 31 +++- .../src/common/stores/entryCopyStore.ts | 15 +- apps/client/src/common/utils/socket.ts | 6 +- .../panel/manage-panel/ManageRundowns.tsx | 2 +- .../sources-panel/SourcesPanel.tsx | 4 +- .../src/features/operator/OperatorExport.tsx | 5 +- .../src/features/operator/useOperatorData.ts | 4 +- .../overview/composite/TimeElements.tsx | 6 +- .../overview/composite/TitleOverview.tsx | 4 +- apps/client/src/features/rundown/Rundown.tsx | 6 +- .../src/features/rundown/RundownExport.tsx | 80 ++++----- .../src/features/rundown/RundownList.tsx | 9 +- .../rundown/hooks/useRundownCommands.ts | 15 +- .../rundown/rundown-table/RundownTable.tsx | 13 +- .../src/features/rundown/useEventSelection.ts | 158 +----------------- .../rundown/useSelectAndRevealEntry.ts | 4 +- .../src/views/backstage/useBackstageData.ts | 4 +- .../views/common/schedule/ScheduleContext.tsx | 4 +- .../src/views/countdown/useCountdownData.ts | 4 +- .../src/views/cuesheet/CuesheetPage.tsx | 19 +-- .../views/cuesheet/CuesheetTableWrapper.tsx | 12 +- .../cuesheet-edit-modal/EntryEditModal.tsx | 9 +- .../cuesheet/cuesheet-table/CuesheetTable.tsx | 12 +- apps/client/src/views/editor/Editor.tsx | 38 +++-- .../src/views/editor/finder/useFinder.tsx | 4 +- .../src/views/editor/title-list/TitleList.tsx | 12 +- .../src/views/timeline/useTimelineData.ts | 4 +- apps/client/src/views/timer/useTimerData.ts | 4 +- 30 files changed, 192 insertions(+), 313 deletions(-) rename apps/client/src/{views/cuesheet/__tests__/useCuesheetRundownSelection.test.ts => common/hooks/__tests__/useRundownSelection.test.ts} (57%) rename apps/client/src/{views/cuesheet/useCuesheetRundownSelection.ts => common/hooks/useRundownSelection.ts} (61%) diff --git a/apps/client/src/AppRouter.tsx b/apps/client/src/AppRouter.tsx index 34d21b950..3650b777a 100644 --- a/apps/client/src/AppRouter.tsx +++ b/apps/client/src/AppRouter.tsx @@ -3,6 +3,7 @@ import { ComponentType, Suspense, lazy, useEffect, useMemo } from 'react'; import { Navigate, Route, useLocation, useNavigate, useParams } from 'react-router'; import ViewNavigationMenu from './common/components/navigation-menu/ViewNavigationMenu'; +import { EditableRundownScopeProvider } from './common/context/EditableRundownScopeProvider'; import { PresetContext } from './common/context/PresetContext'; import useUrlPresets from './common/hooks-query/useUrlPresets'; import { useClientPath } from './common/hooks/useClientPath'; @@ -112,7 +113,9 @@ export default function AppRouter() { path='rundown' element={ - + + + } /> diff --git a/apps/client/src/views/cuesheet/__tests__/useCuesheetRundownSelection.test.ts b/apps/client/src/common/hooks/__tests__/useRundownSelection.test.ts similarity index 57% rename from apps/client/src/views/cuesheet/__tests__/useCuesheetRundownSelection.test.ts rename to apps/client/src/common/hooks/__tests__/useRundownSelection.test.ts index 4baaeb2b4..9544f1149 100644 --- a/apps/client/src/views/cuesheet/__tests__/useCuesheetRundownSelection.test.ts +++ b/apps/client/src/common/hooks/__tests__/useRundownSelection.test.ts @@ -1,13 +1,13 @@ import { FOLLOW_LOADED_RUNDOWN_ID, - getCuesheetRundownStorageKey, + getRundownSelectionStorageKey, resolveSelectedRundownId, -} from '../useCuesheetRundownSelection'; +} from '../useRundownSelection'; -describe('useCuesheetRundownSelection helpers', () => { - it('builds a project-scoped storage key', () => { - expect(getCuesheetRundownStorageKey('http://localhost:4001', 'My Project')).toBe( - 'cuesheet-selected-rundown:http://localhost:4001:My Project', +describe('useRundownSelection helpers', () => { + it('builds a namespace and project scoped storage key', () => { + expect(getRundownSelectionStorageKey('cuesheet', 'http://localhost:4001', 'My Project')).toBe( + 'rundown-selection:cuesheet:http://localhost:4001:My Project', ); }); diff --git a/apps/client/src/views/cuesheet/useCuesheetRundownSelection.ts b/apps/client/src/common/hooks/useRundownSelection.ts similarity index 61% rename from apps/client/src/views/cuesheet/useCuesheetRundownSelection.ts rename to apps/client/src/common/hooks/useRundownSelection.ts index 73ae2de38..b10ede650 100644 --- a/apps/client/src/views/cuesheet/useCuesheetRundownSelection.ts +++ b/apps/client/src/common/hooks/useRundownSelection.ts @@ -2,14 +2,21 @@ import { useSessionStorage } from '@mantine/hooks'; import { startTransition, useCallback, useMemo } from 'react'; import { useNavigate } from 'react-router'; -import { useOrderedProjectList } from '../../common/hooks-query/useProjectList'; -import { useProjectRundowns } from '../../common/hooks-query/useProjectRundowns'; import { serverURL } from '../../externals'; +import { useOrderedProjectList } from '../hooks-query/useProjectList'; +import { useProjectRundowns } from '../hooks-query/useProjectRundowns'; export const FOLLOW_LOADED_RUNDOWN_ID = '__follow-loaded__' as const; -export function getCuesheetRundownStorageKey(server: string, projectFilename: string) { - return `cuesheet-selected-rundown:${server}:${projectFilename}`; +/** each surface keeps its own selection, so panels can point at different rundowns */ +export type RundownSelectionNamespace = 'cuesheet'; + +export function getRundownSelectionStorageKey( + namespace: RundownSelectionNamespace, + server: string, + projectFilename: string, +) { + return `rundown-selection:${namespace}:${server}:${projectFilename}`; } export function resolveSelectedRundownId(storedSelectedRundownId: string | null, availableRundownIds: Set) { @@ -17,14 +24,22 @@ export function resolveSelectedRundownId(storedSelectedRundownId: string | null, return FOLLOW_LOADED_RUNDOWN_ID; } -export function useCuesheetRundownSelection() { +/** + * Persisted choice of which rundown a surface operates on. + * The resolved id is meant to be handed to a rundown scope provider, + * where null means follow whichever rundown is loaded. + */ +export function useRundownSelection(namespace: RundownSelectionNamespace) { 'use memo'; const { data: projectRundowns } = useProjectRundowns(); const { data: { lastLoadedProject }, } = useOrderedProjectList(); - const storageKey = useMemo(() => getCuesheetRundownStorageKey(serverURL, lastLoadedProject), [lastLoadedProject]); + const storageKey = useMemo( + () => getRundownSelectionStorageKey(namespace, serverURL, lastLoadedProject), + [namespace, lastLoadedProject], + ); const [storedSelectedRundownId, setStoredSelectedRundownId] = useSessionStorage({ key: storageKey, defaultValue: FOLLOW_LOADED_RUNDOWN_ID, @@ -38,6 +53,8 @@ export function useCuesheetRundownSelection() { return { loadedRundownId, selectedRundownId, + /** id for the rundown scope, null follows the loaded rundown */ + scopedRundownId: selectedRundownId === FOLLOW_LOADED_RUNDOWN_ID ? null : selectedRundownId, projectRundowns: projectRundowns.rundowns, setSelectedRundownId: (rundownId: string) => { startTransition(() => { @@ -52,7 +69,7 @@ export function useDirectLinkToBackgroundEdit() { data: { lastLoadedProject }, } = useOrderedProjectList(); const navigate = useNavigate(); - const storageKey = getCuesheetRundownStorageKey(serverURL, lastLoadedProject); + const storageKey = getRundownSelectionStorageKey('cuesheet', serverURL, lastLoadedProject); const [_, setStoredSelectedRundownId] = useSessionStorage({ key: storageKey, defaultValue: null }); return useCallback( diff --git a/apps/client/src/common/stores/entryCopyStore.ts b/apps/client/src/common/stores/entryCopyStore.ts index 4ccc2de7b..e35cd2ef9 100644 --- a/apps/client/src/common/stores/entryCopyStore.ts +++ b/apps/client/src/common/stores/entryCopyStore.ts @@ -1,14 +1,21 @@ +import { MaybeString } from 'ontime-types'; import { create } from 'zustand'; type EntryCopyStore = { - entryCopyId: string | null; + entryCopyId: MaybeString; + /** rundown the copied entry belongs to, so a paste knows whether it crosses rundowns */ + entryCopyRundownId: MaybeString; entryCopyMode: 'copy' | 'cut'; - setEntryCopyId: (eventId: string | null, mode?: 'copy' | 'cut') => void; + setEntryCopyId: (eventId: MaybeString, rundownId: MaybeString, mode?: 'copy' | 'cut') => void; }; +/** + * The clipboard is shared across rundowns so entries can be moved between them + */ export const useEntryCopy = create()((set) => ({ entryCopyId: null, + entryCopyRundownId: null, entryCopyMode: 'copy', - setEntryCopyId: (entryCopyId: string | null, mode: 'copy' | 'cut' = 'copy') => - set({ entryCopyId, entryCopyMode: mode }), + setEntryCopyId: (entryCopyId: MaybeString, entryCopyRundownId: MaybeString, mode: 'copy' | 'cut' = 'copy') => + set({ entryCopyId, entryCopyRundownId, entryCopyMode: mode }), })); diff --git a/apps/client/src/common/utils/socket.ts b/apps/client/src/common/utils/socket.ts index a9366cc36..6a16ba04d 100644 --- a/apps/client/src/common/utils/socket.ts +++ b/apps/client/src/common/utils/socket.ts @@ -242,12 +242,12 @@ export function maybeInvalidateRundownCache(revision: MaybeNumber, rundownId?: s return; } - // skip if we dont recognise the ID the revision is lower const queryKey = getRundownQueryKey(rundownId); const cachedRundown = ontimeQueryClient.getQueryData<{ revision: number }>(queryKey); - if (revision === cachedRundown?.revision) { - // we already have the latest change + // we already have this change, or something newer + // messages can arrive after a refetch has already brought in a later revision + if (revision !== null && cachedRundown !== undefined && revision <= cachedRundown.revision) { return; } diff --git a/apps/client/src/features/app-settings/panel/manage-panel/ManageRundowns.tsx b/apps/client/src/features/app-settings/panel/manage-panel/ManageRundowns.tsx index 2e0965e54..03329853e 100644 --- a/apps/client/src/features/app-settings/panel/manage-panel/ManageRundowns.tsx +++ b/apps/client/src/features/app-settings/panel/manage-panel/ManageRundowns.tsx @@ -19,8 +19,8 @@ import Dialog from '../../../../common/components/dialog/Dialog'; import { DropdownMenu } from '../../../../common/components/dropdown-menu/DropdownMenu'; import Tag from '../../../../common/components/tag/Tag'; import { useMutateProjectRundowns, useProjectRundowns } from '../../../../common/hooks-query/useProjectRundowns'; +import { useDirectLinkToBackgroundEdit } from '../../../../common/hooks/useRundownSelection'; import { cx } from '../../../../common/utils/styleUtils'; -import { useDirectLinkToBackgroundEdit } from '../../../../views/cuesheet/useCuesheetRundownSelection'; import * as Panel from '../../panel-utils/PanelUtils'; import RundownRenameForm from './composite/RundownRenameForm'; import { ManageRundownForm } from './ManageRundownForm'; diff --git a/apps/client/src/features/app-settings/panel/manage-panel/sources-panel/SourcesPanel.tsx b/apps/client/src/features/app-settings/panel/manage-panel/sources-panel/SourcesPanel.tsx index 87180a840..7ef599955 100644 --- a/apps/client/src/features/app-settings/panel/manage-panel/sources-panel/SourcesPanel.tsx +++ b/apps/client/src/features/app-settings/panel/manage-panel/sources-panel/SourcesPanel.tsx @@ -24,7 +24,7 @@ import Button from '../../../../../common/components/buttons/Button'; import Info from '../../../../../common/components/info/Info'; import ExternalLink from '../../../../../common/components/link/external-link/ExternalLink'; import Modal from '../../../../../common/components/modal/Modal'; -import useRundown from '../../../../../common/hooks-query/useRundown'; +import { useLoadedRundown } from '../../../../../common/hooks-query/useLoadedRundown'; import { removeFileExtension, validateExcelImport } from '../../../../../common/utils/uploadUtils'; import * as Panel from '../../../panel-utils/PanelUtils'; import GSheetSetup from './GSheetSetup'; @@ -59,7 +59,7 @@ export default function SourcesPanel() { const [activeSource, setActiveSource] = useState(null); const [completedRundownTitle, setCompletedRundownTitle] = useState(''); - const { data: currentRundown } = useRundown(); + const { data: currentRundown } = useLoadedRundown(); const { applyImport } = useSpreadsheetImport(); const navigate = useNavigate(); diff --git a/apps/client/src/features/operator/OperatorExport.tsx b/apps/client/src/features/operator/OperatorExport.tsx index f29f65b38..6a2da345b 100644 --- a/apps/client/src/features/operator/OperatorExport.tsx +++ b/apps/client/src/features/operator/OperatorExport.tsx @@ -1,10 +1,13 @@ import ProtectRoute from '../../common/components/protect-route/ProtectRoute'; +import { EditableRundownScopeProvider } from '../../common/context/EditableRundownScopeProvider'; import Operator from './Operator'; export default function OperatorExport() { return ( - + + + ); } diff --git a/apps/client/src/features/operator/useOperatorData.ts b/apps/client/src/features/operator/useOperatorData.ts index 8eeeae6b2..12dbda6eb 100644 --- a/apps/client/src/features/operator/useOperatorData.ts +++ b/apps/client/src/features/operator/useOperatorData.ts @@ -1,7 +1,7 @@ import { CustomFields, Rundown, Settings } from 'ontime-types'; import useCustomFields from '../../common/hooks-query/useCustomFields'; -import { useRundownWithMetadata } from '../../common/hooks-query/useRundown'; +import { useLoadedRundownWithMetadata } from '../../common/hooks-query/useLoadedRundown'; import useSettings from '../../common/hooks-query/useSettings'; import { RundownMetadataObject } from '../../common/utils/rundownMetadata'; import { ViewData, aggregateQueryStatus } from '../../views/utils/viewLoader.utils'; @@ -14,7 +14,7 @@ export interface OperatorData { } export function useOperatorData(): ViewData { - const { data: rundown, rundownMetadata, status: rundownStatus } = useRundownWithMetadata(); + const { data: rundown, rundownMetadata, status: rundownStatus } = useLoadedRundownWithMetadata(); const { data: customFields, status: customFieldStatus } = useCustomFields(); const { data: settings, status: settingsStatus } = useSettings(); diff --git a/apps/client/src/features/overview/composite/TimeElements.tsx b/apps/client/src/features/overview/composite/TimeElements.tsx index 27e8c8f50..c844c2f9b 100644 --- a/apps/client/src/features/overview/composite/TimeElements.tsx +++ b/apps/client/src/features/overview/composite/TimeElements.tsx @@ -12,7 +12,7 @@ import { } from 'react-icons/tb'; import Tooltip from '../../../common/components/tooltip/Tooltip'; -import { useEntry } from '../../../common/hooks-query/useRundown'; +import { useLoadedEntry } from '../../../common/hooks-query/useLoadedRundown'; import { useAutoTickingClock } from '../../../common/hooks/useAutoTickingClock'; import { useCurrentGroupId, @@ -210,7 +210,7 @@ export function MetadataTimes() { function GroupTimes() { const { clock, mode, groupExpectedEnd, actualGroupStart, currentDay, playback, phase } = useGroupTimerOverView(); const currentGroupId = useCurrentGroupId(); - const group = useEntry(currentGroupId) as OntimeGroup | null; + const group = useLoadedEntry(currentGroupId) as OntimeGroup | null; const hasRunningTimer = phase !== TimerPhase.Pending && isPlaybackActive(playback); @@ -266,7 +266,7 @@ function GroupTimes() { function FlagTimes() { const { clock, mode, actualStart, plannedStart, playback, currentDay, phase } = useFlagTimerOverView(); const { id, expectedStart } = useNextFlag(); - const entry = useEntry(id) as OntimeEvent | null; + const entry = useLoadedEntry(id) as OntimeEvent | null; const hasRunningTimer = phase !== TimerPhase.Pending && isPlaybackActive(playback); diff --git a/apps/client/src/features/overview/composite/TitleOverview.tsx b/apps/client/src/features/overview/composite/TitleOverview.tsx index 5f29c0bf3..7d8f3f586 100644 --- a/apps/client/src/features/overview/composite/TitleOverview.tsx +++ b/apps/client/src/features/overview/composite/TitleOverview.tsx @@ -1,12 +1,12 @@ +import { useLoadedRundownAuxData } from '../../../common/hooks-query/useLoadedRundown'; import useProjectData from '../../../common/hooks-query/useProjectData'; -import { useRundownAuxData } from '../../../common/hooks-query/useRundown'; import style from './TitleOverview.module.scss'; export default function TitleOverview() { 'use memo'; const { data: projectData } = useProjectData(); - const { data: rundownData } = useRundownAuxData(); + const { data: rundownData } = useLoadedRundownAuxData(); const projectTitle = projectData.title.trim(); const rundownTitle = rundownData.title.trim(); diff --git a/apps/client/src/features/rundown/Rundown.tsx b/apps/client/src/features/rundown/Rundown.tsx index 9b96debce..a833d74d1 100644 --- a/apps/client/src/features/rundown/Rundown.tsx +++ b/apps/client/src/features/rundown/Rundown.tsx @@ -67,7 +67,11 @@ export default function Rundown({ order, flatOrder, entries, id, rundownMetadata const { getIsCollapsed, collapseGroup, expandGroup } = useCollapsedGroups(id); const entryActions = useEntryActionsContext(); - const setEntryCopyId = useEntryCopy((state) => state.setEntryCopyId); + const setEntryCopy = useEntryCopy((state) => state.setEntryCopyId); + const setEntryCopyId = useCallback( + (entryId: EntryId | null, mode?: 'copy' | 'cut') => setEntryCopy(entryId, id, mode), + [setEntryCopy, id], + ); // cursor const { editorMode } = useEditorFollowMode(); diff --git a/apps/client/src/features/rundown/RundownExport.tsx b/apps/client/src/features/rundown/RundownExport.tsx index 0fcebf415..9450e1ab2 100644 --- a/apps/client/src/features/rundown/RundownExport.tsx +++ b/apps/client/src/features/rundown/RundownExport.tsx @@ -5,9 +5,6 @@ import * as Editor from '../../common/components/editor-utils/EditorUtils'; import ErrorBoundary from '../../common/components/error-boundary/ErrorBoundary'; import ViewNavigationMenu from '../../common/components/navigation-menu/ViewNavigationMenu'; import ProtectRoute from '../../common/components/protect-route/ProtectRoute'; -import { EntryActionsProvider } from '../../common/context/EntryActionsContext'; -import { useLoadedRundownSource } from '../../common/hooks-query/useScopedRundown'; -import { useEntryActions } from '../../common/hooks/useEntryAction'; import { useIsSmallDevice } from '../../common/hooks/useIsSmallDevice'; import { handleLinks } from '../../common/utils/linkUtils'; import { cx } from '../../common/utils/styleUtils'; @@ -39,28 +36,25 @@ function RundownExport() { defaultValue: RundownViewMode.List, }); const isSmallDevice = useIsSmallDevice(); - const entryActions = useEntryActions(); if (isSmallDevice && isExtracted) { return ( - - -
- - -
- - - - -
+ +
+ + +
+ + + +
- - +
+
); } @@ -70,30 +64,28 @@ function RundownExport() { viewMode === RundownViewMode.Table; return ( - - -
- - {isExtracted && } -
- + +
+ + {isExtracted && } +
+ + + {!isExtracted && handleLinks('rundown', event)} />} + + + + + {!hideSideBar && ( +
- {!isExtracted && handleLinks('rundown', event)} />} - - + - - {!hideSideBar && ( -
- - - -
- )} -
+
+ )}
-
- +
+ ); } @@ -105,8 +97,6 @@ interface RundownRootProps { } function RundownRoot({ isSmallDevice, isExtracted, viewMode, setViewMode }: RundownRootProps) { - const source = useLoadedRundownSource(); - return (
{isSmallDevice ? ( @@ -115,7 +105,7 @@ function RundownRoot({ isSmallDevice, isExtracted, viewMode, setViewMode }: Rund )} {viewMode === RundownViewMode.List ? : } - {viewMode === RundownViewMode.Table && } + {viewMode === RundownViewMode.Table && }
); diff --git a/apps/client/src/features/rundown/RundownList.tsx b/apps/client/src/features/rundown/RundownList.tsx index 86d80906b..f6c3113a5 100644 --- a/apps/client/src/features/rundown/RundownList.tsx +++ b/apps/client/src/features/rundown/RundownList.tsx @@ -1,15 +1,22 @@ +import { Playback } from 'ontime-types'; import { memo } from 'react'; import EmptyFill from '../../common/components/state/EmptyFill'; +import { useRundownScope } from '../../common/context/RundownScopeContext'; import { useRundownWithMetadata } from '../../common/hooks-query/useRundown'; import { useRundownEditor } from '../../common/hooks/useSocket'; import { useTranslation } from '../../translation/TranslationProvider'; import Rundown from './Rundown'; +/** a rundown which is not loaded has no playback to follow */ +const idleFeatureData = { playback: Playback.Stop, selectedEventId: null, nextEventId: null }; + export default memo(RundownList); function RundownList() { + const { isLoaded } = useRundownScope(); const { data, status, rundownMetadata } = useRundownWithMetadata(); - const featureData = useRundownEditor(); + const runtimeFeatureData = useRundownEditor(); + const featureData = isLoaded ? runtimeFeatureData : idleFeatureData; const { getLocalizedString } = useTranslation(); // avoid showing the editable empty state before we know whether the rundown is actually empty diff --git a/apps/client/src/features/rundown/hooks/useRundownCommands.ts b/apps/client/src/features/rundown/hooks/useRundownCommands.ts index e036aa1ce..e6fd1375c 100644 --- a/apps/client/src/features/rundown/hooks/useRundownCommands.ts +++ b/apps/client/src/features/rundown/hooks/useRundownCommands.ts @@ -2,6 +2,7 @@ import { type EntryId, type OntimeEntry, type Rundown, SupportedEntry } from 'on import { getNextGroupNormal, getNextNormal, getPreviousGroupNormal, getPreviousNormal } from 'ontime-utils'; import { useCallback } from 'react'; +import { useRundownScope } from '../../../common/context/RundownScopeContext'; import type { useEntryActions } from '../../../common/hooks/useEntryAction'; import { useEntryCopy } from '../../../common/stores/entryCopyStore'; import { SelectionMode } from '../useEventSelection'; @@ -26,6 +27,7 @@ export function useRundownCommands({ selectEntry: applySelection, handleCollapseGroup, }: UseRundownCommandsOptions) { + const { rundownId } = useRundownScope(); const { addEntry, clone, deleteEntry, move, reorderEntry } = entryActions; const deleteAtCursor = useCallback( @@ -43,12 +45,17 @@ export function useRundownCommands({ const insertCopyAtId = useCallback( (atId: EntryId | null, above = false) => { // lazily get the value from the store - const { entryCopyId, entryCopyMode, setEntryCopyId } = useEntryCopy.getState(); + const { entryCopyId, entryCopyRundownId, entryCopyMode, setEntryCopyId } = useEntryCopy.getState(); if (entryCopyId === null || !entries[entryCopyId]) { // we cant clone without selection return; } + // pasting into a different rundown needs the entry payload, the server clones within a rundown + if (entryCopyRundownId !== null && entryCopyRundownId !== rundownId) { + return; + } + let normalisedAtId = atId; const elementToCopy = entries[entryCopyId]; @@ -65,7 +72,7 @@ export function useRundownCommands({ return; } reorderEntry(entryCopyId, firstId, 'before') - .then(() => setEntryCopyId(null)) + .then(() => setEntryCopyId(null, null)) .catch(() => {}); return; } @@ -74,7 +81,7 @@ export function useRundownCommands({ } const placement = above ? 'before' : 'after'; reorderEntry(entryCopyId, normalisedAtId, placement) - .then(() => setEntryCopyId(null)) + .then(() => setEntryCopyId(null, null)) .catch(() => {}); return; } @@ -85,7 +92,7 @@ export function useRundownCommands({ before: above ? (normalisedAtId ?? undefined) : undefined, }); }, - [entries, flatOrder, clone, reorderEntry], + [entries, flatOrder, clone, reorderEntry, rundownId], ); /** diff --git a/apps/client/src/features/rundown/rundown-table/RundownTable.tsx b/apps/client/src/features/rundown/rundown-table/RundownTable.tsx index 03f28a277..1c3dd7df7 100644 --- a/apps/client/src/features/rundown/rundown-table/RundownTable.tsx +++ b/apps/client/src/features/rundown/rundown-table/RundownTable.tsx @@ -1,9 +1,6 @@ import { memo, useEffect, useMemo } from 'react'; -import { EntryActionsProvider } from '../../../common/context/EntryActionsContext'; import useCustomFields from '../../../common/hooks-query/useCustomFields'; -import { useLoadedRundownSource } from '../../../common/hooks-query/useScopedRundown'; -import { useEntryActions } from '../../../common/hooks/useEntryAction'; import CuesheetDnd from '../../../views/cuesheet/cuesheet-dnd/CuesheetDnd'; import CuesheetTable from '../../../views/cuesheet/cuesheet-table/CuesheetTable'; import { useCuesheetPermissions } from '../../../views/cuesheet/useTablePermissions'; @@ -15,8 +12,6 @@ function RundownTable() { const { data: customFields } = useCustomFields(); const setPermissions = useCuesheetPermissions((state) => state.setPermissions); const { editorMode } = useEditorFollowMode(); - const source = useLoadedRundownSource(); - const actions = useEntryActions(); // Editor always has full permissions useEffect(() => { @@ -32,10 +27,8 @@ function RundownTable() { const columns = useMemo(() => makeRundownColumns(customFields), [customFields]); return ( - - - - - + + + ); } diff --git a/apps/client/src/features/rundown/useEventSelection.ts b/apps/client/src/features/rundown/useEventSelection.ts index 180f525f2..c5c60eabe 100644 --- a/apps/client/src/features/rundown/useEventSelection.ts +++ b/apps/client/src/features/rundown/useEventSelection.ts @@ -1,155 +1,15 @@ -import { EntryId, MaybeNumber, ProjectRundownsList, Rundown, isOntimeEvent } from 'ontime-types'; -import { MouseEvent } from 'react'; -import { create } from 'zustand'; +import { useStore } from 'zustand'; -import { PROJECT_RUNDOWNS, getRundownQueryKey } from '../../common/api/constants'; -import { ontimeQueryClient } from '../../common/queryClient'; -import { isMacOS } from '../../common/utils/deviceUtils'; +import { useRundownScope } from '../../common/context/RundownScopeContext'; +import type { EventSelectionStore } from '../../common/stores/eventSelectionStore'; -export type SelectionMode = 'shift' | 'click' | 'ctrl'; - -interface EventSelectionStore { - selectedEvents: Set; - anchoredIndex: MaybeNumber; - cursor: EntryId | null; - entryMode: 'event' | 'single' | null; - scrollHandler: ((id: EntryId) => void) | null; - setSingleEntrySelection: (selectionArgs: { id: EntryId }) => void; - setSelectedEvents: (selectionArgs: { id: EntryId; index: number; selectMode: SelectionMode }) => void; - clearSelectedEvents: () => void; - clearMultiSelect: () => void; - unselect: (id: EntryId) => void; - setScrollHandler: (handler: ((id: EntryId) => void) | null) => void; - scrollToEntry: (id: EntryId) => void; -} +export { getSelectionMode } from '../../common/stores/eventSelectionStore'; +export type { SelectionMode } from '../../common/stores/eventSelectionStore'; /** - * Keeps track of the selected entries and selection mode - * Provides methods to update the selection based on user interactions + * Selection and cursor for the rundown of the enclosing scope */ -export const useEventSelection = create()((set, get) => ({ - selectedEvents: new Set(), - anchoredIndex: null, - cursor: null, - entryMode: null, - scrollHandler: null, - setSingleEntrySelection: ({ id }) => { - set({ selectedEvents: new Set([id]), anchoredIndex: null, cursor: id, entryMode: 'single' }); - }, - setSelectedEvents: ({ id, index, selectMode }) => { - const { selectedEvents, anchoredIndex, entryMode } = get(); - - // if we are in single mode, we replace the selection and change the mode - if (entryMode === 'single') { - return set({ selectedEvents: new Set([id]), anchoredIndex: index, cursor: id, entryMode: 'event' }); - } - - // on click, we replace selection with event - if (selectMode === 'click') { - return set({ selectedEvents: new Set([id]), anchoredIndex: index, cursor: id, entryMode: 'event' }); - } - - // on ctrl + click, we toggle the selection of that event - if (selectMode === 'ctrl') { - const rundownData = getLoadedRundownData(); - if (!rundownData) return; - - // if it doesnt exist, simply add to the list and set an anchor - if (!selectedEvents.has(id)) { - return set({ - selectedEvents: selectedEvents.add(id), - anchoredIndex: index, - cursor: id, - entryMode: 'event', - }); - } - - // if event is already selected, we remove it from selection - // and set the anchor to the event after - selectedEvents.delete(id); - - const nextIndex = rundownData.order.findIndex( - (eventId, i) => i > index && isOntimeEvent(rundownData.entries[eventId]) && selectedEvents.has(eventId), - ); - - // if we didnt find anything after, set the anchor to the last event - return set({ - selectedEvents, - anchoredIndex: nextIndex < 0 ? rundownData.order.length - 1 : nextIndex, - entryMode: 'event', - }); - } - - // on shift + click, we select a range of events up to the clicked event - if (selectMode === 'shift') { - const rundownData = getLoadedRundownData(); - if (!rundownData) return; - - // get list of rundown with only ontime events - const eventIds: EntryId[] = []; - rundownData.flatOrder.forEach((eventId) => { - const event = rundownData.entries[eventId]; - if (isOntimeEvent(event)) { - eventIds.push(event.id); - } - }); - - const start = anchoredIndex === null ? 0 : Math.min(anchoredIndex, index); - const end = anchoredIndex === null ? index : Math.max(anchoredIndex, index + 1); - - // create new set with range of ids from start to end - const selectedEventIds = eventIds.slice(start, end); - - return set({ - selectedEvents: new Set([...selectedEvents, ...selectedEventIds]), - anchoredIndex: index, - entryMode: 'event', - }); - } - }, - clearSelectedEvents: () => set({ selectedEvents: new Set(), anchoredIndex: null, cursor: null, entryMode: null }), - clearMultiSelect: () => { - const { selectedEvents } = get(); - const [firstSelected] = selectedEvents; - set({ - selectedEvents: new Set(firstSelected ? [firstSelected] : []), - anchoredIndex: null, - entryMode: null, - }); - }, - unselect: (id: string) => { - const { entryMode, selectedEvents } = get(); - selectedEvents.delete(id); - set({ - selectedEvents, - entryMode: selectedEvents.size === 0 ? null : entryMode, - }); - }, - // Sets the scroll handler for programmatic scrolling to entries - setScrollHandler: (handler) => set({ scrollHandler: handler }), - // Scrolls to the specified entry using the registered scroll handler - scrollToEntry: (id: EntryId) => { - const handler = get().scrollHandler; - if (handler) { - handler(id); - } - }, -})); - -function getLoadedRundownData() { - const rundownId = ontimeQueryClient.getQueryData(PROJECT_RUNDOWNS)?.loaded; - if (!rundownId) return undefined; - return ontimeQueryClient.getQueryData(getRundownQueryKey(rundownId)); -} - -export function getSelectionMode(event: MouseEvent): SelectionMode { - if ((isMacOS() && event.metaKey) || event.ctrlKey) { - return 'ctrl'; - } - - if (event.shiftKey) { - return 'shift'; - } - - return 'click'; +export function useEventSelection(selector: (state: EventSelectionStore) => T): T { + const { selectionStore } = useRundownScope(); + return useStore(selectionStore, selector); } diff --git a/apps/client/src/features/rundown/useSelectAndRevealEntry.ts b/apps/client/src/features/rundown/useSelectAndRevealEntry.ts index 8db177c5c..fb1ac4188 100644 --- a/apps/client/src/features/rundown/useSelectAndRevealEntry.ts +++ b/apps/client/src/features/rundown/useSelectAndRevealEntry.ts @@ -1,6 +1,7 @@ import { EntryId, MaybeString } from 'ontime-types'; import { useCallback } from 'react'; +import { useRundownScope } from '../../common/context/RundownScopeContext'; import { useCollapsedGroups } from './useCollapsedGroups'; import { useEventSelection } from './useEventSelection'; @@ -10,7 +11,8 @@ type SelectAndRevealOptions = { parent?: MaybeString; }; -export function useSelectAndRevealEntry(rundownId: string) { +export function useSelectAndRevealEntry() { + const { rundownId } = useRundownScope(); const { expandGroup } = useCollapsedGroups(rundownId); const selectEntry = useEventSelection((state) => state.setSelectedEvents); const scrollToEntry = useEventSelection((state) => state.scrollToEntry); diff --git a/apps/client/src/views/backstage/useBackstageData.ts b/apps/client/src/views/backstage/useBackstageData.ts index 1a804edac..ad5381240 100644 --- a/apps/client/src/views/backstage/useBackstageData.ts +++ b/apps/client/src/views/backstage/useBackstageData.ts @@ -1,8 +1,8 @@ import { CustomFields, OntimeEntry, ProjectData, Settings } from 'ontime-types'; import useCustomFields from '../../common/hooks-query/useCustomFields'; +import { useLoadedFlatRundown } from '../../common/hooks-query/useLoadedRundown'; 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 { ViewData, aggregateQueryStatus } from '../utils/viewLoader.utils'; @@ -20,7 +20,7 @@ export function useBackstageData(): ViewData { const isMirrored = useViewOptionsStore((state) => state.mirror); // HTTP API data - const { data: rundownData, status: rundownStatus } = useFlatRundown(); + const { data: rundownData, status: rundownStatus } = useLoadedFlatRundown(); const { data: projectData, status: projectDataStatus } = useProjectData(); const { data: settings, status: settingsStatus } = useSettings(); const { data: customFields, status: customFieldsStatus } = useCustomFields(); diff --git a/apps/client/src/views/common/schedule/ScheduleContext.tsx b/apps/client/src/views/common/schedule/ScheduleContext.tsx index c260c1958..090011fe8 100644 --- a/apps/client/src/views/common/schedule/ScheduleContext.tsx +++ b/apps/client/src/views/common/schedule/ScheduleContext.tsx @@ -12,7 +12,7 @@ import { useState, } from 'react'; -import { usePartialRundown } from '../../../common/hooks-query/useRundown'; +import { useLoadedPartialRundown } from '../../../common/hooks-query/useLoadedRundown'; import { ExtendedEntry } from '../../../common/utils/rundownMetadata'; import { useScheduleOptions } from './schedule.options'; @@ -45,7 +45,7 @@ export const ScheduleProvider = ({ children, selectedEventId }: PropsWithChildre [filter], ); - const { data: events } = usePartialRundown(filterCallback); + const { data: events } = useLoadedPartialRundown(filterCallback); const [firstIndex, setFirstIndex] = useState(-1); const [numPages, setNumPages] = useState(0); diff --git a/apps/client/src/views/countdown/useCountdownData.ts b/apps/client/src/views/countdown/useCountdownData.ts index 9faaf6c82..0c45b24e1 100644 --- a/apps/client/src/views/countdown/useCountdownData.ts +++ b/apps/client/src/views/countdown/useCountdownData.ts @@ -1,8 +1,8 @@ import { CustomFields, OntimeEntry, ProjectData, Settings } from 'ontime-types'; import useCustomFields from '../../common/hooks-query/useCustomFields'; +import { useLoadedFlatRundownWithMetadata } from '../../common/hooks-query/useLoadedRundown'; import useProjectData from '../../common/hooks-query/useProjectData'; -import { useFlatRundownWithMetadata } from '../../common/hooks-query/useRundown'; import useSettings from '../../common/hooks-query/useSettings'; import { useViewOptionsStore } from '../../common/stores/viewOptions'; import { ExtendedEntry } from '../../common/utils/rundownMetadata'; @@ -21,7 +21,7 @@ export function useCountdownData(): ViewData { const isMirrored = useViewOptionsStore((state) => state.mirror); // HTTP API data - const { data: rundownData, status: rundownStatus } = useFlatRundownWithMetadata(); + const { data: rundownData, status: rundownStatus } = useLoadedFlatRundownWithMetadata(); const { data: projectData, status: projectDataStatus } = useProjectData(); const { data: settings, status: settingsStatus } = useSettings(); const { data: customFields, status: customFieldsStatus } = useCustomFields(); diff --git a/apps/client/src/views/cuesheet/CuesheetPage.tsx b/apps/client/src/views/cuesheet/CuesheetPage.tsx index d2febeb17..7be760bc5 100644 --- a/apps/client/src/views/cuesheet/CuesheetPage.tsx +++ b/apps/client/src/views/cuesheet/CuesheetPage.tsx @@ -3,35 +3,31 @@ import { IoApps } from 'react-icons/io5'; import IconButton from '../../common/components/buttons/IconButton'; import NavigationMenu from '../../common/components/navigation-menu/NavigationMenu'; -import { EntryActionsProvider } from '../../common/context/EntryActionsContext'; -import { useScopedRundown } from '../../common/hooks-query/useScopedRundown'; -import { useScopedEntryActions } from '../../common/hooks/useEntryAction'; +import { EditableRundownScopeProvider } from '../../common/context/EditableRundownScopeProvider'; +import { useRundownSelection } from '../../common/hooks/useRundownSelection'; import { useWindowTitle } from '../../common/hooks/useWindowTitle'; import { getIsNavigationLocked } from '../../externals'; import CuesheetOverview from '../../features/overview/CuesheetOverview'; import EntryEditModal from './cuesheet-edit-modal/EntryEditModal'; import CuesheetProgress from './cuesheet-progress/CuesheetProgress'; import CuesheetTableWrapper from './CuesheetTableWrapper'; -import { FOLLOW_LOADED_RUNDOWN_ID, useCuesheetRundownSelection } from './useCuesheetRundownSelection'; import styles from './CuesheetPage.module.scss'; export default function CuesheetPage() { 'use memo'; const [isMenuOpen, menuHandler] = useDisclosure(); - const { selectedRundownId, loadedRundownId, setSelectedRundownId, projectRundowns } = useCuesheetRundownSelection(); - const source = useScopedRundown(selectedRundownId === FOLLOW_LOADED_RUNDOWN_ID ? loadedRundownId : selectedRundownId); - - const actions = useScopedEntryActions(source.rundownId); + const { scopedRundownId, selectedRundownId, loadedRundownId, setSelectedRundownId, projectRundowns } = + useRundownSelection('cuesheet'); useWindowTitle('Cuesheet'); const isLocked = getIsNavigationLocked(); return ( - + - +
{!isLocked && ( @@ -42,13 +38,12 @@ export default function CuesheetPage() {
-
+ ); } diff --git a/apps/client/src/views/cuesheet/CuesheetTableWrapper.tsx b/apps/client/src/views/cuesheet/CuesheetTableWrapper.tsx index 048f8f1c6..86f7bcea2 100644 --- a/apps/client/src/views/cuesheet/CuesheetTableWrapper.tsx +++ b/apps/client/src/views/cuesheet/CuesheetTableWrapper.tsx @@ -3,19 +3,18 @@ import { memo, use, useMemo } from 'react'; import Select from '../../common/components/select/Select'; import { PresetContext } from '../../common/context/PresetContext'; +import { useRundownScope } from '../../common/context/RundownScopeContext'; import useCustomFields from '../../common/hooks-query/useCustomFields'; -import type { RundownSource } from '../../common/hooks-query/useScopedRundown'; +import { FOLLOW_LOADED_RUNDOWN_ID } from '../../common/hooks/useRundownSelection'; import { AppMode } from '../../ontimeConfig'; import CuesheetDnd from './cuesheet-dnd/CuesheetDnd'; import { makeCuesheetColumns } from './cuesheet-table/cuesheet-table-elements/cuesheetColsFactory'; import CuesheetTable from './cuesheet-table/CuesheetTable'; import { useApplyCuesheetPolicy } from './useApplyCuesheetPolicy'; -import { FOLLOW_LOADED_RUNDOWN_ID } from './useCuesheetRundownSelection'; import styles from './CuesheetPage.module.scss'; interface CuesheetTableWrapperProps { - source: RundownSource; selectedRundownId: MaybeString; loadedRundownId: string; setSelectedRundownId: (rundownId: string) => void; @@ -24,15 +23,14 @@ interface CuesheetTableWrapperProps { export default memo(CuesheetTableWrapper); function CuesheetTableWrapper({ - source, selectedRundownId, setSelectedRundownId, loadedRundownId, projectRundowns, }: CuesheetTableWrapperProps) { const preset = use(PresetContext); - const isCurrentRundown = source.rundownId !== null && source.rundownId === loadedRundownId; - const { cuesheetMode, setCuesheetMode } = useApplyCuesheetPolicy(preset, { canRunMode: isCurrentRundown }); + const { isLoaded } = useRundownScope(); + const { cuesheetMode, setCuesheetMode } = useApplyCuesheetPolicy(preset, { canRunMode: isLoaded }); const { data: customFields } = useCustomFields(); const columns = useMemo( @@ -44,11 +42,9 @@ function CuesheetTableWrapper({ state.selectedEntryId); const closeModal = useEditModal((state) => state.clearSelection); diff --git a/apps/client/src/views/cuesheet/cuesheet-table/CuesheetTable.tsx b/apps/client/src/views/cuesheet/cuesheet-table/CuesheetTable.tsx index 76031ae67..6a562ae16 100644 --- a/apps/client/src/views/cuesheet/cuesheet-table/CuesheetTable.tsx +++ b/apps/client/src/views/cuesheet/cuesheet-table/CuesheetTable.tsx @@ -14,7 +14,8 @@ import { import EmptyFill from '../../../common/components/state/EmptyFill'; import EmptyTableBody from '../../../common/components/state/EmptyTableBody'; import { useEntryActionsContext } from '../../../common/context/EntryActionsContext'; -import type { RundownSource } from '../../../common/hooks-query/useScopedRundown'; +import { useRundownScope } from '../../../common/context/RundownScopeContext'; +import { useFlatRundownWithMetadata, useScopedSelectedEventId } from '../../../common/hooks-query/useRundown'; import type { ExtendedEntry } from '../../../common/utils/rundownMetadata'; import { usePersistedRundownOptions } from '../../../features/rundown/rundown.options'; import { useEventSelection } from '../../../features/rundown/useEventSelection'; @@ -41,20 +42,17 @@ import style from './CuesheetTable.module.scss'; type CuesheetTableBaseProps = { columns: CuesheetColumnDef[]; cuesheetMode: AppMode; - source: RundownSource; insertElement?: ReactNode; }; type EditorCuesheetTableProps = CuesheetTableBaseProps & { tableRoot: 'editor'; setCuesheetMode?: undefined; - isCurrentRundown?: undefined; }; type ViewCuesheetTableProps = CuesheetTableBaseProps & { tableRoot: 'cuesheet'; setCuesheetMode: (mode: AppMode) => void; - isCurrentRundown?: boolean; }; type CuesheetTableProps = EditorCuesheetTableProps | ViewCuesheetTableProps; @@ -62,13 +60,13 @@ type CuesheetTableProps = EditorCuesheetTableProps | ViewCuesheetTableProps; export default function CuesheetTable({ columns, cuesheetMode, - source, tableRoot, setCuesheetMode, - isCurrentRundown, insertElement, }: CuesheetTableProps) { - const { flatRundown, status, selectedEventId } = source; + const { isLoaded: isCurrentRundown } = useRundownScope(); + const { data: flatRundown, status } = useFlatRundownWithMetadata(); + const selectedEventId = useScopedSelectedEventId(); const { updateEntry, updateTimer, addEntry } = useEntryActionsContext(); const { getLocalizedString } = useTranslation(); const canCreateEntries = useCuesheetPermissions((state) => state.canCreateEntries) && cuesheetMode === AppMode.Edit; diff --git a/apps/client/src/views/editor/Editor.tsx b/apps/client/src/views/editor/Editor.tsx index 66d848c65..94ae7d3d9 100644 --- a/apps/client/src/views/editor/Editor.tsx +++ b/apps/client/src/views/editor/Editor.tsx @@ -1,5 +1,6 @@ import { lazy } from 'react'; +import { EditableRundownScopeProvider } from '../../common/context/EditableRundownScopeProvider'; import TrackingPlaybackBar from '../../features/control/playback/tracking-playback-bar/TrackingPlaybackBar'; import { AppMode } from '../../ontimeConfig'; import TitleList from './title-list/TitleList'; @@ -21,7 +22,9 @@ export default function Editor() {
- + + +
); } @@ -29,14 +32,17 @@ export default function Editor() { if (layoutMode === EditorLayoutMode.TRACKING) { return (
-
-
- + {/* the titles and the rundown share a scope, so they follow one cursor */} + +
+
+ +
+
+ +
-
- -
-
+
); @@ -44,14 +50,16 @@ export default function Editor() { return (
-
-
- + +
+
+ +
+
+ +
-
- -
-
+
); } diff --git a/apps/client/src/views/editor/finder/useFinder.tsx b/apps/client/src/views/editor/finder/useFinder.tsx index e933834ce..53e6c4ba1 100644 --- a/apps/client/src/views/editor/finder/useFinder.tsx +++ b/apps/client/src/views/editor/finder/useFinder.tsx @@ -207,10 +207,10 @@ export function searchByText( * @param activeFilter - a field selected from the filter badges, if any */ export default function useFinder(searchValue: string, activeFilter: MaybeString) { - const { data, rundownId } = useFlatRundown(); + const { data } = useFlatRundown(); const { data: customFields } = useCustomFields(); - const selectAndRevealEntry = useSelectAndRevealEntry(rundownId); + const selectAndRevealEntry = useSelectAndRevealEntry(); /** The filters offered to the user: the fixed fields plus whatever the project defines */ const filters = useMemo(() => { diff --git a/apps/client/src/views/editor/title-list/TitleList.tsx b/apps/client/src/views/editor/title-list/TitleList.tsx index 1082a8677..cfe4ee231 100644 --- a/apps/client/src/views/editor/title-list/TitleList.tsx +++ b/apps/client/src/views/editor/title-list/TitleList.tsx @@ -46,7 +46,6 @@ export default function TitleList({ mode }: TitleListProps) { eventData={eventData} selectedEventId={selectedEventId} resolvedFollowEventId={resolvedFollowEventId} - rundownId={rundown.id} /> ); } @@ -56,21 +55,14 @@ interface TitleListContentProps { eventData: ExtendedEntry[]; selectedEventId: string | null; resolvedFollowEventId: string | null; - rundownId: string; } -function TitleListContent({ - mode, - eventData, - selectedEventId, - resolvedFollowEventId, - rundownId, -}: TitleListContentProps) { +function TitleListContent({ mode, eventData, selectedEventId, resolvedFollowEventId }: TitleListContentProps) { 'use memo'; const virtuosoRef = useRef(null); const scrollParentRef = useRef(null); - const selectAndRevealEntry = useSelectAndRevealEntry(rundownId); + const selectAndRevealEntry = useSelectAndRevealEntry(); // Calculate current event info const currentEventInfo = useMemo(() => { diff --git a/apps/client/src/views/timeline/useTimelineData.ts b/apps/client/src/views/timeline/useTimelineData.ts index bd2f996f6..9abc78d94 100644 --- a/apps/client/src/views/timeline/useTimelineData.ts +++ b/apps/client/src/views/timeline/useTimelineData.ts @@ -1,8 +1,8 @@ import { CustomFields, OntimeEntry, ProjectData, Settings } from 'ontime-types'; import useCustomFields from '../../common/hooks-query/useCustomFields'; +import { useLoadedFlatRundownWithMetadata } from '../../common/hooks-query/useLoadedRundown'; import useProjectData from '../../common/hooks-query/useProjectData'; -import { useFlatRundownWithMetadata } from '../../common/hooks-query/useRundown'; import useSettings from '../../common/hooks-query/useSettings'; import { ExtendedEntry } from '../../common/utils/rundownMetadata'; import { ViewData, aggregateQueryStatus } from '../utils/viewLoader.utils'; @@ -16,7 +16,7 @@ export interface TimelineData { export function useTimelineData(): ViewData { // HTTP API data - const { data: rundownData, status: rundownStatus } = useFlatRundownWithMetadata(); + const { data: rundownData, status: rundownStatus } = useLoadedFlatRundownWithMetadata(); const { data: projectData, status: projectDataStatus } = useProjectData(); const { data: settings, status: settingsStatus } = useSettings(); const { data: customFields, status: customFieldsStatus } = useCustomFields(); diff --git a/apps/client/src/views/timer/useTimerData.ts b/apps/client/src/views/timer/useTimerData.ts index 406912e0b..6212b4945 100644 --- a/apps/client/src/views/timer/useTimerData.ts +++ b/apps/client/src/views/timer/useTimerData.ts @@ -1,8 +1,8 @@ import { CustomFields, ProjectData, RundownEntries, Settings, ViewSettings } from 'ontime-types'; import useCustomFields from '../../common/hooks-query/useCustomFields'; +import { useLoadedRundown } from '../../common/hooks-query/useLoadedRundown'; import useProjectData from '../../common/hooks-query/useProjectData'; -import useRundown from '../../common/hooks-query/useRundown'; import useSettings from '../../common/hooks-query/useSettings'; import useViewSettings from '../../common/hooks-query/useViewSettings'; import { useViewOptionsStore } from '../../common/stores/viewOptions'; @@ -26,7 +26,7 @@ export function useTimerData(): ViewData { const { data: viewSettings, status: viewSettingsStatus } = useViewSettings(); const { data: settings, status: settingsStatus } = useSettings(); const { data: customFields, status: customFieldsStatus } = useCustomFields(); - const { data: rundown, status: rundownStatus } = useRundown(); + const { data: rundown, status: rundownStatus } = useLoadedRundown(); const { entries } = rundown; return {