fix: duplicate setState on vue flow mount

# What's changed?

* Set props on watcher initialization, so we avoid duplicate setStates (from composable and watchers)
* Fix pausable watchers not starting up
* Flush watchers on post
This commit is contained in:
Braks
2022-04-15 17:58:02 +02:00
parent 8b3acda42e
commit 12e42e6871
3 changed files with 188 additions and 121 deletions

View File

@@ -23,64 +23,18 @@ const props = withDefaults(defineProps<FlowProps>(), {
applyDefault: undefined,
fitViewOnInit: undefined,
connectOnClick: undefined,
connectionLineStyle: null,
connectionLineStyle: undefined,
})
const emit = defineEmits([...Object.keys(createHooks()), 'update:modelValue', 'update:edges', 'update:nodes'])
const {
id,
store,
hooks,
applyDefault,
setElements,
setEdges,
setNodes,
getNodeTypes,
getEdgeTypes,
onNodesChange,
onEdgesChange,
applyNodeChanges,
applyEdgeChanges,
nodes: storedNodes,
edges: storedEdges,
} = useVueFlow(props)
const emit = defineEmits([...Object.keys(createHooks()), 'update:modelValue', 'update:nodes', 'update:edges'])
const modelProps = useVModels(props, emit)
const { id, hooks, getNodeTypes, getEdgeTypes, ...rest } = useVueFlow()
useWatch(modelProps, { id, hooks, getNodeTypes, getEdgeTypes, ...rest })
useHooks(emit, hooks.value)
const { modelValue, nodes, edges } = useVModels(props, emit)
onMounted(() => useWatch({ modelValue, nodes, edges }, props, store))
if (applyDefault.value) {
onNodesChange(applyNodeChanges)
onEdgesChange(applyEdgeChanges)
}
if (props.modelValue && !storedNodes.value.length) setElements(props.modelValue)
if (props.nodes && !storedNodes.value.length) setNodes(props.nodes)
if (props.edges && !storedEdges.value.length) setEdges(props.edges)
if (modelValue && modelValue.value) {
watch(
[() => storedEdges.value.length, () => storedNodes.value.length],
() => {
modelValue.value = [...storedNodes.value, ...storedEdges.value]
},
{ immediate: true },
)
}
if (nodes && nodes.value) {
watch(
() => storedNodes.value.length,
() => (nodes.value = [...storedNodes.value]),
{ immediate: true },
)
}
if (edges && edges.value) {
watch(
() => storedEdges.value.length,
() => (edges.value = [...storedEdges.value]),
{ immediate: true },
)
}
provide(Slots, useSlots())
</script>

View File

