+
-
+
-
-
+
+
-
-
-
-
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/vue-flow/layouting/initialElements.ts b/src/views/vue-flow/layouting/initialElements.ts
index 2a84ea4b74..caf927103b 100644
--- a/src/views/vue-flow/layouting/initialElements.ts
+++ b/src/views/vue-flow/layouting/initialElements.ts
@@ -1,81 +1,76 @@
import type { Edge, Node } from "@vue-flow/core";
const position = { x: 0, y: 0 };
-const type: string = "process";
+const nodeType = "process";
+const edgeType = "animation";
export const initialNodes: Node[] = [
{
id: "1",
position,
- type
+ type: nodeType
},
{
id: "2",
position,
- type
+ type: nodeType
},
{
id: "2a",
position,
- type
+ type: nodeType
},
{
id: "2b",
position,
- type
+ type: nodeType
},
{
id: "2c",
position,
- type
+ type: nodeType
},
{
id: "2d",
position,
- type
+ type: nodeType
},
{
id: "3",
position,
- type
+ type: nodeType
},
{
id: "4",
position,
- type
+ type: nodeType
},
{
id: "5",
position,
- type
+ type: nodeType
},
{
id: "6",
position,
- type
+ type: nodeType
},
{
id: "7",
position,
- type
+ type: nodeType
}
];
export const initialEdges: Edge[] = [
- { id: "e1-2", source: "1", target: "2", type: "animation", animated: true },
- { id: "e1-3", source: "1", target: "3", type: "animation", animated: true },
- { id: "e2-2a", source: "2", target: "2a", type: "animation", animated: true },
- { id: "e2-2b", source: "2", target: "2b", type: "animation", animated: true },
- { id: "e2-2c", source: "2", target: "2c", type: "animation", animated: true },
- {
- id: "e2c-2d",
- source: "2c",
- target: "2d",
- type: "animation",
- animated: true
- },
- { id: "e3-7", source: "3", target: "4", type: "animation", animated: true },
- { id: "e4-5", source: "4", target: "5", type: "animation", animated: true },
- { id: "e5-6", source: "5", target: "6", type: "animation", animated: true },
- { id: "e5-7", source: "5", target: "7", type: "animation", animated: true }
+ { id: "e1-2", source: "1", target: "2", type: edgeType, animated: true },
+ { id: "e1-3", source: "1", target: "3", type: edgeType, animated: true },
+ { id: "e2-2a", source: "2", target: "2a", type: edgeType, animated: true },
+ { id: "e2-2b", source: "2", target: "2b", type: edgeType, animated: true },
+ { id: "e2-2c", source: "2", target: "2c", type: edgeType, animated: true },
+ { id: "e2c-2d", source: "2c", target: "2d", type: edgeType, animated: true },
+ { id: "e3-7", source: "3", target: "4", type: edgeType, animated: true },
+ { id: "e4-5", source: "4", target: "5", type: edgeType, animated: true },
+ { id: "e5-6", source: "5", target: "6", type: edgeType, animated: true },
+ { id: "e5-7", source: "5", target: "7", type: edgeType, animated: true }
];
diff --git a/src/views/vue-flow/layouting/processNode.vue b/src/views/vue-flow/layouting/processNode.vue
index 0ff966ab57..0feb0873ed 100644
--- a/src/views/vue-flow/layouting/processNode.vue
+++ b/src/views/vue-flow/layouting/processNode.vue
@@ -1,9 +1,19 @@
-
-
-
-
-
-
+
+
+ 📥
+
+
+
+
+
{{ processLabel }}
@@ -79,31 +110,17 @@ const processLabel = toRef(() => {
display: flex;
align-items: center;
justify-content: center;
- width: 18px;
- height: 18px;
+ width: 24px;
+ height: 24px;
padding: 10px;
- font-size: 12px;
- color: white;
- border: 1px solid #4b5563;
border-radius: 99px;
}
-.spinner {
- width: 10px;
- height: 10px;
- border: 3px solid #f3f3f3;
- border-top: 3px solid #10b981;
- border-radius: 50%;
- animation: spin 1s linear infinite;
-}
-
-@keyframes spin {
- 0% {
- transform: rotate(0deg);
- }
-
- 100% {
- transform: rotate(360deg);
- }
+.process-node .vue-flow__handle {
+ width: unset;
+ height: unset;
+ font-size: 12px;
+ background: transparent;
+ border: none;
}
diff --git a/src/views/vue-flow/layouting/useLayout.ts b/src/views/vue-flow/layouting/useLayout.ts
new file mode 100644
index 0000000000..93483a5a9c
--- /dev/null
+++ b/src/views/vue-flow/layouting/useLayout.ts
@@ -0,0 +1,52 @@
+import dagre from "dagre";
+import { ref } from "vue";
+import { Position, useVueFlow } from "@vue-flow/core";
+
+export function useLayout() {
+ const { findNode } = useVueFlow();
+
+ const graph = ref(new dagre.graphlib.Graph());
+
+ const previousDirection = ref("LR");
+
+ function layout(nodes, edges, direction) {
+ const dagreGraph = new dagre.graphlib.Graph();
+
+ graph.value = dagreGraph;
+
+ dagreGraph.setDefaultEdgeLabel(() => ({}));
+
+ const isHorizontal = direction === "LR";
+ dagreGraph.setGraph({ rankdir: direction });
+
+ previousDirection.value = direction;
+
+ for (const node of nodes) {
+ const graphNode = findNode(node.id);
+
+ dagreGraph.setNode(node.id, {
+ width: graphNode.dimensions.width || 150,
+ height: graphNode.dimensions.height || 50
+ });
+ }
+
+ for (const edge of edges) {
+ dagreGraph.setEdge(edge.source, edge.target);
+ }
+
+ dagre.layout(dagreGraph);
+
+ return nodes.map(node => {
+ const nodeWithPosition = dagreGraph.node(node.id);
+
+ return {
+ ...node,
+ targetPosition: isHorizontal ? Position.Left : Position.Top,
+ sourcePosition: isHorizontal ? Position.Right : Position.Bottom,
+ position: { x: nodeWithPosition.x, y: nodeWithPosition.y }
+ };
+ });
+ }
+
+ return { graph, layout, previousDirection };
+}
diff --git a/src/views/vue-flow/layouting/useRunProcess.ts b/src/views/vue-flow/layouting/useRunProcess.ts
index a87a923225..9b074e7ca2 100644
--- a/src/views/vue-flow/layouting/useRunProcess.ts
+++ b/src/views/vue-flow/layouting/useRunProcess.ts
@@ -1,24 +1,40 @@
-import type dagre from "dagre";
-import type { Node } from "@vue-flow/core";
+import { ref, toRef, toValue } from "vue";
import { useVueFlow } from "@vue-flow/core";
-import { type MaybeRefOrGetter, toRef, toValue, ref } from "vue";
-export function useRunProcess(
- dagreGraph: MaybeRefOrGetter
-) {
- const { updateNodeData } = useVueFlow();
+export function useRunProcess({ graph: dagreGraph, cancelOnError = true }) {
+ const { updateNodeData, getConnectedEdges } = useVueFlow();
const graph = toRef(() => toValue(dagreGraph));
const isRunning = ref(false);
- const executedNodes = new Set();
- const runningTasks = new Map();
- async function runNode(node: { id: string }, isStart = false) {
+ const executedNodes = new Set();
+
+ const runningTasks = new Map();
+
+ const upcomingTasks = new Set();
+
+ async function runNode(node, isStart = false) {
if (executedNodes.has(node.id)) {
return;
}
+ upcomingTasks.add(node.id);
+
+ const incomers = getConnectedEdges(node.id).filter(
+ connection => connection.target === node.id
+ );
+
+ await Promise.all(
+ incomers.map(incomer => until(() => !incomer.data.isAnimating))
+ );
+
+ upcomingTasks.clear();
+
+ if (!isRunning.value) {
+ return;
+ }
+
executedNodes.add(node.id);
updateNodeData(node.id, {
@@ -29,32 +45,36 @@ export function useRunProcess(
});
const delay = Math.floor(Math.random() * 2000) + 1000;
- return new Promise(resolve => {
+
+ return new Promise(resolve => {
const timeout = setTimeout(
async () => {
- const children = graph.value.successors(
- node.id
- ) as unknown as string[];
+ const children = graph.value.successors(node.id);
const willThrowError = Math.random() < 0.15;
- if (willThrowError) {
+ if (!isStart && willThrowError) {
updateNodeData(node.id, { isRunning: false, hasError: true });
- await skipDescendants(node.id);
- runningTasks.delete(node.id);
+ if (toValue(cancelOnError)) {
+ await skipDescendants(node.id);
+ runningTasks.delete(node.id);
- resolve();
- return;
+ // @ts-expect-error
+ resolve();
+ return;
+ }
}
updateNodeData(node.id, { isRunning: false, isFinished: true });
+
runningTasks.delete(node.id);
if (children.length > 0) {
await Promise.all(children.map(id => runNode({ id })));
}
+ // @ts-expect-error
resolve();
},
isStart ? 0 : delay
@@ -64,7 +84,7 @@ export function useRunProcess(
});
}
- async function run(nodes: Node[]) {
+ async function run(nodes) {
if (isRunning.value) {
return;
}
@@ -82,7 +102,7 @@ export function useRunProcess(
clear();
}
- function reset(nodes: Node[]) {
+ function reset(nodes) {
clear();
for (const node of nodes) {
@@ -96,8 +116,8 @@ export function useRunProcess(
}
}
- async function skipDescendants(nodeId: string) {
- const children = graph.value.successors(nodeId) as unknown as string[];
+ async function skipDescendants(nodeId) {
+ const children = graph.value.successors(nodeId);
for (const child of children) {
updateNodeData(child, { isRunning: false, isSkipped: true });
@@ -105,9 +125,23 @@ export function useRunProcess(
}
}
- function stop() {
+ async function stop() {
isRunning.value = false;
+ for (const nodeId of upcomingTasks) {
+ clearTimeout(runningTasks.get(nodeId));
+ runningTasks.delete(nodeId);
+ // @ts-expect-error
+ updateNodeData(nodeId, {
+ isRunning: false,
+ isFinished: false,
+ hasError: false,
+ isSkipped: false,
+ isCancelled: true
+ });
+ await skipDescendants(nodeId);
+ }
+
for (const [nodeId, task] of runningTasks) {
clearTimeout(task);
runningTasks.delete(nodeId);
@@ -118,10 +152,11 @@ export function useRunProcess(
isSkipped: false,
isCancelled: true
});
- skipDescendants(nodeId);
+ await skipDescendants(nodeId);
}
executedNodes.clear();
+ upcomingTasks.clear();
}
function clear() {
@@ -132,3 +167,15 @@ export function useRunProcess(
return { run, stop, reset, isRunning };
}
+
+async function until(condition) {
+ return new Promise(resolve => {
+ const interval = setInterval(() => {
+ if (condition()) {
+ clearInterval(interval);
+ // @ts-expect-error
+ resolve();
+ }
+ }, 100);
+ });
+}
diff --git a/src/views/vue-flow/layouting/useShuffle.ts b/src/views/vue-flow/layouting/useShuffle.ts
new file mode 100644
index 0000000000..d2505080a9
--- /dev/null
+++ b/src/views/vue-flow/layouting/useShuffle.ts
@@ -0,0 +1,50 @@
+function shuffleArray(array) {
+ for (let i = array.length - 1; i > 0; i--) {
+ const j = Math.floor(Math.random() * (i + 1));
+ [array[i], array[j]] = [array[j], array[i]];
+ }
+}
+
+function generatePossibleEdges(nodes) {
+ const possibleEdges = [];
+
+ for (const sourceNode of nodes) {
+ for (const targetNode of nodes) {
+ if (sourceNode.id !== targetNode.id) {
+ const edgeId = `e${sourceNode.id}-${targetNode.id}`;
+ possibleEdges.push({
+ id: edgeId,
+ source: sourceNode.id,
+ target: targetNode.id,
+ type: "animation",
+ animated: true
+ });
+ }
+ }
+ }
+
+ return possibleEdges;
+}
+
+export function useShuffle() {
+ return nodes => {
+ const possibleEdges = generatePossibleEdges(nodes);
+ shuffleArray(possibleEdges);
+
+ const usedNodes = new Set();
+ const newEdges = [];
+
+ for (const edge of possibleEdges) {
+ if (
+ !usedNodes.has(edge.target) &&
+ (usedNodes.size === 0 || usedNodes.has(edge.source))
+ ) {
+ newEdges.push(edge);
+ usedNodes.add(edge.source);
+ usedNodes.add(edge.target);
+ }
+ }
+
+ return newEdges;
+ };
+}