fix: Removed PinningProvider and used react-query directly

This commit is contained in:
Tania Gutierrez 2024-03-21 23:15:56 -04:00
parent 39a8789f95
commit 8b8fa967d0
Signed by: riobuenoDevelops
GPG Key ID: 53133EB28EB7E801
8 changed files with 125 additions and 169 deletions

View File

@ -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 (
<div
className={`border border-border rounded-lg absolute w-1/3 bottom-4 right-4 ${
!cidList.length ? "hidden" : "block"
!data?.items.length ? "hidden" : "block"
}`}>
<Accordion type="single" defaultValue="item-1" collapsible>
<AccordionItem value="item-1">
<AccordionTrigger className="font-bold bg-primary px-4 rounded-tr-lg rounded-tl-lg">
{`${completedItems.length}/${cidList.length} items completed`}
{`${completedItems.length}/${data?.items.length} items completed`}
</AccordionTrigger>
<AccordionContent>
<Tabs className="w-full" defaultValue="inProgress">
@ -43,8 +44,8 @@ export const PinningNetworkBanner = () => {
</TabsList>
<TabsContent value="inProgress">
{itemsLeft.length ? (
itemsLeft.map((cid) => (
<PinCidItem key={cid.cid} item={cid} />
itemsLeft.map((item: PinningStatus) => (
<PinCidItem key={item.id} item={item} />
))
) : (
<div className="text-primary-2 text-sm flex justify-center items-center h-10">
@ -54,8 +55,8 @@ export const PinningNetworkBanner = () => {
</TabsContent>
<TabsContent value="completed">
{completedItems.length ? (
completedItems.map((cid) => (
<PinCidItem key={cid.cid} item={cid} />
completedItems.map((item: PinningStatus) => (
<PinCidItem key={item.id} item={item} />
))
) : (
<div className="text-muted text-sm flex justify-center items-center h-10">
@ -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 (
<div className="px-4 mb-4">
<div className="flex justify-between items-center rounded-lg h-10 py-2 px-4 hover:bg-primary/50 group">
<span className="font-semibold">{item.cid}</span>
<span className="font-semibold">{item.id}</span>
<span className="group-hover:hidden">{item.progress}%</span>
<div className="gap-x-2 hidden group-hover:flex">
<Button
variant="ghost"
className="p-2 rounded-full h-6"
onClick={() => onRemoveCidFromList(item.cid)}>
onClick={() => mutate({
cid: item.id,
})}>
<Cross2Icon />
</Button>
</div>

View File

@ -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;

View File

@ -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<PinningStatus | null, void, unknown> {
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<PinningStatus[], void, unknown> {
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
}
}

View File

@ -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;

View File

@ -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);
const onPin = async (cid: string) => {
if (!data?.pinCid) return;
const response = await data?.pinCid(cid);
if (response.success) {
setData((prev) => [...prev, { cid, progress: 0 }]);
} else {
if (!response.success) {
open?.({
type: "destrunctive",
type: "destructive",
message: "Erorr pinning " + cid,
description: response.message,
});
}
};
const getProgress = (cid: string) => {
if (!data?.checkCidProgress) return;
const response = data?.checkCidProgress(cid);
if (!response.done) {
setData((prev) =>
prev.map((cidInfo) => {
const newData = cidInfo;
if (cidInfo.cid === cid) {
newData.progress = response.value.progress;
return newData;
queryClient.invalidateQueries({ queryKey: ["pin-progress"] })
}
return cidInfo;
}),
);
}
};
});
const onRemoveCidFromList = (cid: string) => {
setData((prev) => prev.filter((item) => item.cid !== cid));
};
return { mutate }
}
export const useUnpinMutation = () => {
const queryClient = useQueryClient();
const { mutate } = useMutation({
mutationFn: async ({ cid }) => {
const response = await PinningProcess.unpin(cid);
if (!response.success) {
open?.({
type: "destructive",
message: "Erorr pinning " + cid,
description: response.message,
});
}
queryClient.invalidateQueries({ queryKey: ["pin-progress"] })
}
});
return { mutate }
}
export const usePinning = () => {
const { data } = useQuery({
queryKey: ["pin-progress"],
refetchInterval: (query) => {
if (!query.state.data || !query.state.data.items.length) {
return false;
}
return 1000;
},
refetchIntervalInBackground: true,
queryFn: () => {
const response = PinningProcess.pollAllProgress();
const result = response.next();
return {
cidList,
onPin,
getProgress,
onRemoveCidFromList,
items: result.value || [],
lastUpdated: Date.now()
};
},
})
return {
data
};
};

View File

@ -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 (
<html lang="en">
@ -42,14 +45,14 @@ export default function App() {
const sdk = Sdk.create(import.meta.env.VITE_PORTAL_URL)
const providers = getProviders(sdk);
return (
<QueryClientProvider client={queryClient}>
<Refine
authProvider={providers.auth}
routerProvider={routerProvider}
notificationProvider={notificationProvider}
dataProvider={{
default: providers.default,
files: providers.files,
pinning: providers.pinning
files: providers.files
}}
resources={resources}
options={{disableTelemetry: true}}
@ -58,6 +61,7 @@ export default function App() {
<Outlet/>
</SdkContextProvider>
</Refine>
</QueryClientProvider>
);
}

View File

@ -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<File> }) => {
// const { open } = useNotification();
const { onPin } = usePinning();
const { mutate } = usePinMutation();
return (
<div className="flex items-center justify-between">
@ -43,7 +43,9 @@ const CreatedOnCell = ({ row }: { row: Row<File> }) => {
<DropdownMenuItem
onClick={() => {
console.log(`Adding ${row.getValue("cid")} for pinning...`);
onPin(row.getValue("cid"));
mutate({
cid: row.getValue("cid"),
});
}}>
<DrawingPinIcon className="mr-2" />
Pin CID

View File

@ -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",