mirror of
https://github.com/20kaushik02/spotify-manager-web.git
synced 2025-12-06 09:34:07 +00:00
mmm
This commit is contained in:
parent
6733a3be8e
commit
090ba0b085
9
package-lock.json
generated
9
package-lock.json
generated
@ -18,6 +18,7 @@
|
|||||||
"axios": "^1.7.9",
|
"axios": "^1.7.9",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
|
"react-icons": "^5.4.0",
|
||||||
"react-router-dom": "^7.1.1",
|
"react-router-dom": "^7.1.1",
|
||||||
"react-toastify": "^11.0.2",
|
"react-toastify": "^11.0.2",
|
||||||
"web-vitals": "^4.2.4"
|
"web-vitals": "^4.2.4"
|
||||||
@ -14582,6 +14583,14 @@
|
|||||||
"integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==",
|
"integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/react-icons": {
|
||||||
|
"version": "5.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.4.0.tgz",
|
||||||
|
"integrity": "sha512-7eltJxgVt7X64oHh6wSWNwwbKTCtMfK35hcjvJS0yxEAhPM8oUKdS3+kqaW1vicIltw+kR2unHaa12S9pPALoQ==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-is": {
|
"node_modules/react-is": {
|
||||||
"version": "17.0.2",
|
"version": "17.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||||
|
|||||||
@ -13,6 +13,7 @@
|
|||||||
"axios": "^1.7.9",
|
"axios": "^1.7.9",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
|
"react-icons": "^5.4.0",
|
||||||
"react-router-dom": "^7.1.1",
|
"react-router-dom": "^7.1.1",
|
||||||
"react-toastify": "^11.0.2",
|
"react-toastify": "^11.0.2",
|
||||||
"web-vitals": "^4.2.4"
|
"web-vitals": "^4.2.4"
|
||||||
|
|||||||
@ -1,4 +1,8 @@
|
|||||||
.btn_wrapper {
|
.btn_wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
padding: var(--mb-2);
|
padding: var(--mb-2);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import React, { useCallback, useContext, useEffect } from "react";
|
import React, { useCallback, useContext, useEffect, useState } from "react";
|
||||||
import {
|
import {
|
||||||
ReactFlow,
|
ReactFlow,
|
||||||
Controls,
|
Controls,
|
||||||
Background,
|
Background,
|
||||||
useNodesState,
|
|
||||||
useEdgesState,
|
|
||||||
addEdge,
|
addEdge,
|
||||||
|
applyNodeChanges,
|
||||||
|
applyEdgeChanges,
|
||||||
useReactFlow,
|
useReactFlow,
|
||||||
MarkerType,
|
MarkerType,
|
||||||
BackgroundVariant,
|
BackgroundVariant,
|
||||||
@ -15,6 +15,8 @@ import {
|
|||||||
type ReactFlowInstance,
|
type ReactFlowInstance,
|
||||||
type Node,
|
type Node,
|
||||||
type Edge,
|
type Edge,
|
||||||
|
type OnNodesChange,
|
||||||
|
type OnEdgesChange,
|
||||||
type OnConnect,
|
type OnConnect,
|
||||||
} from "@xyflow/react";
|
} from "@xyflow/react";
|
||||||
import Dagre, { type GraphLabel } from "@dagrejs/dagre";
|
import Dagre, { type GraphLabel } from "@dagrejs/dagre";
|
||||||
@ -22,6 +24,9 @@ import Dagre, { type GraphLabel } from "@dagrejs/dagre";
|
|||||||
import "@xyflow/react/dist/style.css";
|
import "@xyflow/react/dist/style.css";
|
||||||
import styles from "./Graph.module.css";
|
import styles from "./Graph.module.css";
|
||||||
|
|
||||||
|
import { IoIosGitNetwork } from "react-icons/io";
|
||||||
|
import { WiCloudRefresh } from "react-icons/wi";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
showErrorToastNotification,
|
showErrorToastNotification,
|
||||||
showInfoToastNotification,
|
showInfoToastNotification,
|
||||||
@ -77,20 +82,31 @@ const proOptions: ProOptions = { hideAttribution: true };
|
|||||||
const Graph = () => {
|
const Graph = () => {
|
||||||
const refreshAuth = useContext(RefreshAuthContext);
|
const refreshAuth = useContext(RefreshAuthContext);
|
||||||
const flowInstance = useReactFlow();
|
const flowInstance = useReactFlow();
|
||||||
const [playlistNodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
const [playlistNodes, setPlaylistNodes] = useState<Node[]>(initialNodes);
|
||||||
const [linkEdges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
const [linkEdges, setLinkEdges] = useState<Edge[]>(initialEdges);
|
||||||
|
|
||||||
const onFlowInit = (instance: ReactFlowInstance) => {
|
const onFlowInit = (_instance: ReactFlowInstance) => {
|
||||||
console.debug("flow loaded");
|
console.debug("flow loaded");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onNodesChange: OnNodesChange = useCallback(
|
||||||
|
(changes) => setPlaylistNodes((nds) => applyNodeChanges(changes, nds)),
|
||||||
|
[setPlaylistNodes]
|
||||||
|
);
|
||||||
|
const onEdgesChange: OnEdgesChange = useCallback(
|
||||||
|
(changes) => setLinkEdges((eds) => applyEdgeChanges(changes, eds)),
|
||||||
|
[setLinkEdges]
|
||||||
|
);
|
||||||
|
|
||||||
const onConnect: OnConnect = useCallback(
|
const onConnect: OnConnect = useCallback(
|
||||||
(params) => {
|
(connection) => {
|
||||||
setEdges((eds) => addEdge(params, eds));
|
setLinkEdges((eds) => addEdge(connection, eds));
|
||||||
console.debug("new connection");
|
console.debug(
|
||||||
console.debug(params);
|
`new connection: ${connection.source} -> ${connection.target}`
|
||||||
|
);
|
||||||
|
// call API to create link
|
||||||
},
|
},
|
||||||
[setEdges]
|
[setLinkEdges]
|
||||||
);
|
);
|
||||||
|
|
||||||
type getLayoutedElementsOpts = {
|
type getLayoutedElementsOpts = {
|
||||||
@ -137,7 +153,7 @@ const Graph = () => {
|
|||||||
edges: [],
|
edges: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
finalLayout.edges = edges;
|
finalLayout.edges = [...edges];
|
||||||
finalLayout.nodes.push(
|
finalLayout.nodes.push(
|
||||||
...connectedNodes.map((node) => {
|
...connectedNodes.map((node) => {
|
||||||
const position = g.node(node.id);
|
const position = g.node(node.id);
|
||||||
@ -176,68 +192,83 @@ const Graph = () => {
|
|||||||
direction,
|
direction,
|
||||||
});
|
});
|
||||||
|
|
||||||
setNodes([...layouted.nodes]);
|
setPlaylistNodes([...layouted.nodes]);
|
||||||
setEdges([...layouted.edges]);
|
setLinkEdges([...layouted.edges]);
|
||||||
|
|
||||||
setTimeout(flowInstance.fitView);
|
setTimeout(flowInstance.fitView);
|
||||||
console.debug("layout applied");
|
console.debug("layout applied");
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
const fetchGraph = useCallback(async () => {
|
||||||
const fetchGraph = async () => {
|
const resp = await apiFetchGraph();
|
||||||
const resp = await apiFetchGraph();
|
if (resp === undefined) {
|
||||||
if (resp === undefined) {
|
showErrorToastNotification("Please try again after sometime");
|
||||||
showErrorToastNotification("Please try again after sometime");
|
return;
|
||||||
return;
|
}
|
||||||
}
|
if (resp.status === 200) {
|
||||||
if (resp.status === 200) {
|
console.debug(
|
||||||
// place playlist nodes
|
`graph fetched with ${resp.data.playlists?.length} nodes and ${resp.data.links?.length} edges`
|
||||||
setNodes(
|
);
|
||||||
resp.data.playlists?.map((pl, idx) => {
|
// place playlist nodes
|
||||||
return {
|
setPlaylistNodes(
|
||||||
id: `${pl.playlistID}`,
|
resp.data.playlists?.map((pl, idx) => {
|
||||||
position: {
|
return {
|
||||||
x:
|
id: `${pl.playlistID}`,
|
||||||
nodeOffsets.unconnected.origin.x +
|
position: {
|
||||||
Math.floor(idx / 15) * nodeOffsets.unconnected.scaling.x,
|
x:
|
||||||
y:
|
nodeOffsets.unconnected.origin.x +
|
||||||
nodeOffsets.unconnected.origin.y +
|
Math.floor(idx / 15) * nodeOffsets.unconnected.scaling.x,
|
||||||
Math.floor(idx % 15) * nodeOffsets.unconnected.scaling.y,
|
y:
|
||||||
|
nodeOffsets.unconnected.origin.y +
|
||||||
|
Math.floor(idx % 15) * nodeOffsets.unconnected.scaling.y,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
label: pl.playlistName,
|
||||||
|
metadata: {
|
||||||
|
pl,
|
||||||
},
|
},
|
||||||
data: {
|
},
|
||||||
label: pl.playlistName,
|
};
|
||||||
metadata: {
|
}) ?? []
|
||||||
pl,
|
);
|
||||||
},
|
// connect links
|
||||||
},
|
setLinkEdges(
|
||||||
};
|
resp.data.links?.map((link, idx) => {
|
||||||
}) ?? []
|
return {
|
||||||
);
|
id: `${idx}`,
|
||||||
// connect links
|
source: link.from,
|
||||||
setEdges(
|
target: link.to,
|
||||||
resp.data.links?.map((link, idx) => {
|
};
|
||||||
return {
|
}) ?? []
|
||||||
id: `${idx}`,
|
);
|
||||||
source: link.from,
|
showInfoToastNotification("Graph updated.");
|
||||||
target: link.to,
|
return;
|
||||||
};
|
}
|
||||||
}) ?? []
|
if (resp.status >= 500) {
|
||||||
);
|
|
||||||
showInfoToastNotification("Graph updated.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (resp.status >= 500) {
|
|
||||||
showErrorToastNotification(resp.data.message);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (resp.status === 401) {
|
|
||||||
refreshAuth();
|
|
||||||
}
|
|
||||||
showErrorToastNotification(resp.data.message);
|
showErrorToastNotification(resp.data.message);
|
||||||
return;
|
return;
|
||||||
};
|
}
|
||||||
|
if (resp.status === 401) {
|
||||||
|
await refreshAuth();
|
||||||
|
}
|
||||||
|
showErrorToastNotification(resp.data.message);
|
||||||
|
return;
|
||||||
|
}, [refreshAuth]);
|
||||||
|
|
||||||
|
const onArrange = () => {
|
||||||
|
arrangeLayout("TB");
|
||||||
|
};
|
||||||
|
|
||||||
|
const onRefresh = async () => {
|
||||||
|
await fetchGraph();
|
||||||
|
arrangeLayout("TB");
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
fetchGraph();
|
fetchGraph();
|
||||||
}, [refreshAuth, setEdges, setNodes]);
|
// TODO: how to invoke async and sync fns in order correctly inside useEffect?
|
||||||
|
// onRefresh();
|
||||||
|
}, [fetchGraph]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.graph_wrapper}>
|
<div className={styles.graph_wrapper}>
|
||||||
@ -262,7 +293,14 @@ const Graph = () => {
|
|||||||
{/* </Panel> */}
|
{/* </Panel> */}
|
||||||
</ReactFlow>
|
</ReactFlow>
|
||||||
<div className={styles.operations_wrapper}>
|
<div className={styles.operations_wrapper}>
|
||||||
<Button onClickMethod={() => arrangeLayout("TB")}>Arrange</Button>
|
<Button onClickMethod={onRefresh}>
|
||||||
|
<WiCloudRefresh size={36} />
|
||||||
|
Refresh
|
||||||
|
</Button>
|
||||||
|
<Button onClickMethod={onArrange}>
|
||||||
|
<IoIosGitNetwork size={36} />
|
||||||
|
Arrange
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user