|
|
@@ -6,7 +6,14 @@ import type {
|
|
|
CanvasNodeMoveEvent,
|
|
|
IWorkflowNode
|
|
|
} from '../Interface'
|
|
|
-import type { NodeMouseEvent, Connection, NodeDragEvent } from '@vue-flow/core'
|
|
|
+import type {
|
|
|
+ NodeMouseEvent,
|
|
|
+ Connection,
|
|
|
+ NodeDragEvent,
|
|
|
+ CoordinateExtent,
|
|
|
+ CoordinateExtentRange,
|
|
|
+ ValidConnectionFunc
|
|
|
+} from '@vue-flow/core'
|
|
|
|
|
|
import { ref, computed, toRef } from 'vue'
|
|
|
import { VueFlow, useVueFlow, MarkerType } from '@vue-flow/core'
|
|
|
@@ -87,13 +94,16 @@ const props = withDefaults(
|
|
|
id?: string
|
|
|
nodes: IWorkflow['nodes']
|
|
|
edges: IWorkflow['edges']
|
|
|
+ hideChildNode?: boolean
|
|
|
// 是否隐藏带parentId的节点
|
|
|
hideChildNode?: boolean
|
|
|
readOnly?: boolean
|
|
|
nodeMap: Record<string, any>
|
|
|
showControlBar?: boolean
|
|
|
zoomToFit?: boolean
|
|
|
- }>(),
|
|
|
+ translateExtent?: CoordinateExtent
|
|
|
+ nodeExtent?: CoordinateExtent | CoordinateExtentRange
|
|
|
+ }>(),
|
|
|
{
|
|
|
id: 'canvas',
|
|
|
readOnly: false,
|
|
|
@@ -108,6 +118,32 @@ const getNodes = computed(() =>
|
|
|
props.hideChildNode ? props.nodes.filter((node) => !node?.parentId) : props.nodes
|
|
|
)
|
|
|
const defaultViewport = { x: 0, y: 0, zoom: 1 }
|
|
|
+const projectExtent = computed<CoordinateExtent | undefined>(() => {
|
|
|
+ return Array.isArray(props.translateExtent) ? props.translateExtent : undefined
|
|
|
+})
|
|
|
+const isValidConnection: ValidConnectionFunc = (connection) => {
|
|
|
+ if (!connection.source || !connection.target) {
|
|
|
+ return true
|
|
|
+ }
|
|
|
+
|
|
|
+ return connection.source !== connection.target
|
|
|
+}
|
|
|
+
|
|
|
+const clampPositionToExtent = (
|
|
|
+ position: XYPosition,
|
|
|
+ extent?: CoordinateExtent
|
|
|
+): XYPosition => {
|
|
|
+ if (!extent) {
|
|
|
+ return position
|
|
|
+ }
|
|
|
+
|
|
|
+ const [[minX, minY], [maxX, maxY]] = extent
|
|
|
+
|
|
|
+ return {
|
|
|
+ x: Math.min(Math.max(position.x, minX), maxX),
|
|
|
+ y: Math.min(Math.max(position.y, minY), maxY)
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
const showMinimap = ref(false)
|
|
|
const vueFlow = useVueFlow(props.id)
|
|
|
@@ -240,10 +276,13 @@ function getProjectedPosition(event?: MouseEvent | TouchEvent) {
|
|
|
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 clampPositionToExtent(
|
|
|
+ project({
|
|
|
+ x: x - bounds.left,
|
|
|
+ y: y - bounds.top
|
|
|
+ }),
|
|
|
+ projectExtent.value
|
|
|
+ )
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -399,6 +438,9 @@ defineExpose({
|
|
|
:edges="normalizedEdges"
|
|
|
:default-viewport="defaultViewport"
|
|
|
:fit-view-on-init="zoomToFit"
|
|
|
+ :is-valid-connection="isValidConnection"
|
|
|
+ :translate-extent="translateExtent"
|
|
|
+ :node-extent="nodeExtent"
|
|
|
:connection-line-options="{ markerEnd: MarkerType.ArrowClosed }"
|
|
|
:connection-radius="60"
|
|
|
snap-to-grid
|