diff --git a/apps/client/src/views/cuesheet/CuesheetPage.tsx b/apps/client/src/views/cuesheet/CuesheetPage.tsx index 2dd92c457..f1402d8c0 100644 --- a/apps/client/src/views/cuesheet/CuesheetPage.tsx +++ b/apps/client/src/views/cuesheet/CuesheetPage.tsx @@ -17,9 +17,9 @@ import CuesheetEventEditor from '../../features/rundown/event-editor/CuesheetEve import CuesheetDnd from './cuesheet-dnd/CuesheetDnd'; import CuesheetProgress from './cuesheet-progress/CuesheetProgress'; +import { makeCuesheetColumns } from './cuesheet-table/cuesheet-table-elements/cuesheetCols'; import CuesheetTable from './cuesheet-table/CuesheetTable'; import { cuesheetOptions } from './cuesheet.options'; -import { makeCuesheetColumns } from './cuesheet-table/cuesheet-table-elements/cuesheetCols'; import styles from './CuesheetPage.module.scss'; diff --git a/apps/client/src/views/cuesheet/cuesheet-table/CuesheetTable.tsx b/apps/client/src/views/cuesheet/cuesheet-table/CuesheetTable.tsx new file mode 100644 index 000000000..c2f1c4731 --- /dev/null +++ b/apps/client/src/views/cuesheet/cuesheet-table/CuesheetTable.tsx @@ -0,0 +1,180 @@ +import { useRef } from 'react'; +import { IconButton, Menu, MenuButton } from '@chakra-ui/react'; +import { IoEllipsisHorizontal } from '@react-icons/all-files/io5/IoEllipsisHorizontal'; +import { ColumnDef, flexRender, getCoreRowModel, useReactTable } from '@tanstack/react-table'; +import Color from 'color'; +import { + CustomFieldLabel, + isOntimeBlock, + isOntimeDelay, + isOntimeEvent, + MaybeString, + OntimeRundown, + OntimeRundownEntry, +} from 'ontime-types'; + +import useFollowComponent from '../../../common/hooks/useFollowComponent'; +import { useSelectedEventId } from '../../../common/hooks/useSocket'; +import { getAccessibleColour } from '../../../common/utils/styleUtils'; +import { useCuesheetOptions } from '../cuesheet.options'; + +import BlockRow from './cuesheet-table-elements/BlockRow'; +import CuesheetHeader from './cuesheet-table-elements/CuesheetHeader'; +import DelayRow from './cuesheet-table-elements/DelayRow'; +import EventRow from './cuesheet-table-elements/EventRow'; +import CuesheetTableSettings from './cuesheet-table-settings/CuesheetTableSettings'; +import CuesheetTableMenu from './CuesheetTableMenu'; +import useColumnManager from './useColumnManager'; + +import style from './CuesheetTable.module.scss'; + +interface CuesheetTableProps { + data: OntimeRundown; + columns: ColumnDef[]; + handleUpdate: (rowIndex: number, accessor: keyof OntimeRundownEntry, payload: string) => void; + handleUpdateCustom: (rowIndex: number, accessor: CustomFieldLabel, payload: string) => void; + showModal: (eventId: MaybeString) => void; +} + +export default function CuesheetTable(props: CuesheetTableProps) { + const { data, columns, handleUpdate, handleUpdateCustom, showModal } = props; + + const { selectedEventId } = useSelectedEventId(); + const { followSelected, hideDelays, hidePast, hideIndexColumn } = useCuesheetOptions(); + const { columnVisibility, columnOrder, columnSizing, resetColumnOrder, setColumnVisibility, setColumnSizing } = + useColumnManager(columns); + + const selectedRef = useRef(null); + const tableContainerRef = useRef(null); + useFollowComponent({ followRef: selectedRef, scrollRef: tableContainerRef, doFollow: followSelected }); + + const table = useReactTable({ + data, + columns, + columnResizeMode: 'onChange', + state: { + columnOrder, + columnVisibility, + columnSizing, + }, + meta: { + handleUpdate, + handleUpdateCustom, + }, + onColumnVisibilityChange: setColumnVisibility, + onColumnSizingChange: setColumnSizing, + getCoreRowModel: getCoreRowModel(), + }); + + const setAllVisible = () => { + table.toggleAllColumnsVisible(true); + }; + + const resetColumnResizing = () => { + setColumnSizing({}); + }; + + const headerGroups = table.getHeaderGroups(); + const rowModel = table.getRowModel(); + const allLeafColumns = table.getAllLeafColumns(); + + let eventIndex = 0; + // for the first event, it will be past if there is something selected + let isPast = Boolean(selectedEventId); + + return ( + <> + +
+ + + + {rowModel.rows.map((row, index) => { + const key = row.original.id; + const isSelected = selectedEventId === key; + const entry = row.original; + if (isSelected) { + isPast = false; + } + + if (isOntimeBlock(entry)) { + return ; + } + if (isOntimeDelay(entry)) { + if (isPast && hidePast) { + return null; + } + const delayVal = entry.duration; + if (hideDelays || delayVal === 0) { + return null; + } + + return ; + } + if (isOntimeEvent(entry)) { + eventIndex++; + const isSelected = key === selectedEventId; + + if (isPast && hidePast) { + return null; + } + + let rowBgColour: string | undefined; + if (isSelected) { + rowBgColour = '#D20300'; // $red-700 + } else if (entry.colour) { + try { + // the colour is user defined and might be invalid + const accessibleBackgroundColor = Color(getAccessibleColour(entry.colour).backgroundColor); + rowBgColour = accessibleBackgroundColor.fade(0.75).hexa(); + } catch (_error) { + /* we do not handle errors here */ + } + } + + return ( + + + + {row.getVisibleCells().map((cell) => { + return ( + + ); + })} + + + + ); + } + + // currently there is no scenario where entryType is not handled above, either way... + return null; + })} + +
+ } + variant='ontime-ghosted' + /> + + {flexRender(cell.column.columnDef.cell, cell.getContext())} +
+
+ + ); +} diff --git a/apps/client/src/views/cuesheet/cuesheet-table/CuesheetTableMenu.tsx b/apps/client/src/views/cuesheet/cuesheet-table/CuesheetTableMenu.tsx new file mode 100644 index 000000000..e4eabff82 --- /dev/null +++ b/apps/client/src/views/cuesheet/cuesheet-table/CuesheetTableMenu.tsx @@ -0,0 +1,63 @@ +import { MenuDivider, MenuItem, MenuList } from '@chakra-ui/react'; +import { IoAdd } from '@react-icons/all-files/io5/IoAdd'; +import { IoArrowDown } from '@react-icons/all-files/io5/IoArrowDown'; +import { IoArrowUp } from '@react-icons/all-files/io5/IoArrowUp'; +import { IoDuplicateOutline } from '@react-icons/all-files/io5/IoDuplicateOutline'; +import { IoOptions } from '@react-icons/all-files/io5/IoOptions'; +import { IoTrash } from '@react-icons/all-files/io5/IoTrash'; +import { OntimeEvent, SupportedEvent } from 'ontime-types'; + +import { useEventAction } from '../../../common/hooks/useEventAction'; +import { cloneEvent } from '../../../common/utils/eventsManager'; + +interface CuesheetTableMenuProps { + event: OntimeEvent; + entryIndex: number; + showModal: (entryId: string) => void; +} + +export default function CuesheetTableMenu(props: CuesheetTableMenuProps) { + const { event, entryIndex, showModal } = props; + const { addEvent, reorderEvent, deleteEvent } = useEventAction(); + + const handleCloneEvent = () => { + const newEvent = cloneEvent(event); + try { + addEvent(newEvent, { after: event.id }); + } catch (_error) { + // we do not handle errors here + } + }; + + return ( + + } onClick={() => showModal(event.id)}> + Edit ... + + + } onClick={() => addEvent({ type: SupportedEvent.Event }, { before: event.id })}> + Add event above + + } onClick={() => addEvent({ type: SupportedEvent.Event }, { after: event.id })}> + Add event below + + } onClick={handleCloneEvent}> + Clone event + + + } + onClick={() => reorderEvent(event.id, entryIndex, entryIndex - 1)} + > + Move up + + } onClick={() => reorderEvent(event.id, entryIndex, entryIndex + 1)}> + Move down + + } onClick={() => deleteEvent([event.id])}> + Delete + + + ); +}