From fcb43dee88788c696566585a1a4cc309f79f5990 Mon Sep 17 00:00:00 2001 From: Chandra Y Date: Thu, 11 Jul 2024 10:50:39 -0500 Subject: [PATCH] [SelectModal] New features added - back button and first row (#1340) * [SelectModal] Back button and related features * Fix lint error * Disable selection for root if it is first row * Update SelectModal.tsx --------- Co-authored-by: Sal Tijerina --- .../FileListingTable/FileListingTable.tsx | 8 +- .../workspace/src/SelectModal/SelectModal.tsx | 239 ++++++++++++------ 2 files changed, 169 insertions(+), 78 deletions(-) diff --git a/client/modules/_common_components/src/datafiles/FileListingTable/FileListingTable.tsx b/client/modules/_common_components/src/datafiles/FileListingTable/FileListingTable.tsx index 84bdf21c7c..04b1db5914 100644 --- a/client/modules/_common_components/src/datafiles/FileListingTable/FileListingTable.tsx +++ b/client/modules/_common_components/src/datafiles/FileListingTable/FileListingTable.tsx @@ -27,6 +27,7 @@ export const FileListingTable: React.FC< emptyListingDisplay?: React.ReactNode; noSelection?: boolean; searchTerm?: string | null; + currentDisplayPath?: TFileListing | undefined; } & Omit > = ({ api, @@ -40,6 +41,7 @@ export const FileListingTable: React.FC< emptyListingDisplay, searchTerm = '', noSelection, + currentDisplayPath = null, ...props }) => { const limit = 100; @@ -71,8 +73,12 @@ export const FileListingTable: React.FC< if (filterFn) { return filterFn(cl); } + if (currentDisplayPath) { + return [currentDisplayPath, ...cl]; + } + return cl; - }, [data, filterFn]); + }, [data, filterFn, currentDisplayPath]); /* HANDLE FILE SELECTION */ const { selectedFiles, setSelectedFiles } = useSelectedFiles( diff --git a/client/modules/workspace/src/SelectModal/SelectModal.tsx b/client/modules/workspace/src/SelectModal/SelectModal.tsx index d692876934..e49f37b007 100644 --- a/client/modules/workspace/src/SelectModal/SelectModal.tsx +++ b/client/modules/workspace/src/SelectModal/SelectModal.tsx @@ -8,7 +8,11 @@ import { ConfigProvider, ThemeConfig, } from 'antd'; -import { CaretDownOutlined, CaretUpOutlined } from '@ant-design/icons'; +import { + CaretDownOutlined, + CaretUpOutlined, + LeftOutlined, +} from '@ant-design/icons'; import { useAuthenticatedUser, @@ -16,6 +20,7 @@ import { useGetSystems, TTapisSystem, TUser, + TFileListing, } from '@client/hooks'; import { @@ -29,22 +34,7 @@ import { SelectModalProjectListing } from './SelectModalProjectListing'; const api = 'tapis'; const portalName = 'DesignSafe'; -const HeaderTitle: React.FC<{ - api: string; - system: string; - path: string; - label: string; -}> = ({ api, system, path, label }) => { - const getPathName = usePathDisplayName(); - return ( - - -    - - {getPathName(api, system, path, label)} - - ); -}; +const projectPrefix = 'project-'; const SystemSuffixIcon = () => { return ( @@ -109,83 +99,144 @@ const getScheme = (storageSystem: TTapisSystem): string => { return storageSystem.notes?.isMyData ? 'private' : 'public'; }; -const getPath = ( - storageSystem: TTapisSystem, +const getSystemRootPath = ( + storageSystem: TTapisSystem | undefined, user: TUser | undefined ): string => { - return storageSystem.notes?.isMyData + return storageSystem?.notes?.isMyData ? encodeURIComponent('/' + user?.username) : ''; }; +const getBackPath = ( + encodedPath: string, + searchTerm: string | null, + clearSearchTerm: () => void, + system: TTapisSystem | undefined, + user: TUser | undefined +): string => { + if (searchTerm) { + clearSearchTerm(); + return encodedPath; + } + const pathParts = decodeURIComponent(encodedPath).split('/'); + const isRootPath = pathParts.join('/') === getSystemRootPath(system, user); + if (!isRootPath) { + pathParts.pop(); + } + return encodeURIComponent(pathParts.join('/')); +}; + +const getParentFolder = ( + name: string, + system: string, + path: string +): TFileListing => { + return { + format: 'folder', + lastModified: '', + length: 1, + type: 'dir', + mimeType: '', + name: name, + permissions: '', + path: decodeURIComponent(path), + system: system, + }; +}; + function getFilesColumns( api: string, - system: string, path: string, - systemLabel: string, selectionMode: string, + searchTerm: string | null, + clearSearchTerm: () => void, selectionCallback: (path: string) => void, - navCallback: (path: string) => void + navCallback: (path: string) => void, + user: TUser | undefined, + selectedSystem?: TTapisSystem ): TFileListingColumns { return [ { title: ( - - ), - dataIndex: 'name', - ellipsis: true, - - render: (data, record) => - record.format === 'folder' ? ( +
- ) : ( - +
+ ), + dataIndex: 'name', + ellipsis: true, + + render: (data, record, index) => { + const isFolder = record.format === 'folder'; + const marginLeft = index === 0 ? '3rem' : '6rem'; + const commonStyle = { + marginLeft, + textAlign: 'center' as const, + display: 'flex', + alignItems: 'center', + }; + const iconClassName = isFolder ? 'fa fa-folder-o' : 'fa fa-file-o'; + + if (isFolder && index > 0) { + return ( + + ); + } + + return ( + {data} - ), + ); + }, }, { dataIndex: 'path', align: 'end', title: '', - render: (_, record) => { - const shouldRenderSelectButton = + render: (_, record, index) => { + const selectionModeAllowed = (record.type === 'dir' && selectionMode === 'directory') || (record.type === 'file' && selectionMode === 'file') || selectionMode === 'both'; + const isNotRoot = + index > 0 || + record.system.startsWith(projectPrefix) || + path !== getSystemRootPath(selectedSystem, user); + const shouldRenderSelectButton = isNotRoot && selectionModeAllowed; return shouldRenderSelectButton ? ( (null); const [form] = Form.useForm(); + const getPathName = usePathDisplayName(); const handleClose = () => { setIsModalOpen(false); form.resetFields(); onClose(); }; + const clearSearchTerm = () => { + form.resetFields(); + setSearchTerm(null); + }; useEffect(() => { if (isModalOpen) { - form.resetFields(); - setSearchTerm(null); + clearSearchTerm(); } }, [form, isModalOpen]); const { user } = useAuthenticatedUser(); @@ -251,8 +306,9 @@ export const SelectModal: React.FC<{ const defaultParams = useMemo( () => ({ selectedApi: api, - selectedSystem: defaultStorageSystem.id, - selectedPath: getPath(defaultStorageSystem, user), + selectedSystemId: defaultStorageSystem.id, + selectedSystem: defaultStorageSystem, + selectedPath: getSystemRootPath(defaultStorageSystem, user), scheme: getScheme(defaultStorageSystem), }), [defaultStorageSystem, user] @@ -260,16 +316,23 @@ export const SelectModal: React.FC<{ const [selection, setSelection] = useState<{ selectedApi: string; - selectedSystem: string; + selectedSystemId: string; selectedPath: string; scheme?: string; projectId?: string; + selectedSystem?: TTapisSystem; }>(defaultParams); const [systemLabel, setSystemLabel] = useState( defaultStorageSystem.notes.label ?? defaultStorageSystem.id ); const [showProjects, setShowProjects] = useState(false); - const { selectedApi, selectedSystem, selectedPath, scheme } = selection; + const { + selectedApi, + selectedSystemId, + selectedSystem, + selectedPath, + scheme, + } = selection; useEffect(() => setSelection(defaultParams), [isModalOpen, defaultParams]); const [dropdownValue, setDropdownValue] = useState( defaultStorageSystem.id @@ -278,6 +341,13 @@ export const SelectModal: React.FC<{ setDropdownValue(newValue); if (newValue === 'myprojects') { setShowProjects(true); + setSelection({ + selectedApi: api, + selectedSystemId: '', + selectedSystem: undefined, + selectedPath: '', + scheme: '', + }); setSystemLabel('My Projects'); return; } @@ -288,8 +358,9 @@ export const SelectModal: React.FC<{ setShowProjects(false); setSelection({ selectedApi: api, - selectedSystem: system.id, - selectedPath: getPath(system, user), + selectedSystemId: system.id, + selectedSystem: system, + selectedPath: getSystemRootPath(system, user), scheme: getScheme(system), }); setSystemLabel(system.notes.label ?? system.id); @@ -299,9 +370,10 @@ export const SelectModal: React.FC<{ setShowProjects(false); setSelection({ selectedApi: api, - selectedSystem: `project-${uuid}`, + selectedSystemId: `project-${uuid}`, selectedPath: '', projectId: projectId, + selectedSystem: undefined, //not using system for project }); // Intended to indicate searching the root path of a project. setSystemLabel('root'); @@ -310,7 +382,7 @@ export const SelectModal: React.FC<{ useEffect(() => { if (isModalOpen) { let systemValue = system ?? defaultStorageSystem.id; - if (systemValue.startsWith('project-')) { + if (systemValue.startsWith(projectPrefix)) { systemValue = 'myprojects'; } dropdownCallback(systemValue); @@ -341,16 +413,19 @@ export const SelectModal: React.FC<{ () => getFilesColumns( selectedApi, - selectedSystem, selectedPath, - systemLabel, selectionMode, + searchTerm, + clearSearchTerm, (selection: string) => selectCallback(selection), - navCallback + navCallback, + user, + selectedSystem ), [ navCallback, selectedApi, + selectedSystemId, selectedSystem, selectedPath, systemLabel, @@ -391,11 +466,11 @@ export const SelectModal: React.FC<{ )}