From c5b39d4588c273af2afef9c96a2a03e083d5f940 Mon Sep 17 00:00:00 2001 From: bcakmakoglu <78412429+bcakmakoglu@users.noreply.github.com> Date: Sun, 20 Feb 2022 11:03:43 +0100 Subject: [PATCH] feat: try to dispose states on unmount Signed-off-by: bcakmakoglu <78412429+bcakmakoglu@users.noreply.github.com> --- examples/Provider/Sidebar.vue | 8 +- examples/RGBFlow/RGBFlow.vue | 2 +- src/additional-components/MiniMap/MiniMap.vue | 2 +- src/composables/useHandle.ts | 5 + src/composables/useVueFlow.ts | 30 +- src/container/VueFlow/watch.ts | 384 +++++++++--------- src/store/actions.ts | 4 + src/store/state.ts | 2 +- src/store/store.ts | 25 +- src/types/store.ts | 2 +- 10 files changed, 240 insertions(+), 224 deletions(-) diff --git a/examples/Provider/Sidebar.vue b/examples/Provider/Sidebar.vue index cc22221f..d15e515a 100644 --- a/examples/Provider/Sidebar.vue +++ b/examples/Provider/Sidebar.vue @@ -6,12 +6,6 @@ const selectAll = () => { addSelectedNodes(getNodes.value) nodesSelectionActive.value = true } - -const transformString = computed(() => [ - transform.value[0].toFixed(2), - transform.value[1].toFixed(2), - transform.value[2].toFixed(2), -]) diff --git a/src/additional-components/MiniMap/MiniMap.vue b/src/additional-components/MiniMap/MiniMap.vue index 8764c351..b0066cc6 100644 --- a/src/additional-components/MiniMap/MiniMap.vue +++ b/src/additional-components/MiniMap/MiniMap.vue @@ -14,7 +14,7 @@ const props = withDefaults(defineProps(), { maskColor: 'rgb(240, 242, 243, 0.7)', }) -const attrs: any = useAttrs() +const attrs = useAttrs() const window = useWindow() const defaultWidth = 200 diff --git a/src/composables/useHandle.ts b/src/composables/useHandle.ts index 3a72531d..de49478b 100644 --- a/src/composables/useHandle.ts +++ b/src/composables/useHandle.ts @@ -166,4 +166,9 @@ export default (store: FlowStore = useVueFlow().store) => doc.addEventListener('mousemove', onMouseMove as EventListenerOrEventListenerObject) doc.addEventListener('mouseup', onMouseUp as EventListenerOrEventListenerObject) + + onScopeDispose(() => { + doc.removeEventListener('mousemove', onMouseMove as EventListenerOrEventListenerObject) + doc.removeEventListener('mouseup', onMouseUp as EventListenerOrEventListenerObject) + }) } diff --git a/src/composables/useVueFlow.ts b/src/composables/useVueFlow.ts index 989e1b81..afb0479b 100644 --- a/src/composables/useVueFlow.ts +++ b/src/composables/useVueFlow.ts @@ -1,17 +1,17 @@ -import { getCurrentInstance } from 'vue' -import { FlowOptions, UseVueFlow } from '~/types' +import { EffectScope } from 'vue' +import { FlowOptions, UseVueFlow, Store } from '~/types' import { VueFlow } from '~/context' import { useStore } from '~/store' let id = 0 + +type Scope = EffectScope & { vueFlow: UseVueFlow } export default (options?: Partial>): UseVueFlow => { - const currentInstance: any = getCurrentInstance() - let vueFlow: false | UseVueFlow = currentInstance - ? inject(VueFlow, undefined) ?? (currentInstance.vueFlow as UseVueFlow) - : false + const scope = getCurrentScope() as Scope + let vueFlow: UseVueFlow | null = scope ? inject(VueFlow, null) ?? (scope.vueFlow as UseVueFlow) : null if (!vueFlow || (vueFlow && options?.id && options.id !== vueFlow.id)) { const name = options?.id ?? `vue-flow-${id++}` - const store = useStore(options) + let store: Store = useStore(options) vueFlow = { id: name, store: reactive(store), @@ -20,11 +20,19 @@ export default (options?: Partial>): UseVueFlo ...store.actions, ...store.hooksOn, } as unknown as UseVueFlow + + if (scope) { + provide(VueFlow, vueFlow) + scope.vueFlow = vueFlow + } + + onScopeDispose(() => { + vueFlow = null as UseVueFlow + scope.vueFlow = null as UseVueFlow + store = null as Store + }) } - if (currentInstance) { - provide(VueFlow, vueFlow) - currentInstance.vueFlow = vueFlow - } + if (!vueFlow) throw new Error('VueFlow instance not found.') return >vueFlow } diff --git a/src/container/VueFlow/watch.ts b/src/container/VueFlow/watch.ts index 5f03d530..9cb964d8 100644 --- a/src/container/VueFlow/watch.ts +++ b/src/container/VueFlow/watch.ts @@ -15,198 +15,196 @@ export default ( props: FlowProps, store: FlowStore, ) => { - 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 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 = 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 = store.edges + await nextTick() + resume() + } + }) + } - 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) }, - ) - watch( - () => props.edgesUpdatable, - (v) => isDef(v) && (store.edgesUpdatable = v), - { immediate: isDef(props.edgesUpdatable) }, - ) - watch( - () => props.elementsSelectable, - (v) => isDef(v) && (store.elementsSelectable = v), - { immediate: isDef(props.elementsSelectable) }, - ) - watch( - () => props.nodesDraggable, - (v) => isDef(v) && (store.nodesDraggable = v), - { immediate: isDef(props.nodesDraggable) }, - ) - watch( - () => props.nodesConnectable, - (v) => isDef(v) && (store.nodesConnectable = v), - { immediate: isDef(props.nodesConnectable) }, - ) - watch( - () => props.onlyRenderVisibleElements, - (v) => isDef(v) && (store.onlyRenderVisibleElements = v), - { immediate: isDef(props.onlyRenderVisibleElements) }, - ) - watch( - () => props.snapToGrid, - (v) => isDef(v) && (store.snapToGrid = v), - { immediate: isDef(props.snapToGrid) }, - ) - watch( - () => props.snapGrid, - (v) => isDef(v) && (store.snapGrid = v), - { immediate: isDef(props.snapGrid) }, - ) - watch( - () => props.paneMoveable, - (v) => isDef(v) && (store.paneMoveable = v), - { immediate: isDef(props.paneMoveable) }, - ) - watch( - () => props.panOnScroll, - (v) => isDef(v) && (store.panOnScroll = v), - { immediate: isDef(props.panOnScroll) }, - ) - watch( - () => props.panOnScrollMode, - (v) => isDef(v) && (store.panOnScrollMode = v), - { immediate: isDef(props.panOnScrollMode) }, - ) - watch( - () => props.panOnScrollSpeed, - (v) => isDef(v) && (store.panOnScrollSpeed = v), - { immediate: isDef(props.panOnScrollSpeed) }, - ) - watch( - () => props.zoomOnPinch, - (v) => isDef(v) && (store.zoomOnPinch = v), - { immediate: isDef(props.zoomOnPinch) }, - ) - watch( - () => props.zoomOnDoubleClick, - (v) => isDef(v) && (store.zoomOnDoubleClick = v), - { immediate: isDef(props.zoomOnDoubleClick) }, - ) - watch( - () => props.zoomOnScroll, - (v) => isDef(v) && (store.zoomOnScroll = v), - { immediate: isDef(props.zoomOnScroll) }, - ) - watch( - () => props.deleteKeyCode, - (v) => isDef(v) && (store.deleteKeyCode = v), - { immediate: isDef(props.deleteKeyCode) }, - ) - watch( - () => props.zoomActivationKeyCode, - (v) => isDef(v) && (store.zoomActivationKeyCode = v), - { immediate: isDef(props.zoomActivationKeyCode) }, - ) - watch( - () => props.selectionKeyCode, - (v) => isDef(v) && (store.selectionKeyCode = v), - { immediate: isDef(props.selectionKeyCode) }, - ) - watch( - () => props.multiSelectionKeyCode, - (v) => isDef(v) && (store.multiSelectionKeyCode = v), - { immediate: isDef(props.multiSelectionKeyCode) }, - ) - watch( - () => props.connectionLineStyle, - (v) => isDef(v) && (store.connectionLineStyle = v), - { immediate: isDef(props.connectionLineStyle) }, - ) - watch( - () => props.connectionMode, - (v) => isDef(v) && (store.connectionMode = v), - { immediate: isDef(props.connectionMode) }, - ) - watch( - () => props.connectionLineType, - (v) => isDef(v) && (store.connectionLineType = v), - { immediate: isDef(props.connectionLineType) }, - ) - watch( - () => props.defaultMarkerColor, - (v) => isDef(v) && (store.defaultMarkerColor = v), - { immediate: isDef(props.defaultMarkerColor) }, - ) - watch( - () => props.defaultPosition, - (v) => isDef(v) && (store.defaultPosition = v), - { immediate: isDef(props.defaultPosition) }, - ) - watch( - () => props.defaultZoom, - (v) => isDef(v) && (store.defaultZoom = v), - { immediate: isDef(props.defaultZoom) }, - ) - watch( - () => props.translateExtent, - (v) => isDef(v) && (store.translateExtent = v), - { immediate: isDef(props.translateExtent) }, - ) - watch( - () => props.nodeExtent, - (v) => isDef(v) && (store.nodeExtent = v), - { immediate: isDef(props.nodeExtent) }, - ) - watch( - () => props.selectNodesOnDrag, - (v) => isDef(v) && (store.selectNodesOnDrag = v), - { immediate: isDef(props.selectNodesOnDrag) }, - ) - watch( - () => props.edgeUpdaterRadius, - (v) => isDef(v) && (store.edgeUpdaterRadius = v), - { immediate: isDef(props.edgeUpdaterRadius) }, - ) - watch( - () => props.applyDefault, - (v) => isDef(v) && (store.applyDefault = v), - { immediate: isDef(props.applyDefault) }, - ) - watch( - () => props.fitViewOnInit, - (v) => isDef(v) && (store.fitViewOnInit = v), - { immediate: isDef(props.fitViewOnInit) }, - ) + 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) }, + ) + watch( + () => props.edgesUpdatable, + (v) => isDef(v) && (store.edgesUpdatable = v), + { immediate: isDef(props.edgesUpdatable) }, + ) + watch( + () => props.elementsSelectable, + (v) => isDef(v) && (store.elementsSelectable = v), + { immediate: isDef(props.elementsSelectable) }, + ) + watch( + () => props.nodesDraggable, + (v) => isDef(v) && (store.nodesDraggable = v), + { immediate: isDef(props.nodesDraggable) }, + ) + watch( + () => props.nodesConnectable, + (v) => isDef(v) && (store.nodesConnectable = v), + { immediate: isDef(props.nodesConnectable) }, + ) + watch( + () => props.onlyRenderVisibleElements, + (v) => isDef(v) && (store.onlyRenderVisibleElements = v), + { immediate: isDef(props.onlyRenderVisibleElements) }, + ) + watch( + () => props.snapToGrid, + (v) => isDef(v) && (store.snapToGrid = v), + { immediate: isDef(props.snapToGrid) }, + ) + watch( + () => props.snapGrid, + (v) => isDef(v) && (store.snapGrid = v), + { immediate: isDef(props.snapGrid) }, + ) + watch( + () => props.paneMoveable, + (v) => isDef(v) && (store.paneMoveable = v), + { immediate: isDef(props.paneMoveable) }, + ) + watch( + () => props.panOnScroll, + (v) => isDef(v) && (store.panOnScroll = v), + { immediate: isDef(props.panOnScroll) }, + ) + watch( + () => props.panOnScrollMode, + (v) => isDef(v) && (store.panOnScrollMode = v), + { immediate: isDef(props.panOnScrollMode) }, + ) + watch( + () => props.panOnScrollSpeed, + (v) => isDef(v) && (store.panOnScrollSpeed = v), + { immediate: isDef(props.panOnScrollSpeed) }, + ) + watch( + () => props.zoomOnPinch, + (v) => isDef(v) && (store.zoomOnPinch = v), + { immediate: isDef(props.zoomOnPinch) }, + ) + watch( + () => props.zoomOnDoubleClick, + (v) => isDef(v) && (store.zoomOnDoubleClick = v), + { immediate: isDef(props.zoomOnDoubleClick) }, + ) + watch( + () => props.zoomOnScroll, + (v) => isDef(v) && (store.zoomOnScroll = v), + { immediate: isDef(props.zoomOnScroll) }, + ) + watch( + () => props.deleteKeyCode, + (v) => isDef(v) && (store.deleteKeyCode = v), + { immediate: isDef(props.deleteKeyCode) }, + ) + watch( + () => props.zoomActivationKeyCode, + (v) => isDef(v) && (store.zoomActivationKeyCode = v), + { immediate: isDef(props.zoomActivationKeyCode) }, + ) + watch( + () => props.selectionKeyCode, + (v) => isDef(v) && (store.selectionKeyCode = v), + { immediate: isDef(props.selectionKeyCode) }, + ) + watch( + () => props.multiSelectionKeyCode, + (v) => isDef(v) && (store.multiSelectionKeyCode = v), + { immediate: isDef(props.multiSelectionKeyCode) }, + ) + watch( + () => props.connectionLineStyle, + (v) => isDef(v) && (store.connectionLineStyle = v), + { immediate: isDef(props.connectionLineStyle) }, + ) + watch( + () => props.connectionMode, + (v) => isDef(v) && (store.connectionMode = v), + { immediate: isDef(props.connectionMode) }, + ) + watch( + () => props.connectionLineType, + (v) => isDef(v) && (store.connectionLineType = v), + { immediate: isDef(props.connectionLineType) }, + ) + watch( + () => props.defaultMarkerColor, + (v) => isDef(v) && (store.defaultMarkerColor = v), + { immediate: isDef(props.defaultMarkerColor) }, + ) + watch( + () => props.defaultPosition, + (v) => isDef(v) && (store.defaultPosition = v), + { immediate: isDef(props.defaultPosition) }, + ) + watch( + () => props.defaultZoom, + (v) => isDef(v) && (store.defaultZoom = v), + { immediate: isDef(props.defaultZoom) }, + ) + watch( + () => props.translateExtent, + (v) => isDef(v) && (store.translateExtent = v), + { immediate: isDef(props.translateExtent) }, + ) + watch( + () => props.nodeExtent, + (v) => isDef(v) && (store.nodeExtent = v), + { immediate: isDef(props.nodeExtent) }, + ) + watch( + () => props.selectNodesOnDrag, + (v) => isDef(v) && (store.selectNodesOnDrag = v), + { immediate: isDef(props.selectNodesOnDrag) }, + ) + watch( + () => props.edgeUpdaterRadius, + (v) => isDef(v) && (store.edgeUpdaterRadius = v), + { immediate: isDef(props.edgeUpdaterRadius) }, + ) + watch( + () => props.applyDefault, + (v) => isDef(v) && (store.applyDefault = v), + { immediate: isDef(props.applyDefault) }, + ) + }) } diff --git a/src/store/actions.ts b/src/store/actions.ts index 092b43c2..ad15482d 100644 --- a/src/store/actions.ts +++ b/src/store/actions.ts @@ -1,3 +1,4 @@ +import useState from './state' import { CoordinateExtent, EdgeChange, @@ -304,5 +305,8 @@ export default (state: State, getters: ComputedGetters): Actions => { setConnectionNodeId, setInteractive, setState, + $reset: () => { + setState(useState()) + }, } } diff --git a/src/store/state.ts b/src/store/state.ts index 6ccceda5..e7608979 100644 --- a/src/store/state.ts +++ b/src/store/state.ts @@ -97,7 +97,7 @@ export default (opts?: FlowOptions): State => { applyDefault: true, vueFlowVersion: typeof __VUE_FLOW_VERSION__ !== 'undefined' ? __VUE_FLOW_VERSION__ : '-', - } + } as State if (opts) { if (typeof opts.panOnScroll !== 'undefined') state.panOnScroll = opts.panOnScroll diff --git a/src/store/store.ts b/src/store/store.ts index 3ed329a5..99d70dd9 100644 --- a/src/store/store.ts +++ b/src/store/store.ts @@ -1,31 +1,38 @@ import useState from './state' import useActions from './actions' import useGetters from './getters' -import { FlowHooksOn, FlowOptions, Store } from '~/types' +import { FlowHooksOn, FlowOptions, Store, State } from '~/types' export default (preloadedState?: FlowOptions): Store => { - const state = reactive(useState(preloadedState)) - const getters = useGetters(state) - const actions = useActions(state, getters) + const state: State = useState(preloadedState) + const reactiveState = reactive(state) + const getters = useGetters(reactiveState) + const actions = useActions(reactiveState, getters) const hooksOn: FlowHooksOn = {} - Object.entries(state.hooks).forEach(([n, h]) => { + Object.entries(reactiveState.hooks).forEach(([n, h]) => { const name = `on${n.charAt(0).toUpperCase() + n.slice(1)}` hooksOn[name] = h.on as any }) - actions.setState(state) + actions.setState(reactiveState) if (preloadedState) { if (preloadedState.modelValue) actions.setElements(preloadedState.modelValue) if (preloadedState.nodes) actions.setNodes(preloadedState.nodes) if (preloadedState.edges) actions.setEdges(preloadedState.edges) } - return { - state, + const store = { + state: reactiveState, actions, getters, hooksOn, - ...toRefs(state), + ...toRefs(reactiveState), ...getters, ...actions, } as unknown as Store + + onScopeDispose(() => { + store.$reset() + }) + + return store } diff --git a/src/types/store.ts b/src/types/store.ts index 2ee8bdab..ad63deb4 100644 --- a/src/types/store.ts +++ b/src/types/store.ts @@ -90,7 +90,7 @@ export interface Actions { setInteractive: (isInteractive: boolean) => void setState: (state: Partial>) => void updateNodePosition: ({ id, diff, dragging }: { id?: string; diff?: XYPosition; dragging?: boolean }) => void - $destroy: () => void + $reset: () => void } export interface Getters {