fix: action node between to state node on connect

also added connection line
This commit is contained in:
Shariq Ansari 2023-05-02 15:46:52 +05:30
parent 42247bc31f
commit 00b80aa0f5
6 changed files with 161 additions and 34 deletions

View file

@ -3,10 +3,12 @@ import { VueFlow, useVueFlow, Panel, PanelPosition } from "@vue-flow/core";
import { Background } from "@vue-flow/background";
import TransitionEdge from "./components/TransitionEdge.vue";
import StateNode from "./components/StateNode.vue";
import ActionNode from "./components/ActionNode.vue";
import ConnectionLine from "./components/ConnectionLine.vue";
import { useStore } from "./store";
let store = useStore();
let { nodes, onConnect, addNodes, addEdges } = useVueFlow();
let { nodes, findNode, onConnect, addNodes, addEdges } = useVueFlow();
function add_state() {
let state_id = (nodes.value.length + 1).toString();
@ -20,10 +22,50 @@ function add_state() {
]);
}
onConnect(params => {
params.animated = true;
params.type = "transition";
addEdges([params]);
onConnect(edge => {
let source_node = findNode(edge.source);
let target_node = findNode(edge.target);
let source_center = {
x: source_node.position.x + source_node.dimensions.width / 2,
y: source_node.position.y + source_node.dimensions.height / 2
};
let target_center = {
x: target_node.position.x + target_node.dimensions.width / 2,
y: target_node.position.y + target_node.dimensions.height / 2
};
let center_x = (source_center.x + target_center.x) / 2;
let center_y = source_center.y - 16;
const action_node = {
id: "action-" + frappe.utils.get_random(5),
type: "action",
label: "Approve",
position: { x: center_x, y: center_y }
};
addNodes([action_node]);
let action_edge = {
source: edge.source,
sourceHandle: edge.sourceHandle,
target: action_node.id,
targetHandle: "left",
type: "transition",
updatable: true,
animated: true
};
let state_edge = {
source: action_node.id,
sourceHandle: "right",
target: edge.target,
targetHandle: edge.targetHandle,
type: "transition",
updatable: true,
animated: true
};
addEdges([action_edge, state_edge]);
});
</script>
@ -37,9 +79,15 @@ onConnect(params => {
<template #node-state="node">
<StateNode :node="node" />
</template>
<template #node-action="node">
<ActionNode :node="node" />
</template>
<template #edge-transition="props">
<TransitionEdge v-bind="props" />
</template>
<template #connection-line="props">
<ConnectionLine v-bind="props" />
</template>
</VueFlow>
</div>
</template>

View file

@ -0,0 +1,44 @@
<script setup>
import { Handle } from "@vue-flow/core";
const props = defineProps({
node: {
type: Object,
required: true
}
});
</script>
<template>
<div class="node" tabindex="0">
<div v-if="node.label" class="node-label">{{ node.label }}</div>
<div v-else class="node-placeholder text-muted">{{ __("No Label") }}</div>
<Handle
v-for="handle in ['top', 'right', 'bottom', 'left']"
class="handle"
:style="{ [handle]: '-3px', opacity: 0 }"
type="source"
:position="handle"
:id="handle"
@click.stop
/>
</div>
</template>
<style lang="scss" scoped>
.node {
position: relative;
background-color: var(--gray-500);
font-weight: 500;
border-radius: 5px;
padding: 5px 10px;
color: var(--fg-color);
border: 1px solid var(--fg-color);
box-shadow: var(--shadow-base);
}
.vue-flow__node.selected .node {
outline: 1.5px solid var(--primary);
outline-offset: 2px;
}
</style>

View file

@ -0,0 +1,34 @@
<script setup>
import { getSmoothStepPath, SmoothStepEdge } from "@vue-flow/core";
import { computed } from "vue";
const props = defineProps({
sourceX: { type: Number, required: true },
sourceY: { type: Number, required: true },
targetX: { type: Number, required: true },
targetY: { type: Number, required: true },
sourcePosition: { type: String, required: false },
targetPosition: { type: String, required: false },
sourceHandle: { type: Object, required: false },
targetHandle: { type: Object, required: false },
markerEnd: { type: String, required: false }
});
let opposite = {
left: "left",
right: "right",
top: "bottom",
bottom: "top"
};
const d = computed(() =>
getSmoothStepPath({
...props,
borderRadius: 30,
targetPosition: opposite[props.targetPosition]
})
);
</script>
<template>
<SmoothStepEdge class="animated" :path="d[0]" />
</template>

View file

@ -1,5 +1,5 @@
<script setup>
import { Handle, Position } from "@vue-flow/core";
import { Handle } from "@vue-flow/core";
const props = defineProps({
node: {
@ -13,10 +13,14 @@ const props = defineProps({
<div class="node" tabindex="0">
<div v-if="node.label" class="node-label">{{ node.label }}</div>
<div v-else class="node-placeholder text-muted">{{ __("No Label") }}</div>
<Handle class="handle handle-a" type="source" :position="Position.Top" id="a" />
<Handle class="handle handle-b" type="source" :position="Position.Right" id="b" />
<Handle class="handle handle-c" type="source" :position="Position.Bottom" id="c" />
<Handle class="handle handle-d" type="source" :position="Position.Left" id="d" />
<Handle
v-for="handle in ['top', 'right', 'bottom', 'left']"
class="handle"
:style="{ [handle]: '-12px'}"
type="source"
:position="handle"
:id="handle"
/>
</div>
</template>
@ -44,21 +48,5 @@ const props = defineProps({
background-color: var(--gray-600);
border-radius: 50%;
transition: all 0.2s ease-in-out;
&.handle-a {
top: -12px;
}
&.handle-b {
right: -12px;
}
&.handle-c {
bottom: -12px;
}
&.handle-d {
left: -12px;
}
}
</style>

View file

@ -14,6 +14,8 @@ const props = defineProps({
targetPosition: { type: String, required: false },
sourceHandle: { type: String, required: false },
targetHandle: { type: String, required: false },
source: { type: String, required: false },
target: { type: String, required: false },
sourceNode: { type: Object, required: true },
targetNode: { type: Object, required: true },
markerEnd: { type: String, required: false },
@ -23,23 +25,24 @@ const props = defineProps({
let marker_end = {
type: "arrow",
width: 20,
height: 20,
width: 15,
height: 15,
strokeWidth: 1.5,
color: "#687178"
};
let marker_end_primary = {
type: "arrow",
width: 15,
height: 15,
strokeWidth: 1.5,
width: 11,
height: 11,
strokeWidth: 1.7,
color: "#2490ef"
};
watch(
() => props.selected,
() => {
if (props.target?.startsWith("action-")) return;
findEdge(props.id).markerEnd = props.selected ? marker_end_primary : marker_end;
},
{ immediate: true }

View file

@ -6,13 +6,23 @@ export const useStore = defineStore("workflow-builder-store", () => {
elements: [
{ id: "1", label: "Open", type: "state", position: { x: 300, y: 150 } },
{ id: "2", label: "Approved", type: "state", position: { x: 700, y: 150 } },
{ id: "action-1", label: "Approve", type: "action", position: { x: 500, y: 170 } },
{
id: "edge-1-2",
id: "edge-1-action-1",
source: "1",
target: "action-1",
type: "transition",
sourceHandle: "right",
targetHandle: "left",
animated: true,
},
{
id: "edge-action-1-2",
source: "action-1",
target: "2",
type: "transition",
sourceHandle: "b",
targetHandle: "d",
sourceHandle: "right",
targetHandle: "left",
animated: true,
},
],