diff --git a/examples/Superflow/Superflow.vue b/examples/Superflow/Superflow.vue new file mode 100644 index 00000000..eaef687e --- /dev/null +++ b/examples/Superflow/Superflow.vue @@ -0,0 +1,20 @@ + + + diff --git a/examples/router.ts b/examples/router.ts index 10d0463e..0631ce72 100644 --- a/examples/router.ts +++ b/examples/router.ts @@ -3,51 +3,55 @@ import { createRouter, createWebHashHistory, RouterOptions } from 'vue-router' export const routes: RouterOptions['routes'] = [ { path: '/', - redirect: '/basic' + redirect: '/basic', }, { path: '/basic', - component: () => import('./Basic/Basic.vue') + component: () => import('./Basic/Basic.vue'), }, { path: '/custom-connectionline', - component: () => import('./CustomConnectionLine/CustomConnectionLine.vue') + component: () => import('./CustomConnectionLine/CustomConnectionLine.vue'), }, { path: '/custom-node', - component: () => import('./CustomNode/CustomNode.vue') + component: () => import('./CustomNode/CustomNode.vue'), }, { path: '/drag-n-drop', - component: () => import('./DragNDrop/DnD.vue') + component: () => import('./DragNDrop/DnD.vue'), }, { path: '/edges', - component: () => import('./Edges') + component: () => import('./Edges'), }, { path: '/button-edge', - component: () => import('./EdgeWithButton/EdgeWithButton.vue') + component: () => import('./EdgeWithButton/EdgeWithButton.vue'), }, { path: '/edge-types', - component: () => import('./EdgeTypes') + component: () => import('./EdgeTypes'), }, { path: '/empty', - component: () => import('./Empty') + component: () => import('./Empty'), }, { path: '/hidden', - component: () => import('./Hidden') + component: () => import('./Hidden'), }, { path: '/interaction', - component: () => import('./Interaction') - } + component: () => import('./Interaction'), + }, + { + path: '/super-flow', + component: () => import('./Superflow/Superflow.vue'), + }, ] export const router = createRouter({ history: createWebHashHistory(), - routes + routes, }) diff --git a/package.json b/package.json index 6d85aa4d..11f2507f 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "lint": "yarn lint:js" }, "dependencies": { - "@braks/revue-draggable": "^0.2.9", + "@braks/revue-draggable": "0.2.5", "@types/d3": "^7.0.0", "@vueuse/core": "^6.5.3", "d3": "^7.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 67c6c91a..40403f1f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3,7 +3,7 @@ lockfileVersion: 5.3 specifiers: '@antfu/eslint-config': ^0.9.0 '@babel/core': ^7.15.8 - '@braks/revue-draggable': ^0.2.9 + '@braks/revue-draggable': 0.2.5 '@rollup/plugin-babel': ^5.3.0 '@rollup/plugin-commonjs': ^19.0.2 '@rollup/plugin-node-resolve': ^13.0.5 @@ -49,7 +49,7 @@ specifiers: vue-tsc: ^0.2.3 dependencies: - '@braks/revue-draggable': 0.2.9_vue@3.2.20 + '@braks/revue-draggable': 0.2.5_vue@3.2.20 '@types/d3': 7.0.0 '@vueuse/core': 6.5.3_vue@3.2.20 d3: 7.1.1 @@ -500,8 +500,8 @@ packages: to-fast-properties: 2.0.0 dev: true - /@braks/revue-draggable/0.2.9_vue@3.2.20: - resolution: {integrity: sha512-gTDRdv30VD6YmEqrc2mocHmS5vEEOhsDifRNYBJKstopN0Vp3rrJEnZu8bQaE3kEzFJbyfnaxfY1CYNkyCUyIQ==} + /@braks/revue-draggable/0.2.5_vue@3.2.20: + resolution: {integrity: sha512-iKGLFXaurfEqoyPbkANv8ZuO1229krVvUQDDYN9le5WlVssFWcQtA3hNvBD2TPuwil226qXv9SqXGp5kgDhAsA==} peerDependencies: '@vue/composition-api': ^1.0.0-rc.1 vue: ^2.0.0 || >=3.0.5 @@ -510,7 +510,6 @@ packages: optional: true dependencies: '@vueuse/core': 5.3.0_vue@3.2.20 - fast-deep-equal: 3.1.3 vue: 3.2.20 vue-demi: 0.11.4_vue@3.2.20 dev: false diff --git a/src/components/ConnectionLine/ConnectionLine.vue b/src/components/ConnectionLine/ConnectionLine.vue new file mode 100644 index 00000000..344e15f0 --- /dev/null +++ b/src/components/ConnectionLine/ConnectionLine.vue @@ -0,0 +1,102 @@ + + diff --git a/src/components/ConnectionLine/index.tsx b/src/components/ConnectionLine/index.tsx index e87b1502..c8cdfd13 100644 --- a/src/components/ConnectionLine/index.tsx +++ b/src/components/ConnectionLine/index.tsx @@ -1,7 +1,7 @@ import { ref, defineComponent, CSSProperties, PropType, computed, inject, h } from 'vue' -import { getBezierPath } from '../Edges/BezierEdge' -import { getSmoothStepPath } from '../Edges/SmoothStepEdge' +import { getBezierPath } from '../Edges/BezierEdgeDepr' +import { getSmoothStepPath } from '../Edges/SmoothStepEdgeDepr' import { Node, HandleElement, Position, ConnectionLineType, ConnectionLineComponent, RevueFlowStore } from '../../types' interface ConnectionLineProps { @@ -15,18 +15,18 @@ export default defineComponent({ connectionLineStyle: { type: Object as PropType, required: false, - default: () => {} + default: () => {}, }, connectionLineType: { type: String as PropType, required: false, - default: ConnectionLineType.Bezier + default: ConnectionLineType.Bezier, }, customConnectionLine: { type: Object as PropType, required: false, - default: undefined - } + default: undefined, + }, }, setup(props) { const store = inject('store')! @@ -36,15 +36,15 @@ export default defineComponent({ const sourceHandle = computed(() => store.connectionHandleId && store.connectionHandleType ? sourceNode.value?.__rf.handleBounds[store.connectionHandleType].find( - (d: HandleElement) => d.id === store.connectionHandleId + (d: HandleElement) => d.id === store.connectionHandleId, ) - : store.connectionHandleType && sourceNode.value?.__rf.handleBounds[store.connectionHandleType][0] + : store.connectionHandleType && sourceNode.value?.__rf.handleBounds[store.connectionHandleType][0], ) const sourceHandleX = computed(() => - sourceHandle.value ? sourceHandle.value.x + sourceHandle.value.width / 2 : (sourceNode.value?.__rf.width as number) / 2 + sourceHandle.value ? sourceHandle.value.x + sourceHandle.value.width / 2 : (sourceNode.value?.__rf.width as number) / 2, ) const sourceHandleY = computed(() => - sourceHandle.value ? sourceHandle.value.y + sourceHandle.value.height / 2 : sourceNode.value?.__rf.height + sourceHandle.value ? sourceHandle.value.y + sourceHandle.value.height / 2 : sourceNode.value?.__rf.height, ) const sourceX = computed(() => sourceNode.value?.__rf.position.x + sourceHandleX.value) const sourceY = computed(() => sourceNode.value?.__rf.position.y + sourceHandleY.value) @@ -53,7 +53,7 @@ export default defineComponent({ const targetY = computed(() => (store.connectionPosition.y - store.transform[1]) / store.transform[2]) const isRightOrLeft = computed( - () => sourceHandle.value?.position === Position.Left || sourceHandle.value?.position === Position.Right + () => sourceHandle.value?.position === Position.Left || sourceHandle.value?.position === Position.Right, ) const targetPosition = computed(() => (isRightOrLeft.value ? Position.Left : Position.Top)) @@ -67,8 +67,8 @@ export default defineComponent({ sourcePosition: sourceHandle.value?.position, targetX: targetX.value, targetY: targetY.value, - targetPosition: targetPosition.value - }) + targetPosition: targetPosition.value, + }), ) } else if (props.connectionLineType === ConnectionLineType.Step) { dAttr = computed(() => @@ -79,8 +79,8 @@ export default defineComponent({ targetX: targetX.value, targetY: targetY.value, targetPosition: targetPosition.value, - borderRadius: 0 - }) + borderRadius: 0, + }), ) } else if (props.connectionLineType === ConnectionLineType.SmoothStep) { dAttr = computed(() => @@ -90,14 +90,14 @@ export default defineComponent({ sourcePosition: sourceHandle.value?.position, targetX: targetX.value, targetY: targetY.value, - targetPosition: targetPosition.value - }) + targetPosition: targetPosition.value, + }), ) } if (props.customConnectionLine) { return () => ( - + {props.customConnectionLine && h(props.customConnectionLine, { sourceX: sourceX.value, @@ -107,7 +107,7 @@ export default defineComponent({ targetY: targetY.value, targetPosition: targetPosition.value, connectionLineType: props.connectionLineType, - connectionLineStyle: props.connectionLineStyle + connectionLineStyle: props.connectionLineStyle, })} ) @@ -115,11 +115,11 @@ export default defineComponent({ return () => nodesConnectable.value && sourceNode.value ? ( - - + + ) : ( '' ) - } + }, }) diff --git a/src/components/Edges/BezierEdge.tsx b/src/components/Edges/BezierEdge.tsx deleted file mode 100644 index 31d04588..00000000 --- a/src/components/Edges/BezierEdge.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import { defineComponent } from 'vue' -import EdgeText from './EdgeText' -import { getMarkerEnd, getCenter, GetCenterParams, DefaultEdgeProps } from './utils' -import { ArrowHeadType, EdgeType, Position } from '../../types' -import { reactify } from '@vueuse/core' - -interface GetBezierPathParams { - sourceX: number - sourceY: number - sourcePosition?: Position - targetX: number - targetY: number - targetPosition?: Position - centerX?: number - centerY?: number -} - -export function getBezierPath({ - sourceX, - sourceY, - sourcePosition = Position.Bottom, - targetX, - targetY, - targetPosition = Position.Top, - centerX, - centerY -}: GetBezierPathParams): string { - const [_centerX, _centerY] = getCenter({ sourceX, sourceY, targetX, targetY }) - const leftAndRight = [Position.Left, Position.Right] - - const cX = typeof centerX !== 'undefined' ? centerX : _centerX - const cY = typeof centerY !== 'undefined' ? centerY : _centerY - - let path = `M${sourceX},${sourceY} C${sourceX},${cY} ${targetX},${cY} ${targetX},${targetY}` - - if (leftAndRight.includes(sourcePosition) && leftAndRight.includes(targetPosition)) { - path = `M${sourceX},${sourceY} C${cX},${sourceY} ${cX},${targetY} ${targetX},${targetY}` - } else if (leftAndRight.includes(targetPosition)) { - path = `M${sourceX},${sourceY} C${sourceX},${targetY} ${sourceX},${targetY} ${targetX},${targetY}` - } else if (leftAndRight.includes(sourcePosition)) { - path = `M${sourceX},${sourceY} C${targetX},${sourceY} ${targetX},${sourceY} ${targetX},${targetY}` - } - - return path -} - -export default defineComponent({ - inheritAttrs: false, - props: { - ...DefaultEdgeProps - }, - setup(props) { - const centered = reactify(({ sourceX, sourceY, targetX, targetY, targetPosition, sourcePosition }: GetCenterParams) => { - return getCenter({ - sourceX: sourceX, - sourceY: sourceY, - targetX: targetX, - targetY: targetY, - sourcePosition: sourcePosition, - targetPosition: targetPosition - }) - }) - const path = reactify(({ sourceX, sourceY, targetX, targetY, targetPosition, sourcePosition }: GetBezierPathParams) => { - return getBezierPath({ - sourceX: sourceX, - sourceY: sourceY, - targetX: targetX, - targetY: targetY, - targetPosition: targetPosition, - sourcePosition: sourcePosition - }) - }) - - const markerEnd = reactify((arrowHeadType?: ArrowHeadType, markerEndId?: string) => getMarkerEnd(arrowHeadType, markerEndId)) - - return () => ( - <> - - {props.label ? ( - - ) : null} - - ) - } -}) as EdgeType diff --git a/src/components/Edges/BezierEdge.vue b/src/components/Edges/BezierEdge.vue new file mode 100644 index 00000000..38c16528 --- /dev/null +++ b/src/components/Edges/BezierEdge.vue @@ -0,0 +1,67 @@ + + diff --git a/src/components/Edges/Edge.tsx b/src/components/Edges/Edge.tsx deleted file mode 100644 index f77df87e..00000000 --- a/src/components/Edges/Edge.tsx +++ /dev/null @@ -1,248 +0,0 @@ -import { computed, defineComponent, h, inject, PropType, ref, watchPostEffect } from 'vue' -import EdgeAnchor from './EdgeAnchor' -import { ConnectionMode, Edge, Elements, Node, Position, RevueFlowStore, Transform } from '../../types' -import { RevueFlowHooks } from '../../hooks/RevueFlowHooks' -import { getEdgePositions, getHandle, getSourceTargetNodes, isEdgeVisible } from '../../container/EdgeRenderer/utils' -import { isEdge } from '../../utils/graph' -import { onMouseDown } from '../Handle/handler' -import { controlledRef } from '@vueuse/core' - -interface EdgeProps { - edge: Edge - nodes: Node[] - selectedElements: Elements | null - elementsSelectable: boolean - transform: Transform - width: number - height: number - onlyRenderVisibleElements: boolean - connectionMode?: ConnectionMode - markerEndId?: string - edgeUpdaterRadius?: number -} - -export default defineComponent({ - components: { EdgeAnchor }, - props: { - edge: { - type: Object as PropType, - required: true - }, - markerEndId: { - type: Object as PropType, - required: false, - default: undefined - }, - edgeUpdaterRadius: { - type: Number as PropType, - required: false, - default: undefined - }, - type: { - type: Object, - required: true - }, - onlyRenderVisibleElements: { - type: Boolean as PropType, - required: false, - default: false - }, - connectionMode: { - type: String as PropType, - required: false, - default: undefined - } - }, - setup(props) { - const store = inject('store')! - const hooks = inject('hooks')! - const nodes = controlledRef(getSourceTargetNodes(props.edge, store.nodes), { - onBeforeChange: (value, oldValue) => { - if (JSON.stringify(value) === JSON.stringify(oldValue)) return false - } - }) - - watchPostEffect(() => { - nodes.value = getSourceTargetNodes(props.edge, store.nodes) - }) - - hooks.connect.on((connection) => { - hooks.edgeUpdate.trigger({ edge: props.edge, connection }) - }) - - if (!nodes.value.sourceNode) { - console.warn(`couldn't create edge for source id: ${props.edge.source}; edge id: ${props.edge.id}`) - } - - if (!nodes.value.targetNode) { - console.warn(`couldn't create edge for target id: ${props.edge.target}; edge id: ${props.edge.id}`) - } - - // when connection type is loose we can define all handles as sources - const targetNodeHandles = - props.connectionMode === ConnectionMode.Strict - ? nodes.value.targetNode?.__rf.handleBounds.target - : nodes.value.targetNode?.__rf.handleBounds.target || nodes.value.targetNode?.__rf.handleBounds.source - - const sourceHandle = - nodes.value.sourceNode && getHandle(nodes.value.sourceNode.__rf.handleBounds.source, props.edge.sourceHandle || null) - const targetHandle = getHandle(targetNodeHandles, props.edge.targetHandle || null) - const sourcePosition = sourceHandle ? sourceHandle.position : Position.Bottom - const targetPosition = targetHandle ? targetHandle.position : Position.Top - - const isVisible = ({ sourceX, sourceY, targetX, targetY }: Record) => { - return props.onlyRenderVisibleElements - ? isEdgeVisible({ - sourcePos: { x: sourceX, y: sourceY }, - targetPos: { x: targetX, y: targetY }, - width: store.width || 0, - height: store.height || 0, - transform: store.transform - }) - : true - } - - const isSelected = computed(() => store.selectedElements?.some((elm) => isEdge(elm) && elm.id === props.edge.id) || false) - - const edgeElement = () => { - const el: Edge = { - id: props.edge.id || '', - source: props.edge.source, - target: props.edge.target, - type: props.edge.type - } - - if (props.edge.sourceHandle) { - el.sourceHandle = props.edge.sourceHandle - } - - if (props.edge.targetHandle) { - el.targetHandle = props.edge.targetHandle - } - - if (typeof props.edge.data !== 'undefined') { - el.data = props.edge.data - } - - return el - } - - const onEdgeClick = (event: MouseEvent) => { - if (store.elementsSelectable) { - store.unsetNodesSelection() - store.addSelectedElements(edgeElement() as any) - } - - hooks.edgeClick.trigger({ event, edge: edgeElement() }) - } - - const onEdgeContextMenu = (event: MouseEvent) => - hooks.edgeContextMenu.trigger({ - event, - edge: edgeElement() - }) - - const onEdgeMouseEnter = (event: MouseEvent) => hooks.edgeMouseEnter.trigger({ event, edge: edgeElement() }) - - const onEdgeMouseMove = (event: MouseEvent) => hooks.edgeMouseMove.trigger({ event, edge: edgeElement() }) - - const onEdgeMouseLeave = (event: MouseEvent) => hooks.edgeMouseLeave.trigger({ event, edge: edgeElement() }) - - const handleEdgeUpdater = (event: MouseEvent, isSourceHandle: boolean) => { - const nodeId = isSourceHandle ? props.edge.target : props.edge.source - const handleId = isSourceHandle ? props.edge.targetHandle : props.edge.sourceHandle - const isValidConnection = () => true - const isTarget = isSourceHandle - - hooks.edgeUpdateStart.trigger({ event, edge: edgeElement() }) - onMouseDown( - event, - store, - hooks, - handleId as string, - nodeId, - isTarget, - isValidConnection, - isSourceHandle ? 'target' : 'source' - ) - } - - const onEdgeUpdaterSourceMouseDown = (event: MouseEvent) => { - handleEdgeUpdater(event, true) - } - - const onEdgeUpdaterTargetMouseDown = (event: MouseEvent) => { - handleEdgeUpdater(event, false) - } - - const updating = ref(false) - const onEdgeUpdaterMouseEnter = () => (updating.value = true) - const onEdgeUpdaterMouseOut = () => (updating.value = false) - - return () => { - const { targetX, targetY, sourceX, sourceY } = getEdgePositions( - nodes.value.sourceNode as Node, - sourceHandle, - sourcePosition, - nodes.value.targetNode as Node, - targetHandle, - targetPosition - ) - return !props.edge.isHidden && isVisible({ targetX, targetY, sourceX, sourceY }) ? ( - - {h( - props.type, - { - id: props.edge.id, - source: props.edge.source, - target: props.edge.target, - selected: isSelected.value, - animated: props.edge.animated, - label: props.edge.label, - labelStyle: props.edge.labelStyle, - labelShowBg: props.edge.labelShowBg, - labelBgStyle: props.edge.labelBgStyle, - labelBgPadding: props.edge.labelBgPadding, - labelBgBorderRadius: props.edge.labelBgBorderRadius, - data: props.edge.data, - style: props.edge.style, - arrowHeadType: props.edge.arrowHeadType, - sourceX: sourceX, - sourceY: sourceY, - targetX: targetX, - targetY: targetY, - markerEndId: props.markerEndId, - sourceHandleId: props.edge.sourceHandle, - targetHandleId: props.edge.targetHandle - }, - {} - )} - - - - - - - - ) : ( - '' - ) - } - } -}) diff --git a/src/components/Edges/Edge.vue b/src/components/Edges/Edge.vue new file mode 100644 index 00000000..5ebacaa9 --- /dev/null +++ b/src/components/Edges/Edge.vue @@ -0,0 +1,210 @@ + + diff --git a/src/components/Edges/EdgeAnchor.tsx b/src/components/Edges/EdgeAnchor.tsx deleted file mode 100644 index 7a4ebfe1..00000000 --- a/src/components/Edges/EdgeAnchor.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { Position } from '../../types' -import { defineComponent, HTMLAttributes, PropType } from 'vue' - -const shiftX = (x: number, shift: number, position: Position): number => { - if (position === Position.Left) return x - shift - if (position === Position.Right) return x + shift - return x -} - -const shiftY = (y: number, shift: number, position: Position): number => { - if (position === Position.Top) return y - shift - if (position === Position.Bottom) return y + shift - return y -} - -export interface EdgeAnchorProps extends HTMLAttributes { - position: Position - centerX: number - centerY: number - radius?: number -} - -export default defineComponent({ - props: { - position: { - type: String as PropType, - required: true - }, - centerX: { - type: Number as PropType, - required: true - }, - centerY: { - type: Number as PropType, - required: true - }, - radius: { - type: Number as PropType, - required: false, - default: 10 - } - }, - setup(props) { - return () => ( - - ) - } -}) diff --git a/src/components/Edges/EdgeAnchor.vue b/src/components/Edges/EdgeAnchor.vue new file mode 100644 index 00000000..116249f7 --- /dev/null +++ b/src/components/Edges/EdgeAnchor.vue @@ -0,0 +1,37 @@ + + diff --git a/src/components/Edges/EdgeText.tsx b/src/components/Edges/EdgeText.tsx deleted file mode 100644 index 538ea0d3..00000000 --- a/src/components/Edges/EdgeText.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import { EdgeTextProps, Rect } from '../../types' -import { defineComponent, PropType, ref, watchEffect } from 'vue' - -export default defineComponent({ - props: { - x: { - type: Number as PropType, - required: true - }, - y: { - type: Number as PropType, - required: true - }, - label: { - type: [String, Object] as PropType, - required: true - }, - labelStyle: { - type: Object as PropType, - default: () => ({}) - }, - labelShowBg: { - type: Boolean as PropType, - default: true - }, - labelBgStyle: { - type: Object as PropType, - default: () => ({}) - }, - labelBgPadding: { - type: Array as unknown as PropType<[number, number]>, - default: () => [2, 4] - }, - labelBgBorderRadius: { - type: Number as PropType, - default: 2 - } - }, - setup(props, { slots }) { - const edgeRef = ref(null) - const edgeTextBox = ref({ x: 0, y: 0, width: 0, height: 0 }) - - watchEffect(() => { - const textBbox = edgeRef.value?.getBBox() - - if (textBbox) { - edgeTextBox.value = { - x: textBbox.x, - y: textBbox.y, - width: textBbox.width, - height: textBbox.height - } - } - }) - - return () => ( - - {props.labelShowBg && ( - - )} - - {props.label} - - {slots.default ? slots.default() : ''} - - ) - } -}) diff --git a/src/components/Edges/EdgeText.vue b/src/components/Edges/EdgeText.vue new file mode 100644 index 00000000..6f2968cd --- /dev/null +++ b/src/components/Edges/EdgeText.vue @@ -0,0 +1,61 @@ + + diff --git a/src/components/Edges/SmoothStepEdge.tsx b/src/components/Edges/SmoothStepEdge.tsx deleted file mode 100644 index 26916322..00000000 --- a/src/components/Edges/SmoothStepEdge.tsx +++ /dev/null @@ -1,147 +0,0 @@ -import { defineComponent } from 'vue' -import EdgeText from './EdgeText' -import { getMarkerEnd, getCenter, EdgeSmoothProps, GetCenterParams } from './utils' -import { ArrowHeadType, EdgeType, Position } from '../../types' -import { reactify } from '@vueuse/core' -import { getBezierPath } from './BezierEdge' - -// These are some helper methods for drawing the round corners -// The name indicates the direction of the path. "bottomLeftCorner" goes -// from bottom to the left and "leftBottomCorner" goes from left to the bottom. -// We have to consider the direction of the paths because of the animated lines. -const bottomLeftCorner = (x: number, y: number, size: number): string => `L ${x},${y - size}Q ${x},${y} ${x + size},${y}` -const leftBottomCorner = (x: number, y: number, size: number): string => `L ${x + size},${y}Q ${x},${y} ${x},${y - size}` -const bottomRightCorner = (x: number, y: number, size: number): string => `L ${x},${y - size}Q ${x},${y} ${x - size},${y}` -const rightBottomCorner = (x: number, y: number, size: number): string => `L ${x - size},${y}Q ${x},${y} ${x},${y - size}` -const leftTopCorner = (x: number, y: number, size: number): string => `L ${x + size},${y}Q ${x},${y} ${x},${y + size}` -const topLeftCorner = (x: number, y: number, size: number): string => `L ${x},${y + size}Q ${x},${y} ${x + size},${y}` -const topRightCorner = (x: number, y: number, size: number): string => `L ${x},${y + size}Q ${x},${y} ${x - size},${y}` -const rightTopCorner = (x: number, y: number, size: number): string => `L ${x - size},${y}Q ${x},${y} ${x},${y + size}` - -interface GetSmoothStepPathParams { - sourceX: number - sourceY: number - sourcePosition?: Position - targetX: number - targetY: number - targetPosition?: Position - borderRadius?: number - centerX?: number - centerY?: number -} - -export function getSmoothStepPath({ - sourceX, - sourceY, - sourcePosition = Position.Bottom, - targetX, - targetY, - targetPosition = Position.Top, - borderRadius = 5, - centerX, - centerY -}: GetSmoothStepPathParams): string { - const [_centerX, _centerY, offsetX, offsetY] = getCenter({ sourceX, sourceY, targetX, targetY }) - const cornerWidth = Math.min(borderRadius, Math.abs(targetX - sourceX)) - const cornerHeight = Math.min(borderRadius, Math.abs(targetY - sourceY)) - const cornerSize = Math.min(cornerWidth, cornerHeight, offsetX, offsetY) - const leftAndRight = [Position.Left, Position.Right] - const cX = typeof centerX !== 'undefined' ? centerX : _centerX - const cY = typeof centerY !== 'undefined' ? centerY : _centerY - - let firstCornerPath - let secondCornerPath - - if (sourceX <= targetX) { - firstCornerPath = sourceY <= targetY ? bottomLeftCorner(sourceX, cY, cornerSize) : topLeftCorner(sourceX, cY, cornerSize) - secondCornerPath = sourceY <= targetY ? rightTopCorner(targetX, cY, cornerSize) : rightBottomCorner(targetX, cY, cornerSize) - } else { - firstCornerPath = sourceY < targetY ? bottomRightCorner(sourceX, cY, cornerSize) : topRightCorner(sourceX, cY, cornerSize) - secondCornerPath = sourceY < targetY ? leftTopCorner(targetX, cY, cornerSize) : leftBottomCorner(targetX, cY, cornerSize) - } - - if (leftAndRight.includes(sourcePosition) && leftAndRight.includes(targetPosition)) { - if (sourceX <= targetX) { - firstCornerPath = sourceY <= targetY ? rightTopCorner(cX, sourceY, cornerSize) : rightBottomCorner(cX, sourceY, cornerSize) - secondCornerPath = sourceY <= targetY ? bottomLeftCorner(cX, targetY, cornerSize) : topLeftCorner(cX, targetY, cornerSize) - } else if (sourcePosition === Position.Right && targetPosition === Position.Left) { - // and sourceX > targetX - firstCornerPath = sourceY <= targetY ? leftTopCorner(cX, sourceY, cornerSize) : leftBottomCorner(cX, sourceY, cornerSize) - secondCornerPath = sourceY <= targetY ? bottomRightCorner(cX, targetY, cornerSize) : topRightCorner(cX, targetY, cornerSize) - } - } else if (leftAndRight.includes(sourcePosition) && !leftAndRight.includes(targetPosition)) { - if (sourceX <= targetX) { - firstCornerPath = - sourceY <= targetY ? rightTopCorner(targetX, sourceY, cornerSize) : rightBottomCorner(targetX, sourceY, cornerSize) - } else { - firstCornerPath = - sourceY <= targetY ? leftTopCorner(targetX, sourceY, cornerSize) : leftBottomCorner(targetX, sourceY, cornerSize) - } - secondCornerPath = '' - } else if (!leftAndRight.includes(sourcePosition) && leftAndRight.includes(targetPosition)) { - if (sourceX <= targetX) { - firstCornerPath = - sourceY <= targetY ? bottomLeftCorner(sourceX, targetY, cornerSize) : topLeftCorner(sourceX, targetY, cornerSize) - } else { - firstCornerPath = - sourceY <= targetY ? bottomRightCorner(sourceX, targetY, cornerSize) : topRightCorner(sourceX, targetY, cornerSize) - } - secondCornerPath = '' - } - - return `M ${sourceX},${sourceY}${firstCornerPath}${secondCornerPath}L ${targetX},${targetY}` -} - -export default defineComponent({ - inheritAttrs: false, - props: { - ...EdgeSmoothProps - }, - setup(props) { - const centered = reactify(({ sourceX, sourceY, targetX, targetY, targetPosition, sourcePosition }: GetCenterParams) => { - return getCenter({ - sourceX: sourceX, - sourceY: sourceY, - targetX: targetX, - targetY: targetY, - sourcePosition: sourcePosition, - targetPosition: targetPosition - }) - }) - const path = reactify(({ sourceX, sourceY, targetX, targetY, targetPosition, sourcePosition }: GetSmoothStepPathParams) => { - return getBezierPath({ - sourceX: sourceX, - sourceY: sourceY, - targetX: targetX, - targetY: targetY, - targetPosition: targetPosition, - sourcePosition: sourcePosition - }) - }) - - const markerEnd = reactify((arrowHeadType?: ArrowHeadType, markerEndId?: string) => getMarkerEnd(arrowHeadType, markerEndId)) - - return () => ( - <> - - {props.label ? ( - - ) : null} - - ) - } -}) as unknown as EdgeType diff --git a/src/components/Edges/SmoothStepEdge.vue b/src/components/Edges/SmoothStepEdge.vue new file mode 100644 index 00000000..c8b16942 --- /dev/null +++ b/src/components/Edges/SmoothStepEdge.vue @@ -0,0 +1,70 @@ + + diff --git a/src/components/Edges/StepEdge.tsx b/src/components/Edges/StepEdge.tsx deleted file mode 100644 index c3810200..00000000 --- a/src/components/Edges/StepEdge.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { defineComponent } from 'vue' -import SmoothStepEdge from './SmoothStepEdge' -import { EdgeSmoothProps } from './utils' -import { EdgeType } from '../../types' - -export default defineComponent({ - components: { SmoothStepEdge }, - inheritAttrs: false, - props: { - ...EdgeSmoothProps - }, - setup(props) { - return () => - } -}) as EdgeType diff --git a/src/components/Edges/StepEdge.vue b/src/components/Edges/StepEdge.vue new file mode 100644 index 00000000..1ae2a135 --- /dev/null +++ b/src/components/Edges/StepEdge.vue @@ -0,0 +1,44 @@ + + diff --git a/src/components/Edges/StraightEdge.tsx b/src/components/Edges/StraightEdge.tsx deleted file mode 100644 index 4c98858c..00000000 --- a/src/components/Edges/StraightEdge.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { defineComponent } from 'vue' -import EdgeText from './EdgeText' -import { getMarkerEnd, DefaultEdgeProps } from './utils' -import { reactify } from '@vueuse/core' -import { ArrowHeadType, EdgeType } from '../../types' - -export default defineComponent({ - inheritAttrs: false, - props: { - ...DefaultEdgeProps - }, - setup(props) { - const centerY = reactify((targetY: number, sourceY: number) => { - const yOffset = Math.abs(targetY - sourceY) / 2 - return targetY < sourceY ? targetY + yOffset : targetY - yOffset - }) - const centerX = reactify((targetX: number, sourceX: number) => { - const xOffset = Math.abs(targetX - sourceX) / 2 - return targetX < sourceX ? targetX + xOffset : targetX - xOffset - }) - - const markerEnd = reactify((arrowHeadType?: ArrowHeadType, markerEndId?: string) => getMarkerEnd(arrowHeadType, markerEndId)) - - return () => ( - <> - - {props.label ? ( - - ) : null} - - ) - } -}) as unknown as EdgeType diff --git a/src/components/Edges/StraightEdge.vue b/src/components/Edges/StraightEdge.vue new file mode 100644 index 00000000..6d36e075 --- /dev/null +++ b/src/components/Edges/StraightEdge.vue @@ -0,0 +1,77 @@ + + diff --git a/src/components/Edges/index.ts b/src/components/Edges/index.ts index 2430d4ac..75216418 100644 --- a/src/components/Edges/index.ts +++ b/src/components/Edges/index.ts @@ -1,4 +1,4 @@ -export { default as BezierEdge } from './BezierEdge' -export { default as StepEdge } from './StepEdge' -export { default as SmoothStepEdge } from './SmoothStepEdge' -export { default as StraightEdge } from './StraightEdge' +export { default as BezierEdge } from './BezierEdge.vue' +export { default as StepEdge } from './StepEdge.vue' +export { default as SmoothStepEdge } from './SmoothStepEdge.vue' +export { default as StraightEdge } from './StraightEdge.vue' diff --git a/src/components/Edges/utils.ts b/src/components/Edges/utils.ts index e04c5044..4cf31e69 100644 --- a/src/components/Edges/utils.ts +++ b/src/components/Edges/utils.ts @@ -1,5 +1,4 @@ -import { ArrowHeadType, Position, EdgeProps, EdgeSmoothStepProps } from '../../types' -import { PropType } from 'vue' +import { ArrowHeadType, Position } from '~/types' export const getMarkerEnd = (arrowHeadType?: ArrowHeadType, markerEndId?: string): string => { if (typeof markerEndId !== 'undefined' && markerEndId) { @@ -26,7 +25,7 @@ export const getCenter = ({ targetX, targetY, sourcePosition = Position.Bottom, - targetPosition = Position.Top + targetPosition = Position.Top, }: GetCenterParams): [number, number, number, number] => { const sourceIsLeftOrRight = LeftOrRight.includes(sourcePosition) const targetIsLeftOrRight = LeftOrRight.includes(targetPosition) @@ -54,124 +53,129 @@ export const getCenter = ({ return [centerX, centerY, xOffset, yOffset] } -export const DefaultEdgeProps = { - id: { - type: String as PropType, - required: true, - default: 0 - }, - sourceX: { - type: Number as PropType, - required: true, - default: 0 - }, - sourceY: { - type: Number as PropType, - required: true, - default: 0 - }, - source: { - type: String as PropType, - required: true, - default: 0 - }, - target: { - type: String as PropType, - required: true, - default: 0 - }, - selected: { - type: Boolean as PropType, - required: true, - default: false - }, - targetX: { - type: Number as PropType, - required: true, - default: 0 - }, - targetY: { - type: Number as PropType, - required: true, - default: 0 - }, - sourcePosition: { - type: String as PropType, - required: false, - default: Position.Bottom - }, - targetPosition: { - type: String as PropType, - required: false, - default: Position.Top - }, - label: { - type: [String, Object] as PropType, - required: false, - default: () => {} - }, - labelStyle: { - type: Object as PropType, - required: false, - default: undefined - }, - labelShowBg: { - type: Boolean as PropType, - required: false, - default: true - }, - labelBgStyle: { - type: [String, Object] as PropType, - required: false, - default: undefined - }, - labelBgPadding: { - type: Array as unknown as PropType<[number, number]>, - required: false, - default: undefined - }, - labelBgBorderRadius: { - type: Number as PropType, - required: false, - default: undefined - }, - arrowHeadType: { - type: String as PropType, - required: false, - default: undefined - }, - markerEndId: { - type: String as PropType, - required: false, - default: undefined - }, - style: { - type: Object as PropType, - required: false, - default: undefined - }, - data: { - type: Object as PropType, - required: false, - default: () => ({} as any) - }, - sourceHandleId: { - type: String as PropType, - required: false, - default: undefined - }, - targetHandleId: { - type: String as PropType, - required: false, - default: undefined - } +interface GetBezierPathParams { + sourceX: number + sourceY: number + sourcePosition?: Position + targetX: number + targetY: number + targetPosition?: Position + centerX?: number + centerY?: number } -export const EdgeSmoothProps = { - ...DefaultEdgeProps, - borderRadius: { - type: Number as PropType, - required: false, - default: 5 +export function getBezierPath({ + sourceX, + sourceY, + sourcePosition = Position.Bottom, + targetX, + targetY, + targetPosition = Position.Top, + centerX, + centerY, +}: GetBezierPathParams): string { + const [_centerX, _centerY] = getCenter({ sourceX, sourceY, targetX, targetY }) + const leftAndRight = [Position.Left, Position.Right] + + const cX = typeof centerX !== 'undefined' ? centerX : _centerX + const cY = typeof centerY !== 'undefined' ? centerY : _centerY + + let path = `M${sourceX},${sourceY} C${sourceX},${cY} ${targetX},${cY} ${targetX},${targetY}` + + if (leftAndRight.includes(sourcePosition) && leftAndRight.includes(targetPosition)) { + path = `M${sourceX},${sourceY} C${cX},${sourceY} ${cX},${targetY} ${targetX},${targetY}` + } else if (leftAndRight.includes(targetPosition)) { + path = `M${sourceX},${sourceY} C${sourceX},${targetY} ${sourceX},${targetY} ${targetX},${targetY}` + } else if (leftAndRight.includes(sourcePosition)) { + path = `M${sourceX},${sourceY} C${targetX},${sourceY} ${targetX},${sourceY} ${targetX},${targetY}` } + + return path +} + +// These are some helper methods for drawing the round corners +// The name indicates the direction of the path. "bottomLeftCorner" goes +// from bottom to the left and "leftBottomCorner" goes from left to the bottom. +// We have to consider the direction of the paths because of the animated lines. +export const bottomLeftCorner = (x: number, y: number, size: number): string => `L ${x},${y - size}Q ${x},${y} ${x + size},${y}` +export const leftBottomCorner = (x: number, y: number, size: number): string => `L ${x + size},${y}Q ${x},${y} ${x},${y - size}` +export const bottomRightCorner = (x: number, y: number, size: number): string => `L ${x},${y - size}Q ${x},${y} ${x - size},${y}` +export const rightBottomCorner = (x: number, y: number, size: number): string => `L ${x - size},${y}Q ${x},${y} ${x},${y - size}` +export const leftTopCorner = (x: number, y: number, size: number): string => `L ${x + size},${y}Q ${x},${y} ${x},${y + size}` +export const topLeftCorner = (x: number, y: number, size: number): string => `L ${x},${y + size}Q ${x},${y} ${x + size},${y}` +export const topRightCorner = (x: number, y: number, size: number): string => `L ${x},${y + size}Q ${x},${y} ${x - size},${y}` +export const rightTopCorner = (x: number, y: number, size: number): string => `L ${x - size},${y}Q ${x},${y} ${x},${y + size}` + +export interface GetSmoothStepPathParams { + sourceX: number + sourceY: number + sourcePosition?: Position + targetX: number + targetY: number + targetPosition?: Position + borderRadius?: number + centerX?: number + centerY?: number +} + +export function getSmoothStepPath({ + sourceX, + sourceY, + sourcePosition = Position.Bottom, + targetX, + targetY, + targetPosition = Position.Top, + borderRadius = 5, + centerX, + centerY, +}: GetSmoothStepPathParams): string { + const [_centerX, _centerY, offsetX, offsetY] = getCenter({ sourceX, sourceY, targetX, targetY }) + const cornerWidth = Math.min(borderRadius, Math.abs(targetX - sourceX)) + const cornerHeight = Math.min(borderRadius, Math.abs(targetY - sourceY)) + const cornerSize = Math.min(cornerWidth, cornerHeight, offsetX, offsetY) + const leftAndRight = [Position.Left, Position.Right] + const cX = typeof centerX !== 'undefined' ? centerX : _centerX + const cY = typeof centerY !== 'undefined' ? centerY : _centerY + + let firstCornerPath + let secondCornerPath + + if (sourceX <= targetX) { + firstCornerPath = sourceY <= targetY ? bottomLeftCorner(sourceX, cY, cornerSize) : topLeftCorner(sourceX, cY, cornerSize) + secondCornerPath = sourceY <= targetY ? rightTopCorner(targetX, cY, cornerSize) : rightBottomCorner(targetX, cY, cornerSize) + } else { + firstCornerPath = sourceY < targetY ? bottomRightCorner(sourceX, cY, cornerSize) : topRightCorner(sourceX, cY, cornerSize) + secondCornerPath = sourceY < targetY ? leftTopCorner(targetX, cY, cornerSize) : leftBottomCorner(targetX, cY, cornerSize) + } + + if (leftAndRight.includes(sourcePosition) && leftAndRight.includes(targetPosition)) { + if (sourceX <= targetX) { + firstCornerPath = sourceY <= targetY ? rightTopCorner(cX, sourceY, cornerSize) : rightBottomCorner(cX, sourceY, cornerSize) + secondCornerPath = sourceY <= targetY ? bottomLeftCorner(cX, targetY, cornerSize) : topLeftCorner(cX, targetY, cornerSize) + } else if (sourcePosition === Position.Right && targetPosition === Position.Left) { + // and sourceX > targetX + firstCornerPath = sourceY <= targetY ? leftTopCorner(cX, sourceY, cornerSize) : leftBottomCorner(cX, sourceY, cornerSize) + secondCornerPath = sourceY <= targetY ? bottomRightCorner(cX, targetY, cornerSize) : topRightCorner(cX, targetY, cornerSize) + } + } else if (leftAndRight.includes(sourcePosition) && !leftAndRight.includes(targetPosition)) { + if (sourceX <= targetX) { + firstCornerPath = + sourceY <= targetY ? rightTopCorner(targetX, sourceY, cornerSize) : rightBottomCorner(targetX, sourceY, cornerSize) + } else { + firstCornerPath = + sourceY <= targetY ? leftTopCorner(targetX, sourceY, cornerSize) : leftBottomCorner(targetX, sourceY, cornerSize) + } + secondCornerPath = '' + } else if (!leftAndRight.includes(sourcePosition) && leftAndRight.includes(targetPosition)) { + if (sourceX <= targetX) { + firstCornerPath = + sourceY <= targetY ? bottomLeftCorner(sourceX, targetY, cornerSize) : topLeftCorner(sourceX, targetY, cornerSize) + } else { + firstCornerPath = + sourceY <= targetY ? bottomRightCorner(sourceX, targetY, cornerSize) : topRightCorner(sourceX, targetY, cornerSize) + } + secondCornerPath = '' + } + + return `M ${sourceX},${sourceY}${firstCornerPath}${secondCornerPath}L ${targetX},${targetY}` } diff --git a/src/components/Handle/Handle.vue b/src/components/Handle/Handle.vue new file mode 100644 index 00000000..ac3049a3 --- /dev/null +++ b/src/components/Handle/Handle.vue @@ -0,0 +1,48 @@ + + diff --git a/src/components/Handle/handler.ts b/src/components/Handle/handler.ts index 1d3ac48f..b6993505 100644 --- a/src/components/Handle/handler.ts +++ b/src/components/Handle/handler.ts @@ -1,6 +1,6 @@ -import { getHostForElement } from '../../utils' -import { ElementId, ConnectionMode, Connection, HandleType, RevueFlowStore } from '../../types' -import { RevueFlowHooks } from '../../hooks/RevueFlowHooks' +import { getHostForElement } from '~/utils' +import { ElementId, ConnectionMode, Connection, HandleType, XYPosition, RevueFlowStore } from '~/types' +import { RevueFlowHooks } from '~/hooks/RevueFlowHooks' export type ValidConnectionFunc = (connection: Connection) => boolean @@ -19,7 +19,7 @@ function checkElementBelowIsValid( nodeId: ElementId, handleId: ElementId | null, isValidConnection: ValidConnectionFunc, - doc: Document | ShadowRoot + doc: Document, ) { const elementBelow = doc.elementFromPoint(event.clientX, event.clientY) const elementBelowIsTarget = elementBelow?.classList.contains('target') || false @@ -29,7 +29,7 @@ function checkElementBelowIsValid( elementBelow, isValid: false, connection: { source: null, target: null, sourceHandle: null, targetHandle: null }, - isHoveringHandle: false + isHoveringHandle: false, } if (elementBelow && (elementBelowIsTarget || elementBelowIsSource)) { @@ -47,13 +47,13 @@ function checkElementBelowIsValid( source: elementBelowNodeId, sourceHandle: elementBelowHandleId, target: nodeId, - targetHandle: handleId + targetHandle: handleId, } : { source: nodeId, sourceHandle: handleId, target: elementBelowNodeId, - targetHandle: elementBelowHandleId + targetHandle: elementBelowHandleId, } result.connection = connection @@ -79,11 +79,11 @@ export function onMouseDown( connect: RevueFlowHooks['connect'] edgeUpdateEnd: RevueFlowHooks['edgeUpdateEnd'] }, - handleId: ElementId | null, + handleId: ElementId, nodeId: ElementId, isTarget: boolean, - isValidConnection: ValidConnectionFunc, - elementEdgeUpdaterType?: HandleType + isValidConnection: ValidConnectionFunc = () => true, + elementEdgeUpdaterType?: HandleType, ): void { const revueFlowNode = (event.target as Element).closest('.revue-flow') // when revue-flow is used inside a shadow root we can't use document @@ -101,23 +101,29 @@ export function onMouseDown( return } - const handleType = elementEdgeUpdaterType ? elementEdgeUpdaterType : elementBelowIsTarget ? 'target' : 'source' + const handleType = elementEdgeUpdaterType || (elementBelowIsTarget ? 'target' : 'source') const containerBounds = revueFlowNode.getBoundingClientRect() let recentHoveredHandle: Element + const connectionPosition = ref({ + x: event.clientX - containerBounds.left, + y: event.clientY - containerBounds.top, + }) + + if (!store.connectionPosition) store.connectionPosition = { x: 0, y: 0 } store.connectionPosition.x = event.clientX - containerBounds.left store.connectionPosition.y = event.clientY - containerBounds.top store.setConnectionNodeId({ connectionNodeId: nodeId, connectionHandleId: handleId, - connectionHandleType: handleType + connectionHandleType: handleType, }) hooks.connectStart.trigger({ event, params: { nodeId, handleId, handleType } }) function onMouseMove(event: MouseEvent) { - store.connectionPosition.x = event.clientX - containerBounds.left - store.connectionPosition.y = event.clientY - containerBounds.top + connectionPosition.value.x = event.clientX - containerBounds.left + connectionPosition.value.y = event.clientY - containerBounds.top const { connection, elementBelow, isValid, isHoveringHandle } = checkElementBelowIsValid( event, @@ -126,7 +132,7 @@ export function onMouseDown( nodeId, handleId, isValidConnection, - doc + doc, ) if (!isHoveringHandle) { @@ -150,7 +156,7 @@ export function onMouseDown( nodeId, handleId, isValidConnection, - doc + doc, ) hooks.connectStop.trigger(event) @@ -166,7 +172,7 @@ export function onMouseDown( } resetRecentHandle(recentHoveredHandle) - store.setConnectionNodeId({ connectionNodeId: null, connectionHandleId: null, connectionHandleType: null }) + store.setConnectionNodeId({ connectionNodeId: undefined, connectionHandleId: undefined, connectionHandleType: undefined }) doc.removeEventListener('mousemove', onMouseMove as EventListenerOrEventListenerObject) doc.removeEventListener('mouseup', onMouseUp as EventListenerOrEventListenerObject) diff --git a/src/components/Nodes/DefaultNode.vue b/src/components/Nodes/DefaultNode.vue new file mode 100644 index 00000000..1b070818 --- /dev/null +++ b/src/components/Nodes/DefaultNode.vue @@ -0,0 +1,25 @@ + + diff --git a/src/components/Nodes/DefaultNode.tsx b/src/components/Nodes/DefaultNodeDepr.tsx similarity index 100% rename from src/components/Nodes/DefaultNode.tsx rename to src/components/Nodes/DefaultNodeDepr.tsx diff --git a/src/components/Nodes/InputNode.vue b/src/components/Nodes/InputNode.vue new file mode 100644 index 00000000..aec45928 --- /dev/null +++ b/src/components/Nodes/InputNode.vue @@ -0,0 +1,22 @@ + + diff --git a/src/components/Nodes/InputNode.tsx b/src/components/Nodes/InputNodeDepr.tsx similarity index 100% rename from src/components/Nodes/InputNode.tsx rename to src/components/Nodes/InputNodeDepr.tsx diff --git a/src/components/Node.vue b/src/components/Nodes/Node.vue similarity index 67% rename from src/components/Node.vue rename to src/components/Nodes/Node.vue index 5274c044..614b7541 100644 --- a/src/components/Node.vue +++ b/src/components/Nodes/Node.vue @@ -1,37 +1,31 @@ + diff --git a/src/components/Nodes/OutputNode.tsx b/src/components/Nodes/OutputNodeDepr.tsx similarity index 100% rename from src/components/Nodes/OutputNode.tsx rename to src/components/Nodes/OutputNodeDepr.tsx diff --git a/src/components/Nodes/index.ts b/src/components/Nodes/index.ts new file mode 100644 index 00000000..53a63969 --- /dev/null +++ b/src/components/Nodes/index.ts @@ -0,0 +1,3 @@ +export { default as DefaultNode } from './DefaultNode.vue' +export { default as InputNode } from './InputNode.vue' +export { default as OutputNode } from './OutputNode.vue' diff --git a/src/components/NodesSelection/NodesSelection.vue b/src/components/NodesSelection/NodesSelection.vue new file mode 100644 index 00000000..3cf306c8 --- /dev/null +++ b/src/components/NodesSelection/NodesSelection.vue @@ -0,0 +1,88 @@ + + diff --git a/src/components/UserSelection/SelectionRect.vue b/src/components/UserSelection/SelectionRect.vue new file mode 100644 index 00000000..9cf9f5ab --- /dev/null +++ b/src/components/UserSelection/SelectionRect.vue @@ -0,0 +1,20 @@ + + diff --git a/src/components/UserSelection/UserSelection.vue b/src/components/UserSelection/UserSelection.vue new file mode 100644 index 00000000..24fc6814 --- /dev/null +++ b/src/components/UserSelection/UserSelection.vue @@ -0,0 +1,57 @@ + + diff --git a/src/components/UserSelection/utils.ts b/src/components/UserSelection/utils.ts new file mode 100644 index 00000000..d1b3a918 --- /dev/null +++ b/src/components/UserSelection/utils.ts @@ -0,0 +1,15 @@ +import { XYPosition } from '~/types' + +export function getMousePosition(event: MouseEvent): XYPosition | void { + const revueFlowNode = (event.target as Element).closest('.revue-flow') + if (!revueFlowNode) { + return + } + + const containerBounds = revueFlowNode.getBoundingClientRect() + + return { + x: event.clientX - containerBounds.left, + y: event.clientY - containerBounds.top, + } +} diff --git a/src/composables/useZoom.ts b/src/composables/useZoom.ts index 064f0b0f..7ff3d9b6 100644 --- a/src/composables/useZoom.ts +++ b/src/composables/useZoom.ts @@ -1,11 +1,20 @@ import { D3ZoomEvent, zoom, zoomIdentity, ZoomTransform } from 'd3-zoom' import { pointer, select } from 'd3-selection' import { Ref } from 'vue' -import { FlowTransform, PanOnScrollMode, RevueFlowOptions } from '~/types' +import { get } from '@vueuse/core' +import { + D3Selection, + D3Zoom, + FlowTransform, + InitD3ZoomPayload, + KeyCode, + PanOnScrollMode, + Transform, + TranslateExtent, +} from '~/types' import { RevueFlowHooks } from '~/hooks/RevueFlowHooks' import { clamp } from '~/utils' import useKeyPress from '~/hooks/useKeyPress' -import useZoomPanHelper from '~/hooks/useZoomPanHelper' // import { onLoadGetElements, onLoadProject, onLoadToObject } from '~/utils/graph' const viewChanged = (prevTransform: FlowTransform, eventTransform: ZoomTransform): boolean => @@ -17,9 +26,34 @@ const eventToFlowTransform = (eventTransform: ZoomTransform): FlowTransform => ( zoom: eventTransform.k, }) -type UseZoomOptions = RevueFlowOptions +interface UseZoomOptions { + selectionKeyCode?: KeyCode + zoomActivationKeyCode?: KeyCode + paneMoveable?: boolean + minZoom?: number + maxZoom?: number + defaultZoom?: number + defaultPosition?: [number, number] + translateExtent?: TranslateExtent + zoomOnScroll?: boolean + zoomOnPinch?: boolean + panOnScroll?: boolean + panOnScrollSpeed?: number + panOnScrollMode?: PanOnScrollMode + zoomOnDoubleClick?: boolean +} -const useZoom = (zoomPane: Ref, options: UseZoomOptions) => { +interface UseZoom { + transform: Ref + d3Zoom: Ref + d3Selection: Ref +} + +export default function ( + el: Ref, + options: UseZoomOptions, + init: (initD3ZoomPayload: InitD3ZoomPayload) => void = () => {}, +): UseZoom { const { selectionKeyCode = 'Shift', zoomActivationKeyCode = 'Meta', @@ -45,29 +79,24 @@ const useZoom = (zoomPane: Ref, options: UseZoomOptions) => { const clampedX = clamp(defaultPosition[0], translateExtent[0][0], translateExtent[1][0]) const clampedY = clamp(defaultPosition[1], translateExtent[0][1], translateExtent[1][1]) const clampedZoom = clamp(defaultZoom, minZoom, maxZoom) - const transform = ref([clampedX, clampedY, clampedZoom]) + const transform = ref([clampedX, clampedY, clampedZoom]) + const d3Zoom = ref(zoom().scaleExtent([minZoom, maxZoom]).translateExtent(translateExtent)) + const d3Selection = ref() - until(zoomPane) + until(el) .toBeTruthy() .then(() => { - const d3Zoom = zoom().scaleExtent([minZoom, maxZoom]).translateExtent(translateExtent) - const d3Selection = select(unrefElement(zoomPane) as Element).call(d3Zoom) - const d3ZoomHandler = d3Selection.on('wheel.zoom') + const d3z = get(d3Zoom)! + d3Selection.value = select(el.value).call(d3z) + const d3s = get(d3Selection)! + const d3ZoomHandler = d3s.on('wheel.zoom') const updatedTransform = zoomIdentity.translate(clampedX, clampedY).scale(clampedZoom) - - const { fitView, zoomIn, zoomOut, zoomTo } = useZoomPanHelper({ ...options, d3Zoom, d3Selection } as any) - hooks.load.trigger({ - fitView: (params = { padding: 0.1 }) => fitView(params), - zoomIn, - zoomOut, - zoomTo, - setTransform: transform, - } as any) - d3Zoom.transform(d3Selection, updatedTransform) + d3z.transform(d3s, updatedTransform) + init({ d3Zoom: d3z, d3ZoomHandler, d3Selection: d3s }) const applyZoomHandlers = () => { - d3Zoom.on('start', (event: D3ZoomEvent) => { + d3z.on('start', (event: D3ZoomEvent) => { if (viewChanged(prevTransform.value, event.transform)) { const flowTransform = eventToFlowTransform(event.transform) prevTransform.value = flowTransform @@ -76,7 +105,7 @@ const useZoom = (zoomPane: Ref, options: UseZoomOptions) => { } }) - d3Zoom.on('end', (event: D3ZoomEvent) => { + d3z.on('end', (event: D3ZoomEvent) => { if (viewChanged(prevTransform.value, event.transform)) { const flowTransform = eventToFlowTransform(event.transform) prevTransform.value = flowTransform @@ -87,9 +116,9 @@ const useZoom = (zoomPane: Ref, options: UseZoomOptions) => { useKeyPress(selectionKeyCode, (keyPress) => { if (keyPress) { - d3Zoom.on('zoom', null) + d3z.on('zoom', null) } else { - d3Zoom.on('zoom', (event: D3ZoomEvent) => { + d3z.on('zoom', (event: D3ZoomEvent) => { transform.value = [event.transform.x, event.transform.y, event.transform.k] hooks.move.trigger(eventToFlowTransform(event.transform)) }) @@ -98,19 +127,19 @@ const useZoom = (zoomPane: Ref, options: UseZoomOptions) => { useKeyPress(zoomActivationKeyCode, (keyPress) => { if (panOnScroll && keyPress) { - d3Selection + d3s ?.on('wheel', (event: WheelEvent) => { event.preventDefault() event.stopImmediatePropagation() - const currentZoom = d3Selection?.property('__zoom').k || 1 + const currentZoom = d3s?.property('__zoom').k || 1 if (event.ctrlKey && zoomOnPinch) { const point = pointer(event) // taken from https://github.com/d3/d3-zoom/blob/master/src/zoom.js const pinchDelta = -event.deltaY * (event.deltaMode === 1 ? 0.05 : event.deltaMode ? 1 : 0.002) * 10 const zoom = currentZoom * Math.pow(2, pinchDelta) - if (d3Selection) d3Zoom?.scaleTo(d3Selection, zoom, point) + if (d3s) d3z.scaleTo(d3s, zoom, point) return } @@ -121,23 +150,19 @@ const useZoom = (zoomPane: Ref, options: UseZoomOptions) => { const deltaX = panOnScrollMode === PanOnScrollMode.Vertical ? 0 : event.deltaX * deltaNormalize const deltaY = panOnScrollMode === PanOnScrollMode.Horizontal ? 0 : event.deltaY * deltaNormalize - if (d3Selection && panOnScrollSpeed) - d3Zoom?.translateBy( - d3Selection, - -(deltaX / currentZoom) * panOnScrollSpeed, - -(deltaY / currentZoom) * panOnScrollSpeed, - ) + if (d3s && panOnScrollSpeed) + d3z?.translateBy(d3s, -(deltaX / currentZoom) * panOnScrollSpeed, -(deltaY / currentZoom) * panOnScrollSpeed) }) .on('wheel.zoom', null) } else if (typeof d3ZoomHandler !== 'undefined') { - d3Selection?.on('wheel', null).on('wheel.zoom', d3ZoomHandler) + d3s?.on('wheel', null).on('wheel.zoom', d3ZoomHandler) } }) } const applyZoomFilter = () => { const keyPress = useKeyPress(selectionKeyCode) - d3Zoom.filter((event: MouseEvent) => { + d3z.filter((event: MouseEvent) => { const zoomScroll = zoomOnScroll const pinchZoom = zoomOnPinch && event.ctrlKey @@ -179,7 +204,5 @@ const useZoom = (zoomPane: Ref, options: UseZoomOptions) => { applyZoomFilter() }) - return transform + return { transform, d3Zoom, d3Selection } } - -export default useZoom diff --git a/src/container/EdgeRenderer.vue b/src/container/EdgeRenderer.vue index d384f7ec..3ce3d671 100644 --- a/src/container/EdgeRenderer.vue +++ b/src/container/EdgeRenderer.vue @@ -1,49 +1,72 @@ diff --git a/src/container/EdgeRenderer/index.tsx b/src/container/EdgeRenderer/index.tsx index 08011872..7426b64b 100644 --- a/src/container/EdgeRenderer/index.tsx +++ b/src/container/EdgeRenderer/index.tsx @@ -1,8 +1,8 @@ import { CSSProperties, defineComponent, inject, PropType } from 'vue' import { ConnectionLineType, ConnectionLineComponent, ConnectionMode, RevueFlowStore } from '../../types' import ConnectionLine from '../../components/ConnectionLine' +import Edge from '../../components/Edges/EdgeDepr' import MarkerDefinitions from './MarkerDefinitions' -import Edge from '../../components/Edges/Edge' interface EdgeRendererProps { edgeTypes: any @@ -21,59 +21,59 @@ export default defineComponent({ components: { Edge, ConnectionLine, - MarkerDefinitions + MarkerDefinitions, }, props: { edgeTypes: { type: Object, - required: true + required: true, }, connectionLineType: { type: String as PropType, required: false, - default: undefined + default: undefined, }, connectionLineStyle: { type: Object as PropType, required: false, - default: undefined + default: undefined, }, connectionLineComponent: { type: Object as PropType, required: false, - default: undefined + default: undefined, }, connectionMode: { type: String as PropType, required: false, - default: undefined + default: undefined, }, arrowHeadColor: { type: String as PropType, required: false, - default: undefined + default: undefined, }, markerEndId: { type: String as PropType, required: false, - default: undefined + default: undefined, }, onlyRenderVisibleElements: { type: Boolean as PropType, required: false, - default: undefined + default: undefined, }, edgeUpdaterRadius: { type: Number as PropType, required: false, - default: undefined - } + default: undefined, + }, }, setup(props) { const store = inject('store')! return () => ( - + {store.edges.map((edge, i) => ( @@ -95,5 +95,5 @@ export default defineComponent({ ) - } + }, }) diff --git a/src/container/EdgeRenderer/utils.ts b/src/container/EdgeRenderer/utils.ts index e1d193e5..ce883dca 100644 --- a/src/container/EdgeRenderer/utils.ts +++ b/src/container/EdgeRenderer/utils.ts @@ -1,3 +1,4 @@ +import { Ref } from 'vue' import { BezierEdge, StepEdge, SmoothStepEdge, StraightEdge } from '~/components/Edges' import { rectToBox } from '~/utils/graph' import { Position, Node, XYPosition, ElementId, HandleElement, Transform, Edge, EdgeType } from '~/types' @@ -137,12 +138,12 @@ export function isEdgeVisible({ sourcePos, targetPos, width, height, transform } } type SourceTargetNode = { - sourceNode: Node | null - targetNode: Node | null + sourceNode: Node + targetNode: Node } -export const getSourceTargetNodes = (edge: Edge, nodes: Node[]): SourceTargetNode => { - return nodes.reduce( +export const getSourceTargetNodes = (edge: Edge, nodes: Node[]): Ref => { + const sourceTargetNode: SourceTargetNode = nodes.reduce( (res, node) => { if (node.id === edge.source) { res.sourceNode = node @@ -152,6 +153,8 @@ export const getSourceTargetNodes = (edge: Edge, nodes: Node[]): SourceTargetNod } return res }, - { sourceNode: null, targetNode: null } as SourceTargetNode, + { sourceNode: null, targetNode: null } as any, ) + + return ref(sourceTargetNode) } diff --git a/src/container/Flow.vue b/src/container/Flow.vue index 8c811ca0..b1bd0edd 100644 --- a/src/container/Flow.vue +++ b/src/container/Flow.vue @@ -1,7 +1,7 @@
- + diff --git a/src/container/NodeRenderer.vue b/src/container/NodeRenderer.vue index e6ad9057..73670de1 100644 --- a/src/container/NodeRenderer.vue +++ b/src/container/NodeRenderer.vue @@ -1,37 +1,26 @@