mirror of
https://github.com/logos-storage/logos-storage-marketplace-ui.git
synced 2026-01-04 06:23:08 +00:00
Improve folders
This commit is contained in:
parent
c70376bfcb
commit
333aeb1570
49
src/components/Files/FileActions.tsx
Normal file
49
src/components/Files/FileActions.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
35
src/components/Files/FileCell.tsx
Normal file
35
src/components/Files/FileCell.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
44
src/components/Files/FileFilters.tsx
Normal file
44
src/components/Files/FileFilters.tsx
Normal 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>
|
||||
));
|
||||
}
|
||||
@ -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} />
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user