Update files

This commit is contained in:
Arnaud 2024-11-05 09:57:13 +01:00
parent 9e6aa37981
commit 301a6720e8
No known key found for this signature in database
GPG Key ID: 69D6CE281FCAE663
34 changed files with 759 additions and 264 deletions

8
package-lock.json generated
View File

@ -9,7 +9,7 @@
"version": "0.0.7",
"license": "MIT",
"dependencies": {
"@codex-storage/marketplace-ui-components": "^0.0.33",
"@codex-storage/marketplace-ui-components": "^0.0.34",
"@codex-storage/sdk-js": "^0.0.15",
"@sentry/browser": "^8.32.0",
"@sentry/react": "^8.31.0",
@ -379,9 +379,9 @@
"peer": true
},
"node_modules/@codex-storage/marketplace-ui-components": {
"version": "0.0.33",
"resolved": "https://registry.npmjs.org/@codex-storage/marketplace-ui-components/-/marketplace-ui-components-0.0.33.tgz",
"integrity": "sha512-Au/k1FNygejrcHNLdgghgxm8VuERb2bQsI0JjGaIdk4LGgTS0EuMCd0Y6fo6VuZeyuVPN/kFSWMVPBO5xdFEEw==",
"version": "0.0.34",
"resolved": "https://registry.npmjs.org/@codex-storage/marketplace-ui-components/-/marketplace-ui-components-0.0.34.tgz",
"integrity": "sha512-WRSgQU/OiERKBqpUIq95tgz1jKizM8fd28hottD4Ra8Z0gHkFZThxoVGad2pq96ha7U/Q/U+VEW+f+t4Z251rg==",
"dependencies": {
"lucide-react": "^0.453.0"
},

View File

@ -25,7 +25,7 @@
"React"
],
"dependencies": {
"@codex-storage/marketplace-ui-components": "^0.0.33",
"@codex-storage/marketplace-ui-components": "^0.0.34",
"@codex-storage/sdk-js": "^0.0.15",
"@sentry/browser": "^8.32.0",
"@sentry/react": "^8.31.0",

View File

@ -0,0 +1,15 @@
export function CloseIcon() {
return (
<svg
width="10"
height="10"
viewBox="0 0 10 10"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M5.00005 3.93955L8.71255 0.227051L9.77305 1.28755L6.06055 5.00005L9.77305 8.71255L8.71255 9.77305L5.00005 6.06055L1.28755 9.77305L0.227051 8.71255L3.93955 5.00005L0.227051 1.28755L1.28755 0.227051L5.00005 3.93955Z"
fill="white"
/>
</svg>
);
}

View File

@ -9,13 +9,13 @@ export function WalletLines() {
opacity="0.5"
d="M16.8017 44.5368H0.231579C0.103681 44.5368 0 44.6405 0 44.7684C0 44.8963 0.103681 45 0.231579 45H679C681.209 45 683 43.2091 683 41V5C683 2.79086 681.209 1 679 1H644.034C643.689 1 643.345 1.04472 643.011 1.13303L560.776 22.8885C560.443 22.9768 560.099 23.0215 559.753 23.0215H532.049C531.504 23.0215 530.964 22.91 530.463 22.6937L480.993 1.32786C480.493 1.11157 479.953 1 479.408 1H428.397C427.753 1 427.117 1.15593 426.546 1.45451L386.125 22.567C385.553 22.8656 384.945 23.0215 384.3 23.0215H317.645C317.177 23.0215 316.73 23.1039 316.289 23.2648L274.515 38.5387C274.075 38.6996 273.61 38.782 273.141 38.782H244.777C244.302 38.782 243.831 38.6975 243.386 38.5324L202.234 23.2711C201.789 23.1061 201.318 23.0215 200.843 23.0215H164.28C163.623 23.0215 162.977 22.8599 162.397 22.5508L122.879 1.47072C122.299 1.16166 121.653 1 120.996 1H73.6247C72.7112 1 71.8252 1.31267 71.1141 1.88603L19.3123 43.6508C18.6011 44.2242 17.7152 44.5368 16.8017 44.5368Z"
fill="url(#paint0_linear_401_31774)"
fill-opacity="0.06"
fillOpacity="0.06"
/>
<path
opacity="0.6"
d="M0 45H15.3667C17.2079 45 18.9928 44.3649 20.4201 43.2019L70.0062 2.79813C71.4336 1.63509 73.2184 1 75.0596 1H119.977C121.302 1 122.607 1.32923 123.773 1.9581L161.502 22.2977C162.669 22.9266 163.973 23.2558 165.299 23.2558H200.111C201.07 23.2558 202.021 23.4282 202.919 23.7647L242.701 38.675C243.599 39.0115 244.55 39.1839 245.509 39.1839H272.419C273.365 39.1839 274.304 39.016 275.192 38.688L315.613 23.7517C316.5 23.4237 317.424 23.2558 318.37 23.2558C336.899 23.2558 374.308 23.2558 383.286 23.2558C384.587 23.2558 385.856 22.9382 387.007 22.3306L425.663 1.92521C426.814 1.31759 428.096 1 429.398 1H478.564C479.666 1 480.755 1.22743 481.765 1.66803L529.692 22.5878C530.701 23.0284 531.791 23.2558 532.892 23.2558H559.222C559.92 23.2558 560.615 23.1645 561.289 22.9843L642.499 1.27147C643.173 1.09126 643.867 1 644.565 1L683 1"
stroke="#3EE089"
stroke-width="2"
strokeWidth="2"
/>
<defs>
<linearGradient
@ -25,8 +25,8 @@ export function WalletLines() {
x2="341.5"
y2="45"
gradientUnits="userSpaceOnUse">
<stop stop-color="#CFD1D3" />
<stop offset="1" stop-color="#E4E5E7" stop-opacity="0" />
<stop stopColor="#CFD1D3" />
<stop offset="1" stopColor="#E4E5E7" stopOpacity="0" />
</linearGradient>
</defs>
</svg>

View File

@ -0,0 +1,15 @@
export function AllFilesIcon() {
return (
<svg
width="14"
height="16"
viewBox="0 0 14 16"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M3.25 3.5V1.25C3.25 1.05109 3.32902 0.860322 3.46967 0.71967C3.61032 0.579018 3.80109 0.5 4 0.5H13C13.1989 0.5 13.3897 0.579018 13.5303 0.71967C13.671 0.860322 13.75 1.05109 13.75 1.25V11.75C13.75 11.9489 13.671 12.1397 13.5303 12.2803C13.3897 12.421 13.1989 12.5 13 12.5H10.75V14.75C10.75 15.164 10.4125 15.5 9.99475 15.5H1.00525C0.906345 15.5006 0.808298 15.4816 0.716742 15.4442C0.625186 15.4068 0.541925 15.3517 0.471744 15.282C0.401563 15.2123 0.345845 15.1294 0.307791 15.0381C0.269737 14.9468 0.250097 14.8489 0.25 14.75L0.25225 4.25C0.25225 3.836 0.58975 3.5 1.0075 3.5H3.25ZM4.75 3.5H10.75V11H12.25V2H4.75V3.5ZM3.25 7.25V8.75H7.75V7.25H3.25ZM3.25 10.25V11.75H7.75V10.25H3.25Z"
fill="#6FCB94"
/>
</svg>
);
}

View File

@ -0,0 +1,15 @@
export function ArchiveIcon() {
return (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M13.4 3.8001C13.5591 3.8001 13.7117 3.86331 13.8243 3.97583C13.9368 4.08836 14 4.24097 14 4.4001V12.8001C14 12.9592 13.9368 13.1118 13.8243 13.2244C13.7117 13.3369 13.5591 13.4001 13.4 13.4001H2.6C2.44087 13.4001 2.28826 13.3369 2.17574 13.2244C2.06321 13.1118 2 12.9592 2 12.8001V3.2001C2 3.04097 2.06321 2.88836 2.17574 2.77583C2.28826 2.66331 2.44087 2.6001 2.6 2.6001H7.0484L8.2484 3.8001H10.4V5.0001H11.6V3.8001H13.4ZM11.6 8.6001H10.4V9.8001H9.2V11.6001H11.6V8.6001ZM10.4 7.4001H9.2V8.6001H10.4V7.4001ZM11.6 6.2001H10.4V7.4001H11.6V6.2001ZM10.4 5.0001H9.2V6.2001H10.4V5.0001Z"
fill="currentColor"
/>
</svg>
);
}

View File

@ -1,7 +1,7 @@
import { useRef, useState } from "react";
import { COPY_DURATION, ICON_SIZE } from "../../utils/constants";
import { Copy } from "lucide-react";
import { COPY_DURATION } from "../../utils/constants";
import { Button } from "@codex-storage/marketplace-ui-components";
import { CoypIcon } from "./CopyIcon";
type CopyButtonProps = {
cid: string;
@ -27,13 +27,11 @@ export function CidCopyButton({ cid }: CopyButtonProps) {
const label = copied ? "Copied !" : "Copy CID";
const Icon = () => <Copy size={ICON_SIZE} />;
return (
<Button
label={label}
variant="outline"
onClick={onCopy}
Icon={Icon}></Button>
Icon={CoypIcon}></Button>
);
}

View File

@ -0,0 +1,15 @@
export function CoypIcon() {
return (
<svg
width="14"
height="16"
viewBox="0 0 14 16"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M3 3.5V1.25C3 1.05109 3.07902 0.860322 3.21967 0.71967C3.36032 0.579018 3.55109 0.5 3.75 0.5H12.75C12.9489 0.5 13.1397 0.579018 13.2803 0.71967C13.421 0.860322 13.5 1.05109 13.5 1.25V11.75C13.5 11.9489 13.421 12.1397 13.2803 12.2803C13.1397 12.421 12.9489 12.5 12.75 12.5H10.5V14.75C10.5 15.164 10.1625 15.5 9.74475 15.5H0.75525C0.656345 15.5006 0.558298 15.4816 0.466742 15.4442C0.375186 15.4068 0.291925 15.3517 0.221744 15.282C0.151563 15.2123 0.0958447 15.1294 0.0577907 15.0381C0.0197367 14.9468 9.70307e-05 14.8489 0 14.75L0.00225002 4.25C0.00225002 3.836 0.33975 3.5 0.7575 3.5H3ZM1.50225 5L1.5 14H9V5H1.50225ZM4.5 3.5H10.5V11H12V2H4.5V3.5Z"
fill="#99A0AE"
/>
</svg>
);
}

View File

@ -0,0 +1,15 @@
export function DocumentIcon() {
return (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M13.4001 6.2V13.3958C13.4007 13.4746 13.3857 13.5527 13.356 13.6257C13.3264 13.6987 13.2827 13.7652 13.2273 13.8213C13.172 13.8774 13.1062 13.9221 13.0336 13.9527C12.961 13.9834 12.8831 13.9995 12.8043 14H3.1959C3.03799 14 2.88653 13.9373 2.77482 13.8257C2.6631 13.7141 2.60026 13.5627 2.6001 13.4048V2.5952C2.6001 2.273 2.8683 2 3.1989 2H9.2001V5.6C9.2001 5.75913 9.26331 5.91174 9.37583 6.02426C9.48836 6.13679 9.64097 6.2 9.8001 6.2H13.4001ZM13.4001 5H10.4001V2.0018L13.4001 5ZM5.6001 5V6.2H7.4001V5H5.6001ZM5.6001 7.4V8.6H10.4001V7.4H5.6001ZM5.6001 9.8V11H10.4001V9.8H5.6001Z"
fill="currentColor"
/>
</svg>
);
}

View File

@ -0,0 +1,15 @@
export function DownloadIcon() {
return (
<svg
width="16"
height="14"
viewBox="0 0 16 14"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M2 12.25H14V7H15.5V13C15.5 13.1989 15.421 13.3897 15.2803 13.5303C15.1397 13.671 14.9489 13.75 14.75 13.75H1.25C1.05109 13.75 0.860322 13.671 0.71967 13.5303C0.579018 13.3897 0.5 13.1989 0.5 13V7H2V12.25ZM9.5 4.75H13.25L8 10L2.75 4.75H6.5V0.25H9.5V4.75Z"
fill="#969696"
/>
</svg>
);
}

View File

@ -0,0 +1,15 @@
export function FavoriteIcon() {
return (
<svg
width="16"
height="15"
viewBox="0 0 16 15"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M8.00018 11.75L3.59168 14.4425L4.79018 9.4175L0.867676 6.0575L6.01643 5.645L8.00018 0.875L9.98393 5.645L15.1334 6.0575L11.2102 9.4175L12.4087 14.4425L8.00018 11.75Z"
fill="#969696"
/>
</svg>
);
}

View File

@ -0,0 +1,19 @@
.file-actions {
> div {
display: inline-flex;
align-items: center;
border: 1px solid var(--codex-border-color);
border-radius: var(--codex-border-radius);
padding: 0.5rem;
background-color: #14141499;
gap: 8px;
padding: 10px;
.button-icon {
width: 40px;
height: 40px;
background-color: #2f2f2f;
border: 1px solid #96969633;
}
}
}

View File

@ -1,9 +1,10 @@
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";
import "./FileActions.css";
import { DownloadIcon } from "./DownloadIcon";
import { InfoFileIcon } from "./InfoFileIcon";
type Props = {
content: CodexDataContent;
@ -21,15 +22,12 @@ export function FileActions({
const url = CodexSdk.url() + "/api/codex/v1/data/";
return (
<Cell>
<div className="files-fileActions">
<Cell className="file-actions">
<div>
<ButtonIcon
variant="small"
animation="bounce"
onClick={() => window.open(url + content.cid, "_blank")}
Icon={(props) => (
<Download size={ICON_SIZE} {...props} />
)}></ButtonIcon>
Icon={DownloadIcon}></ButtonIcon>
<FolderButton
folders={folders.map(([folder, files]) => [
@ -42,7 +40,7 @@ export function FileActions({
<ButtonIcon
variant="small"
onClick={() => onDetails(content.cid)}
Icon={() => <ReceiptText size={ICON_SIZE} />}></ButtonIcon>
Icon={InfoFileIcon}></ButtonIcon>
</div>
</Cell>
);

View File

@ -0,0 +1,22 @@
.file-cell {
small {
word-break: break-all;
}
> div {
display: flex;
gap: 12px;
align-items: center;
> div {
flex: 1;
}
.button-icon {
width: 40px;
height: 40px;
background-color: #14141499;
border: 1px solid #96969633;
}
}
}

View File

@ -7,6 +7,7 @@ import {
import { CodexDataContent } from "@codex-storage/sdk-js";
import { Copy } from "lucide-react";
import { useState } from "react";
import "./FileCell.css";
type Props = {
content: CodexDataContent;
@ -22,23 +23,23 @@ export function FileCell({ content }: Props) {
return (
<>
<Cell>
<div className="files-cell-file">
<Cell className="file-cell">
<div>
<WebFileIcon type={content.manifest.mimetype || ""} />
<div>
<b>{content.manifest.filename}</b>
<div className="files-fileMeta">
<small className="files-fileMeta-cid">{content.cid}</small>
<ButtonIcon
variant="small"
onClick={() => onCopy(content.cid)}
animation="buzz"
Icon={(props) => (
<Copy size={"1rem"} {...props} />
)}></ButtonIcon>
</div>
<p>
<b>{content.manifest.filename}</b>
</p>
<p>
<small>{content.cid}</small>
</p>
</div>
<ButtonIcon
variant="small"
onClick={() => onCopy(content.cid)}
animation="buzz"
Icon={(props) => <Copy size={"1rem"} {...props} />}></ButtonIcon>
</div>
<Toast message={toast.message} time={toast.time} variant={"success"} />

View File

@ -1,16 +1,118 @@
.fileDetails-header {
padding: 0.75rem 1.5rem;
border-bottom: 1px solid var(--codex-border-color);
display: flex;
align-items: center;
}
.file-details {
background-color: #232323;
border-left: 1px solid #96969633;
border-top-left-radius: 16px;
border-bottom-left-radius: 16px;
padding: 16px;
height: 100%;
.fileDetails-headerTitle {
flex-grow: 1;
}
header {
display: flex;
align-items: center;
font-family: Inter;
font-size: 16px;
font-weight: 500;
line-height: 24px;
letter-spacing: -0.011em;
text-align: left;
.fileDetails-body {
padding: 0;
span {
flex-grow: 1;
}
.button-icon {
background-color: #2f2f2f;
border: 1px solid #96969633;
}
}
.preview {
background-color: #14141499;
border: 1px solid #69696933;
height: 200px;
margin: auto;
border-radius: 10px;
margin-bottom: 16px;
img {
max-width: 100%;
max-height: 100%;
margin: auto;
display: flex;
}
figure {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
height: 100%;
margin: 0;
font-family: Inter;
font-size: 16px;
font-weight: 400;
line-height: 24px;
letter-spacing: -0.011em;
text-align: left;
color: #ffffff33;
p {
margin-top: 8px;
}
}
}
ul {
li {
grid-template-columns: repeat(4, minmax(0, 1fr));
display: grid;
padding: 8px 0;
align-items: center;
p:first-child {
font-family: Inter;
font-size: 14px;
font-weight: 700;
line-height: 20px;
letter-spacing: -0.006em;
text-align: left;
}
p:nth-child(2) {
grid-column: span 2 / span 2;
font-family: Inter;
font-size: 14px;
font-weight: 400;
line-height: 20px;
letter-spacing: -0.006em;
text-align: left;
color: #ffffffcc;
}
&:last-child p:nth-child(2) {
font-family: Inter;
font-size: 14px;
font-weight: 400;
line-height: 20px;
letter-spacing: -0.006em;
text-align: left;
color: #6beca1;
}
}
}
.buttons {
padding: 16px 0;
border-bottom: 1px solid #96969633;
display: flex;
gap: 16px;
button {
flex: 1;
}
}
}
.fileDetails-grid {

View File

@ -2,18 +2,20 @@ import {
ButtonIcon,
Button,
Sheets,
WebFileIcon,
} from "@codex-storage/marketplace-ui-components";
import { CodexDataContent } from "@codex-storage/sdk-js";
import { PrettyBytes } from "../../utils/bytes";
import { ICON_SIZE } from "../../utils/constants";
import { Dates } from "../../utils/dates";
import { CidCopyButton } from "./CidCopyButton";
import "./FileDetails.css";
import { DownloadIcon, X } from "lucide-react";
import { CodexSdk } from "../../sdk/codex";
import { Files } from "../../utils/files";
import { useEffect, useState } from "react";
import { WebStorage } from "../../utils/web-storage";
import { FileDetailsIcon } from "./FileDetailsIcon";
import { CloseIcon } from "../CloseIcon/CloseIcon";
import { DownloadIcon } from "./DownloadIcon";
type Props = {
details: CodexDataContent | null;
@ -37,84 +39,85 @@ export function FileDetails({ onClose, details }: Props) {
const url = CodexSdk.url() + "/api/codex/v1/data/";
const Icon = () => <X size={ICON_SIZE} onClick={onClose} />;
const onDownload = () => window.open(url + details?.cid, "_target");
return (
<Sheets open={!!details} onClose={onClose}>
<>
{details && (
<>
<div className="fileDetails-header">
<b className="fileDetails-headerTitle">File details</b>
<ButtonIcon variant="small" Icon={Icon}></ButtonIcon>
<div className="file-details">
<header>
<FileDetailsIcon></FileDetailsIcon>
<span>File details</span>
<ButtonIcon
onClick={onClose}
variant="small"
Icon={CloseIcon}></ButtonIcon>
</header>
<div className="preview">
{Files.isImage(details.manifest.mimetype) ? (
<img src={url + details.cid} />
) : (
<figure>
<WebFileIcon
type={details.manifest.mimetype || ""}
size={32}
/>
<p>File Preview not available.</p>
</figure>
)}
</div>
{Files.isImage(details.manifest.mimetype) && (
<div className="fileDetails-imageContainer">
<img className="fileDetails-image" src={url + details.cid} />
</div>
)}
<ul>
<li>
<p>CID:</p>
<p>{details.cid}</p>
</li>
<div className="fileDetails-body">
<div className="fileDetails-grid">
<p className="text-secondary">CID:</p>
<p className="fileDetails-gridColumn">{details.cid}</p>
</div>
<li>
<p>File name:</p>
<p>{details.manifest.filename}</p>
</li>
<div className="fileDetails-grid">
<p className="text-secondary">File name:</p>
<p className="fileDetails-gridColumn">
{details.manifest.filename}
<li>
<p>Date:</p>
<p>{Dates.format(details.manifest.uploadedAt).toString()}</p>
</li>
<li>
<p>Mimetype:</p>
<p>{details.manifest.mimetype}</p>
</li>
<li>
<p>Size:</p>
<p>{PrettyBytes(details.manifest.datasetSize)}</p>
</li>
<li>
<p>Protected:</p>
<p>{details.manifest.protected ? "Yes" : "No"}</p>
</li>
<li>
<p>Used:</p>
<p>
<b>{purchases} </b> purchase(s)
</p>
</div>
</li>
</ul>
<div className="fileDetails-grid">
<p className="text-secondary">Date:</p>
<p className="fileDetails-gridColumn">
{Dates.format(details.manifest.uploadedAt).toString()}
</p>
</div>
<div className="buttons">
<CidCopyButton cid={details.cid} />
<div className="fileDetails-grid">
<p className="text-secondary">Mimetype:</p>
<p className="fileDetails-gridColumn">
{details.manifest.mimetype}
</p>
</div>
<div className="fileDetails-grid">
<p className="text-secondary">Size:</p>
<p className="fileDetails-gridColumn">
{PrettyBytes(details.manifest.datasetSize)}
</p>
</div>
<div className="fileDetails-grid">
<p className="text-secondary">Protected:</p>
<p className="fileDetails-gridColumn">
{details.manifest.protected ? "Yes" : "No"}
</p>
</div>
<div className="fileDetails-grid">
<p className="text-secondary">Used:</p>
<p className="fileDetails-gridColumn">
{purchases + " purchase(s)"}
</p>
</div>
<div className="fileDetails-actions">
<CidCopyButton cid={details.cid} />
<Button
Icon={() => <DownloadIcon size={ICON_SIZE} />}
label="Download"
onClick={onDownload}></Button>
</div>
<Button
Icon={DownloadIcon}
label="Download"
variant="outline"
onClick={onDownload}></Button>
</div>
</>
</div>
)}
</>
</Sheets>

View File

@ -0,0 +1,15 @@
export function FileDetailsIcon() {
return (
<svg
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M6.29993 0.00270002V0H16.1981C16.6949 0 17.0999 0.4095 17.0999 0.8928V17.1072C17.0997 17.3441 17.0054 17.5712 16.8378 17.7386C16.6703 17.906 16.4431 18 16.2062 18H1.79362C1.67543 17.9992 1.55856 17.9751 1.44969 17.9291C1.34081 17.8831 1.24206 17.8161 1.15907 17.7319C1.07608 17.6478 1.01048 17.5481 0.966016 17.4386C0.921552 17.3291 0.899094 17.2119 0.899925 17.0937V5.4L6.29993 0.00270002ZM3.44692 5.4H6.29993V2.547L3.44692 5.4ZM8.09993 1.8V6.3C8.09993 6.53869 8.0051 6.76761 7.83632 6.9364C7.66754 7.10518 7.43862 7.2 7.19993 7.2H2.69992V16.2H15.2999V1.8H8.09993Z"
fill="#969696"
/>
</svg>
);
}

View File

@ -0,0 +1,35 @@
.filter {
padding: 4px 8px;
gap: 8px;
border-radius: 6px;
border: 1px solid #96969633;
background-color: #2f2f2f;
font-family: Inter;
font-size: 12px;
font-weight: 500;
line-height: 16px;
text-align: left;
color: #969696;
text-transform: capitalize;
display: inline-flex;
align-items: center;
cursor: pointer;
transition: box-shadow 0.35s;
&:hover {
box-shadow: 0 0 0 3px var(--codex-border-color);
}
svg {
color: #969696;
}
&.filter--active {
border-color: var(--codex-color-primary);
color: var(--codex-color-primary);
svg {
color: var(--codex-color-primary);
}
}
}

View File

@ -1,7 +1,11 @@
import { CodexDataContent } from "@codex-storage/sdk-js";
import { Files } from "../../utils/files";
import { classnames } from "../../utils/classnames";
import { Check } from "lucide-react";
import "./FileFilters.css";
import { ArchiveIcon } from "./ArchiveIcon";
import { ImageIcon } from "./ImageIcon";
import { VideoIcon } from "./VideoIcon";
import { DocumentIcon } from "./DocumentIcon";
type Props = {
files: CodexDataContent[];
@ -9,28 +13,73 @@ type Props = {
selected: string[];
};
function getIcon(type: string) {
switch (type) {
case "image": {
return <ImageIcon></ImageIcon>;
}
case "archive": {
return <ArchiveIcon></ArchiveIcon>;
}
case "video": {
return <VideoIcon></VideoIcon>;
}
default: {
return <DocumentIcon></DocumentIcon>;
}
}
}
function getType(mimetype: string) {
if (Files.isArchive(mimetype)) {
return "archive";
}
if (Files.isImage(mimetype)) {
return "image";
}
if (Files.isVideo(mimetype)) {
return "video";
}
return "document";
}
export function FilterFilters({ selected, files, onFilterToggle }: Props) {
const filters = Array.from(
new Set(
files
.filter((f) => f.manifest.mimetype !== "")
.map((file) =>
Files.isArchive(file.manifest.mimetype)
? "archive"
: Files.type(file.manifest.mimetype)
)
.map((file) => getType(file.manifest.mimetype || ""))
)
);
return filters.map((type) => (
<span
key={type}
className={classnames(
["files-filter"],
["files-filter--active", !!filters.find((f) => selected.includes(f))]
)}
onClick={() => onFilterToggle(type)}>
<span>{type}</span> <Check size={"1rem"}></Check>
</span>
));
const html = filters.map((type) => {
const count = files.reduce(
(acc, file) =>
getType(file.manifest.mimetype || "") === type ? acc + 1 : acc,
0
);
return (
<span
key={type}
className={classnames(
["filter"],
["filter--active", selected.includes(type)]
)}
onClick={() => onFilterToggle(type)}>
{getIcon(type)}
<span>
{type + "s"} ({count})
</span>
</span>
);
});
return <div className="filters">{html}</div>;
}

View File

@ -1,8 +1,29 @@
.files {
border-radius: var(--codex-border-radius);
border: 1px solid var(--codex-border-color);
background-color: var(--codex-background-secondary);
padding: 1rem 1.5rem;
main > section {
display: inline-flex;
justify-content: space-between;
align-items: center;
width: 100%;
gap: 16px;
.tabs {
flex-basis: 75%;
}
}
.filters {
margin-top: 16px;
gap: 16px;
display: flex;
}
.button {
width: 105px;
}
table thead tr th {
background-color: #14141499;
}
}
.files-cell-file {
@ -90,10 +111,6 @@
gap: 0.5rem;
}
.files-fileBody {
margin-top: 1.25rem;
}
.files-folders {
width: 200px;
min-width: 0px;

View File

@ -1,4 +1,4 @@
import { FilesIcon, Folder, Plus, X } from "lucide-react";
import { Plus } from "lucide-react";
import { ChangeEvent, useEffect, useState } from "react";
import { PrettyBytes } from "../../utils/bytes";
import { Dates } from "../../utils/dates";
@ -22,6 +22,9 @@ import { Files as F } from "../../utils/files.ts";
import { FilterFilters } from "./FileFilters.tsx";
import { FileCell } from "./FileCell.tsx";
import { FileActions } from "./FileActions.tsx";
import { FilesIconOutline } from "../FilesIcon/FilesIcon.tsx";
import { AllFilesIcon } from "./AllFilesIcon.tsx";
import { FavoriteIcon } from "./FavoriteIcon.tsx";
type SortFn = (a: CodexDataContent, b: CodexDataContent) => number;
@ -82,17 +85,17 @@ export function Files() {
setFolders([...folders, [folder, []]]);
};
const onFolderDelete = (val: string) => {
WebStorage.folders.delete(val);
// const onFolderDelete = (val: string) => {
// WebStorage.folders.delete(val);
const currentIndex = folders.findIndex(([name]) => name === val);
// const currentIndex = folders.findIndex(([name]) => name === val);
if (currentIndex + 1 == index) {
setIndex(index - 1);
}
// if (currentIndex + 1 == index) {
// setIndex(index - 1);
// }
setFolders(folders.filter(([name]) => name !== val));
};
// setFolders(folders.filter(([name]) => name !== val));
// };
const onFolderToggle = (cid: string, folder: string) => {
const current = folders.find(([name]) => name === folder);
@ -124,22 +127,22 @@ export function Files() {
}
};
const tabs: TabProps[] = folders.map(([folder]) => ({
const tabs: TabProps[] = folders.map(([folder], index) => ({
label: folder,
Icon: () => <Folder size={"1rem"}></Folder>,
IconAfter:
folder === "Favorites"
? undefined
: () => (
<X
size={"1rem"}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
Icon: () => (index === 0 ? <FavoriteIcon></FavoriteIcon> : null),
// IconAfter:
// folder === "Favorites"
// ? undefined
// : () => (
// <X
// size={"1rem"}
// onClick={(e) => {
// e.preventDefault();
// e.stopPropagation();
onFolderDelete(folder);
}}></X>
),
// onFolderDelete(folder);
// }}></X>
// ),
}));
const onSortByFilename = (state: TabSortState) => {
@ -200,8 +203,8 @@ export function Files() {
: setSelectedFilters([...selectedFilters, filter]);
tabs.unshift({
label: "All files",
Icon: () => <FilesIcon size={"1rem"}></FilesIcon>,
label: "All",
Icon: () => <AllFilesIcon></AllFilesIcon>,
});
const items =
@ -241,13 +244,17 @@ export function Files() {
)) || [];
return (
<div className="files">
<div className="files-header">
<div className="files-headerLeft">
<div className="files-title">Files</div>
<div className="card files">
<header>
<div>
<FilesIconOutline></FilesIconOutline>
<h5>Files</h5>
</div>
<div className="files-headerRight">
<div>
</header>
<main>
<section>
<Tabs onTabChange={onTabChange} tabIndex={index} tabs={tabs}></Tabs>
<div className="row gap">
<Input
id="folder"
inputClassName={classnames(["files-folders"])}
@ -256,33 +263,35 @@ export function Files() {
required={true}
pattern="[A-Za-z0-9_\-]*"
maxLength={9}
helper={error || "Enter the folder name"}
size={"medium" as any}
placeholder="Folder name"
onChange={onFolderChange}></Input>
<Button
label="Folder"
Icon={Plus}
variant="outline"
disabled={!!error || !folder}
onClick={onFolderCreate}></Button>
</div>
</section>
<Button
label="Folder"
Icon={Plus}
disabled={!!error || !folder}
onClick={onFolderCreate}></Button>
</div>
</div>
<Tabs onTabChange={onTabChange} tabIndex={index} tabs={tabs}></Tabs>
<div className="files-filters">
<FilterFilters
files={files}
onFilterToggle={onToggleFilter}
selected={selectedFilters}
/>
</div>
<div className="files-fileBody">
<Table headers={headers} rows={rows.slice(0, 4)} defaultSortIndex={2} />
</div>
<div>
<Table
headers={headers}
rows={rows.slice(0, 4)}
defaultSortIndex={2}
/>
</div>
<FileDetails onClose={onClose} details={details} />
<FileDetails onClose={onClose} details={details} />
</main>
</div>
);
}

View File

@ -0,0 +1,15 @@
export function FolderAdd() {
return (
<svg
width="16"
height="14"
viewBox="0 0 16 14"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M8.3105 1.75H14.75C14.9489 1.75 15.1397 1.82902 15.2803 1.96967C15.421 2.11032 15.5 2.30109 15.5 2.5V13C15.5 13.1989 15.421 13.3897 15.2803 13.5303C15.1397 13.671 14.9489 13.75 14.75 13.75H1.25C1.05109 13.75 0.860322 13.671 0.71967 13.5303C0.579018 13.3897 0.5 13.1989 0.5 13V1C0.5 0.801088 0.579018 0.610322 0.71967 0.46967C0.860322 0.329018 1.05109 0.25 1.25 0.25H6.8105L8.3105 1.75ZM2 1.75V12.25H14V3.25H7.6895L6.1895 1.75H2ZM7.25 7V4.75H8.75V7H11V8.5H8.75V10.75H7.25V8.5H5V7H7.25Z"
fill="#969696"
/>
</svg>
);
}

View File

@ -1,38 +1,41 @@
.folderButton {
position: relative;
}
.folder-button {
> div {
position: absolute;
transform: translate(-110px, -75px);
opacity: 0;
transition:
transform 0.25s,
opacity 0.15s;
background-color: var(--codex-background);
padding: 0.5rem;
border-radius: var(--codex-border-radius);
width: 150px;
right: -40px;
border: 1px solid var(--codex-border-color);
z-index: -1;
.folderButton-options {
position: absolute;
transform: translateY(200px);
opacity: 0;
transition:
transform 0.25s,
opacity 0.15s;
background-color: var(--codex-background);
padding: 0.5rem;
border-radius: var(--codex-border-radius);
width: 150px;
right: -40px;
border: 1px solid var(--codex-border-color);
}
&[aria-expanded] {
z-index: 12;
transform: translate(-110px, -200px);
opacity: 1;
}
.folderButton-options[aria-expanded] {
z-index: 12;
transform: translateY(0px);
opacity: 1;
}
> div {
padding: 0.75rem;
transition: background-color 0.35s;
cursor: pointer;
border-radius: var(--codex-border-radius);
display: flex;
align-items: center;
justify-content: space-between;
.folderButton-option {
padding: 0.75rem;
transition: background-color 0.35s;
cursor: pointer;
border-radius: var(--codex-border-radius);
display: flex;
align-items: center;
justify-content: space-between;
}
&:hover {
background-color: var(--codex-background-light);
}
}
.folderButton-option:hover {
background-color: var(--codex-background-light);
svg {
color: var(--codex-color-primary);
}
}
}

View File

@ -1,8 +1,10 @@
import { Backdrop, ButtonIcon } from "@codex-storage/marketplace-ui-components";
import { CheckCircle, Folder } from "lucide-react";
import { CheckCircle } from "lucide-react";
import "./FolderButton.css";
import { useState } from "react";
import { attributes } from "../../utils/attributes";
import { FolderAdd } from "./FolderAdd";
import { classnames } from "../../utils/classnames";
type Props = {
folders: [string, boolean][];
@ -24,35 +26,28 @@ export function FolderButton({ folders, onFolderToggle }: Props) {
);
return (
<div className="folderButton">
<Backdrop open={open} onClose={onClose}></Backdrop>
<>
<div
className={classnames(
["folder-button"],
["folder-button--active", doesFolderContainFile]
)}>
<ButtonIcon
variant="small"
className={classnames([""])}
onClick={onOpen}
Icon={FolderAdd}></ButtonIcon>
<ButtonIcon
variant="small"
onClick={onOpen}
Icon={() => (
<Folder
fill={doesFolderContainFile ? "var(--codex-color-primary)" : ""}
/>
)}></ButtonIcon>
<div className="folderButton-options" {...attr}>
{folders.map(([folder, isActive]) => (
<div
key={folder}
className="folderButton-option"
onClick={() => onFolderToggle(folder)}>
<div>{folder}</div>
<div>
{isActive && (
<span className="text--primary">
<CheckCircle size={"1rem"}></CheckCircle>
</span>
)}
<div {...attr}>
{folders.map(([folder, isActive]) => (
<div key={folder} onClick={() => onFolderToggle(folder)}>
<span>{folder}</span>
{isActive && <CheckCircle size={"1rem"}></CheckCircle>}
</div>
</div>
))}
))}
</div>
</div>
</div>
<Backdrop open={open} onClose={onClose}></Backdrop>
</>
);
}

View File

@ -0,0 +1,15 @@
export function ImageIcon() {
return (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M12.8 3.8001H3.2V12.2001L8.7752 6.6237C8.88772 6.51122 9.0403 6.44803 9.1994 6.44803C9.3585 6.44803 9.51108 6.51122 9.6236 6.6237L12.8 9.8061V3.8001ZM2 3.1959C2.0011 3.03832 2.06414 2.88751 2.17551 2.77603C2.28688 2.66455 2.43763 2.60135 2.5952 2.6001H13.4048C13.7336 2.6001 14 2.8671 14 3.1959V12.8043C13.9989 12.9619 13.9359 13.1127 13.8245 13.2242C13.7131 13.3356 13.5624 13.3988 13.4048 13.4001H2.5952C2.43729 13.3999 2.2859 13.3371 2.17429 13.2254C2.06269 13.1137 2 12.9622 2 12.8043V3.1959ZM5.6 7.4001C5.28174 7.4001 4.97652 7.27367 4.75147 7.04863C4.52643 6.82358 4.4 6.51836 4.4 6.2001C4.4 5.88184 4.52643 5.57661 4.75147 5.35157C4.97652 5.12653 5.28174 5.0001 5.6 5.0001C5.91826 5.0001 6.22348 5.12653 6.44853 5.35157C6.67357 5.57661 6.8 5.88184 6.8 6.2001C6.8 6.51836 6.67357 6.82358 6.44853 7.04863C6.22348 7.27367 5.91826 7.4001 5.6 7.4001Z"
fill="currentColor"
/>
</svg>
);
}

View File

@ -0,0 +1,15 @@
export function InfoFileIcon() {
return (
<svg
width="14"
height="16"
viewBox="0 0 14 16"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M9.25 2H1.75V14H12.25V5H9.25V2ZM0.25 1.244C0.25 0.833 0.58525 0.5 0.99925 0.5H10L13.75 4.25V14.7448C13.7507 14.8432 13.732 14.9409 13.6949 15.0322C13.6579 15.1234 13.6032 15.2065 13.534 15.2766C13.4649 15.3468 13.3826 15.4026 13.2919 15.4409C13.2011 15.4792 13.1037 15.4993 13.0052 15.5H0.99475C0.797784 15.4986 0.609263 15.4198 0.469913 15.2806C0.330563 15.1414 0.251571 14.953 0.25 14.756V1.244ZM6.25 7.25H7.75V11.75H6.25V7.25ZM6.25 4.25H7.75V5.75H6.25V4.25Z"
fill="#969696"
/>
</svg>
);
}

View File

@ -0,0 +1,15 @@
export function VideoIcon() {
return (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M12.5 4.5H8.2071L7.2071 3.5H3.5C3.22386 3.5 3 3.72386 3 4V12C3 12.2761 3.22386 12.5 3.5 12.5H12.5C12.7761 12.5 13 12.2761 13 12V5C13 4.72386 12.7761 4.5 12.5 4.5ZM9.5004 8.3335C9.5923 8.39475 9.61715 8.51895 9.55585 8.61085C9.5412 8.6328 9.52235 8.65165 9.5004 8.6663L7.06095 10.2926C6.96905 10.3539 6.84485 10.3291 6.7836 10.2372C6.7617 10.2043 6.75 10.1657 6.75 10.1262V6.87359C6.75 6.76313 6.83955 6.67358 6.95 6.67358C6.9895 6.67358 7.0281 6.68527 7.06095 6.70717L9.5004 8.3335Z"
fill="currentColor"
/>
</svg>
);
}

View File

@ -0,0 +1,34 @@
type Props = {
width?: number;
};
export function FilesIcon({ width = 16 }: Props) {
return (
<svg
width={width}
viewBox="0 0 16 14"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M3.5 3.25V1C3.5 0.801088 3.57902 0.610322 3.71967 0.46967C3.86032 0.329018 4.05109 0.25 4.25 0.25H9.0605L10.5605 1.75H14.75C14.9489 1.75 15.1397 1.82902 15.2803 1.96967C15.421 2.11032 15.5 2.30109 15.5 2.5V10C15.5 10.1989 15.421 10.3897 15.2803 10.5303C15.1397 10.671 14.9489 10.75 14.75 10.75H12.5V13C12.5 13.1989 12.421 13.3897 12.2803 13.5303C12.1397 13.671 11.9489 13.75 11.75 13.75H1.25C1.05109 13.75 0.860322 13.671 0.71967 13.5303C0.579018 13.3897 0.5 13.1989 0.5 13V4C0.5 3.80109 0.579018 3.61032 0.71967 3.46967C0.860322 3.32902 1.05109 3.25 1.25 3.25H3.5ZM3.5 4.75H2V12.25H11V10.75H3.5V4.75Z"
fill="currentColor"
/>
</svg>
);
}
export function FilesIconOutline() {
return (
<svg
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M3.6 4.4999V1.7999C3.6 1.56121 3.69482 1.33229 3.8636 1.16351C4.03239 0.994723 4.2613 0.899902 4.5 0.899902H10.2726L12.0726 2.6999H17.1C17.3387 2.6999 17.5676 2.79472 17.7364 2.96351C17.9052 3.13229 18 3.36121 18 3.5999V12.5999C18 12.8386 17.9052 13.0675 17.7364 13.2363C17.5676 13.4051 17.3387 13.4999 17.1 13.4999H14.4V16.1999C14.4 16.4386 14.3052 16.6675 14.1364 16.8363C13.9676 17.0051 13.7387 17.0999 13.5 17.0999H0.9C0.661305 17.0999 0.432387 17.0051 0.263604 16.8363C0.0948211 16.6675 0 16.4386 0 16.1999V5.3999C0 5.16121 0.0948211 4.93229 0.263604 4.76351C0.432387 4.59472 0.661305 4.4999 0.9 4.4999H3.6ZM3.6 6.2999H1.8V15.2999H12.6V13.4999H3.6V6.2999ZM5.4 2.6999V11.6999H16.2V4.4999H11.3274L9.5274 2.6999H5.4Z"
fill="#969696"
/>
</svg>
);
}

View File

@ -1,15 +0,0 @@
export function FilesIcon() {
return (
<svg
width="16"
height="14"
viewBox="0 0 16 14"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M3.5 3.25V1C3.5 0.801088 3.57902 0.610322 3.71967 0.46967C3.86032 0.329018 4.05109 0.25 4.25 0.25H9.0605L10.5605 1.75H14.75C14.9489 1.75 15.1397 1.82902 15.2803 1.96967C15.421 2.11032 15.5 2.30109 15.5 2.5V10C15.5 10.1989 15.421 10.3897 15.2803 10.5303C15.1397 10.671 14.9489 10.75 14.75 10.75H12.5V13C12.5 13.1989 12.421 13.3897 12.2803 13.5303C12.1397 13.671 11.9489 13.75 11.75 13.75H1.25C1.05109 13.75 0.860322 13.671 0.71967 13.5303C0.579018 13.3897 0.5 13.1989 0.5 13V4C0.5 3.80109 0.579018 3.61032 0.71967 3.46967C0.860322 3.32902 1.05109 3.25 1.25 3.25H3.5ZM3.5 4.75H2V12.25H11V10.75H3.5V4.75Z"
fill="currentColor"
/>
</svg>
);
}

View File

@ -8,7 +8,7 @@ import { classnames } from "../../utils/classnames";
import { Link } from "@tanstack/react-router";
import { HomeIcon } from "./HomeIcon";
import { WalletIcon } from "./WalletIcon";
import { FilesIcon } from "./FilesIcon";
import { FilesIcon } from "../FilesIcon/FilesIcon";
import { NodesIcon } from "./NodesIcon";
import { AnalyticsIcon } from "./AnalyticsIcon";
import { DeviceIcon } from "./DeviceIcon";

View File

@ -0,0 +1,17 @@
.files-page {
display: flex;
flex-wrap: wrap;
gap: 16px;
.files {
margin-bottom: 16px;
flex: 1 1 67%;
}
.column {
display: flex;
flex-direction: column;
gap: 16px;
flex: 1 1 auto;
}
}

View File

@ -1,5 +1,30 @@
import { createFileRoute } from "@tanstack/react-router";
import { Files } from "../../components/Files/Files";
import "./files.css";
import { ErrorBoundary } from "@sentry/react";
import { ErrorPlaceholder } from "../../components/ErrorPlaceholder/ErrorPlaceholder";
import { ManifestFetchCard } from "../../components/ManifestFetch/ManifestFetchCard";
import { UploadCard } from "../../components/UploadCard/UploadCard";
import { Download } from "../../components/Download/Download";
export const Route = createFileRoute("/dashboard/files")({
component: () => <div>Hello /dashboard/files!</div>,
component: () => (
<div className="files-page">
<Files></Files>
<div className="column">
<ErrorBoundary
fallback={({ error }) => (
<ErrorPlaceholder
error={error}
subtitle="Cannot retrieve the data."
/>
)}>
<UploadCard />
</ErrorBoundary>
<Download></Download>
<ManifestFetchCard />
</div>
</div>
),
});

View File

@ -13,6 +13,9 @@ export const Files = {
isImage(type: string | null) {
return type && type.startsWith("image");
},
isVideo(type: string | null) {
return type && type.startsWith("video");
},
type(mimetype: string | null) {
const [type] = mimetype?.split("/") || []
return type