Remove ColorNode

This commit is contained in:
dvirlabs 2025-07-12 22:06:06 +03:00
parent 552f351949
commit 1b5d7735ce
4 changed files with 40 additions and 142 deletions

View File

@ -1,19 +0,0 @@
{
"nodes": [
{
"id": "1",
"type": "custom",
"data": {
"label": "Node 1",
"icon": "https://s3.dvirlabs.com/lab-icons/dev-tools/argocd.svg"
},
"position": {
"x": 307.58165778037267,
"y": 195.1586839847007
},
"width": 103,
"height": 103
}
],
"edges": []
}

View File

@ -1,26 +0,0 @@
import { Handle, Position } from 'reactflow';
function ColorNode({ data }) {
return (
<div style={{
background: 'white',
border: '2px solid #ff0071',
borderRadius: 8,
padding: 10,
width: 120,
textAlign: 'center'
}}>
<Handle type="target" position={Position.Left} />
<div style={{ fontSize: 12, marginBottom: 6 }}>🎨 Shape Color</div>
<input
type="color"
value={data.color || '#ff0071'}
onChange={(e) => data.onChange?.(e.target.value)}
style={{ width: '100%' }}
/>
<Handle type="source" position={Position.Right} />
</div>
);
}
export default ColorNode;

View File

