refactor: react state for cuesheet v editor layout

This commit is contained in:
alex-arc
2026-06-16 19:31:48 +02:00
parent b2f9b812bd
commit 58dce0a18c
11 changed files with 37 additions and 64 deletions
@@ -59,7 +59,6 @@ export function useRundownWithMetadata(rundownId: Maybe<string>) {
/** /**
* Provides access to a flat rundown * Provides access to a flat rundown
* built from the order and rundown fields * built from the order and rundown fields
* @deprecated
*/ */
export function useFlatRundown(rundownId: Maybe<string>) { export function useFlatRundown(rundownId: Maybe<string>) {
const { data, status } = useRundown(rundownId); const { data, status } = useRundown(rundownId);
@@ -90,7 +89,6 @@ export function useFlatRundownWithMetadata(rundownId: Maybe<string>) {
* Callers MUST memoize the callback with useCallback to prevent * Callers MUST memoize the callback with useCallback to prevent
* re-filtering on every render. * re-filtering on every render.
* *
* @deprecated
*/ */
export function usePartialRundown(rundownId: Maybe<string>, cb: (event: ExtendedEntry<OntimeEntry>) => boolean) { export function usePartialRundown(rundownId: Maybe<string>, cb: (event: ExtendedEntry<OntimeEntry>) => boolean) {
const { data, status } = useFlatRundownWithMetadata(rundownId); const { data, status } = useFlatRundownWithMetadata(rundownId);
@@ -112,10 +110,6 @@ export function useEntry(rundownId: Maybe<string>, entryId: EntryId | null): Ont
return rundown.entries[entryId] ?? null; return rundown.entries[entryId] ?? null;
} }
/**
*
* @deprecated
*/
export function useRundownAuxData(rundownId: Maybe<string>) { export function useRundownAuxData(rundownId: Maybe<string>) {
const { data, status } = useRundown(rundownId); const { data, status } = useRundown(rundownId);
const filteredData = useMemo(() => { const filteredData = useMemo(() => {
@@ -210,7 +210,7 @@ export function MetadataTimes() {
function GroupTimes() { function GroupTimes() {
const { clock, mode, groupExpectedEnd, actualGroupStart, currentDay, playback, phase } = useGroupTimerOverView(); const { clock, mode, groupExpectedEnd, actualGroupStart, currentDay, playback, phase } = useGroupTimerOverView();
const currentGroupId = useCurrentGroupId(); const currentGroupId = useCurrentGroupId();
const group = useEntry(currentGroupId) as OntimeGroup | null; const group = useEntry(null, currentGroupId) as OntimeGroup | null;
const hasRunningTimer = phase !== TimerPhase.Pending && isPlaybackActive(playback); const hasRunningTimer = phase !== TimerPhase.Pending && isPlaybackActive(playback);
@@ -266,7 +266,7 @@ function GroupTimes() {
function FlagTimes() { function FlagTimes() {
const { clock, mode, actualStart, plannedStart, playback, currentDay, phase } = useFlagTimerOverView(); const { clock, mode, actualStart, plannedStart, playback, currentDay, phase } = useFlagTimerOverView();
const { id, expectedStart } = useNextFlag(); const { id, expectedStart } = useNextFlag();
const entry = useEntry(id) as OntimeEvent | null; const entry = useEntry(null, id) as OntimeEvent | null;
const hasRunningTimer = phase !== TimerPhase.Pending && isPlaybackActive(playback); const hasRunningTimer = phase !== TimerPhase.Pending && isPlaybackActive(playback);
@@ -6,7 +6,7 @@ import style from './TitleOverview.module.scss';
export default function TitleOverview() { export default function TitleOverview() {
'use memo'; 'use memo';
const { data: projectData } = useProjectData(); const { data: projectData } = useProjectData();
const { data: rundownData } = useRundownAuxData(); const { data: rundownData } = useRundownAuxData(null);
if (!projectData.title && !rundownData.title) { if (!projectData.title && !rundownData.title) {
return null; return null;
@@ -20,7 +20,7 @@ export function useBackstageData(): ViewData<BackstageData> {
const isMirrored = useViewOptionsStore((state) => state.mirror); const isMirrored = useViewOptionsStore((state) => state.mirror);
// HTTP API data // HTTP API data
const { data: rundownData, status: rundownStatus } = useFlatRundown(); const { data: rundownData, status: rundownStatus } = useFlatRundown(null);
const { data: projectData, status: projectDataStatus } = useProjectData(); const { data: projectData, status: projectDataStatus } = useProjectData();
const { data: settings, status: settingsStatus } = useSettings(); const { data: settings, status: settingsStatus } = useSettings();
const { data: customFields, status: customFieldsStatus } = useCustomFields(); const { data: customFields, status: customFieldsStatus } = useCustomFields();
@@ -45,7 +45,7 @@ export const ScheduleProvider = ({ children, selectedEventId }: PropsWithChildre
[filter], [filter],
); );
const { data: events } = usePartialRundown(filterCallback); const { data: events } = usePartialRundown(null, filterCallback);
const [firstIndex, setFirstIndex] = useState(-1); const [firstIndex, setFirstIndex] = useState(-1);
const [numPages, setNumPages] = useState(0); const [numPages, setNumPages] = useState(0);
@@ -21,7 +21,7 @@ export function useCountdownData(): ViewData<CountdownData> {
const isMirrored = useViewOptionsStore((state) => state.mirror); const isMirrored = useViewOptionsStore((state) => state.mirror);
// HTTP API data // HTTP API data
const { data: rundownData, status: rundownStatus } = useFlatRundownWithMetadata(); const { data: rundownData, status: rundownStatus } = useFlatRundownWithMetadata(null);
const { data: projectData, status: projectDataStatus } = useProjectData(); const { data: projectData, status: projectDataStatus } = useProjectData();
const { data: settings, status: settingsStatus } = useSettings(); const { data: settings, status: settingsStatus } = useSettings();
const { data: customFields, status: customFieldsStatus } = useCustomFields(); const { data: customFields, status: customFieldsStatus } = useCustomFields();
@@ -34,7 +34,6 @@ function CuesheetTableWrapper() {
cuesheetMode={cuesheetMode} cuesheetMode={cuesheetMode}
tableRoot='cuesheet' tableRoot='cuesheet'
setCuesheetMode={setCuesheetMode} setCuesheetMode={setCuesheetMode}
isCurrentRundown={isLoadedRundown}
/> />
)} )}
</CuesheetDnd> </CuesheetDnd>
@@ -1,7 +1,7 @@
import { useTableNav } from '@table-nav/react'; import { useTableNav } from '@table-nav/react';
import { ColumnDef, Table, getCoreRowModel, useReactTable } from '@tanstack/react-table'; import { ColumnDef, Table, getCoreRowModel, useReactTable } from '@tanstack/react-table';
import { OntimeEntry, SupportedEntry, TimeField, isOntimeDelay, isOntimeGroup, isOntimeMilestone } from 'ontime-types'; import { OntimeEntry, SupportedEntry, TimeField, isOntimeDelay, isOntimeGroup, isOntimeMilestone } from 'ontime-types';
import { ComponentProps, ReactNode, memo, useCallback, useEffect, useMemo, useRef } from 'react'; import { ComponentProps, memo, useCallback, useEffect, useMemo, useRef } from 'react';
import { import {
ContextProp, ContextProp,
ItemProps, ItemProps,
@@ -40,25 +40,17 @@ type CuesheetTableBaseProps = {
type EditorCuesheetTableProps = CuesheetTableBaseProps & { type EditorCuesheetTableProps = CuesheetTableBaseProps & {
tableRoot: 'editor'; tableRoot: 'editor';
setCuesheetMode?: undefined; setCuesheetMode?: undefined;
isCurrentRundown?: undefined;
}; };
type ViewCuesheetTableProps = CuesheetTableBaseProps & { type ViewCuesheetTableProps = CuesheetTableBaseProps & {
tableRoot: 'cuesheet'; tableRoot: 'cuesheet';
setCuesheetMode: (mode: AppMode) => void; setCuesheetMode: (mode: AppMode) => void;
isCurrentRundown?: boolean;
}; };
type CuesheetTableProps = EditorCuesheetTableProps | ViewCuesheetTableProps; type CuesheetTableProps = EditorCuesheetTableProps | ViewCuesheetTableProps;
export default function CuesheetTable({ export default function CuesheetTable({ columns, cuesheetMode, tableRoot, setCuesheetMode }: CuesheetTableProps) {
columns, const { flatRundown, status, loadedEventId } = useContextRundownTable();
cuesheetMode,
tableRoot,
setCuesheetMode,
isCurrentRundown,
}: CuesheetTableProps) {
const { flatRundown, status, selectedEventId } = source;
const { updateEntry, updateTimer, addEntry } = useEntryActionsContext(); const { updateEntry, updateTimer, addEntry } = useEntryActionsContext();
const canCreateEntries = useCuesheetPermissions((state) => state.canCreateEntries) && cuesheetMode === AppMode.Edit; const canCreateEntries = useCuesheetPermissions((state) => state.canCreateEntries) && cuesheetMode === AppMode.Edit;
@@ -238,16 +230,9 @@ export default function CuesheetTable({
handleResetResizing={resetColumnResizing} handleResetResizing={resetColumnResizing}
handleResetReordering={resetColumnOrder} handleResetReordering={resetColumnOrder}
handleClearToggles={setAllVisible} handleClearToggles={setAllVisible}
modeControls={ appMode={cuesheetMode}
tableRoot === 'cuesheet' tableRoot={tableRoot}
? { setCuesheetMode={setCuesheetMode}
cuesheetMode,
setCuesheetMode,
isCurrentRundown,
}
: undefined
}
showShare={tableRoot === 'cuesheet'}
/> />
<TableVirtuoso <TableVirtuoso
ref={virtuosoRef} ref={virtuosoRef}
@@ -10,6 +10,7 @@ import Button from '../../../../common/components/buttons/Button';
import Checkbox from '../../../../common/components/checkbox/Checkbox'; import Checkbox from '../../../../common/components/checkbox/Checkbox';
import * as Editor from '../../../../common/components/editor-utils/EditorUtils'; import * as Editor from '../../../../common/components/editor-utils/EditorUtils';
import PopoverContents from '../../../../common/components/popover/Popover'; import PopoverContents from '../../../../common/components/popover/Popover';
import { useRundownSelectionContext } from '../../../../common/context/RundownSelectionContext';
import type { ExtendedEntry } from '../../../../common/utils/rundownMetadata'; import type { ExtendedEntry } from '../../../../common/utils/rundownMetadata';
import { AppMode } from '../../../../ontimeConfig'; import { AppMode } from '../../../../ontimeConfig';
import { useCuesheetPermissions } from '../../useTablePermissions'; import { useCuesheetPermissions } from '../../useTablePermissions';
@@ -31,20 +32,15 @@ type TableHeaderOptionValues = Pick<
'hideTableSeconds' | 'hideIndexColumn' | 'showDelayedTimes' | 'hideDelays' 'hideTableSeconds' | 'hideIndexColumn' | 'showDelayedTimes' | 'hideDelays'
>; >;
type TableModeControls = {
cuesheetMode: AppMode;
setCuesheetMode: (mode: AppMode) => void;
isCurrentRundown?: boolean;
};
interface CuesheetTableHeaderToolbarProps { interface CuesheetTableHeaderToolbarProps {
columns: Column<ExtendedEntry, unknown>[]; columns: Column<ExtendedEntry, unknown>[];
optionsStore: TableHeaderOptionsStore; optionsStore: TableHeaderOptionsStore;
handleResetResizing: () => void; handleResetResizing: () => void;
handleResetReordering: () => void; handleResetReordering: () => void;
handleClearToggles: () => void; handleClearToggles: () => void;
modeControls?: TableModeControls; setCuesheetMode?: (mode: AppMode) => void;
showShare?: boolean; appMode: AppMode;
tableRoot: 'editor' | 'cuesheet';
} }
export default function CuesheetTableHeaderToolbar({ export default function CuesheetTableHeaderToolbar({
@@ -53,22 +49,22 @@ export default function CuesheetTableHeaderToolbar({
handleResetResizing, handleResetResizing,
handleResetReordering, handleResetReordering,
handleClearToggles, handleClearToggles,
modeControls, setCuesheetMode,
showShare = false, tableRoot,
appMode,
}: CuesheetTableHeaderToolbarProps) { }: CuesheetTableHeaderToolbarProps) {
const canChangeMode = useCuesheetPermissions((state) => state.canChangeMode); const canChangeMode = useCuesheetPermissions((state) => state.canChangeMode) && tableRoot === 'cuesheet';
const canShare = useCuesheetPermissions((state) => state.canShare); const canShare = useCuesheetPermissions((state) => state.canShare) && tableRoot === 'cuesheet';
const { isLoadedRundown } = useRundownSelectionContext();
const toggleCuesheetMode = (mode: AppMode[]) => { const toggleCuesheetMode = (mode: AppMode[]) => {
const newValue = mode.at(0); const newValue = mode.at(0);
if (!newValue || !modeControls) return; if (!newValue || !setCuesheetMode) return;
modeControls.setCuesheetMode(newValue); setCuesheetMode(newValue);
}; };
const isBackground = !(modeControls?.isCurrentRundown ?? true);
return ( return (
<Toolbar.Root className={style.tableSettings} data-background-rundown={isBackground}> <Toolbar.Root className={style.tableSettings} data-background-rundown={!isLoadedRundown}>
<ViewSettings optionsStore={optionsStore} /> <ViewSettings optionsStore={optionsStore} />
<ColumnSettings <ColumnSettings
columns={columns} columns={columns}
@@ -76,15 +72,14 @@ export default function CuesheetTableHeaderToolbar({
handleResetReordering={handleResetReordering} handleResetReordering={handleResetReordering}
handleClearToggles={handleClearToggles} handleClearToggles={handleClearToggles}
/> />
{modeControls && canChangeMode && ( <div className={style.apart}>
<div className={style.apart}> <RundownSelect appMode={appMode} />
{/* {insertElement} */} {canChangeMode && (
<RundownSelect cuesheetMode={modeControls.cuesheetMode} />
<ToggleGroup <ToggleGroup
value={[modeControls.cuesheetMode]} value={[appMode]}
onValueChange={toggleCuesheetMode} onValueChange={toggleCuesheetMode}
className={style.group} className={style.group}
disabled={!modeControls.isCurrentRundown} disabled={!isLoadedRundown}
> >
<Toolbar.Button render={<Toggle />} value={AppMode.Run} className={style.radioButton}> <Toolbar.Button render={<Toggle />} value={AppMode.Run} className={style.radioButton}>
Run Run
@@ -93,10 +88,10 @@ export default function CuesheetTableHeaderToolbar({
Edit Edit
</Toolbar.Button> </Toolbar.Button>
</ToggleGroup> </ToggleGroup>
</div> )}
)} </div>
{showShare && canShare && ( {canShare && (
<> <>
<Editor.Separator orientation='vertical' /> <Editor.Separator orientation='vertical' />
<CuesheetShareModal /> <CuesheetShareModal />
@@ -7,10 +7,10 @@ import styles from './RundownSelect.module.scss';
const FOLLOW = '___null___'; const FOLLOW = '___null___';
interface RundownSelectProps { interface RundownSelectProps {
cuesheetMode: AppMode; appMode: AppMode;
} }
export function RundownSelect({ cuesheetMode }: RundownSelectProps) { export function RundownSelect({ appMode }: RundownSelectProps) {
'use memo'; 'use memo';
const { selectRundownId, rundowns, loadedRundownId, selectedRundownId } = useRundownSelectionContext(); const { selectRundownId, rundowns, loadedRundownId, selectedRundownId } = useRundownSelectionContext();
@@ -34,7 +34,7 @@ export function RundownSelect({ cuesheetMode }: RundownSelectProps) {
if (value === FOLLOW) selectRundownId(null); if (value === FOLLOW) selectRundownId(null);
else selectRundownId(value); else selectRundownId(value);
}} }}
disabled={cuesheetMode === AppMode.Run} disabled={appMode === AppMode.Run}
fluid fluid
/> />
</div> </div>
@@ -16,7 +16,7 @@ export interface TimelineData {
export function useTimelineData(): ViewData<TimelineData> { export function useTimelineData(): ViewData<TimelineData> {
// HTTP API data // HTTP API data
const { data: rundownData, status: rundownStatus } = useFlatRundownWithMetadata(); const { data: rundownData, status: rundownStatus } = useFlatRundownWithMetadata(null);
const { data: projectData, status: projectDataStatus } = useProjectData(); const { data: projectData, status: projectDataStatus } = useProjectData();
const { data: settings, status: settingsStatus } = useSettings(); const { data: settings, status: settingsStatus } = useSettings();
const { data: customFields, status: customFieldsStatus } = useCustomFields(); const { data: customFields, status: customFieldsStatus } = useCustomFields();