Improve folders

This commit is contained in:
Arnaud 2024-10-18 17:02:42 +02:00
parent c70376bfcb
commit 333aeb1570
No known key found for this signature in database
GPG Key ID: 69D6CE281FCAE663
4 changed files with 164 additions and 85 deletions

View File

@ -0,0 +1,49 @@
import { ButtonIcon, Cell } from "@codex-storage/marketplace-ui-components";
import { Download, ReceiptText } from "lucide-react";
import { ICON_SIZE } from "../../utils/constants";
import { FolderButton } from "./FolderButton";
import { CodexDataContent } from "@codex-storage/sdk-js";
import { CodexSdk } from "../../sdk/codex";
type Props = {
content: CodexDataContent;
folders: [string, string[]][];
onFolderToggle: (cid: string, folder: string) => void;
onDetails: (cid: string) => void;
};
export function FileActions({
content,
folders,
onFolderToggle,
onDetails,
}: Props) {
const url = CodexSdk.url() + "/api/codex/v1/data/";
return (
<Cell>
<div className="files-fileActions">
<ButtonIcon
variant="small"
animation="bounce"
onClick={() => window.open(url + content.cid, "_blank")}
Icon={(props) => (
<Download size={ICON_SIZE} {...props} />
)}></ButtonIcon>
<FolderButton
folders={folders.map(([folder, files]) => [
folder,
files.includes(content.cid),
])}
onFolderToggle={(folder) => onFolderToggle(content.cid, folder)}
/>
<ButtonIcon
variant="small"
onClick={() => onDetails(content.cid)}
Icon={() => <ReceiptText size={ICON_SIZE} />}></ButtonIcon>
</div>
</Cell>
);
}

View File

@ -0,0 +1,35 @@
import {
ButtonIcon,
Cell,
WebFileIcon,
} from "@codex-storage/marketplace-ui-components";
import { CodexDataContent } from "@codex-storage/sdk-js";
import { Copy } from "lucide-react";
type Props = {
content: CodexDataContent;
};
export function FileCell({ content }: Props) {
const onCopy = (cid: string) => navigator.clipboard.writeText(cid);
return (
<Cell>
<div className="files-cell-file">
<WebFileIcon type={content.manifest.mimetype} />
<div>
<b>{content.manifest.filename}</b>
<div>
<small className="files-fileMeta">{content.cid}</small>
<ButtonIcon
variant="small"
onClick={() => onCopy(content.cid)}
animation="buzz"
Icon={(props) => <Copy size={"1rem"} {...props} />}></ButtonIcon>
</div>
</div>
</div>
</Cell>
);
}

View File

@ -0,0 +1,44 @@
import { CodexDataContent } from "@codex-storage/sdk-js";
import { Files } from "../../utils/files";
import { classnames } from "../../utils/classnames";
import { Check } from "lucide-react";
type Props = {
files: CodexDataContent[];
onFilterToggle: (filter: string) => void;
};
const archiveMimetypes = [
"application/zip",
"application/x-rar-compressed",
"application/x-tar",
"application/gzip",
"application/x-7z-compressed",
"application/gzip", // for .tar.gz
"application/x-bzip2",
"application/x-xz",
];
export function FilterFilters({ files, onFilterToggle }: Props) {
const filters = Array.from(
new Set(
files.map((file) =>
archiveMimetypes.includes(file.manifest.mimetype)
? "archive"
: Files.type(file.manifest.mimetype)
)
)
);
return filters.map((type) => (
<span
key={type}
className={classnames(
["files-filter"],
["files-filter--active", filters.includes(type)]
)}
onClick={() => onFilterToggle(type)}>
<span>{type}</span> <Check size={"1rem"}></Check>
</span>
));
}

View File

