Enhance CustomNode and WorkflowEditor components: Added useCallback for edit and delete handlers, improved button interactions, and ensured node selection state is managed correctly. Updated log file with new server and node registry initialization messages.

This commit is contained in:
Mehmet Oezdag 2025-06-09 16:20:03 +02:00
parent 1c317d1a1d
commit e9cb32eb0e
3 changed files with 79 additions and 47 deletions

View File

@ -200,3 +200,13 @@
{"level":"debug","message":"Loaded node type: webhook","service":"flowforge-backend","timestamp":"2025-06-08 13:50:29"} {"level":"debug","message":"Loaded node type: webhook","service":"flowforge-backend","timestamp":"2025-06-08 13:50:29"}
{"level":"info","message":"Node registry initialized with 6 node types","service":"flowforge-backend","timestamp":"2025-06-08 13:50:29"} {"level":"info","message":"Node registry initialized with 6 node types","service":"flowforge-backend","timestamp":"2025-06-08 13:50:29"}
{"level":"info","message":"Database connection established successfully","service":"flowforge-backend","timestamp":"2025-06-08 13:50:29"} {"level":"info","message":"Database connection established successfully","service":"flowforge-backend","timestamp":"2025-06-08 13:50:29"}
{"level":"info","message":"Server running on port 4000","service":"flowforge-backend","timestamp":"2025-06-09 13:39:55"}
{"level":"info","message":"Initializing node registry","service":"flowforge-backend","timestamp":"2025-06-09 13:39:55"}
{"level":"debug","message":"Loaded node type: delay","service":"flowforge-backend","timestamp":"2025-06-09 13:39:55"}
{"level":"debug","message":"Loaded node type: email","service":"flowforge-backend","timestamp":"2025-06-09 13:39:55"}
{"level":"debug","message":"Loaded node type: function","service":"flowforge-backend","timestamp":"2025-06-09 13:39:55"}
{"level":"debug","message":"Loaded node type: http-request","service":"flowforge-backend","timestamp":"2025-06-09 13:39:55"}
{"level":"debug","message":"Loaded node type: logger","service":"flowforge-backend","timestamp":"2025-06-09 13:39:55"}
{"level":"debug","message":"Loaded node type: webhook","service":"flowforge-backend","timestamp":"2025-06-09 13:39:55"}
{"level":"info","message":"Node registry initialized with 6 node types","service":"flowforge-backend","timestamp":"2025-06-09 13:39:55"}
{"level":"info","message":"Database connection established successfully","service":"flowforge-backend","timestamp":"2025-06-09 13:39:55"}

View File

