import { Check, Copy, Download, FilesIcon, Folder, Plus, ReceiptText, 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, TabProps, Table, Row, Cell, } 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"; type SortFn = (a: CodexDataContent, b: CodexDataContent) => number; export function Files() { const files = useData(); const [index, setIndex] = useState(0); const [folder, setFolder] = useState(""); const [folders, setFolders] = useState<[string, string[]][]>([]); const [error, setError] = useState(""); const [details, setDetails] = useState(null); const [sortFn, setSortFn] = useState(null); const [filters, setFilters] = useState([]); useEffect(() => { WebStorage.folders.list().then((items) => setFolders(items)); }, []); const onClose = () => { setDetails(null); }; const onTabChange = async (i: number) => setIndex(i); const onDetails = (cid: string) => { const d = files.find((file) => file.cid === cid); if (d) { setDetails(d); } }; const onFolderChange = (e: ChangeEvent) => { 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; } if (folders.find(([folder]) => folder === val)) { setError("This folder already exists"); return; } } else { setError("9 alpha characters maximum"); } }; const onFolderCreate = () => { WebStorage.folders.create(folder); 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: () => , IconAfter: folder === "Favorites" ? undefined : () => ( { e.preventDefault(); e.stopPropagation(); onFolderDelete(folder); }}> ), })); const onSortBySize = (state: "" | "asc" | "desc") => { if (!state) { setSortFn(null); return; } setSortFn( () => (a: CodexDataContent, b: CodexDataContent) => state === "desc" ? b.manifest.datasetSize - a.manifest.datasetSize : a.manifest.datasetSize - b.manifest.datasetSize ); }; const onSortByDate = (state: "" | "asc" | "desc") => { 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) => filters.includes(filter) ? setFilters(filters.filter((f) => f !== filter)) : setFilters([...filters, filter]); const onCopy = (cid: string) => navigator.clipboard.writeText(cid); tabs.unshift({ label: "All files", Icon: () => , }); const items = index === 0 ? files : files.filter((file) => folders[index - 1][1].includes(file.cid)); const url = CodexSdk.url() + "/api/codex/v1/data/"; const headers = [ ["file"], ["size", onSortBySize], ["date", onSortByDate], ["actions"], ]; 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)) ); const sorted = sortFn ? [...filtered].sort(sortFn) : filtered; const rows = 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={() => }>
, ]}>
)) || []; return (
Files
{types.map((type) => ( onToggleFilter(type)}> {type} ))}
); }