mirror of
https://github.com/bunny-lab-io/Borealis.git
synced 2025-07-28 07:38:27 -06:00
Redesigned Edge Styles via New Context Menu Panel
This commit is contained in:
@ -1,7 +1,7 @@
|
||||
// //////// PROJECT FILE SEPARATION LINE ////////// CODE AFTER THIS LINE ARE FROM: <ProjectRoot>/Data/WebUI/src/Flow_Editor.jsx
|
||||
|
||||
// Import Node Configuration Sidebar
|
||||
////////// PROJECT FILE SEPARATION LINE ////////// CODE AFTER THIS LINE ARE FROM: <ProjectRoot>/Data/WebUI/src/Flow_Editor.jsx
|
||||
// Import Node Configuration Sidebar and new Context Menu Sidebar
|
||||
import NodeConfigurationSidebar from "./Node_Configuration_Sidebar";
|
||||
import ContextMenuSidebar from "./Context_Menu_Sidebar";
|
||||
|
||||
import React, { useState, useEffect, useCallback, useRef } from "react";
|
||||
import ReactFlow, {
|
||||
@ -12,24 +12,13 @@ import ReactFlow, {
|
||||
useReactFlow
|
||||
} from "reactflow";
|
||||
|
||||
import {
|
||||
Menu,
|
||||
MenuItem,
|
||||
MenuList,
|
||||
Slider,
|
||||
Box
|
||||
} from "@mui/material";
|
||||
|
||||
import { Menu, MenuItem, Box } from "@mui/material";
|
||||
import {
|
||||
Polyline as PolylineIcon,
|
||||
DeleteForever as DeleteForeverIcon,
|
||||
Edit as EditIcon
|
||||
} from "@mui/icons-material";
|
||||
|
||||
import {
|
||||
SketchPicker
|
||||
} from "react-color";
|
||||
|
||||
import "reactflow/dist/style.css";
|
||||
|
||||
export default function FlowEditor({
|
||||
@ -45,60 +34,88 @@ export default function FlowEditor({
|
||||
const [drawerOpen, setDrawerOpen] = useState(false);
|
||||
const [selectedNodeId, setSelectedNodeId] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
window.BorealisOpenDrawer = (id) => {
|
||||
setSelectedNodeId(id);
|
||||
setDrawerOpen(true);
|
||||
};
|
||||
return () => {
|
||||
delete window.BorealisOpenDrawer;
|
||||
};
|
||||
}, []);
|
||||
// Edge Properties Sidebar State
|
||||
const [edgeSidebarOpen, setEdgeSidebarOpen] = useState(false);
|
||||
const [edgeSidebarEdgeId, setEdgeSidebarEdgeId] = useState(null);
|
||||
|
||||
const selectedNode = nodes.find((n) => n.id === selectedNodeId);
|
||||
// Context Menus
|
||||
const [nodeContextMenu, setNodeContextMenu] = useState(null); // { mouseX, mouseY, nodeId }
|
||||
const [edgeContextMenu, setEdgeContextMenu] = useState(null); // { mouseX, mouseY, edgeId }
|
||||
|
||||
// Drag/snap helpers (untouched)
|
||||
const wrapperRef = useRef(null);
|
||||
const { project } = useReactFlow();
|
||||
|
||||
const [contextMenu, setContextMenu] = useState(null);
|
||||
const [edgeContextMenu, setEdgeContextMenu] = useState(null);
|
||||
const [selectedEdgeId, setSelectedEdgeId] = useState(null);
|
||||
const [showColorPicker, setShowColorPicker] = useState(false);
|
||||
const [colorPickerMode, setColorPickerMode] = useState(null);
|
||||
const [labelPadding, setLabelPadding] = useState([8, 4]);
|
||||
const [labelBorderRadius, setLabelBorderRadius] = useState(4);
|
||||
const [labelOpacity, setLabelOpacity] = useState(0.8);
|
||||
const [tempColor, setTempColor] = useState({ hex: "#58a6ff" });
|
||||
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 = {
|
||||
step: "step",
|
||||
curved: "bezier",
|
||||
straight: "straight",
|
||||
smoothstep: "smoothstep"
|
||||
// ----- Node/Edge Definitions -----
|
||||
const selectedNode = nodes.find((n) => n.id === selectedNodeId);
|
||||
const selectedEdge = edges.find((e) => e.id === edgeSidebarEdgeId);
|
||||
|
||||
// --------- Context Menu Handlers ----------
|
||||
const handleRightClick = (e, node) => {
|
||||
e.preventDefault();
|
||||
setNodeContextMenu({ mouseX: e.clientX + 2, mouseY: e.clientY - 6, nodeId: node.id });
|
||||
};
|
||||
|
||||
const animationStyles = {
|
||||
dashes: { animated: true, style: { strokeDasharray: "6 3" } },
|
||||
dots: { animated: true, style: { strokeDasharray: "2 4" } },
|
||||
none: { animated: false, style: {} }
|
||||
const handleEdgeRightClick = (e, edge) => {
|
||||
e.preventDefault();
|
||||
setEdgeContextMenu({ mouseX: e.clientX + 2, mouseY: e.clientY - 6, edgeId: edge.id });
|
||||
};
|
||||
|
||||
// Compute edge-only guides and capture moving node flow-size
|
||||
// --------- Node Context Menu Actions ---------
|
||||
const handleDisconnectAllEdges = (nodeId) => {
|
||||
setEdges((eds) => eds.filter((e) => e.source !== nodeId && e.target !== nodeId));
|
||||
setNodeContextMenu(null);
|
||||
};
|
||||
|
||||
const handleRemoveNode = (nodeId) => {
|
||||
setNodes((nds) => nds.filter((n) => n.id !== nodeId));
|
||||
setEdges((eds) => eds.filter((e) => e.source !== nodeId && e.target !== nodeId));
|
||||
setNodeContextMenu(null);
|
||||
};
|
||||
|
||||
const handleEditNodeProps = (nodeId) => {
|
||||
setSelectedNodeId(nodeId);
|
||||
setDrawerOpen(true);
|
||||
setNodeContextMenu(null);
|
||||
};
|
||||
|
||||
// --------- Edge Context Menu Actions ---------
|
||||
const handleUnlinkEdge = (edgeId) => {
|
||||
setEdges((eds) => eds.filter((e) => e.id !== edgeId));
|
||||
setEdgeContextMenu(null);
|
||||
};
|
||||
|
||||
const handleEditEdgeProps = (edgeId) => {
|
||||
setEdgeSidebarEdgeId(edgeId);
|
||||
setEdgeSidebarOpen(true);
|
||||
setEdgeContextMenu(null);
|
||||
};
|
||||
|
||||
// ----- Sidebar Closing -----
|
||||
const handleCloseNodeSidebar = () => {
|
||||
setDrawerOpen(false);
|
||||
setSelectedNodeId(null);
|
||||
};
|
||||
|
||||
const handleCloseEdgeSidebar = () => {
|
||||
setEdgeSidebarOpen(false);
|
||||
setEdgeSidebarEdgeId(null);
|
||||
};
|
||||
|
||||
// ----- Update Edge Callback for Sidebar -----
|
||||
const updateEdge = (updatedEdgeObj) => {
|
||||
setEdges((eds) =>
|
||||
eds.map((e) => (e.id === updatedEdgeObj.id ? { ...e, ...updatedEdgeObj } : e))
|
||||
);
|
||||
};
|
||||
|
||||
// ----- Drag/Drop, Guides, Node Snap Logic (unchanged) -----
|
||||
const computeGuides = useCallback((dragNode) => {
|
||||
if (!wrapperRef.current) return;
|
||||
const parentRect = wrapperRef.current.getBoundingClientRect();
|
||||
|
||||
// measure moving node in pixel space
|
||||
const dragEl = wrapperRef.current.querySelector(
|
||||
`.react-flow__node[data-id="${dragNode.id}"]`
|
||||
);
|
||||
@ -108,18 +125,11 @@ export default function FlowEditor({
|
||||
const relTop = dr.top - parentRect.top;
|
||||
const relRight = relLeft + dr.width;
|
||||
const relBottom = relTop + dr.height;
|
||||
|
||||
// project pixel corners to flow coords
|
||||
const pTL = project({ x: relLeft, y: relTop });
|
||||
const pTR = project({ x: relRight, y: relTop });
|
||||
const pBL = project({ x: relLeft, y: relBottom });
|
||||
|
||||
movingFlowSize.current = {
|
||||
width: pTR.x - pTL.x,
|
||||
height: pBL.y - pTL.y
|
||||
};
|
||||
movingFlowSize.current = { width: pTR.x - pTL.x, height: pBL.y - pTL.y };
|
||||
}
|
||||
|
||||
const lines = [];
|
||||
nodes.forEach((n) => {
|
||||
if (n.id === dragNode.id) return;
|
||||
@ -132,57 +142,32 @@ export default function FlowEditor({
|
||||
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 (Math.abs(node.position.x - ln.xFlow) < threshold) { snapX = ln.xFlow; show.push({ xPx: ln.xPx }); }
|
||||
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 (Math.abs(node.position.y - ln.yFlow) < threshold) { snapY = ln.yFlow; show.push({ yPx: ln.yPx }); }
|
||||
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(
|
||||
@ -251,120 +236,23 @@ export default function FlowEditor({
|
||||
setEdges((eds) => applyEdgeChanges(changes, eds));
|
||||
}, [setEdges]);
|
||||
|
||||
const handleRightClick = (e, node) => {
|
||||
e.preventDefault();
|
||||
setContextMenu({ mouseX: e.clientX + 2, mouseY: e.clientY - 6, nodeId: node.id });
|
||||
};
|
||||
|
||||
const handleEdgeRightClick = (e, edge) => {
|
||||
e.preventDefault();
|
||||
setEdgeContextMenu({ edgeId: edge.id, mouseX: e.clientX + 2, mouseY: e.clientY - 6 });
|
||||
setSelectedEdgeId(edge.id);
|
||||
};
|
||||
|
||||
const changeEdgeType = (newType) => {
|
||||
setEdges((eds) => eds.map((e) =>
|
||||
e.id === selectedEdgeId ? { ...e, type: edgeStyles[newType] } : e
|
||||
));
|
||||
setEdgeContextMenu(null);
|
||||
};
|
||||
|
||||
const changeEdgeAnimation = (newAnim) => {
|
||||
setEdges((eds) => eds.map((e) => {
|
||||
if (e.id !== selectedEdgeId) return e;
|
||||
const strokeColor = e.style?.stroke || "#58a6ff";
|
||||
const anim = animationStyles[newAnim] || {};
|
||||
return {
|
||||
...e,
|
||||
animated: anim.animated,
|
||||
style: { ...anim.style, stroke: strokeColor },
|
||||
markerEnd: e.markerEnd ? { ...e.markerEnd, color: strokeColor } : undefined
|
||||
};
|
||||
}));
|
||||
setEdgeContextMenu(null);
|
||||
};
|
||||
|
||||
const handleColorChange = (color) => {
|
||||
setEdges((eds) => eds.map((e) => {
|
||||
if (e.id !== selectedEdgeId) return e;
|
||||
const updated = { ...e };
|
||||
if (colorPickerMode === "stroke") {
|
||||
updated.style = { ...e.style, stroke: color.hex };
|
||||
if (e.markerEnd) updated.markerEnd = { ...e.markerEnd, color: color.hex };
|
||||
} else if (colorPickerMode === "labelText") {
|
||||
updated.labelStyle = { ...e.labelStyle, fill: color.hex };
|
||||
} else if (colorPickerMode === "labelBg") {
|
||||
updated.labelBgStyle = { ...e.labelBgStyle, fill: color.hex, fillOpacity: labelOpacity };
|
||||
}
|
||||
return updated;
|
||||
}));
|
||||
};
|
||||
|
||||
const handleAddLabel = () => {
|
||||
setEdges((eds) => eds.map((e) =>
|
||||
e.id === selectedEdgeId ? { ...e, label: "New Label" } : e
|
||||
));
|
||||
setEdgeContextMenu(null);
|
||||
};
|
||||
|
||||
const handleEditLabel = () => {
|
||||
const newText = prompt("Enter label text:");
|
||||
if (newText !== null) {
|
||||
setEdges((eds) => eds.map((e) =>
|
||||
e.id === selectedEdgeId ? { ...e, label: newText } : e
|
||||
));
|
||||
}
|
||||
setEdgeContextMenu(null);
|
||||
};
|
||||
|
||||
const handleRemoveLabel = () => {
|
||||
setEdges((eds) => eds.map((e) =>
|
||||
e.id === selectedEdgeId ? { ...e, label: undefined } : e
|
||||
));
|
||||
setEdgeContextMenu(null);
|
||||
};
|
||||
|
||||
const handlePickColor = (mode) => {
|
||||
setColorPickerMode(mode);
|
||||
setTempColor({ hex: "#58a6ff" });
|
||||
setPickerPos({ x: edgeContextMenu?.mouseX || 0, y: edgeContextMenu?.mouseY || 0 });
|
||||
setShowColorPicker(true);
|
||||
};
|
||||
|
||||
const applyLabelStyleExtras = () => {
|
||||
setEdges((eds) => eds.map((e) =>
|
||||
e.id === selectedEdgeId
|
||||
? {
|
||||
...e,
|
||||
labelBgPadding: labelPadding,
|
||||
labelBgStyle: {
|
||||
...e.labelBgStyle,
|
||||
fillOpacity: labelOpacity,
|
||||
rx: labelBorderRadius,
|
||||
ry: labelBorderRadius
|
||||
}
|
||||
}
|
||||
: e
|
||||
));
|
||||
setEdgeContextMenu(null);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const nodeCountEl = document.getElementById("nodeCount");
|
||||
if (nodeCountEl) nodeCountEl.innerText = nodes.length;
|
||||
}, [nodes]);
|
||||
|
||||
const nodeDef = selectedNode
|
||||
? Object.values(categorizedNodes).flat().find((def) => def.type === selectedNode.type)
|
||||
: null;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flow-editor-container"
|
||||
ref={wrapperRef}
|
||||
style={{ position: "relative" }}
|
||||
>
|
||||
const nodeDef = selectedNode
|
||||
? Object.values(categorizedNodes).flat().find((def) => def.type === selectedNode.type)
|
||||
: null;
|
||||
|
||||
// --------- MAIN RENDER ----------
|
||||
return (
|
||||
<div
|
||||
className="flow-editor-container"
|
||||
ref={wrapperRef}
|
||||
style={{ position: "relative" }}
|
||||
>
|
||||
{/* Node Config Sidebar */}
|
||||
<NodeConfigurationSidebar
|
||||
drawerOpen={drawerOpen}
|
||||
setDrawerOpen={setDrawerOpen}
|
||||
@ -381,6 +269,17 @@ export default function FlowEditor({
|
||||
}
|
||||
/>
|
||||
|
||||
{/* Edge Properties Sidebar */}
|
||||
<ContextMenuSidebar
|
||||
open={edgeSidebarOpen}
|
||||
onClose={handleCloseEdgeSidebar}
|
||||
edge={selectedEdge ? { ...selectedEdge } : null}
|
||||
updateEdge={edge => {
|
||||
// Provide id if missing
|
||||
if (!edge.id && edgeSidebarEdgeId) edge.id = edgeSidebarEdgeId;
|
||||
updateEdge(edge);
|
||||
}}
|
||||
/>
|
||||
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
@ -403,7 +302,7 @@ export default function FlowEditor({
|
||||
<Background id={flowId} variant="lines" gap={65} size={1} color="rgba(255,255,255,0.2)" />
|
||||
</ReactFlow>
|
||||
|
||||
{/* helper lines */}
|
||||
{/* Helper lines for snapping */}
|
||||
{activeGuides.map((ln, i) =>
|
||||
ln.xPx != null ? (
|
||||
<div
|
||||
@ -420,38 +319,29 @@ export default function FlowEditor({
|
||||
)
|
||||
)}
|
||||
|
||||
{/* Node Context Menu */}
|
||||
<Menu
|
||||
open={Boolean(contextMenu)}
|
||||
onClose={() => setContextMenu(null)}
|
||||
open={Boolean(nodeContextMenu)}
|
||||
onClose={() => setNodeContextMenu(null)}
|
||||
anchorReference="anchorPosition"
|
||||
anchorPosition={contextMenu ? { top: contextMenu.mouseY, left: contextMenu.mouseX } : undefined}
|
||||
anchorPosition={nodeContextMenu ? { top: nodeContextMenu.mouseY, left: nodeContextMenu.mouseX } : undefined}
|
||||
PaperProps={{ sx: { bgcolor: "#1e1e1e", color: "#fff", fontSize: "13px" } }}
|
||||
>
|
||||
<MenuItem onClick={() => {
|
||||
if (contextMenu?.nodeId) {
|
||||
setEdges((eds) => eds.filter((e) =>
|
||||
e.source !== contextMenu.nodeId && e.target !== contextMenu.nodeId
|
||||
));
|
||||
}
|
||||
setContextMenu(null);
|
||||
}}>
|
||||
<MenuItem onClick={() => handleEditNodeProps(nodeContextMenu.nodeId)}>
|
||||
<EditIcon sx={{ fontSize: 18, color: "#58a6ff", mr: 1 }} />
|
||||
Edit Properties
|
||||
</MenuItem>
|
||||
<MenuItem onClick={() => handleDisconnectAllEdges(nodeContextMenu.nodeId)}>
|
||||
<PolylineIcon sx={{ fontSize: 18, color: "#58a6ff", mr: 1 }} />
|
||||
Disconnect All Edges
|
||||
</MenuItem>
|
||||
<MenuItem onClick={() => {
|
||||
if (contextMenu?.nodeId) {
|
||||
setNodes((nds) => nds.filter((n) => n.id !== contextMenu.nodeId));
|
||||
setEdges((eds) => eds.filter((e) =>
|
||||
e.source !== contextMenu.nodeId && e.target !== contextMenu.nodeId
|
||||
));
|
||||
}
|
||||
setContextMenu(null);
|
||||
}}>
|
||||
<MenuItem onClick={() => handleRemoveNode(nodeContextMenu.nodeId)}>
|
||||
<DeleteForeverIcon sx={{ fontSize: 18, color: "#ff4f4f", mr: 1 }} />
|
||||
Remove Node
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
|
||||
{/* Edge Context Menu */}
|
||||
<Menu
|
||||
open={Boolean(edgeContextMenu)}
|
||||
onClose={() => setEdgeContextMenu(null)}
|
||||
@ -459,129 +349,15 @@ export default function FlowEditor({
|
||||
anchorPosition={edgeContextMenu ? { top: edgeContextMenu.mouseY, left: edgeContextMenu.mouseX } : undefined}
|
||||
PaperProps={{ sx: { bgcolor: "#1e1e1e", color: "#fff", fontSize: "13px" } }}
|
||||
>
|
||||
<MenuItem onClick={() => setEdges((eds) => eds.filter((e) => e.id !== selectedEdgeId))}>
|
||||
<MenuItem onClick={() => handleEditEdgeProps(edgeContextMenu.edgeId)}>
|
||||
<EditIcon sx={{ fontSize: 18, color: "#58a6ff", mr: 1 }} />
|
||||
Edit Properties
|
||||
</MenuItem>
|
||||
<MenuItem onClick={() => handleUnlinkEdge(edgeContextMenu.edgeId)}>
|
||||
<DeleteForeverIcon sx={{ fontSize: 18, color: "#ff4f4f", mr: 1 }} />
|
||||
Unlink Edge
|
||||
</MenuItem>
|
||||
<MenuItem>
|
||||
Edge Styles
|
||||
<MenuList>
|
||||
<MenuItem onClick={() => changeEdgeType("step")}>Step</MenuItem>
|
||||
<MenuItem onClick={() => changeEdgeType("curved")}>Curved</MenuItem>
|
||||
<MenuItem onClick={() => changeEdgeType("straight")}>Straight</MenuItem>
|
||||
<MenuItem onClick={() => changeEdgeType("smoothstep")}>Smoothstep</MenuItem>
|
||||
</MenuList>
|
||||
</MenuItem>
|
||||
<MenuItem>
|
||||
Animations
|
||||
<MenuList>
|
||||
<MenuItem onClick={() => changeEdgeAnimation("dashes")}>Dashes</MenuItem>
|
||||
<MenuItem onClick={() => changeEdgeAnimation("dots")}>Dots</MenuItem>
|
||||
<MenuItem onClick={() => changeEdgeAnimation("none")}>Solid Line</MenuItem>
|
||||
</MenuList>
|
||||
</MenuItem>
|
||||
<MenuItem>
|
||||
Label
|
||||
<MenuList>
|
||||
<MenuItem onClick={handleAddLabel}>Add</MenuItem>
|
||||
<MenuItem onClick={handleRemoveLabel}>Remove</MenuItem>
|
||||
<MenuItem onClick={handleEditLabel}>
|
||||
<EditIcon sx={{ fontSize: 16, mr: 1 }} />
|
||||
Edit
|
||||
</MenuItem>
|
||||
<MenuItem onClick={() => handlePickColor("labelText")}>Text Color</MenuItem>
|
||||
<MenuItem onClick={() => handlePickColor("labelBg")}>Background Color</MenuItem>
|
||||
<MenuItem>
|
||||
Padding:
|
||||
<input
|
||||
type="text"
|
||||
defaultValue={`${labelPadding[0]},${labelPadding[1]}`}
|
||||
style={{ width: 80, marginLeft: 8 }}
|
||||
onBlur={(e) => {
|
||||
const parts = e.target.value.split(",").map((v) => parseInt(v.trim()));
|
||||
if (parts.length === 2 && parts.every(Number.isFinite)) setLabelPadding(parts);
|
||||
}}
|
||||
/>
|
||||
</MenuItem>
|
||||
<MenuItem>
|
||||
Radius:
|
||||
<input
|
||||
type="number"
|
||||
min="0"
|
||||
max="20"
|
||||
defaultValue={labelBorderRadius}
|
||||
style={{ width: 60, marginLeft: 8 }}
|
||||
onBlur={(e) => {
|
||||
const val = parseInt(e.target.value);
|
||||
if (!isNaN(val)) setLabelBorderRadius(val);
|
||||
}}
|
||||
/>
|
||||
</MenuItem>
|
||||
<MenuItem>
|
||||
Opacity:
|
||||
<Box display="flex" alignItems="center" ml={1}>
|
||||
<Slider
|
||||
value={labelOpacity}
|
||||
onChange={(_, v) => setLabelOpacity(v)}
|
||||
step={0.05}
|
||||
min={0}
|
||||
max={1}
|
||||
style={{ width: 100 }}
|
||||
/>
|
||||
<input
|
||||
type="number"
|
||||
step="0.05"
|
||||
min="0"
|
||||
max="1"
|
||||
value={labelOpacity}
|
||||
style={{ width: 60, marginLeft: 8 }}
|
||||
onChange={(e) => {
|
||||
const v = parseFloat(e.target.value);
|
||||
if (!isNaN(v)) setLabelOpacity(v);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</MenuItem>
|
||||
<MenuItem onClick={applyLabelStyleExtras}>Apply Label Style Changes</MenuItem>
|
||||
</MenuList>
|
||||
</MenuItem>
|
||||
<MenuItem onClick={() => handlePickColor("stroke")}>Color</MenuItem>
|
||||
</Menu>
|
||||
|
||||
{showColorPicker && (
|
||||
<div
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: pickerPos.y,
|
||||
left: pickerPos.x,
|
||||
zIndex: 9999,
|
||||
background: "#1e1e1e",
|
||||
padding: "10px",
|
||||
borderRadius: "8px"
|
||||
}}
|
||||
>
|
||||
<SketchPicker color={tempColor.hex} onChange={(c) => setTempColor(c)} />
|
||||
<div style={{ marginTop: "10px", textAlign: "center" }}>
|
||||
<button
|
||||
onClick={() => {
|
||||
handleColorChange(tempColor);
|
||||
setShowColorPicker(false);
|
||||
}}
|
||||
style={{
|
||||
backgroundColor: "#58a6ff",
|
||||
color: "#121212",
|
||||
border: "none",
|
||||
padding: "6px 12px",
|
||||
borderRadius: "4px",
|
||||
cursor: "pointer",
|
||||
fontWeight: "bold"
|
||||
}}
|
||||
>
|
||||
Set Color
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
Reference in New Issue
Block a user