From 446f480ec68d35409648a32309d864d23f82cb3f Mon Sep 17 00:00:00 2001 From: Jacob Rief Date: Thu, 28 Nov 2024 09:17:55 +0100 Subject: [PATCH] WiP: infinite scroll and more --- client/browser/FileSelectDialog.tsx | 229 +++++++++++++++------------- client/browser/FinderFileSelect.tsx | 3 + client/browser/FolderStructure.tsx | 26 +++- client/browser/MenuBar.tsx | 13 +- 4 files changed, 148 insertions(+), 123 deletions(-) diff --git a/client/browser/FileSelectDialog.tsx b/client/browser/FileSelectDialog.tsx index abd66f592..d73b9b1fb 100644 --- a/client/browser/FileSelectDialog.tsx +++ b/client/browser/FileSelectDialog.tsx @@ -3,6 +3,7 @@ import React, { lazy, memo, Suspense, + useCallback, useEffect, useImperativeHandle, useMemo, @@ -57,9 +58,9 @@ const ScrollSpy = (props) => { const {fetchFiles} = props; const {ref, inView} = useInView({ triggerOnce: true, - onChange: (loadMore) => { + onChange: async (loadMore) => { if (loadMore && !inView) { - fetchFiles(); + await fetchFiles(); } }, }); @@ -87,14 +88,14 @@ const FilesList = memo((props: any) => { <>{structure.files.map(file => (
  • selectFile(file)}>
  • ))} - {structure.offset !== null && } + {structure.offset !== null && } )} ); }); -const FileSelectDialog = forwardRef((props: any, forwardedRef) => { +const FileSelectDialog = forwardRef(function FileSelectDialog(props: any, forwardedRef) { const {realm, baseUrl, csrfToken} = props; const [structure, setStructure] = useState({ root_folder: null, @@ -108,74 +109,73 @@ const FileSelectDialog = forwardRef((props: any, forwardedRef) => { const ref = useRef(null); const uploaderRef = useRef(null); const [uploadedFile, setUploadedFile] = useState(null); - const dialog = ref.current?.closest('dialog'); + const [currentFolderId, setCurrentFolderId] = useState(null); + const [currentFolderElement, setCurrentFolderElement] = useState(null); + + useImperativeHandle(forwardedRef, () => ({scrollToCurrentFolder, dismissAndClose})); useEffect(() => { - if (!uploadedFile) { + if (structure.root_folder === null) { getStructure(); } - }, [uploadedFile]); - - useImperativeHandle(forwardedRef, () => ({dismissAndClose})); - - const setCurrentFolder = (folderId) => { - setStructure(prevStructure => { - const newStructure = Object.assign(structure, { - ...prevStructure, - last_folder: folderId, - files: [], - offset: null, - recursive: false, - search_query: '', - }); + }, [structure.root_folder]); + + useEffect(() => { + if (currentFolderId && uploadedFile === null) { fetchFiles(); - return newStructure; - }); - }; + } + }, [currentFolderId, uploadedFile]); - async function toggleRecursive(folderId: string) { - setStructure(prevStructure => { - const newStructure = Object.assign(structure, { - ...prevStructure, - last_folder: folderId, - files: [], - offset: null, - recursive: prevStructure.recursive ? false : true, - search_query: '', - }); + useEffect(() => { + if (structure.root_folder) { fetchFiles(); - return newStructure; + } + }, [structure.recursive, structure.search_query]); + + function setCurrentFolder(folderId){ + setCurrentFolderId(folderId); + setStructure({ + ...structure, + last_folder: folderId, + files: [], + offset: null, + recursive: false, + search_query: '', }); } - const setSearchQuery = (query) => { - setStructure(prevStructure => { - const newStructure = Object.assign(structure, { - ...prevStructure, - files: [], - offset: null, - recursive: false, - search_query: query, - }); - fetchFiles(); - return newStructure; + function toggleRecursive(folderId: string) { + setStructure({ + ...structure, + last_folder: folderId, + files: [], + offset: null, + recursive: structure.recursive ? false : true, + search_query: '', }); - }; + } - const refreshFilesList = () => { - setStructure(prevStructure => { - const newStructure = Object.assign(structure, { - root_folder: prevStructure.root_folder, - files: [], - last_folder: prevStructure.last_folder, - offset: null, - search_query: prevStructure.search_query, - labels: prevStructure.labels, - }); - fetchFiles(); - return newStructure; + function setSearchQuery(query) { + setStructure({ + ...structure, + files: [], + offset: null, + recursive: false, + search_query: query, }); - }; + } + + function refreshFilesList(){ + setStructure({ + ...structure, + root_folder: structure.root_folder, + files: [], + last_folder: structure.last_folder, + offset: null, + search_query: structure.search_query, + labels: structure.labels, + }); + } async function getStructure() { const response = await fetch(`${baseUrl}structure/${realm}`); @@ -186,7 +186,7 @@ const FileSelectDialog = forwardRef((props: any, forwardedRef) => { } } - async function fetchFiles() { + const fetchFiles = useCallback(async () => { const fetchUrl = (() => { const params = new URLSearchParams(); if (structure.recursive) { @@ -199,7 +199,7 @@ const FileSelectDialog = forwardRef((props: any, forwardedRef) => { params.set('q', structure.search_query); return `${baseUrl}${structure.last_folder}/search?${params.toString()}`; } - return `${baseUrl}${structure.last_folder}/list?${params.toString()}`; + return `${baseUrl}${structure.last_folder}/list${params.size === 0 ? '' : `?${params.toString()}`}`; })(); const response = await fetch(fetchUrl); if (response.ok) { @@ -212,20 +212,26 @@ const FileSelectDialog = forwardRef((props: any, forwardedRef) => { } else { console.error(response); } - } + }, [structure.last_folder, structure.recursive, structure.search_query, structure.offset]); - function refreshStructure() { - setStructure({...structure}); - } + const refreshStructure = () => setStructure({...structure}); - function handleUpload(folderId, uploadedFiles) { + const handleUpload = (folderId, uploadedFiles) => { + setCurrentFolderId(folderId); setUploadedFile(uploadedFiles[0]); - } + }; - function selectFile(fileInfo) { + const selectFile = useCallback(fileInfo => { props.selectFile(fileInfo); setUploadedFile(null); + refreshFilesList(); props.closeDialog(); + }, []); + + function scrollToCurrentFolder() { + if (currentFolderElement) { + currentFolderElement.scrollIntoView({behavior: 'smooth', block: 'center'}); + } } function dismissAndClose() { @@ -245,53 +251,58 @@ const FileSelectDialog = forwardRef((props: any, forwardedRef) => { }); } setUploadedFile(null); + refreshFilesList(); props.closeDialog(); } + console.log('FileSelectDialog', structure); + return (<>
    {uploadedFile ? - : <> - uploaderRef.current.openUploader()} - labels={structure.labels} - /> -
    - - { - structure.files === null ? -
    {gettext("Loading files…")}
    : - - }
    -
    - } + : <> + uploaderRef.current.openUploader()} + labels={structure.labels} + searchQuery={structure.search_query} + /> +
    + + { + structure.files === null ? +
    {gettext("Loading files…")}
    : + + }
    +
    + }
    diff --git a/client/browser/FolderStructure.tsx b/client/browser/FolderStructure.tsx index d94e96838..767aca852 100644 --- a/client/browser/FolderStructure.tsx +++ b/client/browser/FolderStructure.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {useEffect, useRef} from 'react'; import ArrowDownIcon from '../icons/arrow-down.svg'; import ArrowRightIcon from '../icons/arrow-right.svg'; import EmptyIcon from '../icons/empty.svg'; @@ -8,19 +8,26 @@ import RootIcon from '../icons/root.svg'; function FolderEntry(props) { - const {folder, toggleOpen, setCurrentFolder, openRecursive, isCurrent, isListed} = props; + const {folder, toggleOpen, setCurrentFolder, openRecursive, isCurrent, isListed, setCurrentFolderElement} = props; + const ref = useRef(null); if (folder.is_root) { return ( setCurrentFolder(folder.id)}>); } + useEffect(() => { + if (isCurrent) { + setCurrentFolderElement(ref.current); + } + }, []); + return (<>{ folder.has_subfolders ? { folder.is_open ? : } : } - {isListed || isCurrent ? : } + openRecursive()} role="button">{isListed || isCurrent ? : } {isCurrent - ? {folder.name} + ? {folder.name} : setCurrentFolder(folder.id)} role="button">{folder.name} } ); @@ -28,8 +35,9 @@ function FolderEntry(props) { export default function FolderStructure(props) { - const {baseUrl, folder, lastFolderId, setCurrentFolder, toggleRecursive, refreshStructure} = props; + const {baseUrl, folder, lastFolderId, setCurrentFolder, toggleRecursive, refreshStructure, setCurrentFolderElement} = props; const isListed = props.isListed === false ? lastFolderId === folder.id : props.isListed; + const isCurrent = lastFolderId === folder.id; async function fetchChildren() { const response = await fetch(`${baseUrl}${folder.id}/fetch`); @@ -68,9 +76,9 @@ export default function FolderStructure(props) { await fetch(`${baseUrl}${folder.id}/open`); } } - toggleRecursive(folder.id); + await toggleRecursive(folder.id); } else { - setCurrentFolder(folder.id); + await setCurrentFolder(folder.id); } } @@ -81,8 +89,9 @@ export default function FolderStructure(props) { toggleOpen={toggleOpen} setCurrentFolder={setCurrentFolder} openRecursive={openRecursive} - isCurrent={lastFolderId === folder.id} + isCurrent={isCurrent} isListed={isListed} + setCurrentFolderElement={setCurrentFolderElement} /> {folder.is_open && folder.children && (
      @@ -96,6 +105,7 @@ export default function FolderStructure(props) { toggleRecursive={toggleRecursive} refreshStructure={refreshStructure} isListed={isListed} + setCurrentFolderElement={setCurrentFolderElement} /> ))}
    )} diff --git a/client/browser/MenuBar.tsx b/client/browser/MenuBar.tsx index 26c7e7c42..1a9ee6da7 100644 --- a/client/browser/MenuBar.tsx +++ b/client/browser/MenuBar.tsx @@ -8,14 +8,14 @@ import UploadIcon from '../icons/upload.svg'; export default function MenuBar(props) { - const {refreshFilesList, setSearchQuery, openUploader, labels} = props; + const {refreshFilesList, setSearchQuery, openUploader, labels, searchQuery} = props; const searchRef = useRef(null); const [searchRealm, setSearchRealm] = useSearchRealm('current'); - function handleSearch(event) { - const performSearch = () => { + async function handleSearch(event) { + const performSearch = async () => { const searchQuery = searchRef.current.value || ''; - setSearchQuery(searchQuery); + await setSearchQuery(searchQuery); }; const resetSearch = () => { setSearchQuery(''); @@ -26,10 +26,10 @@ export default function MenuBar(props) { resetSearch(); } else if (event.type === 'keydown' && event.key === 'Enter') { // pressed Enter - searchRef.current.value.length === 0 ? resetSearch() : performSearch(); + searchRef.current.value.length === 0 ? resetSearch() : await performSearch(); } else if (event.type === 'click' && searchRef.current.value.length > 2) { // clicked on the search button - performSearch(); + await performSearch(); } } @@ -52,6 +52,7 @@ export default function MenuBar(props) {