From 7df2bfa146a0d9aa8f6f855041f378fb2fca4a06 Mon Sep 17 00:00:00 2001 From: braks <78412429+bcakmakoglu@users.noreply.github.com> Date: Wed, 19 Jul 2023 18:48:12 +0200 Subject: [PATCH] feat(core): resolve promise from viewport actions on transition end --- examples/vite/src/Basic/BasicOptionsAPI.vue | 1 + packages/core/src/composables/useViewport.ts | 108 ++++++++++++------- packages/core/src/types/zoom.ts | 12 +-- 3 files changed, 77 insertions(+), 44 deletions(-) diff --git a/examples/vite/src/Basic/BasicOptionsAPI.vue b/examples/vite/src/Basic/BasicOptionsAPI.vue index c789b516..6c64139b 100644 --- a/examples/vite/src/Basic/BasicOptionsAPI.vue +++ b/examples/vite/src/Basic/BasicOptionsAPI.vue @@ -64,6 +64,7 @@ export default defineComponent({ :min-zoom="0.2" :max-zoom="4" :zoom-on-scroll="false" + fit-view-on-init @connect="onConnect" @pane-ready="onPaneReady" @node-drag-stop="onNodeDragStop" diff --git a/packages/core/src/composables/useViewport.ts b/packages/core/src/composables/useViewport.ts index 283abe58..4235fadc 100644 --- a/packages/core/src/composables/useViewport.ts +++ b/packages/core/src/composables/useViewport.ts @@ -1,5 +1,5 @@ import { zoomIdentity } from 'd3-zoom' -import { computed, ref } from 'vue' +import { computed } from 'vue' import type { ComputedGetters, D3Selection, GraphNode, State, ViewportFunctions } from '~/types' import { clampPosition, getRectOfNodes, getTransformForBounds, pointToRendererPoint } from '~/utils' @@ -9,7 +9,9 @@ interface ExtendedViewport extends ViewportFunctions { const DEFAULT_PADDING = 0.1 -function noop() {} +function noop() { + return Promise.resolve(false) +} const initialViewportHelper: ExtendedViewport = { zoomIn: noop, @@ -25,59 +27,89 @@ const initialViewportHelper: ExtendedViewport = { } export function useViewport(state: State, getters: ComputedGetters) { - const { nodes, d3Zoom, d3Selection, dimensions, translateExtent, minZoom, maxZoom, viewport, snapToGrid, snapGrid, hooks } = - $(state) + const { nodes, d3Zoom, d3Selection, dimensions, translateExtent, minZoom, maxZoom, viewport, snapToGrid, snapGrid } = $(state) - const { getNodes } = getters + const { getNodes, getNodesInitialized } = getters - const nodesInitialized = ref(false) - - hooks.nodesInitialized.on(() => { - nodesInitialized.value = true - }) - - const isReady = computed(() => !!d3Zoom && !!d3Selection && !!dimensions.width && !!dimensions.height && nodesInitialized.value) + const isReady = computed( + () => + !!d3Zoom && + !!d3Selection && + !!dimensions.width && + !!dimensions.height && + getNodesInitialized.value.length === getNodes.value.length, + ) function zoom(scale: number, duration?: number) { - if (d3Selection && d3Zoom) { - d3Zoom.scaleBy(transition(d3Selection, duration), scale) - } + return new Promise((resolve) => { + if (d3Selection && d3Zoom) { + d3Zoom.scaleBy( + transition(d3Selection, duration, () => { + resolve(true) + }), + scale, + ) + } else { + resolve(false) + } + }) } function transformViewport(x: number, y: number, zoom: number, duration?: number) { - // enforce translate extent - const { x: clampedX, y: clampedY } = clampPosition({ x: -x, y: -y }, translateExtent) + return new Promise((resolve) => { + // enforce translate extent + const { x: clampedX, y: clampedY } = clampPosition({ x: -x, y: -y }, translateExtent) - const nextTransform = zoomIdentity.translate(-clampedX, -clampedY).scale(zoom) + const nextTransform = zoomIdentity.translate(-clampedX, -clampedY).scale(zoom) - if (d3Selection && d3Zoom) { - d3Zoom.transform(transition(d3Selection, duration), nextTransform) - } + if (d3Selection && d3Zoom) { + d3Zoom.transform( + transition(d3Selection, duration, () => { + resolve(true) + }), + nextTransform, + ) + } else { + resolve(false) + } + }) } return computed(() => { if (isReady.value) { return { initialized: true, + // todo: allow passing scale as option zoomIn: (options) => { - zoom(1.2, options?.duration) + return zoom(1.2, options?.duration) }, zoomOut: (options) => { - zoom(1 / 1.2, options?.duration) + return zoom(1 / 1.2, options?.duration) }, zoomTo: (zoomLevel, options) => { - if (d3Selection && d3Zoom) { - d3Zoom.scaleTo(transition(d3Selection, options?.duration), zoomLevel) - } + return new Promise((resolve) => { + if (d3Selection && d3Zoom) { + d3Zoom.scaleTo( + transition(d3Selection, options?.duration, () => { + resolve(true) + }), + zoomLevel, + ) + } else { + resolve(false) + } + }) }, setTransform: (transform, options) => { - transformViewport(transform.x, transform.y, transform.zoom, options?.duration) + return transformViewport(transform.x, transform.y, transform.zoom, options?.duration) + }, + getTransform: () => { + return { + x: viewport.x, + y: viewport.y, + zoom: viewport.zoom, + } }, - getTransform: () => ({ - x: viewport.x, - y: viewport.y, - zoom: viewport.zoom, - }), fitView: ( options = { padding: DEFAULT_PADDING, @@ -97,7 +129,7 @@ export function useViewport(state: State, getters: ComputedGetters) { }) if (!nodesToFit.length) { - return + return Promise.resolve(false) } const bounds = getRectOfNodes(nodesToFit) @@ -112,14 +144,14 @@ export function useViewport(state: State, getters: ComputedGetters) { options.offset, ) - transformViewport(x, y, zoom, options?.duration) + return transformViewport(x, y, zoom, options?.duration) }, setCenter: (x, y, options) => { const nextZoom = typeof options?.zoom !== 'undefined' ? options.zoom : maxZoom const centerX = dimensions.width / 2 - x * nextZoom const centerY = dimensions.height / 2 - y * nextZoom - transformViewport(centerX, centerY, nextZoom, options?.duration) + return transformViewport(centerX, centerY, nextZoom, options?.duration) }, fitBounds: (bounds, options = { padding: DEFAULT_PADDING }) => { const { x, y, zoom } = getTransformForBounds( @@ -131,7 +163,7 @@ export function useViewport(state: State, getters: ComputedGetters) { options.padding, ) - transformViewport(x, y, zoom, options?.duration) + return transformViewport(x, y, zoom, options?.duration) }, project: (position) => pointToRendererPoint(position, viewport, snapToGrid, snapGrid), } @@ -141,6 +173,6 @@ export function useViewport(state: State, getters: ComputedGetters) { }) } -function transition(selection: D3Selection, ms = 0) { - return selection.transition().duration(ms) +function transition(selection: D3Selection, ms = 0, onEnd: () => void) { + return selection.transition().duration(ms).on('end', onEnd) } diff --git a/packages/core/src/types/zoom.ts b/packages/core/src/types/zoom.ts index 8d09c9fc..c4a3e3f1 100644 --- a/packages/core/src/types/zoom.ts +++ b/packages/core/src/types/zoom.ts @@ -42,28 +42,28 @@ export type FitBoundsOptions = TransitionOptions & { } /** Fit the viewport around visible nodes */ -export type FitView = (fitViewOptions?: FitViewParams) => void +export type FitView = (fitViewOptions?: FitViewParams) => Promise /** project a position onto the viewport, i.e. a mouse event clientX/clientY onto graph coordinates */ export type Project = (position: XYPosition) => XYPosition /** set center of viewport */ -export type SetCenter = (x: number, y: number, options?: SetCenterOptions) => void +export type SetCenter = (x: number, y: number, options?: SetCenterOptions) => Promise /** fit the viewport around bounds */ -export type FitBounds = (bounds: Rect, options?: FitBoundsOptions) => void +export type FitBounds = (bounds: Rect, options?: FitBoundsOptions) => Promise /** zoom in/out */ -export type ZoomInOut = (options?: TransitionOptions) => void +export type ZoomInOut = (options?: TransitionOptions) => Promise /** zoom to a specific level */ -export type ZoomTo = (zoomLevel: number, options?: TransitionOptions) => void +export type ZoomTo = (zoomLevel: number, options?: TransitionOptions) => Promise /** get current viewport transform */ export type GetTransform = () => ViewportTransform /** set current viewport transform */ -export type SetTransform = (transform: ViewportTransform, options?: TransitionOptions) => void +export type SetTransform = (transform: ViewportTransform, options?: TransitionOptions) => Promise export interface ViewportFunctions { zoomIn: ZoomInOut