@ -17,59 +17,45 @@ import CustomNode from './CustomNode';
import IconSelector from './IconSelector'; import IconSelector from './IconSelector';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import RouterNode from './RouterNode'; import RouterNode from './RouterNode';
import ColorNode from './ColorNode';
const nodeTypes = { const nodeTypes = {
custom: CustomNode, custom: CustomNode,
router: RouterNode, router: RouterNode,
color: ColorNode,
}; };
function Diagram() { function Diagram() {
const [nodes, setNodes, onNodesChange] = useNodesState([]); const [nodes, setNodes, onNodesChange] = useNodesState([]);
const [edges, setEdges, onEdgesChange] = useEdgesState([]); const [edges, setEdges, onEdgesChange] = useEdgesState([]);
const [selectedNode, setSelectedNode] = useState(null); const [selectedNode, setSelectedNode] = useState(null);
const [selectedEdge, setSelectedEdge] = useState(null);
const [showForm, setShowForm] = useState(false); const [showForm, setShowForm] = useState(false);
const [newLabel, setNewLabel] = useState(''); const [newLabel, setNewLabel] = useState('');
const [newType, setNewType] = useState('custom'); const [newType, setNewType] = useState('custom');
const [selectedIcon, setSelectedIcon] = useState(''); const [selectedIcon, setSelectedIcon] = useState('');
const [selectedEdge, setSelectedEdge] = useState(null);
const [diagramName, setDiagramName] = useState(null); const [diagramName, setDiagramName] = useState(null);
const [diagramList, setDiagramList] = useState([]); const [diagramList, setDiagramList] = useState([]);
const isIframe = window.self !== window.top; const isIframe = window.self !== window.top;
useEffect(() => { useEffect(() => {
let isMounted = true;
const load = async () => { const load = async () => {
try { try {
const { diagrams } = await listDiagrams(); const { diagrams } = await listDiagrams();
if (isMounted) {
setDiagramList(diagrams); setDiagramList(diagrams);
if (diagrams.length > 0) { if (diagrams.length > 0) {
setDiagramName((prev) => prev || diagrams[0]); setDiagramName((prev) => prev || diagrams[0]);
} else { } else {
setDiagramName(null); setDiagramName(null);
} }
}
} catch (err) { } catch (err) {
console.error(err); console.error(err);
setDiagramList([]); setDiagramList([]);
setDiagramName(null); setDiagramName(null);
} }
}; };
load(); load();
return () => {
isMounted = false;
};
}, []); }, []);
useEffect(() => { useEffect(() => {
let isMounted = true;
const loadDiagram = async () => { const loadDiagram = async () => {
if (!diagramName) { if (!diagramName) {
setNodes([]); setNodes([]);
@ -78,18 +64,13 @@ function Diagram() {
} }
try { try {
const data = await fetchDiagramByName(diagramName); const data = await fetchDiagramByName(diagramName);
if (isMounted) {
setNodes(data.nodes || []); setNodes(data.nodes || []);
const sanitizedEdges = (data.edges || []).map(({ id, source, target, animated }) => ({ const sanitizedEdges = (data.edges || []).map(({ id, source, target, animated }) => ({
id, id, source, target, animated: !!animated
source,
target,
animated: !!animated,
})); }));
setEdges(sanitizedEdges); setEdges(sanitizedEdges);
setSelectedNode(null); setSelectedNode(null);
setSelectedEdge(null); setSelectedEdge(null);
}
} catch (err) { } catch (err) {
console.error(err); console.error(err);
toast.error(`❌ Failed to load diagram "${diagramName}"`); toast.error(`❌ Failed to load diagram "${diagramName}"`);
@ -97,29 +78,18 @@ function Diagram() {
setEdges([]); setEdges([]);
} }
}; };
loadDiagram(); loadDiagram();
return () => { }, [diagramName]);
isMounted = false;
};
}, [diagramName, setNodes, setEdges]);
const handleSave = async () => { const handleSave = async () => {
if (!diagramName) return; if (!diagramName) return;
const cleanedEdges = edges.map(({ id, source, target, animated }) => ({ const cleanedEdges = edges.map(({ id, source, target, animated }) => ({
id, id, source, target, animated: !!animated
source,
target,
animated: !!animated,
})); }));
await saveDiagramByName(diagramName, { nodes, edges: cleanedEdges }); await saveDiagramByName(diagramName, { nodes, edges: cleanedEdges });
toast.success(`✅ Diagram "${diagramName}" saved!`); toast.success(`✅ Diagram "${diagramName}" saved!`);
}; };
const handleAddNode = () => {
setShowForm(true);
};
const handleSubmitNode = () => { const handleSubmitNode = () => {
const id = (nodes.length + 1).toString(); const id = (nodes.length + 1).toString();
const label = newLabel || `Node ${id}`; const label = newLabel || `Node ${id}`;
@ -128,9 +98,7 @@ function Diagram() {
const newNode = { const newNode = {
id, id,
type: newType, type: newType,
data: newType === 'custom' data: newType === 'custom' ? { label, icon } : { label },
? { label, icon }
: { label }, // router and other types don't have icons
position: { x: Math.random() * 400, y: Math.random() * 300 }, position: { x: Math.random() * 400, y: Math.random() * 300 },
}; };
@ -150,6 +118,11 @@ function Diagram() {
setSelectedNode(null); setSelectedNode(null);
}; };
const onConnect = useCallback(
(params) => setEdges((eds) => addEdge({ ...params, animated: true }, eds)),
[setEdges]
);
const onEdgeClick = (_, edge) => { const onEdgeClick = (_, edge) => {
setSelectedEdge(edge); setSelectedEdge(edge);
setSelectedNode(null); setSelectedNode(null);
@ -162,12 +135,9 @@ function Diagram() {
setSelectedEdge(null); setSelectedEdge(null);
}; };
const onConnect = useCallback( const onNodeClick = (_, node) => setSelectedNode(node);
(params) => setEdges((eds) => addEdge({ ...params, animated: true }, eds)),
[setEdges]
);
const handleNodeDoubleClick = (event, node) => { const handleNodeDoubleClick = (_, node) => {
const newLabel = prompt('Enter new name:', node.data.label); const newLabel = prompt('Enter new name:', node.data.label);
if (newLabel !== null) { if (newLabel !== null) {
setNodes((nds) => setNodes((nds) =>
@ -179,19 +149,12 @@ function Diagram() {
} }
}; };
const onNodeClick = (_, node) => {
setSelectedNode(node);
};
const handleDeleteDiagram = async () => { const handleDeleteDiagram = async () => {
if (!diagramName || diagramName === 'default') { if (!diagramName || diagramName === 'default') {
toast.warn("❌ Cannot delete 'default' diagram or nothing selected."); toast.warn("❌ Cannot delete 'default' diagram or nothing selected.");
return; return;
} }
if (!confirm(`Delete "${diagramName}"?`)) return;
const confirmDelete = confirm(`Are you sure you want to delete diagram "${diagramName}"?`);
if (!confirmDelete) return;
try { try {
await deleteDiagramByName(diagramName); await deleteDiagramByName(diagramName);
const updatedList = diagramList.filter(name => name !== diagramName); const updatedList = diagramList.filter(name => name !== diagramName);
@ -208,8 +171,8 @@ function Diagram() {
return ( return (
<div style={{ width: '100vw', height: '100vh' }}> <div style={{ width: '100vw', height: '100vh' }}>
<div style={{ position: 'absolute', zIndex: 10, right: 10, top: 10, display: 'flex', alignItems: 'center', gap: '10px', height: '48px' }}> <div style={{ position: 'absolute', zIndex: 10, right: 10, top: 10, display: 'flex', alignItems: 'center', gap: 10 }}>
<div style={{ position: 'relative', display: 'flex', alignItems: 'center' }}> <div style={{ position: 'relative' }}>
<select <select
value={diagramName || ''} value={diagramName || ''}
onChange={(e) => setDiagramName(e.target.value)} onChange={(e) => setDiagramName(e.target.value)}
@ -229,22 +192,12 @@ function Diagram() {
}} }}
> >
{diagramList.map((name) => ( {diagramList.map((name) => (
<option key={name} value={name} style={{ color: 'white', font: 'bold 16px Arial' }}> <option key={name} value={name}>
📌 {name} 📌 {name}
</option> </option>
))} ))}
</select> </select>
<div <div style={{ position: 'absolute', right: 12, top: '50%', transform: 'translateY(-50%)', pointerEvents: 'none', color: 'white' }}>
style={{
position: 'absolute',
right: 12,
top: '50%',
transform: 'translateY(-50%)',
pointerEvents: 'none',
fontSize: 16,
color: 'white',
}}
>
</div> </div>
</div> </div>
@ -255,43 +208,36 @@ function Diagram() {
const newName = prompt("Enter new diagram name:"); const newName = prompt("Enter new diagram name:");
if (newName) { if (newName) {
if (!diagramList.includes(newName)) { if (!diagramList.includes(newName)) {
const updatedList = [...diagramList, newName]; setDiagramList((prev) => [...prev, newName]);
setDiagramList(updatedList);
} }
setDiagramName(newName); setDiagramName(newName);
setNodes([]); setNodes([]);
setEdges([]); setEdges([]);
setSelectedNode(null);
setSelectedEdge(null);
} }
}}>🆕 New Diagram</button> }}>🆕 New Diagram</button>
<button className="btn" onClick={handleSave} style={{ background: 'green' }}>💾 Save</button> <button className="btn" onClick={handleSave} style={{ background: 'green' }}>💾 Save</button>
<button className="btn" onClick={handleAddNode} style={{ background: 'blue' }}> Add Node</button> <button className="btn" onClick={() => setShowForm(true)} style={{ background: 'blue' }}> Add Node</button>
<button className="btn" onClick={handleDeleteNode} style={{ background: selectedNode ? '#b81a1a' : '#ccc' }} disabled={!selectedNode}>🗑 Delete Node</button>
<button className="btn" onClick={handleDeleteNode} style={{ background: selectedNode ? '#b81a1a' : '#ccc', color: selectedNode ? 'white' : '#666', cursor: selectedNode ? 'pointer' : 'not-allowed' }} disabled={!selectedNode}>🗑 Delete Node</button> <button className="btn" onClick={handleDeleteEdge} style={{ background: selectedEdge ? '#b81a1a' : '#ccc' }} disabled={!selectedEdge}>🗑 Delete Edge</button>
<button className="btn" onClick={handleDeleteEdge} style={{ background: selectedEdge ? '#b81a1a' : '#ccc', color: selectedEdge ? 'white' : '#666', cursor: selectedEdge ? 'pointer' : 'not-allowed' }} disabled={!selectedEdge}>🗑 Delete Edge</button>
<button className="btn" onClick={handleDeleteDiagram} style={{ background: 'red' }}>🗑 Delete Diagram</button> <button className="btn" onClick={handleDeleteDiagram} style={{ background: 'red' }}>🗑 Delete Diagram</button>
</> </>
)} )}
</div> </div>
{showForm && ( {showForm && (
<div style={{ position: 'absolute', zIndex: 20, right: 10, top: 80, background: '#e6f0fa', padding: '16px', borderRadius: '12px', boxShadow: '0 4px 8px rgba(0,0,0,0.15)', width: 280 }}> <div style={{ position: 'absolute', zIndex: 20, right: 10, top: 80, background: '#13141a', padding: '16px', borderRadius: '12px', boxShadow: '0 4px 8px rgba(0,0,0,0.15)', width: 280 }}>
<h4 style={{ color: '#125ea8', marginBottom: 12, fontSize: 18, display: 'flex', alignItems: 'center', gap: 8 }}>🧩 Add New Node</h4> <h4 style={{ color: 'white', marginBottom: 12 }}>🧩 Add New Node</h4>
<label style={{ fontWeight: 'bold', marginBottom: 4, display: 'block', color: '#333' }}>Label:</label> <label style={{ fontWeight: 'bold', color: 'white' }}>Label:</label>
<input <input
type="text" type="text"
placeholder="Enter label"
value={newLabel} value={newLabel}
onChange={(e) => setNewLabel(e.target.value)} onChange={(e) => setNewLabel(e.target.value)}
style={{ padding: '6px', width: '100%', borderRadius: 6, border: '1px solid #ccc', marginBottom: 12 }} style={{ padding: '6px', width: '100%', borderRadius: 6, border: '1px solid #ccc', marginBottom: 12 }}
/> />
<label style={{ fontWeight: 'bold', marginBottom: 4, display: 'block', color: '#333' }}>Type:</label> <label style={{ fontWeight: 'bold', color: 'white' }}>Type:</label>
<select <select
value={newType} value={newType}
onChange={(e) => setNewType(e.target.value)} onChange={(e) => setNewType(e.target.value)}
@ -299,12 +245,9 @@ function Diagram() {
> >
<option value="custom">🧱 Custom</option> <option value="custom">🧱 Custom</option>
<option value="router">📡 Router</option> <option value="router">📡 Router</option>
<option value="color">🎨 Color Node</option>
</select> </select>
{newType === 'custom' && ( {newType === 'custom' && <IconSelector onSelect={setSelectedIcon} />}
<IconSelector onSelect={setSelectedIcon} />
)}
<div style={{ marginTop: 12 }}> <div style={{ marginTop: 12 }}>
<button className="btn" onClick={handleSubmitNode}> Add</button> <button className="btn" onClick={handleSubmitNode}> Add</button>

View File

@ -25,7 +25,7 @@ function IconSelector({ onSelect }) {
return ( return (
<div style={{ marginBottom: '1rem' }}> <div style={{ marginBottom: '1rem' }}>
<div style={{ marginBottom: '0.5rem' }}> <div style={{ marginBottom: '0.5rem' }}>
<label style={{ fontWeight: 'bold', color: '#333' }}>Category:&nbsp;</label> <label style={{ fontWeight: 'bold', color: 'white' }}>Category:&nbsp;</label>
<select <select
value={selectedCategory} value={selectedCategory}
onChange={handleCategoryChange} onChange={handleCategoryChange}
@ -47,7 +47,7 @@ function IconSelector({ onSelect }) {
{selectedCategory && ( {selectedCategory && (
<div> <div>
<label style={{ fontWeight: 'bold', color: 'black' }}>Choose an icon:</label> <label style={{ fontWeight: 'bold', color: 'white' }}>Choose an icon:</label>
<div <div
style={{ style={{
display: 'grid', display: 'grid',
@ -81,7 +81,7 @@ function IconSelector({ onSelect }) {
))} ))}
</div> </div>
{selectedIcon && ( {selectedIcon && (
<p style={{ marginTop: 8, fontSize: 13, color: 'black' }}> <p style={{ marginTop: 8, fontSize: 13, color: 'white' }}>
Selected: <code>{selectedIcon.split('/').pop()}</code> Selected: <code>{selectedIcon.split('/').pop()}</code>
</p> </p>
)} )}