Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sidebar in browser #2

Merged
merged 36 commits into from
Nov 27, 2022
Merged
Changes from 1 commit
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
8ea1b7e
Fixed multi download not appearing correctly
MangoCubes Nov 22, 2022
f161936
Added caching for FileBrowser
MangoCubes Nov 22, 2022
870884f
Added separate sidebar for filebrowser
MangoCubes Nov 22, 2022
72457ef
Stateful cache
MangoCubes Nov 22, 2022
741977e
Cache is used as primary item storage instead
MangoCubes Nov 22, 2022
826a20f
Starting work on filebrowser tree
MangoCubes Nov 23, 2022
324edfe
Empty cache container is added for each child directories
MangoCubes Nov 23, 2022
179b11c
Items on sidebar loads if it is not already
MangoCubes Nov 23, 2022
ed2062c
Added optional controller to make sidebar tree independent from main …
MangoCubes Nov 23, 2022
adb529e
Cache will be used unless explicitly refreshed
MangoCubes Nov 23, 2022
1fa8f2a
Fixed cache being invalidated upon going to parent folder
MangoCubes Nov 23, 2022
934bb20
Moved code into the scope where it matters
MangoCubes Nov 23, 2022
65a90f8
Merged two boolean states into one enum
MangoCubes Nov 25, 2022
aee848f
Failsafe when items is empty
MangoCubes Nov 25, 2022
ed84c71
Concurrency handling
MangoCubes Nov 25, 2022
e7762ca
Moved types for reuse
MangoCubes Nov 25, 2022
d87a47d
Removed potential invalid states
MangoCubes Nov 25, 2022
d761aaf
Applied similar method to vault browser
MangoCubes Nov 25, 2022
d947396
Nonexistent record is correctly assumed as not started
MangoCubes Nov 25, 2022
f42640f
Starting work on vault sidebar
MangoCubes Nov 25, 2022
137a4c8
Added sidebar back into vault browser
MangoCubes Nov 25, 2022
271c72d
Loading dir id happens after loading screen is shown
MangoCubes Nov 26, 2022
d4782c5
Buttons are disabled properly during loading screen
MangoCubes Nov 26, 2022
0a29e1b
Applying similar approach on FileBrowser for simpler code
MangoCubes Nov 26, 2022
5d1abe5
Fixed contents going out of sidebar's intended size
MangoCubes Nov 26, 2022
55390f1
Experimental way of handling async id fetching
MangoCubes Nov 26, 2022
2ba62b7
Fixed wrong id being passed
MangoCubes Nov 26, 2022
1af5810
Decrypted name is displayed instead of placeholder
MangoCubes Nov 26, 2022
4133119
Added placeholder if there are no subdirs
MangoCubes Nov 27, 2022
a364c44
Fixed missing unique key
MangoCubes Nov 27, 2022
9537277
Fixed text going over sidebar
MangoCubes Nov 27, 2022
8325561
Reusing component for both sidebars
MangoCubes Nov 27, 2022
4806381
Browser control separated from querying
MangoCubes Nov 27, 2022
0f84220
Minor bug fixes
MangoCubes Nov 27, 2022
184966f
Previous items will not be discarded during query
MangoCubes Nov 27, 2022
ec45fb9
Functional reload button
MangoCubes Nov 27, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Applied similar method to vault browser
  • Loading branch information
MangoCubes committed Nov 25, 2022
commit d761aafc9f9f022ddc54d423f40fda8750c84d11
150 changes: 99 additions & 51 deletions src/web/vaultBrowser/VaultBrowser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,39 @@ import { ArrowBack, Article, Delete, Download, Folder, Refresh } from "@mui/icon
import { Box, AppBar, Toolbar, Typography, Tooltip, IconButton } from "@mui/material";
import { DataGrid, GridActionsCellItem, GridRenderCellParams, GridRowParams, GridSelectionModel } from "@mui/x-data-grid";
import { DirID, EncryptedDir, EncryptedItem, ItemPath, Vault } from "cryptomator-ts";
import { useEffect, useMemo, useState } from "react";
import { useEffect, useMemo, useRef, useState } from "react";
import { WebDAV } from "../../lib/cryptomator/WebDAV";
import { DirCache, ExpStatus } from "../../types/types";
import { ItemDownloader } from "../ItemDownloader";

enum Querying {
/**
* Disable nothing
*/
None,
/**
* Show items, but disallow changing dir and interacting with buttons
*/
Partial,
/**
* Hide items and show loading icon instead
*/
Full
}

type DirInfo = {
name: string;
id: DirID;
};

/**
* The following approach is used for this component:
* Encrypted directory can be loaded independently from current directory
* If the changed directory is not loaded, it will trigger loading
* Otherwise, it uses cache
* Force reload will always bypass and override cache
*/

