refactor(store): check if state variables have to be re-set
* instead of just pasting over the state constantly check what needs to be re-set Signed-off-by: Braks <78412429+bcakmakoglu@users.noreply.github.com>
This commit is contained in:
@@ -11,7 +11,6 @@ import {
|
|||||||
NodeExtent,
|
NodeExtent,
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
EdgeTypes,
|
EdgeTypes,
|
||||||
FlowState,
|
|
||||||
FlowInstance,
|
FlowInstance,
|
||||||
Loading,
|
Loading,
|
||||||
FlowOptions,
|
FlowOptions,
|
||||||
@@ -116,7 +115,7 @@ const options = Object.assign({}, store.$state, props)
|
|||||||
|
|
||||||
invoke(async () => {
|
invoke(async () => {
|
||||||
store.setState(options)
|
store.setState(options)
|
||||||
store.setElements(elements.value)
|
await store.setElements(elements.value)
|
||||||
store.isReady = true
|
store.isReady = true
|
||||||
|
|
||||||
// if ssr we can't wait for dimensions, they'll never really exist
|
// if ssr we can't wait for dimensions, they'll never really exist
|
||||||
@@ -139,26 +138,25 @@ invoke(async () => {
|
|||||||
store.instance = instance
|
store.instance = instance
|
||||||
watch(
|
watch(
|
||||||
() => props,
|
() => props,
|
||||||
() => store.setState({ ...store.$state, ...props } as FlowState),
|
() => store.setState(props),
|
||||||
|
{ flush: 'post', deep: true },
|
||||||
|
)
|
||||||
|
watch(
|
||||||
|
() => props.modelValue.length,
|
||||||
|
() => store.setElements(elements.value),
|
||||||
|
)
|
||||||
|
const { pause, resume } = pausableWatch(elements, store.setElements, { flush: 'post' })
|
||||||
|
watch(
|
||||||
|
() => store.elements,
|
||||||
|
(val) => {
|
||||||
|
pause()
|
||||||
|
elements.value = val
|
||||||
|
nextTick(resume)
|
||||||
|
},
|
||||||
{ flush: 'post', deep: true },
|
{ flush: 'post', deep: true },
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.modelValue.length,
|
|
||||||
() => store.setElements(elements.value),
|
|
||||||
)
|
|
||||||
const { pause, resume } = pausableWatch(elements, store.setElements, { flush: 'post' })
|
|
||||||
watch(
|
|
||||||
() => store.elements,
|
|
||||||
(val) => {
|
|
||||||
pause()
|
|
||||||
elements.value = val
|
|
||||||
nextTick(resume)
|
|
||||||
},
|
|
||||||
{ flush: 'post', deep: true },
|
|
||||||
)
|
|
||||||
|
|
||||||
const transitionName = computed(() => {
|
const transitionName = computed(() => {
|
||||||
let name = ''
|
let name = ''
|
||||||
if (typeof store.loading === 'object' && store.loading.transition) {
|
if (typeof store.loading === 'object' && store.loading.transition) {
|
||||||
@@ -170,6 +168,11 @@ const transitionName = computed(() => {
|
|||||||
|
|
||||||
onBeforeUnmount(() => store?.$dispose())
|
onBeforeUnmount(() => store?.$dispose())
|
||||||
</script>
|
</script>
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
name: 'VueFlow'
|
||||||
|
}
|
||||||
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="vue-flow">
|
<div class="vue-flow">
|
||||||
<Transition :key="`vue-flow-transition-${store.$id}`" :name="transitionName">
|
<Transition :key="`vue-flow-transition-${store.$id}`" :name="transitionName">
|
||||||
|
|||||||
+59
-28
@@ -1,16 +1,18 @@
|
|||||||
import microDiff from 'microdiff'
|
import microDiff from 'microdiff'
|
||||||
import { setActivePinia, createPinia, defineStore, StoreDefinition, acceptHMRUpdate } from 'pinia'
|
import { setActivePinia, createPinia, defineStore, StoreDefinition, acceptHMRUpdate } from 'pinia'
|
||||||
import { FlowState, FlowActions, Elements, FlowGetters, GraphNode, GraphEdge, Edge } from '~/types'
|
import { FlowState, FlowActions, Elements, FlowGetters, GraphNode, GraphEdge, Edge, NextElements } from '~/types'
|
||||||
import {
|
import {
|
||||||
getConnectedEdges,
|
getConnectedEdges,
|
||||||
getNodesInside,
|
getNodesInside,
|
||||||
getRectOfNodes,
|
getRectOfNodes,
|
||||||
parseElements,
|
|
||||||
defaultNodeTypes,
|
defaultNodeTypes,
|
||||||
defaultEdgeTypes,
|
defaultEdgeTypes,
|
||||||
isGraphNode,
|
isGraphNode,
|
||||||
getSourceTargetNodes,
|
getSourceTargetNodes,
|
||||||
isEdge,
|
isEdge,
|
||||||
|
processElements,
|
||||||
|
parseElement,
|
||||||
|
parseElements,
|
||||||
} from '~/utils'
|
} from '~/utils'
|
||||||
|
|
||||||
const pinia = createPinia()
|
const pinia = createPinia()
|
||||||
@@ -84,8 +86,19 @@ export default (id: string, preloadedState: FlowState) => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
setElements(elements) {
|
async setElements(elements) {
|
||||||
const { nodes, edges } = parseElements(elements, this.getNodes, this.getEdges, this.nodeExtent)
|
const { nodes, edges } = parseElements(elements, this.elements, this.nodeExtent)
|
||||||
|
edges.forEach((edge: Edge, i, arr) => {
|
||||||
|
const { sourceNode, targetNode } = getSourceTargetNodes(edge, nodes)
|
||||||
|
if (!sourceNode) console.warn(`couldn't create edge for source id: ${edge.source}; edge id: ${edge.id}`)
|
||||||
|
if (!targetNode) console.warn(`couldn't create edge for target id: ${edge.target}; edge id: ${edge.id}`)
|
||||||
|
|
||||||
|
arr[i] = {
|
||||||
|
...edge,
|
||||||
|
sourceNode,
|
||||||
|
targetNode,
|
||||||
|
}
|
||||||
|
})
|
||||||
this.elements = [...nodes, ...edges]
|
this.elements = [...nodes, ...edges]
|
||||||
},
|
},
|
||||||
setUserSelection(mousePos) {
|
setUserSelection(mousePos) {
|
||||||
@@ -175,34 +188,52 @@ export default (id: string, preloadedState: FlowState) => {
|
|||||||
this.elementsSelectable = isInteractive
|
this.elementsSelectable = isInteractive
|
||||||
},
|
},
|
||||||
addElements(elements: Elements) {
|
addElements(elements: Elements) {
|
||||||
const { nodes, edges } = parseElements(elements, this.getNodes, this.getEdges, this.nodeExtent)
|
const { nodes, edges } = parseElements(elements, this.elements, this.nodeExtent)
|
||||||
|
edges.forEach((edge: Edge, i, arr) => {
|
||||||
|
const { sourceNode, targetNode } = getSourceTargetNodes(edge, nodes)
|
||||||
|
if (!sourceNode) console.warn(`couldn't create edge for source id: ${edge.source}; edge id: ${edge.id}`)
|
||||||
|
if (!targetNode) console.warn(`couldn't create edge for target id: ${edge.target}; edge id: ${edge.id}`)
|
||||||
|
|
||||||
|
arr[i] = {
|
||||||
|
...edge,
|
||||||
|
sourceNode,
|
||||||
|
targetNode,
|
||||||
|
}
|
||||||
|
})
|
||||||
this.elements = [...this.elements, ...nodes, ...edges]
|
this.elements = [...this.elements, ...nodes, ...edges]
|
||||||
},
|
},
|
||||||
async setState(state) {
|
async setState(state) {
|
||||||
// set state variables
|
if (state.panOnScroll) this.panOnScroll = state.panOnScroll
|
||||||
const skip = [
|
if (state.panOnScrollMode) this.panOnScrollMode = state.panOnScrollMode
|
||||||
'modelValue',
|
if (state.panOnScrollSpeed) this.panOnScrollSpeed = state.panOnScrollSpeed
|
||||||
'd3Zoom',
|
if (state.paneMoveable) this.paneMoveable = state.paneMoveable
|
||||||
'd3Selection',
|
if (state.zoomOnScroll) this.zoomOnScroll = state.zoomOnScroll
|
||||||
'd3ZoomHandler',
|
if (state.preventScrolling) this.preventScrolling = state.preventScrolling
|
||||||
'minZoom',
|
if (state.zoomOnDoubleClick) this.zoomOnDoubleClick = state.zoomOnDoubleClick
|
||||||
'maxZoom',
|
if (state.zoomOnPinch) this.zoomOnPinch = state.zoomOnPinch
|
||||||
'translateExtent',
|
if (state.defaultZoom) this.defaultZoom = state.defaultZoom
|
||||||
'nodeExtent',
|
if (state.defaultPosition) this.defaultPosition = state.defaultPosition
|
||||||
]
|
if (state.edgeTypes) this.edgeTypes = state.edgeTypes
|
||||||
for (const opt of Object.keys(state)) {
|
if (state.nodeTypes) this.nodeTypes = state.nodeTypes
|
||||||
const val = state[opt as keyof FlowState]
|
if (state.storageKey) this.storageKey = state.storageKey
|
||||||
if (typeof val !== 'undefined' && !skip.includes(opt)) {
|
if (state.edgeUpdaterRadius) this.edgeUpdaterRadius = state.edgeUpdaterRadius
|
||||||
if (typeof val === 'object' && !Array.isArray(val)) {
|
if (state.elementsSelectable) this.elementsSelectable = state.elementsSelectable
|
||||||
;(store as any)[opt] = { ...(store as any)[opt], ...val }
|
if (state.onlyRenderVisibleElements) this.onlyRenderVisibleElements = state.onlyRenderVisibleElements
|
||||||
} else (store as any)[opt] = val
|
if (state.nodesConnectable) this.nodesConnectable = state.nodesConnectable
|
||||||
}
|
if (state.nodesDraggable) this.nodesDraggable = state.nodesDraggable
|
||||||
}
|
if (state.arrowHeadColor) this.arrowHeadColor = state.arrowHeadColor
|
||||||
|
if (state.markerEndId) this.markerEndId = state.markerEndId
|
||||||
|
if (state.deleteKeyCode) this.deleteKeyCode = state.deleteKeyCode
|
||||||
|
if (state.selectionKeyCode) this.selectionKeyCode = state.selectionKeyCode
|
||||||
|
if (state.zoomActivationKeyCode) this.zoomActivationKeyCode = state.zoomActivationKeyCode
|
||||||
|
if (state.multiSelectionKeyCode) this.multiSelectionKeyCode = state.multiSelectionKeyCode
|
||||||
|
if (state.snapToGrid) this.snapToGrid = state.snapToGrid
|
||||||
|
if (state.snapGrid) this.snapGrid = state.snapGrid
|
||||||
if (!this.isReady) await until(() => this.d3Zoom).not.toBeUndefined()
|
if (!this.isReady) await until(() => this.d3Zoom).not.toBeUndefined()
|
||||||
this.setMinZoom(state.minZoom)
|
if (state.maxZoom) this.setMaxZoom(state.maxZoom)
|
||||||
this.setMaxZoom(state.maxZoom)
|
if (state.minZoom) this.setMinZoom(state.minZoom)
|
||||||
this.setTranslateExtent(state.translateExtent)
|
if (state.translateExtent) this.setTranslateExtent(state.translateExtent)
|
||||||
this.setNodeExtent(state.nodeExtent)
|
if (state.nodeExtent) this.setNodeExtent(state.nodeExtent)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -9,6 +9,11 @@ export type FlowElement<T = any> = GraphNode<T> | GraphEdge<T>
|
|||||||
export type FlowElements<T = any> = FlowElement<T>[]
|
export type FlowElements<T = any> = FlowElement<T>[]
|
||||||
export type Elements<T = any> = (Node<T> | Edge<T>)[]
|
export type Elements<T = any> = (Node<T> | Edge<T>)[]
|
||||||
|
|
||||||
|
export type NextElements = {
|
||||||
|
nodes: GraphNode[]
|
||||||
|
edges: GraphEdge[]
|
||||||
|
}
|
||||||
|
|
||||||
export type Transform = [number, number, number]
|
export type Transform = [number, number, number]
|
||||||
|
|
||||||
export enum Position {
|
export enum Position {
|
||||||
|
|||||||
+2
-1
@@ -73,7 +73,7 @@ export interface FlowState extends Omit<FlowOptions, 'elements'> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface FlowActions {
|
export interface FlowActions {
|
||||||
setElements: (elements: Elements) => void
|
setElements: (elements: Elements) => Promise<void>
|
||||||
setUserSelection: (mousePos: XYPosition) => void
|
setUserSelection: (mousePos: XYPosition) => void
|
||||||
updateUserSelection: (mousePos: XYPosition) => void
|
updateUserSelection: (mousePos: XYPosition) => void
|
||||||
unsetUserSelection: () => void
|
unsetUserSelection: () => void
|
||||||
@@ -89,6 +89,7 @@ export interface FlowActions {
|
|||||||
setConnectionNodeId: (payload: SetConnectionId) => void
|
setConnectionNodeId: (payload: SetConnectionId) => void
|
||||||
setInteractive: (isInteractive: boolean) => void
|
setInteractive: (isInteractive: boolean) => void
|
||||||
addElements: (elements: Elements) => void
|
addElements: (elements: Elements) => void
|
||||||
|
setState: (state: FlowOptions) => Promise<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FlowGetters {
|
export interface FlowGetters {
|
||||||
|
|||||||
+63
-34
@@ -15,6 +15,7 @@ import {
|
|||||||
GraphNode,
|
GraphNode,
|
||||||
FlowElements,
|
FlowElements,
|
||||||
GraphEdge,
|
GraphEdge,
|
||||||
|
NextElements,
|
||||||
} from '~/types'
|
} from '~/types'
|
||||||
import { useWindow } from '~/composables'
|
import { useWindow } from '~/composables'
|
||||||
import { getSourceTargetNodes } from '~/utils/edge'
|
import { getSourceTargetNodes } from '~/utils/edge'
|
||||||
@@ -312,46 +313,74 @@ export const getTransformForBounds = (
|
|||||||
return [x, y, clampedZoom]
|
return [x, y, clampedZoom]
|
||||||
}
|
}
|
||||||
|
|
||||||
type NextElements = {
|
export const processElements = (elements: Elements, fn: (element: Node | Edge) => void) => {
|
||||||
nodes: GraphNode[]
|
return new Promise((resolve) => {
|
||||||
edges: GraphEdge[]
|
const chunk = 50
|
||||||
|
let index = 0
|
||||||
|
function doChunk() {
|
||||||
|
let cnt = chunk
|
||||||
|
while (cnt-- && index < elements.length) {
|
||||||
|
fn(elements[index])
|
||||||
|
++index
|
||||||
|
}
|
||||||
|
if (index < elements.length) {
|
||||||
|
// set Timeout for async iteration
|
||||||
|
nextTick(doChunk)
|
||||||
|
} else {
|
||||||
|
resolve(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
doChunk()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
export const parseElements = (elements: Elements, nodes: GraphNode[], edges: GraphEdge[], nodeExtent: NodeExtent) => {
|
|
||||||
|
export const parseElement = (element: Node | Edge, prevElements: FlowElements, nodeExtent: NodeExtent) => {
|
||||||
|
let parsed: GraphEdge | GraphNode = {} as any
|
||||||
|
const index = prevElements.map((x) => x.id).indexOf(element.id)
|
||||||
|
if (isNode(element)) {
|
||||||
|
const storeNode = prevElements[index]
|
||||||
|
|
||||||
|
if (storeNode) {
|
||||||
|
const updatedNode = {
|
||||||
|
...storeNode,
|
||||||
|
...element,
|
||||||
|
} as GraphNode
|
||||||
|
|
||||||
|
if (typeof element.type !== 'undefined' && element.type !== storeNode.type) {
|
||||||
|
// we reset the elements dimensions here in order to force a re-calculation of the bounds.
|
||||||
|
// When the type of a node changes it is possible that the number or positions of handles changes too.
|
||||||
|
updatedNode.__vf.width = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed = updatedNode
|
||||||
|
} else {
|
||||||
|
parsed = parseNode(element, nodeExtent)
|
||||||
|
}
|
||||||
|
} else if (isEdge(element)) {
|
||||||
|
const storeEdge = prevElements[index]
|
||||||
|
|
||||||
|
if (storeEdge) {
|
||||||
|
parsed = {
|
||||||
|
...storeEdge,
|
||||||
|
...element,
|
||||||
|
} as GraphEdge
|
||||||
|
} else {
|
||||||
|
parsed = parseEdge(element)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { parsed, index }
|
||||||
|
}
|
||||||
|
|
||||||
|
export const parseElements = (elements: Elements, prevElements: FlowElements, nodeExtent: NodeExtent) => {
|
||||||
const next: NextElements = {
|
const next: NextElements = {
|
||||||
nodes: [],
|
nodes: [],
|
||||||
edges: [],
|
edges: [],
|
||||||
}
|
}
|
||||||
for (const element of elements) {
|
for (const element of elements) {
|
||||||
if (isNode(element)) {
|
const { parsed } = parseElement(element, prevElements, nodeExtent)
|
||||||
const storeNode = nodes[nodes.map((x) => x.id).indexOf(element.id)]
|
if (parsed) {
|
||||||
|
if (isEdge(parsed)) next.edges.push(parsed)
|
||||||
if (storeNode) {
|
else next.nodes.push(parsed)
|
||||||
const updatedNode = {
|
|
||||||
...storeNode,
|
|
||||||
...element,
|
|
||||||
} as GraphNode
|
|
||||||
|
|
||||||
if (typeof element.type !== 'undefined' && element.type !== storeNode.type) {
|
|
||||||
// we reset the elements dimensions here in order to force a re-calculation of the bounds.
|
|
||||||
// When the type of a node changes it is possible that the number or positions of handles changes too.
|
|
||||||
updatedNode.__vf.width = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
next.nodes.push(updatedNode)
|
|
||||||
} else {
|
|
||||||
next.nodes.push(parseNode(element, nodeExtent))
|
|
||||||
}
|
|
||||||
} else if (isEdge(element)) {
|
|
||||||
const storeEdge = edges[edges.map((x) => x.id).indexOf(element.id)]
|
|
||||||
|
|
||||||
if (storeEdge) {
|
|
||||||
next.edges.push({
|
|
||||||
...storeEdge,
|
|
||||||
...element,
|
|
||||||
} as GraphEdge)
|
|
||||||
} else {
|
|
||||||
next.edges.push(parseEdge(element))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
next.edges.forEach((edge, i, arr) => {
|
next.edges.forEach((edge, i, arr) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user