diff --git a/packages/components/src/DateTimeInput.tsx b/packages/components/src/DateTimeInput.tsx index 79164fbeb8..3a168950f2 100644 --- a/packages/components/src/DateTimeInput.tsx +++ b/packages/components/src/DateTimeInput.tsx @@ -36,7 +36,9 @@ function fixIncompleteValue(value: string): string { return value; } -const removeSeparators = (value: string) => value.replace(/\u200B/g, ''); +function removeSeparators(value: string): string { + return value.replace(/\u200B/g, ''); +} const EXAMPLES = [addSeparators(DEFAULT_VALUE_STRING)]; diff --git a/packages/file-explorer/src/FileList.tsx b/packages/file-explorer/src/FileList.tsx index 190afdf01f..15d8796642 100644 --- a/packages/file-explorer/src/FileList.tsx +++ b/packages/file-explorer/src/FileList.tsx @@ -1,14 +1,6 @@ -import { - ItemList, - Range, - RenderItemProps, - Tooltip, -} from '@deephaven/components'; -import { dhPython, vsCode, vsFolder, vsFolderOpened } from '@deephaven/icons'; +import { ItemList, Range } from '@deephaven/components'; import Log from '@deephaven/log'; import { RangeUtils } from '@deephaven/utils'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { IconDefinition } from '@fortawesome/fontawesome-svg-core'; import classNames from 'classnames'; import React, { useCallback, @@ -19,12 +11,8 @@ import React, { } from 'react'; import { FileStorageItem, FileStorageTable, isDirectory } from './FileStorage'; import './FileList.scss'; -import FileUtils, { MIME_TYPE } from './FileUtils'; -import { - DEFAULT_ROW_HEIGHT, - getMoveOperation, - getPathFromItem, -} from './FileListUtils'; +import { DEFAULT_ROW_HEIGHT, getMoveOperation } from './FileListUtils'; +import { FileListItem, FileListRenderItemProps } from './FileListItem'; const log = Log.module('FileList'); @@ -39,19 +27,6 @@ export type ListViewport = { bottom: number; }; -export type FileListRenderItemProps = RenderItemProps & { - children?: JSX.Element; - dropTargetItem?: FileStorageItem; - draggedItems?: FileStorageItem[]; - isDragInProgress: boolean; - isDropTargetValid: boolean; - - onDragStart(index: number, e: React.DragEvent): void; - onDragOver(index: number, e: React.DragEvent): void; - onDragEnd(index: number, e: React.DragEvent): void; - onDrop(index: number, e: React.DragEvent): void; -}; - export interface FileListProps { table: FileStorageTable; @@ -76,104 +51,6 @@ const DRAG_HOVER_TIMEOUT = 500; const ITEM_LIST_CLASS_NAME = 'item-list-scroll-pane'; -export function RenderFileListItem( - props: FileListRenderItemProps -): JSX.Element { - const { - children, - draggedItems, - isDragInProgress, - isDropTargetValid, - isSelected, - item, - itemIndex, - dropTargetItem, - onDragStart, - onDragOver, - onDragEnd, - onDrop, - } = props; - - const isDragged = - draggedItems?.some(draggedItem => draggedItem.id === item.id) ?? false; - const itemPath = getPathFromItem(item); - const dropTargetPath = - isDragInProgress && dropTargetItem ? getPathFromItem(dropTargetItem) : null; - - const isExactDropTarget = - isDragInProgress && - isDropTargetValid && - isDirectory(item) && - dropTargetPath === itemPath; - const isInDropTarget = - isDragInProgress && isDropTargetValid && dropTargetPath === itemPath; - const isInvalidDropTarget = - isDragInProgress && !isDropTargetValid && dropTargetPath === itemPath; - - const icon = getItemIcon(item); - const depth = FileUtils.getDepth(item.filename); - const depthLines = Array(depth) - .fill(null) - .map((value, index) => ( - // eslint-disable-next-line react/no-array-index-key - - )); - - return ( -
onDragStart(itemIndex, e)} - onDragOver={e => onDragOver(itemIndex, e)} - onDragEnd={e => onDragEnd(itemIndex, e)} - onDrop={e => onDrop(itemIndex, e)} - draggable - role="presentation" - aria-label={item.basename} - > - {depthLines}{' '} - {' '} - - {children ?? item.basename} - - {children ?? item.basename} - - -
- ); -} - -/** - * Get the icon definition for a file or folder item - * @param item Item to get the icon for - * @returns Icon definition to pass in the FontAwesomeIcon icon prop - */ -function getItemIcon(item: FileStorageItem): IconDefinition { - if (isDirectory(item)) { - return item.isExpanded ? vsFolderOpened : vsFolder; - } - const mimeType = FileUtils.getMimeType(item.basename); - switch (mimeType) { - case MIME_TYPE.PYTHON: - return dhPython; - default: - return vsCode; - } -} - /** * Component that displays and allows interaction with the file system in the provided FileStorageTable. */ @@ -185,7 +62,7 @@ export function FileList(props: FileListProps): JSX.Element { onMove, onSelect, onSelectionChange = () => undefined, - renderItem = RenderFileListItem, + renderItem = FileListItem, rowHeight = DEFAULT_ROW_HEIGHT, overscanCount = ItemList.DEFAULT_OVERSCAN, } = props; diff --git a/packages/file-explorer/src/FileListContainer.tsx b/packages/file-explorer/src/FileListContainer.tsx index fd99395e8e..6a68d7b346 100644 --- a/packages/file-explorer/src/FileListContainer.tsx +++ b/packages/file-explorer/src/FileListContainer.tsx @@ -1,10 +1,8 @@ import { ContextAction, ContextActions } from '@deephaven/components'; import { assertNotNull } from '@deephaven/utils'; import React, { useCallback, useMemo, useState } from 'react'; -import FileList, { - RenderFileListItem, - FileListRenderItemProps, -} from './FileList'; +import FileList from './FileList'; +import { FileListItem, FileListRenderItemProps } from './FileListItem'; import { DEFAULT_ROW_HEIGHT } from './FileListUtils'; import { FileStorageItem, FileStorageTable, isDirectory } from './FileStorage'; import SHORTCUTS from './FileExplorerShortcuts'; @@ -191,19 +189,20 @@ export function FileListContainer(props: FileListContainerProps): JSX.Element { (itemProps: FileListRenderItemProps): JSX.Element => { const { item } = itemProps; if (renameItem && renameItem.filename === item.filename) { - return RenderFileListItem({ - ...itemProps, - children: ( + return ( + // eslint-disable-next-line react/jsx-props-no-spreading + - ), - }); + + ); } - return RenderFileListItem(itemProps); + // eslint-disable-next-line react/jsx-props-no-spreading + return ; }, [handleRenameCancel, handleRenameSubmit, renameItem, validateRenameItem] ); diff --git a/packages/file-explorer/src/FileListItem.tsx b/packages/file-explorer/src/FileListItem.tsx new file mode 100644 index 0000000000..6cba083a24 --- /dev/null +++ b/packages/file-explorer/src/FileListItem.tsx @@ -0,0 +1,121 @@ +import React from 'react'; +import { Tooltip, RenderItemProps } from '@deephaven/components'; +import { dhPython, vsCode, vsFolder, vsFolderOpened } from '@deephaven/icons'; +import { IconDefinition } from '@fortawesome/fontawesome-svg-core'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import classNames from 'classnames'; +import { FileStorageItem, isDirectory } from './FileStorage'; +import './FileList.scss'; +import FileUtils, { MIME_TYPE } from './FileUtils'; +import { getPathFromItem } from './FileListUtils'; + +/** + * Get the icon definition for a file or folder item + * @param item Item to get the icon for + * @returns Icon definition to pass in the FontAwesomeIcon icon prop + */ +function getItemIcon(item: FileStorageItem): IconDefinition { + if (isDirectory(item)) { + return item.isExpanded ? vsFolderOpened : vsFolder; + } + const mimeType = FileUtils.getMimeType(item.basename); + switch (mimeType) { + case MIME_TYPE.PYTHON: + return dhPython; + default: + return vsCode; + } +} + +export type FileListRenderItemProps = RenderItemProps & { + children?: JSX.Element; + dropTargetItem?: FileStorageItem; + draggedItems?: FileStorageItem[]; + isDragInProgress: boolean; + isDropTargetValid: boolean; + + onDragStart(index: number, e: React.DragEvent): void; + onDragOver(index: number, e: React.DragEvent): void; + onDragEnd(index: number, e: React.DragEvent): void; + onDrop(index: number, e: React.DragEvent): void; +}; + +export function FileListItem(props: FileListRenderItemProps): JSX.Element { + const { + children, + draggedItems, + isDragInProgress, + isDropTargetValid, + isSelected, + item, + itemIndex, + dropTargetItem, + onDragStart, + onDragOver, + onDragEnd, + onDrop, + } = props; + + const isDragged = + draggedItems?.some(draggedItem => draggedItem.id === item.id) ?? false; + const itemPath = getPathFromItem(item); + const dropTargetPath = + isDragInProgress && dropTargetItem ? getPathFromItem(dropTargetItem) : null; + + const isExactDropTarget = + isDragInProgress && + isDropTargetValid && + isDirectory(item) && + dropTargetPath === itemPath; + const isInDropTarget = + isDragInProgress && isDropTargetValid && dropTargetPath === itemPath; + const isInvalidDropTarget = + isDragInProgress && !isDropTargetValid && dropTargetPath === itemPath; + + const icon = getItemIcon(item); + const depth = FileUtils.getDepth(item.filename); + const depthLines = Array(depth) + .fill(null) + .map((value, index) => ( + // eslint-disable-next-line react/no-array-index-key + + )); + + return ( +
onDragStart(itemIndex, e)} + onDragOver={e => onDragOver(itemIndex, e)} + onDragEnd={e => onDragEnd(itemIndex, e)} + onDrop={e => onDrop(itemIndex, e)} + draggable + role="presentation" + aria-label={item.basename} + > + {depthLines}{' '} + {' '} + + {children ?? item.basename} + + {children ?? item.basename} + + +
+ ); +} + +export default FileListItem; diff --git a/packages/file-explorer/src/index.ts b/packages/file-explorer/src/index.ts index 3a414c6e18..270532e952 100644 --- a/packages/file-explorer/src/index.ts +++ b/packages/file-explorer/src/index.ts @@ -3,6 +3,7 @@ import FileExplorer from './FileExplorer'; export * from './FileExplorer'; export * from './FileListContainer'; export * from './FileList'; +export * from './FileListItem'; export * from './FileListUtils'; export * from './FileStorage'; export { default as FileExistsError } from './FileExistsError';