diff --git a/src/ui/src/components/workflows/WorkflowsWorkflow.vue b/src/ui/src/components/workflows/WorkflowsWorkflow.vue index 8df276866..2ea1d6bcf 100644 --- a/src/ui/src/components/workflows/WorkflowsWorkflow.vue +++ b/src/ui/src/components/workflows/WorkflowsWorkflow.vue @@ -55,6 +55,16 @@
+ + view_column + wf.getComponents(workflowComponentId, { sortedByPosition: true }), ); @@ -214,6 +227,99 @@ function handleClick() { selectedArrow.value = null; } +function organizeNodesInColumns() { + const columns: Map> = new Map(); + + function scan(node: Component, layer: number) { + columns.forEach((column) => { + if (column.has(node)) { + column.delete(node); + } + }); + if (!columns.has(layer)) { + columns.set(layer, new Set()); + } + const column = columns.get(layer); + column.add(node); + node.outs?.forEach((out) => { + const outNode = wf.getComponentById(out.toNodeId); + scan(outNode, layer + 1); + }); + } + + const dependencies: Map> = new Map(); + + nodes.value.forEach((node) => { + node.outs?.forEach((outNode) => { + if (!dependencies.has(outNode.toNodeId)) { + dependencies.set(outNode.toNodeId, new Set()); + } + dependencies.get(outNode.toNodeId).add(node.id); + }); + }); + + nodes.value + .filter((node) => !dependencies.has(node.id)) + .forEach((startNode) => { + scan(startNode, 0); + }); + + return columns; +} + +function calculateAutoarrangeDimensions(columns: Map>) { + const columnDimensions: Map = + new Map(); + const nodeDimensions: Map = new Map(); + columns.forEach((nodes, layer) => { + let height = 0; + let width = 0; + nodes.forEach((node) => { + const nodeEl = nodeContainerEl.value.querySelector( + `[data-writer-id="${node.id}"]`, + ); + if (!nodeEl) return; + const nodeBCR = nodeEl.getBoundingClientRect(); + nodeDimensions.set(node.id, { + height: nodeBCR.height * (1 / zoomLevel.value), + }); + height += + nodeBCR.height * (1 / zoomLevel.value) + AUTOARRANGE_ROW_GAP_PX; + width = Math.max(width, nodeBCR.width * (1 / zoomLevel.value)); + }); + columnDimensions.set(layer, { + height: height - AUTOARRANGE_ROW_GAP_PX, + width, + }); + }); + return { columnDimensions, nodeDimensions }; +} + +function handleAutoarrange() { + const columns = organizeNodesInColumns(); + const { columnDimensions, nodeDimensions } = + calculateAutoarrangeDimensions(columns); + const maxColumnHeight = Math.max( + ...Array.from(columnDimensions.values()).map( + (dimensions) => dimensions.height, + ), + ); + + let x = AUTOARRANGE_COLUMN_GAP_PX; + for (let i = 0; i < columns.size; i++) { + const nodes = Array.from(columns.get(i)).sort((a, b) => + a.y > b.y ? 1 : -1, + ); + const { width, height } = columnDimensions.get(i); + let y = (maxColumnHeight - height) / 2 + AUTOARRANGE_ROW_GAP_PX; + nodes.forEach((node) => { + changeCoordinates(node.id, x, y); + y += nodeDimensions.get(node.id).height + AUTOARRANGE_ROW_GAP_PX; + }); + x += width + AUTOARRANGE_COLUMN_GAP_PX; + } +} + async function handleRun() { if (isRunning.value) return; isRunning.value = true;