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":"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":"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';
const getNodeColor = (type) => {
@ -71,50 +71,71 @@ const CustomNode = ({ id, data, selected, onEdit, onDelete }) => {
const nodeColor = getNodeColor(data.nodeType);
const nodeIcon = getNodeIcon(data.nodeType);
const handleEdit = (e) => {
e.stopPropagation();
const handleEdit = useCallback((e) => {
e?.stopPropagation?.();
e?.preventDefault?.();
if (onEdit) {
onEdit(id);
}
};
}, [id, onEdit]);
const handleDelete = (e) => {
e.stopPropagation();
const handleDelete = useCallback((e) => {
e?.stopPropagation?.();
e?.preventDefault?.();
if (onDelete) {
onDelete(id);
}
};
}, [id, onDelete]);
return (
<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' }}
>
{/* Action buttons - show on hover or when selected */}
<div className={`absolute -top-2 -right-2 flex space-x-1 transition-opacity ${selected ? 'opacity-100' : 'opacity-0 group-hover:opacity-100'}`}>
{/* Action buttons - always visible for debugging */}
<div
className="absolute -top-2 -right-2 flex space-x-1 transition-all duration-200 z-50 opacity-100"
style={{ pointerEvents: 'auto' }}
>
{/* Edit button */}
<button
onClick={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"
style={{ pointerEvents: 'auto' }}
<div
onMouseDown={handleEdit}
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',
userSelect: 'none',
position: 'relative',
zIndex: 1000,
touchAction: 'manipulation'
}}
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" />
</svg>
</button>
</div>
{/* Delete button */}
<button
onClick={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"
style={{ pointerEvents: 'auto' }}
<div
onMouseDown={handleDelete}
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',
userSelect: 'none',
position: 'relative',
zIndex: 1000,
touchAction: 'manipulation'
}}
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" />
</svg>
</button>
</div>
</div>
{/* 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 ReactFlow, {
ReactFlowProvider,
@ -51,16 +51,39 @@ const WorkflowEditor = () => {
const [viewport, setViewport] = useState({ x: 0, y: 0, zoom: 1 });
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
const nodeTypeDefinitions = {
const nodeTypeDefinitions = useMemo(() => ({
customNode: (nodeProps) => (
<CustomNode
{...nodeProps}
selected={selectedNode && selectedNode.id === nodeProps.id}
onEdit={handleEditNode}
onDelete={handleDeleteNode}
/>
)
};
}), [selectedNode, handleEditNode, handleDeleteNode]);
// Load workflow if editing existing one
useEffect(() => {
@ -306,28 +329,6 @@ const WorkflowEditor = () => {
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
const handleExecuteWorkflow = (executionId) => {
setLatestExecutionId(executionId);