From a8a69a44ced11be0c0ef72ab70ca8eb704ca858e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladan=20Tomi=C4=87?= Date: Mon, 9 Oct 2023 17:40:06 +0200 Subject: [PATCH 1/3] feat: mobile file list view (#541) --- .../DriveItemDropdown/DriveItemDropdown.tsx | 3 ++ .../DriveItemDropdown/DriveItemMenu.tsx | 8 ++- .../DriveActionHeaderMobile.tsx | 54 ++++++++++++++++++- .../DriveTableFooter/DriveTableFooter.tsx | 18 ++++--- .../DriveTableHeader/DriveTableHeader.tsx | 4 +- .../DriveTableItem/DrivaTableItem.module.scss | 3 ++ .../Tables/DriveTableItem/DriveTableItem.tsx | 12 ++++- .../Views/DriveListView/DriveListView.tsx | 2 +- src/pages/drive/index.tsx | 3 ++ 9 files changed, 92 insertions(+), 15 deletions(-) create mode 100644 src/components/Tables/DriveTableItem/DrivaTableItem.module.scss diff --git a/src/components/Dropdowns/DriveItemDropdown/DriveItemDropdown.tsx b/src/components/Dropdowns/DriveItemDropdown/DriveItemDropdown.tsx index 358e8e7b..267d0f19 100644 --- a/src/components/Dropdowns/DriveItemDropdown/DriveItemDropdown.tsx +++ b/src/components/Dropdowns/DriveItemDropdown/DriveItemDropdown.tsx @@ -10,12 +10,14 @@ interface DriveItemDropdownProps extends UpdateDriveProps { data: { name: string; }; + mobileAlign?: 'left' | 'right'; handlePreviewClick?: () => void; } const DriveDropdown: FC = ({ type, data, + mobileAlign, updateDrive, handlePreviewClick, }) => { @@ -29,6 +31,7 @@ const DriveDropdown: FC = ({ diff --git a/src/components/Dropdowns/DriveItemDropdown/DriveItemMenu.tsx b/src/components/Dropdowns/DriveItemDropdown/DriveItemMenu.tsx index 649e9df6..f039a7a9 100644 --- a/src/components/Dropdowns/DriveItemDropdown/DriveItemMenu.tsx +++ b/src/components/Dropdowns/DriveItemDropdown/DriveItemMenu.tsx @@ -23,12 +23,14 @@ interface DriveItemMenuProps extends UpdateDriveProps { data: { name: string; }; + mobileAlign?: 'left' | 'right'; handlePreviewClick?: () => void; } const DriveItemMenu: FC = ({ type, data, + mobileAlign, updateDrive, handlePreviewClick, }) => { @@ -147,7 +149,11 @@ const DriveItemMenu: FC = ({ return ( <> - + void; onBackToDrive: () => void; + toggleView: () => void; + toggleSort: () => void; } const DriveActionHeaderMobile = ({ podName, + driveView, directory, onDirectorySelect, onBackToDrive, + toggleView, + toggleSort, }: DriveActionHeaderMobileProps) => { + const { theme } = useContext(ThemeContext); + return ( - <> +
- +
+
+
); }; diff --git a/src/components/Tables/DriveTableFooter/DriveTableFooter.tsx b/src/components/Tables/DriveTableFooter/DriveTableFooter.tsx index 3099de08..bbbbb703 100644 --- a/src/components/Tables/DriveTableFooter/DriveTableFooter.tsx +++ b/src/components/Tables/DriveTableFooter/DriveTableFooter.tsx @@ -67,16 +67,18 @@ const DriveTableFooter: FC = ({
- {` - ${page * rowsPerPage + 1}-${ - page * rowsPerPage + rowsPerPage > totalDriveItems - ? totalDriveItems - : page * rowsPerPage + rowsPerPage - } of - ${totalDriveItems}`} + + {` + ${page * rowsPerPage + 1}-${ + page * rowsPerPage + rowsPerPage > totalDriveItems + ? totalDriveItems + : page * rowsPerPage + rowsPerPage + } of `} + + {totalDriveItems}
-
+
{theme === 'light' ? : } diff --git a/src/components/Tables/DriveTableHeader/DriveTableHeader.tsx b/src/components/Tables/DriveTableHeader/DriveTableHeader.tsx index f2b26e0f..81cd53c4 100644 --- a/src/components/Tables/DriveTableHeader/DriveTableHeader.tsx +++ b/src/components/Tables/DriveTableHeader/DriveTableHeader.tsx @@ -12,7 +12,9 @@ const DriveTableHeader: FC = () => { {intl.get('FILE_NAME')} {intl.get('FILE_TYPE')} {intl.get('FILE_SIZE')} - {intl.get('CREATED')} + + {intl.get('CREATED')} + {/* Empty Table Header for Dropdown Menu */} ); diff --git a/src/components/Tables/DriveTableItem/DrivaTableItem.module.scss b/src/components/Tables/DriveTableItem/DrivaTableItem.module.scss new file mode 100644 index 00000000..94cab4e6 --- /dev/null +++ b/src/components/Tables/DriveTableItem/DrivaTableItem.module.scss @@ -0,0 +1,3 @@ +.name-column { + max-width: 200px; +} diff --git a/src/components/Tables/DriveTableItem/DriveTableItem.tsx b/src/components/Tables/DriveTableItem/DriveTableItem.tsx index 3d3cfb6c..2ef69d9e 100644 --- a/src/components/Tables/DriveTableItem/DriveTableItem.tsx +++ b/src/components/Tables/DriveTableItem/DriveTableItem.tsx @@ -9,6 +9,8 @@ import formatDate from '@utils/formatDate'; import { extractFileExtension } from '@utils/filename'; import { UpdateDriveProps } from '@interfaces/handlers'; +import classes from './DrivaTableItem.module.scss'; + interface DriveTableItemProps extends UpdateDriveProps { type: 'folder' | 'file'; data: { @@ -33,7 +35,9 @@ const DriveTableItem: FC = ({ className="w-full h-14 even:bg-color-shade-dark-3-day odd:bg-color-shade-dark-4-day dark:odd:bg-color-shade-dark-4-night dark:even:bg-color-shade-white-day border border-color-shade-black-day dark:border-color-accents-purple-black shadow-sm cursor-pointer" onClick={onClick} > - + {shortenString(data.name.split('.').shift(), 24)} @@ -42,7 +46,9 @@ const DriveTableItem: FC = ({ {type === 'file' ? prettyBytes(data?.size) : '-'} - + {formatDate(data?.creationTime, false)} @@ -50,6 +56,8 @@ const DriveTableItem: FC = ({ diff --git a/src/components/Views/DriveListView/DriveListView.tsx b/src/components/Views/DriveListView/DriveListView.tsx index a605630c..41a6b206 100644 --- a/src/components/Views/DriveListView/DriveListView.tsx +++ b/src/components/Views/DriveListView/DriveListView.tsx @@ -41,7 +41,7 @@ const DriveListView: FC = ({ }; return ( -
+
diff --git a/src/pages/drive/index.tsx b/src/pages/drive/index.tsx index 45e7fc7f..f3345f92 100644 --- a/src/pages/drive/index.tsx +++ b/src/pages/drive/index.tsx @@ -247,9 +247,12 @@ const Drive: FC = () => {
Date: Tue, 10 Oct 2023 10:57:51 +0200 Subject: [PATCH 2/3] fix: list view pagination --- .../DriveTableFooter/DriveTableFooter.tsx | 17 ++++++------- .../DriveTableHeader/DriveTableHeader.tsx | 20 ++++++++------- .../Views/DriveListView/DriveListView.tsx | 25 +++++++++++++------ 3 files changed, 37 insertions(+), 25 deletions(-) diff --git a/src/components/Tables/DriveTableFooter/DriveTableFooter.tsx b/src/components/Tables/DriveTableFooter/DriveTableFooter.tsx index bbbbb703..1ca4ea72 100644 --- a/src/components/Tables/DriveTableFooter/DriveTableFooter.tsx +++ b/src/components/Tables/DriveTableFooter/DriveTableFooter.tsx @@ -57,7 +57,9 @@ const DriveTableFooter: FC = ({ return (
- {intl.get('ROWS_PER_PAGE')} + + {intl.get('ROWS_PER_PAGE')} +
- - - - - - + + + + + + + + + ); }; diff --git a/src/components/Views/DriveListView/DriveListView.tsx b/src/components/Views/DriveListView/DriveListView.tsx index 41a6b206..ae2029ae 100644 --- a/src/components/Views/DriveListView/DriveListView.tsx +++ b/src/components/Views/DriveListView/DriveListView.tsx @@ -1,4 +1,4 @@ -import { FC, useState } from 'react'; +import { FC, useMemo, useState } from 'react'; import { DriveTableHeader, @@ -24,6 +24,19 @@ const DriveListView: FC = ({ }) => { const [page, setPage] = useState(0); const [rowsPerPage, setRowsPerPage] = useState(10); + const startIndex = page * rowsPerPage; + const endIndex = startIndex + rowsPerPage; + + const { pageDirectories, pageFiles } = useMemo( + () => ({ + pageDirectories: (directories || []).slice(startIndex, endIndex), + pageFiles: (files || []).slice( + startIndex - directories.length, + endIndex - directories.length + ), + }), + [directories, files, startIndex, endIndex] + ); const handlePageUp = () => { if ( @@ -45,9 +58,8 @@ const DriveListView: FC = ({
{intl.get('FILE_NAME')}{intl.get('FILE_TYPE')}{intl.get('FILE_SIZE')}{/* Empty Table Header for Dropdown Menu */}
{intl.get('FILE_NAME')}{intl.get('FILE_TYPE')}{intl.get('FILE_SIZE')}{/* Empty Table Header for Dropdown Menu */}
- {directories - ?.slice(page * rowsPerPage, page * rowsPerPage + (rowsPerPage + 1)) - .map((directory) => ( + + {pageDirectories.map((directory) => ( = ({ /> ))} - {files - ?.slice(page * rowsPerPage, page * rowsPerPage + (rowsPerPage + 1)) - .map((data) => ( + {pageFiles.map((data) => ( = ({ updateDrive={updateDrive} /> ))} +
Date: Thu, 12 Oct 2023 14:59:36 +0200 Subject: [PATCH 3/3] feat: pod search (#542) --- src/@types/fdp-storage.d.ts | 13 ++ src/components/Inputs/SearchBar/SearchBar.tsx | 39 ++++-- .../PreviewFileModal/PreviewFileModal.tsx | 2 +- .../MainNavigationBar/MainNavigationBar.tsx | 5 +- .../Views/DriveGridView/DriveGridView.tsx | 4 +- .../Views/DriveListView/DriveListView.tsx | 4 +- src/context/SearchContext.tsx | 8 ++ src/pages/drive/index.tsx | 121 +++++++++++++++--- src/utils/filename.ts | 8 ++ 9 files changed, 168 insertions(+), 36 deletions(-) create mode 100644 src/@types/fdp-storage.d.ts diff --git a/src/@types/fdp-storage.d.ts b/src/@types/fdp-storage.d.ts new file mode 100644 index 00000000..98964aac --- /dev/null +++ b/src/@types/fdp-storage.d.ts @@ -0,0 +1,13 @@ +import type { + DirectoryItem as FdpDirectoryItem, + FileItem as FdpFileItem, +} from '@fairdatasociety/fdp-storage'; + +declare module '@fairdatasociety/fdp-storage' { + interface DirectoryItem extends FdpDirectoryItem { + path?: string; + } + interface FileItem extends FdpFileItem { + path?: string; + } +} diff --git a/src/components/Inputs/SearchBar/SearchBar.tsx b/src/components/Inputs/SearchBar/SearchBar.tsx index f9498380..af193210 100644 --- a/src/components/Inputs/SearchBar/SearchBar.tsx +++ b/src/components/Inputs/SearchBar/SearchBar.tsx @@ -10,42 +10,59 @@ import CloseDarkIcon from '@media/UI/close-light.svg'; import classes from './SearchBar.module.scss'; import { useLocales } from '@context/LocalesContext'; +import { useForm } from 'react-hook-form'; +import PodContext from '@context/PodContext'; interface SearchBarProps {} const SearchBar: FC = () => { const { theme } = useContext(ThemeContext); - const { search, updateSearch } = useContext(SearchContext); + const { updateSearch, searchDisabled } = useContext(SearchContext); + const { activePod, loading } = useContext(PodContext); const { intl } = useLocales(); + const { register, handleSubmit, reset } = useForm<{ search: '' }>(); + + const onSubmitInternal = ({ search }) => updateSearch(search); return ( -
- {theme === 'light' ? ( - - ) : ( - - )} +
+ + {theme === 'light' ? ( + + ) : ( + + )} + updateSearch(e.target.value)} + {...register('search', { required: true })} /> -
updateSearch('')}> +
{ + reset(); + updateSearch(''); + }} + > {theme === 'light' ? ( ) : ( )}
-
+
); }; diff --git a/src/components/Modals/PreviewFileModal/PreviewFileModal.tsx b/src/components/Modals/PreviewFileModal/PreviewFileModal.tsx index 2d6b64f3..490749c0 100644 --- a/src/components/Modals/PreviewFileModal/PreviewFileModal.tsx +++ b/src/components/Modals/PreviewFileModal/PreviewFileModal.tsx @@ -70,7 +70,7 @@ const PreviewFileModal: FC = ({ setLoading(true); downloadFile(fdpClientRef.current, { filename: previewFile?.name, - directory: directoryName, + directory: previewFile.path || directoryName, podName: activePod, }) .then(async (response) => { diff --git a/src/components/NavigationBars/MainNavigationBar/MainNavigationBar.tsx b/src/components/NavigationBars/MainNavigationBar/MainNavigationBar.tsx index 27bf6296..6242c184 100644 --- a/src/components/NavigationBars/MainNavigationBar/MainNavigationBar.tsx +++ b/src/components/NavigationBars/MainNavigationBar/MainNavigationBar.tsx @@ -44,10 +44,9 @@ const MainNavigationBar: FC> = () => {
- {/* TODO: Search functionality should be implemented */} - {/*
+
-
*/} +
void; + directoryOnClick: (directory: DirectoryItem) => void; fileOnClick: (data: FileItem) => void; } @@ -28,7 +28,7 @@ const DriveGridView: FC = ({ size: directory.size, creationTime: (directory.raw as any)?.meta?.creationTime, }} - onClick={() => directoryOnClick(directory.name)} + onClick={() => directoryOnClick(directory)} updateDrive={updateDrive} /> ))} diff --git a/src/components/Views/DriveListView/DriveListView.tsx b/src/components/Views/DriveListView/DriveListView.tsx index a605630c..09dc6f26 100644 --- a/src/components/Views/DriveListView/DriveListView.tsx +++ b/src/components/Views/DriveListView/DriveListView.tsx @@ -11,7 +11,7 @@ import { UpdateDriveProps } from '@interfaces/handlers'; interface DriveListViewProps extends UpdateDriveProps { directories: DirectoryItem[]; files: FileItem[]; - directoryOnClick: (directoryName: string) => void; + directoryOnClick: (directory: DirectoryItem) => void; fileOnClick: (data: FileItem) => void; } @@ -56,7 +56,7 @@ const DriveListView: FC = ({ size: directory.size, creationTime: (directory.raw as any)?.meta?.creationTime, }} - onClick={() => directoryOnClick(directory.name)} + onClick={() => directoryOnClick(directory)} updateDrive={updateDrive} /> ))} diff --git a/src/context/SearchContext.tsx b/src/context/SearchContext.tsx index 6d78b35f..d64d5515 100644 --- a/src/context/SearchContext.tsx +++ b/src/context/SearchContext.tsx @@ -2,7 +2,9 @@ import { FC, ReactNode, createContext, useState } from 'react'; interface SearchContext { search: string; + searchDisabled: boolean; updateSearch: (newSearch: string) => void; + setSearchDisabled: (disabled: boolean) => void; } interface SearchContextProps { @@ -11,14 +13,18 @@ interface SearchContextProps { const searchContextDefaultValues: SearchContext = { search: '', + searchDisabled: false, // eslint-disable-next-line @typescript-eslint/no-empty-function updateSearch: () => {}, + // eslint-disable-next-line @typescript-eslint/no-empty-function + setSearchDisabled: () => {}, }; const SearchContext = createContext(searchContextDefaultValues); const SearchProvider: FC = ({ children }) => { const [search, setSearch] = useState(''); + const [searchDisabled, setSearchDisabled] = useState(false); const updateSearch = (newSearch: string) => { setSearch(newSearch.trim()); @@ -28,7 +34,9 @@ const SearchProvider: FC = ({ children }) => { {children} diff --git a/src/pages/drive/index.tsx b/src/pages/drive/index.tsx index 45e7fc7f..9b92ea72 100644 --- a/src/pages/drive/index.tsx +++ b/src/pages/drive/index.tsx @@ -1,5 +1,5 @@ /* eslint-disable react-hooks/exhaustive-deps */ -import { FC, useContext, useEffect, useState } from 'react'; +import { FC, useContext, useEffect, useRef, useState } from 'react'; import { useMatomo } from '@datapunt/matomo-tracker-react'; import { useFdpStorage } from '@context/FdpStorageContext'; import ThemeContext from '@context/ThemeContext'; @@ -41,6 +41,7 @@ import { } from '@utils/error'; import PodList from '@components/Views/PodList/PodList'; import FeedbackMessage from '@components/FeedbackMessage/FeedbackMessage'; +import { constructPath, rootPathToRelative } from '@utils/filename'; const Drive: FC = () => { const { trackPageView } = useMatomo(); @@ -69,6 +70,7 @@ const Drive: FC = () => { const [driveSort, setDriveSort] = useState('a-z'); const { fdpClientRef, getAccountAddress } = useFdpStorage(); const [error, setError] = useState(null); + const searchControllerRef = useRef(null); useEffect(() => { trackPageView({ @@ -91,6 +93,7 @@ const Drive: FC = () => { return; } setError(null); + updateSearch(''); const userAddress = await getAccountAddress(); const directory = directoryName || 'root'; @@ -187,7 +190,7 @@ const Drive: FC = () => { } }; - const handleDirectoryOnClick = (newDirectoryName: string) => { + const handleDirectoryOnClick = (newDirectory: DirectoryItem) => { if (loading) { return; } @@ -196,10 +199,14 @@ const Drive: FC = () => { setLoading(true); - if (directoryName !== 'root') { - setDirectoryName(directoryName + '/' + newDirectoryName); + if (newDirectory.path) { + setDirectoryName( + rootPathToRelative(constructPath(newDirectory.path, newDirectory.name)) + ); + } else if (directoryName !== 'root') { + setDirectoryName(directoryName + '/' + newDirectory.name); } else { - setDirectoryName(newDirectoryName); + setDirectoryName(newDirectory.name); } setLoading(false); @@ -225,10 +232,6 @@ const Drive: FC = () => { setShowPreviewModal(true); }; - const handleSearchFilter = (driveItem: DirectoryItem | FileItem) => { - return driveItem.name.toLowerCase().includes(search.toLocaleLowerCase()); - }; - const handlePodSelect = (pod: string) => { setError(null); setActivePod(pod); @@ -241,6 +244,94 @@ const Drive: FC = () => { setDirectoryName(''); }; + const handleSearch = async () => { + try { + if (loading || !activePod) { + return; + } + + searchControllerRef.current = new AbortController(); + + setLoading(true); + + let matchedFiles: FileItem[] = []; + let matchedDirectories: DirectoryItem[] = []; + let folders: { path: string; depth: number }[] = [ + { path: '/', depth: 0 }, + ]; + const maxDepth = 3; + + while (folders.length > 0) { + const { path, depth } = folders.shift(); + + let content: DirectoryItem; + try { + content = await getFilesAndDirectories( + fdpClientRef.current, + activePod, + path === '/' ? 'root' : path + ); + + // eslint-disable-next-line no-empty + } catch (error) { + console.error(error); + } + + if (searchControllerRef.current.signal.aborted) { + return; + } + + if (Array.isArray(content?.files)) { + matchedFiles = matchedFiles + .concat(content.files.filter(({ name }) => name.includes(search))) + .map((file) => { + file.path = path; + return file; + }); + } + + if (Array.isArray(content?.directories)) { + matchedDirectories = matchedDirectories.concat( + content.directories + .filter(({ name }) => name.includes(search)) + .map((directory) => { + directory.path = path; + return directory; + }) + ); + + if (depth < maxDepth) { + folders = folders.concat( + content.directories.map(({ name }) => ({ + path: constructPath(path, name), + depth: depth + 1, + })) + ); + } + } + } + + setFiles(matchedFiles); + setDirectories(matchedDirectories); + } catch (error) { + setError(errorToString(error)); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + if (searchControllerRef.current) { + searchControllerRef.current.abort(); + } + + if (search) { + handleSearch(); + } else { + handleUpdateDrive(); + } + }, [search]); + return (
@@ -298,10 +389,8 @@ const Drive: FC = () => {
{driveView === 'grid' ? ( { {driveView === 'list' ? (