diff --git a/src/components/Files/FileActions.tsx b/src/components/Files/FileActions.tsx
new file mode 100644
index 0000000..c520797
--- /dev/null
+++ b/src/components/Files/FileActions.tsx
@@ -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 (
+ |
+
+ window.open(url + content.cid, "_blank")}
+ Icon={(props) => (
+
+ )}>
+
+ [
+ folder,
+ files.includes(content.cid),
+ ])}
+ onFolderToggle={(folder) => onFolderToggle(content.cid, folder)}
+ />
+
+ onDetails(content.cid)}
+ Icon={() => }>
+
+ |
+ );
+}
diff --git a/src/components/Files/FileCell.tsx b/src/components/Files/FileCell.tsx
new file mode 100644
index 0000000..de06a5f
--- /dev/null
+++ b/src/components/Files/FileCell.tsx
@@ -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 (
+
+
+
+
+
+ {content.manifest.filename}
+
+ {content.cid}
+ onCopy(content.cid)}
+ animation="buzz"
+ Icon={(props) => }>
+
+
+
+ |
+ );
+}
diff --git a/src/components/Files/FileFilters.tsx b/src/components/Files/FileFilters.tsx
new file mode 100644
index 0000000..28e30e3
--- /dev/null
+++ b/src/components/Files/FileFilters.tsx
@@ -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) => (
+ onFilterToggle(type)}>
+ {type}
+
+ ));
+}
diff --git a/src/components/Files/Files.tsx b/src/components/Files/Files.tsx
index 706a832..104cd98 100644
--- a/src/components/Files/Files.tsx
+++ b/src/components/Files/Files.tsx
@@ -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: () => ,
@@ -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) => (
-
-
-
-
-
{c.manifest.filename}
-
- {c.cid}
-
-
-
- ,
+ ,
{PrettyBytes(c.manifest.datasetSize)} | ,
{Dates.format(c.manifest.uploadedAt).toString()} | ,
-
-
- window.open(url + c.cid, "_blank")}
- Icon={(props) => (
-
- )}>
-
- [
- folder,
- files.includes(c.cid),
- ])}
- onFolderToggle={(folder) => onFolderToggle(c.cid, folder)}
- />
-
- onCopy(c.cid)}
- animation="buzz"
- Icon={(props) => (
-
- )}>
-
- onDetails(c.cid)}
- Icon={() => }>
-
- | ,
+ ,
]}>
)) || [];
@@ -303,21 +264,11 @@ export function Files() {
- {types.map((type) => (
- onToggleFilter(type)}>
- {type}
-
- ))}
+