Skip to content

Commit 6c655c9

Browse files
committed
WiP: infinite scroll and more
1 parent 942a1eb commit 6c655c9

File tree

5 files changed

+170
-150
lines changed

5 files changed

+170
-150
lines changed

client/browser/FileSelectDialog.tsx

Lines changed: 130 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import React, {
33
lazy,
44
memo,
55
Suspense,
6+
useCallback,
67
useEffect,
78
useImperativeHandle,
89
useMemo,
@@ -12,7 +13,8 @@ import React, {
1213
import {useInView} from 'react-intersection-observer';
1314
import {Tooltip} from 'react-tooltip';
1415
import FigureLabels from '../common/FigureLabels';
15-
import FileUploader from '../common/FileUploader';
16+
import FileUploader from '../common/FileUploader';
17+
import {useSearchRealm} from '../common/Storage';
1618
import BrowserEditor from './BrowserEditor';
1719
import FolderStructure from './FolderStructure';
1820
import MenuBar from './MenuBar';
@@ -53,32 +55,27 @@ function Figure(props) {
5355
}
5456

5557

56-
const ScrollSpy = (props) => {
57-
const {fetchFiles} = props;
58+
function ScrollSpy(props) {
59+
const {setDirty} = props;
5860
const {ref, inView} = useInView({
5961
triggerOnce: true,
60-
onChange: (loadMore) => {
62+
onChange: async (loadMore) => {
6163
if (loadMore && !inView) {
62-
fetchFiles();
64+
setDirty(true);
6365
}
6466
},
6567
});
6668
console.log('ScrollSpy', inView);
67-
if (inView) {
68-
console.log('already visible');
69-
fetchFiles();
70-
}
71-
7269
return (
7370
<div className="scroll-spy" ref={ref}></div>
7471
);
75-
};
72+
}
7673

7774

7875
const FilesList = memo((props: any) => {
79-
const {structure, fetchFiles, selectFile} = props;
76+
const {structure, setDirty, selectFile} = props;
8077

81-
console.log('FolderList', structure);
78+
console.log('FilesList', structure);
8279

8380
return (
8481
<ul className="files-browser">{
@@ -87,14 +84,14 @@ const FilesList = memo((props: any) => {
8784
<>{structure.files.map(file => (
8885
<li key={file.id} onClick={() => selectFile(file)}><Figure {...file} /></li>
8986
))}
90-
{structure.offset !== null && <ScrollSpy fetchFiles={fetchFiles} />}
87+
{structure.offset !== null && <ScrollSpy key={structure.offset} setDirty={setDirty} />}
9188
</>
9289
)}</ul>
9390
);
9491
});
9592

9693

97-
const FileSelectDialog = forwardRef((props: any, forwardedRef) => {
94+
const FileSelectDialog = forwardRef((props: any, forwardedRef)=> {
9895
const {realm, baseUrl, csrfToken} = props;
9996
const [structure, setStructure] = useState({
10097
root_folder: null,
@@ -105,79 +102,82 @@ const FileSelectDialog = forwardRef((props: any, forwardedRef) => {
105102
search_query: '',
106103
labels: [],
107104
});
105+
const [isDirty, setDirty] = useState(false);
108106
const ref = useRef(null);
109107
const uploaderRef = useRef(null);
108+
const menuBarRef = useRef(null);
110109
const [uploadedFile, setUploadedFile] = useState(null);
111-
const dialog = ref.current?.closest('dialog');
110+
const [currentFolderElement, setCurrentFolderElement] = useState(null);
111+
const [searchRealm, setSearchRealm] = useSearchRealm('current');
112+
113+
useImperativeHandle(forwardedRef, () => ({scrollToCurrentFolder, dismissAndClose}));
112114

113115
useEffect(() => {
114-
if (!uploadedFile) {
115-
getStructure();
116+
if (structure.root_folder === null) {
117+
initializeStructure();
118+
} else if (isDirty) {
119+
fetchFiles();
116120
}
117-
}, [uploadedFile]);
118-
119-
useImperativeHandle(forwardedRef, () => ({dismissAndClose}));
121+
}, [isDirty]);
120122

121-
const setCurrentFolder = (folderId) => {
122-
setStructure(prevStructure => {
123-
const newStructure = Object.assign(structure, {
124-
...prevStructure,
125-
last_folder: folderId,
126-
files: [],
127-
offset: null,
128-
recursive: false,
129-
search_query: '',
130-
});
131-
fetchFiles();
132-
return newStructure;
123+
function setCurrentFolder(folderId){
124+
setStructure({
125+
...structure,
126+
last_folder: folderId,
127+
files: [],
128+
offset: null,
129+
recursive: false,
130+
search_query: '',
133131
});
134-
};
132+
setDirty(true);
133+
menuBarRef.current.clearSearch();
134+
}
135135

136-
async function toggleRecursive(folderId: string) {
137-
setStructure(prevStructure => {
138-
const newStructure = Object.assign(structure, {
139-
...prevStructure,
140-
last_folder: folderId,
141-
files: [],
142-
offset: null,
143-
recursive: prevStructure.recursive ? false : true,
144-
search_query: '',
145-
});
146-
fetchFiles();
147-
return newStructure;
136+
function refreshFilesList() {
137+
setStructure({
138+
...structure,
139+
files: [],
140+
offset: null,
148141
});
142+
setDirty(true);
149143
}
150144

151-
const setSearchQuery = (query) => {
152-
setStructure(prevStructure => {
153-
const newStructure = Object.assign(structure, {
154-
...prevStructure,
145+
function changeSearchRealm(value) {
146+
if (value !== searchRealm) {
147+
setSearchRealm(value);
148+
setStructure({
149+
...structure,
155150
files: [],
156151
offset: null,
157-
recursive: false,
158-
search_query: query,
159152
});
160-
fetchFiles();
161-
return newStructure;
153+
setDirty(true);
154+
}
155+
}
156+
157+
function toggleRecursive(folderId: string) {
158+
setStructure({
159+
...structure,
160+
last_folder: folderId,
161+
files: [],
162+
offset: null,
163+
recursive: structure.recursive ? false : true,
162164
});
163-
};
165+
setDirty(true);
166+
}
164167

165-
const refreshFilesList = () => {
166-
setStructure(prevStructure => {
167-
const newStructure = Object.assign(structure, {
168-
root_folder: prevStructure.root_folder,
169-
files: [],
170-
last_folder: prevStructure.last_folder,
171-
offset: null,
172-
search_query: prevStructure.search_query,
173-
labels: prevStructure.labels,
174-
});
175-
fetchFiles();
176-
return newStructure;
168+
function setSearchQuery(query) {
169+
setStructure({
170+
...structure,
171+
files: [],
172+
offset: null,
173+
recursive: false,
174+
search_query: query,
177175
});
178-
};
176+
setDirty(true);
177+
}
179178

180-
async function getStructure() {
179+
async function initializeStructure() {
180+
setDirty(false);
181181
const response = await fetch(`${baseUrl}structure/${realm}`);
182182
if (response.ok) {
183183
setStructure(await response.json());
@@ -199,7 +199,7 @@ const FileSelectDialog = forwardRef((props: any, forwardedRef) => {
199199
params.set('q', structure.search_query);
200200
return `${baseUrl}${structure.last_folder}/search?${params.toString()}`;
201201
}
202-
return `${baseUrl}${structure.last_folder}/list?${params.toString()}`;
202+
return `${baseUrl}${structure.last_folder}/list${params.size === 0 ? '' : `?${params.toString()}`}`;
203203
})();
204204
const response = await fetch(fetchUrl);
205205
if (response.ok) {
@@ -209,23 +209,29 @@ const FileSelectDialog = forwardRef((props: any, forwardedRef) => {
209209
files: structure.files.concat(body.files),
210210
offset: body.offset,
211211
});
212+
setDirty(false);
212213
} else {
213214
console.error(response);
214215
}
215216
}
216217

217-
function refreshStructure() {
218-
setStructure({...structure});
219-
}
220-
221-
function handleUpload(folderId, uploadedFiles) {
218+
function handleUpload(folderId, uploadedFiles){
219+
if (structure.last_folder !== folderId)
220+
throw new Error('Folder mismatch');
222221
setUploadedFile(uploadedFiles[0]);
223222
}
224223

225-
function selectFile(fileInfo) {
224+
const selectFile = useCallback(fileInfo => {
226225
props.selectFile(fileInfo);
227226
setUploadedFile(null);
227+
refreshFilesList();
228228
props.closeDialog();
229+
}, []);
230+
231+
function scrollToCurrentFolder() {
232+
if (currentFolderElement) {
233+
currentFolderElement.scrollIntoView({behavior: 'smooth', block: 'center'});
234+
}
229235
}
230236

231237
function dismissAndClose() {
@@ -248,50 +254,57 @@ const FileSelectDialog = forwardRef((props: any, forwardedRef) => {
248254
props.closeDialog();
249255
}
250256

257+
console.log('FileSelectDialog', isDirty, structure);
258+
251259
return (<>
252260
<div className="wrapper" ref={ref}>
253261
{uploadedFile ?
254-
<BrowserEditor
255-
uploadedFile={uploadedFile}
256-
mainContent={ref.current}
257-
settings={{csrfToken, baseUrl, selectFile, dismissAndClose, labels: structure.labels}}
258-
/> : <>
259-
<MenuBar
260-
refreshFilesList={refreshFilesList}
261-
setSearchQuery={setSearchQuery}
262-
openUploader={() => uploaderRef.current.openUploader()}
263-
labels={structure.labels}
264-
/>
265-
<div className="browser-body">
266-
<nav className="folder-structure">
267-
<ul role="navigation">
268-
{structure.root_folder && <FolderStructure
269-
baseUrl={baseUrl}
270-
folder={structure.root_folder}
271-
lastFolderId={structure.last_folder}
272-
setCurrentFolder={setCurrentFolder}
273-
toggleRecursive={toggleRecursive}
274-
refreshStructure={refreshStructure}
275-
isListed={structure.recursive ? false : null}
276-
/>}
277-
</ul>
278-
</nav>
279-
<FileUploader
280-
folderId={structure.last_folder}
281-
handleUpload={handleUpload}
282-
ref={uploaderRef}
283-
settings={{csrf_token: csrfToken, base_url: baseUrl}}
284-
>{
285-
structure.files === null ?
286-
<div className="status">{gettext("Loading files…")}</div> :
287-
<FilesList
288-
structure={structure}
289-
fetchFiles={fetchFiles}
290-
selectFile={selectFile}
291-
/>
292-
}</FileUploader>
293-
</div>
294-
</>}
262+
<BrowserEditor
263+
uploadedFile={uploadedFile}
264+
mainContent={ref.current}
265+
settings={{csrfToken, baseUrl, selectFile, dismissAndClose, labels: structure.labels}}
266+
/> : <>
267+
<MenuBar
268+
ref={menuBarRef}
269+
openUploader={() => uploaderRef.current.openUploader()}
270+
labels={structure.labels}
271+
refreshFilesList={refreshFilesList}
272+
setDirty={setDirty}
273+
setSearchQuery={setSearchQuery}
274+
searchRealm={searchRealm}
275+
setSearchRealm={changeSearchRealm}
276+
/>
277+
<div className="browser-body">
278+
<nav className="folder-structure">
279+
<ul role="navigation">
280+
{structure.root_folder && <FolderStructure
281+
baseUrl={baseUrl}
282+
folder={structure.root_folder}
283+
lastFolderId={structure.last_folder}
284+
setCurrentFolder={setCurrentFolder}
285+
toggleRecursive={toggleRecursive}
286+
refreshStructure={() => setStructure({...structure})}
287+
isListed={structure.recursive ? false : null}
288+
setCurrentFolderElement={setCurrentFolderElement}
289+
/>}
290+
</ul>
291+
</nav>
292+
<FileUploader
293+
folderId={structure.last_folder}
294+
handleUpload={handleUpload}
295+
ref={uploaderRef}
296+
settings={{csrf_token: csrfToken, base_url: baseUrl}}
297+
>{
298+
structure.files === null ?
299+
<div className="status">{gettext("Loading files…")}</div> :
300+
<FilesList
301+
structure={structure}
302+
setDirty={setDirty}
303+
selectFile={selectFile}
304+
/>
305+
}</FileUploader>
306+
</div>
307+
</>}
295308
</div>
296309
<div
297310
className="close-button"

client/browser/FinderFileSelect.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export default function FinderFileSelect(props) {
4545

4646
function openDialog() {
4747
dialogRef.current.showModal();
48+
selectRef.current.scrollToCurrentFolder();
4849
}
4950

5051
function removeFile() {
@@ -70,6 +71,8 @@ export default function FinderFileSelect(props) {
7071
return date.toLocaleString();
7172
}
7273

74+
console.log('FinderFileSelect', selectedFile);
75+
7376
return (<>
7477
<slot ref={slotRef} />
7578
<div className="finder-file-select">

0 commit comments

Comments
 (0)