@@ -1,76 +1,187 @@
import { Ref } from 'vue'
import { FlowProps, Store, Node, Edge, Elements } from '~/types'
import { Ref, ToRefs } from 'vue'
import { WatchPausableReturn } from '@vueuse/core'
import { FlowProps, UseVueFlow } from '~/types'
const isDef = <T>(val: T): val is NonNullable<T> => typeof val !== 'undefined'
export default (
{
modelValue,
nodes,
edges,
}: {
modelValue?: Ref<Elements | undefined>
nodes?: Ref<Node[] | undefined>
edges?: Ref<Edge[] | undefined>
},
props: FlowProps,
store: Store,
) => {
export default (models: ToRefs<FlowProps>, store: UseVueFlow) => {
const scope = getCurrentScope()
scope?.run(() => {
if (isDefined(props.modelValue)) {
const { pause, resume } = pausableWatch([() => props.modelValue, () => props.modelValue?.length], async ([v]) => {
if (v && Array.isArray(v)) {
pause()
store.setElements(v)
if (modelValue) modelValue.value = [...store.nodes, ...store.edges]
await nextTick()
resume()
}
})
}
if (isDefined(props.nodes)) {
const { pause, resume } = pausableWatch([() => props.nodes, () => props.nodes?.length], async ([v]) => {
if (v && Array.isArray(v)) {
pause()
store.setNodes(v)
if (nodes) nodes.value = store.nodes
await nextTick()
resume()
}
})
}
if (isDefined(props.edges)) {
const { pause, resume } = pausableWatch([() => props.edges, () => props.edges?.length], async ([v]) => {
if (v && Array.isArray(v)) {
pause()
store.setEdges(v)
if (edges) edges.value = store.edges
await nextTick()
resume()
}
})
}
const watchModelValue = () => {
scope.run(() => {
let pauseModel: WatchPausableReturn
let pauseStore: WatchPausableReturn
watch(
() => props.maxZoom,
(v) => isDef(v) && store.setMaxZoom(v),
{ immediate: isDef(props.maxZoom) },
)
watch(
() => props.minZoom,
(v) => isDef(v) && store.setMinZoom(v),
{ immediate: isDef(props.minZoom) },
)
// eslint-disable-next-line prefer-const
pauseModel = watchPausable(
[models.modelValue, () => models.modelValue?.value?.length],
([v]) => {
if (v && Array.isArray(v)) {
if (pauseStore) pauseStore.pause()
if (pauseModel) pauseModel.pause()
const skip = ['modelValue', 'edges', 'nodes', 'maxZoom', 'minZoom']
Object.keys(props).forEach((p) => {
if (!skip.includes(p)) {
const prop = props[p as keyof typeof props]
watch(
() => prop,
(v) => isDef(v) && ((store as any)[p] = v),
{ immediate: isDef(prop) },
store.setElements(v)
pauseStore = watchPausable(
[() => store.edges.value.length, () => store.nodes.value.length],
() => {
models.modelValue!.value = [...store.nodes.value, ...store.edges.value]
},
{ immediate: true },
)
nextTick(() => {
if (pauseStore) pauseStore.resume()
if (pauseModel) pauseModel.resume()
})
}
},
{ immediate: !!(models.modelValue && models.modelValue.value), flush: 'post' },
)
})
}
const watchNodesValue = () => {
scope.run(() => {
let pauseModel: WatchPausableReturn
let pauseStore: WatchPausableReturn
// eslint-disable-next-line prefer-const
pauseModel = watchPausable(
[models.nodes, () => models.nodes?.value?.length],
async ([v]) => {
if (v && Array.isArray(v)) {
if (pauseStore) pauseStore.pause()
if (pauseModel) pauseModel.pause()
store.setNodes(v)
pauseStore = watchPausable(
() => store.nodes.value.length,
() => (models.nodes!.value = [...store.nodes.value]),
{ immediate: true },
)
nextTick(() => {
if (pauseStore) pauseStore.resume()
if (pauseModel) pauseModel.resume()
})
}
},
{ immediate: !!(models.nodes && models.nodes.value), flush: 'post' },
)
})
}
const watchEdgesValue = () => {
scope.run(() => {
let pauseModel: WatchPausableReturn
let pauseStore: WatchPausableReturn
// eslint-disable-next-line prefer-const
pauseModel = watchPausable(
[models.edges, () => models.edges?.value?.length],
async ([v]) => {
if (v && Array.isArray(v)) {
if (pauseStore) pauseStore.pause()
if (pauseModel) pauseModel.pause()
store.setEdges(v)
pauseStore = watchPausable(
() => store.edges.value.length,
() => (models.edges!.value = [...store.edges.value]),
{ immediate: true },
)
nextTick(() => {
if (pauseStore) pauseStore.resume()
if (pauseModel) pauseModel.resume()
})
}
},
{ immediate: !!(models.edges && models.edges.value), flush: 'post' },
)
})
}
const watchMaxZoom = () => {
scope.run(() => {
watch(
[() => models.maxZoom, models.maxZoom],
() => {
if (models.maxZoom && isDef(models.maxZoom.value)) {
store.setMaxZoom(models.maxZoom.value)
}
},
{ immediate: isDef(models.maxZoom?.value) },
)
})
}
const watchMinZoom = () => {
scope.run(() => {
watch(
[() => models.minZoom, models.minZoom],
() => {
if (models.minZoom && isDef(models.minZoom.value)) {
store.setMinZoom(models.minZoom.value)
}
},
{ immediate: isDef(models.minZoom?.value) },
)
})
}
const watchApplyDefault = () => {
scope.run(() => {
watch(
[() => models.applyDefault, models.applyDefault],
() => {
if (models.applyDefault && isDef(models.applyDefault.value)) {
store.applyDefault.value = models.applyDefault.value
}
},
{ immediate: isDef(models.applyDefault?.value) },
)
watch(
store.applyDefault,
() => {
if (store.applyDefault.value) {
store.onNodesChange(store.applyNodeChanges)
store.onEdgesChange(store.applyEdgeChanges)
}
},
{ immediate: true },
)
})
}
watchModelValue()
watchNodesValue()
watchEdgesValue()
watchMaxZoom()
watchMinZoom()
watchApplyDefault()
const skip = ['id', 'modelValue', 'edges', 'nodes', 'maxZoom', 'minZoom', 'applyDefault']
Object.keys(models).forEach((m) => {
if (!skip.includes(m)) {
const model = models[m as keyof typeof models]
const storedValue = (<any>store)[m] as Ref
scope.run(() => {
watch(
[() => model, model],
() => {
if (model && isDef(model.value)) {
storedValue.value = model.value
}
},
{ immediate: isDef(model?.value) },
)
})
}
})
})

View File

@@ -38,6 +38,8 @@ const isDef = <T>(val: T): val is NonNullable<T> => typeof val !== 'undefined'
const defaultState = (): State => ({
nodes: [],
edges: [],
nodeTypes: {},
edgeTypes: {},
initialized: false,
instance: null,