From 13eca98133e5cd3f4ab37333d681131aba0e40b6 Mon Sep 17 00:00:00 2001 From: Carlos Valente <34649812+cpvalente@users.noreply.github.com> Date: Thu, 14 Sep 2023 21:06:48 +0200 Subject: [PATCH] refactor: recompose and memoise (#522) --- .../client/src/features/cuesheet/Cuesheet.tsx | 137 +++--------------- .../cuesheet-table-elements/BlockRow.tsx | 18 +++ .../CuesheetHeader.tsx | 108 ++++++++++++++ .../cuesheet-table-elements/CuesheetRow.tsx | 6 + .../cuesheet-table-elements/DelayRow.tsx | 22 +++ .../EditableCell.tsx | 0 .../cuesheet-table-elements/EventRow.tsx | 38 +++++ .../SortableCell.tsx | 0 .../CuesheetTableHeader.tsx | 28 +--- .../CuesheetTableHeaderTimers.tsx | 31 ++++ .../CuesheetTableSettings.tsx | 5 +- .../src/features/cuesheet/cuesheetCols.tsx | 2 +- 12 files changed, 252 insertions(+), 143 deletions(-) create mode 100644 apps/client/src/features/cuesheet/cuesheet-table-elements/BlockRow.tsx create mode 100644 apps/client/src/features/cuesheet/cuesheet-table-elements/CuesheetHeader.tsx create mode 100644 apps/client/src/features/cuesheet/cuesheet-table-elements/CuesheetRow.tsx create mode 100644 apps/client/src/features/cuesheet/cuesheet-table-elements/DelayRow.tsx rename apps/client/src/features/cuesheet/{tableElements => cuesheet-table-elements}/EditableCell.tsx (100%) create mode 100644 apps/client/src/features/cuesheet/cuesheet-table-elements/EventRow.tsx rename apps/client/src/features/cuesheet/{tableElements => cuesheet-table-elements}/SortableCell.tsx (100%) create mode 100644 apps/client/src/features/cuesheet/cuesheet-table-header/CuesheetTableHeaderTimers.tsx diff --git a/apps/client/src/features/cuesheet/Cuesheet.tsx b/apps/client/src/features/cuesheet/Cuesheet.tsx index 6d377a826..928766528 100644 --- a/apps/client/src/features/cuesheet/Cuesheet.tsx +++ b/apps/client/src/features/cuesheet/Cuesheet.tsx @@ -1,34 +1,20 @@ import { useRef } from 'react'; -import { Tooltip } from '@chakra-ui/react'; -import { - closestCenter, - DndContext, - DragEndEvent, - KeyboardSensor, - PointerSensor, - TouchSensor, - useSensor, - useSensors, -} from '@dnd-kit/core'; -import { horizontalListSortingStrategy, SortableContext, sortableKeyboardCoordinates } from '@dnd-kit/sortable'; import { ColumnDef, flexRender, getCoreRowModel, useReactTable } from '@tanstack/react-table'; import { isOntimeBlock, isOntimeDelay, isOntimeEvent, OntimeRundown, OntimeRundownEntry } from 'ontime-types'; import useFollowComponent from '../../common/hooks/useFollowComponent'; import { useLocalStorage } from '../../common/hooks/useLocalStorage'; -import { millisToDelayString } from '../../common/utils/dateConfig'; -import { getAccessibleColour } from '../../common/utils/styleUtils'; -import { tooltipDelayFast } from '../../ontimeConfig'; +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 { useCuesheetSettings } from './store/CuesheetSettings'; -import { SortableCell } from './tableElements/SortableCell'; import { initialColumnOrder } from './cuesheetCols'; import style from './Cuesheet.module.scss'; -const pastOpacity = '0.2'; - interface CuesheetProps { data: OntimeRundown; columns: ColumnDef[]; @@ -67,49 +53,6 @@ export default function Cuesheet({ data, columns, handleUpdate, selectedId }: Cu getCoreRowModel: getCoreRowModel(), }); - const sensors = useSensors( - useSensor(PointerSensor, { - activationConstraint: { - delay: 100, - tolerance: 50, - }, - }), - useSensor(TouchSensor, { - activationConstraint: { - delay: 100, - tolerance: 50, - }, - }), - useSensor(KeyboardSensor, { - coordinateGetter: sortableKeyboardCoordinates, - }), - ); - - const handleOnDragEnd = (event: DragEndEvent) => { - const { delta, active, over } = event; - - // cancel if delta y is greater than 200 - if (delta.y > 200) return; - // cancel if we do not have an over id - if (over?.id == null) return; - - // get index of from - const fromIndex = columnOrder.indexOf(active.id as string); - - // get index of to - const toIndex = columnOrder.indexOf(over.id as string); - - if (toIndex === -1) { - return; - } - - const reorderedCols = [...columnOrder]; - const reorderedItem = reorderedCols.splice(fromIndex, 1); - reorderedCols.splice(toIndex, 0, reorderedItem[0]); - - saveColumnOrder(reorderedCols); - }; - const resetColumnOrder = () => { saveColumnOrder(initialColumnOrder); }; @@ -122,6 +65,8 @@ export default function Cuesheet({ data, columns, handleUpdate, selectedId }: Cu setColumnSizing({}); }; + const headerGroups = table.getHeaderGroups; + let eventIndex = 0; let isPast = Boolean(selectedId); @@ -137,37 +82,7 @@ export default function Cuesheet({ data, columns, handleUpdate, selectedId }: Cu )}
- - {table.getHeaderGroups().map((headerGroup) => { - const key = headerGroup.id; - - return ( - - - - - {headerGroup.headers.map((header) => { - const width = header.getSize(); - - return ( - - {header.isPlaceholder - ? null - : flexRender(header.column.columnDef.header, header.getContext())} - - ); - })} - - - - ); - })} - - + {table.getRowModel().rows.map((row) => { const key = row.original.id; @@ -177,13 +92,7 @@ export default function Cuesheet({ data, columns, handleUpdate, selectedId }: Cu } if (isOntimeBlock(row.original)) { - const title = row.original.title; - - return ( - - - - ); + return ; } if (isOntimeDelay(row.original)) { const delayVal = row.original.duration; @@ -192,12 +101,7 @@ export default function Cuesheet({ data, columns, handleUpdate, selectedId }: Cu return null; } - const delayTime = millisToDelayString(delayVal); - return ( - - - - ); + return ; } if (isOntimeEvent(row.original)) { eventIndex++; @@ -210,25 +114,20 @@ export default function Cuesheet({ data, columns, handleUpdate, selectedId }: Cu return null; } - const bgFallback = 'transparent'; - const bgColour = row.original.colour || bgFallback; - const textColour = bgColour === bgFallback ? undefined : getAccessibleColour(bgColour); - const isSkipped = row.original.skip; - let rowBgColour: string | undefined; - if (row.original.id === selectedId) { + if (isSelected) { rowBgColour = 'var(--cuesheet-running-bg-override, #D20300)'; // $red-700 } + return ( - - {row.getVisibleCells().map((cell) => { return ( ); })} - + ); } diff --git a/apps/client/src/features/cuesheet/cuesheet-table-elements/BlockRow.tsx b/apps/client/src/features/cuesheet/cuesheet-table-elements/BlockRow.tsx new file mode 100644 index 000000000..7b2b398d0 --- /dev/null +++ b/apps/client/src/features/cuesheet/cuesheet-table-elements/BlockRow.tsx @@ -0,0 +1,18 @@ +import { memo } from 'react'; + +import style from '../Cuesheet.module.scss'; + +interface BlockRowProps { + title: string; +} + +function BlockRow(props: BlockRowProps) { + const { title } = props; + return ( + + + + ); +} + +export default memo(BlockRow); diff --git a/apps/client/src/features/cuesheet/cuesheet-table-elements/CuesheetHeader.tsx b/apps/client/src/features/cuesheet/cuesheet-table-elements/CuesheetHeader.tsx new file mode 100644 index 000000000..a340fc098 --- /dev/null +++ b/apps/client/src/features/cuesheet/cuesheet-table-elements/CuesheetHeader.tsx @@ -0,0 +1,108 @@ +import { memo } from 'react'; +import { Tooltip } from '@chakra-ui/react'; +import { + closestCenter, + DndContext, + DragEndEvent, + KeyboardSensor, + PointerSensor, + TouchSensor, + useSensor, + useSensors, +} from '@dnd-kit/core'; +import { horizontalListSortingStrategy, SortableContext, sortableKeyboardCoordinates } from '@dnd-kit/sortable'; +import { flexRender, HeaderGroup } from '@tanstack/react-table'; +import { OntimeRundownEntry } from 'ontime-types'; + +import { useLocalStorage } from '../../../common/hooks/useLocalStorage'; +import { tooltipDelayFast } from '../../../ontimeConfig'; +import { initialColumnOrder } from '../cuesheetCols'; + +import { SortableCell } from './SortableCell'; + +import style from '../Cuesheet.module.scss'; + +interface CuesheetHeaderProps { + headerGroups: () => HeaderGroup[]; +} + +function CuesheetHeader(props: CuesheetHeaderProps) { + const { headerGroups } = props; + const [columnOrder, saveColumnOrder] = useLocalStorage('table-order', initialColumnOrder); + + const handleOnDragEnd = (event: DragEndEvent) => { + const { delta, active, over } = event; + + // cancel if delta y is greater than 200 + if (delta.y > 200) return; + // cancel if we do not have an over id + if (over?.id == null) return; + + // get index of from + const fromIndex = columnOrder.indexOf(active.id as string); + + // get index of to + const toIndex = columnOrder.indexOf(over.id as string); + + if (toIndex === -1) { + return; + } + + const reorderedCols = [...columnOrder]; + const reorderedItem = reorderedCols.splice(fromIndex, 1); + reorderedCols.splice(toIndex, 0, reorderedItem[0]); + + saveColumnOrder(reorderedCols); + }; + + const sensors = useSensors( + useSensor(PointerSensor, { + activationConstraint: { + delay: 100, + tolerance: 50, + }, + }), + useSensor(TouchSensor, { + activationConstraint: { + delay: 100, + tolerance: 50, + }, + }), + useSensor(KeyboardSensor, { + coordinateGetter: sortableKeyboardCoordinates, + }), + ); + + return ( + + {headerGroups().map((headerGroup) => { + const key = headerGroup.id; + + return ( + + + + + {headerGroup.headers.map((header) => { + const width = header.getSize(); + + return ( + + {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())} + + ); + })} + + + + ); + })} + + ); +} + +export default memo(CuesheetHeader); diff --git a/apps/client/src/features/cuesheet/cuesheet-table-elements/CuesheetRow.tsx b/apps/client/src/features/cuesheet/cuesheet-table-elements/CuesheetRow.tsx new file mode 100644 index 000000000..5a0a9ae89 --- /dev/null +++ b/apps/client/src/features/cuesheet/cuesheet-table-elements/CuesheetRow.tsx @@ -0,0 +1,6 @@ +interface CuesheetRowProps { + row: OntimeRundownEntry; + isSelected: boolean; +} + +function CuesheetRow() {} diff --git a/apps/client/src/features/cuesheet/cuesheet-table-elements/DelayRow.tsx b/apps/client/src/features/cuesheet/cuesheet-table-elements/DelayRow.tsx new file mode 100644 index 000000000..1afbd3d26 --- /dev/null +++ b/apps/client/src/features/cuesheet/cuesheet-table-elements/DelayRow.tsx @@ -0,0 +1,22 @@ +import { memo } from 'react'; + +import { millisToDelayString } from '../../../common/utils/dateConfig'; + +import style from '../Cuesheet.module.scss'; + +interface DelayRowProps { + duration: number; +} + +function DelayRow(props: DelayRowProps) { + const { duration } = props; + const delayTime = millisToDelayString(duration); + + return ( + + + + ); +} + +export default memo(DelayRow); diff --git a/apps/client/src/features/cuesheet/tableElements/EditableCell.tsx b/apps/client/src/features/cuesheet/cuesheet-table-elements/EditableCell.tsx similarity index 100% rename from apps/client/src/features/cuesheet/tableElements/EditableCell.tsx rename to apps/client/src/features/cuesheet/cuesheet-table-elements/EditableCell.tsx diff --git a/apps/client/src/features/cuesheet/cuesheet-table-elements/EventRow.tsx b/apps/client/src/features/cuesheet/cuesheet-table-elements/EventRow.tsx new file mode 100644 index 000000000..4368cc79e --- /dev/null +++ b/apps/client/src/features/cuesheet/cuesheet-table-elements/EventRow.tsx @@ -0,0 +1,38 @@ +import { memo, MutableRefObject, PropsWithChildren } from 'react'; + +import { getAccessibleColour } from '../../../common/utils/styleUtils'; + +import style from '../Cuesheet.module.scss'; + +const pastOpacity = '0.2'; + +interface EventRowProps { + eventIndex: number; + isPast?: boolean; + selectedRef?: MutableRefObject; + skip?: boolean; + colour?: string; +} + +function EventRow(props: PropsWithChildren) { + const { children, eventIndex, isPast, selectedRef, skip, colour } = props; + + const bgFallback = 'transparent'; + const bgColour = colour || bgFallback; + const textColour = bgColour === bgFallback ? undefined : getAccessibleColour(bgColour); + + return ( + + + {children} + + ); +} + +export default memo(EventRow); diff --git a/apps/client/src/features/cuesheet/tableElements/SortableCell.tsx b/apps/client/src/features/cuesheet/cuesheet-table-elements/SortableCell.tsx similarity index 100% rename from apps/client/src/features/cuesheet/tableElements/SortableCell.tsx rename to apps/client/src/features/cuesheet/cuesheet-table-elements/SortableCell.tsx diff --git a/apps/client/src/features/cuesheet/cuesheet-table-header/CuesheetTableHeader.tsx b/apps/client/src/features/cuesheet/cuesheet-table-header/CuesheetTableHeader.tsx index b5445f93f..e33db5236 100644 --- a/apps/client/src/features/cuesheet/cuesheet-table-header/CuesheetTableHeader.tsx +++ b/apps/client/src/features/cuesheet/cuesheet-table-header/CuesheetTableHeader.tsx @@ -4,16 +4,14 @@ import { IoExpand } from '@react-icons/all-files/io5/IoExpand'; import { IoLocate } from '@react-icons/all-files/io5/IoLocate'; import { IoSettingsOutline } from '@react-icons/all-files/io5/IoSettingsOutline'; import { EventData, Playback } from 'ontime-types'; -import { formatDisplay } from 'ontime-utils'; import PlaybackIcon from '../../../common/components/playback-icon/PlaybackIcon'; -import useFullscreen from '../../../common/hooks/useFullscreen'; -import { useTimer } from '../../../common/hooks/useSocket'; import useEventData from '../../../common/hooks-query/useEventData'; -import { formatTime } from '../../../common/utils/time'; import { tooltipDelayFast } from '../../../ontimeConfig'; import { useCuesheetSettings } from '../store/CuesheetSettings'; +import CuesheetTableHeaderTimers from './CuesheetTableHeaderTimers'; + import style from './CuesheetTableHeader.module.scss'; interface CuesheetTableHeaderProps { @@ -31,8 +29,9 @@ export default function CuesheetTableHeader({ handleCSVExport, featureData }: Cu const showSettings = useCuesheetSettings((state) => state.showSettings); const toggleSettings = useCuesheetSettings((state) => state.toggleSettings); const toggleFollow = useCuesheetSettings((state) => state.toggleFollow); - const timer = useTimer(); - const { isFullScreen, toggleFullScreen } = useFullscreen(); + // const { isFullScreen, toggleFullScreen } = useFullscreen(); + const isFullScreen = false; + const toggleFullScreen = () => undefined; const { data: event } = useEventData(); const exportCsv = () => { @@ -47,14 +46,6 @@ export default function CuesheetTableHeader({ handleCSVExport, featureData }: Cu featureData.numEvents ? featureData.numEvents : '-' }`; - // prepare presentation variables - const isOvertime = (timer.current ?? 0) < 0; - const timerNow = timer.current == null ? '-' : `${isOvertime ? '-' : ''}${formatDisplay(timer.current)}`; - const timeNow = formatTime(timer.clock, { - showSeconds: true, - format: 'hh:mm:ss a', - }); - return (
@@ -65,14 +56,7 @@ export default function CuesheetTableHeader({ handleCSVExport, featureData }: Cu
{selected}
-
-
Running Timer
-
{timerNow}
-
-
-
Time Now
-
{timeNow}
-
+
toggleFollow()} className={`${style.actionIcon} ${followSelected ? style.enabled : ''}`}> diff --git a/apps/client/src/features/cuesheet/cuesheet-table-header/CuesheetTableHeaderTimers.tsx b/apps/client/src/features/cuesheet/cuesheet-table-header/CuesheetTableHeaderTimers.tsx new file mode 100644 index 000000000..c3173697b --- /dev/null +++ b/apps/client/src/features/cuesheet/cuesheet-table-header/CuesheetTableHeaderTimers.tsx @@ -0,0 +1,31 @@ +import { formatDisplay } from 'ontime-utils'; + +import { useTimer } from '../../../common/hooks/useSocket'; +import { formatTime } from '../../../common/utils/time'; + +import style from './CuesheetTableHeader.module.scss'; + +export default function CuesheetTableHeaderTimers() { + const timer = useTimer(); + + // prepare presentation variables + const isOvertime = (timer.current ?? 0) < 0; + const timerNow = timer.current == null ? '-' : `${isOvertime ? '-' : ''}${formatDisplay(timer.current)}`; + const timeNow = formatTime(timer.clock, { + showSeconds: true, + format: 'hh:mm:ss a', + }); + + return ( + <> +
+
Running Timer
+
{timerNow}
+
+
+
Time Now
+
{timeNow}
+
+ + ); +} diff --git a/apps/client/src/features/cuesheet/cuesheet-table-settings/CuesheetTableSettings.tsx b/apps/client/src/features/cuesheet/cuesheet-table-settings/CuesheetTableSettings.tsx index accd5ce9c..a3b125507 100644 --- a/apps/client/src/features/cuesheet/cuesheet-table-settings/CuesheetTableSettings.tsx +++ b/apps/client/src/features/cuesheet/cuesheet-table-settings/CuesheetTableSettings.tsx @@ -1,3 +1,4 @@ +import { memo } from 'react'; import { Button, Checkbox, Switch } from '@chakra-ui/react'; import { Column } from '@tanstack/react-table'; import { OntimeRundownEntry } from 'ontime-types'; @@ -19,7 +20,7 @@ interface CuesheetTableSettingsProps { handleClearToggles: () => void; } -export default function CuesheetTableSettings(props: CuesheetTableSettingsProps) { +function CuesheetTableSettings(props: CuesheetTableSettingsProps) { const { columns, handleResetResizing, handleResetReordering, handleClearToggles } = props; const showPrevious = useCuesheetSettings((state) => state.showPrevious); const togglePreviousVisibility = useCuesheetSettings((state) => state.togglePreviousVisibility); @@ -81,3 +82,5 @@ export default function CuesheetTableSettings(props: CuesheetTableSettingsProps)
); } + +export default memo(CuesheetTableSettings); diff --git a/apps/client/src/features/cuesheet/cuesheetCols.tsx b/apps/client/src/features/cuesheet/cuesheetCols.tsx index 13446f305..7969dafa3 100644 --- a/apps/client/src/features/cuesheet/cuesheetCols.tsx +++ b/apps/client/src/features/cuesheet/cuesheetCols.tsx @@ -6,8 +6,8 @@ import { millisToString } from 'ontime-utils'; import DelayIndicator from '../../common/components/delay-indicator/DelayIndicator'; +import EditableCell from './cuesheet-table-elements/EditableCell'; import { useCuesheetSettings } from './store/CuesheetSettings'; -import EditableCell from './tableElements/EditableCell'; import style from './Cuesheet.module.scss';
- - # - -
{title}
{delayTime}
- {eventIndex} - @@ -236,7 +135,7 @@ export default function Cuesheet({ data, columns, handleUpdate, selectedId }: Cu
{title}
+ + # + +
{delayTime}
+ {eventIndex} +