|
|
@@ -12,18 +12,13 @@ import type { NodeMouseEvent, Connection, NodeDragEvent } from '@vue-flow/core'
|
|
|
import { ref, onMounted, computed, provide } from 'vue'
|
|
|
import { VueFlow, useVueFlow, MarkerType } from '@vue-flow/core'
|
|
|
import { MiniMap } from '@vue-flow/minimap'
|
|
|
+import { onKeyDown, onKeyUp, useThrottleFn } from '@vueuse/core'
|
|
|
|
|
|
import CanvasNode from './elements/nodes/CanvasNode.vue'
|
|
|
import CanvasEdge from './elements/edges/CanvasEdge.vue'
|
|
|
import CanvasArrowHeadMarker from './elements/edges/CanvasArrowHeadMarker.vue'
|
|
|
import CanvasBackground from './elements/background/CanvasBackground.vue'
|
|
|
import CanvasControlBar from './elements/control-bar/CanvasControlBar.vue'
|
|
|
-import ConditionNode from './elements/node-temp/ConditionNode.vue'
|
|
|
-import StartNode from './elements/node-temp/StartNode.vue'
|
|
|
-import HttpNode from './elements/node-temp/HttpNode1.vue'
|
|
|
-import EndNode from './elements/node-temp/EndNode.vue'
|
|
|
-import CodeNode from './elements/node-temp/CodeNode.vue'
|
|
|
-import DataBaseNode from './elements/node-temp/DataBaseNode.vue'
|
|
|
|
|
|
defineOptions({
|
|
|
name: 'workflow-canvas'
|
|
|
@@ -47,6 +42,7 @@ const emit = defineEmits<{
|
|
|
'update:logs:output-open': [open?: boolean]
|
|
|
'update:has-range-selection': [isActive: boolean]
|
|
|
'click:node': [id: string, position: XYPosition]
|
|
|
+ 'dblclick:node': [id: string, position: XYPosition]
|
|
|
'click:node:add': [id: string, handle: string]
|
|
|
'initialized:nodes': []
|
|
|
'run:node': [id: string]
|
|
|
@@ -93,7 +89,20 @@ const props = withDefaults(
|
|
|
const showMinimap = ref(false)
|
|
|
const vueFlow = useVueFlow(props.id)
|
|
|
|
|
|
-const { viewport, viewportRef, project, zoomIn, zoomOut, fitView, zoomTo } = vueFlow
|
|
|
+const {
|
|
|
+ viewport,
|
|
|
+ viewportRef,
|
|
|
+ project,
|
|
|
+ zoomIn,
|
|
|
+ zoomOut,
|
|
|
+ fitView,
|
|
|
+ zoomTo,
|
|
|
+ onNodeMouseEnter,
|
|
|
+ onNodeMouseLeave,
|
|
|
+ onEdgeMouseEnter,
|
|
|
+ onEdgeMouseLeave,
|
|
|
+ onEdgeMouseMove
|
|
|
+} = vueFlow
|
|
|
|
|
|
const nodeDataById = computed((): Record<string, IWorkflowNode['data']> => {
|
|
|
return props.nodes.reduce<Record<string, IWorkflowNode['data']>>((acc, node) => {
|
|
|
@@ -102,6 +111,57 @@ const nodeDataById = computed((): Record<string, IWorkflowNode['data']> => {
|
|
|
}, {})
|
|
|
})
|
|
|
|
|
|
+/**
|
|
|
+ * Edge and Nodes Hovering
|
|
|
+ */
|
|
|
+
|
|
|
+const edgesHoveredById = ref<Record<string, boolean>>({})
|
|
|
+const edgesBringToFrontById = ref<Record<string, boolean>>({})
|
|
|
+
|
|
|
+onEdgeMouseEnter(({ edge }) => {
|
|
|
+ edgesBringToFrontById.value = { [edge.id]: true }
|
|
|
+ edgesHoveredById.value = { [edge.id]: true }
|
|
|
+})
|
|
|
+
|
|
|
+onEdgeMouseMove(
|
|
|
+ useThrottleFn(({ edge, event }) => {
|
|
|
+ const type = edge.data.source.type
|
|
|
+ if (type !== 'ai_tool') {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!edge.data.maxConnections || edge.data.maxConnections > 1) {
|
|
|
+ const projectedPosition = getProjectedPosition(event)
|
|
|
+ const yDiff = projectedPosition.y - edge.targetY
|
|
|
+ if (yDiff < 4 * 16) {
|
|
|
+ edgesBringToFrontById.value = { [edge.id]: false }
|
|
|
+ } else {
|
|
|
+ edgesBringToFrontById.value = { [edge.id]: true }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }, 100)
|
|
|
+)
|
|
|
+
|
|
|
+onEdgeMouseLeave(({ edge }) => {
|
|
|
+ edgesBringToFrontById.value = { [edge.id]: false }
|
|
|
+ edgesHoveredById.value = { [edge.id]: false }
|
|
|
+})
|
|
|
+
|
|
|
+function onUpdateEdgeLabelHovered(id: string, hovered: boolean) {
|
|
|
+ edgesBringToFrontById.value = { [id]: true }
|
|
|
+ edgesHoveredById.value[id] = hovered
|
|
|
+}
|
|
|
+
|
|
|
+const nodesHoveredById = ref<Record<string, boolean>>({})
|
|
|
+
|
|
|
+onNodeMouseEnter(({ node }) => {
|
|
|
+ nodesHoveredById.value = { [node.id]: true }
|
|
|
+})
|
|
|
+
|
|
|
+onNodeMouseLeave(({ node }) => {
|
|
|
+ nodesHoveredById.value = { [node.id]: false }
|
|
|
+})
|
|
|
+
|
|
|
/**
|
|
|
* Returns the position of a mouse or touch event
|
|
|
*/
|
|
|
@@ -130,6 +190,10 @@ const onNodeClick = ({ node, event }: NodeMouseEvent) => {
|
|
|
emit('click:node', node.id, getProjectedPosition(event))
|
|
|
}
|
|
|
|
|
|
+const onNodeDoubleClick = ({ node, event }: NodeMouseEvent) => {
|
|
|
+ emit('dblclick:node', node.id, getProjectedPosition(event))
|
|
|
+}
|
|
|
+
|
|
|
function onDrop(event: DragEvent) {
|
|
|
const position = getProjectedPosition(event)
|
|
|
|
|
|
@@ -258,6 +322,7 @@ defineExpose({
|
|
|
snap-to-grid
|
|
|
:snap-grid="[16, 16]"
|
|
|
@node-click="onNodeClick"
|
|
|
+ @node-double-click="onNodeDoubleClick"
|
|
|
@node-drag-stop="onNodeDragStop"
|
|
|
@drop="onDrop"
|
|
|
@connect="onConnect"
|
|
|
@@ -267,21 +332,30 @@ defineExpose({
|
|
|
v-bind="$attrs"
|
|
|
>
|
|
|
<template #node-canvas-node="nodeProps">
|
|
|
- <CanvasNode
|
|
|
- v-bind="nodeProps"
|
|
|
- :data="nodeDataById[nodeProps.id]!"
|
|
|
- @move="onUpdateNodePosition"
|
|
|
- @update="onUpdateNodeAttrs"
|
|
|
- />
|
|
|
+ <slot name="node" v-bind="{ nodeProps }">
|
|
|
+ <CanvasNode
|
|
|
+ v-bind="nodeProps"
|
|
|
+ :data="nodeDataById[nodeProps.id]!"
|
|
|
+ :read-only="readOnly"
|
|
|
+ :hovered="nodesHoveredById[nodeProps.id]"
|
|
|
+ @move="onUpdateNodePosition"
|
|
|
+ @update="onUpdateNodeAttrs"
|
|
|
+ />
|
|
|
+ </slot>
|
|
|
</template>
|
|
|
|
|
|
<template #edge-canvas-edge="edgeProps">
|
|
|
- <CanvasEdge
|
|
|
- v-bind="edgeProps"
|
|
|
- marker-end="url(#custom-arrow-head-marker)"
|
|
|
- @add="onClickConnectionAdd"
|
|
|
- @delete="onDeleteConnection"
|
|
|
- />
|
|
|
+ <slot name="edge" v-bind="{ edgeProps }">
|
|
|
+ <CanvasEdge
|
|
|
+ v-bind="edgeProps"
|
|
|
+ :read-only="readOnly"
|
|
|
+ :hovered="edgesHoveredById[edgeProps.id]"
|
|
|
+ marker-end="url(#custom-arrow-head-marker)"
|
|
|
+ @add="onClickConnectionAdd"
|
|
|
+ @delete="onDeleteConnection"
|
|
|
+ @update:label:hovered="onUpdateEdgeLabelHovered(edgeProps.id, $event)"
|
|
|
+ />
|
|
|
+ </slot>
|
|
|
</template>
|
|
|
|
|
|
<template #background>
|