From d90613119fc6bacf4e11b6b0017b86571e8a42d9 Mon Sep 17 00:00:00 2001 From: Kodjo Sossouvi Date: Wed, 2 Jul 2025 20:55:06 +0200 Subject: [PATCH] I can link items by hovering --- src/components/workflows/assets/Workflows.css | 22 ++++- src/components/workflows/assets/Workflows.js | 98 ++++++++++++++++--- 2 files changed, 108 insertions(+), 12 deletions(-) diff --git a/src/components/workflows/assets/Workflows.css b/src/components/workflows/assets/Workflows.css index 718e2fb..e68d2b9 100644 --- a/src/components/workflows/assets/Workflows.css +++ b/src/components/workflows/assets/Workflows.css @@ -32,6 +32,10 @@ box-shadow: 0 0 10px rgba(239, 68, 68, 0.3); } +.wkf-workflow-component.dragging { + transition: none; +} + .wkf-connection-line { position: absolute; pointer-events: none; @@ -47,8 +51,19 @@ cursor: crosshair; border: 2px solid white; box-shadow: 0 2px 4px rgba(0,0,0,0.2); + transition: background-color 0.2s, transform 0.2s; } +.wkf-connection-point.potential-connection { + box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.5); + animation: pulse 0.7s infinite; +} + +.wkf-connection-point.potential-start { + background: #ef4444; +} + + .wkf-output-point { right: -6px; top: 50%; @@ -64,4 +79,9 @@ .wkf-connection-point:hover { background: #ef4444; transform: translateY(-50%) scale(1.2); -} \ No newline at end of file +} +@keyframes pulse { + 0% { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.7); } + 70% { box-shadow: 0 0 0 6px rgba(59, 130, 246, 0); } + 100% { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0); } +} diff --git a/src/components/workflows/assets/Workflows.js b/src/components/workflows/assets/Workflows.js index 38310a8..6ed9d06 100644 --- a/src/components/workflows/assets/Workflows.js +++ b/src/components/workflows/assets/Workflows.js @@ -4,8 +4,8 @@ function bindWorkflowDesigner(elementId) { draggedType: null, draggedComponent: null, selectedComponent: null, - connectionMode: false, - connectionStart: null + connectionStart: null, + potentialConnectionStart: null, }; // Get the designer container and canvas @@ -25,8 +25,24 @@ function bindWorkflowDesigner(elementId) { // Handle components if (event.target.closest('.wkf-workflow-component')) { - designer.draggedComponent = event.target.closest('.wkf-workflow-component').dataset.componentId; + const component = event.target.closest('.wkf-workflow-component'); + component.classList.add('dragging'); + designer.draggedComponent = component.dataset.componentId; event.dataTransfer.effectAllowed = 'move'; + + // Create an invisible image to use as the drag image + const invisibleImg = new Image(); + invisibleImg.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'; // 1px transparent GIF + event.dataTransfer.setDragImage(invisibleImg, 0, 0); + + // Highlight all valid output points on other components + designerContainer.querySelectorAll('.wkf-connection-point').forEach(point => { + if (point.dataset.pointType === 'output' && + point.dataset.componentId !== designer.draggedComponent) { + point.classList.add('potential-connection'); + } + }); + } }); @@ -40,13 +56,44 @@ function bindWorkflowDesigner(elementId) { const y = event.clientY - rect.top - 40; component.style.left = Math.max(0, x) + 'px'; component.style.top = Math.max(0, y) + 'px'; + + const componentRect = component.getBoundingClientRect(); + const componentId = component.dataset.componentId; + const outputPoints = designerContainer.querySelectorAll('.wkf-connection-point[data-point-type="output"]'); + + outputPoints.forEach(point => { + if (point.dataset.componentId === componentId) return; // Skip points from the same component + + const pointRect = point.getBoundingClientRect(); + const pointCircle = { + x: pointRect.left + pointRect.width / 2, + y: pointRect.top + pointRect.height / 2, + radius: 6 + }; + + if (point != designer.potentialConnectionStart && _isOverlapping(componentRect, pointCircle)) { + console.debug("overlapping !") + outputPoints.forEach(other_point => { + other_point.classList.remove("potential-start") + }); + designer.potentialConnectionStart = point.dataset.componentId; + point.classList.add('potential-start'); + } + + }); + // Update connections in real-time updateConnections(designerContainer); }); designerContainer.addEventListener('dragend', (event) => { if (!event.target.closest('.wkf-workflow-component')) return; - if (designer.draggedComponent) { + + if (designer.draggedComponent) { + const component = event.target.closest('.wkf-workflow-component'); + const draggedComponentId = component.dataset.componentId; + component.classList.remove('dragging'); + const rect = canvas.getBoundingClientRect(); const x = event.clientX - rect.left - 64; const y = event.clientY - rect.top - 40; @@ -61,7 +108,30 @@ function bindWorkflowDesigner(elementId) { y: Math.max(0, y), } }); + + // Create connection if we ended the drag over a connection point + if (designer.potentialConnectionStart) { + + htmx.ajax('POST', '/workflows/add-connection', { + target: `#c_${elementId}`, + headers: {"Content-Type": "application/x-www-form-urlencoded"}, + swap: "innerHTML", + values: { + _id: elementId, + from_id: designer.potentialConnectionStart, // The output point we're hovering over + to_id: draggedComponentId, // The component we're dragging + } + }); + } + + // Remove highlighting from all connection points + designerContainer.querySelectorAll('.wkf-connection-point').forEach(point => { + point.classList.remove('potential-connection'); + }); + designer.draggedComponent = null; + designer.potentialConnectionStart = null; + // Update connections after drag ends updateConnections(designerContainer); } @@ -217,10 +287,16 @@ function bindWorkflowDesigner(elementId) { return designer; } -// Initialize all workflow designers on page load -document.addEventListener('DOMContentLoaded', () => { - // Find all workflow designer containers and bind them - document.querySelectorAll('[id^="wkf-designer-"]').forEach(designer => { - bindDesigner(designer.id); - }); -}); \ No newline at end of file +function _isOverlapping(rect, circle) { + // Find the closest point on the rectangle to the circle's center + const closestX = Math.max(rect.x, Math.min(circle.x, rect.x + rect.width)); + const closestY = Math.max(rect.y, Math.min(circle.y, rect.y + rect.height)); + + // Calculate the distance between the circle's center and the closest point + const deltaX = circle.x - closestX; + const deltaY = circle.y - closestY; + const distanceSquared = deltaX * deltaX + deltaY * deltaY; + + // Check if the distance is less than or equal to the circle's radius + return distanceSquared <= circle.radius * circle.radius; +}