Added Edge Styling & Labeling
This commit is contained in:
parent
5f4125b197
commit
e5e5b26a4c
1
.gitignore
vendored
1
.gitignore
vendored
@ -17,3 +17,4 @@ Borealis-Server.exe
|
|||||||
|
|
||||||
# Misc Files/Folders
|
# Misc Files/Folders
|
||||||
.vs/s
|
.vs/s
|
||||||
|
AI_Model_Custom_Instructions.md
|
@ -12,6 +12,7 @@
|
|||||||
"@emotion/react": "11.14.0",
|
"@emotion/react": "11.14.0",
|
||||||
"@emotion/styled": "11.14.0",
|
"@emotion/styled": "11.14.0",
|
||||||
"react-resizable": "3.0.5",
|
"react-resizable": "3.0.5",
|
||||||
|
"react-color": "2.19.3",
|
||||||
"reactflow": "11.11.4",
|
"reactflow": "11.11.4",
|
||||||
"socket.io-client": "4.8.1",
|
"socket.io-client": "4.8.1",
|
||||||
"react-simple-keyboard": "3.8.62",
|
"react-simple-keyboard": "3.8.62",
|
||||||
|
@ -8,25 +8,20 @@ import ReactFlow, {
|
|||||||
applyEdgeChanges,
|
applyEdgeChanges,
|
||||||
useReactFlow
|
useReactFlow
|
||||||
} from "reactflow";
|
} from "reactflow";
|
||||||
import { Menu, MenuItem } from "@mui/material";
|
import { Menu, MenuItem, MenuList, Slider, Box } from "@mui/material";
|
||||||
import {
|
import {
|
||||||
Polyline as PolylineIcon,
|
Polyline as PolylineIcon,
|
||||||
DeleteForever as DeleteForeverIcon
|
DeleteForever as DeleteForeverIcon,
|
||||||
|
DoubleArrow as DoubleArrowIcon,
|
||||||
|
LinearScale as LinearScaleIcon,
|
||||||
|
Timeline as TimelineIcon,
|
||||||
|
FormatColorFill as FormatColorFillIcon,
|
||||||
|
ArrowRight as ArrowRightIcon,
|
||||||
|
Edit as EditIcon
|
||||||
} from "@mui/icons-material";
|
} from "@mui/icons-material";
|
||||||
|
import { SketchPicker } from "react-color";
|
||||||
import "reactflow/dist/style.css";
|
import "reactflow/dist/style.css";
|
||||||
|
|
||||||
/**
|
|
||||||
* Single flow editor component.
|
|
||||||
*
|
|
||||||
* Props:
|
|
||||||
* - nodes
|
|
||||||
* - edges
|
|
||||||
* - setNodes
|
|
||||||
* - setEdges
|
|
||||||
* - nodeTypes
|
|
||||||
* - categorizedNodes (used to find node meta info on drop)
|
|
||||||
*/
|
|
||||||
export default function FlowEditor({
|
export default function FlowEditor({
|
||||||
nodes,
|
nodes,
|
||||||
edges,
|
edges,
|
||||||
@ -38,36 +33,38 @@ 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 [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 });
|
||||||
|
|
||||||
|
const edgeStyles = {
|
||||||
|
step: "step",
|
||||||
|
curved: "bezier",
|
||||||
|
straight: "straight"
|
||||||
|
};
|
||||||
|
|
||||||
|
const animationStyles = {
|
||||||
|
dashes: { animated: true, style: { strokeDasharray: "6 3" } },
|
||||||
|
dots: { animated: true, style: { strokeDasharray: "2 4" } },
|
||||||
|
none: { animated: false, style: {} }
|
||||||
|
};
|
||||||
|
|
||||||
const onDrop = useCallback(
|
const onDrop = useCallback(
|
||||||
(event) => {
|
(event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const type = event.dataTransfer.getData("application/reactflow");
|
const type = event.dataTransfer.getData("application/reactflow");
|
||||||
if (!type) return;
|
if (!type) return;
|
||||||
|
|
||||||
const bounds = wrapperRef.current.getBoundingClientRect();
|
const bounds = wrapperRef.current.getBoundingClientRect();
|
||||||
const position = project({
|
const position = project({ x: event.clientX - bounds.left, y: event.clientY - bounds.top });
|
||||||
x: event.clientX - bounds.left,
|
|
||||||
y: event.clientY - bounds.top
|
|
||||||
});
|
|
||||||
|
|
||||||
const id = "node-" + Date.now();
|
const id = "node-" + Date.now();
|
||||||
|
const nodeMeta = Object.values(categorizedNodes).flat().find((n) => n.type === type);
|
||||||
// Find node definition in the categorizedNodes
|
const newNode = { id, type, position, data: { label: nodeMeta?.label || type, content: nodeMeta?.content } };
|
||||||
const nodeMeta = Object.values(categorizedNodes)
|
|
||||||
.flat()
|
|
||||||
.find((n) => n.type === type);
|
|
||||||
|
|
||||||
const newNode = {
|
|
||||||
id: id,
|
|
||||||
type: type,
|
|
||||||
position: position,
|
|
||||||
data: {
|
|
||||||
label: nodeMeta?.label || type,
|
|
||||||
content: nodeMeta?.content
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
setNodes((nds) => [...nds, newNode]);
|
setNodes((nds) => [...nds, newNode]);
|
||||||
},
|
},
|
||||||
[project, setNodes, categorizedNodes]
|
[project, setNodes, categorizedNodes]
|
||||||
@ -79,21 +76,19 @@ export default function FlowEditor({
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const onConnect = useCallback(
|
const onConnect = useCallback(
|
||||||
(params) =>
|
(params) => {
|
||||||
setEdges((eds) =>
|
setEdges((eds) =>
|
||||||
addEdge(
|
addEdge(
|
||||||
{
|
{
|
||||||
...params,
|
...params,
|
||||||
type: "smoothstep",
|
type: "bezier",
|
||||||
animated: true,
|
animated: true,
|
||||||
style: {
|
style: { strokeDasharray: "6 3", stroke: "#58a6ff" }
|
||||||
strokeDasharray: "6 3",
|
|
||||||
stroke: "#58a6ff"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
eds
|
eds
|
||||||
)
|
)
|
||||||
),
|
);
|
||||||
|
},
|
||||||
[setEdges]
|
[setEdges]
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -101,7 +96,6 @@ export default function FlowEditor({
|
|||||||
(changes) => setNodes((nds) => applyNodeChanges(changes, nds)),
|
(changes) => setNodes((nds) => applyNodeChanges(changes, nds)),
|
||||||
[setNodes]
|
[setNodes]
|
||||||
);
|
);
|
||||||
|
|
||||||
const onEdgesChange = useCallback(
|
const onEdgesChange = useCallback(
|
||||||
(changes) => setEdges((eds) => applyEdgeChanges(changes, eds)),
|
(changes) => setEdges((eds) => applyEdgeChanges(changes, eds)),
|
||||||
[setEdges]
|
[setEdges]
|
||||||
@ -109,45 +103,119 @@ export default function FlowEditor({
|
|||||||
|
|
||||||
const handleRightClick = (e, node) => {
|
const handleRightClick = (e, node) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setContextMenu({
|
setContextMenu({ mouseX: e.clientX + 2, mouseY: e.clientY - 6, nodeId: node.id });
|
||||||
mouseX: e.clientX + 2,
|
|
||||||
mouseY: e.clientY - 6,
|
|
||||||
nodeId: node.id
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDisconnect = () => {
|
const handleEdgeRightClick = (e, edge) => {
|
||||||
if (contextMenu?.nodeId) {
|
e.preventDefault();
|
||||||
|
setEdgeContextMenu({ edgeId: edge.id, mouseX: e.clientX + 2, mouseY: e.clientY - 6 });
|
||||||
|
setSelectedEdgeId(edge.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
const changeEdgeType = (newType) => {
|
||||||
setEdges((eds) =>
|
setEdges((eds) =>
|
||||||
eds.filter(
|
eds.map((e) =>
|
||||||
(e) =>
|
e.id === selectedEdgeId ? { ...e, type: edgeStyles[newType] } : e
|
||||||
e.source !== contextMenu.nodeId &&
|
)
|
||||||
e.target !== contextMenu.nodeId
|
);
|
||||||
|
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
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
setContextMenu(null);
|
setEdgeContextMenu(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRemoveNode = () => {
|
const handleRemoveLabel = () => {
|
||||||
if (contextMenu?.nodeId) {
|
|
||||||
setNodes((nds) => nds.filter((n) => n.id !== contextMenu.nodeId));
|
|
||||||
setEdges((eds) =>
|
setEdges((eds) =>
|
||||||
eds.filter(
|
eds.map((e) =>
|
||||||
(e) =>
|
e.id === selectedEdgeId ? { ...e, label: undefined } : e
|
||||||
e.source !== contextMenu.nodeId &&
|
|
||||||
e.target !== contextMenu.nodeId
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
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
|
||||||
}
|
}
|
||||||
setContextMenu(null);
|
}
|
||||||
|
: e
|
||||||
|
)
|
||||||
|
);
|
||||||
|
setEdgeContextMenu(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const nodeCountEl = document.getElementById("nodeCount");
|
const nodeCountEl = document.getElementById("nodeCount");
|
||||||
if (nodeCountEl) {
|
if (nodeCountEl) nodeCountEl.innerText = nodes.length;
|
||||||
nodeCountEl.innerText = nodes.length;
|
|
||||||
}
|
|
||||||
}, [nodes]);
|
}, [nodes]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -162,26 +230,18 @@ export default function FlowEditor({
|
|||||||
onDrop={onDrop}
|
onDrop={onDrop}
|
||||||
onDragOver={onDragOver}
|
onDragOver={onDragOver}
|
||||||
onNodeContextMenu={handleRightClick}
|
onNodeContextMenu={handleRightClick}
|
||||||
|
onEdgeContextMenu={handleEdgeRightClick}
|
||||||
defaultViewport={{ x: 0, y: 0, zoom: 1.5 }}
|
defaultViewport={{ x: 0, y: 0, zoom: 1.5 }}
|
||||||
edgeOptions={{
|
edgeOptions={{
|
||||||
type: "smoothstep",
|
type: "bezier",
|
||||||
animated: true,
|
animated: true,
|
||||||
style: {
|
style: { strokeDasharray: "6 3", stroke: "#58a6ff" }
|
||||||
strokeDasharray: "6 3",
|
|
||||||
stroke: "#58a6ff"
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
proOptions={{ hideAttribution: true }}
|
proOptions={{ hideAttribution: true }}
|
||||||
>
|
>
|
||||||
<Background
|
<Background variant="lines" gap={65} size={1} color="rgba(255,255,255,0.2)" />
|
||||||
variant="lines"
|
|
||||||
gap={65}
|
|
||||||
size={1}
|
|
||||||
color="rgba(255, 255, 255, 0.2)"
|
|
||||||
/>
|
|
||||||
</ReactFlow>
|
</ReactFlow>
|
||||||
|
|
||||||
{/* Right-click node menu */}
|
|
||||||
<Menu
|
<Menu
|
||||||
open={Boolean(contextMenu)}
|
open={Boolean(contextMenu)}
|
||||||
onClose={() => setContextMenu(null)}
|
onClose={() => setContextMenu(null)}
|
||||||
@ -191,23 +251,196 @@ export default function FlowEditor({
|
|||||||
? { top: contextMenu.mouseY, left: contextMenu.mouseX }
|
? { top: contextMenu.mouseY, left: contextMenu.mouseX }
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
PaperProps={{
|
PaperProps={{ sx: { bgcolor: "#1e1e1e", color: "#fff", fontSize: "13px" } }}
|
||||||
sx: {
|
>
|
||||||
bgcolor: "#1e1e1e",
|
<MenuItem
|
||||||
color: "#fff",
|
onClick={() => {
|
||||||
fontSize: "13px"
|
if (contextMenu?.nodeId) {
|
||||||
|
setEdges((eds) =>
|
||||||
|
eds.filter(
|
||||||
|
(e) =>
|
||||||
|
e.source !== contextMenu.nodeId &&
|
||||||
|
e.target !== contextMenu.nodeId
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
setContextMenu(null);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<MenuItem onClick={handleDisconnect}>
|
<PolylineIcon
|
||||||
<PolylineIcon sx={{ fontSize: 18, color: "#58a6ff", mr: 1 }} />
|
sx={{ fontSize: 18, color: "#58a6ff", mr: 1 }}
|
||||||
|
/>
|
||||||
Disconnect All Edges
|
Disconnect All Edges
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem onClick={handleRemoveNode}>
|
<MenuItem
|
||||||
<DeleteForeverIcon sx={{ fontSize: 18, color: "#ff4f4f", mr: 1 }} />
|
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);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DeleteForeverIcon
|
||||||
|
sx={{ fontSize: 18, color: "#ff4f4f", mr: 1 }}
|
||||||
|
/>
|
||||||
Remove Node
|
Remove Node
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</Menu>
|
</Menu>
|
||||||
|
|
||||||
|
<Menu
|
||||||
|
open={Boolean(edgeContextMenu)}
|
||||||
|
onClose={() => setEdgeContextMenu(null)}
|
||||||
|
anchorReference="anchorPosition"
|
||||||
|
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)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<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>
|
||||||
|
</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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
69
project_directory_tree.txt
Normal file
69
project_directory_tree.txt
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
Folder Structure:
|
||||||
|
├── Dependencies
|
||||||
|
├── Launch-Borealis.ps1
|
||||||
|
├── Launch-Borealis.sh
|
||||||
|
└── Data
|
||||||
|
├── Agent
|
||||||
|
│ ├── agent-requirements.txt
|
||||||
|
│ ├── Borealis.ico
|
||||||
|
│ ├── borealis-agent.py
|
||||||
|
│ └── Package_Borealis-Agent.ps1
|
||||||
|
└── Server
|
||||||
|
├── Borealis.ico
|
||||||
|
├── Package-Borealis-Server.ps1
|
||||||
|
├── server.py
|
||||||
|
├── server-requirements.txt
|
||||||
|
├── Python_API_Endpoints
|
||||||
|
│ ├── ocr_engines.py
|
||||||
|
│ └── Tesseract-OCR
|
||||||
|
├── Sounds
|
||||||
|
│ └── Short_Beep.wav
|
||||||
|
├── WebUI
|
||||||
|
│ ├── package.json
|
||||||
|
│ ├── public
|
||||||
|
│ │ ├── Borealis_Logo.png
|
||||||
|
│ │ ├── Borealis_Logo_Full.png
|
||||||
|
│ │ ├── favicon.ico
|
||||||
|
│ │ └── index.html
|
||||||
|
│ └── src
|
||||||
|
│ ├── App.jsx
|
||||||
|
│ ├── Borealis.css
|
||||||
|
│ ├── Dialogs.jsx
|
||||||
|
│ ├── Flow_Editor.jsx
|
||||||
|
│ ├── Flow_Tabs.jsx
|
||||||
|
│ ├── index.js
|
||||||
|
│ ├── Node_Sidebar.jsx
|
||||||
|
│ ├── Status_Bar.jsx
|
||||||
|
│ └── nodes
|
||||||
|
│ ├── Agents
|
||||||
|
│ │ ├── Node_Agent.jsx
|
||||||
|
│ │ └── Node_Agent_Role_Screenshot.jsx
|
||||||
|
│ ├── Alerting
|
||||||
|
│ │ └── Node_Alert_Sound.jsx
|
||||||
|
│ ├── Data Analysis
|
||||||
|
│ │ └── Node_OCR_Text_Extraction.jsx
|
||||||
|
│ ├── Data Manipulation
|
||||||
|
│ │ └── Node_Array_Index_Extractor.jsx
|
||||||
|
│ ├── Flyff Universe
|
||||||
|
│ ├── General Purpose
|
||||||
|
│ │ ├── Node_Data.jsx
|
||||||
|
│ │ ├── Node_Logical_Operators.jsx
|
||||||
|
│ │ └── Node_Math_Operation.jsx
|
||||||
|
│ ├── Image Processing
|
||||||
|
│ │ ├── Node_Adjust_Contrast.jsx
|
||||||
|
│ │ ├── Node_BW_Threshold.jsx
|
||||||
|
│ │ ├── Node_Convert_to_Grayscale.jsx
|
||||||
|
│ │ ├── Node_Export_Image.jsx
|
||||||
|
│ │ ├── Node_Image_Viewer.jsx
|
||||||
|
│ │ └── Node_Upload_Image.jsx
|
||||||
|
│ ├── Macro Automation
|
||||||
|
│ │ └── Node_Macro_KeyPress.jsx
|
||||||
|
│ ├── Organization
|
||||||
|
│ │ └── Node_Backdrop_Group_Box.jsx
|
||||||
|
│ └── Reporting
|
||||||
|
│ └── Node_Export_to_CSV.jsx
|
||||||
|
└── Workflows
|
||||||
|
├── Examples
|
||||||
|
│ └── Logic-Comparison-Example.json
|
||||||
|
└── Flyff Universe
|
||||||
|
└── Flyff_OCR_Workflow.json
|
Loading…
x
Reference in New Issue
Block a user