@ -1,4 +1,4 @@
import React, { memo } from 'react'; import React, { memo, useCallback } from 'react';
import { Handle, Position } from 'reactflow'; import { Handle, Position } from 'reactflow';
const getNodeColor = (type) => { const getNodeColor = (type) => {
@ -71,50 +71,71 @@ const CustomNode = ({ id, data, selected, onEdit, onDelete }) => {
const nodeColor = getNodeColor(data.nodeType); const nodeColor = getNodeColor(data.nodeType);
const nodeIcon = getNodeIcon(data.nodeType); const nodeIcon = getNodeIcon(data.nodeType);
const handleEdit = (e) => { const handleEdit = useCallback((e) => {
e.stopPropagation(); e?.stopPropagation?.();
e?.preventDefault?.();
if (onEdit) { if (onEdit) {
onEdit(id); onEdit(id);
} }
}; }, [id, onEdit]);
const handleDelete = (e) => { const handleDelete = useCallback((e) => {
e.stopPropagation(); e?.stopPropagation?.();
e?.preventDefault?.();
if (onDelete) { if (onDelete) {
onDelete(id); onDelete(id);
} }
}; }, [id, onDelete]);
return ( return (
<div <div
className={`group relative ${selected ? 'ring-2 ring-primary-400' : ''} bg-white rounded-md shadow-md border-l-4 ${nodeColor} p-3 min-w-[180px] cursor-grab active:cursor-grabbing hover:shadow-lg transition-shadow`} className={`group relative ${selected ? 'ring-2 ring-primary-400' : ''} bg-white rounded-md shadow-md border-l-4 ${nodeColor} p-3 min-w-[180px] cursor-grab active:cursor-grabbing hover:shadow-lg transition-all duration-200`}
style={{ pointerEvents: 'auto' }} style={{ pointerEvents: 'auto' }}
> >
{/* Action buttons - show on hover or when selected */} {/* Action buttons - always visible for debugging */}
<div className={`absolute -top-2 -right-2 flex space-x-1 transition-opacity ${selected ? 'opacity-100' : 'opacity-0 group-hover:opacity-100'}`}> <div
className="absolute -top-2 -right-2 flex space-x-1 transition-all duration-200 z-50 opacity-100"
style={{ pointerEvents: 'auto' }}
>
{/* Edit button */} {/* Edit button */}
<button <div
onClick={handleEdit} onMouseDown={handleEdit}
className="w-6 h-6 bg-blue-500 hover:bg-blue-600 text-white rounded-full flex items-center justify-center shadow-md transition-colors" className="w-10 h-10 bg-blue-500 hover:bg-blue-600 text-white rounded-full flex items-center justify-center shadow-xl transition-all duration-200 hover:scale-105 border-2 border-white cursor-pointer select-none"
style={{ pointerEvents: 'auto' }} style={{
pointerEvents: 'auto',
userSelect: 'none',
position: 'relative',
zIndex: 1000,
touchAction: 'manipulation'
}}
title="Edit node" title="Edit node"
role="button"
tabIndex={0}
> >
<svg xmlns="http://www.w3.org/2000/svg" className="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 pointer-events-none" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
</svg> </svg>
</button> </div>
{/* Delete button */} {/* Delete button */}
<button <div
onClick={handleDelete} onMouseDown={handleDelete}
className="w-6 h-6 bg-red-500 hover:bg-red-600 text-white rounded-full flex items-center justify-center shadow-md transition-colors" className="w-10 h-10 bg-red-500 hover:bg-red-600 text-white rounded-full flex items-center justify-center shadow-xl transition-all duration-200 hover:scale-105 border-2 border-white cursor-pointer select-none"
style={{ pointerEvents: 'auto' }} style={{
pointerEvents: 'auto',
userSelect: 'none',
position: 'relative',
zIndex: 1000,
touchAction: 'manipulation'
}}
title="Delete node" title="Delete node"
role="button"
tabIndex={0}
> >
<svg xmlns="http://www.w3.org/2000/svg" className="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 pointer-events-none" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg> </svg>
</button> </div>
</div> </div>
{/* Input handle */} {/* Input handle */}

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect, useCallback, useRef } from 'react'; import React, { useState, useEffect, useCallback, useRef, useMemo } from 'react';
import { useParams, useNavigate } from 'react-router-dom'; import { useParams, useNavigate } from 'react-router-dom';
import ReactFlow, { import ReactFlow, {
ReactFlowProvider, ReactFlowProvider,
@ -51,16 +51,39 @@ const WorkflowEditor = () => {
const [viewport, setViewport] = useState({ x: 0, y: 0, zoom: 1 }); const [viewport, setViewport] = useState({ x: 0, y: 0, zoom: 1 });
const [isFirstLoad, setIsFirstLoad] = useState(true); const [isFirstLoad, setIsFirstLoad] = useState(true);
// Handle node editing
const handleEditNode = useCallback((nodeId) => {
const node = nodes.find(n => n.id === nodeId);
if (node) {
setSelectedNode(node);
}
}, [nodes, setSelectedNode]);
// Handle node deletion
const handleDeleteNode = useCallback((nodeId) => {
// Show confirmation dialog
if (window.confirm('Are you sure you want to delete this node?')) {
setNodes((nds) => nds.filter((n) => n.id !== nodeId));
setEdges((eds) => eds.filter((e) => e.source !== nodeId && e.target !== nodeId));
// Clear selection if the deleted node was selected
if (selectedNode && selectedNode.id === nodeId) {
setSelectedNode(null);
}
}
}, [setNodes, setEdges, selectedNode, nodes]);
// Node type definitions for React Flow // Node type definitions for React Flow
const nodeTypeDefinitions = { const nodeTypeDefinitions = useMemo(() => ({
customNode: (nodeProps) => ( customNode: (nodeProps) => (
<CustomNode <CustomNode
{...nodeProps} {...nodeProps}
selected={selectedNode && selectedNode.id === nodeProps.id}
onEdit={handleEditNode} onEdit={handleEditNode}
onDelete={handleDeleteNode} onDelete={handleDeleteNode}
/> />
) )
}; }), [selectedNode, handleEditNode, handleDeleteNode]);
// Load workflow if editing existing one // Load workflow if editing existing one
useEffect(() => { useEffect(() => {
@ -306,28 +329,6 @@ const WorkflowEditor = () => {
setNodes((nds) => nds.concat(newNode)); setNodes((nds) => nds.concat(newNode));
}; };
// Handle node editing
const handleEditNode = useCallback((nodeId) => {
const node = nodes.find(n => n.id === nodeId);
if (node) {
setSelectedNode(node);
}
}, [nodes]);
// Handle node deletion
const handleDeleteNode = useCallback((nodeId) => {
// Show confirmation dialog
if (window.confirm('Are you sure you want to delete this node?')) {
setNodes((nds) => nds.filter((n) => n.id !== nodeId));
setEdges((eds) => eds.filter((e) => e.source !== nodeId && e.target !== nodeId));
// Clear selection if the deleted node was selected
if (selectedNode && selectedNode.id === nodeId) {
setSelectedNode(null);
}
}
}, [setNodes, setEdges, selectedNode]);
// Handle workflow execution // Handle workflow execution
const handleExecuteWorkflow = (executionId) => { const handleExecuteWorkflow = (executionId) => {
setLatestExecutionId(executionId); setLatestExecutionId(executionId);