From 3a2c23e2e3e3bced030124d3b5a3a4d3a9836ae9 Mon Sep 17 00:00:00 2001 From: Kaushik Narayan R Date: Fri, 14 Mar 2025 15:06:40 -0700 Subject: [PATCH] chain populating, type corrections, disable buttons when loading --- src/api/operations.ts | 22 ++++++++++++ src/api/paths.ts | 1 + src/components/APIWrapper/index.tsx | 13 +++----- src/components/Button/index.tsx | 9 ++++- src/pages/Graph/index.tsx | 52 +++++++++++++++++++++-------- 5 files changed, 75 insertions(+), 22 deletions(-) diff --git a/src/api/operations.ts b/src/api/operations.ts index 19ff03a..28e007e 100644 --- a/src/api/operations.ts +++ b/src/api/operations.ts @@ -1,6 +1,7 @@ import type { AxiosResponse } from "axios"; import { type apiRespBaseType, axiosInstance } from "./axiosInstance.ts"; import { + opBackfillChainURL, opBackfillLinkURL, opCreateLinkURL, opDeleteLinkURL, @@ -43,6 +44,16 @@ interface backfillLinkDataType extends apiRespBaseType { localNum: number; } +type backfillChainBodyType = { + root: string; // playlistID +}; + +interface backfillChainDataType extends apiRespBaseType { + toAddNum: number; + addedNum: number; + localNum: number; +} + type pruneLinkBodyType = createLinkBodyType; interface pruneLinkDataType extends apiRespBaseType { @@ -105,6 +116,17 @@ export const apiBackfillLink = async ( } }; +export const apiBackfillChain = async ( + data: backfillChainBodyType +): Promise> => { + try { + const response = await axiosInstance.put(opBackfillChainURL, data); + return response; + } catch (error: any) { + return error.response; + } +}; + export const apiPruneLink = async ( data: pruneLinkBodyType ): Promise> => { diff --git a/src/api/paths.ts b/src/api/paths.ts index 9e34ee1..15e77df 100644 --- a/src/api/paths.ts +++ b/src/api/paths.ts @@ -15,4 +15,5 @@ export const opUpdateUserDataURL = "api/operations/update"; export const opCreateLinkURL = "api/operations/link"; export const opDeleteLinkURL: "api/operations/link" = opCreateLinkURL; export const opBackfillLinkURL = "api/operations/populate/link"; +export const opBackfillChainURL = "api/operations/populate/chain"; export const opPruneLinkURL = "api/operations/prune/link"; diff --git a/src/components/APIWrapper/index.tsx b/src/components/APIWrapper/index.tsx index 48c5d75..ddaf45c 100644 --- a/src/components/APIWrapper/index.tsx +++ b/src/components/APIWrapper/index.tsx @@ -14,22 +14,19 @@ const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); // because hooks (namely, useContext) can't be used outside functional components // so find a better way to pass refreshAuth -type APIWrapperProps = { - apiFn( - data?: any, - config?: AxiosRequestConfig - ): Promise>; +type APIWrapperProps = { + apiFn(data?: B, config?: AxiosRequestConfig): Promise>; refreshAuth: () => Promise; - data?: any; + data?: B; config?: AxiosRequestConfig; }; -const APIWrapper = async ({ +const APIWrapper = async ({ apiFn, refreshAuth, data, config, -}: APIWrapperProps): Promise | null> => { +}: APIWrapperProps): Promise | null> => { let apiResp; for (let i = 1; i <= maxRetries + 1; i++) { apiResp = await apiFn(data, config); diff --git a/src/components/Button/index.tsx b/src/components/Button/index.tsx index 66c2d4c..346824d 100644 --- a/src/components/Button/index.tsx +++ b/src/components/Button/index.tsx @@ -3,11 +3,13 @@ import styles from "./Button.module.css"; type ButtonProps = { children: React.ReactNode; + disabled?: boolean; onClickMethod?: () => void; }; const Button = ({ children, + disabled = false, onClickMethod = () => {}, }: ButtonProps): React.ReactNode => { const clickHandler = (e: React.MouseEvent) => { @@ -15,7 +17,12 @@ const Button = ({ onClickMethod(); }; return ( - ); diff --git a/src/pages/Graph/index.tsx b/src/pages/Graph/index.tsx index b2cecc4..a9af6e6 100644 --- a/src/pages/Graph/index.tsx +++ b/src/pages/Graph/index.tsx @@ -43,6 +43,7 @@ import { import { spotifyPlaylistLinkPrefix } from "../../api/paths.ts"; import { + apiBackfillChain, apiBackfillLink, apiCreateLink, apiDeleteLink, @@ -154,7 +155,6 @@ const Graph = (): React.ReactNode => { const onFlowInit: OnInit = (_instance) => { console.debug("flow loaded"); - console.log(selectedNodeID); }; // base event handling @@ -252,14 +252,13 @@ const Graph = (): React.ReactNode => { [refreshAuth] ); - // backfill link const backfillLink = async () => { if (selectedEdgeID === "") { - showWarnToastNotification("Select an edge!"); + showWarnToastNotification("Select a link!"); return; } const selectedEdge = linkEdges.filter((ed) => ed.id === selectedEdgeID)[0]; - if (!selectedEdge) throw new ReferenceError("no edge selected"); + if (!selectedEdge) throw new ReferenceError("no link selected"); setLoading(true); const resp = await APIWrapper({ apiFn: apiBackfillLink, @@ -280,7 +279,34 @@ const Graph = (): React.ReactNode => { return; }; - // prune link + const backfillChain = async () => { + if (selectedNodeID === "") { + showWarnToastNotification("Select a playlist!"); + return; + } + const selectedNode = playlistNodes.filter( + (nd) => nd.id === selectedNodeID + )[0]; + if (!selectedNode) throw new ReferenceError("no playlist selected"); + setLoading(true); + const resp = await APIWrapper({ + apiFn: apiBackfillChain, + data: { + root: spotifyPlaylistLinkPrefix + selectedNodeID, + }, + refreshAuth, + }); + setLoading(false); + + if (resp?.status === 200) { + if (resp?.data.addedNum < resp?.data.toAddNum) + showWarnToastNotification(resp?.data.message); + else showSuccessToastNotification(resp?.data.message); + return; + } + return; + }; + const pruneLink = async () => { if (selectedEdgeID === "") { showWarnToastNotification("Select an edge!"); @@ -531,29 +557,29 @@ const Graph = (): React.ReactNode => { {loading && }
- -
- -
- -
- -