Skip to content

Commit 149c007

Browse files
Copilotmikebarkmin
andcommitted
Fix viewport behavior: use defaultViewport, add translateExtent, persist viewport state
Co-authored-by: mikebarkmin <2592379+mikebarkmin@users.noreply.github.com>
1 parent 0dd11aa commit 149c007

1 file changed

Lines changed: 44 additions & 8 deletions

File tree

packages/learningmap/src/LearningMap.tsx

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ export function LearningMap({
6868
const updateNodesStates = useViewerStore(state => state.updateNodesStates);
6969
const updateNodeState = useViewerStore(state => state.updateNodeState);
7070

71-
const { fitView, getViewport, setViewport } = useReactFlow();
71+
const { fitView, getViewport } = useReactFlow();
7272

7373
// Use language from settings if available, otherwise use prop
7474
const effectiveLanguage = settings?.language || language;
@@ -78,14 +78,40 @@ export function LearningMap({
7878

7979
const parsedRoadmap = parseRoadmapData(roadmapData);
8080

81+
// Calculate translateExtent to ensure at least one node is always visible
82+
const calculateTranslateExtent = useCallback(() => {
83+
if (nodes.length === 0) return [[-Infinity, -Infinity], [Infinity, Infinity]] as [[number, number], [number, number]];
84+
85+
const padding = 200; // Add padding so nodes aren't at the very edge
86+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
87+
88+
nodes.forEach(node => {
89+
if (node.position) {
90+
// Estimate node size (approximate, could be refined)
91+
const nodeWidth = node.width || 200;
92+
const nodeHeight = node.height || 100;
93+
94+
minX = Math.min(minX, node.position.x - padding);
95+
minY = Math.min(minY, node.position.y - padding);
96+
maxX = Math.max(maxX, node.position.x + nodeWidth + padding);
97+
maxY = Math.max(maxY, node.position.y + nodeHeight + padding);
98+
}
99+
});
100+
101+
return [[minX, minY], [maxX, maxY]] as [[number, number], [number, number]];
102+
}, [nodes]);
103+
81104
useEffect(() => {
82105
loadRoadmapData(parsedRoadmap, initialState);
83-
setViewport({
84-
x: initialState?.x || settings?.viewport?.x || 0,
85-
y: initialState?.y || settings?.viewport?.y || 0,
86-
zoom: initialState?.zoom || settings?.viewport?.zoom || 1,
87-
});
88-
}, [roadmapData, initialState]);
106+
107+
// Only use fitView if there's no saved state
108+
if (!initialState) {
109+
// Use setTimeout to ensure nodes are rendered before fitView
110+
setTimeout(() => {
111+
fitView({ duration: 0, padding: 0.2 });
112+
}, 0);
113+
}
114+
}, [roadmapData, initialState, loadRoadmapData, fitView]);
89115

90116
const onNodeClick = useCallback((_: any, node: Node, focus: boolean = false) => {
91117
if (!isInteractableNode(node)) return;
@@ -135,6 +161,15 @@ export function LearningMap({
135161
type: "default",
136162
};
137163

164+
// Determine default viewport (only used if no saved state exists)
165+
const defaultViewport = {
166+
x: initialState?.x || settings?.viewport?.x || 0,
167+
y: initialState?.y || settings?.viewport?.y || 0,
168+
zoom: initialState?.zoom || settings?.viewport?.zoom || 1,
169+
};
170+
171+
const translateExtent = calculateTranslateExtent();
172+
138173
return (
139174
<div
140175
className="editor-canvas"
@@ -159,7 +194,8 @@ export function LearningMap({
159194
onNodeClick={onNodeClick}
160195
onNodesChange={onNodesChange}
161196
nodeTypes={nodeTypes}
162-
fitView
197+
defaultViewport={defaultViewport}
198+
translateExtent={translateExtent}
163199
proOptions={{ hideAttribution: true }}
164200
defaultEdgeOptions={defaultEdgeOptions}
165201
nodesDraggable={false}

0 commit comments

Comments
 (0)