export function VaultBrowser(props: {
vault: Vault,
client: WebDAV,
Expand All @@ -14,10 +43,12 @@ export function VaultBrowser(props: {
openDownloads: () => void
}){

const [dir, setDir] = useState<EncryptedDir[]>([]);
const [items, setItems] = useState<EncryptedItem[]>([]);
const [querying, setQuerying] = useState(false);
const [dir, setDir] = useState<DirInfo[]>([]);
const [items, setItems] = useState<DirCache<EncryptedItem>>({});
const [querying, setQuerying] = useState<Querying>(Querying.None);
const [sel, setSel] = useState<GridSelectionModel>([]);

const itemsCache = useRef<DirCache<EncryptedItem>>({'': {explored: ExpStatus.NotStarted}});

const columns = useMemo(() => [
{field: 'type', headerName: '', width: 24, renderCell: (params: GridRenderCellParams<string>) => {
Expand All @@ -39,67 +70,84 @@ export function VaultBrowser(props: {
], [props.download]);

const rows = useMemo(() => {
if (querying) return [];
else {
const rows = [];
if(dir.length){
rows.push(
{
id: 'parent',
name: 'Up one level',
type: 'parent'
}
);
}
for(const item of items){
rows.push({
id: item.fullName,
name: item.decryptedName,
type: item.type,
obj: item
});
}
return rows;
const rows = [];
if(dir.length){
rows.push(
{
id: 'parent',
name: 'Up one level',
type: 'parent'
}
);
}
}, [querying]);

useEffect(() => {
loadItems();
}, [dir]);

useEffect(() => {
console.log(items);
const currentDirId = dir.length === 0 ? '' as DirID : dir[dir.length - 1].id;
const currentItems = items[currentDirId];
if(currentItems?.explored !== ExpStatus.Ready) return [];
for(const item of currentItems.child){
rows.push({
id: item.fullName,
name: item.decryptedName,
type: item.type,
obj: item
});
}
return rows;
}, [items]);

const loadItems = async () => {
const temp = [...items];
const saveItems = (data: DirCache<EncryptedItem>) => {
for(const k in data) itemsCache.current[k] = data[k];
setItems({...itemsCache.current});
}

const loadItems = async (dirId: DirID, bypassCache?: boolean) => {
const temp = itemsCache.current[dirId] ?? {
child: [],
explored: ExpStatus.NotStarted
};
if(temp.explored === ExpStatus.Ready && !bypassCache) return;
try {
setQuerying(true);
setItems([]);
const newItems = await props.vault.listItems(dir.length === 0 ? '' as DirID : await dir[dir.length - 1].getDirId());
setItems(newItems);
const update: DirCache<EncryptedItem> = {};
update[dirId] = {explored: ExpStatus.Querying};
saveItems(update);
const newItems = await props.vault.listItems(dirId);
update[dirId] = {
child: newItems,
explored: ExpStatus.Ready
}
saveItems(update);
} catch(e) {
setItems(temp);
} finally {
setQuerying(false);
const delta: DirCache<EncryptedItem> = {};
delta[dirId] = {explored: ExpStatus.Error};
saveItems(delta);
}
}

const onRowClick = (r: GridRowParams) => {
if(r.row.type === 'parent') loadSubDir(null);
else if(r.row.type === 'd') loadSubDir(r.row.obj);
const changeDir = async (subDir: DirInfo | null) => {
setQuerying(Querying.Full);
if (subDir === null) {
const newDir = dir.slice(0, -1);
await loadItems(dir[dir.length - 1].id);
setDir(newDir);
} else {
await loadItems(subDir.id);
setDir([...dir, subDir]);
}
setQuerying(Querying.None);
}

const loadSubDir = async (subDir: EncryptedDir | null) => {
if (subDir === null) setDir(dir.slice(0, -1));
else setDir([...dir, subDir]);
const onRowClick = async (r: GridRowParams) => {
if(r.row.type === 'parent') changeDir(null);
else if(r.row.type === 'd') changeDir({
name: (r.row.obj as EncryptedDir).decryptedName,
id: await (r.row.obj as EncryptedDir).getDirId()
});
}

return (
<Box sx={{display: 'flex', flexDirection: 'column', height: '100%', flex: 1}}>
<AppBar position='static'>
<Toolbar>
<Typography variant='h5'>{`${[props.vault.name]}: ${dir.length === 0 ? 'Root' : dir[dir.length - 1].decryptedName}`}</Typography>
<Typography variant='h5'>{`${[props.vault.name]}: ${dir.length === 0 ? 'Root' : dir[dir.length - 1].name}`}</Typography>
<Box sx={{flex: 1}}/>
<Tooltip title='Refresh'>
<span>
Expand All @@ -117,11 +165,11 @@ export function VaultBrowser(props: {
isRowSelectable={(params: GridRowParams) => params.row.type !== 'parent'}
columns={columns}
rows={rows}
loading={querying}
loading={querying === Querying.Full}
checkboxSelection
selectionModel={sel}
onSelectionModelChange={items => {
if(!querying) setSel(items);
if(querying !== Querying.None) setSel(items);
}}
/>
</Box>
Expand Down