135 lines
4.2 KiB
TypeScript
135 lines
4.2 KiB
TypeScript
import { tryOnScopeDispose } from '@vueuse/core'
|
|
import type { EffectScope } from 'vue'
|
|
import { effectScope, getCurrentScope, inject, provide, watch } from 'vue'
|
|
import type { EdgeChange, FlowOptions, NodeChange, VueFlowStore } from '../types'
|
|
import { warn } from '../utils'
|
|
import { VueFlow } from '../context'
|
|
import { Storage } from '../utils/storage'
|
|
|
|
type Injection = VueFlowStore | null | undefined
|
|
type Scope = (EffectScope & { vueFlowId: string }) | undefined
|
|
|
|
// todo: maybe replace the storage with a context based solution; This would break calling useVueFlow outside a setup function though, which should be fine
|
|
/**
|
|
* Composable that provides access to a store instance
|
|
*
|
|
* If no id is provided, the store instance is injected from context
|
|
*
|
|
* If no store instance is found in context, a new store instance is created and registered in storage
|
|
*
|
|
* @public
|
|
* @param options - optional options to initialize the store instance
|
|
* @returns a vue flow store instance
|
|
*/
|
|
export function useVueFlow(options?: FlowOptions): VueFlowStore {
|
|
const storage = Storage.getInstance()
|
|
|
|
const scope = getCurrentScope() as Scope
|
|
|
|
const id = options?.id
|
|
const vueFlowId = scope?.vueFlowId || id
|
|
|
|
let vueFlow: Injection
|
|
|
|
/**
|
|
* check if we can get a store instance through injections
|
|
* this should be the regular way after initialization
|
|
*/
|
|
if (scope) {
|
|
const injection = inject(VueFlow, null)
|
|
if (typeof injection !== 'undefined' && injection !== null) {
|
|
vueFlow = injection
|
|
}
|
|
}
|
|
|
|
/**
|
|
* check if we can get a store instance through storage
|
|
* this requires options id or an id on the current scope
|
|
*/
|
|
if (!vueFlow) {
|
|
if (vueFlowId) {
|
|
vueFlow = storage.get(vueFlowId)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* If we cannot find any store instance in the previous steps
|
|
* _or_ if the store instance we found does not match up with provided ids
|
|
* create a new store instance and register it in storage
|
|
*/
|
|
if (!vueFlow || (vueFlow && id && id !== vueFlow.id)) {
|
|
const name = id ?? storage.getId()
|
|
|
|
const state = storage.create(name, options)
|
|
|
|
vueFlow = state
|
|
|
|
const vfScope = scope ?? effectScope(true)
|
|
vfScope.run(() => {
|
|
/**
|
|
* We have to watch the applyDefault option here,
|
|
* because we need to register the default hooks before the `VueFlow` component is actually mounted and props passed
|
|
* Otherwise calling `addNodes` while the component is not mounted will not trigger any changes unless change handlers are explicitly bound
|
|
*/
|
|
watch(
|
|
state.applyDefault,
|
|
(shouldApplyDefault, __, onCleanup) => {
|
|
const nodesChangeHandler = (changes: NodeChange[]) => {
|
|
state.applyNodeChanges(changes)
|
|
}
|
|
|
|
const edgesChangeHandler = (changes: EdgeChange[]) => {
|
|
state.applyEdgeChanges(changes)
|
|
}
|
|
|
|
if (shouldApplyDefault) {
|
|
state.onNodesChange(nodesChangeHandler)
|
|
state.onEdgesChange(edgesChangeHandler)
|
|
} else {
|
|
state.hooks.value.nodesChange.off(nodesChangeHandler)
|
|
state.hooks.value.edgesChange.off(edgesChangeHandler)
|
|
}
|
|
|
|
// Release handlers on cleanup
|
|
onCleanup(() => {
|
|
state.hooks.value.nodesChange.off(nodesChangeHandler)
|
|
state.hooks.value.edgesChange.off(edgesChangeHandler)
|
|
})
|
|
},
|
|
{ immediate: true },
|
|
)
|
|
|
|
// Destroy store instance on scope dispose
|
|
tryOnScopeDispose(() => {
|
|
if (vueFlow) {
|
|
const storedInstance = storage.get(vueFlow.id)
|
|
|
|
if (storedInstance) {
|
|
storedInstance.$destroy()
|
|
} else {
|
|
warn(`No store instance found for id ${vueFlow.id} in storage.`)
|
|
}
|
|
}
|
|
})
|
|
})
|
|
} else {
|
|
// If options were passed, overwrite state with the options' values
|
|
if (options) {
|
|
vueFlow.setState(options)
|
|
}
|
|
}
|
|
|
|
// Provide a fresh instance into context if we are in a scope
|
|
if (scope) {
|
|
provide(VueFlow, vueFlow)
|
|
|
|
scope.vueFlowId = vueFlow.id
|
|
}
|
|
|
|
if (options) {
|
|
warn('options are deprecated and will be removed in the next major version. Use props on the `<VueFlow>` component instead.')
|
|
}
|
|
|
|
return vueFlow
|
|
}
|