fix: column reordering

refactor: migrate local storage hook
This commit is contained in:
Carlos Valente
2024-05-27 15:53:24 +02:00
committed by Carlos Valente
parent 4beac19c4a
commit b5993b91e1
4 changed files with 41 additions and 83 deletions
@@ -1,46 +0,0 @@
import { useSyncExternalStore } from 'react';
const STORAGE_EVENT = 'ontime-storage';
function getSnapshot(key: string): string | null {
try {
return window.localStorage.getItem(`ontime-${key}`);
} catch {
return null;
}
}
function getParsedJson<T>(localStorageValue: string | null, initialValue: T): T {
try {
return localStorageValue ? JSON.parse(localStorageValue) : initialValue;
} catch {
return initialValue;
}
}
export const useLocalStorage = <T>(key: string, initialValue: T) => {
const localStorageValue = useSyncExternalStore(subscribe, () => getSnapshot(key));
const parsedLocalStorageValue = getParsedJson(localStorageValue, initialValue);
/**
* @description Set value to local storage
* @param value
*/
const setLocalStorageValue = (value: T | ((val: T) => T)) => {
// Allow value to be a function so we have same API as useState
const valueToStore = value instanceof Function ? value(parsedLocalStorageValue) : value;
localStorage.setItem(`ontime-${key}`, JSON.stringify(valueToStore));
window.dispatchEvent(new StorageEvent(STORAGE_EVENT));
};
return [parsedLocalStorageValue, setLocalStorageValue] as const;
};
function subscribe(callback: () => void) {
window.addEventListener(STORAGE_EVENT, callback);
return () => {
window.removeEventListener(STORAGE_EVENT, callback);
};
}
@@ -1,11 +1,11 @@
import { Tooltip } from '@chakra-ui/react'; import { Tooltip } from '@chakra-ui/react';
import { useLocalStorage } from '@mantine/hooks';
import { IoAdd } from '@react-icons/all-files/io5/IoAdd'; import { IoAdd } from '@react-icons/all-files/io5/IoAdd';
import { IoRemove } from '@react-icons/all-files/io5/IoRemove'; import { IoRemove } from '@react-icons/all-files/io5/IoRemove';
import { Playback } from 'ontime-types'; import { Playback } from 'ontime-types';
import { MILLIS_PER_HOUR, MILLIS_PER_SECOND, parseUserTime } from 'ontime-utils'; import { MILLIS_PER_HOUR, MILLIS_PER_SECOND, parseUserTime } from 'ontime-utils';
import TimeInput from '../../../../common/components/input/time-input/TimeInput'; import TimeInput from '../../../../common/components/input/time-input/TimeInput';
import { useLocalStorage } from '../../../../common/hooks/useLocalStorage';
import { setPlayback } from '../../../../common/hooks/useSocket'; import { setPlayback } from '../../../../common/hooks/useSocket';
import { tooltipDelayMid } from '../../../../ontimeConfig'; import { tooltipDelayMid } from '../../../../ontimeConfig';
import TapButton from '../tap-button/TapButton'; import TapButton from '../tap-button/TapButton';
@@ -18,7 +18,7 @@ interface AddTimeProps {
export default function AddTime(props: AddTimeProps) { export default function AddTime(props: AddTimeProps) {
const { playback } = props; const { playback } = props;
const [time, setTime] = useLocalStorage('add-time', 300_000); // 5 minutes const [time, setTime] = useLocalStorage({ key: 'add-time', defaultValue: 300_000 }); // 5 minutes
const handleTimeChange = (_field: string, value: string) => { const handleTimeChange = (_field: string, value: string) => {
const newTime = parseUserTime(value); const newTime = parseUserTime(value);
+35 -6
View File
@@ -1,10 +1,10 @@
import { useRef } from 'react'; import { useCallback, useEffect, useRef } from 'react';
import { useLocalStorage } from '@mantine/hooks';
import { ColumnDef, flexRender, getCoreRowModel, useReactTable } from '@tanstack/react-table'; import { ColumnDef, flexRender, getCoreRowModel, useReactTable } from '@tanstack/react-table';
import Color from 'color'; import Color from 'color';
import { isOntimeBlock, isOntimeDelay, isOntimeEvent, OntimeRundown, OntimeRundownEntry } from 'ontime-types'; import { isOntimeBlock, isOntimeDelay, isOntimeEvent, OntimeRundown, OntimeRundownEntry } from 'ontime-types';
import useFollowComponent from '../../common/hooks/useFollowComponent'; import useFollowComponent from '../../common/hooks/useFollowComponent';
import { useLocalStorage } from '../../common/hooks/useLocalStorage';
import { getAccessibleColour } from '../../common/utils/styleUtils'; import { getAccessibleColour } from '../../common/utils/styleUtils';
import BlockRow from './cuesheet-table-elements/BlockRow'; import BlockRow from './cuesheet-table-elements/BlockRow';
@@ -27,14 +27,23 @@ interface CuesheetProps {
export default function Cuesheet({ data, columns, handleUpdate, selectedId }: CuesheetProps) { export default function Cuesheet({ data, columns, handleUpdate, selectedId }: CuesheetProps) {
const { followSelected, showSettings, showDelayBlock, showPrevious } = useCuesheetSettings(); const { followSelected, showSettings, showDelayBlock, showPrevious } = useCuesheetSettings();
const [columnVisibility, setColumnVisibility] = useLocalStorage('table-hidden', {}); const [columnVisibility, setColumnVisibility] = useLocalStorage({ key: 'table-hidden', defaultValue: {} });
const [columnOrder, saveColumnOrder] = useLocalStorage<string[]>('table-order', initialColumnOrder); const [columnOrder, saveColumnOrder] = useLocalStorage<string[]>({
const [columnSizing, setColumnSizing] = useLocalStorage('table-sizes', {}); key: 'table-order',
defaultValue: initialColumnOrder,
});
const [columnSizing, setColumnSizing] = useLocalStorage({ key: 'table-sizes', defaultValue: {} });
const selectedRef = useRef<HTMLTableRowElement | null>(null); const selectedRef = useRef<HTMLTableRowElement | null>(null);
const tableContainerRef = useRef<HTMLDivElement | null>(null); const tableContainerRef = useRef<HTMLDivElement | null>(null);
useFollowComponent({ followRef: selectedRef, scrollRef: tableContainerRef, doFollow: followSelected }); useFollowComponent({ followRef: selectedRef, scrollRef: tableContainerRef, doFollow: followSelected });
// keep column order in sync with columns
useEffect(() => {
const order = columns.map((col) => col.id as string);
saveColumnOrder(order);
}, [columns, saveColumnOrder]);
const table = useReactTable({ const table = useReactTable({
data, data,
columns, columns,
@@ -64,6 +73,26 @@ export default function Cuesheet({ data, columns, handleUpdate, selectedId }: Cu
setColumnSizing({}); setColumnSizing({});
}; };
const reorder = useCallback(
(fromId: string, toId: string) => {
// get index of from
const fromIndex = columnOrder.indexOf(fromId);
// get index of to
const toIndex = columnOrder.indexOf(toId);
if (toIndex === -1) {
return;
}
const reorderedCols = [...columnOrder];
const reorderedItem = reorderedCols.splice(fromIndex, 1);
reorderedCols.splice(toIndex, 0, reorderedItem[0]);
saveColumnOrder(reorderedCols);
},
[columnOrder, saveColumnOrder],
);
const headerGroups = table.getHeaderGroups(); const headerGroups = table.getHeaderGroups();
const rowModel = table.getRowModel(); const rowModel = table.getRowModel();
const allLeafColumns = table.getAllLeafColumns(); const allLeafColumns = table.getAllLeafColumns();
@@ -83,7 +112,7 @@ export default function Cuesheet({ data, columns, handleUpdate, selectedId }: Cu
)} )}
<div ref={tableContainerRef} className={style.cuesheetContainer}> <div ref={tableContainerRef} className={style.cuesheetContainer}>
<table className={style.cuesheet}> <table className={style.cuesheet}>
<CuesheetHeader headerGroups={headerGroups} /> <CuesheetHeader headerGroups={headerGroups} saveColumnOrder={reorder} />
<tbody> <tbody>
{rowModel.rows.map((row) => { {rowModel.rows.map((row) => {
const key = row.original.id; const key = row.original.id;
@@ -1,4 +1,3 @@
import { memo, useEffect } from 'react';
import { Tooltip } from '@chakra-ui/react'; import { Tooltip } from '@chakra-ui/react';
import { import {
closestCenter, closestCenter,
@@ -14,10 +13,8 @@ import { horizontalListSortingStrategy, SortableContext, sortableKeyboardCoordin
import { flexRender, HeaderGroup } from '@tanstack/react-table'; import { flexRender, HeaderGroup } from '@tanstack/react-table';
import { OntimeRundownEntry } from 'ontime-types'; import { OntimeRundownEntry } from 'ontime-types';
import { useLocalStorage } from '../../../common/hooks/useLocalStorage';
import { getAccessibleColour } from '../../../common/utils/styleUtils'; import { getAccessibleColour } from '../../../common/utils/styleUtils';
import { tooltipDelayFast } from '../../../ontimeConfig'; import { tooltipDelayFast } from '../../../ontimeConfig';
import { initialColumnOrder } from '../cuesheetCols';
import { SortableCell } from './SortableCell'; import { SortableCell } from './SortableCell';
@@ -25,17 +22,11 @@ import style from '../Cuesheet.module.scss';
interface CuesheetHeaderProps { interface CuesheetHeaderProps {
headerGroups: HeaderGroup<OntimeRundownEntry>[]; headerGroups: HeaderGroup<OntimeRundownEntry>[];
saveColumnOrder: (fromId: string, toId: string) => void;
} }
function CuesheetHeader(props: CuesheetHeaderProps) { export default function CuesheetHeader(props: CuesheetHeaderProps) {
const { headerGroups } = props; const { headerGroups, saveColumnOrder } = props;
const [columnOrder, saveColumnOrder] = useLocalStorage<string[]>('table-order', initialColumnOrder);
useEffect(() => {
if (!localStorage.getItem('table-order')) {
saveColumnOrder(initialColumnOrder);
}
}, [saveColumnOrder]);
const handleOnDragEnd = (event: DragEndEvent) => { const handleOnDragEnd = (event: DragEndEvent) => {
const { delta, active, over } = event; const { delta, active, over } = event;
@@ -45,21 +36,7 @@ function CuesheetHeader(props: CuesheetHeaderProps) {
// cancel if we do not have an over id // cancel if we do not have an over id
if (over?.id == null) return; if (over?.id == null) return;
// get index of from saveColumnOrder(active.id as string, over.id as string);
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( const sensors = useSensors(
@@ -119,5 +96,3 @@ function CuesheetHeader(props: CuesheetHeaderProps) {
</thead> </thead>
); );
} }
export default memo(CuesheetHeader);