refactor: table mode and context

This commit is contained in:
Carlos Valente
2026-01-10 09:41:40 +01:00
committed by Carlos Valente
parent 8c22968bc9
commit c5045ad82e
64 changed files with 560 additions and 273 deletions
@@ -3,6 +3,8 @@ import { useDisclosure } from '@mantine/hooks';
import IconButton from '../../common/components/buttons/IconButton';
import NavigationMenu from '../../common/components/navigation-menu/NavigationMenu';
import { EntryActionsProvider } from '../../common/context/EntryActionsContext';
import { useEntryActions } from '../../common/hooks/useEntryAction';
import { useWindowTitle } from '../../common/hooks/useWindowTitle';
import { getIsNavigationLocked } from '../../externals';
import CuesheetOverview from '../../features/overview/CuesheetOverview';
@@ -15,13 +17,14 @@ import styles from './CuesheetPage.module.scss';
export default function CuesheetPage() {
const [isMenuOpen, menuHandler] = useDisclosure();
const entryActions = useEntryActions();
useWindowTitle('Cuesheet');
const isLocked = getIsNavigationLocked();
return (
<>
<EntryActionsProvider actions={entryActions}>
<NavigationMenu isOpen={isMenuOpen} onClose={menuHandler.close} />
<CuesheetEditModal />
<div className={styles.tableWrapper} data-testid='cuesheet'>
@@ -35,6 +38,6 @@ export default function CuesheetPage() {
<CuesheetProgress />
<CuesheetTableWrapper />
</div>
</>
</EntryActionsProvider>
);
}
@@ -6,12 +6,13 @@ import { isOntimeDelay, isOntimeGroup, isOntimeMilestone, OntimeEntry, TimeField
import EmptyPage from '../../../common/components/state/EmptyPage';
import EmptyTableBody from '../../../common/components/state/EmptyTableBody';
import { useEntryActions } from '../../../common/hooks/useEntryAction';
import { useEntryActionsContext } from '../../../common/context/EntryActionsContext';
import { useSelectedEventId } from '../../../common/hooks/useSocket';
import { useFlatRundownWithMetadata } from '../../../common/hooks-query/useRundown';
import type { ExtendedEntry } from '../../../common/utils/rundownMetadata';
import { usePersistedRundownOptions } from '../../../features/rundown/rundown.options';
import EditorTableSettings from '../../../features/rundown/rundown-table/EditorTableSettings';
import { useEventSelection } from '../../../features/rundown/useEventSelection';
import { AppMode } from '../../../ontimeConfig';
import { usePersistedCuesheetOptions } from '../cuesheet.options';
@@ -35,7 +36,7 @@ interface CuesheetTableProps {
export default function CuesheetTable({ columns, cuesheetMode, tableRoot = 'cuesheet' }: CuesheetTableProps) {
const { data, status } = useFlatRundownWithMetadata();
const { updateEntry, updateTimer } = useEntryActions();
const { updateEntry, updateTimer } = useEntryActionsContext();
const useOptions = tableRoot === 'editor' ? usePersistedRundownOptions : usePersistedCuesheetOptions;
const showDelayedTimes = useOptions((state) => state.showDelayedTimes);
@@ -43,6 +44,7 @@ export default function CuesheetTable({ columns, cuesheetMode, tableRoot = 'cues
const hideIndexColumn = useOptions((state) => state.hideIndexColumn);
const selectedEventId = useSelectedEventId();
const cursor = useEventSelection((state) => state.cursor);
const virtuosoRef = useRef<TableVirtuosoHandle | null>(null);
const { listeners } = useTableNav();
@@ -112,15 +114,22 @@ export default function CuesheetTable({ columns, cuesheetMode, tableRoot = 'cues
setColumnSizing({});
}, [setColumnSizing]);
// in run mode, we follow the selected row
// in edit mode, we follow the cursor (e.g. from finder)
useEffect(() => {
if (cuesheetMode === AppMode.Edit || virtuosoRef.current === null || !selectedEventId) {
if (virtuosoRef.current === null) {
return;
}
const eventIndex = data.findIndex((event) => event.id === selectedEventId);
virtuosoRef.current.scrollToIndex({ index: eventIndex, behavior: 'smooth' });
}, [cuesheetMode, data, selectedEventId]);
const targetId = cuesheetMode === AppMode.Edit ? cursor : selectedEventId;
if (!targetId) {
return;
}
const eventIndex = data.findIndex((event) => event.id === targetId);
if (eventIndex !== -1) {
virtuosoRef.current.scrollToIndex({ index: eventIndex, behavior: 'smooth', align: 'center' });
}
}, [cuesheetMode, data, selectedEventId, cursor]);
/**
* To improve performance on resizing, we memoise the column sizes
@@ -184,6 +193,7 @@ export default function CuesheetTable({ columns, cuesheetMode, tableRoot = 'cues
const row = rows[rowIndex];
const key = row.original.id;
const entry = row.original;
const hasCursor = entry.id === cursor;
if (isOntimeGroup(entry)) {
return (
@@ -195,6 +205,7 @@ export default function CuesheetTable({ columns, cuesheetMode, tableRoot = 'cues
rowIndex={row.index}
table={table}
injectedStyles={injectedStyles}
hasCursor={hasCursor}
{...virtuosoProps}
/>
);
@@ -202,7 +213,13 @@ export default function CuesheetTable({ columns, cuesheetMode, tableRoot = 'cues
if (isOntimeDelay(entry)) {
return (
<DelayRow key={key} duration={entry.duration} injectedStyles={injectedStyles} {...virtuosoProps} />
<DelayRow
key={key}
duration={entry.duration}
injectedStyles={injectedStyles}
hasCursor={hasCursor}
{...virtuosoProps}
/>
);
}
@@ -219,6 +236,7 @@ export default function CuesheetTable({ columns, cuesheetMode, tableRoot = 'cues
rowIndex={rowIndex}
table={table}
injectedStyles={injectedStyles}
hasCursor={hasCursor}
{...virtuosoProps}
/>
);
@@ -241,6 +259,7 @@ export default function CuesheetTable({ columns, cuesheetMode, tableRoot = 'cues
rowIndex={rowIndex}
table={table}
injectedStyles={injectedStyles}
hasCursor={hasCursor}
{...virtuosoProps}
/>
);
@@ -5,6 +5,12 @@
color: $ontime-delay-text;
border-left: 4px solid transparent;
&[data-cursor='true'] {
outline: 2px solid $blue-500;
outline-offset: -2px;
background: color-mix(in srgb, transparent 80%, var(--user-bg, $gray-500) 20%);
}
td {
width: calc(100% - 4px);
padding-block: 0.5rem;
@@ -8,9 +8,10 @@ import style from './DelayRow.module.scss';
interface DelayRowProps {
duration: number;
injectedStyles?: CSSProperties;
hasCursor?: boolean;
}
function DelayRow({ duration, injectedStyles, ...virtuosoProps }: DelayRowProps) {
function DelayRow({ duration, injectedStyles, hasCursor, ...virtuosoProps }: DelayRowProps) {
const hideDelays = usePersistedCuesheetOptions((state) => state.hideDelays);
if (hideDelays || duration === 0) {
@@ -29,7 +30,13 @@ function DelayRow({ duration, injectedStyles, ...virtuosoProps }: DelayRowProps)
const delayTime = millisToDelayString(duration, 'expanded');
return (
<tr className={style.delayRow} data-testid='cuesheet-delay' style={injectedStyles} {...virtuosoProps}>
<tr
className={style.delayRow}
data-testid='cuesheet-delay'
style={injectedStyles}
data-cursor={hasCursor}
{...virtuosoProps}
>
<td tabIndex={0}>{delayTime}</td>
</tr>
);
@@ -12,6 +12,12 @@
background: color-mix(in srgb, transparent 80%, var(--user-bg, $gray-500) 20%);
}
&[data-cursor='true'] {
outline: 2px solid $blue-500;
outline-offset: -2px;
background: color-mix(in srgb, transparent 80%, var(--user-bg, $gray-500) 20%);
}
&.firstAfterGroup {
margin-top: 2rem;
}
@@ -27,6 +27,7 @@ interface EventRowProps {
rowIndex: number;
table: Table<ExtendedEntry<OntimeEntry>>;
injectedStyles?: CSSProperties;
hasCursor?: boolean;
}
export default function EventRow({
@@ -44,6 +45,7 @@ export default function EventRow({
rowIndex,
table,
injectedStyles,
hasCursor,
...virtuosoProps
}: EventRowProps) {
const { cuesheetMode, hideIndexColumn } = table.options.meta?.options ?? {
@@ -87,6 +89,7 @@ export default function EventRow({
opacity: `${isPast ? '0.2' : '1'}`,
'--user-bg': groupColour ?? 'transparent',
}}
data-cursor={hasCursor}
data-testid='cuesheet-event'
{...virtuosoProps}
>
@@ -17,6 +17,12 @@
font-weight: bold;
&[data-cursor='true'] {
outline: 2px solid $blue-500;
outline-offset: -2px;
background: color-mix(in srgb, transparent 80%, var(--user-bg, $gray-500) 20%);
}
td {
min-height: 3.5rem;
padding-top: 0.75rem !important; // fighting styles from cuesheet-table
@@ -17,6 +17,7 @@ interface GroupRowProps {
rowIndex: number;
table: Table<ExtendedEntry>;
injectedStyles?: CSSProperties;
hasCursor?: boolean;
}
export default function GroupRow({
@@ -26,6 +27,7 @@ export default function GroupRow({
rowIndex,
table,
injectedStyles,
hasCursor,
...virtuosoProps
}: GroupRowProps) {
const { cuesheetMode, hideIndexColumn } = table.options.meta?.options ?? {
@@ -40,6 +42,7 @@ export default function GroupRow({
className={style.groupRow}
style={{ ...injectedStyles, '--user-bg': colour }}
data-testid='cuesheet-group'
data-cursor={hasCursor}
{...virtuosoProps}
>
{cuesheetMode === AppMode.Edit && (
@@ -13,6 +13,12 @@
background: color-mix(in srgb, transparent 80%, var(--user-bg, $gray-500) 20%);
}
&[data-cursor='true'] {
outline: 2px solid $blue-500;
outline-offset: -2px;
background: color-mix(in srgb, transparent 80%, var(--user-bg, $gray-500) 20%);
}
td {
background-color: $gray-1250;
border-radius: 2px;
@@ -22,6 +22,7 @@ interface MilestoneRowProps {
rowIndex: number;
table: Table<ExtendedEntry>;
injectedStyles?: CSSProperties;
hasCursor?: boolean;
}
export default function MilestoneRow({
@@ -32,6 +33,7 @@ export default function MilestoneRow({
colour,
rowId,
rowIndex,
hasCursor,
table,
injectedStyles,
...virtuosoProps
@@ -64,6 +66,7 @@ export default function MilestoneRow({
'--user-bg': parentBgColour ?? 'transparent',
}}
data-testid='cuesheet-milestone'
data-cursor={hasCursor}
{...virtuosoProps}
>
{cuesheetMode === AppMode.Edit && (
@@ -3,7 +3,7 @@ import { IoAdd, IoArrowDown, IoArrowUp, IoDuplicateOutline, IoOptions, IoTrash }
import { SupportedEntry } from 'ontime-types';
import { PositionedDropdownMenu } from '../../../../common/components/dropdown-menu/DropdownMenu';
import { useEntryActions } from '../../../../common/hooks/useEntryAction';
import { useEntryActionsContext } from '../../../../common/context/EntryActionsContext';
import { useCuesheetEditModal } from '../../cuesheet-edit-modal/useCuesheetEditModal';
import { useCuesheetPermissions } from '../../useTablePermissions';
@@ -13,7 +13,7 @@ export default memo(CuesheetTableMenu);
function CuesheetTableMenu() {
const { isOpen, entryId, entryIndex, parentId, flag, position, closeMenu } = useCuesheetTableMenu();
const { addEntry, clone, deleteEntry, move, updateEntry } = useEntryActions();
const { addEntry, clone, deleteEntry, move, updateEntry } = useEntryActionsContext();
const showModal = useCuesheetEditModal((state) => state.setEditableEntry);
const permissions = useCuesheetPermissions();
@@ -3,7 +3,7 @@ import { IoAdd, IoArrowDown, IoArrowUp, IoDuplicateOutline, IoOptions, IoTrash }
import { SupportedEntry } from 'ontime-types';
import { PositionedDropdownMenu } from '../../../../common/components/dropdown-menu/DropdownMenu';
import { useEntryActions } from '../../../../common/hooks/useEntryAction';
import { useEntryActionsContext } from '../../../../common/context/EntryActionsContext';
import { useCuesheetEditModal } from '../../cuesheet-edit-modal/useCuesheetEditModal';
import { useCuesheetTableMenu } from './useCuesheetTableMenu';
@@ -12,7 +12,7 @@ export default memo(EditorTableMenu);
function EditorTableMenu() {
const { isOpen, entryId, entryIndex, parentId, flag, position, closeMenu } = useCuesheetTableMenu();
const { addEntry, clone, deleteEntry, move, updateEntry } = useEntryActions();
const { addEntry, clone, deleteEntry, move, updateEntry } = useEntryActionsContext();
const showModal = useCuesheetEditModal((state) => state.setEditableEntry);
if (!isOpen) {
@@ -1,5 +1,5 @@
import { ReactNode, use } from 'react';
import { IoChevronDown, IoOptions, IoSettingsOutline } from 'react-icons/io5';
import { IoBookOutline, IoChevronDown, IoOptions } from 'react-icons/io5';
import { Popover } from '@base-ui/react/popover';
import { Toggle } from '@base-ui/react/toggle';
import { ToggleGroup } from '@base-ui/react/toggle-group';
@@ -100,7 +100,7 @@ export function ViewSettings({ optionsStore }: ViewSettingsProps) {
<Toolbar.Button
render={
<Button variant='ghosted-white'>
<IoSettingsOutline /> Settings
<IoOptions /> Settings
<IoChevronDown />
</Button>
}
@@ -162,7 +162,7 @@ export function ColumnSettings({
<Toolbar.Button
render={
<Button variant='ghosted-white'>
<IoOptions /> View
<IoBookOutline /> Columns
<IoChevronDown />
</Button>
}
@@ -183,7 +183,6 @@ export function ColumnSettings({
);
})}
</div>
<Editor.Separator orientation='vertical' />
<div className={style.column}>
<Editor.Label className={style.sectionTitle}>Reset Options</Editor.Label>
<Button size='small' fluid onClick={handleClearToggles}>