mirror of
https://github.com/20kaushik02/spotify-manager-web.git
synced 2025-12-06 09:34:07 +00:00
chain populating, type corrections, disable buttons when loading
This commit is contained in:
parent
1e46c087d6
commit
3a2c23e2e3
@ -1,6 +1,7 @@
|
|||||||
import type { AxiosResponse } from "axios";
|
import type { AxiosResponse } from "axios";
|
||||||
import { type apiRespBaseType, axiosInstance } from "./axiosInstance.ts";
|
import { type apiRespBaseType, axiosInstance } from "./axiosInstance.ts";
|
||||||
import {
|
import {
|
||||||
|
opBackfillChainURL,
|
||||||
opBackfillLinkURL,
|
opBackfillLinkURL,
|
||||||
opCreateLinkURL,
|
opCreateLinkURL,
|
||||||
opDeleteLinkURL,
|
opDeleteLinkURL,
|
||||||
@ -43,6 +44,16 @@ interface backfillLinkDataType extends apiRespBaseType {
|
|||||||
localNum: number;
|
localNum: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type backfillChainBodyType = {
|
||||||
|
root: string; // playlistID
|
||||||
|
};
|
||||||
|
|
||||||
|
interface backfillChainDataType extends apiRespBaseType {
|
||||||
|
toAddNum: number;
|
||||||
|
addedNum: number;
|
||||||
|
localNum: number;
|
||||||
|
}
|
||||||
|
|
||||||
type pruneLinkBodyType = createLinkBodyType;
|
type pruneLinkBodyType = createLinkBodyType;
|
||||||
|
|
||||||
interface pruneLinkDataType extends apiRespBaseType {
|
interface pruneLinkDataType extends apiRespBaseType {
|
||||||
@ -105,6 +116,17 @@ export const apiBackfillLink = async (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const apiBackfillChain = async (
|
||||||
|
data: backfillChainBodyType
|
||||||
|
): Promise<AxiosResponse<backfillChainDataType, any>> => {
|
||||||
|
try {
|
||||||
|
const response = await axiosInstance.put(opBackfillChainURL, data);
|
||||||
|
return response;
|
||||||
|
} catch (error: any) {
|
||||||
|
return error.response;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const apiPruneLink = async (
|
export const apiPruneLink = async (
|
||||||
data: pruneLinkBodyType
|
data: pruneLinkBodyType
|
||||||
): Promise<AxiosResponse<pruneLinkDataType, any>> => {
|
): Promise<AxiosResponse<pruneLinkDataType, any>> => {
|
||||||
|
|||||||
@ -15,4 +15,5 @@ export const opUpdateUserDataURL = "api/operations/update";
|
|||||||
export const opCreateLinkURL = "api/operations/link";
|
export const opCreateLinkURL = "api/operations/link";
|
||||||
export const opDeleteLinkURL: "api/operations/link" = opCreateLinkURL;
|
export const opDeleteLinkURL: "api/operations/link" = opCreateLinkURL;
|
||||||
export const opBackfillLinkURL = "api/operations/populate/link";
|
export const opBackfillLinkURL = "api/operations/populate/link";
|
||||||
|
export const opBackfillChainURL = "api/operations/populate/chain";
|
||||||
export const opPruneLinkURL = "api/operations/prune/link";
|
export const opPruneLinkURL = "api/operations/prune/link";
|
||||||
|
|||||||
@ -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
|
// because hooks (namely, useContext) can't be used outside functional components
|
||||||
// so find a better way to pass refreshAuth
|
// so find a better way to pass refreshAuth
|
||||||
|
|
||||||
type APIWrapperProps<T extends apiRespBaseType> = {
|
type APIWrapperProps<B, T extends apiRespBaseType> = {
|
||||||
apiFn(
|
apiFn(data?: B, config?: AxiosRequestConfig): Promise<AxiosResponse<T, any>>;
|
||||||
data?: any,
|
|
||||||
config?: AxiosRequestConfig
|
|
||||||
): Promise<AxiosResponse<T, any>>;
|
|
||||||
refreshAuth: () => Promise<boolean>;
|
refreshAuth: () => Promise<boolean>;
|
||||||
data?: any;
|
data?: B;
|
||||||
config?: AxiosRequestConfig;
|
config?: AxiosRequestConfig;
|
||||||
};
|
};
|
||||||
|
|
||||||
const APIWrapper = async <T extends apiRespBaseType>({
|
const APIWrapper = async <B, T extends apiRespBaseType>({
|
||||||
apiFn,
|
apiFn,
|
||||||
refreshAuth,
|
refreshAuth,
|
||||||
data,
|
data,
|
||||||
config,
|
config,
|
||||||
}: APIWrapperProps<T>): Promise<AxiosResponse<T, any> | null> => {
|
}: APIWrapperProps<B, T>): Promise<AxiosResponse<T, any> | null> => {
|
||||||
let apiResp;
|
let apiResp;
|
||||||
for (let i = 1; i <= maxRetries + 1; i++) {
|
for (let i = 1; i <= maxRetries + 1; i++) {
|
||||||
apiResp = await apiFn(data, config);
|
apiResp = await apiFn(data, config);
|
||||||
|
|||||||
@ -3,11 +3,13 @@ import styles from "./Button.module.css";
|
|||||||
|
|
||||||
type ButtonProps = {
|
type ButtonProps = {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
|
disabled?: boolean;
|
||||||
onClickMethod?: () => void;
|
onClickMethod?: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Button = ({
|
const Button = ({
|
||||||
children,
|
children,
|
||||||
|
disabled = false,
|
||||||
onClickMethod = () => {},
|
onClickMethod = () => {},
|
||||||
}: ButtonProps): React.ReactNode => {
|
}: ButtonProps): React.ReactNode => {
|
||||||
const clickHandler = (e: React.MouseEvent) => {
|
const clickHandler = (e: React.MouseEvent) => {
|
||||||
@ -15,7 +17,12 @@ const Button = ({
|
|||||||
onClickMethod();
|
onClickMethod();
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<button type="button" className={styles.btn_wrapper} onClick={clickHandler}>
|
<button
|
||||||
|
type="button"
|
||||||
|
disabled={disabled}
|
||||||
|
className={styles.btn_wrapper}
|
||||||
|
onClick={clickHandler}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -43,6 +43,7 @@ import {
|
|||||||
|
|
||||||
import { spotifyPlaylistLinkPrefix } from "../../api/paths.ts";
|
import { spotifyPlaylistLinkPrefix } from "../../api/paths.ts";
|
||||||
import {
|
import {
|
||||||
|
apiBackfillChain,
|
||||||
apiBackfillLink,
|
apiBackfillLink,
|
||||||
apiCreateLink,
|
apiCreateLink,
|
||||||
apiDeleteLink,
|
apiDeleteLink,
|
||||||
@ -154,7 +155,6 @@ const Graph = (): React.ReactNode => {
|
|||||||
|
|
||||||
const onFlowInit: OnInit = (_instance) => {
|
const onFlowInit: OnInit = (_instance) => {
|
||||||
console.debug("flow loaded");
|
console.debug("flow loaded");
|
||||||
console.log(selectedNodeID);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// base event handling
|
// base event handling
|
||||||
@ -252,14 +252,13 @@ const Graph = (): React.ReactNode => {
|
|||||||
[refreshAuth]
|
[refreshAuth]
|
||||||
);
|
);
|
||||||
|
|
||||||
// backfill link
|
|
||||||
const backfillLink = async () => {
|
const backfillLink = async () => {
|
||||||
if (selectedEdgeID === "") {
|
if (selectedEdgeID === "") {
|
||||||
showWarnToastNotification("Select an edge!");
|
showWarnToastNotification("Select a link!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const selectedEdge = linkEdges.filter((ed) => ed.id === selectedEdgeID)[0];
|
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);
|
setLoading(true);
|
||||||
const resp = await APIWrapper({
|
const resp = await APIWrapper({
|
||||||
apiFn: apiBackfillLink,
|
apiFn: apiBackfillLink,
|
||||||
@ -280,7 +279,34 @@ const Graph = (): React.ReactNode => {
|
|||||||
return;
|
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 () => {
|
const pruneLink = async () => {
|
||||||
if (selectedEdgeID === "") {
|
if (selectedEdgeID === "") {
|
||||||
showWarnToastNotification("Select an edge!");
|
showWarnToastNotification("Select an edge!");
|
||||||
@ -531,29 +557,29 @@ const Graph = (): React.ReactNode => {
|
|||||||
<Panel position="top-right">{loading && <SimpleLoader />}</Panel>
|
<Panel position="top-right">{loading && <SimpleLoader />}</Panel>
|
||||||
</ReactFlow>
|
</ReactFlow>
|
||||||
<div className={`${styles.operations_wrapper} custom_scrollbar`}>
|
<div className={`${styles.operations_wrapper} custom_scrollbar`}>
|
||||||
<Button onClickMethod={backfillLink}>
|
<Button disabled={loading} onClickMethod={backfillLink}>
|
||||||
<PiSupersetOf size={36} />
|
<PiSupersetOf size={36} />
|
||||||
Backfill Link
|
Backfill Link
|
||||||
</Button>
|
</Button>
|
||||||
<Button>
|
<Button disabled={loading} onClickMethod={backfillChain}>
|
||||||
<PiSupersetOf size={36} />
|
<PiSupersetOf size={36} />
|
||||||
Backfill Chain
|
Backfill Chain
|
||||||
</Button>
|
</Button>
|
||||||
<hr className={styles.divider} />
|
<hr className={styles.divider} />
|
||||||
<Button onClickMethod={pruneLink}>
|
<Button disabled={loading} onClickMethod={pruneLink}>
|
||||||
<PiSubsetOf size={36} />
|
<PiSubsetOf size={36} />
|
||||||
Prune Link
|
Prune Link
|
||||||
</Button>
|
</Button>
|
||||||
<Button>
|
<Button disabled={loading}>
|
||||||
<PiSubsetOf size={36} />
|
<PiSubsetOf size={36} />
|
||||||
Prune Link
|
Prune Link
|
||||||
</Button>
|
</Button>
|
||||||
<hr className={styles.divider} />
|
<hr className={styles.divider} />
|
||||||
<Button onClickMethod={() => arrangeLayout("TB")}>
|
<Button disabled={loading} onClickMethod={() => arrangeLayout("TB")}>
|
||||||
<IoIosGitNetwork size={36} />
|
<IoIosGitNetwork size={36} />
|
||||||
Arrange
|
Arrange
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClickMethod={toggleInteractive}>
|
<Button disabled={loading} onClickMethod={toggleInteractive}>
|
||||||
{isInteractive() ? (
|
{isInteractive() ? (
|
||||||
<MdOutlineLock size={36} />
|
<MdOutlineLock size={36} />
|
||||||
) : (
|
) : (
|
||||||
@ -562,14 +588,14 @@ const Graph = (): React.ReactNode => {
|
|||||||
{isInteractive() ? "Lock" : "Unlock"}
|
{isInteractive() ? "Lock" : "Unlock"}
|
||||||
</Button>
|
</Button>
|
||||||
<hr className={styles.divider} />
|
<hr className={styles.divider} />
|
||||||
<Button onClickMethod={updateUserData}>
|
<Button disabled={loading} onClickMethod={updateUserData}>
|
||||||
<span className={styles.icons}>
|
<span className={styles.icons}>
|
||||||
<WiCloudRefresh size={36} />
|
<WiCloudRefresh size={36} />
|
||||||
<AiFillSpotify size={36} />
|
<AiFillSpotify size={36} />
|
||||||
</span>
|
</span>
|
||||||
Sync Spotify
|
Sync Spotify
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClickMethod={refreshGraph}>
|
<Button disabled={loading} onClickMethod={refreshGraph}>
|
||||||
<WiCloudRefresh size={36} />
|
<WiCloudRefresh size={36} />
|
||||||
Refresh Graph
|
Refresh Graph
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user