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:
Braks
2021-11-25 15:21:36 +01:00
parent 74cc61ad8d
commit 96d54c59dc
5 changed files with 150 additions and 81 deletions
+21 -18
View File
@@ -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
View File
@@ -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)
}, },
}, },
}) })
+5
View File
@@ -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
View File
@@ -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
View File
@@ -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) => {