mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-10 09:53:48 +00:00
refactor: recompose and memoise (#522)
This commit is contained in:
@@ -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<OntimeRundownEntry>[];
|
||||
@@ -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
|
||||
)}
|
||||
<div ref={tableContainerRef} className={style.cuesheetContainer}>
|
||||
<table className={style.cuesheet}>
|
||||
<thead className={style.tableHeader}>
|
||||
{table.getHeaderGroups().map((headerGroup) => {
|
||||
const key = headerGroup.id;
|
||||
|
||||
return (
|
||||
<DndContext key={key} sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleOnDragEnd}>
|
||||
<tr key={headerGroup.id}>
|
||||
<th className={style.indexColumn}>
|
||||
<Tooltip label='Event Order' openDelay={tooltipDelayFast}>
|
||||
#
|
||||
</Tooltip>
|
||||
</th>
|
||||
<SortableContext key={key} items={headerGroup.headers} strategy={horizontalListSortingStrategy}>
|
||||
{headerGroup.headers.map((header) => {
|
||||
const width = header.getSize();
|
||||
|
||||
return (
|
||||
<SortableCell key={header.column.columnDef.id} header={header} style={{ width }}>
|
||||
{header.isPlaceholder
|
||||
? null
|
||||
: flexRender(header.column.columnDef.header, header.getContext())}
|
||||
</SortableCell>
|
||||
);
|
||||
})}
|
||||
</SortableContext>
|
||||
</tr>
|
||||
</DndContext>
|
||||
);
|
||||
})}
|
||||
</thead>
|
||||
|
||||
<CuesheetHeader headerGroups={headerGroups} />
|
||||
<tbody>
|
||||
{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 (
|
||||
<tr key={key} className={style.blockRow}>
|
||||
<td>{title}</td>
|
||||
</tr>
|
||||
);
|
||||
return <BlockRow key={key} title={row.original.title} />;
|
||||
}
|
||||
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 (
|
||||
<tr key={key} className={style.delayRow}>
|
||||
<td>{delayTime}</td>
|
||||
</tr>
|
||||
);
|
||||
return <DelayRow key={key} duration={delayVal} />;
|
||||
}
|
||||
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 (
|
||||
<tr
|
||||
<EventRow
|
||||
key={key}
|
||||
className={`${style.eventRow} ${isSkipped ? style.skip : ''}`}
|
||||
style={{ opacity: `${isPast ? pastOpacity : '1'}` }}
|
||||
ref={isSelected ? selectedRef : undefined}
|
||||
eventIndex={eventIndex}
|
||||
isPast={isPast}
|
||||
selectedRef={isSelected ? selectedRef : undefined}
|
||||
skip={row.original.skip}
|
||||
colour={row.original.colour}
|
||||
>
|
||||
<td className={style.indexColumn} style={{ backgroundColor: bgColour, color: textColour?.color }}>
|
||||
{eventIndex}
|
||||
</td>
|
||||
{row.getVisibleCells().map((cell) => {
|
||||
return (
|
||||
<td key={cell.id} style={{ width: cell.column.getSize(), backgroundColor: rowBgColour }}>
|
||||
@@ -236,7 +135,7 @@ export default function Cuesheet({ data, columns, handleUpdate, selectedId }: Cu
|
||||
</td>
|
||||
);
|
||||
})}
|
||||
</tr>
|
||||
</EventRow>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 (
|
||||
<tr className={style.blockRow}>
|
||||
<td>{title}</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
export default memo(BlockRow);
|
||||
@@ -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<OntimeRundownEntry>[];
|
||||
}
|
||||
|
||||
function CuesheetHeader(props: CuesheetHeaderProps) {
|
||||
const { headerGroups } = props;
|
||||
const [columnOrder, saveColumnOrder] = useLocalStorage<string[]>('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 (
|
||||
<thead className={style.tableHeader}>
|
||||
{headerGroups().map((headerGroup) => {
|
||||
const key = headerGroup.id;
|
||||
|
||||
return (
|
||||
<DndContext key={key} sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleOnDragEnd}>
|
||||
<tr key={headerGroup.id}>
|
||||
<th className={style.indexColumn}>
|
||||
<Tooltip label='Event Order' openDelay={tooltipDelayFast}>
|
||||
#
|
||||
</Tooltip>
|
||||
</th>
|
||||
<SortableContext key={key} items={headerGroup.headers} strategy={horizontalListSortingStrategy}>
|
||||
{headerGroup.headers.map((header) => {
|
||||
const width = header.getSize();
|
||||
|
||||
return (
|
||||
<SortableCell key={header.column.columnDef.id} header={header} style={{ width }}>
|
||||
{header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
|
||||
</SortableCell>
|
||||
);
|
||||
})}
|
||||
</SortableContext>
|
||||
</tr>
|
||||
</DndContext>
|
||||
);
|
||||
})}
|
||||
</thead>
|
||||
);
|
||||
}
|
||||
|
||||
export default memo(CuesheetHeader);
|
||||
@@ -0,0 +1,6 @@
|
||||
interface CuesheetRowProps {
|
||||
row: OntimeRundownEntry;
|
||||
isSelected: boolean;
|
||||
}
|
||||
|
||||
function CuesheetRow() {}
|
||||
@@ -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 (
|
||||
<tr className={style.delayRow}>
|
||||
<td>{delayTime}</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
export default memo(DelayRow);
|
||||
@@ -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<HTMLTableRowElement | null>;
|
||||
skip?: boolean;
|
||||
colour?: string;
|
||||
}
|
||||
|
||||
function EventRow(props: PropsWithChildren<EventRowProps>) {
|
||||
const { children, eventIndex, isPast, selectedRef, skip, colour } = props;
|
||||
|
||||
const bgFallback = 'transparent';
|
||||
const bgColour = colour || bgFallback;
|
||||
const textColour = bgColour === bgFallback ? undefined : getAccessibleColour(bgColour);
|
||||
|
||||
return (
|
||||
<tr
|
||||
className={`${style.eventRow} ${skip ? style.skip : ''}`}
|
||||
style={{ opacity: `${isPast ? pastOpacity : '1'}` }}
|
||||
ref={selectedRef}
|
||||
>
|
||||
<td className={style.indexColumn} style={{ backgroundColor: bgColour, color: textColour?.color }}>
|
||||
{eventIndex}
|
||||
</td>
|
||||
{children}
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
export default memo(EventRow);
|
||||
@@ -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 (
|
||||
<div className={style.header}>
|
||||
<div className={style.event}>
|
||||
@@ -65,14 +56,7 @@ export default function CuesheetTableHeader({ handleCSVExport, featureData }: Cu
|
||||
<div className={style.playbackLabel}>{selected}</div>
|
||||
<PlaybackIcon state={featureData.playback} />
|
||||
</div>
|
||||
<div className={style.timer}>
|
||||
<div className={style.timerLabel}>Running Timer</div>
|
||||
<div className={style.value}>{timerNow}</div>
|
||||
</div>
|
||||
<div className={style.clock}>
|
||||
<div className={style.clockLabel}>Time Now</div>
|
||||
<div className={style.value}>{timeNow}</div>
|
||||
</div>
|
||||
<CuesheetTableHeaderTimers />
|
||||
<div className={style.headerActions}>
|
||||
<Tooltip openDelay={tooltipDelayFast} label='Toggle follow'>
|
||||
<span onClick={() => toggleFollow()} className={`${style.actionIcon} ${followSelected ? style.enabled : ''}`}>
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
<div className={style.timer}>
|
||||
<div className={style.timerLabel}>Running Timer</div>
|
||||
<div className={style.value}>{timerNow}</div>
|
||||
</div>
|
||||
<div className={style.clock}>
|
||||
<div className={style.clockLabel}>Time Now</div>
|
||||
<div className={style.value}>{timeNow}</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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)
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default memo(CuesheetTableSettings);
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user