|
@@ -2,60 +2,68 @@
|
|
|
import { VueFlow, useVueFlow, type NodeMouseEvent, MarkerType } from '@vue-flow/core'
|
|
import { VueFlow, useVueFlow, type NodeMouseEvent, MarkerType } from '@vue-flow/core'
|
|
|
import { MiniMap } from '@vue-flow/minimap'
|
|
import { MiniMap } from '@vue-flow/minimap'
|
|
|
import type { IWorkflow, XYPosition } from '../Interface'
|
|
import type { IWorkflow, XYPosition } from '../Interface'
|
|
|
|
|
+import type { SourceType } from '@repo/nodes'
|
|
|
|
|
|
|
|
import CanvasNode from './elements/CanvasNode.vue'
|
|
import CanvasNode from './elements/CanvasNode.vue'
|
|
|
import CanvasEdge from './elements/CanvasEdge.vue'
|
|
import CanvasEdge from './elements/CanvasEdge.vue'
|
|
|
import CanvasBackground from './elements/background/CanvasBackground.vue'
|
|
import CanvasBackground from './elements/background/CanvasBackground.vue'
|
|
|
import CanvasControlBar from './elements/CanvasControlBar.vue'
|
|
import CanvasControlBar from './elements/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({
|
|
defineOptions({
|
|
|
- name: 'workflow-canvas'
|
|
|
|
|
|
|
+ name: 'workflow-canvas'
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
const emit = defineEmits<{
|
|
const emit = defineEmits<{
|
|
|
- 'update:node:position': [id: string, position: XYPosition]
|
|
|
|
|
- 'update:node:activated': [id: string, event?: MouseEvent]
|
|
|
|
|
- 'update:node:deactivated': [id: string]
|
|
|
|
|
- 'update:node:enabled': [id: string]
|
|
|
|
|
- 'update:node:selected': [id?: string]
|
|
|
|
|
- 'update:node:name': [id: string]
|
|
|
|
|
- 'update:node:parameters': [id: string, parameters: Record<string, unknown>]
|
|
|
|
|
- 'update:node:inputs': [id: string]
|
|
|
|
|
- 'update:node:outputs': [id: string]
|
|
|
|
|
- 'update:logs-open': [open?: boolean]
|
|
|
|
|
- 'update:logs:input-open': [open?: boolean]
|
|
|
|
|
- 'update:logs:output-open': [open?: boolean]
|
|
|
|
|
- 'update:has-range-selection': [isActive: boolean]
|
|
|
|
|
- 'click:node': [id: string, position: XYPosition]
|
|
|
|
|
- 'click:node:add': [id: string, handle: string]
|
|
|
|
|
- 'run:node': [id: string]
|
|
|
|
|
- 'copy:production:url': [id: string]
|
|
|
|
|
- 'copy:test:url': [id: string]
|
|
|
|
|
- 'delete:node': [id: string]
|
|
|
|
|
- 'replace:node': [id: string]
|
|
|
|
|
- 'create:node': [source: any]
|
|
|
|
|
- 'create:sticky': []
|
|
|
|
|
- 'delete:nodes': [ids: string[]]
|
|
|
|
|
- 'update:nodes:enabled': [ids: string[]]
|
|
|
|
|
- 'copy:nodes': [ids: string[]]
|
|
|
|
|
- 'duplicate:nodes': [ids: string[]]
|
|
|
|
|
- 'cut:nodes': [ids: string[]]
|
|
|
|
|
- 'drag-and-drop': [position: XYPosition, event: DragEvent]
|
|
|
|
|
|
|
+ 'update:node:position': [id: string, position: XYPosition]
|
|
|
|
|
+ 'update:node:activated': [id: string, event?: MouseEvent]
|
|
|
|
|
+ 'update:node:deactivated': [id: string]
|
|
|
|
|
+ 'update:node:enabled': [id: string]
|
|
|
|
|
+ 'update:node:selected': [id?: string]
|
|
|
|
|
+ 'update:node:name': [id: string]
|
|
|
|
|
+ 'update:node:parameters': [id: string, parameters: Record<string, unknown>]
|
|
|
|
|
+ 'update:node:inputs': [id: string]
|
|
|
|
|
+ 'update:node:outputs': [id: string]
|
|
|
|
|
+ 'update:logs-open': [open?: boolean]
|
|
|
|
|
+ 'update:logs:input-open': [open?: boolean]
|
|
|
|
|
+ 'update:logs:output-open': [open?: boolean]
|
|
|
|
|
+ 'update:has-range-selection': [isActive: boolean]
|
|
|
|
|
+ 'click:node': [id: string, position: XYPosition]
|
|
|
|
|
+ 'click:node:add': [id: string, handle: string]
|
|
|
|
|
+ 'run:node': [id: string]
|
|
|
|
|
+ 'copy:production:url': [id: string]
|
|
|
|
|
+ 'copy:test:url': [id: string]
|
|
|
|
|
+ 'delete:node': [id: string]
|
|
|
|
|
+ 'replace:node': [id: string]
|
|
|
|
|
+ 'create:node': [source: any]
|
|
|
|
|
+ 'create:sticky': []
|
|
|
|
|
+ 'delete:nodes': [ids: string[]]
|
|
|
|
|
+ 'update:nodes:enabled': [ids: string[]]
|
|
|
|
|
+ 'copy:nodes': [ids: string[]]
|
|
|
|
|
+ 'duplicate:nodes': [ids: string[]]
|
|
|
|
|
+ 'cut:nodes': [ids: string[]]
|
|
|
|
|
+ 'drag-and-drop': [position: XYPosition, event: DragEvent]
|
|
|
|
|
+ 'run': []
|
|
|
}>()
|
|
}>()
|
|
|
|
|
|
|
|
const props = withDefaults(
|
|
const props = withDefaults(
|
|
|
- defineProps<{
|
|
|
|
|
- id?: string
|
|
|
|
|
- nodes: IWorkflow['nodes']
|
|
|
|
|
- edges: IWorkflow['edges']
|
|
|
|
|
- readOnly?: boolean
|
|
|
|
|
- }>(),
|
|
|
|
|
- {
|
|
|
|
|
- id: 'canvas',
|
|
|
|
|
- readOnly: false,
|
|
|
|
|
- nodes: () => [],
|
|
|
|
|
- edges: () => []
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ defineProps<{
|
|
|
|
|
+ id?: string
|
|
|
|
|
+ nodes: IWorkflow['nodes']
|
|
|
|
|
+ edges: IWorkflow['edges']
|
|
|
|
|
+ readOnly?: boolean
|
|
|
|
|
+ }>(),
|
|
|
|
|
+ {
|
|
|
|
|
+ id: 'canvas',
|
|
|
|
|
+ readOnly: false,
|
|
|
|
|
+ nodes: () => [],
|
|
|
|
|
+ edges: () => []
|
|
|
|
|
+ }
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
const vueFlow = useVueFlow(props.id)
|
|
const vueFlow = useVueFlow(props.id)
|
|
@@ -66,91 +74,103 @@ const { viewport, viewportRef, project, zoomIn, zoomOut, fitView, zoomTo } = vue
|
|
|
* Returns the position of a mouse or touch event
|
|
* Returns the position of a mouse or touch event
|
|
|
*/
|
|
*/
|
|
|
const getMousePosition = (event: MouseEvent | TouchEvent): XYPosition => {
|
|
const getMousePosition = (event: MouseEvent | TouchEvent): XYPosition => {
|
|
|
- const x = (event && 'clientX' in event ? event.clientX : event?.touches?.[0]?.clientX) ?? 0
|
|
|
|
|
- const y = (event && 'clientY' in event ? event.clientY : event?.touches?.[0]?.clientY) ?? 0
|
|
|
|
|
|
|
+ const x = (event && 'clientX' in event ? event.clientX : event?.touches?.[0]?.clientX) ?? 0
|
|
|
|
|
+ const y = (event && 'clientY' in event ? event.clientY : event?.touches?.[0]?.clientY) ?? 0
|
|
|
|
|
|
|
|
- return { x, y }
|
|
|
|
|
|
|
+ return { x, y }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function getProjectedPosition(event?: MouseEvent | TouchEvent) {
|
|
function getProjectedPosition(event?: MouseEvent | TouchEvent) {
|
|
|
- const bounds = viewportRef.value?.getBoundingClientRect() ?? { left: 0, top: 0 }
|
|
|
|
|
- const { x, y } = event ? getMousePosition(event) : { x: 0, y: 0 }
|
|
|
|
|
|
|
+ const bounds = viewportRef.value?.getBoundingClientRect() ?? { left: 0, top: 0 }
|
|
|
|
|
+ const { x, y } = event ? getMousePosition(event) : { x: 0, y: 0 }
|
|
|
|
|
|
|
|
- return project({
|
|
|
|
|
- x: x - bounds.left,
|
|
|
|
|
- y: y - bounds.top
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ return project({
|
|
|
|
|
+ x: x - bounds.left,
|
|
|
|
|
+ y: y - bounds.top
|
|
|
|
|
+ })
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const onNodeClick = ({ node, event }: NodeMouseEvent) => {
|
|
const onNodeClick = ({ node, event }: NodeMouseEvent) => {
|
|
|
- emit('click:node', node.id, getProjectedPosition(event))
|
|
|
|
|
|
|
+ emit('click:node', node.id, getProjectedPosition(event))
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function onDrop(event: DragEvent) {
|
|
function onDrop(event: DragEvent) {
|
|
|
- const position = getProjectedPosition(event)
|
|
|
|
|
|
|
+ const position = getProjectedPosition(event)
|
|
|
|
|
|
|
|
- emit('drag-and-drop', position, event)
|
|
|
|
|
|
|
+ emit('drag-and-drop', position, event)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const onZoomIn = () => {
|
|
const onZoomIn = () => {
|
|
|
- zoomIn()
|
|
|
|
|
|
|
+ zoomIn()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const onZoomOut = () => {
|
|
const onZoomOut = () => {
|
|
|
- zoomOut()
|
|
|
|
|
|
|
+ zoomOut()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const onZoomToFit = () => {
|
|
const onZoomToFit = () => {
|
|
|
- fitView()
|
|
|
|
|
|
|
+ fitView()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const onResetZoom = () => {
|
|
const onResetZoom = () => {
|
|
|
- zoomTo(1)
|
|
|
|
|
|
|
+ zoomTo(1)
|
|
|
}
|
|
}
|
|
|
|
|
+const onAddNode = (value: SourceType) => {
|
|
|
|
|
+ emit('create:node', value)
|
|
|
|
|
+}
|
|
|
|
|
+const handleRun = () => {
|
|
|
|
|
+ emit('run')
|
|
|
|
|
+}
|
|
|
|
|
+console.log(props.nodes)
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
|
<template>
|
|
<template>
|
|
|
- <VueFlow
|
|
|
|
|
- :id="id"
|
|
|
|
|
- :nodes="nodes"
|
|
|
|
|
- :edges="edges"
|
|
|
|
|
- :connection-line-options="{ markerEnd: MarkerType.ArrowClosed }"
|
|
|
|
|
- :connection-radius="60"
|
|
|
|
|
- @node-click="onNodeClick"
|
|
|
|
|
- @drop="onDrop"
|
|
|
|
|
- v-bind="$attrs"
|
|
|
|
|
- >
|
|
|
|
|
- <template #node-canvas-node="nodeProps">
|
|
|
|
|
- <CanvasNode v-bind="nodeProps" />
|
|
|
|
|
- </template>
|
|
|
|
|
-
|
|
|
|
|
- <template #edge-canvas-edge="edgeProps">
|
|
|
|
|
- <CanvasEdge v-bind="edgeProps" />
|
|
|
|
|
- </template>
|
|
|
|
|
-
|
|
|
|
|
- <template #background>
|
|
|
|
|
- <rect width="100%" height="100%" fill="#f0f0f0" />
|
|
|
|
|
- </template>
|
|
|
|
|
-
|
|
|
|
|
- <MiniMap
|
|
|
|
|
- :height="120"
|
|
|
|
|
- :width="180"
|
|
|
|
|
- :node-border-radius="16"
|
|
|
|
|
- class="bg-white bottom-40px!"
|
|
|
|
|
- position="bottom-left"
|
|
|
|
|
- pannable
|
|
|
|
|
- zoomable
|
|
|
|
|
- />
|
|
|
|
|
-
|
|
|
|
|
- <CanvasControlBar
|
|
|
|
|
- @zoom-in="onZoomIn"
|
|
|
|
|
- @zoom-out="onZoomOut"
|
|
|
|
|
- @zoom-to-fit="onZoomToFit"
|
|
|
|
|
- @reset-zoom="onResetZoom"
|
|
|
|
|
- />
|
|
|
|
|
-
|
|
|
|
|
- <CanvasBackground :viewport="viewport" :striped="readOnly" />
|
|
|
|
|
- </VueFlow>
|
|
|
|
|
|
|
+ <VueFlow :id="id" :nodes="nodes" :edges="edges" :connection-line-options="{ markerEnd: MarkerType.ArrowClosed }"
|
|
|
|
|
+ :connection-radius="60" @node-click="onNodeClick" @drop="onDrop" v-bind="$attrs">
|
|
|
|
|
+ <template #node-canvas-node="nodeProps">
|
|
|
|
|
+ <CanvasNode v-bind="nodeProps" />
|
|
|
|
|
+ </template>
|
|
|
|
|
+
|
|
|
|
|
+ <template #node-start-node="nodeProps">
|
|
|
|
|
+ <StartNode v-bind="nodeProps" />
|
|
|
|
|
+ </template>
|
|
|
|
|
+
|
|
|
|
|
+ <template #node-end-node="nodeProps">
|
|
|
|
|
+ <EndNode v-bind="nodeProps" />
|
|
|
|
|
+ </template>
|
|
|
|
|
+
|
|
|
|
|
+ <template #node-http-node="nodeProps">
|
|
|
|
|
+ <HttpNode v-bind="nodeProps" />
|
|
|
|
|
+ </template>
|
|
|
|
|
+
|
|
|
|
|
+ <template #node-code-node="nodeProps">
|
|
|
|
|
+ <CodeNode v-bind="nodeProps" />
|
|
|
|
|
+ </template>
|
|
|
|
|
+
|
|
|
|
|
+ <template #node-database-node="nodeProps">
|
|
|
|
|
+ <DataBaseNode v-bind="nodeProps" />
|
|
|
|
|
+ </template>
|
|
|
|
|
+
|
|
|
|
|
+ <template #node-condition-node="nodeProps">
|
|
|
|
|
+ <ConditionNode v-bind="nodeProps" />
|
|
|
|
|
+ </template>
|
|
|
|
|
+
|
|
|
|
|
+ <template #edge-canvas-edge="edgeProps">
|
|
|
|
|
+ <CanvasEdge v-bind="edgeProps" />
|
|
|
|
|
+ </template>
|
|
|
|
|
+
|
|
|
|
|
+ <template #background>
|
|
|
|
|
+ <rect width="100%" height="100%" fill="#f0f0f0" />
|
|
|
|
|
+ </template>
|
|
|
|
|
+
|
|
|
|
|
+ <MiniMap :height="120" :width="180" :node-border-radius="16" class="bg-white bottom-40px!"
|
|
|
|
|
+ position="bottom-left" pannable zoomable />
|
|
|
|
|
+
|
|
|
|
|
+ <CanvasControlBar @zoom-in="onZoomIn" @zoom-out="onZoomOut" @zoom-to-fit="onZoomToFit" @reset-zoom="onResetZoom"
|
|
|
|
|
+ @add-node="onAddNode" @run="handleRun" />
|
|
|
|
|
+
|
|
|
|
|
+ <CanvasBackground :viewport="viewport" :striped="readOnly" />
|
|
|
|
|
+ </VueFlow>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<style>
|
|
<style>
|