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' ? (