update: Add effect scope to useZoomPanHelper.ts

* Easier cleanup after zoompanhelper has been initialized
This commit is contained in:
Braks
2021-08-17 19:45:26 +02:00
parent 8d8a4a3d1e
commit b8ce50ae3e
+63 -57
View File
@@ -1,5 +1,5 @@
import { zoomIdentity } from 'd3-zoom'; import { zoomIdentity } from 'd3-zoom';
import { computed, inject } from 'vue'; import { computed, effectScope, inject } from 'vue';
import { createEventHook, EventHookOn, until } from '@vueuse/core'; import { createEventHook, EventHookOn, until } from '@vueuse/core';
import { Selection, ZoomBehavior } from 'd3'; import { Selection, ZoomBehavior } from 'd3';
import { getRectOfNodes, pointToRendererPoint, getTransformForBounds } from '../utils/graph'; import { getRectOfNodes, pointToRendererPoint, getTransformForBounds } from '../utils/graph';
@@ -10,73 +10,79 @@ const DEFAULT_PADDING = 0.1;
const useZoomPanHelper = (): { onReady: EventHookOn<ZoomPanHelperFunctions> } => { const useZoomPanHelper = (): { onReady: EventHookOn<ZoomPanHelperFunctions> } => {
const zoomPanHelperEvent = createEventHook<ZoomPanHelperFunctions>(); const zoomPanHelperEvent = createEventHook<ZoomPanHelperFunctions>();
const store = inject<RevueFlowStore>('store')!; const store = inject<RevueFlowStore>('store')!;
const d3Zoom = computed(() => store.d3Zoom);
const d3Selection = computed(() => store.d3Selection);
const createZoomPanHelper = ( const scope = effectScope();
d3Zoom: ZoomBehavior<Element, unknown>, scope.run(() => {
d3Selection: Selection<Element, any, any, any> const d3Zoom = computed(() => store.d3Zoom);
): ZoomPanHelperFunctions => ({ const d3Selection = computed(() => store.d3Selection);
zoomIn: () => d3Zoom?.scaleBy(d3Selection, 1.2),
zoomOut: () => d3Zoom?.scaleBy(d3Selection, 1 / 1.2),
zoomTo: (zoomLevel: number) => d3Zoom.scaleTo(d3Selection, zoomLevel),
transform: (transform: FlowTransform) => {
const nextTransform = zoomIdentity.translate(transform.x, transform.y).scale(transform.zoom);
d3Zoom.transform(d3Selection, nextTransform); const createZoomPanHelper = (
}, d3Zoom: ZoomBehavior<Element, unknown>,
fitView: (options: FitViewParams = { padding: DEFAULT_PADDING, includeHiddenNodes: false }) => { d3Selection: Selection<Element, any, any, any>
const { nodes, width, height, minZoom, maxZoom } = store; ): ZoomPanHelperFunctions => ({
zoomIn: () => d3Zoom?.scaleBy(d3Selection, 1.2),
zoomOut: () => d3Zoom?.scaleBy(d3Selection, 1 / 1.2),
zoomTo: (zoomLevel: number) => d3Zoom.scaleTo(d3Selection, zoomLevel),
transform: (transform: FlowTransform) => {
const nextTransform = zoomIdentity.translate(transform.x, transform.y).scale(transform.zoom);
if (!nodes.length) { d3Zoom.transform(d3Selection, nextTransform);
return; },
} fitView: (options: FitViewParams = { padding: DEFAULT_PADDING, includeHiddenNodes: false }) => {
const { nodes, width, height, minZoom, maxZoom } = store;
const bounds = getRectOfNodes(options.includeHiddenNodes ? nodes : nodes.filter((node) => !node.isHidden)); if (!nodes.length) {
const [x, y, zoom] = getTransformForBounds( return;
bounds, }
width,
height,
options.minZoom || minZoom,
options.maxZoom || maxZoom,
options.padding || DEFAULT_PADDING
);
const transform = zoomIdentity.translate(x, y).scale(zoom);
d3Zoom?.transform(d3Selection, transform); const bounds = getRectOfNodes(options.includeHiddenNodes ? nodes : nodes.filter((node) => !node.isHidden));
}, const [x, y, zoom] = getTransformForBounds(
setCenter: (x: number, y: number, zoom?: number) => { bounds,
const { width, height, maxZoom } = store; width,
height,
options.minZoom || minZoom,
options.maxZoom || maxZoom,
options.padding || DEFAULT_PADDING
);
const transform = zoomIdentity.translate(x, y).scale(zoom);
const nextZoom = typeof zoom !== 'undefined' ? zoom : maxZoom; d3Zoom?.transform(d3Selection, transform);
const centerX = width / 2 - x * nextZoom; },
const centerY = height / 2 - y * nextZoom; setCenter: (x: number, y: number, zoom?: number) => {
const transform = zoomIdentity.translate(centerX, centerY).scale(nextZoom); const { width, height, maxZoom } = store;
d3Zoom.transform(d3Selection, transform); const nextZoom = typeof zoom !== 'undefined' ? zoom : maxZoom;
}, const centerX = width / 2 - x * nextZoom;
fitBounds: (bounds: Rect, padding = DEFAULT_PADDING) => { const centerY = height / 2 - y * nextZoom;
const { width, height, minZoom, maxZoom } = store; const transform = zoomIdentity.translate(centerX, centerY).scale(nextZoom);
const [x, y, zoom] = getTransformForBounds(bounds, width, height, minZoom, maxZoom, padding);
const transform = zoomIdentity.translate(x, y).scale(zoom);
d3Zoom.transform(d3Selection, transform); d3Zoom.transform(d3Selection, transform);
}, },
project: (position: XYPosition) => { fitBounds: (bounds: Rect, padding = DEFAULT_PADDING) => {
const { transform, snapToGrid, snapGrid } = store; const { width, height, minZoom, maxZoom } = store;
return pointToRendererPoint(position, transform, snapToGrid, snapGrid); const [x, y, zoom] = getTransformForBounds(bounds, width, height, minZoom, maxZoom, padding);
} const transform = zoomIdentity.translate(x, y).scale(zoom);
});
until(d3Zoom) d3Zoom.transform(d3Selection, transform);
.toBeTruthy() },
.then(() => { project: (position: XYPosition) => {
if (d3Zoom.value && d3Selection.value) { const { transform, snapToGrid, snapGrid } = store;
zoomPanHelperEvent.trigger(createZoomPanHelper(d3Zoom.value, d3Selection.value)); return pointToRendererPoint(position, transform, snapToGrid, snapGrid);
} else {
throw Error('D3 could not be initialized.');
} }
}); });
until(d3Zoom)
.toBeTruthy()
.then(() => {
if (d3Zoom.value && d3Selection.value) {
zoomPanHelperEvent.trigger(createZoomPanHelper(d3Zoom.value, d3Selection.value));
} else {
throw Error('D3 could not be initialized.');
}
scope.stop();
});
});
return { return {
onReady: zoomPanHelperEvent.on onReady: zoomPanHelperEvent.on
}; };