mirror of
https://github.com/bunny-lab-io/Borealis.git
synced 2025-07-27 08:48:27 -06:00
Added Node Alignment Helper Lines
This commit is contained in:
@ -23,7 +23,7 @@ import { SketchPicker } from "react-color";
|
|||||||
import "reactflow/dist/style.css";
|
import "reactflow/dist/style.css";
|
||||||
|
|
||||||
export default function FlowEditor({
|
export default function FlowEditor({
|
||||||
flowId, //Used to Fix Grid Issues Across Multiple Flow Tabs
|
flowId,
|
||||||
nodes,
|
nodes,
|
||||||
edges,
|
edges,
|
||||||
setNodes,
|
setNodes,
|
||||||
@ -33,6 +33,7 @@ export default function FlowEditor({
|
|||||||
}) {
|
}) {
|
||||||
const wrapperRef = useRef(null);
|
const wrapperRef = useRef(null);
|
||||||
const { project } = useReactFlow();
|
const { project } = useReactFlow();
|
||||||
|
|
||||||
const [contextMenu, setContextMenu] = useState(null);
|
const [contextMenu, setContextMenu] = useState(null);
|
||||||
const [edgeContextMenu, setEdgeContextMenu] = useState(null);
|
const [edgeContextMenu, setEdgeContextMenu] = useState(null);
|
||||||
const [selectedEdgeId, setSelectedEdgeId] = useState(null);
|
const [selectedEdgeId, setSelectedEdgeId] = useState(null);
|
||||||
@ -44,6 +45,15 @@ export default function FlowEditor({
|
|||||||
const [tempColor, setTempColor] = useState({ hex: "#58a6ff" });
|
const [tempColor, setTempColor] = useState({ hex: "#58a6ff" });
|
||||||
const [pickerPos, setPickerPos] = useState({ x: 0, y: 0 });
|
const [pickerPos, setPickerPos] = useState({ x: 0, y: 0 });
|
||||||
|
|
||||||
|
// helper-line state
|
||||||
|
// guides: array of { xFlow, xPx } or { yFlow, yPx } for stationary nodes
|
||||||
|
const [guides, setGuides] = useState([]);
|
||||||
|
// activeGuides: array of { xPx } or { yPx } to draw
|
||||||
|
const [activeGuides, setActiveGuides] = useState([]);
|
||||||
|
|
||||||
|
// store moving node flow-size on drag start
|
||||||
|
const movingFlowSize = useRef({ width: 0, height: 0 });
|
||||||
|
|
||||||
const edgeStyles = {
|
const edgeStyles = {
|
||||||
step: "step",
|
step: "step",
|
||||||
curved: "bezier",
|
curved: "bezier",
|
||||||
@ -52,64 +62,167 @@ export default function FlowEditor({
|
|||||||
|
|
||||||
const animationStyles = {
|
const animationStyles = {
|
||||||
dashes: { animated: true, style: { strokeDasharray: "6 3" } },
|
dashes: { animated: true, style: { strokeDasharray: "6 3" } },
|
||||||
dots: { animated: true, style: { strokeDasharray: "2 4" } },
|
dots: { animated: true, style: { strokeDasharray: "2 4" } },
|
||||||
none: { animated: false, style: {} }
|
none: { animated: false, style: {} }
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDrop = useCallback(
|
// Compute edge-only guides and capture moving node flow-size
|
||||||
(event) => {
|
const computeGuides = useCallback((dragNode) => {
|
||||||
event.preventDefault();
|
if (!wrapperRef.current) return;
|
||||||
const type = event.dataTransfer.getData("application/reactflow");
|
const parentRect = wrapperRef.current.getBoundingClientRect();
|
||||||
if (!type) return;
|
|
||||||
const bounds = wrapperRef.current.getBoundingClientRect();
|
// measure moving node in pixel space
|
||||||
const position = project({ x: event.clientX - bounds.left, y: event.clientY - bounds.top });
|
const dragEl = wrapperRef.current.querySelector(
|
||||||
const id = "node-" + Date.now();
|
`.react-flow__node[data-id="${dragNode.id}"]`
|
||||||
const nodeMeta = Object.values(categorizedNodes).flat().find((n) => n.type === type);
|
);
|
||||||
const newNode = {
|
if (dragEl) {
|
||||||
id,
|
const dr = dragEl.getBoundingClientRect();
|
||||||
type,
|
const relLeft = dr.left - parentRect.left;
|
||||||
position,
|
const relTop = dr.top - parentRect.top;
|
||||||
data: {
|
const relRight = relLeft + dr.width;
|
||||||
label: nodeMeta?.label || type,
|
const relBottom = relTop + dr.height;
|
||||||
content: nodeMeta?.content
|
|
||||||
},
|
// project pixel corners to flow coords
|
||||||
dragHandle: '.borealis-node-header' // <-- Add this line
|
const pTL = project({ x: relLeft, y: relTop });
|
||||||
};
|
const pTR = project({ x: relRight, y: relTop });
|
||||||
setNodes((nds) => [...nds, newNode]);
|
const pBL = project({ x: relLeft, y: relBottom });
|
||||||
},
|
|
||||||
[project, setNodes, categorizedNodes]
|
movingFlowSize.current = {
|
||||||
);
|
width: pTR.x - pTL.x,
|
||||||
|
height: pBL.y - pTL.y
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const lines = [];
|
||||||
|
nodes.forEach((n) => {
|
||||||
|
if (n.id === dragNode.id) return;
|
||||||
|
const el = wrapperRef.current.querySelector(
|
||||||
|
`.react-flow__node[data-id="${n.id}"]`
|
||||||
|
);
|
||||||
|
if (!el) return;
|
||||||
|
const r = el.getBoundingClientRect();
|
||||||
|
const relLeft = r.left - parentRect.left;
|
||||||
|
const relTop = r.top - parentRect.top;
|
||||||
|
const relRight = relLeft + r.width;
|
||||||
|
const relBottom = relTop + r.height;
|
||||||
|
|
||||||
|
// project pixel to flow coords
|
||||||
|
const pTL = project({ x: relLeft, y: relTop });
|
||||||
|
const pTR = project({ x: relRight, y: relTop });
|
||||||
|
const pBL = project({ x: relLeft, y: relBottom });
|
||||||
|
|
||||||
|
// vertical guides: left edge, right edge
|
||||||
|
lines.push({ xFlow: pTL.x, xPx: relLeft });
|
||||||
|
lines.push({ xFlow: pTR.x, xPx: relRight });
|
||||||
|
|
||||||
|
// horizontal guides: top edge, bottom edge
|
||||||
|
lines.push({ yFlow: pTL.y, yPx: relTop });
|
||||||
|
lines.push({ yFlow: pBL.y, yPx: relBottom });
|
||||||
|
});
|
||||||
|
setGuides(lines);
|
||||||
|
}, [nodes, project]);
|
||||||
|
|
||||||
|
// Snap & show only matching guides within threshold during drag
|
||||||
|
const onNodeDrag = useCallback((_, node) => {
|
||||||
|
const threshold = 5;
|
||||||
|
let snapX = null, snapY = null;
|
||||||
|
const show = [];
|
||||||
|
const { width: fw, height: fh } = movingFlowSize.current;
|
||||||
|
|
||||||
|
guides.forEach((ln) => {
|
||||||
|
if (ln.xFlow != null) {
|
||||||
|
// moving left edge to stationary edges
|
||||||
|
if (Math.abs(node.position.x - ln.xFlow) < threshold) {
|
||||||
|
snapX = ln.xFlow;
|
||||||
|
show.push({ xPx: ln.xPx });
|
||||||
|
}
|
||||||
|
// moving right edge to stationary edges
|
||||||
|
else if (Math.abs(node.position.x + fw - ln.xFlow) < threshold) {
|
||||||
|
snapX = ln.xFlow - fw;
|
||||||
|
show.push({ xPx: ln.xPx });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ln.yFlow != null) {
|
||||||
|
// moving top edge
|
||||||
|
if (Math.abs(node.position.y - ln.yFlow) < threshold) {
|
||||||
|
snapY = ln.yFlow;
|
||||||
|
show.push({ yPx: ln.yPx });
|
||||||
|
}
|
||||||
|
// moving bottom edge
|
||||||
|
else if (Math.abs(node.position.y + fh - ln.yFlow) < threshold) {
|
||||||
|
snapY = ln.yFlow - fh;
|
||||||
|
show.push({ yPx: ln.yPx });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (snapX !== null || snapY !== null) {
|
||||||
|
setNodes((nds) =>
|
||||||
|
applyNodeChanges(
|
||||||
|
[{
|
||||||
|
id: node.id,
|
||||||
|
type: "position",
|
||||||
|
position: {
|
||||||
|
x: snapX !== null ? snapX : node.position.x,
|
||||||
|
y: snapY !== null ? snapY : node.position.y
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
nds
|
||||||
|
)
|
||||||
|
);
|
||||||
|
setActiveGuides(show);
|
||||||
|
} else {
|
||||||
|
setActiveGuides([]);
|
||||||
|
}
|
||||||
|
}, [guides, setNodes]);
|
||||||
|
|
||||||
|
const onDrop = useCallback((event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
const type = event.dataTransfer.getData("application/reactflow");
|
||||||
|
if (!type) return;
|
||||||
|
const bounds = wrapperRef.current.getBoundingClientRect();
|
||||||
|
const position = project({
|
||||||
|
x: event.clientX - bounds.left,
|
||||||
|
y: event.clientY - bounds.top
|
||||||
|
});
|
||||||
|
const id = "node-" + Date.now();
|
||||||
|
const nodeMeta = Object.values(categorizedNodes).flat().find((n) => n.type === type);
|
||||||
|
const newNode = {
|
||||||
|
id,
|
||||||
|
type,
|
||||||
|
position,
|
||||||
|
data: {
|
||||||
|
label: nodeMeta?.label || type,
|
||||||
|
content: nodeMeta?.content
|
||||||
|
},
|
||||||
|
dragHandle: ".borealis-node-header"
|
||||||
|
};
|
||||||
|
setNodes((nds) => [...nds, newNode]);
|
||||||
|
}, [project, setNodes, categorizedNodes]);
|
||||||
|
|
||||||
const onDragOver = useCallback((event) => {
|
const onDragOver = useCallback((event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.dataTransfer.dropEffect = "move";
|
event.dataTransfer.dropEffect = "move";
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const onConnect = useCallback(
|
const onConnect = useCallback((params) => {
|
||||||
(params) => {
|
setEdges((eds) =>
|
||||||
setEdges((eds) =>
|
addEdge({
|
||||||
addEdge(
|
...params,
|
||||||
{
|
type: "bezier",
|
||||||
...params,
|
animated: true,
|
||||||
type: "bezier",
|
style: { strokeDasharray: "6 3", stroke: "#58a6ff" }
|
||||||
animated: true,
|
}, eds)
|
||||||
style: { strokeDasharray: "6 3", stroke: "#58a6ff" }
|
);
|
||||||
},
|
}, [setEdges]);
|
||||||
eds
|
|
||||||
)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
[setEdges]
|
|
||||||
);
|
|
||||||
|
|
||||||
const onNodesChange = useCallback(
|
const onNodesChange = useCallback((changes) => {
|
||||||
(changes) => setNodes((nds) => applyNodeChanges(changes, nds)),
|
setNodes((nds) => applyNodeChanges(changes, nds));
|
||||||
[setNodes]
|
}, [setNodes]);
|
||||||
);
|
|
||||||
const onEdgesChange = useCallback(
|
const onEdgesChange = useCallback((changes) => {
|
||||||
(changes) => setEdges((eds) => applyEdgeChanges(changes, eds)),
|
setEdges((eds) => applyEdgeChanges(changes, eds));
|
||||||
[setEdges]
|
}, [setEdges]);
|
||||||
);
|
|
||||||
|
|
||||||
const handleRightClick = (e, node) => {
|
const handleRightClick = (e, node) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@ -123,76 +236,64 @@ export default function FlowEditor({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const changeEdgeType = (newType) => {
|
const changeEdgeType = (newType) => {
|
||||||
setEdges((eds) =>
|
setEdges((eds) => eds.map((e) =>
|
||||||
eds.map((e) =>
|
e.id === selectedEdgeId ? { ...e, type: edgeStyles[newType] } : e
|
||||||
e.id === selectedEdgeId ? { ...e, type: edgeStyles[newType] } : e
|
));
|
||||||
)
|
|
||||||
);
|
|
||||||
setEdgeContextMenu(null);
|
setEdgeContextMenu(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const changeEdgeAnimation = (newAnim) => {
|
const changeEdgeAnimation = (newAnim) => {
|
||||||
setEdges((eds) =>
|
setEdges((eds) => eds.map((e) => {
|
||||||
eds.map((e) => {
|
if (e.id !== selectedEdgeId) return e;
|
||||||
if (e.id !== selectedEdgeId) return e;
|
const strokeColor = e.style?.stroke || "#58a6ff";
|
||||||
const strokeColor = e.style?.stroke || "#58a6ff";
|
const anim = animationStyles[newAnim] || {};
|
||||||
const anim = animationStyles[newAnim] || {};
|
return {
|
||||||
return {
|
...e,
|
||||||
...e,
|
animated: anim.animated,
|
||||||
animated: anim.animated,
|
style: { ...anim.style, stroke: strokeColor },
|
||||||
style: { ...anim.style, stroke: strokeColor },
|
markerEnd: e.markerEnd ? { ...e.markerEnd, color: strokeColor } : undefined
|
||||||
markerEnd: e.markerEnd ? { ...e.markerEnd, color: strokeColor } : undefined
|
};
|
||||||
};
|
}));
|
||||||
})
|
|
||||||
);
|
|
||||||
setEdgeContextMenu(null);
|
setEdgeContextMenu(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleColorChange = (color) => {
|
const handleColorChange = (color) => {
|
||||||
setEdges((eds) =>
|
setEdges((eds) => eds.map((e) => {
|
||||||
eds.map((e) => {
|
if (e.id !== selectedEdgeId) return e;
|
||||||
if (e.id !== selectedEdgeId) return e;
|
const updated = { ...e };
|
||||||
const updated = { ...e };
|
if (colorPickerMode === "stroke") {
|
||||||
if (colorPickerMode === "stroke") {
|
updated.style = { ...e.style, stroke: color.hex };
|
||||||
updated.style = { ...e.style, stroke: color.hex };
|
if (e.markerEnd) updated.markerEnd = { ...e.markerEnd, color: color.hex };
|
||||||
if (e.markerEnd) updated.markerEnd = { ...e.markerEnd, color: color.hex };
|
} else if (colorPickerMode === "labelText") {
|
||||||
} else if (colorPickerMode === "labelText") {
|
updated.labelStyle = { ...e.labelStyle, fill: color.hex };
|
||||||
updated.labelStyle = { ...e.labelStyle, fill: color.hex };
|
} else if (colorPickerMode === "labelBg") {
|
||||||
} else if (colorPickerMode === "labelBg") {
|
updated.labelBgStyle = { ...e.labelBgStyle, fill: color.hex, fillOpacity: labelOpacity };
|
||||||
updated.labelBgStyle = { ...e.labelBgStyle, fill: color.hex, fillOpacity: labelOpacity };
|
}
|
||||||
}
|
return updated;
|
||||||
return updated;
|
}));
|
||||||
})
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAddLabel = () => {
|
const handleAddLabel = () => {
|
||||||
setEdges((eds) =>
|
setEdges((eds) => eds.map((e) =>
|
||||||
eds.map((e) =>
|
e.id === selectedEdgeId ? { ...e, label: "New Label" } : e
|
||||||
e.id === selectedEdgeId ? { ...e, label: "New Label" } : e
|
));
|
||||||
)
|
|
||||||
);
|
|
||||||
setEdgeContextMenu(null);
|
setEdgeContextMenu(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEditLabel = () => {
|
const handleEditLabel = () => {
|
||||||
const newText = prompt("Enter label text:");
|
const newText = prompt("Enter label text:");
|
||||||
if (newText !== null) {
|
if (newText !== null) {
|
||||||
setEdges((eds) =>
|
setEdges((eds) => eds.map((e) =>
|
||||||
eds.map((e) =>
|
e.id === selectedEdgeId ? { ...e, label: newText } : e
|
||||||
e.id === selectedEdgeId ? { ...e, label: newText } : e
|
));
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
setEdgeContextMenu(null);
|
setEdgeContextMenu(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRemoveLabel = () => {
|
const handleRemoveLabel = () => {
|
||||||
setEdges((eds) =>
|
setEdges((eds) => eds.map((e) =>
|
||||||
eds.map((e) =>
|
e.id === selectedEdgeId ? { ...e, label: undefined } : e
|
||||||
e.id === selectedEdgeId ? { ...e, label: undefined } : e
|
));
|
||||||
)
|
|
||||||
);
|
|
||||||
setEdgeContextMenu(null);
|
setEdgeContextMenu(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -204,22 +305,20 @@ export default function FlowEditor({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const applyLabelStyleExtras = () => {
|
const applyLabelStyleExtras = () => {
|
||||||
setEdges((eds) =>
|
setEdges((eds) => eds.map((e) =>
|
||||||
eds.map((e) =>
|
e.id === selectedEdgeId
|
||||||
e.id === selectedEdgeId
|
? {
|
||||||
? {
|
...e,
|
||||||
...e,
|
labelBgPadding: labelPadding,
|
||||||
labelBgPadding: labelPadding,
|
labelBgStyle: {
|
||||||
labelBgStyle: {
|
...e.labelBgStyle,
|
||||||
...e.labelBgStyle,
|
fillOpacity: labelOpacity,
|
||||||
fillOpacity: labelOpacity,
|
rx: labelBorderRadius,
|
||||||
rx: labelBorderRadius,
|
ry: labelBorderRadius
|
||||||
ry: labelBorderRadius
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
: e
|
}
|
||||||
)
|
: e
|
||||||
);
|
));
|
||||||
setEdgeContextMenu(null);
|
setEdgeContextMenu(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -242,66 +341,60 @@ export default function FlowEditor({
|
|||||||
onNodeContextMenu={handleRightClick}
|
onNodeContextMenu={handleRightClick}
|
||||||
onEdgeContextMenu={handleEdgeRightClick}
|
onEdgeContextMenu={handleEdgeRightClick}
|
||||||
defaultViewport={{ x: 0, y: 0, zoom: 1.5 }}
|
defaultViewport={{ x: 0, y: 0, zoom: 1.5 }}
|
||||||
edgeOptions={{
|
edgeOptions={{ type: "bezier", animated: true, style: { strokeDasharray: "6 3", stroke: "#58a6ff" } }}
|
||||||
type: "bezier",
|
|
||||||
animated: true,
|
|
||||||
style: { strokeDasharray: "6 3", stroke: "#58a6ff" }
|
|
||||||
}}
|
|
||||||
proOptions={{ hideAttribution: true }}
|
proOptions={{ hideAttribution: true }}
|
||||||
|
onNodeDragStart={(_, node) => computeGuides(node)}
|
||||||
|
onNodeDrag={onNodeDrag}
|
||||||
|
onNodeDragStop={() => { setGuides([]); setActiveGuides([]); }}
|
||||||
>
|
>
|
||||||
<Background id={flowId} variant="lines" gap={65} size={1} color="rgba(255,255,255,0.2)" />
|
<Background id={flowId} variant="lines" gap={65} size={1} color="rgba(255,255,255,0.2)" />
|
||||||
</ReactFlow>
|
</ReactFlow>
|
||||||
|
|
||||||
|
{/* helper lines */}
|
||||||
|
{activeGuides.map((ln, i) =>
|
||||||
|
ln.xPx != null ? (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
className="helper-line helper-line-vertical"
|
||||||
|
style={{ left: ln.xPx + "px", top: 0 }}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
className="helper-line helper-line-horizontal"
|
||||||
|
style={{ top: ln.yPx + "px", left: 0 }}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
|
||||||
<Menu
|
<Menu
|
||||||
open={Boolean(contextMenu)}
|
open={Boolean(contextMenu)}
|
||||||
onClose={() => setContextMenu(null)}
|
onClose={() => setContextMenu(null)}
|
||||||
anchorReference="anchorPosition"
|
anchorReference="anchorPosition"
|
||||||
anchorPosition={
|
anchorPosition={contextMenu ? { top: contextMenu.mouseY, left: contextMenu.mouseX } : undefined}
|
||||||
contextMenu
|
|
||||||
? { top: contextMenu.mouseY, left: contextMenu.mouseX }
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
PaperProps={{ sx: { bgcolor: "#1e1e1e", color: "#fff", fontSize: "13px" } }}
|
PaperProps={{ sx: { bgcolor: "#1e1e1e", color: "#fff", fontSize: "13px" } }}
|
||||||
>
|
>
|
||||||
<MenuItem
|
<MenuItem onClick={() => {
|
||||||
onClick={() => {
|
if (contextMenu?.nodeId) {
|
||||||
if (contextMenu?.nodeId) {
|
setEdges((eds) => eds.filter((e) =>
|
||||||
setEdges((eds) =>
|
e.source !== contextMenu.nodeId && e.target !== contextMenu.nodeId
|
||||||
eds.filter(
|
));
|
||||||
(e) =>
|
}
|
||||||
e.source !== contextMenu.nodeId &&
|
setContextMenu(null);
|
||||||
e.target !== contextMenu.nodeId
|
}}>
|
||||||
)
|
<PolylineIcon sx={{ fontSize: 18, color: "#58a6ff", mr: 1 }} />
|
||||||
);
|
|
||||||
}
|
|
||||||
setContextMenu(null);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<PolylineIcon
|
|
||||||
sx={{ fontSize: 18, color: "#58a6ff", mr: 1 }}
|
|
||||||
/>
|
|
||||||
Disconnect All Edges
|
Disconnect All Edges
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem
|
<MenuItem onClick={() => {
|
||||||
onClick={() => {
|
if (contextMenu?.nodeId) {
|
||||||
if (contextMenu?.nodeId) {
|
setNodes((nds) => nds.filter((n) => n.id !== contextMenu.nodeId));
|
||||||
setNodes((nds) =>
|
setEdges((eds) => eds.filter((e) =>
|
||||||
nds.filter((n) => n.id !== contextMenu.nodeId)
|
e.source !== contextMenu.nodeId && e.target !== contextMenu.nodeId
|
||||||
);
|
));
|
||||||
setEdges((eds) =>
|
}
|
||||||
eds.filter(
|
setContextMenu(null);
|
||||||
(e) =>
|
}}>
|
||||||
e.source !== contextMenu.nodeId &&
|
<DeleteForeverIcon sx={{ fontSize: 18, color: "#ff4f4f", mr: 1 }} />
|
||||||
e.target !== contextMenu.nodeId
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
setContextMenu(null);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<DeleteForeverIcon
|
|
||||||
sx={{ fontSize: 18, color: "#ff4f4f", mr: 1 }}
|
|
||||||
/>
|
|
||||||
Remove Node
|
Remove Node
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</Menu>
|
</Menu>
|
||||||
@ -310,23 +403,11 @@ export default function FlowEditor({
|
|||||||
open={Boolean(edgeContextMenu)}
|
open={Boolean(edgeContextMenu)}
|
||||||
onClose={() => setEdgeContextMenu(null)}
|
onClose={() => setEdgeContextMenu(null)}
|
||||||
anchorReference="anchorPosition"
|
anchorReference="anchorPosition"
|
||||||
anchorPosition={
|
anchorPosition={edgeContextMenu ? { top: edgeContextMenu.mouseY, left: edgeContextMenu.mouseX } : undefined}
|
||||||
edgeContextMenu
|
|
||||||
? { top: edgeContextMenu.mouseY, left: edgeContextMenu.mouseX }
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
PaperProps={{ sx: { bgcolor: "#1e1e1e", color: "#fff", fontSize: "13px" } }}
|
PaperProps={{ sx: { bgcolor: "#1e1e1e", color: "#fff", fontSize: "13px" } }}
|
||||||
>
|
>
|
||||||
<MenuItem
|
<MenuItem onClick={() => setEdges((eds) => eds.filter((e) => e.id !== selectedEdgeId))}>
|
||||||
onClick={() =>
|
<DeleteForeverIcon sx={{ fontSize: 18, color: "#ff4f4f", mr: 1 }} />
|
||||||
setEdges((eds) =>
|
|
||||||
eds.filter((e) => e.id !== selectedEdgeId)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<DeleteForeverIcon
|
|
||||||
sx={{ fontSize: 18, color: "#ff4f4f", mr: 1 }}
|
|
||||||
/>
|
|
||||||
Unlink Edge
|
Unlink Edge
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem>
|
<MenuItem>
|
||||||
@ -360,13 +441,11 @@ export default function FlowEditor({
|
|||||||
Padding:
|
Padding:
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
defaultValue={`${labelPadding[0]},${labelPadding[1]}`}
|
defaultValue={`${labelPadding[0]},${labelPadding[1]}`}
|
||||||
style={{ width: 80, marginLeft: 8 }}
|
style={{ width: 80, marginLeft: 8 }}
|
||||||
onBlur={(e) => {
|
onBlur={(e) => {
|
||||||
const parts = e.target.value.split(",").map((v) => parseInt(v.trim()));
|
const parts = e.target.value.split(",").map((v) => parseInt(v.trim()));
|
||||||
if (parts.length === 2 && parts.every(Number.isFinite)) {
|
if (parts.length === 2 && parts.every(Number.isFinite)) setLabelPadding(parts);
|
||||||
setLabelPadding(parts);
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
@ -409,9 +488,7 @@ export default function FlowEditor({
|
|||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem onClick={applyLabelStyleExtras}>
|
<MenuItem onClick={applyLabelStyleExtras}>Apply Label Style Changes</MenuItem>
|
||||||
Apply Label Style Changes
|
|
||||||
</MenuItem>
|
|
||||||
</MenuList>
|
</MenuList>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem onClick={() => handlePickColor("stroke")}>Color</MenuItem>
|
<MenuItem onClick={() => handlePickColor("stroke")}>Color</MenuItem>
|
||||||
|
Reference in New Issue
Block a user