Skip to content

Commit e881de0

Browse files
authored
Merge pull request #27 from openpatch/copilot/add-support-working-learning-maps
Add Support for Working on Learning Maps
2 parents bd52a72 + 149c007 commit e881de0

9 files changed

Lines changed: 3560 additions & 2205 deletions

File tree

packages/learningmap/src/LearningMap.tsx

Lines changed: 45 additions & 9 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;
@@ -124,7 +150,7 @@ export function LearningMap({
124150
root.dispatchEvent(new CustomEvent("change", { detail: minimalState }));
125151
}
126152
}
127-
}, [nodes, onChange]);
153+
}, [nodes, getViewport, getRoadmapState]);
128154

129155
const defaultEdgeOptions = {
130156
animated: false,
@@ -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}

platforms/web/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
"dependencies": {
1313
"@learningmap/learningmap": "workspace:*",
1414
"react": "^19.2.0",
15-
"react-dom": "^19.2.0"
15+
"react-dom": "^19.2.0",
16+
"react-router-dom": "^7.9.4",
17+
"zustand": "^5.0.8"
1618
},
1719
"devDependencies": {
1820
"@eslint/js": "^9.36.0",

platforms/web/src/App.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
import './App.css'
2+
import { BrowserRouter, Routes, Route } from 'react-router-dom';
23
import { LearningMapEditor } from '@learningmap/learningmap';
34
import "@learningmap/learningmap/index.css";
5+
import Learn from './Learn';
46

57
function App() {
6-
78
return (
8-
<LearningMapEditor
9-
jsonStore="https://json.openpatch.org"
10-
/>
9+
<BrowserRouter>
10+
<Routes>
11+
<Route path="/" element={<LearningMapEditor jsonStore="https://json.openpatch.org" />} />
12+
<Route path="/learn" element={<Learn />} />
13+
</Routes>
14+
</BrowserRouter>
1115
)
1216
}
1317

0 commit comments

Comments
 (0)