From 80d35004b6398ba5a4cc771561aaf897da456695 Mon Sep 17 00:00:00 2001 From: Arnaud Date: Thu, 26 Sep 2024 15:50:23 +0200 Subject: [PATCH 1/3] Refactor to make names more consistent --- .../Availailibities/Availabilities.tsx | 100 ----------------- ...stStepper.css => StorageRequestCreate.css} | 2 +- ...stStepper.tsx => StorageRequestCreate.tsx} | 12 +-- ...uestDone.css => StorageRequestSuccess.css} | 0 ...uestDone.tsx => StorageRequestSuccess.tsx} | 7 +- .../dashboard/availabilities.css} | 0 src/routes/dashboard/availabilities.tsx | 101 +++++++++++++++++- src/routes/dashboard/purchases.tsx | 8 +- src/sdk/codex.ts | 17 +-- 9 files changed, 117 insertions(+), 130 deletions(-) delete mode 100644 src/components/Availailibities/Availabilities.tsx rename src/components/StorageRequestSetup/{StorageRequestStepper.css => StorageRequestCreate.css} (66%) rename src/components/StorageRequestSetup/{StorageRequestStepper.tsx => StorageRequestCreate.tsx} (90%) rename src/components/StorageRequestSetup/{StorageRequestDone.css => StorageRequestSuccess.css} (100%) rename src/components/StorageRequestSetup/{StorageRequestDone.tsx => StorageRequestSuccess.tsx} (84%) rename src/{components/Availailibities/Availabilities.css => routes/dashboard/availabilities.css} (100%) diff --git a/src/components/Availailibities/Availabilities.tsx b/src/components/Availailibities/Availabilities.tsx deleted file mode 100644 index 257d7de..0000000 --- a/src/components/Availailibities/Availabilities.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import { - SpaceAllocation, - Spinner, -} from "@codex-storage/marketplace-ui-components"; -import { useQuery } from "@tanstack/react-query"; -import { Promises } from "../../utils/promises"; -import { AvailabilityCreate } from "../Availability/AvailabilityCreate"; -import { CodexSdk } from "../../sdk/codex"; -import { Strings } from "../../utils/strings"; -import { AvailabilitiesTable } from "../Availability/AvailabilitiesTable"; -import "./Availabilities.css"; - -const defaultSpace = { - quotaMaxBytes: 0, - quotaReservedBytes: 0, - quotaUsedBytes: 0, - totalBlocks: 0, -}; - -export function Availabilities() { - { - // Error will be catched in ErrorBounday - const { data: availabilities = [], isPending } = useQuery({ - queryFn: () => - CodexSdk.marketplace - .availabilities() - .then((s) => Promises.rejectOnError(s)) - .then((res) => res.sort((a, b) => b.totalSize - a.totalSize)), - queryKey: ["availabilities"], - initialData: [], - - // No need to retry because if the connection to the node - // is back again, all the queries will be invalidated. - retry: false, - - // The client node should be local, so display the cache value while - // making a background request looks good. - staleTime: 0, - - // Refreshing when focus returns can be useful if a user comes back - // to the UI after performing an operation in the terminal. - refetchOnWindowFocus: true, - }); - - // Error will be catched in ErrorBounday - const { data: space = defaultSpace } = useQuery({ - queryFn: () => - CodexSdk.data.space().then((s) => Promises.rejectOnError(s)), - queryKey: ["space"], - initialData: defaultSpace, - - // No need to retry because if the connection to the node - // is back again, all the queries will be invalidated. - retry: false, - - // The client node should be local, so display the cache value while - // making a background request looks good. - staleTime: 0, - - // Refreshing when focus returns can be useful if a user comes back - // to the UI after performing an operation in the terminal. - refetchOnWindowFocus: true, - }); - - const allocation = availabilities - .map((a) => ({ - title: Strings.shortId(a.id), - size: a.totalSize, - })) - .slice(0, 6); - - return ( -
-
- {isPending ? ( -
- -
- ) : ( -
- -
- )} - -
-
- -
-
- -
-
-
-
- ); - } -} diff --git a/src/components/StorageRequestSetup/StorageRequestStepper.css b/src/components/StorageRequestSetup/StorageRequestCreate.css similarity index 66% rename from src/components/StorageRequestSetup/StorageRequestStepper.css rename to src/components/StorageRequestSetup/StorageRequestCreate.css index 64ac1c3..09e57a1 100644 --- a/src/components/StorageRequestSetup/StorageRequestStepper.css +++ b/src/components/StorageRequestSetup/StorageRequestCreate.css @@ -1,5 +1,5 @@ @media (min-width: 801px) { - .storageRequest-stepper { + .storageRequestCreate { min-width: 700px; } } diff --git a/src/components/StorageRequestSetup/StorageRequestStepper.tsx b/src/components/StorageRequestSetup/StorageRequestCreate.tsx similarity index 90% rename from src/components/StorageRequestSetup/StorageRequestStepper.tsx rename to src/components/StorageRequestSetup/StorageRequestCreate.tsx index 7401520..a352b32 100644 --- a/src/components/StorageRequestSetup/StorageRequestStepper.tsx +++ b/src/components/StorageRequestSetup/StorageRequestCreate.tsx @@ -1,4 +1,4 @@ -import { StorageRequestFileChooser } from "../../components/StorageRequestSetup/StorageRequestFileChooser"; +import { StorageRequestFileChooser } from "./StorageRequestFileChooser"; import { useEffect, useRef, useState } from "react"; import { WebStorage } from "../../utils/web-storage"; import { STEPPER_DURATION } from "../../utils/constants"; @@ -10,11 +10,11 @@ import { Stepper, useStepperReducer, } from "@codex-storage/marketplace-ui-components"; -import { StorageRequestDone } from "./StorageRequestDone"; +import { StorageRequestSuccess } from "./StorageRequestSuccess"; import { Times } from "../../utils/times"; import { useStorageRequestMutation } from "./useStorageRequestMutation"; import { Plus } from "lucide-react"; -import "./StorageRequestStepper.css"; +import "./StorageRequestCreate.css"; import { StorageRequestError } from "./StorageRequestError"; const CONFIRM_STATE = 2; @@ -31,7 +31,7 @@ const defaultStorageRequest: StorageRequest = { expiration: 300, }; -export function StorageRequestStepper() { +export function StorageRequestCreate() { const [storageRequest, setStorageRequest] = useState( defaultStorageRequest ); @@ -60,7 +60,7 @@ export function StorageRequestStepper() { const components = [ StorageRequestFileChooser, StorageRequestReview, - error ? StorageRequestError : StorageRequestDone, + error ? StorageRequestError : StorageRequestSuccess, ]; const onNextStep = async (step: number) => { @@ -133,7 +133,7 @@ export function StorageRequestStepper() { duration={STEPPER_DURATION} onNextStep={onNextStep} backLabel={backLabel} - className="storageRequest-stepper" + className="storageRequestCreate" nextLabel={nextLabel}> { dispatch({ type: "toggle-buttons", diff --git a/src/components/Availailibities/Availabilities.css b/src/routes/dashboard/availabilities.css similarity index 100% rename from src/components/Availailibities/Availabilities.css rename to src/routes/dashboard/availabilities.css diff --git a/src/routes/dashboard/availabilities.tsx b/src/routes/dashboard/availabilities.tsx index 368b6d8..b4850f4 100644 --- a/src/routes/dashboard/availabilities.tsx +++ b/src/routes/dashboard/availabilities.tsx @@ -1,7 +1,106 @@ import { createFileRoute } from "@tanstack/react-router"; -import { Availabilities } from "../../components/Availailibities/Availabilities"; import { ErrorBoundary } from "@sentry/react"; import { ErrorPlaceholder } from "../../components/ErrorPlaceholder/ErrorPlaceholder"; +import { + SpaceAllocation, + Spinner, +} from "@codex-storage/marketplace-ui-components"; +import { useQuery } from "@tanstack/react-query"; +import { Promises } from "../../utils/promises"; +import { CodexSdk } from "../../sdk/codex"; +import { Strings } from "../../utils/strings"; +import "./availabilities.css"; +import { AvailabilitiesTable } from "../../components/Availability/AvailabilitiesTable"; +import { AvailabilityCreate } from "../../components/Availability/AvailabilityCreate"; + +const defaultSpace = { + quotaMaxBytes: 0, + quotaReservedBytes: 0, + quotaUsedBytes: 0, + totalBlocks: 0, +}; + +export function Availabilities() { + { + // Error will be catched in ErrorBounday + const { data: availabilities = [], isPending } = useQuery({ + queryFn: () => + CodexSdk.marketplace + .availabilities() + .then((s) => Promises.rejectOnError(s)) + .then((res) => res.sort((a, b) => b.totalSize - a.totalSize)), + queryKey: ["availabilities"], + initialData: [], + + // No need to retry because if the connection to the node + // is back again, all the queries will be invalidated. + retry: false, + + // The client node should be local, so display the cache value while + // making a background request looks good. + staleTime: 0, + + // Refreshing when focus returns can be useful if a user comes back + // to the UI after performing an operation in the terminal. + refetchOnWindowFocus: true, + }); + + // Error will be catched in ErrorBounday + const { data: space = defaultSpace } = useQuery({ + queryFn: () => + CodexSdk.data.space().then((s) => Promises.rejectOnError(s)), + queryKey: ["space"], + initialData: defaultSpace, + + // No need to retry because if the connection to the node + // is back again, all the queries will be invalidated. + retry: false, + + // The client node should be local, so display the cache value while + // making a background request looks good. + staleTime: 0, + + // Refreshing when focus returns can be useful if a user comes back + // to the UI after performing an operation in the terminal. + refetchOnWindowFocus: true, + }); + + const allocation = availabilities + .map((a) => ({ + title: Strings.shortId(a.id), + size: a.totalSize, + })) + .slice(0, 6); + + return ( +
+
+ {isPending ? ( +
+ +
+ ) : ( +
+ +
+ )} + +
+
+ +
+
+ +
+
+
+
+ ); + } +} export const Route = createFileRoute("/dashboard/availabilities")({ component: () => ( diff --git a/src/routes/dashboard/purchases.tsx b/src/routes/dashboard/purchases.tsx index 54c6173..371afb3 100644 --- a/src/routes/dashboard/purchases.tsx +++ b/src/routes/dashboard/purchases.tsx @@ -2,7 +2,7 @@ import { useQuery } from "@tanstack/react-query"; import { createFileRoute } from "@tanstack/react-router"; import { CodexSdk } from "../../sdk/codex"; import { Cell, Spinner, Table } from "@codex-storage/marketplace-ui-components"; -import { StorageRequestStepper } from "../../components/StorageRequestSetup/StorageRequestStepper"; +import { StorageRequestCreate } from "../../components/StorageRequestSetup/StorageRequestCreate"; import "./purchases.css"; import { FileCell } from "../../components/FileCellRender/FileCell"; import { CustomStateCellRender } from "../../components/CustomStateCellRender/CustomStateCellRender"; @@ -66,12 +66,10 @@ const Purchases = () => { ]; }) || []; - // TODO make name uniforms - return (
- +
@@ -79,8 +77,6 @@ const Purchases = () => { ); }; -// TODO make uniforms for availabilities - export const Route = createFileRoute("/dashboard/purchases")({ component: () => ( diff --git a/src/sdk/codex.ts b/src/sdk/codex.ts index e403635..491afeb 100644 --- a/src/sdk/codex.ts +++ b/src/sdk/codex.ts @@ -23,20 +23,11 @@ export const CodexSdk = { return WebStorage.set("codex-node-url", url); }, - // TODO Change this - get debug() { - return client.debug; - }, + debug: client.debug, - get data() { - return client.data; - }, + data: client.data, - get node() { - return client.node; - }, + node: client.node, - get marketplace() { - return client.marketplace; - }, + marketplace: client.marketplace, }; From 14dac86dabf98bbf5c04d691719c31f85572f685 Mon Sep 17 00:00:00 2001 From: Arnaud Date: Thu, 26 Sep 2024 15:53:37 +0200 Subject: [PATCH 2/3] Move the 'mock' data into a proxy object while waiting for the API to provide the metadata --- .../StorageRequestFileChooser.tsx | 8 +- src/hooks/useData.tsx | 84 +++------- src/proxy.ts | 147 ++++++++++++++++++ vite.config.ts | 6 + 4 files changed, 172 insertions(+), 73 deletions(-) create mode 100644 src/proxy.ts diff --git a/src/components/StorageRequestSetup/StorageRequestFileChooser.tsx b/src/components/StorageRequestSetup/StorageRequestFileChooser.tsx index 8a926f4..6e17b48 100644 --- a/src/components/StorageRequestSetup/StorageRequestFileChooser.tsx +++ b/src/components/StorageRequestSetup/StorageRequestFileChooser.tsx @@ -36,13 +36,7 @@ export function StorageRequestFileChooser({ onStorageRequestChange({ cid: value }); }; - const onSuccess = (data: string, file: File) => { - // TODO Move this to proxy object - WebStorage.set(data, { - type: file.type, - name: file.name, - }); - + const onSuccess = (data: string) => { onStorageRequestChange({ cid: data }); }; diff --git a/src/hooks/useData.tsx b/src/hooks/useData.tsx index 835dc4d..bf4a815 100644 --- a/src/hooks/useData.tsx +++ b/src/hooks/useData.tsx @@ -1,76 +1,28 @@ import { useQuery } from "@tanstack/react-query"; -import { FilesStorage } from "../utils/file-storage"; import { CodexSdk } from "../sdk/codex"; -import * as Sentry from "@sentry/browser"; -import { CodexDataContent } from "@codex-storage/sdk-js"; +import { CodexDataResponse } from "@codex-storage/sdk-js"; +import { Promises } from "../utils/promises"; export function useData() { - const { data = [] } = useQuery({ - queryFn: (): Promise => { - // TODO refactor - return Promise.resolve().then(async () => { - const res = await CodexSdk.data.cids(); + const { data = { content: [] } satisfies CodexDataResponse } = + useQuery({ + queryFn: (_) => + CodexSdk.data.cids().then((res) => Promises.rejectOnError(res)), + queryKey: ["cids"], - if (res.error) { - if (import.meta.env.PROD) { - Sentry.captureException(res.data); - } - return []; - } + initialData: { content: [] } satisfies CodexDataResponse, - const metadata = await FilesStorage.list(); + // No need to retry because if the connection to the node + // is back again, all the queries will be invalidated. + retry: false, - return res.data.content.map((content, index) => { - if (content.manifest.filename) { - return content; - } + // The client node should be local, so display the cache value while + // making a background request looks good. + staleTime: 0, - const value = metadata.find(([cid]) => content.cid === cid); + // Don't expect something new when coming back to the UI + refetchOnWindowFocus: false, + }); - if (!value) { - return { - cid: content.cid, - manifest: { - ...content.manifest, - mimetype: "N/A", - uploadedAt: new Date(0, 0, 0, 0, 0, 0).toJSON(), - filename: "N/A" + index, - }, - }; - } - - const { - mimetype = "", - name = "", - uploadedAt = new Date(0, 0, 0, 0, 0, 0).toJSON(), - } = value[1]; - - return { - cid: content.cid, - manifest: { - ...content.manifest, - mimetype, - filename: name, - uploadedAt: uploadedAt, - }, - }; - }); - }); - }, - queryKey: ["cids"], - initialData: [], - - // No need to retry because if the connection to the node - // is back again, all the queries will be invalidated. - retry: false, - - // The client node should be local, so display the cache value while - // making a background request looks good. - staleTime: 0, - - // Don't expect something new when coming back to the UI - refetchOnWindowFocus: false, - }); - - return data; + return data.content; } diff --git a/src/proxy.ts b/src/proxy.ts new file mode 100644 index 0000000..46c5e2f --- /dev/null +++ b/src/proxy.ts @@ -0,0 +1,147 @@ +import { + CodexData, + CodexDataResponse, + CodexMarketplace, + SafeValue, + UploadResponse, +} from "@codex-storage/sdk-js"; +import { CodexSdk as Sdk } from "./sdk/codex"; +import { Promises } from "./utils/promises"; +import { WebStorage } from "./utils/web-storage"; +import * as Sentry from "@sentry/browser"; +import { FilesStorage } from "./utils/file-storage"; + +class CodexDataMock extends CodexData { + override upload( + file: File, + onProgress?: (loaded: number, total: number) => void + ): Promise { + const res = super.upload(file, onProgress); + + return res.then(({ result, abort }) => { + return { + abort, + result: result.then((safe) => { + if (!safe.error) { + return WebStorage.set(safe.data, { + type: file.type, + name: file.name, + }).then(() => safe); + } + + return safe; + }), + }; + }); + } + + override async cids(): Promise> { + const res = await super.cids(); + + if (res.error) { + return res; + } + + const metadata = await FilesStorage.list(); + + const content = res.data.content.map((content, index) => { + if (content.manifest.filename) { + return content; + } + + const value = metadata.find(([cid]) => content.cid === cid); + + if (!value) { + return { + cid: content.cid, + manifest: { + ...content.manifest, + mimetype: "N/A", + uploadedAt: new Date(0, 0, 0, 0, 0, 0).toJSON(), + filename: "N/A" + index, + }, + }; + } + + const { + mimetype = "", + name = "", + uploadedAt = new Date(0, 0, 0, 0, 0, 0).toJSON(), + } = value[1]; + + return { + cid: content.cid, + manifest: { + ...content.manifest, + mimetype, + filename: name, + uploadedAt: uploadedAt, + }, + }; + }); + + return { error: false, data: { content } }; + } +} + +class CodexMarketplaceMock extends CodexMarketplace { + // override createStorageRequest( + // input: CodexCreateStorageRequestInput + // ): Promise> { + // return Promise.resolve({ + // error: true, + // data: { + // message: "C'est balo", + // }, + // }); + // } + // override createAvailability(): Promise< + // SafeValue + // > { + // return Promise.resolve({ + // error: true, + // data: { + // message: "C'est balo", + // }, + // }); + // } + // override reservations(): Promise> { + // return Promise.resolve({ + // error: false, + // data: [ + // { + // id: "0x123456789", + // availabilityId: "0x12345678910", + // requestId: "0x1234567891011", + // /** + // * Size in bytes + // */ + // size: 500_000_000 + "", + // /** + // * Slot Index as hexadecimal string + // */ + // slotIndex: "2", + // }, + // { + // id: "0x987654321", + // availabilityId: "0x9876543210", + // requestId: "0x98765432100", + // /** + // * Size in bytes + // */ + // size: 500_000_000 + "", + // /** + // * Slot Index as hexadecimal string + // */ + // slotIndex: "1", + // }, + // ], + // }); + // } +} + +export const CodexSdk = { + ...Sdk, + marketplace: new CodexMarketplaceMock(import.meta.env.VITE_CODEX_API_URL), + data: new CodexDataMock(import.meta.env.VITE_CODEX_API_URL), +}; diff --git a/vite.config.ts b/vite.config.ts index 2db29db..395f15e 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -17,4 +17,10 @@ export default defineConfig({ }, }, }, + resolve: { + alias: { + "../sdk/codex": "../proxy", + "../../sdk/codex": "../../proxy", + }, + }, }); From 42a25814d072ce29df9f8018e3dc2e0a22c49c80 Mon Sep 17 00:00:00 2001 From: Arnaud Date: Thu, 26 Sep 2024 16:02:32 +0200 Subject: [PATCH 3/3] Fix calculation --- .../Availability/AvailabilitySpaceAllocation.tsx | 7 +++---- src/components/Availability/availability.domain.ts | 11 +++++------ 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/components/Availability/AvailabilitySpaceAllocation.tsx b/src/components/Availability/AvailabilitySpaceAllocation.tsx index 86cf24e..79bbe7c 100644 --- a/src/components/Availability/AvailabilitySpaceAllocation.tsx +++ b/src/components/Availability/AvailabilitySpaceAllocation.tsx @@ -1,6 +1,5 @@ import { CodexNodeSpace } from "@codex-storage/sdk-js"; import { AvailabilityState } from "./types"; -import { GB, TB } from "../../utils/constants"; import { SpaceAllocation } from "@codex-storage/marketplace-ui-components"; import "./AvailabilitySpaceAllocation.css"; import { availabilityUnit } from "./availability.domain"; @@ -24,15 +23,15 @@ export function AvailabilitySpaceAllocation({ availability, space }: Props) { const spaceData = [ { title: "Space allocated", - size: allocated, + size: Math.trunc(allocated), }, { title: "New space allocation", - size: size, + size: Math.trunc(size), }, { title: "Remaining space", - size: remaining, + size: Math.trunc(remaining), }, ]; diff --git a/src/components/Availability/availability.domain.ts b/src/components/Availability/availability.domain.ts index 3175fa5..909f326 100644 --- a/src/components/Availability/availability.domain.ts +++ b/src/components/Availability/availability.domain.ts @@ -5,16 +5,15 @@ import { AvailabilityState } from "./types"; export const availabilityUnit = (unit: "gb" | "tb") => unit === "gb" ? GB : TB; -export const availabilityMax = (space: CodexNodeSpace, unit: "gb" | "tb") => { - const bytes = availabilityUnit(unit); - return space.quotaMaxBytes / bytes - space.quotaReservedBytes / bytes; -}; +export const availabilityMax = (space: CodexNodeSpace) => + space.quotaMaxBytes - space.quotaReservedBytes - space.quotaUsedBytes; export const isAvailabilityValid = ( - totalSize: string | number, + availability: AvailabilityState, max: number ) => { - const size = parseFloat(totalSize.toString()); + const unit = availabilityUnit(availability.totalSizeUnit); + const size = parseFloat(availability.totalSize.toString()) * unit; return size > 0 && size <= max; };