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