From c0c67c6ee6371ef31513c8ca400914cbe8b92ec4 Mon Sep 17 00:00:00 2001 From: Braks <78412429+bcakmakoglu@users.noreply.github.com> Date: Sun, 10 Apr 2022 16:35:21 +0200 Subject: [PATCH] fix(viewport): await dimensions before using zoompan utils --- package/src/composables/index.ts | 2 +- package/src/composables/useZoomPanHelper.ts | 184 ++++++++++++------- package/src/container/Viewport/Transform.vue | 24 +-- 3 files changed, 125 insertions(+), 85 deletions(-) diff --git a/package/src/composables/index.ts b/package/src/composables/index.ts index 4ff5c57e..2e8ddf10 100644 --- a/package/src/composables/index.ts +++ b/package/src/composables/index.ts @@ -1,5 +1,5 @@ export { default as useHandle } from './useHandle' export { default as useKeyPress } from './useKeyPress' -export { default as useZoomPanHelper } from './useZoomPanHelper' +export { default as useZoomPanHelper, untilDimensions } from './useZoomPanHelper' export { default as useWindow } from './useWindow' export { default as useVueFlow } from './useVueFlow' diff --git a/package/src/composables/useZoomPanHelper.ts b/package/src/composables/useZoomPanHelper.ts index d47e586d..eece81ac 100644 --- a/package/src/composables/useZoomPanHelper.ts +++ b/package/src/composables/useZoomPanHelper.ts @@ -1,5 +1,6 @@ import { zoomIdentity } from 'd3-zoom' import useVueFlow from './useVueFlow' +import useWindow from './useWindow' import { getRectOfNodes, pointToRendererPoint, getTransformForBounds } from '~/utils' import { GraphNode, Store, ViewportFuncs, D3Selection } from '~/types' @@ -7,72 +8,131 @@ const DEFAULT_PADDING = 0.1 const transition = (selection: D3Selection, ms = 0) => selection.transition().duration(ms) -export default (store: Store = useVueFlow().store): ViewportFuncs => ({ - zoomIn: (options) => store.d3Selection && store.d3Zoom?.scaleBy(transition(store.d3Selection, options?.duration), 1.2), - zoomOut: (options) => store.d3Selection && store.d3Zoom?.scaleBy(transition(store.d3Selection, options?.duration), 1 / 1.2), - zoomTo: (zoomLevel, options) => - store.d3Selection && store.d3Zoom?.scaleTo(transition(store.d3Selection, options?.duration), zoomLevel), - setTransform: (transform, options) => { - const nextTransform = zoomIdentity.translate(transform.x, transform.y).scale(transform.zoom) - store.d3Selection && store.d3Zoom?.transform(transition(store.d3Selection, options?.duration), nextTransform) - }, - getTransform: () => ({ - x: store.viewport.x, - y: store.viewport.y, - zoom: store.viewport.zoom, - }), - fitView: ( - options = { - padding: DEFAULT_PADDING, - includeHiddenNodes: false, - duration: 0, +export const untilDimensions = async (store: Store) => { + // if ssr we can't wait for dimensions, they'll never really exist + const window = useWindow() + if ('screen' in window) { + // wait until viewport dimensions has been established + await until(store.dimensions).toMatch(({ height, width }) => !isNaN(width) && width > 0 && !isNaN(height) && height > 0) + + // if initial nodes are present, wait until the node dimensions have been established + if (store.getNodes.length > 0) { + await until(store.getNodes).toMatch( + (nodes) => + !!nodes.filter(({ dimensions: { width, height } }) => !isNaN(width) && width > 0 && !isNaN(height) && height > 0) + .length, + ) + } + } + + return true +} + +export default (store: Store = useVueFlow().store): ViewportFuncs => { + const hasDimensions = ref(false) + store.hooks.paneReady.on(() => (hasDimensions.value = true)) + + const zoomTo: ViewportFuncs['zoomTo'] = async (zoomLevel, options) => { + if (!hasDimensions.value) await untilDimensions(store) + + if (store.d3Selection && store.d3Zoom) { + store.d3Zoom.scaleTo(transition(store.d3Selection, options?.duration), zoomLevel) + } + } + + const zoom = async (scale: number, duration?: number) => { + if (!hasDimensions.value) await untilDimensions(store) + + if (store.d3Selection && store.d3Zoom) { + store.d3Zoom.scaleTo(transition(store.d3Selection, duration), scale) + } + } + + const zoomIn: ViewportFuncs['zoomIn'] = async (options) => { + await zoom(1.2, options?.duration) + } + + const zoomOut: ViewportFuncs['zoomOut'] = async (options) => { + await zoom(1 / 1.2, options?.duration) + } + + const transformViewport = (x: number, y: number, zoom: number, duration?: number) => { + const nextTransform = zoomIdentity.translate(x, y).scale(zoom) + if (store.d3Selection && store.d3Zoom) { + store.d3Zoom.transform(transition(store.d3Selection, duration), nextTransform) + } + } + + return { + zoomIn, + zoomOut, + zoomTo, + setTransform: async (transform, options) => { + if (!hasDimensions.value) await untilDimensions(store) + transformViewport(transform.x, transform.y, transform.zoom, options?.duration) }, - ) => { - if (!store.getNodes.length) return + getTransform: () => ({ + x: store.viewport.x, + y: store.viewport.y, + zoom: store.viewport.zoom, + }), + fitView: async ( + options = { + padding: DEFAULT_PADDING, + includeHiddenNodes: false, + duration: 0, + }, + ) => { + if (!hasDimensions.value) await untilDimensions(store) - let nodes: GraphNode[] = [] - if (options.nodes) { - nodes = store.nodes.filter((n) => options.nodes?.includes(n.id)) - } - if (!nodes || !nodes.length) { - nodes = options.includeHiddenNodes ? store.nodes : store.getNodes - } + if (!store.getNodes.length) return - const bounds = getRectOfNodes(nodes) - const { x, y, zoom } = getTransformForBounds( - bounds, - store.dimensions.width, - store.dimensions.height, - options.minZoom ?? store.minZoom, - options.maxZoom ?? store.maxZoom, - options.padding ?? DEFAULT_PADDING, - options.offset, - ) + let nodes: GraphNode[] = [] + if (options.nodes) { + nodes = store.nodes.filter((n) => options.nodes?.includes(n.id)) + } - const transform = zoomIdentity.translate(x, y).scale(zoom) + if (!nodes || !nodes.length) { + nodes = options.includeHiddenNodes ? store.nodes : store.getNodes + } - store.d3Selection && store.d3Zoom?.transform(transition(store.d3Selection, options?.duration), transform) - }, - setCenter: (x, y, options) => { - const nextZoom = typeof options?.zoom !== 'undefined' ? options.zoom : store.maxZoom - const centerX = store.dimensions.width / 2 - x * nextZoom - const centerY = store.dimensions.height / 2 - y * nextZoom - const transform = zoomIdentity.translate(centerX, centerY).scale(nextZoom) + const bounds = getRectOfNodes(nodes) - store.d3Selection && store.d3Zoom?.transform(transition(store.d3Selection, options?.duration), transform) - }, - fitBounds: (bounds, options = { padding: DEFAULT_PADDING }) => { - const { x, y, zoom } = getTransformForBounds( - bounds, - store.dimensions.width, - store.dimensions.height, - store.minZoom, - store.maxZoom, - options.padding, - ) - const transform = zoomIdentity.translate(x, y).scale(zoom) + const { x, y, zoom } = getTransformForBounds( + bounds, + store.dimensions.width, + store.dimensions.height, + options.minZoom ?? store.minZoom, + options.maxZoom ?? store.maxZoom, + options.padding ?? DEFAULT_PADDING, + options.offset, + ) - store.d3Selection && store.d3Zoom?.transform(transition(store.d3Selection, options.duration), transform) - }, - project: (position) => pointToRendererPoint(position, store.viewport, store.snapToGrid, store.snapGrid), -}) + transformViewport(x, y, zoom, options?.duration) + }, + setCenter: async (x, y, options) => { + if (!hasDimensions.value) await untilDimensions(store) + + const nextZoom = typeof options?.zoom !== 'undefined' ? options.zoom : store.maxZoom + const centerX = store.dimensions.width / 2 - x * nextZoom + const centerY = store.dimensions.height / 2 - y * nextZoom + + transformViewport(centerX, centerY, nextZoom, options?.duration) + }, + fitBounds: async (bounds, options = { padding: DEFAULT_PADDING }) => { + if (!hasDimensions.value) await untilDimensions(store) + + const { x, y, zoom } = getTransformForBounds( + bounds, + store.dimensions.width, + store.dimensions.height, + store.minZoom, + store.maxZoom, + options.padding, + ) + + transformViewport(x, y, zoom, options?.duration) + }, + project: (position) => pointToRendererPoint(position, store.viewport, store.snapToGrid, store.snapGrid), + } +} diff --git a/package/src/container/Viewport/Transform.vue b/package/src/container/Viewport/Transform.vue index fa0af717..a6adc184 100644 --- a/package/src/container/Viewport/Transform.vue +++ b/package/src/container/Viewport/Transform.vue @@ -1,32 +1,12 @@