@ -1,21 +1,9 @@
import {
Check,
Copy,
Download,
FilesIcon,
Folder,
Plus,
ReceiptText,
X,
} from "lucide-react";
import { FilesIcon, Folder, Plus, X } from "lucide-react";
import { ChangeEvent, useEffect, useState } from "react";
import { PrettyBytes } from "../../utils/bytes";
import { Dates } from "../../utils/dates";
import "./Files.css";
import { ICON_SIZE } from "../../utils/constants";
import {
ButtonIcon,
WebFileIcon,
Tabs,
Input,
Button,
@ -23,15 +11,17 @@ import {
Table,
Row,
Cell,
TabSortState,
} from "@codex-storage/marketplace-ui-components";
import { FileDetails } from "./FileDetails.tsx";
import { useData } from "../../hooks/useData.tsx";
import { WebStorage } from "../../utils/web-storage.ts";
import { CodexSdk } from "../../sdk/codex.ts";
import { FolderButton } from "./FolderButton.tsx";
import { classnames } from "../../utils/classnames.ts";
import { CodexDataContent } from "@codex-storage/sdk-js";
import { Files as F } from "../../utils/files.ts";
import { FilterFilters } from "./FileFilters.tsx";
import { FileCell } from "./FileCell.tsx";
import { FileActions } from "./FileActions.tsx";
type SortFn = (a: CodexDataContent, b: CodexDataContent) => number;
@ -49,9 +39,7 @@ export function Files() {
WebStorage.folders.list().then((items) => setFolders(items));
}, []);
const onClose = () => {
setDetails(null);
};
const onClose = () => setDetails(null);
const onTabChange = async (i: number) => setIndex(i);
@ -154,7 +142,25 @@ export function Files() {
),
}));
const onSortBySize = (state: "" | "asc" | "desc") => {
const onSortByFilename = (state: TabSortState) => {
if (!state) {
setSortFn(null);
return;
}
setSortFn(
() => (a: CodexDataContent, b: CodexDataContent) =>
state === "desc"
? b.manifest.filename
.toLocaleLowerCase()
.localeCompare(a.manifest.filename.toLocaleLowerCase())
: a.manifest.filename
.toLocaleLowerCase()
.localeCompare(b.manifest.filename.toLocaleLowerCase())
);
};
const onSortBySize = (state: TabSortState) => {
if (!state) {
setSortFn(null);
return;
@ -168,7 +174,7 @@ export function Files() {
);
};
const onSortByDate = (state: "" | "asc" | "desc") => {
const onSortByDate = (state: TabSortState) => {
if (!state) {
setSortFn(null);
return;
@ -189,8 +195,6 @@ export function Files() {
? setFilters(filters.filter((f) => f !== filter))
: setFilters([...filters, filter]);
const onCopy = (cid: string) => navigator.clipboard.writeText(cid);
tabs.unshift({
label: "All files",
Icon: () => <FilesIcon size={"1rem"}></FilesIcon>,
@ -201,18 +205,13 @@ export function Files() {
? files
: files.filter((file) => folders[index - 1][1].includes(file.cid));
const url = CodexSdk.url() + "/api/codex/v1/data/";
const headers = [
["file"],
["file", onSortByFilename],
["size", onSortBySize],
["date", onSortByDate],
["actions"],
];
] satisfies [string, ((state: TabSortState) => void)?][];
const types = Array.from(
new Set(files.map((file) => F.type(file.manifest.mimetype)))
);
const filtered = items.filter(
(item) =>
filters.length === 0 || filters.includes(F.type(item.manifest.mimetype))
@ -223,52 +222,14 @@ export function Files() {
sorted.map((c) => (
<Row
cells={[
<Cell>
<div className="files-cell-file">
<WebFileIcon type={c.manifest.mimetype} />
<div>
<b>{c.manifest.filename}</b>
<div>
<small className="files-fileMeta">{c.cid}</small>
</div>
</div>
</div>
</Cell>,
<FileCell content={c}></FileCell>,
<Cell>{PrettyBytes(c.manifest.datasetSize)}</Cell>,
<Cell>{Dates.format(c.manifest.uploadedAt).toString()}</Cell>,
<Cell>
<div className="files-fileActions">
<ButtonIcon
variant="small"
animation="bounce"
onClick={() => window.open(url + c.cid, "_blank")}
Icon={(props) => (
<Download size={ICON_SIZE} {...props} />
)}></ButtonIcon>
<FolderButton
folders={folders.map(([folder, files]) => [
folder,
files.includes(c.cid),
])}
onFolderToggle={(folder) => onFolderToggle(c.cid, folder)}
/>
<ButtonIcon
variant="small"
onClick={() => onCopy(c.cid)}
animation="buzz"
Icon={(props) => (
<Copy size={ICON_SIZE} {...props} />
)}></ButtonIcon>
<ButtonIcon
variant="small"
onClick={() => onDetails(c.cid)}
Icon={() => <ReceiptText size={ICON_SIZE} />}></ButtonIcon>
</div>
</Cell>,
<FileActions
content={c}
folders={folders}
onDetails={onDetails}
onFolderToggle={onFolderToggle}></FileActions>,
]}></Row>
)) || [];
@ -303,21 +264,11 @@ export function Files() {
<Tabs onTabChange={onTabChange} tabIndex={index} tabs={tabs}></Tabs>
<div className="files-filters">
{types.map((type) => (
<span
key={type}
className={classnames(
["files-filter"],
["files-filter--active", filters.includes(type)]
)}
onClick={() => onToggleFilter(type)}>
<span>{type}</span> <Check size={"1rem"}></Check>
</span>
))}
<FilterFilters files={files} onFilterToggle={onToggleFilter} />
</div>
<div className="files-fileBody">
<Table headers={headers as any} rows={rows} />
<Table headers={headers} rows={rows} defaultSortIndex={3} />
</div>
<FileDetails onClose={onClose} details={details} />