diff --git a/apps/editor/src/ui/editor/EditorCanvas.tsx b/apps/editor/src/ui/editor/EditorCanvas.tsx index 14de323..7ba3ed9 100644 --- a/apps/editor/src/ui/editor/EditorCanvas.tsx +++ b/apps/editor/src/ui/editor/EditorCanvas.tsx @@ -1104,13 +1104,36 @@ export default function EditorCanvas({ onBack, onSaveToServer, isPublishedToOpen didInitialViewportRef.current = true } - // Two rAFs to let React Flow apply any pending node measurements/positions. - const id = globalThis.requestAnimationFrame(() => center()) - const id2 = globalThis.requestAnimationFrame(() => center()) + // Switching back from Tree View unhides this container (display:none → + // visible) in the same tick that triggers this effect, but the browser + // doesn't recompute layout — and React Flow's own ResizeObserver doesn't + // refresh its cached container size — until a later frame. Centering + // immediately would compute pan/zoom against a stale 0x0 size, so poll + // until the container actually has a measurable size (bounded, in case + // it's genuinely hidden for some other reason). + const MAX_ATTEMPTS = 30 + let attempts = 0 + let rafId: number + + const waitForSizeThenCenter = () => { + const { width, height } = reactFlowWrapRef.current?.getBoundingClientRect() ?? { width: 0, height: 0 } + if ((width > 0 && height > 0) || attempts >= MAX_ATTEMPTS) { + center() + return + } + attempts += 1 + rafId = globalThis.requestAnimationFrame(waitForSizeThenCenter) + } + + // One rAF to let React Flow apply any pending node measurements/positions + // before the size-polling loop starts. + const id = globalThis.requestAnimationFrame(() => { + rafId = globalThis.requestAnimationFrame(waitForSizeThenCenter) + }) return () => { globalThis.cancelAnimationFrame(id) - globalThis.cancelAnimationFrame(id2) + globalThis.cancelAnimationFrame(rafId) } }, []) diff --git a/apps/editor/src/ui/editor/components/CanvasHeader.tsx b/apps/editor/src/ui/editor/components/CanvasHeader.tsx index 8731b58..abba4cc 100644 --- a/apps/editor/src/ui/editor/components/CanvasHeader.tsx +++ b/apps/editor/src/ui/editor/components/CanvasHeader.tsx @@ -11,6 +11,9 @@ type MenuItem = { disabled?: boolean } +// Hierarchy layout is hidden (redundant with Canvas View) but kept wired up for now; slated for removal. +const SHOW_HIERARCHY_LAYOUT_MENU_ITEM = false + function PopoverMenu({ label, icon: Icon, @@ -380,9 +383,9 @@ export default function CanvasHeader({ icon={Cog6ToothIcon} items={[ { label: 'Settings', icon: Cog6ToothIcon, onClick: onOpenSettings }, - { label: 'Hierarchy layout', icon: Bars3BottomLeftIcon, onClick: onResetHierarchy, disabled: !onResetHierarchy }, - { label: 'Star layout', icon: SparklesIcon, onClick: onResetStar, disabled: !onResetStar }, - { label: activeView === 'tree' ? '✓ Tree view' : 'Tree view', icon: QueueListIcon, onClick: onSwitchTreeView, disabled: !onSwitchTreeView }, + ...(SHOW_HIERARCHY_LAYOUT_MENU_ITEM ? [{ label: 'Hierarchy layout', icon: Bars3BottomLeftIcon, onClick: onResetHierarchy, disabled: !onResetHierarchy }] : []), + { label: 'Canvas View', icon: SparklesIcon, onClick: onResetStar, disabled: !onResetStar }, + { label: activeView === 'tree' ? '✓ Tree View' : 'Tree View', icon: QueueListIcon, onClick: onSwitchTreeView, disabled: !onSwitchTreeView }, 'divider', { label: 'Help', icon: QuestionMarkCircleIcon, onClick: () => {} }, ]}