mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-17 13:23:35 +00:00
refactor: entry actions in cuesheet
fix: insert entry before fix: avoid double submit on enter fix: move events in the rundown fix: clone groups
This commit is contained in:
committed by
Carlos Valente
parent
8ad260d28a
commit
24a3823d3b
@@ -1,10 +1,10 @@
|
||||
import { memo, useCallback, useMemo, useRef } from 'react';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTableNav } from '@table-nav/react';
|
||||
import { ColumnDef, getCoreRowModel, useReactTable } from '@tanstack/react-table';
|
||||
import { OntimeEntry, TimeField } from 'ontime-types';
|
||||
|
||||
import { useEntryActions } from '../../../common/hooks/useEntryAction';
|
||||
import useFollowComponent from '../../../common/hooks/useFollowComponent';
|
||||
import { useFollowSelected } from '../../../common/hooks/useFollowComponent';
|
||||
import { usePersistedCuesheetOptions } from '../cuesheet.options';
|
||||
|
||||
import CuesheetBody from './cuesheet-table-elements/CuesheetBody';
|
||||
@@ -26,9 +26,7 @@ export default function CuesheetTable({ data, columns }: CuesheetTableProps) {
|
||||
const showDelayedTimes = usePersistedCuesheetOptions((state) => state.showDelayedTimes);
|
||||
const hideTableSeconds = usePersistedCuesheetOptions((state) => state.hideTableSeconds);
|
||||
|
||||
const selectedRef = useRef<HTMLTableRowElement | null>(null);
|
||||
const tableContainerRef = useRef<HTMLDivElement | null>(null);
|
||||
useFollowComponent({ followRef: selectedRef, scrollRef: tableContainerRef, doFollow: followPlayback });
|
||||
const { selectedRef, scrollRef } = useFollowSelected(followPlayback);
|
||||
|
||||
const { listeners } = useTableNav();
|
||||
|
||||
@@ -106,12 +104,13 @@ export default function CuesheetTable({ data, columns }: CuesheetTableProps) {
|
||||
const headers = table.getFlatHeaders();
|
||||
const colSizes: { [key: string]: number } = {};
|
||||
for (let i = 0; i < headers.length; i++) {
|
||||
const header = headers[i]!;
|
||||
const header = headers[i];
|
||||
if (!header) continue;
|
||||
colSizes[`--header-${header.id}-size`] = header.getSize();
|
||||
colSizes[`--col-${header.column.id}-size`] = header.column.getSize();
|
||||
}
|
||||
return colSizes;
|
||||
}, [table.getState().columnSizingInfo, table.getState().columnSizing]);
|
||||
}, [table]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -121,7 +120,7 @@ export default function CuesheetTable({ data, columns }: CuesheetTableProps) {
|
||||
handleResetReordering={resetColumnOrder}
|
||||
handleClearToggles={setAllVisible}
|
||||
/>
|
||||
<div ref={tableContainerRef} className={style.cuesheetContainer}>
|
||||
<div className={style.cuesheetContainer} ref={scrollRef}>
|
||||
<table className={style.cuesheet} id='cuesheet' style={{ ...columnSizeVars }} {...listeners}>
|
||||
<CuesheetHeader headerGroups={headerGroups} />
|
||||
{table.getState().columnSizingInfo.isResizingColumn ? (
|
||||
|
||||
@@ -40,7 +40,7 @@ export default function BlockRow({ blockId, colour, hidePast, rowId, rowIndex, t
|
||||
onClick={(e) => {
|
||||
const rect = e.currentTarget.getBoundingClientRect();
|
||||
const yPos = 8 + rect.y + rect.height / 2;
|
||||
openMenu({ x: rect.x, y: yPos }, blockId, rowIndex);
|
||||
openMenu({ x: rect.x, y: yPos }, blockId, rowIndex, null);
|
||||
}}
|
||||
>
|
||||
<IoEllipsisHorizontal />
|
||||
|
||||
+4
-6
@@ -1,4 +1,4 @@
|
||||
import { MutableRefObject, useMemo } from 'react';
|
||||
import { RefObject, useMemo } from 'react';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { RowModel, Table } from '@tanstack/react-table';
|
||||
import { isOntimeBlock, isOntimeDelay, isOntimeEvent, OntimeBlock, OntimeEntry, Rundown } from 'ontime-types';
|
||||
@@ -17,7 +17,7 @@ import { useVisibleRowsStore } from './visibleRowsStore';
|
||||
|
||||
interface CuesheetBodyProps {
|
||||
rowModel: RowModel<OntimeEntry>;
|
||||
selectedRef: MutableRefObject<HTMLTableRowElement | null>;
|
||||
selectedRef: RefObject<HTMLTableRowElement>;
|
||||
table: Table<OntimeEntry>;
|
||||
}
|
||||
|
||||
@@ -106,7 +106,6 @@ export default function CuesheetBody({ rowModel, selectedRef, table }: CuesheetB
|
||||
const isSelected = key === selectedEventId;
|
||||
const columnHash = getColumnHash();
|
||||
|
||||
|
||||
if (isPast && hidePast) {
|
||||
return null;
|
||||
}
|
||||
@@ -129,14 +128,13 @@ export default function CuesheetBody({ rowModel, selectedRef, table }: CuesheetB
|
||||
let firstAfterBlock = false;
|
||||
if (entry.parent) {
|
||||
const rundown = queryClient.getQueryData<Rundown>(RUNDOWN);
|
||||
const parentEntry = rundown?.entries[entry.parent];
|
||||
parentBgColour = (parentEntry as OntimeBlock).colour;
|
||||
const parentEntry = rundown?.entries[entry.parent] as OntimeBlock | undefined;
|
||||
parentBgColour = parentEntry?.colour;
|
||||
hadBlock = true;
|
||||
} else if (hadBlock) {
|
||||
firstAfterBlock = true;
|
||||
hadBlock = false;
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<EventRow
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { memo, MutableRefObject, useLayoutEffect, useRef } from 'react';
|
||||
import { MutableRefObject, useLayoutEffect, useRef } from 'react';
|
||||
import { IoEllipsisHorizontal } from 'react-icons/io5';
|
||||
import { flexRender, Table } from '@tanstack/react-table';
|
||||
import { OntimeEntry, OntimeEvent, RGBColour } from 'ontime-types';
|
||||
@@ -31,21 +31,7 @@ interface EventRowProps {
|
||||
firstAfterBlock: boolean;
|
||||
}
|
||||
|
||||
export default memo(EventRow, (prevProps, nextProps) => {
|
||||
return (
|
||||
prevProps.rowId === nextProps.rowId &&
|
||||
prevProps.event.revision === nextProps.event.revision &&
|
||||
prevProps.eventIndex === nextProps.eventIndex &&
|
||||
prevProps.rowIndex === nextProps.rowIndex &&
|
||||
prevProps.isPast === nextProps.isPast &&
|
||||
prevProps.selectedRef === nextProps.selectedRef &&
|
||||
prevProps.rowBgColour === nextProps.rowBgColour &&
|
||||
prevProps.parentBgColour === nextProps.parentBgColour &&
|
||||
prevProps.columnHash === nextProps.columnHash
|
||||
);
|
||||
});
|
||||
|
||||
function EventRow({
|
||||
export default function EventRow({
|
||||
rowId,
|
||||
event,
|
||||
eventIndex,
|
||||
@@ -87,7 +73,12 @@ function EventRow({
|
||||
return (
|
||||
<tr
|
||||
id={rowId}
|
||||
className={cx([style.eventRow, event.skip && style.skip, firstAfterBlock && style.firstAfterBlock, Boolean(parentBgColour) && style.hasParent])}
|
||||
className={cx([
|
||||
style.eventRow,
|
||||
event.skip && style.skip,
|
||||
firstAfterBlock && style.firstAfterBlock,
|
||||
Boolean(parentBgColour) && style.hasParent,
|
||||
])}
|
||||
style={{
|
||||
opacity: `${isPast ? '0.2' : '1'}`,
|
||||
'--user-bg': parentBgColour ?? 'transparent',
|
||||
@@ -103,7 +94,7 @@ function EventRow({
|
||||
onClick={(e) => {
|
||||
const rect = e.currentTarget.getBoundingClientRect();
|
||||
const yPos = 8 + rect.y + rect.height / 2;
|
||||
openMenu({ x: rect.x, y: yPos }, event.id, rowIndex);
|
||||
openMenu({ x: rect.x, y: yPos }, event.id, rowIndex, event.parent);
|
||||
}}
|
||||
>
|
||||
<IoEllipsisHorizontal />
|
||||
|
||||
+16
-29
@@ -1,10 +1,9 @@
|
||||
import { memo } from 'react';
|
||||
import { IoAdd, IoArrowDown, IoArrowUp, IoDuplicateOutline, IoOptions, IoTrash } from 'react-icons/io5';
|
||||
import { Menu, MenuButton, MenuDivider, MenuItem, MenuList, Portal } from '@chakra-ui/react';
|
||||
import { isOntimeEvent, SupportedEntry } from 'ontime-types';
|
||||
import { SupportedEntry } from 'ontime-types';
|
||||
|
||||
import { useEntryActions } from '../../../../common/hooks/useEntryAction';
|
||||
import { cloneEvent } from '../../../../common/utils/clone';
|
||||
import { useCuesheetEditModal } from '../../cuesheet-edit-modal/useCuesheetEditModal';
|
||||
|
||||
import { useCuesheetTableMenu } from './useCuesheetTableMenu';
|
||||
@@ -12,28 +11,10 @@ import { useCuesheetTableMenu } from './useCuesheetTableMenu';
|
||||
export default memo(CuesheetTableMenu);
|
||||
|
||||
function CuesheetTableMenu() {
|
||||
const { isOpen, eventId, entryIndex, position, closeMenu } = useCuesheetTableMenu();
|
||||
const { addEntry, getEntryById, move, deleteEntry } = useEntryActions();
|
||||
const { isOpen, entryId, entryIndex, parentId, position, closeMenu } = useCuesheetTableMenu();
|
||||
const { addEntry, clone, deleteEntry, move } = useEntryActions();
|
||||
const showModal = useCuesheetEditModal((state) => state.setEditableEntry);
|
||||
|
||||
const handleCloneEvent = () => {
|
||||
if (!eventId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentEvent = getEntryById(eventId);
|
||||
if (!currentEvent || !isOntimeEvent(currentEvent)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newEvent = cloneEvent(currentEvent);
|
||||
try {
|
||||
addEntry(newEvent, { after: eventId });
|
||||
} catch (_error) {
|
||||
// we do not handle errors here
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Portal>
|
||||
{isOpen && (
|
||||
@@ -48,27 +29,33 @@ function CuesheetTableMenu() {
|
||||
h={1}
|
||||
/>
|
||||
<MenuList>
|
||||
<MenuItem icon={<IoOptions />} onClick={() => showModal(eventId)}>
|
||||
<MenuItem icon={<IoOptions />} onClick={() => showModal(entryId)}>
|
||||
Edit ...
|
||||
</MenuItem>
|
||||
<MenuDivider />
|
||||
<MenuItem icon={<IoAdd />} onClick={() => addEntry({ type: SupportedEntry.Event }, { before: eventId })}>
|
||||
<MenuItem
|
||||
icon={<IoAdd />}
|
||||
onClick={() => addEntry({ type: SupportedEntry.Event, parent: parentId }, { before: entryId })}
|
||||
>
|
||||
Add event above
|
||||
</MenuItem>
|
||||
<MenuItem icon={<IoAdd />} onClick={() => addEntry({ type: SupportedEntry.Event }, { after: eventId })}>
|
||||
<MenuItem
|
||||
icon={<IoAdd />}
|
||||
onClick={() => addEntry({ type: SupportedEntry.Event, parent: parentId }, { after: entryId })}
|
||||
>
|
||||
Add event below
|
||||
</MenuItem>
|
||||
<MenuItem icon={<IoDuplicateOutline />} onClick={handleCloneEvent}>
|
||||
<MenuItem icon={<IoDuplicateOutline />} onClick={() => clone(entryId)}>
|
||||
Clone event
|
||||
</MenuItem>
|
||||
<MenuDivider />
|
||||
<MenuItem isDisabled={entryIndex < 1} icon={<IoArrowUp />} onClick={() => move(eventId, 'up')}>
|
||||
<MenuItem isDisabled={entryIndex < 1} icon={<IoArrowUp />} onClick={() => move(entryId, 'up')}>
|
||||
Move up
|
||||
</MenuItem>
|
||||
<MenuItem icon={<IoArrowDown />} onClick={() => move(eventId, 'down')}>
|
||||
<MenuItem icon={<IoArrowDown />} onClick={() => move(entryId, 'down')}>
|
||||
Move down
|
||||
</MenuItem>
|
||||
<MenuItem icon={<IoTrash />} onClick={() => deleteEntry([eventId])}>
|
||||
<MenuItem icon={<IoTrash />} onClick={() => deleteEntry([entryId])}>
|
||||
Delete
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
|
||||
+10
-6
@@ -1,31 +1,35 @@
|
||||
import { EntryId } from 'ontime-types';
|
||||
import { create } from 'zustand';
|
||||
|
||||
type Anchor = { x: number; y: number };
|
||||
|
||||
type OpenMenu = {
|
||||
isOpen: true;
|
||||
eventId: string;
|
||||
entryId: EntryId;
|
||||
entryIndex: number;
|
||||
parentId: EntryId | null;
|
||||
};
|
||||
|
||||
type ClosedMenu = {
|
||||
isOpen: false;
|
||||
eventId: null;
|
||||
entryId: null;
|
||||
entryIndex: null;
|
||||
parentId: null;
|
||||
};
|
||||
|
||||
type CuesheetTableMenuStore = (OpenMenu | ClosedMenu) & {
|
||||
position: Anchor;
|
||||
openMenu: (position: Anchor, eventId: string, entryIndex: number) => void;
|
||||
openMenu: (position: Anchor, entryId: EntryId, entryIndex: number, parentId: EntryId | null) => void;
|
||||
closeMenu: () => void;
|
||||
};
|
||||
|
||||
export const useCuesheetTableMenu = create<CuesheetTableMenuStore>((set) => ({
|
||||
isOpen: false,
|
||||
eventId: null,
|
||||
entryId: null,
|
||||
entryIndex: null,
|
||||
parentId: null,
|
||||
position: { x: 0, y: 0 },
|
||||
openMenu: (position: Anchor, eventId: string, entryIndex: number) =>
|
||||
set({ isOpen: true, position, eventId, entryIndex }),
|
||||
openMenu: (position: Anchor, entryId: EntryId, entryIndex: number, parentId: EntryId | null) =>
|
||||
set({ isOpen: true, position, entryId, entryIndex, parentId }),
|
||||
closeMenu: () => set({ isOpen: false }),
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user