I can drag and drop items into the canvas

I
This commit is contained in:
2025-07-02 18:23:49 +02:00
parent 7f6a19813d
commit f4e8f7a16c
10 changed files with 555 additions and 12 deletions

View File

@@ -0,0 +1,226 @@
function bindWorkflowDesigner(elementId) {
// Store state for this specific designer instance
const designer = {
draggedType: null,
draggedComponent: null,
selectedComponent: null,
connectionMode: false,
connectionStart: null
};
// Get the designer container and canvas
const designerContainer = document.getElementById(elementId);
//const canvas = designerContainer.querySelector('.wkf-canvas');
const canvas = document.getElementById("c_" + elementId);
// === Event delegation for components ===
// Use event delegation for all component-related events
designerContainer.addEventListener('dragstart', (event) => {
// Handle toolbox items
if (event.target.closest('.wkf-toolbox-item')) {
designer.draggedType = event.target.closest('.wkf-toolbox-item').dataset.type;
event.dataTransfer.effectAllowed = 'copy';
}
// Handle components
if (event.target.closest('.wkf-workflow-component')) {
designer.draggedComponent = event.target.closest('.wkf-workflow-component').dataset.componentId;
event.dataTransfer.effectAllowed = 'move';
}
});
designerContainer.addEventListener('drag', (event) => {
if (!event.target.closest('.wkf-workflow-component')) return;
if (event.clientX === 0 && event.clientY === 0) return; // Ignore invalid drag events
const component = event.target.closest('.wkf-workflow-component');
const rect = canvas.getBoundingClientRect();
const x = event.clientX - rect.left - 64;
const y = event.clientY - rect.top - 40;
component.style.left = Math.max(0, x) + 'px';
component.style.top = Math.max(0, y) + 'px';
// Update connections in real-time
updateConnections(designerContainer);
});
designerContainer.addEventListener('dragend', (event) => {
if (!event.target.closest('.wkf-workflow-component')) return;
if (designer.draggedComponent) {
const rect = canvas.getBoundingClientRect();
const x = event.clientX - rect.left - 64;
const y = event.clientY - rect.top - 40;
htmx.ajax('POST', '/workflows/move-component', {
target: `#c_${elementId}`,
headers: {"Content-Type": "application/x-www-form-urlencoded"},
swap: "innerHTML",
values: {
_id: elementId,
component_id: designer.draggedComponent,
x: Math.max(0, x),
y: Math.max(0, y),
}
});
designer.draggedComponent = null;
// Update connections after drag ends
updateConnections(designerContainer);
}
});
// Handle component clicks using event delegation
designerContainer.addEventListener('click', (event) => {
// Handle canvas clicks to reset connection mode and deselect components
if (event.target === canvas || event.target.classList.contains('wkf-canvas')) {
if (designer.connectionStart) {
designerContainer.querySelectorAll('.wkf-connection-point').forEach(point => {
point.style.background = '#3b82f6';
});
designer.connectionStart = null;
}
// Deselect components
designerContainer.querySelectorAll('.wkf-workflow-component').forEach(comp => {
comp.classList.remove('selected');
});
designer.selectedComponent = null;
return;
}
// Handle component selection
const component = event.target.closest('.wkf-workflow-component');
if (component) {
event.stopPropagation();
// Remove previous selection
designerContainer.querySelectorAll('.wkf-workflow-component').forEach(comp => {
comp.classList.remove('selected');
});
// Select current component
component.classList.add('selected');
designer.selectedComponent = component.dataset.componentId;
}
// Handle connection points
const connectionPoint = event.target.closest('.wkf-connection-point');
if (connectionPoint) {
event.stopPropagation();
const componentId = connectionPoint.dataset.componentId;
const pointType = connectionPoint.dataset.pointType;
if (!designer.connectionStart) {
// Start connection from output point
if (pointType === 'output') {
designer.connectionStart = {
componentId: componentId,
pointType: pointType
};
connectionPoint.style.background = '#ef4444';
console.log('Connection started from:', componentId);
}
} else {
// Complete connection to input point
if (pointType === 'input' && componentId !== designer.connectionStart.componentId) {
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.connectionStart.componentId,
to_id: componentId,
}
});
}
// Reset connection mode
designerContainer.querySelectorAll('.wkf-connection-point').forEach(point => {
point.style.background = '#3b82f6';
});
designer.connectionStart = null;
}
}
});
// Canvas drag over event (for dropping new components)
canvas.addEventListener('dragover', (event) => {
event.preventDefault();
event.dataTransfer.dropEffect = 'copy';
});
// Canvas drop event (for creating new components)
canvas.addEventListener('drop', (event) => {
event.preventDefault();
if (designer.draggedType) {
const rect = event.currentTarget.getBoundingClientRect();
const x = event.clientX - rect.left - 64; // Center the component
const y = event.clientY - rect.top - 40;
htmx.ajax('POST', '/workflows/add-component', {
target: `#c_${elementId}`,
headers: {"Content-Type": "application/x-www-form-urlencoded"},
swap: "innerHTML",
values: {
_id: elementId,
component_type: designer.draggedType,
x: Math.max(0, x),
y: Math.max(0, y),
}
});
designer.draggedType = null;
}
});
// Delete selected component with Delete key
document.addEventListener('keydown', (event) => {
if (event.key === 'Delete' && designer.selectedComponent) {
htmx.ajax('POST', '/workflows/delete-component', {
target: `#c_${elementId}`,
headers: {"Content-Type": "application/x-www-form-urlencoded"},
swap: "innerHTML",
values: {
_id: elementId,
component_id: designer.selectedComponent,
}
});
}
});
// Function to update all connection lines for this designer
function updateConnections(container) {
const connectionLines = container.querySelectorAll('.wkf-connection-line');
connectionLines.forEach(svg => {
const connectionData = svg.dataset;
if (connectionData.fromId && connectionData.toId) {
updateConnectionLine(svg, connectionData.fromId, connectionData.toId, container);
}
});
}
// Function to update a single connection line
function updateConnectionLine(svg, fromId, toId, container) {
const fromComp = container.querySelector(`[data-component-id="${fromId}"]`);
const toComp = container.querySelector(`[data-component-id="${toId}"]`);
if (!fromComp || !toComp) return;
// Get current positions
const fromX = parseInt(fromComp.style.left) + 128; // component width + output point
const fromY = parseInt(fromComp.style.top) + 40; // component height / 2
const toX = parseInt(toComp.style.left);
const toY = parseInt(toComp.style.top) + 40;
// Create curved path
const midX = (fromX + toX) / 2;
const path = `M ${fromX} ${fromY} C ${midX} ${fromY}, ${midX} ${toY}, ${toX} ${toY}`;
// Update the path
const pathElement = svg.querySelector('path');
if (pathElement) {
pathElement.setAttribute('d', path);
}
}
// Return the designer object for possible external use
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);
});
});