From 318cc2d81b09636a9b1e9fa0a0550088c9ce17c8 Mon Sep 17 00:00:00 2001 From: somebody1234 Date: Wed, 17 Jan 2024 08:13:15 +1000 Subject: [PATCH] Inline `Table`, `TableRow` and `TableColumn` (#8760) These components are a legacy remnant of the old design, where `Table` needed to be reusable because there was a separate table for each asset type. # Important Notes None --- .../lib/dashboard/src/components/Table.tsx | 356 ---------- .../dashboard/src/components/TableColumn.tsx | 31 - .../lib/dashboard/src/components/TableRow.tsx | 187 ------ .../src/components/dashboard/AssetRow.tsx | 264 +++++--- .../src/components/dashboard/column.ts | 52 +- .../NameColumn.tsx} | 0 .../src/components/dashboard/columnHeading.ts | 7 +- .../columnHeading/ModifiedColumnHeading.tsx | 7 +- .../columnHeading/NameColumnHeading.tsx | 7 +- .../layouts/dashboard/AssetContextMenu.tsx | 10 +- .../src/layouts/dashboard/AssetsTable.tsx | 608 ++++++++++-------- .../dashboard/AssetsTableContextMenu.tsx | 196 ++++++ 12 files changed, 731 insertions(+), 994 deletions(-) delete mode 100644 app/ide-desktop/lib/dashboard/src/components/Table.tsx delete mode 100644 app/ide-desktop/lib/dashboard/src/components/TableColumn.tsx delete mode 100644 app/ide-desktop/lib/dashboard/src/components/TableRow.tsx rename app/ide-desktop/lib/dashboard/src/components/dashboard/{AssetNameColumn.tsx => column/NameColumn.tsx} (100%) create mode 100644 app/ide-desktop/lib/dashboard/src/layouts/dashboard/AssetsTableContextMenu.tsx diff --git a/app/ide-desktop/lib/dashboard/src/components/Table.tsx b/app/ide-desktop/lib/dashboard/src/components/Table.tsx deleted file mode 100644 index d24a26ebf167..000000000000 --- a/app/ide-desktop/lib/dashboard/src/components/Table.tsx +++ /dev/null @@ -1,356 +0,0 @@ -/** @file A table that projects an object into each column. - * This is intended to be specialized into components for specific item types, rather than - * being used directly. */ -import * as React from 'react' - -import * as shortcutsProvider from '#/providers/ShortcutsProvider' -import * as set from '#/utilities/set' -import * as shortcutsModule from '#/utilities/shortcuts' - -import Spinner, * as spinner from '#/components/Spinner' -import type * as tableColumn from '#/components/TableColumn' -import type * as tableRow from '#/components/TableRow' -import TableRow from '#/components/TableRow' - -// ================= -// === Constants === -// ================= - -/** The size of the loading spinner. */ -const LOADING_SPINNER_SIZE = 36 - -// ============================= -// === Partial `Props` types === -// ============================= - -/** `state: State`. */ -interface StateProp { - state: State -} - -/** `initialRowState: RowState`. */ -interface InitialRowStateProp { - initialRowState: RowState -} - -/** `selectedKeys` and `setSelectedKeys` when they are present. */ -interface InternalSelectedKeysProps { - selectedKeys: Set - setSelectedKeys: React.Dispatch>> -} - -/** The absence of `selectedKeys` and `setSelectedKeys`. */ -interface InternalNoSelectedKeysProps { - selectedKeys?: never - setSelectedKeys?: never -} - -// ============= -// === Table === -// ============= - -/** Props for a {@link Table}. */ -interface InternalTableProps { - rowComponent?: (props: tableRow.TableRowProps) => JSX.Element | null - scrollContainerRef?: React.RefObject - headerRowRef?: React.RefObject - footer?: React.ReactNode - items: T[] - filter?: ((item: T) => boolean) | null - state?: State - initialRowState?: RowState - getKey: (item: T) => Key - selectedKeys?: Set - setSelectedKeys?: React.Dispatch>> - columns: tableColumn.TableColumn[] - isLoading: boolean - placeholder?: JSX.Element - className?: string - onContextMenu: ( - selectedKeys: Set, - event: React.MouseEvent, - setSelectedKeys: (items: Set) => void - ) => void - draggableRows?: boolean - onDragLeave?: React.DragEventHandler - onRowDragStart?: (event: React.DragEvent, item: T, key: Key) => void - onRowDrag?: (event: React.DragEvent, item: T, key: Key) => void - onRowDragOver?: (event: React.DragEvent, item: T, key: Key) => void - onRowDragEnd?: (event: React.DragEvent, item: T, key: Key) => void - onRowDrop?: (event: React.DragEvent, item: T, key: Key) => void -} - -/** Props for a {@link Table}. */ -export type TableProps< - T, - State = never, - RowState = never, - Key extends string = string, -> = InternalTableProps & - ([RowState] extends [never] ? unknown : InitialRowStateProp) & - ([State] extends [never] ? unknown : StateProp) & - (InternalNoSelectedKeysProps | InternalSelectedKeysProps) - -/** Table that projects an object into each column. */ -export default function Table( - props: TableProps -) { - const { rowComponent: RowComponent = TableRow, scrollContainerRef, headerRowRef } = props - const { footer, items, filter, getKey } = props - const { selectedKeys: rawSelectedKeys, setSelectedKeys: rawSetSelectedKeys, columns } = props - const { isLoading, placeholder, onContextMenu, draggableRows, onDragLeave } = props - const { onRowDragStart, onRowDrag, onRowDragOver, onRowDragEnd, onRowDrop } = props - const { className, initialRowState, state } = props - const rowProps = { className, initialRowState, state } - - const { shortcuts } = shortcutsProvider.useShortcuts() - const [spinnerState, setSpinnerState] = React.useState(spinner.SpinnerState.initial) - // This should not be made mutable for the sake of optimization, otherwise its value may - // be different after `await`ing an I/O operation. Also, a change in its value should trigger - // a re-render. - const [fallbackSelectedKeys, fallbackSetSelectedKeys] = React.useState(() => new Set()) - const [selectedKeys, setSelectedKeys] = - rawSelectedKeys != null - ? [rawSelectedKeys, rawSetSelectedKeys] - : [fallbackSelectedKeys, fallbackSetSelectedKeys] - const [previouslySelectedKey, setPreviouslySelectedKey] = React.useState(null) - const bodyRef = React.useRef(null) - - // This is required to prevent the table body from overlapping the table header, because - // the table header is transparent. - React.useEffect(() => { - const body = bodyRef.current - const scrollContainer = scrollContainerRef?.current - if (body != null && scrollContainer != null) { - let isClipPathUpdateQueued = false - const updateClipPath = () => { - isClipPathUpdateQueued = false - body.style.clipPath = `inset(${scrollContainer.scrollTop}px 0 0 0)` - } - const onScroll = () => { - if (!isClipPathUpdateQueued) { - isClipPathUpdateQueued = true - requestAnimationFrame(updateClipPath) - } - } - updateClipPath() - scrollContainer.addEventListener('scroll', onScroll) - return () => { - scrollContainer.removeEventListener('scroll', onScroll) - } - } else { - return - } - }, [/* should never change */ scrollContainerRef]) - - React.useEffect(() => { - const onDocumentClick = (event: MouseEvent) => { - if ( - !shortcuts.matchesMouseAction( - shortcutsModule.MouseAction.selectAdditional, - event - ) && - !shortcuts.matchesMouseAction( - shortcutsModule.MouseAction.selectAdditionalRange, - event - ) && - selectedKeys.size !== 0 - ) { - setSelectedKeys(new Set()) - } - } - document.addEventListener('click', onDocumentClick) - return () => { - document.removeEventListener('click', onDocumentClick) - } - }, [selectedKeys, /* should never change */ setSelectedKeys, shortcuts]) - - React.useEffect(() => { - if (isLoading) { - // Ensure the spinner stays in the "initial" state for at least one frame, - // to ensure the CSS animation begins at the initial state. - requestAnimationFrame(() => { - setSpinnerState(spinner.SpinnerState.loadingFast) - }) - } else { - setSpinnerState(spinner.SpinnerState.initial) - } - }, [isLoading]) - - const onRowClick = React.useCallback( - ( - innerRowProps: tableRow.TableRowInnerProps, - event: React.MouseEvent - ) => { - const { key } = innerRowProps - event.stopPropagation() - const getNewlySelectedKeys = () => { - if (previouslySelectedKey == null) { - return [key] - } else { - const index1 = items.findIndex( - innerItem => getKey(innerItem) === previouslySelectedKey - ) - const index2 = items.findIndex(innerItem => getKey(innerItem) === key) - const selectedItems = - index1 <= index2 - ? items.slice(index1, index2 + 1) - : items.slice(index2, index1 + 1) - return selectedItems.map(getKey) - } - } - if (shortcuts.matchesMouseAction(shortcutsModule.MouseAction.selectRange, event)) { - setSelectedKeys(new Set(getNewlySelectedKeys())) - } else if ( - shortcuts.matchesMouseAction( - shortcutsModule.MouseAction.selectAdditionalRange, - event - ) - ) { - setSelectedKeys( - oldSelectedItems => new Set([...oldSelectedItems, ...getNewlySelectedKeys()]) - ) - } else if ( - shortcuts.matchesMouseAction(shortcutsModule.MouseAction.selectAdditional, event) - ) { - setSelectedKeys(oldSelectedItems => { - const newItems = new Set(oldSelectedItems) - if (oldSelectedItems.has(key)) { - newItems.delete(key) - } else { - newItems.add(key) - } - return newItems - }) - } else { - setSelectedKeys(new Set([key])) - } - setPreviouslySelectedKey(key) - }, - [ - items, - previouslySelectedKey, - shortcuts, - /* should never change */ setSelectedKeys, - /* should never change */ getKey, - ] - ) - - const headerRow = ( - - {columns.map(column => { - // This is a React component, even though it does not contain JSX. - // eslint-disable-next-line no-restricted-syntax - const Heading = column.heading - return ( - - - - ) - })} - - ) - - const itemRows = isLoading ? ( - - -
- -
- - - ) : ( - items.map(item => { - const key = getKey(item) - const isSelected = selectedKeys.has(key) - const isSoleSelectedItem = selectedKeys.size === 1 && isSelected - return ( -