refactor(types): Remove internal __vf field
* fields are merged into the GraphNode / Node interface * Remove passing props from VueFlow container, use injected state Signed-off-by: Braks <78412429+bcakmakoglu@users.noreply.github.com>
This commit is contained in:
@@ -8,8 +8,8 @@ store.hooks.nodeDragStop.on(({ node }) => {
|
|||||||
const nodes = getNodesInside(
|
const nodes = getNodesInside(
|
||||||
store.getNodes,
|
store.getNodes,
|
||||||
{
|
{
|
||||||
height: props.__vf.height,
|
height: props.height,
|
||||||
width: props.__vf.width,
|
width: props.width,
|
||||||
x: props.position.x,
|
x: props.position.x,
|
||||||
y: props.position.y,
|
y: props.position.y,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ const onLoad = (flowInstance: FlowInstance) => {
|
|||||||
console.log(flowInstance.getElements())
|
console.log(flowInstance.getElements())
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialElements: Elements = getElements(10, 10)
|
const initialElements: Elements = getElements(50, 20)
|
||||||
const elements = ref(initialElements)
|
const elements = ref(initialElements)
|
||||||
|
|
||||||
const onConnect = (params: Connection | Edge) => (elements.value = addEdge(params, elements.value))
|
const onConnect = (params: Connection | Edge) => (elements.value = addEdge(params, elements.value))
|
||||||
|
|||||||
@@ -93,10 +93,10 @@ export default {
|
|||||||
>
|
>
|
||||||
<template v-for="node of store.getNodes" :key="`mini-map-node-${node.id}`">
|
<template v-for="node of store.getNodes" :key="`mini-map-node-${node.id}`">
|
||||||
<slot
|
<slot
|
||||||
:x="node.position.x"
|
:x="node.computedPosition.x"
|
||||||
:y="node.position.y"
|
:y="node.computedPosition.y"
|
||||||
:width="node.__vf.width"
|
:width="node.width"
|
||||||
:height="node.__vf.height"
|
:height="node.height"
|
||||||
:style="node.style"
|
:style="node.style"
|
||||||
:class="nodeClassNameFunc(node)"
|
:class="nodeClassNameFunc(node)"
|
||||||
:color="nodeColorFunc(node)"
|
:color="nodeColorFunc(node)"
|
||||||
@@ -106,10 +106,10 @@ export default {
|
|||||||
:shape-rendering="shapeRendering"
|
:shape-rendering="shapeRendering"
|
||||||
>
|
>
|
||||||
<MiniMapNode
|
<MiniMapNode
|
||||||
:x="node.position.x"
|
:x="node.computedPosition.x"
|
||||||
:y="node.position.y"
|
:y="node.computedPosition.y"
|
||||||
:width="node.__vf.width"
|
:width="node.width"
|
||||||
:height="node.__vf.height"
|
:height="node.height"
|
||||||
:style="node.style"
|
:style="node.style"
|
||||||
:class="nodeClassNameFunc(node)"
|
:class="nodeClassNameFunc(node)"
|
||||||
:color="nodeColorFunc(node)"
|
:color="nodeColorFunc(node)"
|
||||||
|
|||||||
@@ -1,56 +1,31 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { CSSProperties } from 'vue'
|
|
||||||
import { getBezierPath, getSmoothStepPath } from '../Edges/utils'
|
import { getBezierPath, getSmoothStepPath } from '../Edges/utils'
|
||||||
import {
|
import { ConnectionLineType, GraphNode, HandleElement, Position } from '../../types'
|
||||||
ConnectionLineType,
|
import { useStore } from '../../composables'
|
||||||
HandleElement,
|
|
||||||
Position,
|
|
||||||
HandleType,
|
|
||||||
XYPosition,
|
|
||||||
ConnectionMode,
|
|
||||||
Transform,
|
|
||||||
GraphNode,
|
|
||||||
} from '../../types'
|
|
||||||
|
|
||||||
interface ConnectionLineProps {
|
interface ConnectionLineProps {
|
||||||
sourceNode: GraphNode
|
sourceNode: GraphNode
|
||||||
connectionLineType?: ConnectionLineType
|
|
||||||
connectionLineStyle?: CSSProperties
|
|
||||||
connectionHandleId?: string
|
|
||||||
connectionNodeId?: string
|
|
||||||
connectionHandleType?: HandleType
|
|
||||||
connectionPosition?: XYPosition
|
|
||||||
connectionMode: ConnectionMode
|
|
||||||
nodes: GraphNode[]
|
|
||||||
transform: Transform
|
|
||||||
}
|
}
|
||||||
|
const props = defineProps<ConnectionLineProps>()
|
||||||
const props = withDefaults(defineProps<ConnectionLineProps>(), {
|
const store = useStore()
|
||||||
connectionLineType: ConnectionLineType.Bezier,
|
|
||||||
connectionLineStyle: () => ({}),
|
|
||||||
connectionPosition: () => ({ x: 0, y: 0 }),
|
|
||||||
})
|
|
||||||
|
|
||||||
const sourceHandle =
|
const sourceHandle =
|
||||||
props.connectionHandleId && props.connectionHandleType
|
store.connectionHandleId && store.connectionHandleType
|
||||||
? props.sourceNode.__vf.handleBounds[props.connectionHandleType]?.find(
|
? props.sourceNode.handleBounds[store.connectionHandleType]?.find((d: HandleElement) => d.id === store.connectionHandleId)
|
||||||
(d: HandleElement) => d.id === props.connectionHandleId,
|
: store.connectionHandleType && props.sourceNode.handleBounds[store.connectionHandleType ?? 'source']?.[0]
|
||||||
)
|
const sourceHandleX = sourceHandle ? sourceHandle.x + sourceHandle.width / 2 : (props.sourceNode.width as number) / 2
|
||||||
: props.connectionHandleType && props.sourceNode.__vf.handleBounds[props.connectionHandleType ?? 'source']?.[0]
|
const sourceHandleY = sourceHandle ? sourceHandle.y + sourceHandle.height / 2 : props.sourceNode.height
|
||||||
const sourceHandleX = sourceHandle ? sourceHandle.x + sourceHandle.width / 2 : (props.sourceNode.__vf.width as number) / 2
|
|
||||||
const sourceHandleY = sourceHandle ? sourceHandle.y + sourceHandle.height / 2 : props.sourceNode.__vf.height
|
|
||||||
const sourceX = props.sourceNode.position.x + sourceHandleX
|
const sourceX = props.sourceNode.position.x + sourceHandleX
|
||||||
const sourceY = props.sourceNode.position.y + sourceHandleY
|
const sourceY = props.sourceNode.position.y + sourceHandleY
|
||||||
|
|
||||||
const isRightOrLeft = sourceHandle?.position === Position.Left || sourceHandle?.position === Position.Right
|
const isRightOrLeft = sourceHandle?.position === Position.Left || sourceHandle?.position === Position.Right
|
||||||
const targetPosition = isRightOrLeft ? Position.Left : Position.Top
|
const targetPosition = isRightOrLeft ? Position.Left : Position.Top
|
||||||
|
|
||||||
const targetX = computed(() => (props.connectionPosition.x - props.transform[0]) / props.transform[2])
|
const targetX = computed(() => (store.connectionPosition.x - store.transform[0]) / store.transform[2])
|
||||||
const targetY = computed(() => (props.connectionPosition.y - props.transform[1]) / props.transform[2])
|
const targetY = computed(() => (store.connectionPosition.y - store.transform[1]) / store.transform[2])
|
||||||
|
|
||||||
const dAttr = computed(() => {
|
const dAttr = computed(() => {
|
||||||
let path = `M${sourceX},${sourceY} ${targetX.value},${targetY.value}`
|
let path = `M${sourceX},${sourceY} ${targetX.value},${targetY.value}`
|
||||||
switch (props.connectionLineType) {
|
switch (store.connectionLineType) {
|
||||||
case ConnectionLineType.Bezier:
|
case ConnectionLineType.Bezier:
|
||||||
path = getBezierPath({
|
path = getBezierPath({
|
||||||
sourceX,
|
sourceX,
|
||||||
@@ -101,14 +76,14 @@ export default {
|
|||||||
targetX,
|
targetX,
|
||||||
targetY,
|
targetY,
|
||||||
targetPosition,
|
targetPosition,
|
||||||
connectionLineType: props.connectionLineType,
|
connectionLineType: store.connectionLineType,
|
||||||
connectionLineStyle: props.connectionLineStyle,
|
connectionLineStyle: store.connectionLineStyle,
|
||||||
nodes: props.nodes,
|
nodes: store.getNodes,
|
||||||
sourceNode: props.sourceNode,
|
sourceNode: props.sourceNode,
|
||||||
sourceHandle,
|
sourceHandle,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<path :d="dAttr" class="vue-flow__connection-path" :style="props.connectionLineStyle" />
|
<path :d="dAttr" class="vue-flow__connection-path" :style="store.connectionLineStyle" />
|
||||||
</slot>
|
</slot>
|
||||||
</g>
|
</g>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -61,15 +61,15 @@ const handleEdgeUpdater = (event: MouseEvent, isSourceHandle: boolean) => {
|
|||||||
// when connection type is loose we can define all handles as sources
|
// when connection type is loose we can define all handles as sources
|
||||||
const targetNodeHandles = computed(() =>
|
const targetNodeHandles = computed(() =>
|
||||||
store.connectionMode === ConnectionMode.Strict
|
store.connectionMode === ConnectionMode.Strict
|
||||||
? props.edge.targetNode.__vf.handleBounds.target
|
? props.edge.targetNode.handleBounds.target
|
||||||
: props.edge.targetNode.__vf.handleBounds.target ?? props.edge.targetNode.__vf.handleBounds.source,
|
: props.edge.targetNode.handleBounds.target ?? props.edge.targetNode.handleBounds.source,
|
||||||
)
|
)
|
||||||
|
|
||||||
const sourceHandle = controlledComputed(
|
const sourceHandle = controlledComputed(
|
||||||
() => props.edge,
|
() => props.edge,
|
||||||
() => {
|
() => {
|
||||||
if (props.edge.sourceNode && props.edge.sourceNode.__vf.handleBounds.source)
|
if (props.edge.sourceNode && props.edge.sourceNode.handleBounds.source)
|
||||||
return getHandle(props.edge.sourceNode.__vf.handleBounds.source, props.edge.sourceHandle)
|
return getHandle(props.edge.sourceNode.handleBounds.source, props.edge.sourceHandle)
|
||||||
else return undefined
|
else return undefined
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -84,24 +84,24 @@ const updateNodePosition = (node: GraphNode, { x, y }: XYPosition) => {
|
|||||||
const position = {
|
const position = {
|
||||||
x: node.position.x + x,
|
x: node.position.x + x,
|
||||||
y: node.position.y + y,
|
y: node.position.y + y,
|
||||||
z: node.__vf.position.z,
|
z: node.computedPosition.z,
|
||||||
}
|
}
|
||||||
|
|
||||||
let extent = store.nodeExtent
|
let extent = store.nodeExtent
|
||||||
if (node.extent === 'parent' && node.parentNode && node.__vf.width && node.__vf.height) {
|
if (node.extent === 'parent' && node.parentNode && node.width && node.height) {
|
||||||
const parent = node.__vf.parentNode
|
const parent = node.parentNode
|
||||||
extent =
|
extent =
|
||||||
parent.__vf.width && parent.__vf.height
|
parent.width && parent.height
|
||||||
? [
|
? [
|
||||||
[0, 0],
|
[0, 0],
|
||||||
[parent.__vf.width - node.__vf.width, parent.__vf.height - node.__vf.height],
|
[parent.width - node.width, parent.height - node.height],
|
||||||
]
|
]
|
||||||
: extent
|
: extent
|
||||||
}
|
}
|
||||||
node.position = clampPosition(position, extent)
|
node.position = clampPosition(position, extent)
|
||||||
if (node.parentNode || node.__vf.isParent) {
|
if (node.parentNode || node.isParent) {
|
||||||
node.__vf.position = calculateXYZPosition(node, { z: position.z, ...node.position })
|
node.computedPosition = calculateXYZPosition(node, { z: position.z, ...node.position })
|
||||||
} else node.__vf.position = { z: position.z, ...node.position }
|
} else node.computedPosition = { ...node.position, z: position.z }
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateNodeDimensions = ({ nodeElement, forceUpdate }: NodeDimensionUpdate) => {
|
const updateNodeDimensions = ({ nodeElement, forceUpdate }: NodeDimensionUpdate) => {
|
||||||
@@ -110,42 +110,36 @@ const updateNodeDimensions = ({ nodeElement, forceUpdate }: NodeDimensionUpdate)
|
|||||||
const doUpdate =
|
const doUpdate =
|
||||||
dimensions.width &&
|
dimensions.width &&
|
||||||
dimensions.height &&
|
dimensions.height &&
|
||||||
(node.value.__vf.width !== dimensions.width || node.value.__vf.height !== dimensions.height || forceUpdate)
|
(node.value.width !== dimensions.width || node.value.height !== dimensions.height || forceUpdate)
|
||||||
|
|
||||||
if (doUpdate) {
|
if (doUpdate) {
|
||||||
const handleBounds = getHandleBounds(nodeElement, store.transform[2], store.id)
|
const handleBounds = getHandleBounds(nodeElement, store.transform[2], store.id)
|
||||||
|
|
||||||
node.value.__vf = {
|
node.value = {
|
||||||
...node.value.__vf,
|
...node.value,
|
||||||
...dimensions,
|
...dimensions,
|
||||||
handleBounds,
|
handleBounds,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tryOnMounted(() => {
|
onRenderTriggered(() => console.log(node.value.id))
|
||||||
watch([() => node.value.__vf.width, () => node.value.__vf.height], () => {
|
onMounted(() => {
|
||||||
|
nextTick(() => {
|
||||||
updateNodeDimensions({
|
updateNodeDimensions({
|
||||||
id: node.value.id,
|
id: props.node.id,
|
||||||
nodeElement: nodeElement.value,
|
nodeElement: nodeElement.value,
|
||||||
forceUpdate: true,
|
forceUpdate: true,
|
||||||
})
|
})
|
||||||
})
|
|
||||||
useResizeObserver(nodeElement, (entries) =>
|
|
||||||
entries.forEach((entry) => {
|
|
||||||
updateNodeDimensions({
|
|
||||||
id: entry.target.getAttribute('data-id') as string,
|
|
||||||
nodeElement: entry.target as HTMLDivElement,
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
watch([() => node.value.type, () => node.value.sourcePosition, () => node.value.targetPosition], () => {
|
useResizeObserver(nodeElement, (entries) =>
|
||||||
updateNodeDimensions({
|
entries.forEach((entry) => {
|
||||||
id: node.value.id,
|
updateNodeDimensions({
|
||||||
nodeElement: nodeElement.value,
|
id: entry.target.getAttribute('data-id') as string,
|
||||||
forceUpdate: true,
|
nodeElement: entry.target as HTMLDivElement,
|
||||||
})
|
})
|
||||||
|
}),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
@@ -180,10 +174,10 @@ export default {
|
|||||||
node.class,
|
node.class,
|
||||||
]"
|
]"
|
||||||
:style="{
|
:style="{
|
||||||
zIndex: node.dragging || selected ? 1000 : node.__vf.position.z,
|
zIndex: node.dragging || selected ? 1000 : node.computedPosition.z,
|
||||||
transform: `translate(${node.__vf.position.x}px,${node.__vf.position.y}px)`,
|
transform: `translate(${node.computedPosition.x}px,${node.computedPosition.y}px)`,
|
||||||
pointerEvents: props.selectable || props.draggable ? 'all' : 'none',
|
pointerEvents: props.selectable || props.draggable ? 'all' : 'none',
|
||||||
opacity: node.__vf.width !== null && node.__vf.height !== null ? 1 : 0,
|
opacity: node.width !== null && node.height !== null ? 1 : 0,
|
||||||
...node.style,
|
...node.style,
|
||||||
}"
|
}"
|
||||||
:data-id="node.id"
|
:data-id="node.id"
|
||||||
@@ -200,7 +194,6 @@ export default {
|
|||||||
data: node.data,
|
data: node.data,
|
||||||
type: node.type,
|
type: node.type,
|
||||||
position: node.position,
|
position: node.position,
|
||||||
__vf: node.__vf,
|
|
||||||
selected,
|
selected,
|
||||||
connectable: props.connectable,
|
connectable: props.connectable,
|
||||||
sourcePosition: node.sourcePosition,
|
sourcePosition: node.sourcePosition,
|
||||||
@@ -217,7 +210,6 @@ export default {
|
|||||||
data: node.data,
|
data: node.data,
|
||||||
type: node.type,
|
type: node.type,
|
||||||
position: node.position,
|
position: node.position,
|
||||||
__vf: node.__vf,
|
|
||||||
selected,
|
selected,
|
||||||
connectable: props.connectable,
|
connectable: props.connectable,
|
||||||
sourcePosition: node.sourcePosition,
|
sourcePosition: node.sourcePosition,
|
||||||
|
|||||||
@@ -1,16 +1,9 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { Draggable, DraggableEventListener } from '@braks/revue-draggable'
|
import { Draggable, DraggableEventListener } from '@braks/revue-draggable'
|
||||||
import { useStore } from '../../composables'
|
import { useStore } from '../../composables'
|
||||||
import { FlowElements } from '../../types'
|
|
||||||
import { getRectOfNodes } from '../../utils'
|
import { getRectOfNodes } from '../../utils'
|
||||||
|
|
||||||
interface NodesSelectionProps {
|
|
||||||
selectedElements: FlowElements
|
|
||||||
}
|
|
||||||
|
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
|
|
||||||
const props = defineProps<NodesSelectionProps>()
|
|
||||||
const selectedNodesBBox = getRectOfNodes(store.getSelectedNodes)
|
const selectedNodesBBox = getRectOfNodes(store.getSelectedNodes)
|
||||||
const innerStyle = {
|
const innerStyle = {
|
||||||
width: `${selectedNodesBBox.width}px`,
|
width: `${selectedNodesBBox.width}px`,
|
||||||
@@ -27,12 +20,12 @@ const onDrag: DraggableEventListener = ({ event, data }) => {
|
|||||||
x: node.position.x + data.deltaX,
|
x: node.position.x + data.deltaX,
|
||||||
y: node.position.y + data.deltaY,
|
y: node.position.y + data.deltaY,
|
||||||
}
|
}
|
||||||
node.__vf.isDragging = true
|
node.dragging = true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const onStop: DraggableEventListener = ({ event }) => {
|
const onStop: DraggableEventListener = ({ event }) => {
|
||||||
store.hooks.selectionDragStop.trigger({ event, nodes: store.getSelectedNodes })
|
store.hooks.selectionDragStop.trigger({ event, nodes: store.getSelectedNodes })
|
||||||
store.getSelectedNodes.forEach((node) => (node.__vf.isDragging = false))
|
store.getSelectedNodes.forEach((node) => (node.dragging = false))
|
||||||
}
|
}
|
||||||
const onContextMenu = (event: MouseEvent) => store.hooks.selectionContextMenu.trigger({ event, nodes: store.getSelectedNodes })
|
const onContextMenu = (event: MouseEvent) => store.hooks.selectionContextMenu.trigger({ event, nodes: store.getSelectedNodes })
|
||||||
|
|
||||||
|
|||||||
@@ -1,75 +1,37 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { CSSProperties } from 'vue'
|
|
||||||
import {
|
|
||||||
ConnectionLineType,
|
|
||||||
ConnectionMode,
|
|
||||||
Dimensions,
|
|
||||||
EdgeComponent,
|
|
||||||
GraphEdge,
|
|
||||||
GraphNode,
|
|
||||||
HandleType,
|
|
||||||
Transform,
|
|
||||||
XYPosition,
|
|
||||||
} from '../../types'
|
|
||||||
import EdgeWrapper from '../../components/Edges/EdgeWrapper.vue'
|
import EdgeWrapper from '../../components/Edges/EdgeWrapper.vue'
|
||||||
import ConnectionLine from '../../components/ConnectionLine/ConnectionLine.vue'
|
import ConnectionLine from '../../components/ConnectionLine/ConnectionLine.vue'
|
||||||
|
import { useStore } from '../../composables'
|
||||||
import MarkerDefinitions from './MarkerDefinitions.vue'
|
import MarkerDefinitions from './MarkerDefinitions.vue'
|
||||||
|
|
||||||
interface EdgeRendererProps {
|
const store = useStore()
|
||||||
edges: GraphEdge[]
|
|
||||||
nodes: GraphNode[]
|
|
||||||
edgeTypes: Record<string, EdgeComponent>
|
|
||||||
transform: Transform
|
|
||||||
dimensions: Dimensions
|
|
||||||
connectionLineType?: ConnectionLineType
|
|
||||||
connectionLineStyle?: CSSProperties
|
|
||||||
arrowHeadColor?: string
|
|
||||||
markerEndId?: string
|
|
||||||
elementsSelectable?: boolean
|
|
||||||
edgeUpdaterRadius?: number
|
|
||||||
connectionNodeId?: string
|
|
||||||
connectionHandleType?: HandleType
|
|
||||||
connectionHandleId?: string
|
|
||||||
connectionPosition?: XYPosition
|
|
||||||
connectionMode: ConnectionMode
|
|
||||||
nodesConnectable?: boolean
|
|
||||||
edgesUpdatable?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<EdgeRendererProps>(), {
|
|
||||||
arrowHeadColor: '#b1b1b7',
|
|
||||||
connectionLineType: ConnectionLineType.Bezier,
|
|
||||||
edgeUpdaterRadius: 10,
|
|
||||||
handleEdgeUpdate: false,
|
|
||||||
})
|
|
||||||
|
|
||||||
const getType = (type?: string) => {
|
const getType = (type?: string) => {
|
||||||
const t = type ?? 'default'
|
const t = type ?? 'default'
|
||||||
let edgeType = props.edgeTypes[t]
|
let edgeType = store.getEdgeTypes[t]
|
||||||
if (!edgeType) {
|
if (!edgeType) {
|
||||||
edgeType = props.edgeTypes.default
|
edgeType = store.getEdgeTypes.default
|
||||||
console.warn(`Edge type "${type}" not found. Using fallback type "default".`)
|
console.warn(`Edge type "${type}" not found. Using fallback type "default".`)
|
||||||
}
|
}
|
||||||
return edgeType
|
return edgeType
|
||||||
}
|
}
|
||||||
|
|
||||||
const sourceNode = controlledComputed(
|
const sourceNode = controlledComputed(
|
||||||
() => props.connectionNodeId,
|
() => store.connectionNodeId,
|
||||||
() => {
|
() => {
|
||||||
if (props.connectionNodeId) return props.nodes[props.nodes.map((n) => n.id).indexOf(props.connectionNodeId)]
|
if (store.connectionNodeId) return store.getNodes[store.getNodes.map((n) => n.id).indexOf(store.connectionNodeId)]
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
const connectionLineVisible = controlledComputed(
|
const connectionLineVisible = controlledComputed(
|
||||||
() => props.connectionNodeId,
|
() => store.connectionNodeId,
|
||||||
() =>
|
() =>
|
||||||
!!(
|
!!(
|
||||||
sourceNode.value &&
|
sourceNode.value &&
|
||||||
(typeof sourceNode.value.connectable === 'undefined' ? props.nodesConnectable : sourceNode.value.connectable) &&
|
(typeof sourceNode.value.connectable === 'undefined' ? store.nodesConnectable : sourceNode.value.connectable) &&
|
||||||
props.connectionNodeId &&
|
store.connectionNodeId &&
|
||||||
props.connectionHandleType
|
store.connectionHandleType
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
const transform = computed(() => `translate(${props.transform[0]},${props.transform[1]}) scale(${props.transform[2]})`)
|
const transform = computed(() => `translate(${store.transform[0]},${store.transform[1]}) scale(${store.transform[2]})`)
|
||||||
</script>
|
</script>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export default {
|
export default {
|
||||||
@@ -77,37 +39,25 @@ export default {
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<svg :width="props.dimensions.width" :height="props.dimensions.height" class="vue-flow__edges">
|
<svg :width="store.dimensions.width" :height="store.dimensions.height" class="vue-flow__edges">
|
||||||
<MarkerDefinitions :color="props.arrowHeadColor" />
|
<MarkerDefinitions :color="store.arrowHeadColor" />
|
||||||
<g :transform="transform">
|
<g :transform="transform">
|
||||||
<EdgeWrapper
|
<EdgeWrapper
|
||||||
v-for="(edge, i) of props.edges"
|
v-for="(edge, i) of store.getEdges"
|
||||||
:key="`${edge.id}-${i}`"
|
:key="`${edge.id}-${i}`"
|
||||||
:edge="edge"
|
:edge="edge"
|
||||||
:component="getType(edge.type)"
|
:component="getType(edge.type)"
|
||||||
:selectable="props.elementsSelectable"
|
:selectable="store.elementsSelectable"
|
||||||
:updatable="typeof edge.updatable === 'undefined' ? props.edgesUpdatable : edge.updatable"
|
:updatable="typeof edge.updatable === 'undefined' ? store.edgesUpdatable : edge.updatable"
|
||||||
:edge-updater-radius="props.edgeUpdaterRadius"
|
:edge-updater-radius="store.edgeUpdaterRadius"
|
||||||
:dimensions="props.dimensions"
|
:dimensions="store.dimensions"
|
||||||
:transform="props.transform"
|
:transform="store.transform"
|
||||||
>
|
>
|
||||||
<template #default="edgeProps">
|
<template #default="edgeProps">
|
||||||
<slot :name="`edge-${edge.type}`" v-bind="edgeProps"></slot>
|
<slot :name="`edge-${edge.type}`" v-bind="edgeProps"></slot>
|
||||||
</template>
|
</template>
|
||||||
</EdgeWrapper>
|
</EdgeWrapper>
|
||||||
<ConnectionLine
|
<ConnectionLine v-if="connectionLineVisible && sourceNode" :source-node="sourceNode">
|
||||||
v-if="connectionLineVisible && sourceNode"
|
|
||||||
:source-node="sourceNode"
|
|
||||||
:connection-line-style="props.connectionLineStyle"
|
|
||||||
:connection-line-type="props.connectionLineType"
|
|
||||||
:connection-handle-id="props.connectionHandleId"
|
|
||||||
:connection-node-id="props.connectionNodeId"
|
|
||||||
:connection-handle-type="props.connectionHandleType"
|
|
||||||
:connection-position="props.connectionPosition"
|
|
||||||
:connection-mode="props.connectionMode"
|
|
||||||
:nodes="props.nodes"
|
|
||||||
:transform="props.transform"
|
|
||||||
>
|
|
||||||
<template #default="customConnectionLineProps">
|
<template #default="customConnectionLineProps">
|
||||||
<slot name="custom-connection-line" v-bind="customConnectionLineProps"></slot>
|
<slot name="custom-connection-line" v-bind="customConnectionLineProps"></slot>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,40 +1,21 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { GraphNode, NodeComponent, SnapGrid, Transform } from '../../types'
|
import { GraphNode } from '../../types'
|
||||||
import NodeWrapper from '../../components/Nodes/NodeWrapper.vue'
|
import NodeWrapper from '../../components/Nodes/NodeWrapper.vue'
|
||||||
|
import { useStore } from '../../composables'
|
||||||
|
|
||||||
interface NodeRendererProps {
|
const store = useStore()
|
||||||
nodes?: GraphNode[]
|
|
||||||
nodeTypes: Record<string, NodeComponent>
|
|
||||||
selectNodesOnDrag?: boolean
|
|
||||||
snapToGrid?: boolean
|
|
||||||
snapGrid?: SnapGrid
|
|
||||||
transform: Transform
|
|
||||||
elementsSelectable?: boolean
|
|
||||||
nodesDraggable?: boolean
|
|
||||||
nodesConnectable?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<NodeRendererProps>(), {
|
|
||||||
selectNodesOnDrag: true,
|
|
||||||
nodesConnectable: true,
|
|
||||||
nodesDraggable: true,
|
|
||||||
elementsSelectable: true,
|
|
||||||
snapToGrid: false,
|
|
||||||
snapGrid: () => [15, 15],
|
|
||||||
})
|
|
||||||
|
|
||||||
const getType = (type?: string) => {
|
const getType = (type?: string) => {
|
||||||
const t = type ?? 'default'
|
const t = type ?? 'default'
|
||||||
let nodeType = props.nodeTypes[t]
|
let nodeType = store.getNodeTypes[t]
|
||||||
if (!nodeType) {
|
if (!nodeType) {
|
||||||
nodeType = props.nodeTypes.default
|
nodeType = store.getNodeTypes.default
|
||||||
console.warn(`Node type "${type}" not found. Using fallback type "default".`)
|
console.warn(`Node type "${type}" not found. Using fallback type "default".`)
|
||||||
}
|
}
|
||||||
return nodeType
|
return nodeType
|
||||||
}
|
}
|
||||||
|
|
||||||
const transform = computed(() => `translate(${props.transform[0]}px,${props.transform[1]}px) scale(${props.transform[2]})`)
|
const transform = computed(() => `translate(${store.transform[0]}px,${store.transform[1]}px) scale(${store.transform[2]})`)
|
||||||
const snapGrid = (node: GraphNode) => node.snapGrid ?? (props.snapToGrid ? props.snapGrid : undefined)
|
const snapGrid = (node: GraphNode) => node.snapGrid ?? (store.snapToGrid ? store.snapGrid : undefined)
|
||||||
</script>
|
</script>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export default {
|
export default {
|
||||||
@@ -44,16 +25,16 @@ export default {
|
|||||||
<template>
|
<template>
|
||||||
<div class="vue-flow__nodes" :style="{ transform }">
|
<div class="vue-flow__nodes" :style="{ transform }">
|
||||||
<NodeWrapper
|
<NodeWrapper
|
||||||
v-for="(node, i) of props.nodes"
|
v-for="(node, i) of store.getNodes"
|
||||||
:key="`${node.id}-${i}`"
|
:key="`${node.id}-${i}`"
|
||||||
:node="node"
|
:node="node"
|
||||||
:component="getType(node.type)"
|
:component="getType(node.type)"
|
||||||
:draggable="typeof node.draggable === 'undefined' ? props.nodesDraggable : node.draggable"
|
:draggable="typeof node.draggable === 'undefined' ? store.nodesDraggable : node.draggable"
|
||||||
:selectable="typeof node.selectable === 'undefined' ? props.elementsSelectable : node.selectable"
|
:selectable="typeof node.selectable === 'undefined' ? store.elementsSelectable : node.selectable"
|
||||||
:connectable="typeof node.connectable === 'undefined' ? props.nodesConnectable : node.connectable"
|
:connectable="typeof node.connectable === 'undefined' ? store.nodesConnectable : node.connectable"
|
||||||
:snap-grid="snapGrid(node)"
|
:snap-grid="snapGrid(node)"
|
||||||
:select-nodes-on-drag="props.selectNodesOnDrag"
|
:select-nodes-on-drag="store.selectNodesOnDrag"
|
||||||
:scale="props.transform[2]"
|
:scale="store.transform[2]"
|
||||||
>
|
>
|
||||||
<template #default="nodeProps">
|
<template #default="nodeProps">
|
||||||
<slot :name="`node-${node.type}`" v-bind="nodeProps"></slot>
|
<slot :name="`node-${node.type}`" v-bind="nodeProps"></slot>
|
||||||
|
|||||||
@@ -5,24 +5,6 @@ import { getConnectedEdges, isGraphNode } from '../../utils'
|
|||||||
import NodesSelection from '../../components/NodesSelection/NodesSelection.vue'
|
import NodesSelection from '../../components/NodesSelection/NodesSelection.vue'
|
||||||
import UserSelection from '../../components/UserSelection/UserSelection.vue'
|
import UserSelection from '../../components/UserSelection/UserSelection.vue'
|
||||||
|
|
||||||
interface SelectionPaneProps {
|
|
||||||
edges: GraphEdge[]
|
|
||||||
selectedElements?: FlowElements
|
|
||||||
selectionKeyCode?: KeyCode
|
|
||||||
deleteKeyCode?: KeyCode
|
|
||||||
multiSelectionKeyCode?: KeyCode
|
|
||||||
elementsSelectable?: boolean
|
|
||||||
nodesSelectionActive?: boolean
|
|
||||||
selectionActive?: boolean
|
|
||||||
}
|
|
||||||
const props = withDefaults(defineProps<SelectionPaneProps>(), {
|
|
||||||
selectionKeyCode: 'Shift',
|
|
||||||
deleteKeyCode: 'Backspace',
|
|
||||||
multiSelectionKeyCode: 'Meta',
|
|
||||||
elementsSelectable: true,
|
|
||||||
selectedElements: () => [],
|
|
||||||
})
|
|
||||||
|
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
|
|
||||||
const onClick = (event: MouseEvent) => {
|
const onClick = (event: MouseEvent) => {
|
||||||
@@ -38,11 +20,11 @@ const onWheel = (event: WheelEvent) => store.hooks.paneScroll.trigger(event)
|
|||||||
const userSelection = ref(false)
|
const userSelection = ref(false)
|
||||||
|
|
||||||
tryOnMounted(() => {
|
tryOnMounted(() => {
|
||||||
useKeyPress(props.deleteKeyCode, (keyPressed) => {
|
useKeyPress(store.deleteKeyCode, (keyPressed) => {
|
||||||
if (keyPressed && props.selectedElements) {
|
if (keyPressed && store.selectedElements) {
|
||||||
const selectedNodes = props.selectedElements.filter(isGraphNode)
|
const selectedNodes = store.selectedElements.filter(isGraphNode)
|
||||||
const connectedEdges = getConnectedEdges(selectedNodes, props.edges)
|
const connectedEdges = getConnectedEdges(selectedNodes, store.getEdges)
|
||||||
const elementsToRemove = [...props.selectedElements, ...connectedEdges].reduce(
|
const elementsToRemove = [...store.selectedElements, ...connectedEdges].reduce(
|
||||||
(res, item) => res.set(item.id, item),
|
(res, item) => res.set(item.id, item),
|
||||||
new Map<string, FlowElement>(),
|
new Map<string, FlowElement>(),
|
||||||
)
|
)
|
||||||
@@ -53,12 +35,12 @@ tryOnMounted(() => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
useKeyPress(props.multiSelectionKeyCode, (keyPressed) => {
|
useKeyPress(store.multiSelectionKeyCode, (keyPressed) => {
|
||||||
store.multiSelectionActive = keyPressed
|
store.multiSelectionActive = keyPressed
|
||||||
})
|
})
|
||||||
|
|
||||||
useKeyPress(props.selectionKeyCode, (keyPressed) => {
|
useKeyPress(store.selectionKeyCode, (keyPressed) => {
|
||||||
userSelection.value = keyPressed && (props.selectionActive || props.elementsSelectable)
|
userSelection.value = keyPressed && (store.selectionActive || store.elementsSelectable)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
@@ -70,12 +52,7 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
<UserSelection v-if="userSelection" id="user-selection" :key="`user-selection-${store.id}`" />
|
<UserSelection v-if="userSelection" :key="`user-selection-${store.id}`" />
|
||||||
<NodesSelection
|
<NodesSelection v-if="store.nodesSelectionActive" :key="`nodes-selction-${store.id}`" />
|
||||||
v-if="props.nodesSelectionActive"
|
|
||||||
id="nodes-selection"
|
|
||||||
:key="`nodes-selction-${store.id}`"
|
|
||||||
:selected-elements="props.selectedElements"
|
|
||||||
/>
|
|
||||||
<div :key="`flow-pane-${store.id}`" class="vue-flow__pane" @click="onClick" @contextmenu="onContextMenu" @wheel="onWheel" />
|
<div :key="`flow-pane-${store.id}`" class="vue-flow__pane" @click="onClick" @contextmenu="onContextMenu" @wheel="onWheel" />
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,16 +1,4 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { CSSProperties } from 'vue'
|
|
||||||
import {
|
|
||||||
ConnectionLineType,
|
|
||||||
ConnectionMode,
|
|
||||||
Elements,
|
|
||||||
PanOnScrollMode,
|
|
||||||
KeyCode,
|
|
||||||
CoordinateExtent,
|
|
||||||
NodeTypes,
|
|
||||||
EdgeTypes,
|
|
||||||
FlowOptions,
|
|
||||||
} from '../../types'
|
|
||||||
import Renderer from '../Renderer/Renderer.vue'
|
import Renderer from '../Renderer/Renderer.vue'
|
||||||
import ZoomPane from '../ZoomPane/ZoomPane.vue'
|
import ZoomPane from '../ZoomPane/ZoomPane.vue'
|
||||||
import SelectionPane from '../SelectionPane/SelectionPane.vue'
|
import SelectionPane from '../SelectionPane/SelectionPane.vue'
|
||||||
@@ -18,47 +6,7 @@ import NodeRenderer from '../NodeRenderer/NodeRenderer.vue'
|
|||||||
import EdgeRenderer from '../EdgeRenderer/EdgeRenderer.vue'
|
import EdgeRenderer from '../EdgeRenderer/EdgeRenderer.vue'
|
||||||
import LoadingIndicator from '../../components/Loading/LoadingIndicator.vue'
|
import LoadingIndicator from '../../components/Loading/LoadingIndicator.vue'
|
||||||
import { createHooks, initFlow } from '../../composables'
|
import { createHooks, initFlow } from '../../composables'
|
||||||
|
import type { FlowProps } from '../../types/flow'
|
||||||
interface FlowProps extends FlowOptions {
|
|
||||||
id?: string
|
|
||||||
modelValue?: Elements
|
|
||||||
nodeTypes?: NodeTypes
|
|
||||||
edgeTypes?: EdgeTypes
|
|
||||||
connectionMode?: ConnectionMode
|
|
||||||
connectionLineType?: ConnectionLineType
|
|
||||||
connectionLineStyle?: CSSProperties
|
|
||||||
deleteKeyCode?: KeyCode
|
|
||||||
selectionKeyCode?: KeyCode
|
|
||||||
multiSelectionKeyCode?: KeyCode
|
|
||||||
zoomActivationKeyCode?: KeyCode
|
|
||||||
preventScrolling?: boolean
|
|
||||||
snapToGrid?: boolean
|
|
||||||
snapGrid?: [number, number]
|
|
||||||
onlyRenderVisibleElements?: boolean
|
|
||||||
edgesUpdatable?: boolean
|
|
||||||
nodesDraggable?: boolean
|
|
||||||
nodesConnectable?: boolean
|
|
||||||
elementsSelectable?: boolean
|
|
||||||
selectNodesOnDrag?: boolean
|
|
||||||
paneMoveable?: boolean
|
|
||||||
minZoom?: number
|
|
||||||
maxZoom?: number
|
|
||||||
defaultZoom?: number
|
|
||||||
defaultPosition?: [number, number]
|
|
||||||
translateExtent?: CoordinateExtent
|
|
||||||
nodeExtent?: CoordinateExtent
|
|
||||||
arrowHeadColor?: string
|
|
||||||
markerEndId?: string
|
|
||||||
zoomOnScroll?: boolean
|
|
||||||
zoomOnPinch?: boolean
|
|
||||||
panOnScroll?: boolean
|
|
||||||
panOnScrollSpeed?: number
|
|
||||||
panOnScrollMode?: PanOnScrollMode
|
|
||||||
zoomOnDoubleClick?: boolean
|
|
||||||
edgeUpdaterRadius?: number
|
|
||||||
storageKey?: string
|
|
||||||
loading?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<FlowProps>(), {
|
const props = withDefaults(defineProps<FlowProps>(), {
|
||||||
modelValue: () => [],
|
modelValue: () => [],
|
||||||
@@ -119,45 +67,10 @@ export default {
|
|||||||
<Suspense timeout="0">
|
<Suspense timeout="0">
|
||||||
<template #default>
|
<template #default>
|
||||||
<Renderer :key="`renderer-${store.id}`" :elements="elements">
|
<Renderer :key="`renderer-${store.id}`" :elements="elements">
|
||||||
<ZoomPane
|
<ZoomPane :key="`zoom-pane-${store.id}`">
|
||||||
:key="`zoom-pane-${store.id}`"
|
|
||||||
:prevent-scrolling="store.preventScrolling"
|
|
||||||
:selection-key-code="store.selectionKeyCode"
|
|
||||||
:zoom-activation-key-code="store.zoomActivationKeyCode"
|
|
||||||
:default-zoom="store.defaultZoom"
|
|
||||||
:default-position="store.defaultPosition"
|
|
||||||
:zoom-on-scroll="store.zoomOnScroll"
|
|
||||||
:zoom-on-pinch="store.zoomOnPinch"
|
|
||||||
:zoom-on-double-click="store.zoomOnDoubleClick"
|
|
||||||
:pan-on-scroll="store.panOnScroll"
|
|
||||||
:pan-on-scroll-speed="store.panOnScrollSpeed"
|
|
||||||
:pan-on-scroll-mode="store.panOnScrollMode"
|
|
||||||
:pane-moveable="store.paneMoveable"
|
|
||||||
>
|
|
||||||
<template #default="zoomPaneProps">
|
<template #default="zoomPaneProps">
|
||||||
<SelectionPane
|
<SelectionPane :key="`selection-pane-${store.id}`">
|
||||||
:key="`selection-pane-${store.id}`"
|
<NodeRenderer :key="`node-renderer-${store.id}`">
|
||||||
:edges="store.getEdges"
|
|
||||||
:selected-elements="store.selectedElements"
|
|
||||||
:selection-active="store.selectionActive"
|
|
||||||
:nodes-selection-active="store.nodesSelectionActive"
|
|
||||||
:elements-selectable="store.elementsSelectable"
|
|
||||||
:delete-key-code="store.deleteKeyCode"
|
|
||||||
:multi-selection-key-code="store.multiSelectionKeyCode"
|
|
||||||
:selection-key-code="store.selectionKeyCode"
|
|
||||||
>
|
|
||||||
<NodeRenderer
|
|
||||||
:key="`node-renderer-${store.id}`"
|
|
||||||
:nodes="store.getNodes"
|
|
||||||
:node-types="store.getNodeTypes"
|
|
||||||
:snap-to-grid="store.snapToGrid"
|
|
||||||
:snap-grid="store.snapGrid"
|
|
||||||
:select-nodes-on-drag="store.selectNodesOnDrag"
|
|
||||||
:transform="store.transform"
|
|
||||||
:elements-selectable="store.elementsSelectable"
|
|
||||||
:nodes-draggable="store.nodesDraggable"
|
|
||||||
:nodes-connectable="store.nodesConnectable"
|
|
||||||
>
|
|
||||||
<template
|
<template
|
||||||
v-for="nodeName of Object.keys(store.getNodeTypes)"
|
v-for="nodeName of Object.keys(store.getNodeTypes)"
|
||||||
#[`node-${nodeName}`]="nodeProps"
|
#[`node-${nodeName}`]="nodeProps"
|
||||||
@@ -166,28 +79,7 @@ export default {
|
|||||||
<slot :name="`node-${nodeName}`" v-bind="nodeProps" />
|
<slot :name="`node-${nodeName}`" v-bind="nodeProps" />
|
||||||
</template>
|
</template>
|
||||||
</NodeRenderer>
|
</NodeRenderer>
|
||||||
<EdgeRenderer
|
<EdgeRenderer :key="`edge-renderer-${store.id}`">
|
||||||
:key="`edge-renderer-${store.id}`"
|
|
||||||
:transform="store.transform"
|
|
||||||
:dimensions="store.dimensions"
|
|
||||||
:edges="store.getEdges"
|
|
||||||
:edge-types="store.getEdgeTypes"
|
|
||||||
:nodes="store.getNodes"
|
|
||||||
:selected-elements="store.selectedElements"
|
|
||||||
:elements-selectable="store.elementsSelectable"
|
|
||||||
:connection-node-id="store.connectionNodeId"
|
|
||||||
:connction-handle-id="store.connectionHandleId"
|
|
||||||
:connection-handle-type="store.connectionHandleType"
|
|
||||||
:connection-position="store.connectionPosition"
|
|
||||||
:connection-mode="store.connectionMode"
|
|
||||||
:nodes-connectable="store.nodesConnectable"
|
|
||||||
:connection-line-type="store.connectionLineType"
|
|
||||||
:connection-line-style="store.connectionLineStyle"
|
|
||||||
:arrow-head-color="store.arrowHeadColor"
|
|
||||||
:marker-end-id="store.markerEndId"
|
|
||||||
:only-render-visible-elements="store.onlyRenderVisibleElements"
|
|
||||||
:edges-updatable="store.edgesUpdatable"
|
|
||||||
>
|
|
||||||
<template
|
<template
|
||||||
v-for="edgeName of Object.keys(store.getEdgeTypes)"
|
v-for="edgeName of Object.keys(store.getEdgeTypes)"
|
||||||
#[`edge-${edgeName}`]="edgeProps"
|
#[`edge-${edgeName}`]="edgeProps"
|
||||||
|
|||||||
@@ -1,39 +1,10 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { D3ZoomEvent, zoom, zoomIdentity, ZoomTransform } from 'd3-zoom'
|
import { D3ZoomEvent, zoom, zoomIdentity, ZoomTransform } from 'd3-zoom'
|
||||||
import { pointer, select } from 'd3-selection'
|
import { pointer, select } from 'd3-selection'
|
||||||
import { FlowTransform, KeyCode, PanOnScrollMode } from '../../types'
|
import { FlowTransform, PanOnScrollMode } from '../../types'
|
||||||
import { useKeyPress, useStore, useWindow } from '../../composables'
|
import { useKeyPress, useStore, useWindow } from '../../composables'
|
||||||
import { clamp } from '../../utils'
|
import { clamp, clampPosition } from '../../utils'
|
||||||
|
|
||||||
interface ZoomPaneProps {
|
|
||||||
selectionKeyCode?: KeyCode
|
|
||||||
zoomActivationKeyCode?: KeyCode
|
|
||||||
paneMoveable?: boolean
|
|
||||||
defaultZoom?: number
|
|
||||||
defaultPosition?: [number, number]
|
|
||||||
zoomOnScroll?: boolean
|
|
||||||
zoomOnPinch?: boolean
|
|
||||||
panOnScroll?: boolean
|
|
||||||
panOnScrollSpeed?: number
|
|
||||||
panOnScrollMode?: PanOnScrollMode
|
|
||||||
zoomOnDoubleClick?: boolean
|
|
||||||
preventScrolling?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<ZoomPaneProps>(), {
|
|
||||||
selectionKeyCode: 'Shift',
|
|
||||||
zoomActivationKeyCode: 'Meta',
|
|
||||||
defaultZoom: 1,
|
|
||||||
defaultPosition: () => [0, 0],
|
|
||||||
zoomOnScroll: true,
|
|
||||||
zoomOnPinch: true,
|
|
||||||
zoomOnDoubleClick: true,
|
|
||||||
panOnScroll: false,
|
|
||||||
panOnScrollSpeed: 0.5,
|
|
||||||
panOnScrollMode: PanOnScrollMode.Free,
|
|
||||||
paneMoveable: true,
|
|
||||||
preventScrolling: true,
|
|
||||||
})
|
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
const zoomPaneEl = templateRef<HTMLDivElement>('zoomPane', null)
|
const zoomPaneEl = templateRef<HTMLDivElement>('zoomPane', null)
|
||||||
|
|
||||||
@@ -50,10 +21,11 @@ const eventToFlowTransform = (eventTransform: ZoomTransform): FlowTransform => (
|
|||||||
|
|
||||||
const noWheel = (event: Event) => (event.target as Element).closest('.nowheel')
|
const noWheel = (event: Event) => (event.target as Element).closest('.nowheel')
|
||||||
|
|
||||||
const clampedX = clamp(props.defaultPosition[0], store.translateExtent[0][0], store.translateExtent[1][0])
|
const clampedZoom = clamp(store.defaultZoom, store.minZoom, store.maxZoom)
|
||||||
const clampedY = clamp(props.defaultPosition[1], store.translateExtent[0][1], store.translateExtent[1][1])
|
const transform = ref({
|
||||||
const clampedZoom = clamp(props.defaultZoom, store.minZoom, store.maxZoom)
|
...clampPosition({ x: store.defaultPosition[0], y: store.defaultPosition[1] }, store.translateExtent),
|
||||||
const transform = ref({ x: clampedX, y: clampedY, zoom: clampedZoom })
|
zoom: clampedZoom,
|
||||||
|
})
|
||||||
const d3Zoom = ref(zoom<HTMLDivElement, any>().scaleExtent([store.minZoom, store.maxZoom]).translateExtent(store.translateExtent))
|
const d3Zoom = ref(zoom<HTMLDivElement, any>().scaleExtent([store.minZoom, store.maxZoom]).translateExtent(store.translateExtent))
|
||||||
const d3Selection = ref()
|
const d3Selection = ref()
|
||||||
|
|
||||||
@@ -66,7 +38,7 @@ nextTick(async () => {
|
|||||||
const d3s = d3Selection.value!
|
const d3s = d3Selection.value!
|
||||||
const d3ZoomHandler = d3s.on('wheel.zoom')
|
const d3ZoomHandler = d3s.on('wheel.zoom')
|
||||||
|
|
||||||
const updatedTransform = zoomIdentity.translate(clampedX, clampedY).scale(clampedZoom)
|
const updatedTransform = zoomIdentity.translate(transform.value.x, transform.value.y).scale(transform.value.zoom)
|
||||||
d3z.transform(d3s, updatedTransform)
|
d3z.transform(d3s, updatedTransform)
|
||||||
store.initD3Zoom({ d3Zoom: d3z, d3Selection: d3s, d3ZoomHandler })
|
store.initD3Zoom({ d3Zoom: d3z, d3Selection: d3s, d3ZoomHandler })
|
||||||
store.transform = [updatedTransform.x, updatedTransform.y, updatedTransform.k]
|
store.transform = [updatedTransform.x, updatedTransform.y, updatedTransform.k]
|
||||||
@@ -87,7 +59,7 @@ nextTick(async () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
useKeyPress(props.selectionKeyCode, (keyPress) => {
|
useKeyPress(store.selectionKeyCode, (keyPress) => {
|
||||||
if (keyPress) {
|
if (keyPress) {
|
||||||
d3z.on('zoom', null)
|
d3z.on('zoom', null)
|
||||||
} else {
|
} else {
|
||||||
@@ -101,8 +73,8 @@ nextTick(async () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
useKeyPress(props.zoomActivationKeyCode, (keyPress) => {
|
useKeyPress(store.zoomActivationKeyCode, (keyPress) => {
|
||||||
if (props.panOnScroll && keyPress) {
|
if (store.panOnScroll && keyPress) {
|
||||||
d3s
|
d3s
|
||||||
?.on('wheel', (event: WheelEvent) => {
|
?.on('wheel', (event: WheelEvent) => {
|
||||||
if (noWheel(event)) return
|
if (noWheel(event)) return
|
||||||
@@ -111,7 +83,7 @@ nextTick(async () => {
|
|||||||
|
|
||||||
const currentZoom = d3s?.property('__zoom').k || 1
|
const currentZoom = d3s?.property('__zoom').k || 1
|
||||||
|
|
||||||
if (event.ctrlKey && props.zoomOnPinch) {
|
if (event.ctrlKey && store.zoomOnPinch) {
|
||||||
const point = pointer(event)
|
const point = pointer(event)
|
||||||
// taken from https://github.com/d3/d3-zoom/blob/master/src/zoom.js
|
// 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 pinchDelta = -event.deltaY * (event.deltaMode === 1 ? 0.05 : event.deltaMode ? 1 : 0.002) * 10
|
||||||
@@ -124,40 +96,40 @@ nextTick(async () => {
|
|||||||
// increase scroll speed in firefox
|
// increase scroll speed in firefox
|
||||||
// firefox: deltaMode === 1; chrome: deltaMode === 0
|
// firefox: deltaMode === 1; chrome: deltaMode === 0
|
||||||
const deltaNormalize = event.deltaMode === 1 ? 20 : 1
|
const deltaNormalize = event.deltaMode === 1 ? 20 : 1
|
||||||
const deltaX = props.panOnScrollMode === PanOnScrollMode.Vertical ? 0 : event.deltaX * deltaNormalize
|
const deltaX = store.panOnScrollMode === PanOnScrollMode.Vertical ? 0 : event.deltaX * deltaNormalize
|
||||||
const deltaY = props.panOnScrollMode === PanOnScrollMode.Horizontal ? 0 : event.deltaY * deltaNormalize
|
const deltaY = store.panOnScrollMode === PanOnScrollMode.Horizontal ? 0 : event.deltaY * deltaNormalize
|
||||||
|
|
||||||
if (d3s && props.panOnScrollSpeed)
|
if (d3s && store.panOnScrollSpeed)
|
||||||
d3z?.translateBy(
|
d3z?.translateBy(
|
||||||
d3s,
|
d3s,
|
||||||
-(deltaX / currentZoom) * props.panOnScrollSpeed,
|
-(deltaX / currentZoom) * store.panOnScrollSpeed,
|
||||||
-(deltaY / currentZoom) * props.panOnScrollSpeed,
|
-(deltaY / currentZoom) * store.panOnScrollSpeed,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.on('wheel.zoom', null)
|
.on('wheel.zoom', null)
|
||||||
} else if (typeof d3ZoomHandler !== 'undefined') {
|
} else if (typeof d3ZoomHandler !== 'undefined') {
|
||||||
d3s
|
d3s
|
||||||
?.on('wheel', (event: WheelEvent) => {
|
?.on('wheel', (event: WheelEvent) => {
|
||||||
if ((!props.zoomOnScroll && props.preventScrolling) || !props.preventScrolling || noWheel(event)) return
|
if ((!store.zoomOnScroll && store.preventScrolling) || !store.preventScrolling || noWheel(event)) return
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
})
|
})
|
||||||
.on('wheel.zoom', d3ZoomHandler)
|
.on('wheel.zoom', d3ZoomHandler)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const keyPress = useKeyPress(props.selectionKeyCode)
|
const keyPress = useKeyPress(store.selectionKeyCode)
|
||||||
d3z.filter((event: MouseEvent) => {
|
d3z.filter((event: MouseEvent) => {
|
||||||
const pinchZoom = props.zoomOnPinch && event.ctrlKey
|
const pinchZoom = store.zoomOnPinch && event.ctrlKey
|
||||||
|
|
||||||
// if all interactions are disabled, we prevent all zoom events
|
// if all interactions are disabled, we prevent all zoom events
|
||||||
if (!props.paneMoveable && !props.zoomOnScroll && !props.panOnScroll && !props.zoomOnDoubleClick && !props.zoomOnPinch)
|
if (!store.paneMoveable && !store.zoomOnScroll && !store.panOnScroll && !store.zoomOnDoubleClick && !store.zoomOnPinch)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
// during a selection we prevent all other interactions
|
// during a selection we prevent all other interactions
|
||||||
if (keyPress.value) return false
|
if (keyPress.value) return false
|
||||||
|
|
||||||
// if zoom on double click is disabled, we prevent the double click event
|
// if zoom on double click is disabled, we prevent the double click event
|
||||||
if (!props.zoomOnDoubleClick && event.type === 'dblclick') return false
|
if (!store.zoomOnDoubleClick && event.type === 'dblclick') return false
|
||||||
|
|
||||||
if (noWheel(event) && event.type === 'wheel') return false
|
if (noWheel(event) && event.type === 'wheel') return false
|
||||||
|
|
||||||
@@ -171,13 +143,13 @@ nextTick(async () => {
|
|||||||
// when the target element is a node selection, we still allow zooming
|
// when the target element is a node selection, we still allow zooming
|
||||||
if ((event.target as Element).closest('.vue-flow__nodesselection') && event.type !== 'wheel') return false
|
if ((event.target as Element).closest('.vue-flow__nodesselection') && event.type !== 'wheel') return false
|
||||||
|
|
||||||
if (!props.zoomOnPinch && event.ctrlKey && event.type === 'wheel') return false
|
if (!store.zoomOnPinch && event.ctrlKey && event.type === 'wheel') return false
|
||||||
|
|
||||||
// when there is no scroll handling enabled, we prevent all wheel events
|
// when there is no scroll handling enabled, we prevent all wheel events
|
||||||
if (!props.zoomOnScroll && !props.panOnScroll && !pinchZoom && event.type === 'wheel') return false
|
if (!store.zoomOnScroll && !store.panOnScroll && !pinchZoom && event.type === 'wheel') return false
|
||||||
|
|
||||||
// if the pane is not movable, we prevent dragging it with mousestart or touchstart
|
// if the pane is not movable, we prevent dragging it with mousestart or touchstart
|
||||||
if (!props.paneMoveable && (event.type === 'mousedown' || event.type === 'touchstart')) return false
|
if (!store.paneMoveable && (event.type === 'mousedown' || event.type === 'touchstart')) return false
|
||||||
|
|
||||||
// default filter for d3-zoom
|
// default filter for d3-zoom
|
||||||
return (!event.ctrlKey || event.type === 'wheel') && !event.button
|
return (!event.ctrlKey || event.type === 'wheel') && !event.button
|
||||||
@@ -190,24 +162,24 @@ const { width, height } = useElementBounding(zoomPaneEl)
|
|||||||
const window = useWindow()
|
const window = useWindow()
|
||||||
if ('screen' in window) await until(() => store.isReady).toBe(true)
|
if ('screen' in window) await until(() => store.isReady).toBe(true)
|
||||||
|
|
||||||
watch(
|
nextTick(() => {
|
||||||
[width, height],
|
watch(
|
||||||
([newWidth, newHeight]) => {
|
[width, height],
|
||||||
store.dimensions.width = newWidth
|
([newWidth, newHeight]) => {
|
||||||
store.dimensions.height = newHeight
|
store.dimensions.width = newWidth
|
||||||
},
|
store.dimensions.height = newHeight
|
||||||
{ flush: 'sync', immediate: true },
|
},
|
||||||
)
|
{ flush: 'sync', immediate: true },
|
||||||
watch(
|
)
|
||||||
transform,
|
watch(
|
||||||
(val) =>
|
transform,
|
||||||
(store.transform = [
|
(val) => {
|
||||||
clamp(val.x, store.translateExtent[0][0], store.translateExtent[1][0]),
|
const { x, y } = clampPosition(val, store.translateExtent)
|
||||||
clamp(val.y, store.translateExtent[0][1], store.translateExtent[1][1]),
|
store.transform = [x, y, clamp(val.zoom, store.minZoom, store.maxZoom)]
|
||||||
clamp(val.zoom, store.minZoom, store.maxZoom),
|
},
|
||||||
]),
|
{ flush: 'sync', immediate: true },
|
||||||
{ flush: 'sync', immediate: true },
|
)
|
||||||
)
|
})
|
||||||
</script>
|
</script>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export default {
|
export default {
|
||||||
@@ -216,11 +188,6 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div ref="zoomPane" class="vue-flow__zoompane">
|
<div ref="zoomPane" class="vue-flow__zoompane">
|
||||||
<slot
|
<slot />
|
||||||
v-bind="{
|
|
||||||
transform: store.transform,
|
|
||||||
dimensions: { width, height },
|
|
||||||
}"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
+13
-4
@@ -1,4 +1,4 @@
|
|||||||
import { FlowGetters, FlowState, GraphEdge, GraphNode } from '~/types'
|
import { FlowGetters, FlowState, GraphEdge, GraphNode, Node } from '~/types'
|
||||||
import { defaultEdgeTypes, defaultNodeTypes, getNodesInside, getSourceTargetNodes, isEdge, isGraphNode } from '~/utils'
|
import { defaultEdgeTypes, defaultNodeTypes, getNodesInside, getSourceTargetNodes, isEdge, isGraphNode } from '~/utils'
|
||||||
|
|
||||||
export default (state: FlowState): FlowGetters => {
|
export default (state: FlowState): FlowGetters => {
|
||||||
@@ -19,8 +19,16 @@ export default (state: FlowState): FlowGetters => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const getNodes = computed<GraphNode[]>(() => {
|
const getNodes = computed<GraphNode[]>(() => {
|
||||||
if (state.isReady) {
|
if (state.isReady && state.dimensions.width && state.dimensions.height) {
|
||||||
const nodes = state.elements.filter((n) => isGraphNode(n) && !n.hidden) as GraphNode[]
|
console.log('getter')
|
||||||
|
const nodes: GraphNode[] = []
|
||||||
|
const parseNode = (n: Node) => {
|
||||||
|
nodes.push(<GraphNode>n)
|
||||||
|
if (n.children && n.children.length) {
|
||||||
|
n.children.forEach((c) => parseNode(c))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state.elements.filter((n) => isGraphNode(n) && !n.hidden).forEach((node) => parseNode(<GraphNode>node))
|
||||||
return state.onlyRenderVisibleElements
|
return state.onlyRenderVisibleElements
|
||||||
? nodes &&
|
? nodes &&
|
||||||
getNodesInside(
|
getNodesInside(
|
||||||
@@ -41,7 +49,8 @@ export default (state: FlowState): FlowGetters => {
|
|||||||
|
|
||||||
const getEdges = computed<GraphEdge[]>(() => {
|
const getEdges = computed<GraphEdge[]>(() => {
|
||||||
const edges = state.elements.filter((e) => isEdge(e) && !e.hidden) as GraphEdge[]
|
const edges = state.elements.filter((e) => isEdge(e) && !e.hidden) as GraphEdge[]
|
||||||
if (state.isReady) {
|
if (state.isReady && state.dimensions.width && state.dimensions.height) {
|
||||||
|
console.log('getter2')
|
||||||
return (
|
return (
|
||||||
edges
|
edges
|
||||||
.map((edge) => {
|
.map((edge) => {
|
||||||
|
|||||||
+6
-2
@@ -109,9 +109,9 @@ export type FlowInstance<T = any> = {
|
|||||||
toObject: ToObjectFunc<T>
|
toObject: ToObjectFunc<T>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FlowOptions {
|
export interface FlowProps {
|
||||||
|
modelValue?: Elements
|
||||||
id?: string
|
id?: string
|
||||||
elements?: Elements
|
|
||||||
nodeTypes?: NodeTypes
|
nodeTypes?: NodeTypes
|
||||||
edgeTypes?: EdgeTypes
|
edgeTypes?: EdgeTypes
|
||||||
connectionMode?: ConnectionMode
|
connectionMode?: ConnectionMode
|
||||||
@@ -149,3 +149,7 @@ export interface FlowOptions {
|
|||||||
storageKey?: string
|
storageKey?: string
|
||||||
loading?: string
|
loading?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface FlowOptions extends Omit<FlowProps, 'modelValue'> {
|
||||||
|
elements?: Elements
|
||||||
|
}
|
||||||
|
|||||||
+20
-15
@@ -1,19 +1,9 @@
|
|||||||
import { DraggableOptions } from '@braks/revue-draggable'
|
import { DraggableOptions } from '@braks/revue-draggable'
|
||||||
import { XYPosition, Position, SnapGrid, Element, Dimensions, XYZPosition } from './flow'
|
import { XYPosition, Position, SnapGrid, Element, XYZPosition } from './flow'
|
||||||
import { HandleElement, ValidConnectionFunc } from './components'
|
import { HandleElement, ValidConnectionFunc } from './components'
|
||||||
|
|
||||||
export type CoordinateExtent = [[number, number], [number, number]]
|
export type CoordinateExtent = [[number, number], [number, number]]
|
||||||
|
|
||||||
export interface VFInternals extends Dimensions {
|
|
||||||
handleBounds: {
|
|
||||||
source?: HandleElement[]
|
|
||||||
target?: HandleElement[]
|
|
||||||
}
|
|
||||||
parentNode?: any
|
|
||||||
isParent?: boolean
|
|
||||||
position: XYZPosition
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Draggable = Omit<DraggableOptions, 'scale' | 'grid' | 'enableUserSelectHack' | 'enableTransformFix'> | boolean
|
export type Draggable = Omit<DraggableOptions, 'scale' | 'grid' | 'enableUserSelectHack' | 'enableTransformFix'> | boolean
|
||||||
|
|
||||||
export interface Node<T = any> extends Element<T> {
|
export interface Node<T = any> extends Element<T> {
|
||||||
@@ -28,18 +18,27 @@ export interface Node<T = any> extends Element<T> {
|
|||||||
isValidTargetPos?: ValidConnectionFunc
|
isValidTargetPos?: ValidConnectionFunc
|
||||||
isValidSourcePos?: ValidConnectionFunc
|
isValidSourcePos?: ValidConnectionFunc
|
||||||
extent?: 'parent' | CoordinateExtent
|
extent?: 'parent' | CoordinateExtent
|
||||||
parentNode?: string
|
children?: Node[]
|
||||||
|
width?: number
|
||||||
|
height?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GraphNode<T = any> extends Node<T> {
|
export interface GraphNode<T = any> extends Node<T> {
|
||||||
__vf: VFInternals
|
handleBounds: {
|
||||||
|
source?: HandleElement[]
|
||||||
|
target?: HandleElement[]
|
||||||
|
}
|
||||||
|
parentNode?: GraphNode
|
||||||
|
isParent?: boolean
|
||||||
|
computedPosition: XYZPosition
|
||||||
selected?: boolean
|
selected?: boolean
|
||||||
dragging?: boolean
|
dragging?: boolean
|
||||||
|
width: number
|
||||||
|
height: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NodeProps<T = any> extends GraphNode {
|
export interface NodeProps<T = any> extends GraphNode {
|
||||||
id: string
|
id: string
|
||||||
position: XYPosition
|
|
||||||
type?: string
|
type?: string
|
||||||
data?: T
|
data?: T
|
||||||
selected?: boolean
|
selected?: boolean
|
||||||
@@ -49,7 +48,13 @@ export interface NodeProps<T = any> extends GraphNode {
|
|||||||
dragging?: boolean
|
dragging?: boolean
|
||||||
isValidTargetPos?: ValidConnectionFunc
|
isValidTargetPos?: ValidConnectionFunc
|
||||||
isValidSourcePos?: ValidConnectionFunc
|
isValidSourcePos?: ValidConnectionFunc
|
||||||
__vf: VFInternals
|
handleBounds: {
|
||||||
|
source?: HandleElement[]
|
||||||
|
target?: HandleElement[]
|
||||||
|
}
|
||||||
|
parentNode?: GraphNode
|
||||||
|
isParent?: boolean
|
||||||
|
position: XYZPosition
|
||||||
}
|
}
|
||||||
|
|
||||||
export type NodeDimensionUpdate = {
|
export type NodeDimensionUpdate = {
|
||||||
|
|||||||
+2
-2
@@ -4,8 +4,8 @@ import { Edge, EdgePositions, GraphNode, HandleElement, Position, Transform, XYP
|
|||||||
export function getHandlePosition(position: Position, node: GraphNode, handle?: HandleElement): XYPosition {
|
export function getHandlePosition(position: Position, node: GraphNode, handle?: HandleElement): XYPosition {
|
||||||
const x = (handle?.x ?? 0) + node.position.x
|
const x = (handle?.x ?? 0) + node.position.x
|
||||||
const y = (handle?.y ?? 0) + node.position.y
|
const y = (handle?.y ?? 0) + node.position.y
|
||||||
const width = handle?.width ?? node.__vf.width
|
const width = handle?.width ?? node.width
|
||||||
const height = handle?.height ?? node.__vf.height
|
const height = handle?.height ?? node.height
|
||||||
|
|
||||||
switch (position) {
|
switch (position) {
|
||||||
case Position.Top:
|
case Position.Top:
|
||||||
|
|||||||
+27
-31
@@ -169,20 +169,17 @@ export const parseNode = (node: Node, nodeExtent: CoordinateExtent): GraphNode =
|
|||||||
...node,
|
...node,
|
||||||
id: node.id.toString(),
|
id: node.id.toString(),
|
||||||
type: node.type ?? 'default',
|
type: node.type ?? 'default',
|
||||||
__vf: {
|
width: 0,
|
||||||
width: 0,
|
height: 0,
|
||||||
height: 0,
|
handleBounds: {
|
||||||
handleBounds: {
|
source: [],
|
||||||
source: [],
|
target: [],
|
||||||
target: [],
|
},
|
||||||
},
|
computedPosition: {
|
||||||
position: {
|
z: typeof node.style?.zIndex === 'string' ? parseInt(node.style?.zIndex) : node.style?.zIndex ?? 0,
|
||||||
z: typeof node.style?.zIndex === 'string' ? parseInt(node.style?.zIndex) : node.style?.zIndex ?? 0,
|
...clampPosition(node.position, nodeExtent),
|
||||||
...clampPosition(node.position, nodeExtent),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
dragging: false,
|
dragging: false,
|
||||||
position: clampPosition(node.position, nodeExtent),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
export const parseEdge = (edge: Edge): GraphEdge => ({
|
export const parseEdge = (edge: Edge): GraphEdge => ({
|
||||||
@@ -222,7 +219,7 @@ export const getBoundsofRects = (rect1: Rect, rect2: Rect) => boxToRect(getBound
|
|||||||
|
|
||||||
export const getRectOfNodes = (nodes: GraphNode[]) => {
|
export const getRectOfNodes = (nodes: GraphNode[]) => {
|
||||||
const box = nodes.reduce(
|
const box = nodes.reduce(
|
||||||
(currBox, { position = { x: 0, y: 0 }, __vf: { width = 0, height = 0 } = {} }) =>
|
(currBox, { position = { x: 0, y: 0 }, width = 0, height = 0 } = {} as any) =>
|
||||||
getBoundsOfBoxes(
|
getBoundsOfBoxes(
|
||||||
currBox,
|
currBox,
|
||||||
rectToBox({
|
rectToBox({
|
||||||
@@ -251,12 +248,8 @@ export const getNodesInside = (nodes: GraphNode[], rect: Rect, [tx, ty, tScale]:
|
|||||||
})
|
})
|
||||||
|
|
||||||
return nodes.filter((node) => {
|
return nodes.filter((node) => {
|
||||||
if (!node.__vf || node.selectable === false) return false
|
if (!node || node.selectable === false) return false
|
||||||
const {
|
const { position = { x: 0, y: 0 }, width = 0, height = 0, dragging = false } = node
|
||||||
position = { x: 0, y: 0 },
|
|
||||||
__vf: { width = 0, height = 0 },
|
|
||||||
dragging = false,
|
|
||||||
} = node
|
|
||||||
const nBox = rectToBox({ ...position, width, height } as any)
|
const nBox = rectToBox({ ...position, width, height } as any)
|
||||||
const xOverlap = Math.max(0, Math.min(rBox.x2, nBox.x2) - Math.max(rBox.x, nBox.x))
|
const xOverlap = Math.max(0, Math.min(rBox.x2, nBox.x2) - Math.max(rBox.x, nBox.x))
|
||||||
const yOverlap = Math.max(0, Math.min(rBox.y2, nBox.y2) - Math.max(rBox.y, nBox.y))
|
const yOverlap = Math.max(0, Math.min(rBox.y2, nBox.y2) - Math.max(rBox.y, nBox.y))
|
||||||
@@ -321,11 +314,11 @@ export const getTransformForBounds = (
|
|||||||
|
|
||||||
export const processElements = (elements: FlowElements, fn: (elements: FlowElements) => void) => {
|
export const processElements = (elements: FlowElements, fn: (elements: FlowElements) => void) => {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const chunk = 50
|
const chunk = 100
|
||||||
let index = 0
|
let index = 0
|
||||||
function doChunk() {
|
function doChunk() {
|
||||||
const chunkPos = chunk * index
|
const chunkPos = chunk * index
|
||||||
const lastChunk = elements.length - chunkPos < chunkPos
|
const lastChunk = elements.length - chunkPos < chunk * (index > 0 ? index : 1)
|
||||||
const cnt = !lastChunk ? chunk : elements.length - chunkPos
|
const cnt = !lastChunk ? chunk : elements.length - chunkPos
|
||||||
fn(elements.slice(chunkPos, chunkPos + cnt))
|
fn(elements.slice(chunkPos, chunkPos + cnt))
|
||||||
index++
|
index++
|
||||||
@@ -370,10 +363,13 @@ export const parseElement = (element: Node | Edge, prevElements: FlowElements, n
|
|||||||
|
|
||||||
export function calculateXYZPosition(node: GraphNode, result: XYZPosition): XYZPosition {
|
export function calculateXYZPosition(node: GraphNode, result: XYZPosition): XYZPosition {
|
||||||
if (!node.parentNode) return result
|
if (!node.parentNode) return result
|
||||||
return calculateXYZPosition(node.__vf.parentNode, {
|
return calculateXYZPosition(node.parentNode, {
|
||||||
x: result.x + node.__vf.parentNode.position.x,
|
x: result.x + node.parentNode.position.x,
|
||||||
y: result.y + node.__vf.parentNode.position.y,
|
y: result.y + node.parentNode.position.y,
|
||||||
z: node.__vf.parentNode.__vf.z > node.__vf.position.z ? node.__vf.parentNode.__vf.z + 1 : node.__vf.position.z,
|
z:
|
||||||
|
node.parentNode.computedPosition.z > node.computedPosition.z
|
||||||
|
? node.parentNode.computedPosition.z + 1
|
||||||
|
: node.computedPosition.z,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -392,12 +388,12 @@ export const parseElements = (elements: Elements, prevElements: FlowElements, no
|
|||||||
|
|
||||||
nodes.forEach((n) => {
|
nodes.forEach((n) => {
|
||||||
if (n.parentNode) {
|
if (n.parentNode) {
|
||||||
const parent = nodes.find((p) => p.id === n.parentNode)
|
const parent = nodes.find((p) => p.id === n.parentNode?.id)
|
||||||
if (n.parentNode && !parent) {
|
if (n.parentNode && !parent) {
|
||||||
console.error(`Parent node ${n.parentNode} not found`)
|
console.error(`Parent node ${n.parentNode} not found`)
|
||||||
} else if (parent) {
|
} else if (parent) {
|
||||||
n.__vf.parentNode = parent
|
n.parentNode = parent
|
||||||
parent.__vf.isParent = true
|
parent.isParent = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -418,7 +414,7 @@ export const parseElements = (elements: Elements, prevElements: FlowElements, no
|
|||||||
|
|
||||||
export function isParentSelected(node: GraphNode): boolean {
|
export function isParentSelected(node: GraphNode): boolean {
|
||||||
if (!node.parentNode) return false
|
if (!node.parentNode) return false
|
||||||
if (!node.__vf.parentNode) return false
|
if (!node.parentNode) return false
|
||||||
if (node.__vf.parentNode.selected) return true
|
if (node.parentNode.selected) return true
|
||||||
return isParentSelected(node.__vf.parentNode)
|
return isParentSelected(node.parentNode)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user