diff --git a/app/components/pinnning-network-banner.tsx b/app/components/pinnning-network-banner.tsx index 98c12a0..2e626cc 100644 --- a/app/components/pinnning-network-banner.tsx +++ b/app/components/pinnning-network-banner.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo } from "react"; +import { useMemo } from "react"; import { Accordion, AccordionContent, @@ -6,34 +6,35 @@ import { AccordionTrigger, } from "./ui/accordion"; import { Progress } from "./ui/progress"; -import { usePinning } from "~/hooks/usePinnning"; -import { IPinningData } from "~/providers/PinningProvider"; +import { usePinning, useUnpinMutation } from "~/hooks/usePinnning"; import { Tabs, TabsTrigger, TabsList, TabsContent } from "./ui/tabs"; import { Button } from "./ui/button"; import { Cross2Icon } from "@radix-ui/react-icons"; +import { PinningStatus } from "~/data/pinning"; export const PinningNetworkBanner = () => { - const { cidList } = usePinning(); + const { data} = usePinning(); + // TODO: Adapt to real API const itemsLeft = useMemo( - () => cidList.filter((item) => item.progress < 100), - [cidList], + () => data?.items.filter((item: PinningStatus) => item.status.includes("inprogress")) || [], + [data], ); const completedItems = useMemo( - () => cidList.filter((item) => item.progress === 100), - [cidList], + () => data?.items.filter((item: PinningStatus) => item.status.includes("completed")) || [], + [data], ); return (
- {`${completedItems.length}/${cidList.length} items completed`} + {`${completedItems.length}/${data?.items.length} items completed`} @@ -43,8 +44,8 @@ export const PinningNetworkBanner = () => { {itemsLeft.length ? ( - itemsLeft.map((cid) => ( - + itemsLeft.map((item: PinningStatus) => ( + )) ) : (
@@ -54,8 +55,8 @@ export const PinningNetworkBanner = () => { {completedItems.length ? ( - completedItems.map((cid) => ( - + completedItems.map((item: PinningStatus) => ( + )) ) : (
@@ -71,29 +72,21 @@ export const PinningNetworkBanner = () => { ); }; -const PinCidItem = ({ item }: { item: IPinningData }) => { - const { getProgress, onRemoveCidFromList } = usePinning(); - - useEffect(() => { - if (item.progress < 100) { - const intervalId = setInterval(() => { - getProgress(item.cid); - }, 1000); // Adjust the interval time (1000ms = 1 second) as needed - - return () => clearInterval(intervalId); // Clear interval on component unmount - } - }, [getProgress, item]); // Add dependencies to ensure the effect runs correctly +const PinCidItem = ({ item }: { item: PinningStatus }) => { + const { mutate } = useUnpinMutation(); return (
- {item.cid} + {item.id} {item.progress}%
diff --git a/app/data/pinning-provider.ts b/app/data/pinning-provider.ts deleted file mode 100644 index 7ef3c93..0000000 --- a/app/data/pinning-provider.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { SdkProvider } from "~/data/sdk-provider.js"; -import { PinningProcess } from "./pinning"; - -export const pinningProvider = { - getList: () => { - console.log("Not implemented"); - return { - data: [], - total: 0, - }; - }, - getOne: () => { - console.log("Not implemented"); - return Promise.resolve({ - data: { - id: 1, - }, - }); - }, - update: () => { - console.log("Not implemented"); - return Promise.resolve({ - data: {}, - }); - }, - create: () => { - console.log("Not implemented"); - return Promise.resolve({ - data: {}, - }); - }, - deleteOne: () => { - console.log("Not implemented"); - return Promise.resolve({ - data: {}, - }); - }, - getApiUrl: () => "", - custom: () => { - - const pinCid = async (cid: string) => { - return await PinningProcess.pin(cid); - } - - const unpinCid = async (cid: string) => { - console.log("Not Implemented"); - } - - const checkCid = async (cid: string) => { - console.log("Not Implemented"); - } - - const checkCidProgress = (cid: string) => { - const progressGenerator = PinningProcess.pollProgress(cid); - - return progressGenerator.next(); - } - - return { - pinCid, - unpinCid, - checkCid, - checkCidProgress - } - }, -} satisfies SdkProvider; diff --git a/app/data/pinning.ts b/app/data/pinning.ts index d5643f6..f7dcf31 100644 --- a/app/data/pinning.ts +++ b/app/data/pinning.ts @@ -1,4 +1,4 @@ -interface PinningStatus { +export interface PinningStatus { id: string; progress: number; status: 'inprogress' | 'completed' | 'stale'; @@ -30,13 +30,26 @@ export class PinningProcess { return { success: true, message: "Pinning process started" }; } - static *pollProgress(id: string): Generator { - let status = PinningProcess.instances.get(id); - while (status && status.status !== 'completed') { - yield status; - status = PinningProcess.instances.get(id); + static async unpin(id: string): Promise<{ success: boolean; message: string }> { + if (!PinningProcess.instances.has(id)) { + return { success: false, message: "ID not found or not being processed" }; } - yield status ?? null; // Yield the final status, could be null if ID doesn't exist + + PinningProcess.instances.delete(id); + return { success: true, message: "Pinning process removed" } + } + + static *pollAllProgress(): Generator { + let allStatuses = Array.from(PinningProcess.instances.values()); + let inProgress = allStatuses.some(status => status.status !== 'completed'); + + while (inProgress) { + yield allStatuses; + allStatuses = Array.from(PinningProcess.instances.values()); + inProgress = allStatuses.some(status => status.status !== 'completed'); + } + + yield allStatuses ?? []; // Yield the final statuses } } diff --git a/app/data/providers.ts b/app/data/providers.ts index 51453d3..09e5696 100644 --- a/app/data/providers.ts +++ b/app/data/providers.ts @@ -4,7 +4,6 @@ import {Sdk} from "@lumeweb/portal-sdk"; import {accountProvider} from "~/data/account-provider.js"; import type {SdkProvider} from "~/data/sdk-provider.js"; import {createPortalAuthProvider} from "~/data/auth-provider.js"; -import { pinningProvider } from "./pinning-provider"; interface DataProviders { default: SdkProvider; @@ -25,7 +24,6 @@ export function getProviders(sdk: Sdk) { default: accountProvider, auth: createPortalAuthProvider(sdk), files: fileProvider, - pinning: pinningProvider }; return providers; diff --git a/app/hooks/usePinnning.ts b/app/hooks/usePinnning.ts index 9f55145..324b8a0 100644 --- a/app/hooks/usePinnning.ts +++ b/app/hooks/usePinnning.ts @@ -1,62 +1,73 @@ -import { useCustom, useNotification } from "@refinedev/core"; -import { usePinningContext } from "~/providers/PinningProvider"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { PinningProcess } from "~/data/pinning"; -export const usePinning = () => { - const { open } = useNotification(); - const { data: cidList, setData } = usePinningContext(); +// TODO: Adapt to real API - const { data } = useCustom({ - // useCustom requires URL and METHOD params - url: "", - method: "get", - dataProviderName: "pinning", +export const usePinMutation = () => { + const queryClient = useQueryClient(); + const { mutate } = useMutation({ + mutationFn: async ({ cid }) => { + const response = await PinningProcess.pin(cid); + + if (!response.success) { + open?.({ + type: "destructive", + message: "Erorr pinning " + cid, + description: response.message, + }); + } + + queryClient.invalidateQueries({ queryKey: ["pin-progress"] }) + } }); - const onPin = async (cid: string) => { - if (!data?.pinCid) return; + return { mutate } +} - const response = await data?.pinCid(cid); +export const useUnpinMutation = () => { + const queryClient = useQueryClient(); + const { mutate } = useMutation({ + mutationFn: async ({ cid }) => { + const response = await PinningProcess.unpin(cid); - if (response.success) { - setData((prev) => [...prev, { cid, progress: 0 }]); - } else { - open?.({ - type: "destrunctive", - message: "Erorr pinning " + cid, - description: response.message, - }); + if (!response.success) { + open?.({ + type: "destructive", + message: "Erorr pinning " + cid, + description: response.message, + }); + } + + queryClient.invalidateQueries({ queryKey: ["pin-progress"] }) } - }; + }); - const getProgress = (cid: string) => { - if (!data?.checkCidProgress) return; + return { mutate } +} - const response = data?.checkCidProgress(cid); +export const usePinning = () => { + const { data } = useQuery({ + queryKey: ["pin-progress"], + refetchInterval: (query) => { + if (!query.state.data || !query.state.data.items.length) { + return false; + } - if (!response.done) { - setData((prev) => - prev.map((cidInfo) => { - const newData = cidInfo; + return 1000; + }, + refetchIntervalInBackground: true, + queryFn: () => { + const response = PinningProcess.pollAllProgress(); + const result = response.next(); - if (cidInfo.cid === cid) { - newData.progress = response.value.progress; - - return newData; - } - return cidInfo; - }), - ); - } - }; - - const onRemoveCidFromList = (cid: string) => { - setData((prev) => prev.filter((item) => item.cid !== cid)); - }; + return { + items: result.value || [], + lastUpdated: Date.now() + }; + }, + }) return { - cidList, - onPin, - getProgress, - onRemoveCidFromList, + data }; }; diff --git a/app/root.tsx b/app/root.tsx index 0d1d3cb..9ecc103 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -13,11 +13,14 @@ import { Toaster } from "~/components/ui/toaster"; import {getProviders} from "~/data/providers.js"; import {Sdk} from "@lumeweb/portal-sdk"; import resources from "~/data/resources.js"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; export const links: LinksFunction = () => [ { rel: "stylesheet", href: stylesheet }, ]; +const queryClient = new QueryClient(); + export function Layout({children}: { children: React.ReactNode }) { return ( @@ -42,22 +45,23 @@ export default function App() { const sdk = Sdk.create(import.meta.env.VITE_PORTAL_URL) const providers = getProviders(sdk); return ( - - - - - + + + + + + + ); } diff --git a/app/routes/file-manager/columns.tsx b/app/routes/file-manager/columns.tsx index e56c63f..57c8dee 100644 --- a/app/routes/file-manager/columns.tsx +++ b/app/routes/file-manager/columns.tsx @@ -11,7 +11,7 @@ import { DropdownMenuTrigger, } from "~/components/ui/dropdown-menu"; -import { usePinning } from "~/hooks/usePinnning"; +import { usePinMutation } from "~/hooks/usePinnning"; import { cn } from "~/utils"; // This type is used to define the shape of our data. @@ -25,7 +25,7 @@ export type File = { const CreatedOnCell = ({ row }: { row: Row }) => { // const { open } = useNotification(); - const { onPin } = usePinning(); + const { mutate } = usePinMutation(); return (
@@ -43,7 +43,9 @@ const CreatedOnCell = ({ row }: { row: Row }) => { { console.log(`Adding ${row.getValue("cid")} for pinning...`); - onPin(row.getValue("cid")); + mutate({ + cid: row.getValue("cid"), + }); }}> Pin CID diff --git a/package.json b/package.json index c5bc542..665f4f3 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "@refinedev/remix-router": "https://gitpkg.now.sh/LumeWeb/refine/packages/remix?remix", "@remix-run/node": "^2.8.0", "@remix-run/react": "^2.8.0", + "@tanstack/react-query": "^5.28.6", "@tanstack/react-table": "^8.13.2", "@uppy/core": "^3.9.3", "@uppy/tus": "^3.5.3",