From 090ba0b0852b6adff5089d1a336b3213f6244f23 Mon Sep 17 00:00:00 2001 From: Kaushik Narayan R Date: Sat, 4 Jan 2025 13:44:53 -0700 Subject: [PATCH] mmm --- package-lock.json | 9 ++ package.json | 1 + src/components/Button/Button.module.css | 4 + src/pages/Graph/index.tsx | 170 +++++++++++++++--------- 4 files changed, 118 insertions(+), 66 deletions(-) diff --git a/package-lock.json b/package-lock.json index a0a6a9c..07f8281 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "axios": "^1.7.9", "react": "^19.0.0", "react-dom": "^19.0.0", + "react-icons": "^5.4.0", "react-router-dom": "^7.1.1", "react-toastify": "^11.0.2", "web-vitals": "^4.2.4" @@ -14582,6 +14583,14 @@ "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==", "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": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", diff --git a/package.json b/package.json index e3c4549..48d9883 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "axios": "^1.7.9", "react": "^19.0.0", "react-dom": "^19.0.0", + "react-icons": "^5.4.0", "react-router-dom": "^7.1.1", "react-toastify": "^11.0.2", "web-vitals": "^4.2.4" diff --git a/src/components/Button/Button.module.css b/src/components/Button/Button.module.css index 31bbde0..cb09206 100644 --- a/src/components/Button/Button.module.css +++ b/src/components/Button/Button.module.css @@ -1,4 +1,8 @@ .btn_wrapper { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; padding: var(--mb-2); width: 100%; cursor: pointer; diff --git a/src/pages/Graph/index.tsx b/src/pages/Graph/index.tsx index 7a895c5..8d86c36 100644 --- a/src/pages/Graph/index.tsx +++ b/src/pages/Graph/index.tsx @@ -1,11 +1,11 @@ -import React, { useCallback, useContext, useEffect } from "react"; +import React, { useCallback, useContext, useEffect, useState } from "react"; import { ReactFlow, Controls, Background, - useNodesState, - useEdgesState, addEdge, + applyNodeChanges, + applyEdgeChanges, useReactFlow, MarkerType, BackgroundVariant, @@ -15,6 +15,8 @@ import { type ReactFlowInstance, type Node, type Edge, + type OnNodesChange, + type OnEdgesChange, type OnConnect, } from "@xyflow/react"; 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 styles from "./Graph.module.css"; +import { IoIosGitNetwork } from "react-icons/io"; +import { WiCloudRefresh } from "react-icons/wi"; + import { showErrorToastNotification, showInfoToastNotification, @@ -77,20 +82,31 @@ const proOptions: ProOptions = { hideAttribution: true }; const Graph = () => { const refreshAuth = useContext(RefreshAuthContext); const flowInstance = useReactFlow(); - const [playlistNodes, setNodes, onNodesChange] = useNodesState(initialNodes); - const [linkEdges, setEdges, onEdgesChange] = useEdgesState(initialEdges); + const [playlistNodes, setPlaylistNodes] = useState(initialNodes); + const [linkEdges, setLinkEdges] = useState(initialEdges); - const onFlowInit = (instance: ReactFlowInstance) => { + const onFlowInit = (_instance: ReactFlowInstance) => { 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( - (params) => { - setEdges((eds) => addEdge(params, eds)); - console.debug("new connection"); - console.debug(params); + (connection) => { + setLinkEdges((eds) => addEdge(connection, eds)); + console.debug( + `new connection: ${connection.source} -> ${connection.target}` + ); + // call API to create link }, - [setEdges] + [setLinkEdges] ); type getLayoutedElementsOpts = { @@ -137,7 +153,7 @@ const Graph = () => { edges: [], }; - finalLayout.edges = edges; + finalLayout.edges = [...edges]; finalLayout.nodes.push( ...connectedNodes.map((node) => { const position = g.node(node.id); @@ -176,68 +192,83 @@ const Graph = () => { direction, }); - setNodes([...layouted.nodes]); - setEdges([...layouted.edges]); + setPlaylistNodes([...layouted.nodes]); + setLinkEdges([...layouted.edges]); setTimeout(flowInstance.fitView); console.debug("layout applied"); }; - useEffect(() => { - const fetchGraph = async () => { - const resp = await apiFetchGraph(); - if (resp === undefined) { - showErrorToastNotification("Please try again after sometime"); - return; - } - if (resp.status === 200) { - // place playlist nodes - setNodes( - resp.data.playlists?.map((pl, idx) => { - return { - id: `${pl.playlistID}`, - position: { - x: - nodeOffsets.unconnected.origin.x + - Math.floor(idx / 15) * nodeOffsets.unconnected.scaling.x, - y: - nodeOffsets.unconnected.origin.y + - Math.floor(idx % 15) * nodeOffsets.unconnected.scaling.y, + const fetchGraph = useCallback(async () => { + const resp = await apiFetchGraph(); + if (resp === undefined) { + showErrorToastNotification("Please try again after sometime"); + return; + } + if (resp.status === 200) { + console.debug( + `graph fetched with ${resp.data.playlists?.length} nodes and ${resp.data.links?.length} edges` + ); + // place playlist nodes + setPlaylistNodes( + resp.data.playlists?.map((pl, idx) => { + return { + id: `${pl.playlistID}`, + position: { + x: + nodeOffsets.unconnected.origin.x + + Math.floor(idx / 15) * nodeOffsets.unconnected.scaling.x, + 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 - setEdges( - resp.data.links?.map((link, idx) => { - return { - id: `${idx}`, - source: link.from, - target: link.to, - }; - }) ?? [] - ); - showInfoToastNotification("Graph updated."); - return; - } - if (resp.status >= 500) { - showErrorToastNotification(resp.data.message); - return; - } - if (resp.status === 401) { - refreshAuth(); - } + }, + }; + }) ?? [] + ); + // connect links + setLinkEdges( + resp.data.links?.map((link, idx) => { + return { + id: `${idx}`, + source: link.from, + target: link.to, + }; + }) ?? [] + ); + showInfoToastNotification("Graph updated."); + return; + } + if (resp.status >= 500) { showErrorToastNotification(resp.data.message); 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(); - }, [refreshAuth, setEdges, setNodes]); + // TODO: how to invoke async and sync fns in order correctly inside useEffect? + // onRefresh(); + }, [fetchGraph]); return (
@@ -262,7 +293,14 @@ const Graph = () => { {/* */}
- + +
);