diff --git a/src/pages/Graph/Graph.module.css b/src/pages/Graph/Graph.module.css index 75513f8..c522a6b 100644 --- a/src/pages/Graph/Graph.module.css +++ b/src/pages/Graph/Graph.module.css @@ -11,6 +11,7 @@ flex-direction: column; align-items: center; justify-content: flex-start; + gap: var(--mb-3); height: 100vh; width: 10vw; padding: var(--mb-3); diff --git a/src/pages/Graph/index.tsx b/src/pages/Graph/index.tsx index 8d86c36..40e9511 100644 --- a/src/pages/Graph/index.tsx +++ b/src/pages/Graph/index.tsx @@ -2,6 +2,7 @@ import React, { useCallback, useContext, useEffect, useState } from "react"; import { ReactFlow, Controls, + MiniMap, Background, addEdge, applyNodeChanges, @@ -18,6 +19,8 @@ import { type OnNodesChange, type OnEdgesChange, type OnConnect, + type OnDelete, + type OnBeforeDelete, } from "@xyflow/react"; import Dagre, { type GraphLabel } from "@dagrejs/dagre"; @@ -26,6 +29,7 @@ import styles from "./Graph.module.css"; import { IoIosGitNetwork } from "react-icons/io"; import { WiCloudRefresh } from "react-icons/wi"; +import { MdOutlineLock, MdOutlineLockOpen } from "react-icons/md"; import { showErrorToastNotification, @@ -37,8 +41,8 @@ import { apiFetchGraph } from "../../api/operations"; import { RefreshAuthContext } from "../../App"; import Button from "../../components/Button"; -const initialNodes: any[] = []; -const initialEdges: any[] = []; +const initialNodes: Node[] = []; +const initialEdges: Edge[] = []; const nodeOffsets = { connected: { @@ -63,6 +67,18 @@ const nodeOffsets = { }, }; +interface Interactive { + ndDrag: boolean; + ndConn: boolean; + elsSel: boolean; +} + +const initialInteractive: Interactive = { + ndDrag: true, + ndConn: true, + elsSel: true, +}; + const edgeOptions: DefaultEdgeOptions = { animated: true, style: { @@ -72,8 +88,9 @@ const edgeOptions: DefaultEdgeOptions = { markerEnd: { type: MarkerType.ArrowClosed, color: "white", - width: 40, - height: 40, + width: 16, + height: 16, + orient: "auto", }, }; @@ -84,11 +101,31 @@ const Graph = () => { const flowInstance = useReactFlow(); const [playlistNodes, setPlaylistNodes] = useState(initialNodes); const [linkEdges, setLinkEdges] = useState(initialEdges); + const [interactive, setInteractive] = + useState(initialInteractive); const onFlowInit = (_instance: ReactFlowInstance) => { console.debug("flow loaded"); }; + const onFlowBeforeDelete: OnBeforeDelete = useCallback( + async ({ nodes, edges }) => { + // can't delete playlists + if (nodes.length > 0) { + showErrorToastNotification("Can't delete playlists!"); + return false; + } + return { nodes, edges }; + }, + [] + ); + + const onFlowAfterDelete: OnDelete = useCallback(({ nodes, edges }) => { + console.debug("deleted edges"); + console.debug(edges); + }, []); + + // base event handling const onNodesChange: OnNodesChange = useCallback( (changes) => setPlaylistNodes((nds) => applyNodeChanges(changes, nds)), [setPlaylistNodes] @@ -121,9 +158,9 @@ const Graph = () => { g.setDefaultEdgeLabel(() => ({})); g.setGraph({ rankdir: options.direction, - nodesep: 200, - edgesep: 200, - ranksep: 200, + nodesep: 100, + edgesep: 100, + ranksep: 100, }); edges.forEach((edge) => g.setEdge(edge.source, edge.target)); @@ -235,7 +272,7 @@ const Graph = () => { setLinkEdges( resp.data.links?.map((link, idx) => { return { - id: `${idx}`, + id: `${link.from}->${link.to}`, source: link.from, target: link.to, }; @@ -270,27 +307,46 @@ const Graph = () => { // onRefresh(); }, [fetchGraph]); + const toggleInteractive = () => { + setInteractive({ + ndDrag: !interactive.ndDrag, + ndConn: !interactive.ndConn, + elsSel: !interactive.elsSel, + }); + }; + + const isInteractive = () => { + return interactive.ndDrag && interactive.ndConn && interactive.elsSel; + }; + return (
- + + - {/* */} - {/* */} - {/* */} - {/* */}
+
);