fix(editor): fit the view after loaded nodes are measured
This commit is contained in:
@@ -25,7 +25,7 @@ import {
|
|||||||
VueFlow,
|
VueFlow,
|
||||||
type XYPosition,
|
type XYPosition,
|
||||||
} from "@vue-flow/core"
|
} from "@vue-flow/core"
|
||||||
import { computed, nextTick, onBeforeUnmount, onMounted, useTemplateRef } from "vue"
|
import { computed, onBeforeUnmount, onMounted, useTemplateRef } from "vue"
|
||||||
import { useAutosave } from "@/composables/useAutosave"
|
import { useAutosave } from "@/composables/useAutosave"
|
||||||
import { parseModel, serializeModel } from "@/model/io"
|
import { parseModel, serializeModel } from "@/model/io"
|
||||||
import { project } from "@/model/projection"
|
import { project } from "@/model/projection"
|
||||||
@@ -57,6 +57,7 @@ const {
|
|||||||
onConnectStart,
|
onConnectStart,
|
||||||
onConnectEnd,
|
onConnectEnd,
|
||||||
onError,
|
onError,
|
||||||
|
onNodesInitialized,
|
||||||
getSelectedNodes,
|
getSelectedNodes,
|
||||||
getSelectedEdges,
|
getSelectedEdges,
|
||||||
viewport,
|
viewport,
|
||||||
@@ -68,6 +69,18 @@ const {
|
|||||||
// view once a restored model has re-projected so it lands framed.
|
// view once a restored model has re-projected so it lands framed.
|
||||||
useAutosave({ onRestore: () => fitView({ padding: 0.2 }) })
|
useAutosave({ onRestore: () => fitView({ padding: 0.2 }) })
|
||||||
|
|
||||||
|
// Fit the view after a *load* (sample/import), once the freshly projected nodes
|
||||||
|
// have been measured. Vue Flow measures node dimensions a frame after they mount,
|
||||||
|
// so calling fitView straight after setModel fits to 0×0 boxes and the model
|
||||||
|
// lands jammed in the top-left; onNodesInitialized fires post-measure, so framing
|
||||||
|
// is correct. A one-shot flag keeps it scoped to loads (not every re-init).
|
||||||
|
let fitAfterInit = false
|
||||||
|
onNodesInitialized(() => {
|
||||||
|
if (!fitAfterInit) return
|
||||||
|
fitAfterInit = false
|
||||||
|
fitView({ padding: 0.2 })
|
||||||
|
})
|
||||||
|
|
||||||
onNodeDragStart(() => store.beginInteraction())
|
onNodeDragStart(() => store.beginInteraction())
|
||||||
onNodeDragStop(({ nodes: dragged }) => {
|
onNodeDragStop(({ nodes: dragged }) => {
|
||||||
for (const node of dragged) store.moveNode(node.id, node.position)
|
for (const node of dragged) store.moveNode(node.id, node.position)
|
||||||
@@ -175,15 +188,14 @@ function onDragOver(event: DragEvent): void {
|
|||||||
* stay frictionless on the empty starting canvas. Fit the view once the
|
* stay frictionless on the empty starting canvas. Fit the view once the
|
||||||
* projection has re-derived so the loaded model lands framed.
|
* projection has re-derived so the loaded model lands framed.
|
||||||
*/
|
*/
|
||||||
async function loadSample(sample: Sample): Promise<void> {
|
function loadSample(sample: Sample): void {
|
||||||
if (store.nodeCount > 0 && !window.confirm(`Replace the current model with “${sample.title}”?`)) {
|
if (store.nodeCount > 0 && !window.confirm(`Replace the current model with “${sample.title}”?`)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
fitAfterInit = true
|
||||||
store.setModel(sample.build())
|
store.setModel(sample.build())
|
||||||
// Close the DaisyUI dropdown (it stays open while the trigger keeps focus).
|
// Close the DaisyUI dropdown (it stays open while the trigger keeps focus).
|
||||||
;(document.activeElement as HTMLElement | null)?.blur()
|
;(document.activeElement as HTMLElement | null)?.blur()
|
||||||
await nextTick()
|
|
||||||
fitView({ padding: 0.2 })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const fileInput = useTemplateRef<HTMLInputElement>("fileInput")
|
const fileInput = useTemplateRef<HTMLInputElement>("fileInput")
|
||||||
@@ -229,9 +241,8 @@ async function onImportFile(event: Event): Promise<void> {
|
|||||||
) {
|
) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
fitAfterInit = true
|
||||||
store.setModel(result.model)
|
store.setModel(result.model)
|
||||||
await nextTick()
|
|
||||||
fitView({ padding: 0.2 })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function isTextEntry(target: EventTarget | null): boolean {
|
function isTextEntry(target: EventTarget | null): boolean {
|
||||||
|
|||||||
Reference in New Issue
Block a user