abt to start link creation/removal

This commit is contained in:
Kaushik Narayan R 2025-01-04 16:12:36 -07:00
parent 090ba0b085
commit f471c666e7
2 changed files with 80 additions and 15 deletions

View File

@ -11,6 +11,7 @@
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: flex-start; justify-content: flex-start;
gap: var(--mb-3);
height: 100vh; height: 100vh;
width: 10vw; width: 10vw;
padding: var(--mb-3); padding: var(--mb-3);

View File

@ -2,6 +2,7 @@ import React, { useCallback, useContext, useEffect, useState } from "react";
import { import {
ReactFlow, ReactFlow,
Controls, Controls,
MiniMap,
Background, Background,
addEdge, addEdge,
applyNodeChanges, applyNodeChanges,
@ -18,6 +19,8 @@ import {
type OnNodesChange, type OnNodesChange,
type OnEdgesChange, type OnEdgesChange,
type OnConnect, type OnConnect,
type OnDelete,
type OnBeforeDelete,
} from "@xyflow/react"; } from "@xyflow/react";
import Dagre, { type GraphLabel } from "@dagrejs/dagre"; import Dagre, { type GraphLabel } from "@dagrejs/dagre";
@ -26,6 +29,7 @@ import styles from "./Graph.module.css";
import { IoIosGitNetwork } from "react-icons/io"; import { IoIosGitNetwork } from "react-icons/io";
import { WiCloudRefresh } from "react-icons/wi"; import { WiCloudRefresh } from "react-icons/wi";
import { MdOutlineLock, MdOutlineLockOpen } from "react-icons/md";
import { import {
showErrorToastNotification, showErrorToastNotification,
@ -37,8 +41,8 @@ import { apiFetchGraph } from "../../api/operations";
import { RefreshAuthContext } from "../../App"; import { RefreshAuthContext } from "../../App";
import Button from "../../components/Button"; import Button from "../../components/Button";
const initialNodes: any[] = []; const initialNodes: Node[] = [];
const initialEdges: any[] = []; const initialEdges: Edge[] = [];
const nodeOffsets = { const nodeOffsets = {
connected: { 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 = { const edgeOptions: DefaultEdgeOptions = {
animated: true, animated: true,
style: { style: {
@ -72,8 +88,9 @@ const edgeOptions: DefaultEdgeOptions = {
markerEnd: { markerEnd: {
type: MarkerType.ArrowClosed, type: MarkerType.ArrowClosed,
color: "white", color: "white",
width: 40, width: 16,
height: 40, height: 16,
orient: "auto",
}, },
}; };
@ -84,11 +101,31 @@ const Graph = () => {
const flowInstance = useReactFlow(); const flowInstance = useReactFlow();
const [playlistNodes, setPlaylistNodes] = useState<Node[]>(initialNodes); const [playlistNodes, setPlaylistNodes] = useState<Node[]>(initialNodes);
const [linkEdges, setLinkEdges] = useState<Edge[]>(initialEdges); const [linkEdges, setLinkEdges] = useState<Edge[]>(initialEdges);
const [interactive, setInteractive] =
useState<Interactive>(initialInteractive);
const onFlowInit = (_instance: ReactFlowInstance) => { const onFlowInit = (_instance: ReactFlowInstance) => {
console.debug("flow loaded"); 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( const onNodesChange: OnNodesChange = useCallback(
(changes) => setPlaylistNodes((nds) => applyNodeChanges(changes, nds)), (changes) => setPlaylistNodes((nds) => applyNodeChanges(changes, nds)),
[setPlaylistNodes] [setPlaylistNodes]
@ -121,9 +158,9 @@ const Graph = () => {
g.setDefaultEdgeLabel(() => ({})); g.setDefaultEdgeLabel(() => ({}));
g.setGraph({ g.setGraph({
rankdir: options.direction, rankdir: options.direction,
nodesep: 200, nodesep: 100,
edgesep: 200, edgesep: 100,
ranksep: 200, ranksep: 100,
}); });
edges.forEach((edge) => g.setEdge(edge.source, edge.target)); edges.forEach((edge) => g.setEdge(edge.source, edge.target));
@ -235,7 +272,7 @@ const Graph = () => {
setLinkEdges( setLinkEdges(
resp.data.links?.map((link, idx) => { resp.data.links?.map((link, idx) => {
return { return {
id: `${idx}`, id: `${link.from}->${link.to}`,
source: link.from, source: link.from,
target: link.to, target: link.to,
}; };
@ -270,27 +307,46 @@ const Graph = () => {
// onRefresh(); // onRefresh();
}, [fetchGraph]); }, [fetchGraph]);
const toggleInteractive = () => {
setInteractive({
ndDrag: !interactive.ndDrag,
ndConn: !interactive.ndConn,
elsSel: !interactive.elsSel,
});
};
const isInteractive = () => {
return interactive.ndDrag && interactive.ndConn && interactive.elsSel;
};
return ( return (
<div className={styles.graph_wrapper}> <div className={styles.graph_wrapper}>
<ReactFlow <ReactFlow
nodes={playlistNodes} nodes={playlistNodes}
edges={linkEdges} edges={linkEdges}
defaultEdgeOptions={edgeOptions} defaultEdgeOptions={edgeOptions}
connectionLineType={ConnectionLineType.SmoothStep}
fitView
proOptions={proOptions} proOptions={proOptions}
connectionLineType={ConnectionLineType.SmoothStep}
connectOnClick={false}
fitView
colorMode={"light"} colorMode={"light"}
edgesReconnectable={false}
nodesFocusable={false}
nodesDraggable={interactive.ndDrag}
nodesConnectable={interactive.ndConn}
elementsSelectable={interactive.elsSel}
deleteKeyCode={["Delete", "Backspace"]}
multiSelectionKeyCode={null}
onInit={onFlowInit} onInit={onFlowInit}
onBeforeDelete={onFlowBeforeDelete}
onDelete={onFlowAfterDelete}
onNodesChange={onNodesChange} onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange} onEdgesChange={onEdgesChange}
onConnect={onConnect} onConnect={onConnect}
> >
<Controls /> <Controls onInteractiveChange={toggleInteractive} />
<MiniMap pannable zoomable />
<Background variant={BackgroundVariant.Dots} gap={36} size={3} /> <Background variant={BackgroundVariant.Dots} gap={36} size={3} />
{/* <Panel position="top-right"> */}
{/* <button onClick={() => arrangeLayout('TB')}>Arrange vertically</button> */}
{/* <button onClick={() => arrangeLayout('LR')}>Arrange horizontally</button> */}
{/* </Panel> */}
</ReactFlow> </ReactFlow>
<div className={styles.operations_wrapper}> <div className={styles.operations_wrapper}>
<Button onClickMethod={onRefresh}> <Button onClickMethod={onRefresh}>
@ -301,6 +357,14 @@ const Graph = () => {
<IoIosGitNetwork size={36} /> <IoIosGitNetwork size={36} />
Arrange Arrange
</Button> </Button>
<Button onClickMethod={toggleInteractive}>
{isInteractive() ? (
<MdOutlineLock size={36} />
) : (
<MdOutlineLockOpen size={36} />
)}
{isInteractive() ? "Lock" : "Unlock"}
</Button>
</div> </div>
</div> </div>
); );