diff --git a/app/gui2/e2e/componentBrowser.spec.ts b/app/gui2/e2e/componentBrowser.spec.ts index 256262023807..39181f29f2a7 100644 --- a/app/gui2/e2e/componentBrowser.spec.ts +++ b/app/gui2/e2e/componentBrowser.spec.ts @@ -86,7 +86,7 @@ test('Graph Editor pans to Component Browser', async ({ page }) => { await locate.graphNodeByBinding(page, 'final').click() await page.mouse.move(100, 80) await page.mouse.down({ button: 'middle' }) - await page.mouse.move(100, 700) + await page.mouse.move(100, 1200) await page.mouse.up({ button: 'middle' }) await expect(locate.graphNodeByBinding(page, 'final')).not.toBeInViewport() await locate.graphEditor(page).press('Enter') diff --git a/app/gui2/e2e/edgeRendering.spec.ts b/app/gui2/e2e/edgeRendering.spec.ts index 761f4a775f98..059f40332045 100644 --- a/app/gui2/e2e/edgeRendering.spec.ts +++ b/app/gui2/e2e/edgeRendering.spec.ts @@ -1,4 +1,4 @@ -import { expect, Page, test } from '@playwright/test' +import { expect, test, type Page } from '@playwright/test' import * as actions from './actions' import { edgesFromNodeWithBinding, edgesToNodeWithBinding } from './locate' diff --git a/app/gui2/src/components/GraphEditor.vue b/app/gui2/src/components/GraphEditor.vue index ed7ad4ea3540..a9b1e3ddb051 100644 --- a/app/gui2/src/components/GraphEditor.vue +++ b/app/gui2/src/components/GraphEditor.vue @@ -55,7 +55,7 @@ import { Rect } from '@/util/data/rect' import { Vec2 } from '@/util/data/vec2' import { encoding, set } from 'lib0' import { encodeMethodPointer } from 'shared/languageServerTypes' -import { computed, onMounted, ref, shallowRef, toRef, watch } from 'vue' +import { computed, onMounted, ref, shallowRef, toRef, watch, watchEffect } from 'vue' const keyboard = provideKeyboard() const graphStore = useGraphStore() @@ -69,14 +69,32 @@ const suggestionDb = useSuggestionDbStore() const viewportNode = ref() onMounted(() => viewportNode.value?.focus()) const graphNavigator = provideGraphNavigator(viewportNode, keyboard) -useNavigatorStorage(graphNavigator, (enc) => { - // Navigator viewport needs to be stored separately for: - // - each project - // - each function within the project - encoding.writeVarString(enc, projectStore.name) - const methodPtr = graphStore.currentMethodPointer() - if (methodPtr != null) encodeMethodPointer(enc, methodPtr) -}) +useNavigatorStorage( + graphNavigator, + (enc) => { + // Navigator viewport needs to be stored separately for: + // - each project + // - each function within the project + encoding.writeVarString(enc, projectStore.name) + const methodPtr = graphStore.currentMethodPointer() + if (methodPtr != null) encodeMethodPointer(enc, methodPtr) + }, + waitInitializationAndPanToAll, +) + +let stopInitialization: (() => void) | undefined +function waitInitializationAndPanToAll() { + stopInitialization?.() + stopInitialization = watchEffect(() => { + const nodesCount = graphStore.db.nodeIdToNode.size + const visibleNodeAreas = graphStore.visibleNodeAreas + if (nodesCount > 0 && visibleNodeAreas.length == nodesCount) { + zoomToSelected(true) + stopInitialization?.() + stopInitialization = undefined + } + }) +} function selectionBounds() { if (!viewportNode.value) return @@ -90,9 +108,10 @@ function selectionBounds() { if (bounds.isFinite()) return bounds } -function zoomToSelected() { +function zoomToSelected(skipAnimation: boolean = false) { const bounds = selectionBounds() - if (bounds) graphNavigator.panAndZoomTo(bounds, 0.1, Math.max(1, graphNavigator.targetScale)) + if (bounds) + graphNavigator.panAndZoomTo(bounds, 0.1, Math.max(1, graphNavigator.targetScale), skipAnimation) } function panToSelected() { @@ -210,7 +229,9 @@ const graphBindingsHandler = graphBindings.handler({ } }, deleteSelected, - zoomToSelected, + zoomToSelected() { + zoomToSelected() + }, selectAll() { nodeSelection.selectAll() }, diff --git a/app/gui2/src/composables/navigator.ts b/app/gui2/src/composables/navigator.ts index de875532e6ec..63ac39972a46 100644 --- a/app/gui2/src/composables/navigator.ts +++ b/app/gui2/src/composables/navigator.ts @@ -76,6 +76,7 @@ export function useNavigator(viewportNode: Ref, keyboard: K rect: Rect, minScale = PAN_AND_ZOOM_DEFAULT_SCALE_RANGE[0], maxScale = PAN_AND_ZOOM_DEFAULT_SCALE_RANGE[1], + skipAnimation = false, ) { if (!viewportNode.value) return targetScale.value = Math.max( @@ -87,6 +88,10 @@ export function useNavigator(viewportNode: Ref, keyboard: K ), ) targetCenter.value = rect.center().finiteOrZero() + if (skipAnimation) { + scale.skip() + center.skip() + } } /** Pan to include the given prioritized list of coordinates. diff --git a/app/gui2/src/composables/navigatorStorage.ts b/app/gui2/src/composables/navigatorStorage.ts index 68a5dc5e590a..d423962a2738 100644 --- a/app/gui2/src/composables/navigatorStorage.ts +++ b/app/gui2/src/composables/navigatorStorage.ts @@ -2,7 +2,7 @@ import { Vec2 } from '@/util/data/vec2' import { debouncedWatch, useLocalStorage } from '@vueuse/core' import { encoding } from 'lib0' import { xxHash128 } from 'shared/ast/ffi' -import { computed, watch } from 'vue' +import { computed, nextTick, watch } from 'vue' import type { NavigatorComposable } from './navigator' /** @@ -12,10 +12,13 @@ import type { NavigatorComposable } from './navigator' * @param reactiveStorageKeyEncoder A **reactive** encoder from which a storage key is derived. Data * that is encoded in this function dictates the effective identity of stored viewport. Whenever the * encoded data changes, the stored viewport value is restored to navigator. + * @param initializeViewport A function that will be called when no stored viewport is found for the + * current storage key. */ export function useNavigatorStorage( navigator: NavigatorComposable, reactiveStorageKeyEncoder: (enc: encoding.Encoder) => void, + initializeViewport: () => void, ) { const graphViewportStorageKey = computed(() => xxHash128(encoding.encode(reactiveStorageKeyEncoder)), @@ -64,6 +67,7 @@ export function useNavigatorStorage( function restoreViewport(storageKey: string) { const restored = storedViewport.value.get(storageKey) + if (restored == null) nextTick(initializeViewport) const pos = restored ? Vec2.FromXY(restored).finiteOrZero() : Vec2.Zero const scale = restored?.s ?? 1 navigator.setCenterAndScale(pos, scale)