Merge pull request 'develop' (#6) from develop into master

Reviewed-on: #6
This commit is contained in:
dvirlabs 2025-07-01 23:36:39 +00:00
commit 73dffb2893
6 changed files with 95 additions and 11 deletions

View File

@ -1,4 +1,25 @@
{ {
"nodes": [], "nodes": [
{
"id": "1",
"type": "custom",
"data": {
"label": "Node 1",
"icon": "https://s3.dvirlabs.com/lab-icons/dev-tools/argocd.svg"
},
"position": {
"x": 68.5849844537563,
"y": 225.18693121594464
},
"width": 82,
"height": 82,
"selected": false,
"positionAbsolute": {
"x": 68.5849844537563,
"y": 225.18693121594464
},
"dragging": false
}
],
"edges": [] "edges": []
} }

View File

@ -10,6 +10,7 @@
"dependencies": { "dependencies": {
"react": "^19.1.0", "react": "^19.1.0",
"react-dom": "^19.1.0", "react-dom": "^19.1.0",
"react-toastify": "^11.0.5",
"reactflow": "^11.11.4" "reactflow": "^11.11.4"
}, },
"devDependencies": { "devDependencies": {
@ -1589,6 +1590,14 @@
"resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz", "resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz",
"integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==" "integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w=="
}, },
"node_modules/clsx": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
"engines": {
"node": ">=6"
}
},
"node_modules/color-convert": { "node_modules/color-convert": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@ -2434,6 +2443,18 @@
"react": "^19.1.0" "react": "^19.1.0"
} }
}, },
"node_modules/react-toastify": {
"version": "11.0.5",
"resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-11.0.5.tgz",
"integrity": "sha512-EpqHBGvnSTtHYhCPLxML05NLY2ZX0JURbAdNYa6BUkk+amz4wbKBQvoKQAB0ardvSarUBuY4Q4s1sluAzZwkmA==",
"dependencies": {
"clsx": "^2.1.1"
},
"peerDependencies": {
"react": "^18 || ^19",
"react-dom": "^18 || ^19"
}
},
"node_modules/reactflow": { "node_modules/reactflow": {
"version": "11.11.4", "version": "11.11.4",
"resolved": "https://registry.npmjs.org/reactflow/-/reactflow-11.11.4.tgz", "resolved": "https://registry.npmjs.org/reactflow/-/reactflow-11.11.4.tgz",

View File

@ -12,6 +12,7 @@
"dependencies": { "dependencies": {
"react": "^19.1.0", "react": "^19.1.0",
"react-dom": "^19.1.0", "react-dom": "^19.1.0",
"react-toastify": "^11.0.5",
"reactflow": "^11.11.4" "reactflow": "^11.11.4"
}, },
"devDependencies": { "devDependencies": {

View File

@ -1,7 +1,14 @@
import Diagram from './components/Diagram'; import Diagram from './components/Diagram';
import { ToastContainer } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
function App() { function App() {
return <Diagram />; return (
<>
<Diagram />
<ToastContainer position="bottom-right" autoClose={3000} />
</>
);
} }
export default App; export default App;

View File

@ -3,31 +3,61 @@ import { Handle, Position } from 'reactflow';
function CustomNode({ data }) { function CustomNode({ data }) {
const icon = data.icon || 'https://s3.dvirlabs.com/lab-icons/default.svg'; const icon = data.icon || 'https://s3.dvirlabs.com/lab-icons/default.svg';
const handleSize = 5;
const visibleHandleStyle = {
background: '#1976d2',
width: handleSize,
height: handleSize,
borderRadius: '50%',
zIndex: 2,
};
const invisibleHandleStyle = {
background: 'transparent',
width: handleSize,
height: handleSize,
borderRadius: '50%',
zIndex: 1,
};
return ( return (
<div style={{ <div style={{
background: 'transparent', background: 'transparent',
border: '2px solid #1976d2', border: '2px solid #1976d2',
borderRadius: 6, borderRadius: 6,
padding: 10,
textAlign: 'center', textAlign: 'center',
width: 80, width: 80,
height: 40, height: 80,
boxShadow: '0 2px 6px rgba(0,0,0,0.2)', boxShadow: '0 2px 6px rgba(0,0,0,0.2)',
position: 'relative', position: 'relative',
}}> }}>
{/* Handles - חובה */} {/* === TOP === */}
<Handle type="target" position={Position.Top} /> <Handle id="source-top" type="source" position={Position.Top} style={{ ...visibleHandleStyle, top: -6 }} />
<Handle type="source" position={Position.Bottom} /> <Handle id="target-top" type="target" position={Position.Top} style={{ ...invisibleHandleStyle, top: -6 }} />
{/* === BOTTOM === */}
<Handle id="source-bottom" type="source" position={Position.Bottom} style={{ ...visibleHandleStyle, bottom: -6 }} />
<Handle id="target-bottom" type="target" position={Position.Bottom} style={{ ...invisibleHandleStyle, bottom: -6 }} />
{/* === LEFT === */}
<Handle id="source-left" type="source" position={Position.Left} style={{ ...visibleHandleStyle, left: -6, top: 30 }} />
<Handle id="target-left" type="target" position={Position.Left} style={{ ...invisibleHandleStyle, left: -6, top: 30 }} />
{/* === RIGHT === */}
<Handle id="source-right" type="source" position={Position.Right} style={{ ...visibleHandleStyle, right: -6, top: 30 }} />
<Handle id="target-right" type="target" position={Position.Right} style={{ ...invisibleHandleStyle, right: -6, top: 30 }} />
{/* === ICON + LABEL === */}
<img <img
src={icon} src={icon}
alt={data.label} alt={data.label}
onError={(e) => { onError={(e) => {
e.target.src = 'https://s3.dvirlabs.com/lab-icons/default.svg'; e.target.src = 'https://s3.dvirlabs.com/lab-icons/default.svg';
}} }}
style={{ width: 40, height: 40, marginBottom: 6 }} style={{ width: 40, height: 40, marginTop: 6 }}
/> />
<div style={{ fontWeight: 'bold' }}>{data.label}</div> <div style={{ fontWeight: 'bold', fontSize: 12 }}>{data.label}</div>
</div> </div>
); );
} }

View File

@ -1,4 +1,3 @@
// src/components/Diagram.jsx
import { useEffect, useState, useCallback } from 'react'; import { useEffect, useState, useCallback } from 'react';
import ReactFlow, { import ReactFlow, {
addEdge, addEdge,
@ -11,6 +10,7 @@ import 'reactflow/dist/style.css';
import { fetchDiagram, saveDiagram } from '../services/api'; import { fetchDiagram, saveDiagram } from '../services/api';
import CustomNode from './CustomNode'; import CustomNode from './CustomNode';
import IconSelector from './IconSelector'; import IconSelector from './IconSelector';
import { toast } from 'react-toastify';
const nodeTypes = { const nodeTypes = {
custom: CustomNode, custom: CustomNode,
@ -45,7 +45,7 @@ function Diagram() {
animated: !!animated, animated: !!animated,
})); }));
await saveDiagram({ nodes, edges: cleanedEdges }); await saveDiagram({ nodes, edges: cleanedEdges });
alert('Diagram saved!'); toast.success('✅ Diagram saved!');
}; };
const handleAddNode = () => { const handleAddNode = () => {
@ -65,6 +65,7 @@ function Diagram() {
}; };
setNodes((nds) => [...nds, newNode]); setNodes((nds) => [...nds, newNode]);
toast.success(`🟢 Node "${label}" added`);
setShowForm(false); setShowForm(false);
setNewLabel(''); setNewLabel('');
setSelectedIcon(''); setSelectedIcon('');
@ -72,8 +73,10 @@ function Diagram() {
const handleDeleteNode = () => { const handleDeleteNode = () => {
if (!selectedNode) return; if (!selectedNode) return;
setNodes((nds) => nds.filter((n) => n.id !== selectedNode.id)); setNodes((nds) => nds.filter((n) => n.id !== selectedNode.id));
setEdges((eds) => eds.filter((e) => e.source !== selectedNode.id && e.target !== selectedNode.id)); setEdges((eds) => eds.filter((e) => e.source !== selectedNode.id && e.target !== selectedNode.id));
toast.error(`🗑️ Node "${selectedNode.data.label}" deleted`);
setSelectedNode(null); setSelectedNode(null);
}; };
@ -90,6 +93,7 @@ function Diagram() {
n.id === node.id ? { ...n, data: { ...n.data, label: newLabel } } : n n.id === node.id ? { ...n, data: { ...n.data, label: newLabel } } : n
) )
); );
toast.info(`✏️ Node renamed to "${newLabel}"`);
} }
}; };