I can drag and drop items into the canvas
I
This commit is contained in:
226
src/components/workflows/assets/Workflows.js
Normal file
226
src/components/workflows/assets/Workflows.js
Normal 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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user