289 lines
7.7 KiB
TypeScript
Raw Normal View History

2024-10-18 17:02:42 +02:00
import { FilesIcon, Folder, Plus, X } from "lucide-react";
2024-10-16 10:12:36 +02:00
import { ChangeEvent, useEffect, useState } from "react";
2024-09-16 12:38:56 +02:00
import { PrettyBytes } from "../../utils/bytes";
import { Dates } from "../../utils/dates";
2024-08-29 18:45:52 +02:00
import "./Files.css";
import {
Tabs,
2024-10-10 10:31:30 +02:00
Input,
Button,
TabProps,
2024-10-16 10:12:36 +02:00
Table,
Row,
Cell,
2024-10-18 17:02:42 +02:00
TabSortState,
2024-08-30 17:26:27 +02:00
} from "@codex-storage/marketplace-ui-components";
2024-08-29 18:45:52 +02:00
import { FileDetails } from "./FileDetails.tsx";
import { useData } from "../../hooks/useData.tsx";
2024-10-10 10:31:30 +02:00
import { WebStorage } from "../../utils/web-storage.ts";
import { classnames } from "../../utils/classnames.ts";
2024-10-16 10:12:36 +02:00
import { CodexDataContent } from "@codex-storage/sdk-js";
import { Files as F } from "../../utils/files.ts";
2024-10-18 17:02:42 +02:00
import { FilterFilters } from "./FileFilters.tsx";
import { FileCell } from "./FileCell.tsx";
import { FileActions } from "./FileActions.tsx";
2024-08-29 18:45:52 +02:00
2024-10-16 10:12:36 +02:00
type SortFn = (a: CodexDataContent, b: CodexDataContent) => number;
2024-08-29 18:45:52 +02:00
export function Files() {
const files = useData();
const [index, setIndex] = useState(0);
2024-10-10 10:31:30 +02:00
const [folder, setFolder] = useState("");
const [folders, setFolders] = useState<[string, string[]][]>([]);
const [error, setError] = useState("");
2024-10-16 10:12:36 +02:00
const [details, setDetails] = useState<CodexDataContent | null>(null);
const [sortFn, setSortFn] = useState<SortFn | null>(null);
2024-10-21 16:28:35 +02:00
const [selectedFilters, setSelectedFilters] = useState<string[]>([]);
2024-08-29 18:45:52 +02:00
useEffect(() => {
2024-10-10 10:31:30 +02:00
WebStorage.folders.list().then((items) => setFolders(items));
2024-08-29 18:45:52 +02:00
}, []);
2024-10-18 17:02:42 +02:00
const onClose = () => setDetails(null);
2024-08-29 18:45:52 +02:00
2024-10-10 10:31:30 +02:00
const onTabChange = async (i: number) => setIndex(i);
2024-08-29 18:45:52 +02:00
2024-10-16 10:12:36 +02:00
const onDetails = (cid: string) => {
const d = files.find((file) => file.cid === cid);
2024-08-29 18:45:52 +02:00
2024-10-16 10:12:36 +02:00
if (d) {
setDetails(d);
}
};
2024-10-10 10:31:30 +02:00
const onFolderChange = (e: ChangeEvent<HTMLInputElement>) => {
const val = e.currentTarget.value;
setFolder(val);
setError("");
if (!val) {
return;
}
if (e.currentTarget.checkValidity()) {
if (folders.length >= 5) {
setError("5 folders limit reached");
return;
}
2024-10-15 12:20:40 +02:00
if (folders.find(([folder]) => folder === val)) {
setError("This folder already exists");
return;
}
2024-08-29 18:45:52 +02:00
} else {
2024-10-10 10:31:30 +02:00
setError("9 alpha characters maximum");
2024-08-29 18:45:52 +02:00
}
};
2024-10-10 10:31:30 +02:00
const onFolderCreate = () => {
WebStorage.folders.create(folder);
2024-08-29 18:45:52 +02:00
2024-10-10 10:31:30 +02:00
setFolder("");
setFolders([...folders, [folder, []]]);
};
const onFolderDelete = (val: string) => {
WebStorage.folders.delete(val);
const currentIndex = folders.findIndex(([name]) => name === val);
if (currentIndex + 1 == index) {
setIndex(index - 1);
}
setFolders(folders.filter(([name]) => name !== val));
};
const onFolderToggle = (cid: string, folder: string) => {
const current = folders.find(([name]) => name === folder);
if (!current) {
return;
}
const [, files] = current;
if (files.includes(cid)) {
WebStorage.folders.deleteFile(folder, cid);
setFolders(
folders.map(([name, files]) =>
name === folder
? [name, files.filter((id) => id !== cid)]
: [name, files]
)
);
} else {
WebStorage.folders.addFile(folder, cid);
setFolders(
folders.map(([name, files]) =>
name === folder ? [name, [...files, cid]] : [name, files]
)
);
}
};
const tabs: TabProps[] = folders.map(([folder]) => ({
label: folder,
Icon: () => <Folder size={"1rem"}></Folder>,
2024-10-15 12:20:40 +02:00
IconAfter:
folder === "Favorites"
? undefined
: () => (
<X
size={"1rem"}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
onFolderDelete(folder);
}}></X>
),
2024-10-10 10:31:30 +02:00
}));
2024-10-18 17:02:42 +02:00
const onSortByFilename = (state: TabSortState) => {
if (!state) {
setSortFn(null);
return;
}
setSortFn(
2024-11-01 20:23:47 +01:00
() =>
(
{ manifest: { filename: afilename } }: CodexDataContent,
{ manifest: { filename: bfilename } }: CodexDataContent
) =>
state === "desc"
? (bfilename || "")
.toLocaleLowerCase()
.localeCompare((afilename || "").toLocaleLowerCase())
: (afilename || "")
.toLocaleLowerCase()
.localeCompare((bfilename || "").toLocaleLowerCase())
2024-10-18 17:02:42 +02:00
);
};
const onSortBySize = (state: TabSortState) => {
2024-10-16 10:12:36 +02:00
if (!state) {
setSortFn(null);
return;
}
setSortFn(
() => (a: CodexDataContent, b: CodexDataContent) =>
state === "desc"
? b.manifest.datasetSize - a.manifest.datasetSize
: a.manifest.datasetSize - b.manifest.datasetSize
);
};
2024-10-18 17:02:42 +02:00
const onSortByDate = (state: TabSortState) => {
2024-10-16 10:12:36 +02:00
if (!state) {
setSortFn(null);
return;
}
setSortFn(
() => (a: CodexDataContent, b: CodexDataContent) =>
state === "desc"
? new Date(b.manifest.uploadedAt).getTime() -
new Date(a.manifest.uploadedAt).getTime()
: new Date(a.manifest.uploadedAt).getTime() -
new Date(b.manifest.uploadedAt).getTime()
);
};
const onToggleFilter = (filter: string) =>
2024-10-21 16:28:35 +02:00
selectedFilters.includes(filter)
? setSelectedFilters(selectedFilters.filter((f) => f !== filter))
: setSelectedFilters([...selectedFilters, filter]);
2024-10-15 12:20:40 +02:00
2024-10-10 10:31:30 +02:00
tabs.unshift({
label: "All files",
Icon: () => <FilesIcon size={"1rem"}></FilesIcon>,
});
2024-10-15 12:20:40 +02:00
2024-10-10 10:31:30 +02:00
const items =
index === 0
? files
: files.filter((file) => folders[index - 1][1].includes(file.cid));
2024-08-29 18:45:52 +02:00
2024-10-16 10:12:36 +02:00
const headers = [
2024-10-18 17:02:42 +02:00
["file", onSortByFilename],
2024-10-16 10:12:36 +02:00
["size", onSortBySize],
["date", onSortByDate],
["actions"],
2024-10-18 17:02:42 +02:00
] satisfies [string, ((state: TabSortState) => void)?][];
2024-10-16 10:12:36 +02:00
const filtered = items.filter(
(item) =>
2024-10-21 16:28:35 +02:00
selectedFilters.length === 0 ||
selectedFilters.includes(F.type(item.manifest.mimetype)) ||
(selectedFilters.includes("archive") &&
F.isArchive(item.manifest.mimetype))
2024-10-16 10:12:36 +02:00
);
const sorted = sortFn ? [...filtered].sort(sortFn) : filtered;
const rows =
sorted.map((c) => (
<Row
cells={[
2024-10-18 17:02:42 +02:00
<FileCell content={c}></FileCell>,
2024-10-16 10:12:36 +02:00
<Cell>{PrettyBytes(c.manifest.datasetSize)}</Cell>,
<Cell>{Dates.format(c.manifest.uploadedAt).toString()}</Cell>,
2024-10-18 17:02:42 +02:00
<FileActions
content={c}
folders={folders}
onDetails={onDetails}
onFolderToggle={onFolderToggle}></FileActions>,
2024-10-16 10:12:36 +02:00
]}></Row>
)) || [];
2024-08-29 18:45:52 +02:00
return (
<div className="files">
<div className="files-header">
2024-10-10 10:31:30 +02:00
<div className="files-headerLeft">
<div className="files-title">Files</div>
</div>
<div className="files-headerRight">
<div>
<Input
id="folder"
inputClassName={classnames(["files-folders"])}
isInvalid={folder !== "" && !!error}
value={folder}
required={true}
2024-10-15 12:20:40 +02:00
pattern="[A-Za-z0-9_\-]*"
2024-10-10 10:31:30 +02:00
maxLength={9}
helper={error || "Enter the folder name"}
onChange={onFolderChange}></Input>
</div>
<Button
label="Folder"
Icon={Plus}
disabled={!!error || !folder}
onClick={onFolderCreate}></Button>
</div>
2024-08-29 18:45:52 +02:00
</div>
2024-10-10 10:31:30 +02:00
<Tabs onTabChange={onTabChange} tabIndex={index} tabs={tabs}></Tabs>
2024-10-16 10:12:36 +02:00
<div className="files-filters">
2024-10-21 16:28:35 +02:00
<FilterFilters
files={files}
onFilterToggle={onToggleFilter}
selected={selectedFilters}
/>
2024-10-16 10:12:36 +02:00
</div>
2024-08-29 18:45:52 +02:00
<div className="files-fileBody">
2024-10-21 16:28:35 +02:00
<Table headers={headers} rows={rows} defaultSortIndex={2} />
2024-08-29 18:45:52 +02:00
</div>
2024-10-16 10:12:36 +02:00
<FileDetails onClose={onClose} details={details} />
2024-08-29 18:45:52 +02:00
</div>
);
}