update(script-setup): Refactor additional-components
* Remove jsx files fix: ConnectionLine.vue not recalculating properly
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
import { CSSProperties } from 'vue'
|
||||
import { ConnectionLineType, CustomConnectionLine, HandleElement, Position, RevueFlowStore, Node } from '~/types'
|
||||
import { ConnectionLineType, CustomConnectionLine, HandleElement, Node, Position, RevueFlowStore } from '~/types'
|
||||
import { RevueFlowHooks } from '~/hooks/RevueFlowHooks'
|
||||
import { getBezierPath, getSmoothStepPath } from '~/components/Edges/utils'
|
||||
|
||||
@@ -16,76 +16,67 @@ const props = withDefaults(defineProps<ConnectionLineProps>(), {
|
||||
connectionLineStyle: () => ({}),
|
||||
})
|
||||
|
||||
const store = inject<RevueFlowStore>('store')!
|
||||
const hooks = inject<RevueFlowHooks>('hooks')!
|
||||
const store = inject<RevueFlowStore>('store')!
|
||||
|
||||
const sourceHandle = computed(() =>
|
||||
const sourceHandle =
|
||||
store.connectionHandleId && store.connectionHandleType
|
||||
? props.sourceNode.__rf.handleBounds[store.connectionHandleType].find((d: HandleElement) => d.id === store.connectionHandleId)
|
||||
: store.connectionHandleType && props.sourceNode.__rf.handleBounds[store.connectionHandleType][0],
|
||||
)
|
||||
const sourceHandleX = computed(() =>
|
||||
sourceHandle.value ? sourceHandle.value.x + sourceHandle.value.width / 2 : (props.sourceNode.__rf.width as number) / 2,
|
||||
)
|
||||
const sourceHandleY = computed(() =>
|
||||
sourceHandle.value ? sourceHandle.value.y + sourceHandle.value.height / 2 : props.sourceNode.__rf.height,
|
||||
)
|
||||
const sourceX = computed(() => props.sourceNode.__rf.position.x + sourceHandleX.value)
|
||||
const sourceY = computed(() => props.sourceNode.__rf.position.y + sourceHandleY.value)
|
||||
: store.connectionHandleType && props.sourceNode.__rf.handleBounds[store.connectionHandleType][0]
|
||||
const sourceHandleX = sourceHandle ? sourceHandle.x + sourceHandle.width / 2 : (props.sourceNode.__rf.width as number) / 2
|
||||
const sourceHandleY = sourceHandle ? sourceHandle.y + sourceHandle.height / 2 : props.sourceNode.__rf.height
|
||||
const sourceX = props.sourceNode.__rf.position.x + sourceHandleX
|
||||
const sourceY = props.sourceNode.__rf.position.y + sourceHandleY
|
||||
|
||||
const targetX = computed(() => (store.connectionPosition?.x - store.transform[0]) / store.transform[2])
|
||||
const targetY = computed(() => (store.connectionPosition?.y - store.transform[1]) / store.transform[2])
|
||||
const isRightOrLeft = sourceHandle.position === Position.Left || sourceHandle.position === Position.Right
|
||||
const targetPosition = isRightOrLeft ? Position.Left : Position.Top
|
||||
|
||||
const isRightOrLeft = computed(
|
||||
() => sourceHandle.value?.position === Position.Left || sourceHandle.value?.position === Position.Right,
|
||||
)
|
||||
const targetPosition = computed(() => (isRightOrLeft.value ? Position.Left : Position.Top))
|
||||
const targetX = computed(() => (store.connectionPosition.x - store.transform[0]) / store.transform[2])
|
||||
const targetY = computed(() => (store.connectionPosition.y - store.transform[1]) / store.transform[2])
|
||||
|
||||
let dAttr = computed(() => `M${sourceX.value},${sourceY.value} ${targetX.value},${targetY.value}`)
|
||||
|
||||
if (props.connectionLineType === ConnectionLineType.Bezier) {
|
||||
dAttr = computed(() =>
|
||||
getBezierPath({
|
||||
sourceX: sourceX.value,
|
||||
sourceY: sourceY.value,
|
||||
sourcePosition: sourceHandle.value?.position,
|
||||
targetX: targetX.value,
|
||||
targetY: targetY.value,
|
||||
targetPosition: targetPosition.value,
|
||||
}),
|
||||
)
|
||||
} else if (props.connectionLineType === ConnectionLineType.Step) {
|
||||
dAttr = computed(() =>
|
||||
getSmoothStepPath({
|
||||
sourceX: sourceX.value,
|
||||
sourceY: sourceY.value,
|
||||
sourcePosition: sourceHandle.value?.position,
|
||||
targetX: targetX.value,
|
||||
targetY: targetY.value,
|
||||
targetPosition: targetPosition.value,
|
||||
borderRadius: 0,
|
||||
}),
|
||||
)
|
||||
} else if (props.connectionLineType === ConnectionLineType.SmoothStep) {
|
||||
dAttr = computed(() =>
|
||||
getSmoothStepPath({
|
||||
sourceX: sourceX.value,
|
||||
sourceY: sourceY.value,
|
||||
sourcePosition: sourceHandle.value?.position,
|
||||
targetX: targetX.value,
|
||||
targetY: targetY.value,
|
||||
targetPosition: targetPosition.value,
|
||||
}),
|
||||
)
|
||||
}
|
||||
watch(dAttr, () => console.log(dAttr.value))
|
||||
const dAttr = computed(() => {
|
||||
let path = `M${sourceX.value},${sourceY.value} ${targetX.value},${targetY.value}`
|
||||
switch (props.connectionLineType) {
|
||||
case ConnectionLineType.Bezier:
|
||||
path = getBezierPath({
|
||||
sourceX,
|
||||
sourceY,
|
||||
sourcePosition: sourceHandle.position,
|
||||
targetX: targetX.value,
|
||||
targetY: targetY.value,
|
||||
targetPosition,
|
||||
})
|
||||
break
|
||||
case ConnectionLineType.Step:
|
||||
path = getSmoothStepPath({
|
||||
sourceX,
|
||||
sourceY,
|
||||
sourcePosition: sourceHandle.position,
|
||||
targetX: targetX.value,
|
||||
targetY: targetY.value,
|
||||
targetPosition,
|
||||
borderRadius: 0,
|
||||
})
|
||||
break
|
||||
case ConnectionLineType.SmoothStep:
|
||||
path = getSmoothStepPath({
|
||||
sourceX,
|
||||
sourceY,
|
||||
sourcePosition: sourceHandle.position,
|
||||
targetX: targetX.value,
|
||||
targetY: targetY.value,
|
||||
targetPosition,
|
||||
})
|
||||
break
|
||||
}
|
||||
return path
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<g>
|
||||
<g class="revue-flow__connection">
|
||||
<component
|
||||
:is="props.customConnectionLine"
|
||||
v-if="props.customConnectionLine"
|
||||
class="revue-flow__connection"
|
||||
v-bind="{
|
||||
sourceX: sourceX,
|
||||
sourceY: sourceY,
|
||||
|
||||
@@ -1,125 +0,0 @@
|
||||
import { ref, defineComponent, CSSProperties, PropType, computed, inject, h } from 'vue'
|
||||
|
||||
import { getBezierPath } from '../Edges/BezierEdgeDepr'
|
||||
import { getSmoothStepPath } from '../Edges/SmoothStepEdgeDepr'
|
||||
import { Node, HandleElement, Position, ConnectionLineType, ConnectionLineComponent, RevueFlowStore } from '../../types'
|
||||
|
||||
interface ConnectionLineProps {
|
||||
connectionLineType: ConnectionLineType
|
||||
connectionLineStyle?: CSSProperties
|
||||
customConnectionLine?: ConnectionLineComponent
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
connectionLineStyle: {
|
||||
type: Object as PropType<ConnectionLineProps['connectionLineStyle']>,
|
||||
required: false,
|
||||
default: () => {},
|
||||
},
|
||||
connectionLineType: {
|
||||
type: String as PropType<ConnectionLineProps['connectionLineType']>,
|
||||
required: false,
|
||||
default: ConnectionLineType.Bezier,
|
||||
},
|
||||
customConnectionLine: {
|
||||
type: Object as PropType<ConnectionLineProps['customConnectionLine']>,
|
||||
required: false,
|
||||
default: undefined,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const store = inject<RevueFlowStore>('store')!
|
||||
const sourceNode = ref<Node | null>(store.nodes.find((n) => n.id === store.connectionNodeId) || null)
|
||||
const nodesConnectable = computed(() => store.nodesConnectable)
|
||||
|
||||
const sourceHandle = computed(() =>
|
||||
store.connectionHandleId && store.connectionHandleType
|
||||
? sourceNode.value?.__rf.handleBounds[store.connectionHandleType].find(
|
||||
(d: HandleElement) => d.id === store.connectionHandleId,
|
||||
)
|
||||
: 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,
|
||||
)
|
||||
const sourceHandleY = computed(() =>
|
||||
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)
|
||||
|
||||
const targetX = computed(() => (store.connectionPosition.x - store.transform[0]) / store.transform[2])
|
||||
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,
|
||||
)
|
||||
const targetPosition = computed(() => (isRightOrLeft.value ? Position.Left : Position.Top))
|
||||
|
||||
let dAttr = computed(() => `M${sourceX.value},${sourceY.value} ${targetX.value},${targetY.value}`)
|
||||
|
||||
if (props.connectionLineType === ConnectionLineType.Bezier) {
|
||||
dAttr = computed(() =>
|
||||
getBezierPath({
|
||||
sourceX: sourceX.value,
|
||||
sourceY: sourceY.value,
|
||||
sourcePosition: sourceHandle.value?.position,
|
||||
targetX: targetX.value,
|
||||
targetY: targetY.value,
|
||||
targetPosition: targetPosition.value,
|
||||
}),
|
||||
)
|
||||
} else if (props.connectionLineType === ConnectionLineType.Step) {
|
||||
dAttr = computed(() =>
|
||||
getSmoothStepPath({
|
||||
sourceX: sourceX.value,
|
||||
sourceY: sourceY.value,
|
||||
sourcePosition: sourceHandle.value?.position,
|
||||
targetX: targetX.value,
|
||||
targetY: targetY.value,
|
||||
targetPosition: targetPosition.value,
|
||||
borderRadius: 0,
|
||||
}),
|
||||
)
|
||||
} else if (props.connectionLineType === ConnectionLineType.SmoothStep) {
|
||||
dAttr = computed(() =>
|
||||
getSmoothStepPath({
|
||||
sourceX: sourceX.value,
|
||||
sourceY: sourceY.value,
|
||||
sourcePosition: sourceHandle.value?.position,
|
||||
targetX: targetX.value,
|
||||
targetY: targetY.value,
|
||||
targetPosition: targetPosition.value,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
if (props.customConnectionLine) {
|
||||
return () => (
|
||||
<g className="revue-flow__connection">
|
||||
{props.customConnectionLine &&
|
||||
h(props.customConnectionLine, {
|
||||
sourceX: sourceX.value,
|
||||
sourceY: sourceY.value,
|
||||
sourcePosition: sourceHandle.value?.position,
|
||||
targetX: targetX.value,
|
||||
targetY: targetY.value,
|
||||
targetPosition: targetPosition.value,
|
||||
connectionLineType: props.connectionLineType,
|
||||
connectionLineStyle: props.connectionLineStyle,
|
||||
})}
|
||||
</g>
|
||||
)
|
||||
}
|
||||
|
||||
return () =>
|
||||
nodesConnectable.value && sourceNode.value ? (
|
||||
<g className="revue-flow__connection">
|
||||
<path d={dAttr.value} className="revue-flow__connection-path" style={props.connectionLineStyle} />
|
||||
</g>
|
||||
) : (
|
||||
''
|
||||
)
|
||||
},
|
||||
})
|
||||
@@ -4,7 +4,7 @@ import { RevueFlowHooks } from '~/hooks/RevueFlowHooks'
|
||||
import { getEdgePositions, getHandle, getSourceTargetNodes, isEdgeVisible } from '~/container/EdgeRenderer/utils'
|
||||
import { isEdge } from '~/utils/graph'
|
||||
import { ConnectionMode, Dimensions, Edge, EdgeType, Elements, Position, RevueFlowStore, Transform } from '~/types'
|
||||
import { onMouseDown } from '~/components/Handle/handler'
|
||||
import { onMouseDown } from '~/components/Handle/utils'
|
||||
|
||||
interface EdgeProps {
|
||||
type: EdgeType
|
||||
@@ -12,7 +12,7 @@ interface EdgeProps {
|
||||
nodes: ReturnType<typeof getSourceTargetNodes>
|
||||
dimensions: Dimensions
|
||||
transform: Transform
|
||||
selectedElements?: Elements | null
|
||||
selectedElements?: Elements
|
||||
elementsSelectable?: boolean
|
||||
onlyRenderVisibleElements?: boolean
|
||||
connectionMode?: ConnectionMode
|
||||
@@ -23,7 +23,6 @@ interface EdgeProps {
|
||||
const props = withDefaults(defineProps<EdgeProps>(), {
|
||||
elementsSelectable: true,
|
||||
onlyRenderVisibleElements: false,
|
||||
selectedElements: () => null,
|
||||
})
|
||||
|
||||
const store = inject<RevueFlowStore>('store')!
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
import { ConnectionMode, ElementId, Position, RevueFlowStore } from '~/types'
|
||||
import { onMouseDown, ValidConnectionFunc } from '~/components/Handle/handler'
|
||||
import { ElementId, Position, RevueFlowStore } from '~/types'
|
||||
import { onMouseDown, ValidConnectionFunc } from '~/components/Handle/utils'
|
||||
import { RevueFlowHooks } from '~/hooks/RevueFlowHooks'
|
||||
|
||||
interface HandleProps {
|
||||
@@ -15,7 +15,6 @@ const props = withDefaults(defineProps<HandleProps>(), {
|
||||
id: '',
|
||||
type: 'source',
|
||||
position: Position.Top,
|
||||
isValidConnection: () => true,
|
||||
connectable: true,
|
||||
})
|
||||
|
||||
@@ -24,7 +23,18 @@ const hooks = inject<RevueFlowHooks>('hooks')!
|
||||
const nodeId = inject<ElementId>('NodeIdContext')!
|
||||
|
||||
const onMouseDownHandler = (event: MouseEvent) =>
|
||||
onMouseDown(event, store, hooks, props.id, nodeId, props.type === 'target', props.isValidConnection)
|
||||
onMouseDown(
|
||||
event,
|
||||
store,
|
||||
hooks,
|
||||
props.id,
|
||||
nodeId,
|
||||
props.type === 'target',
|
||||
props.isValidConnection ??
|
||||
function () {
|
||||
return true
|
||||
},
|
||||
)
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
import { ElementId, Position, RevueFlowStore } from '../../types'
|
||||
import { onMouseDown, ValidConnectionFunc } from './handler'
|
||||
import { defineComponent, inject, PropType } from 'vue'
|
||||
import { RevueFlowHooks } from '../../hooks/RevueFlowHooks'
|
||||
|
||||
const alwaysValid = () => true
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'source'
|
||||
},
|
||||
position: {
|
||||
type: String as PropType<Position>,
|
||||
required: false,
|
||||
default: Position.Top
|
||||
},
|
||||
isValidConnection: {
|
||||
type: Function as PropType<ValidConnectionFunc>,
|
||||
required: false,
|
||||
default: alwaysValid
|
||||
},
|
||||
isConnectable: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
},
|
||||
id: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: undefined
|
||||
}
|
||||
},
|
||||
setup(props, { slots }) {
|
||||
const store = inject<RevueFlowStore>('store')!
|
||||
const hooks = inject<RevueFlowHooks>('hooks')!
|
||||
const nodeId = inject<ElementId>('NodeIdContext')!
|
||||
|
||||
const onMouseDownHandler = (event: MouseEvent) =>
|
||||
onMouseDown(event, store, hooks, props.id as string, nodeId, props.type === 'target', props.isValidConnection)
|
||||
|
||||
return () => (
|
||||
<div
|
||||
data-handleid={props.id}
|
||||
data-nodeid={nodeId}
|
||||
data-handlepos={props.position}
|
||||
class={[
|
||||
'revue-flow__handle',
|
||||
`revue-flow__handle-${props.position}`,
|
||||
'nodrag',
|
||||
{
|
||||
source: props.type !== 'target',
|
||||
target: props.type === 'target',
|
||||
connectable: props.isConnectable
|
||||
}
|
||||
]}
|
||||
onMousedown={onMouseDownHandler}
|
||||
>
|
||||
{slots.default ? slots.default() : ''}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -1,5 +1,5 @@
|
||||
import { getHostForElement } from '~/utils'
|
||||
import { ElementId, ConnectionMode, Connection, HandleType, XYPosition, RevueFlowStore } from '~/types'
|
||||
import { ElementId, ConnectionMode, Connection, HandleType, RevueFlowStore } from '~/types'
|
||||
import { RevueFlowHooks } from '~/hooks/RevueFlowHooks'
|
||||
|
||||
export type ValidConnectionFunc = (connection: Connection) => boolean
|
||||
@@ -82,7 +82,9 @@ export function onMouseDown(
|
||||
handleId: ElementId,
|
||||
nodeId: ElementId,
|
||||
isTarget: boolean,
|
||||
isValidConnection: ValidConnectionFunc = () => true,
|
||||
isValidConnection: ValidConnectionFunc = () => {
|
||||
return true
|
||||
},
|
||||
elementEdgeUpdaterType?: HandleType,
|
||||
): void {
|
||||
const revueFlowNode = (event.target as Element).closest('.revue-flow')
|
||||
@@ -105,12 +107,6 @@ export function onMouseDown(
|
||||
const containerBounds = revueFlowNode.getBoundingClientRect()
|
||||
let recentHoveredHandle: Element
|
||||
|
||||
const connectionPosition = ref<XYPosition>({
|
||||
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
|
||||
|
||||
@@ -122,8 +118,8 @@ export function onMouseDown(
|
||||
hooks.connectStart.trigger({ event, params: { nodeId, handleId, handleType } })
|
||||
|
||||
function onMouseMove(event: MouseEvent) {
|
||||
connectionPosition.value.x = event.clientX - containerBounds.left
|
||||
connectionPosition.value.y = event.clientY - containerBounds.top
|
||||
store.connectionPosition.x = event.clientX - containerBounds.left
|
||||
store.connectionPosition.y = event.clientY - containerBounds.top
|
||||
|
||||
const { connection, elementBelow, isValid, isHoveringHandle } = checkElementBelowIsValid(
|
||||
event,
|
||||
@@ -173,6 +169,7 @@ export function onMouseDown(
|
||||
|
||||
resetRecentHandle(recentHoveredHandle)
|
||||
store.setConnectionNodeId({ connectionNodeId: undefined, connectionHandleId: undefined, connectionHandleType: undefined })
|
||||
store.connectionPosition = { x: NaN, y: NaN }
|
||||
|
||||
doc.removeEventListener('mousemove', onMouseMove as EventListenerOrEventListenerObject)
|
||||
doc.removeEventListener('mouseup', onMouseUp as EventListenerOrEventListenerObject)
|
||||
@@ -1,40 +0,0 @@
|
||||
import { defineComponent, PropType } from 'vue'
|
||||
import Handle from '../../components/Handle'
|
||||
import { NodeProps, NodeType, Position } from '../../types'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'DefaultNode',
|
||||
components: { Handle },
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
data: {
|
||||
type: Object as PropType<NodeProps['data']>,
|
||||
required: false,
|
||||
default: undefined as any
|
||||
},
|
||||
isConnectable: {
|
||||
type: Boolean as PropType<NodeProps['isConnectable']>,
|
||||
required: false,
|
||||
default: undefined
|
||||
},
|
||||
targetPosition: {
|
||||
type: String as PropType<NodeProps['targetPosition']>,
|
||||
required: false,
|
||||
default: Position.Top
|
||||
},
|
||||
sourcePosition: {
|
||||
type: String as PropType<NodeProps['sourcePosition']>,
|
||||
required: false,
|
||||
default: Position.Bottom
|
||||
}
|
||||
},
|
||||
setup(props) {
|
||||
return () => (
|
||||
<>
|
||||
<Handle type="target" position={props.targetPosition} isConnectable={props.isConnectable} />
|
||||
{props.data?.label}
|
||||
<Handle type="source" position={props.sourcePosition} isConnectable={props.isConnectable} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
}) as NodeType
|
||||
@@ -1,34 +0,0 @@
|
||||
import Handle from '../../components/Handle'
|
||||
import { NodeProps, NodeType, Position } from '../../types'
|
||||
import { defineComponent, PropType } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'InputNode',
|
||||
components: { Handle },
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
data: {
|
||||
type: Object as PropType<NodeProps['data']>,
|
||||
required: false,
|
||||
default: undefined as any
|
||||
},
|
||||
isConnectable: {
|
||||
type: Boolean as PropType<NodeProps['isConnectable']>,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
sourcePosition: {
|
||||
type: String as PropType<NodeProps['sourcePosition']>,
|
||||
required: false,
|
||||
default: Position.Bottom
|
||||
}
|
||||
},
|
||||
setup(props) {
|
||||
return () => (
|
||||
<>
|
||||
{props.data?.label}
|
||||
<Handle type="source" position={props.sourcePosition} isConnectable={props.isConnectable} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
}) as NodeType
|
||||
@@ -1,236 +0,0 @@
|
||||
import { computed, defineComponent, h, inject, onMounted, PropType, provide } from 'vue'
|
||||
import { templateRef, useResizeObserver } from '@vueuse/core'
|
||||
import { DraggableCore, DraggableEventListener } from '@braks/revue-draggable'
|
||||
import { Node, NodeDimensionUpdate, NodeType, RevueFlowStore } from '../../types'
|
||||
import { RevueFlowHooks } from '../../hooks/RevueFlowHooks'
|
||||
|
||||
interface NodeProps {
|
||||
nodeTypes: Record<string, NodeType>
|
||||
snapToGrid: boolean
|
||||
snapGrid: [number, number]
|
||||
selectNodesOnDrag: boolean
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
components: { DraggableCore },
|
||||
props: {
|
||||
node: {
|
||||
type: Object as PropType<Node>,
|
||||
required: true
|
||||
},
|
||||
type: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
snapToGrid: {
|
||||
type: Boolean as PropType<NodeProps['snapToGrid']>,
|
||||
required: false,
|
||||
default: undefined
|
||||
},
|
||||
snapGrid: {
|
||||
type: Array as unknown as PropType<NodeProps['snapGrid']>,
|
||||
required: false,
|
||||
default: undefined
|
||||
},
|
||||
selectNodesOnDrag: {
|
||||
type: Boolean as PropType<NodeProps['selectNodesOnDrag']>,
|
||||
required: false,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
setup(props) {
|
||||
const store = inject<RevueFlowStore>('store')!
|
||||
const hooks = inject<RevueFlowHooks>('hooks')!
|
||||
provide('NodeIdContext', props.node.id)
|
||||
|
||||
const nodeElement = templateRef<HTMLDivElement>('nodeElement', null)
|
||||
|
||||
const selected = computed(() => store.selectedElements?.some(({ id }) => id === props.node.id) || false)
|
||||
const isDraggable = computed(
|
||||
() => props.node.draggable || (store.nodesDraggable && typeof props.node.draggable === 'undefined')
|
||||
)
|
||||
const isSelectable = computed(
|
||||
() => props.node.selectable || (store.elementsSelectable && typeof props.node.selectable === 'undefined')
|
||||
)
|
||||
const isConnectable = computed(
|
||||
() => props.node.connectable || (store.nodesConnectable && typeof props.node.connectable === 'undefined')
|
||||
)
|
||||
|
||||
const node = () =>
|
||||
({
|
||||
id: props.node.id,
|
||||
type: props.node.type,
|
||||
position: { x: props.node.__rf.position.x, y: props.node.__rf.position.y },
|
||||
data: props.node.data
|
||||
} as Node)
|
||||
|
||||
const onMouseEnterHandler = () => {
|
||||
if (props.node.__rf.isDragging) {
|
||||
return
|
||||
}
|
||||
|
||||
return (event: MouseEvent) => hooks.nodeMouseEnter.trigger({ event, node: node() })
|
||||
}
|
||||
|
||||
const onMouseMoveHandler = () => {
|
||||
if (props.node.__rf.isDragging) {
|
||||
return
|
||||
}
|
||||
|
||||
return (event: MouseEvent) => hooks.nodeMouseMove.trigger({ event, node: node() })
|
||||
}
|
||||
|
||||
const onMouseLeaveHandler = () => {
|
||||
if (props.node.__rf.isDragging) {
|
||||
return
|
||||
}
|
||||
|
||||
return (event: MouseEvent) => hooks.nodeMouseLeave.trigger({ event, node: node() })
|
||||
}
|
||||
|
||||
const onContextMenuHandler = () => {
|
||||
return (event: MouseEvent) => hooks.nodeContextMenu.trigger({ event, node: node() })
|
||||
}
|
||||
|
||||
const onSelectNodeHandler = (event: MouseEvent) => {
|
||||
if (!isDraggable.value) {
|
||||
const n = node()
|
||||
if (isSelectable.value) {
|
||||
store.unsetNodesSelection()
|
||||
|
||||
if (!selected.value) {
|
||||
store.addSelectedElements([n])
|
||||
}
|
||||
}
|
||||
|
||||
hooks.nodeClick.trigger({ event, node: n })
|
||||
}
|
||||
}
|
||||
|
||||
const onDragStart: DraggableEventListener = ({ event }) => {
|
||||
const n = node()
|
||||
hooks.nodeDragStart.trigger({ event, node: n })
|
||||
|
||||
if (props.selectNodesOnDrag && isSelectable.value) {
|
||||
store.unsetNodesSelection()
|
||||
|
||||
if (!selected.value) {
|
||||
store.addSelectedElements([n])
|
||||
}
|
||||
} else if (!props.selectNodesOnDrag && !selected.value && isSelectable.value) {
|
||||
store.unsetNodesSelection()
|
||||
store.addSelectedElements([])
|
||||
}
|
||||
}
|
||||
|
||||
const onDrag: DraggableEventListener = ({ event, data }) => {
|
||||
const n = node()
|
||||
n.position.x += data.deltaX
|
||||
n.position.y += data.deltaY
|
||||
hooks.nodeDrag.trigger({ event, node: n })
|
||||
|
||||
store?.updateNodePosDiff({
|
||||
id: props.node.id as string,
|
||||
diff: {
|
||||
x: data.deltaX,
|
||||
y: data.deltaY
|
||||
},
|
||||
isDragging: true
|
||||
})
|
||||
}
|
||||
|
||||
const onDragStop: DraggableEventListener = ({ event }) => {
|
||||
const n = node()
|
||||
// onDragStop also gets called when user just clicks on a node.
|
||||
// Because of that we set dragging to true inside the onDrag handler and handle the click here
|
||||
if (!props.node.__rf.isDragging) {
|
||||
if (isSelectable.value && !props.selectNodesOnDrag && !selected.value) {
|
||||
store.addSelectedElements([n])
|
||||
}
|
||||
|
||||
hooks.nodeClick.trigger({ event, node: n })
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
store.updateNodePosDiff({
|
||||
id: n.id || '',
|
||||
isDragging: false
|
||||
})
|
||||
|
||||
hooks.nodeDragStop.trigger({ event, node: n })
|
||||
}
|
||||
|
||||
useResizeObserver(nodeElement, (entries) => {
|
||||
const updates: NodeDimensionUpdate[] = entries.map((entry) => ({
|
||||
id: entry.target.getAttribute('data-id') || '',
|
||||
nodeElement: entry.target as HTMLDivElement
|
||||
}))
|
||||
|
||||
store.updateNodeDimensions(updates)
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
store.updateNodeDimensions([
|
||||
{
|
||||
id: props.node.id || '',
|
||||
nodeElement: nodeElement.value,
|
||||
forceUpdate: true
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
return () =>
|
||||
props.node.isHidden ? (
|
||||
''
|
||||
) : (
|
||||
<DraggableCore
|
||||
cancel=".nodrag"
|
||||
scale={store.transform[2]}
|
||||
disabled={!isDraggable.value}
|
||||
grid={props.snapGrid}
|
||||
enableUserSelectHack={false}
|
||||
onStart={onDragStart}
|
||||
onMove={onDrag}
|
||||
onStop={onDragStop}
|
||||
>
|
||||
<div
|
||||
ref="nodeElement"
|
||||
class={[
|
||||
'revue-flow__node',
|
||||
`revue-flow__node-${props.node.type}`,
|
||||
{
|
||||
selected: selected.value,
|
||||
selectable: isSelectable.value
|
||||
}
|
||||
]}
|
||||
style={{
|
||||
zIndex: selected.value ? 10 : 3,
|
||||
transform: `translate(${props.node.__rf.position.x}px,${props.node.__rf.position.y}px)`,
|
||||
pointerEvents: isSelectable.value || isDraggable.value ? 'all' : 'none',
|
||||
opacity: props.node.__rf.width !== null && props.node.__rf.height !== null ? 1 : 0,
|
||||
...props.node.style
|
||||
}}
|
||||
data-id={props.node.id}
|
||||
onMouseenter={onMouseEnterHandler}
|
||||
onMousemove={onMouseMoveHandler}
|
||||
onMouseleave={onMouseLeaveHandler}
|
||||
onContextmenu={onContextMenuHandler}
|
||||
onClick={onSelectNodeHandler}
|
||||
>
|
||||
{h(props.type, {
|
||||
data: props.node.data,
|
||||
type: props.node.type,
|
||||
xPos: props.node.__rf.position.x,
|
||||
yPos: props.node.__rf.position.y,
|
||||
selected: selected.value,
|
||||
isConnectable: isConnectable.value,
|
||||
sourcePosition: props.node.sourcePosition,
|
||||
targetPosition: props.node.targetPosition,
|
||||
isDragging: props.node.__rf.isDragging
|
||||
})}
|
||||
</div>
|
||||
</DraggableCore>
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -1,34 +0,0 @@
|
||||
import Handle from '../../components/Handle'
|
||||
import { NodeProps, NodeType, Position } from '../../types'
|
||||
import { defineComponent, PropType } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'OutputNode',
|
||||
components: { Handle },
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
data: {
|
||||
type: Object as PropType<NodeProps['data']>,
|
||||
required: false,
|
||||
default: undefined as any
|
||||
},
|
||||
isConnectable: {
|
||||
type: Boolean as PropType<NodeProps['isConnectable']>,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
targetPosition: {
|
||||
type: String as PropType<NodeProps['targetPosition']>,
|
||||
required: false,
|
||||
default: Position.Top
|
||||
}
|
||||
},
|
||||
setup(props) {
|
||||
return () => (
|
||||
<>
|
||||
{props.data?.label}
|
||||
<Handle type="source" position={props.targetPosition} isConnectable={props.isConnectable} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
}) as NodeType
|
||||
@@ -1,103 +0,0 @@
|
||||
// @ts-nocheck
|
||||
/**
|
||||
* The nodes selection rectangle gets displayed when a user
|
||||
* made a selectio with on or several nodes
|
||||
*/
|
||||
import { computed, defineComponent, inject } from 'vue'
|
||||
import { Draggable, DraggableEventListener } from '@braks/revue-draggable'
|
||||
import { isNode } from '../../utils/graph'
|
||||
import { Node, RevueFlowStore } from '../../types'
|
||||
import { RevueFlowHooks } from '../../hooks/RevueFlowHooks'
|
||||
|
||||
export interface NodesSelectionProps {
|
||||
onSelectionDragStart?: (event: MouseEvent, nodes: Node[]) => void
|
||||
onSelectionDrag?: (event: MouseEvent, nodes: Node[]) => void
|
||||
onSelectionDragStop?: (event: MouseEvent, nodes: Node[]) => void
|
||||
onSelectionContextMenu?: (event: MouseEvent, nodes: Node[]) => void
|
||||
}
|
||||
|
||||
const NodesSelection = defineComponent({
|
||||
setup() {
|
||||
const store = inject<RevueFlowStore>('store')!
|
||||
const hooks = inject<RevueFlowHooks>('hooks')!
|
||||
const grid = computed(() => (store.snapToGrid ? store.snapGrid : [1, 1])! as [number, number])
|
||||
const transform = computed(() => store.transform)
|
||||
|
||||
const selectedNodes = computed(() =>
|
||||
store.selectedElements
|
||||
? store.selectedElements.filter(isNode).map((selectedNode) => {
|
||||
const matchingNode = store.nodes.find((node) => node.id === selectedNode.id)
|
||||
|
||||
return {
|
||||
...matchingNode,
|
||||
position: matchingNode?.__rf.position
|
||||
} as Node
|
||||
})
|
||||
: []
|
||||
)
|
||||
|
||||
const style = computed(() => ({
|
||||
transform: `translate(${store.transform[0]}px,${store.transform[1]}px) scale(${store.transform[2]})`
|
||||
}))
|
||||
|
||||
const innerStyle = computed(() => ({
|
||||
width: `${store.selectedNodesBbox.width}px`,
|
||||
height: `${store.selectedNodesBbox.height}px`,
|
||||
top: `${store.selectedNodesBbox.y}px`,
|
||||
left: `${store.selectedNodesBbox.x}px`
|
||||
}))
|
||||
|
||||
const onStart: DraggableEventListener = ({ event }) => {
|
||||
hooks.selectionDragStart.trigger({ event, nodes: selectedNodes.value })
|
||||
}
|
||||
|
||||
const onDrag: DraggableEventListener = ({ event, data }) => {
|
||||
hooks.selectionDrag.trigger({ event, nodes: selectedNodes.value })
|
||||
|
||||
store.updateNodePosDiff({
|
||||
diff: {
|
||||
x: data.deltaX,
|
||||
y: data.deltaY
|
||||
},
|
||||
isDragging: true
|
||||
})
|
||||
}
|
||||
|
||||
const onStop: DraggableEventListener = ({ event }) => {
|
||||
store.updateNodePosDiff({
|
||||
isDragging: false
|
||||
})
|
||||
|
||||
hooks.selectionDragStop.trigger({ event, nodes: selectedNodes.value })
|
||||
}
|
||||
|
||||
const onContextMenu = (event: MouseEvent) => {
|
||||
const selectedNodes: Node[] = store.selectedElements
|
||||
? store.selectedElements.filter(isNode).map((selectedNode) => store.nodes.find((node) => node.id === selectedNode.id))
|
||||
: []
|
||||
|
||||
hooks.selectionContextMenu.trigger({ event, nodes: selectedNodes })
|
||||
}
|
||||
|
||||
return () => {
|
||||
return !store.selectedElements || store.selectionActive ? (
|
||||
''
|
||||
) : (
|
||||
<div class="revue-flow__nodesselection" style={style.value}>
|
||||
<Draggable
|
||||
onStart={onStart}
|
||||
onMove={onDrag}
|
||||
onStop={onStop}
|
||||
scale={transform.value[2]}
|
||||
grid={grid.value}
|
||||
enableUserSelectHack={false}
|
||||
>
|
||||
<div class="revue-flow__nodesselection-rect" onContextmenu={onContextMenu} style={innerStyle.value} />
|
||||
</Draggable>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export default NodesSelection
|
||||
@@ -1,96 +0,0 @@
|
||||
/**
|
||||
* The user selection rectangle gets displayed when a user drags the mouse while pressing shift
|
||||
*/
|
||||
import { RevueFlowStore, XYPosition } from '../../types'
|
||||
import { computed, defineComponent, inject } from 'vue'
|
||||
import { templateRef, useEventListener } from '@vueuse/core'
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
const SelectionRect = (props: { width: number; height: number; x: number; y: number }) => {
|
||||
return (
|
||||
<div
|
||||
class="revue-flow__selection"
|
||||
style={{
|
||||
width: `${props.width}px`,
|
||||
height: `${props.height}px`,
|
||||
transform: `translate(${props.x}px, ${props.y}px)`
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
components: { SelectionRect },
|
||||
setup() {
|
||||
const store = inject<RevueFlowStore>('store')!
|
||||
const el = templateRef('user-selection', null)
|
||||
const shouldRender = computed(() => store.selectionActive || store.elementsSelectable)
|
||||
|
||||
const onMouseDown = (event: MouseEvent) => {
|
||||
const mousePos = getMousePosition(event)
|
||||
if (!mousePos) {
|
||||
return
|
||||
}
|
||||
|
||||
store.setUserSelection(mousePos)
|
||||
}
|
||||
|
||||
const onMouseMove = (event: MouseEvent) => {
|
||||
if (!store.selectionActive) {
|
||||
return
|
||||
}
|
||||
const mousePos = getMousePosition(event)
|
||||
if (!mousePos) {
|
||||
return
|
||||
}
|
||||
|
||||
store.updateUserSelection(mousePos)
|
||||
}
|
||||
|
||||
const onMouseUp = () => {
|
||||
store.unsetUserSelection()
|
||||
}
|
||||
|
||||
const onMouseLeave = () => {
|
||||
store.unsetUserSelection()
|
||||
store.unsetNodesSelection()
|
||||
}
|
||||
|
||||
useEventListener(el, 'mousedown', onMouseDown)
|
||||
useEventListener(el, 'mousemove', onMouseMove)
|
||||
useEventListener(el, 'click', onMouseUp)
|
||||
useEventListener(el, 'mouseup', onMouseUp)
|
||||
useEventListener(el, 'mouseleave', onMouseLeave)
|
||||
|
||||
return () =>
|
||||
shouldRender.value ? (
|
||||
<div class="revue-flow__selectionpane" ref="user-selection">
|
||||
{store?.userSelectionRect.draw ? (
|
||||
<SelectionRect
|
||||
width={store.userSelectionRect.width}
|
||||
height={store.userSelectionRect.height}
|
||||
x={store.userSelectionRect.x}
|
||||
y={store.userSelectionRect.y}
|
||||
/>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
''
|
||||
)
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user