Skip to content

Commit 54cb21f

Browse files
authored
Merge pull request #23 from openpatch/copilot/add-initial-viewport-setting
Add initial viewport settings to configure map position and zoom on load
2 parents 0729d78 + 570bdd3 commit 54cb21f

File tree

4 files changed

+104
-6
lines changed

4 files changed

+104
-6
lines changed

packages/learningmap/src/EditorCanvas.tsx

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { useCallback, memo } from "react";
2-
import { ReactFlow, Controls, Background, ControlButton, OnSelectionChangeFunc, Node, Edge } from "@xyflow/react";
1+
import { useCallback, memo, useEffect } from "react";
2+
import { ReactFlow, Controls, Background, ControlButton, OnSelectionChangeFunc, Node, Edge, useReactFlow } from "@xyflow/react";
33
import { Info, Redo, Undo } from "lucide-react";
44
import { useEditorStore, useTemporalStore } from "./editorStore";
55
import { TaskNode } from "./nodes/TaskNode";
@@ -45,9 +45,22 @@ export const EditorCanvas = memo(({ defaultLanguage = "en" }: EditorCanvasProps)
4545
const setEdgeDrawerOpen = useEditorStore(state => state.setEdgeDrawerOpen);
4646
const setHelpOpen = useEditorStore(state => state.setHelpOpen);
4747

48+
const { setViewport } = useReactFlow();
49+
4850
const language = settings?.language || defaultLanguage;
4951
const t = getTranslations(language);
5052

53+
// Apply viewport from settings on mount or when settings change
54+
useEffect(() => {
55+
if (settings?.viewport) {
56+
setViewport({
57+
x: settings.viewport.x,
58+
y: settings.viewport.y,
59+
zoom: settings.viewport.zoom,
60+
});
61+
}
62+
}, [settings?.viewport, setViewport]);
63+
5164
// Temporal store for undo/redo
5265
const { undo, redo, canUndo, canRedo } = useTemporalStore((state) => ({
5366
undo: state.undo,
@@ -105,7 +118,6 @@ export const EditorCanvas = memo(({ defaultLanguage = "en" }: EditorCanvasProps)
105118
nodeTypes={nodeTypes}
106119
selectionOnDrag={false}
107120
edgeTypes={edgeTypes}
108-
fitView
109121
proOptions={{ hideAttribution: true }}
110122
defaultEdgeOptions={defaultEdgeOptions}
111123
nodesDraggable={true}

packages/learningmap/src/LearningMap.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,9 @@ export function LearningMap({
8181
useEffect(() => {
8282
loadRoadmapData(parsedRoadmap, initialState);
8383
setViewport({
84-
x: initialState?.x || 0,
85-
y: initialState?.y || 0,
86-
zoom: initialState?.zoom || 1,
84+
x: initialState?.x || settings?.viewport?.x || 0,
85+
y: initialState?.y || settings?.viewport?.y || 0,
86+
zoom: initialState?.zoom || settings?.viewport?.zoom || 1,
8787
});
8888
}, [roadmapData, initialState]);
8989

packages/learningmap/src/SettingsDrawer.tsx

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,18 @@ export const SettingsDrawer: React.FC<SettingsDrawerProps> = ({
4444
onClose();
4545
};
4646

47+
const handleUseCurrentViewport = () => {
48+
const viewport = getViewport();
49+
setLocalSettings(settings => ({
50+
...settings,
51+
viewport: {
52+
x: Math.round(viewport.x),
53+
y: Math.round(viewport.y),
54+
zoom: parseFloat(viewport.zoom.toFixed(2)),
55+
}
56+
}));
57+
};
58+
4759
return (
4860
<>
4961
<div className="drawer-overlay" onClick={onClose} />
@@ -82,6 +94,59 @@ export const SettingsDrawer: React.FC<SettingsDrawerProps> = ({
8294
onChange={color => setLocalSettings(settings => ({ ...settings, background: { ...settings.background, color } }))}
8395
/>
8496
</div>
97+
98+
<div className="form-group">
99+
<label>{t.initialViewport}</label>
100+
<div style={{ display: 'flex', gap: '8px', alignItems: 'center', marginTop: '8px' }}>
101+
<div style={{ flex: 1 }}>
102+
<label style={{ fontSize: '0.875rem', color: '#666' }}>{t.viewportX}</label>
103+
<input
104+
type="number"
105+
value={localSettings?.viewport?.x ?? 0}
106+
onChange={(e) => setLocalSettings(settings => ({
107+
...settings,
108+
viewport: { ...settings.viewport, x: parseFloat(e.target.value) || 0, y: settings.viewport?.y ?? 0, zoom: settings.viewport?.zoom ?? 1 }
109+
}))}
110+
style={{ width: '100%' }}
111+
/>
112+
</div>
113+
<div style={{ flex: 1 }}>
114+
<label style={{ fontSize: '0.875rem', color: '#666' }}>{t.viewportY}</label>
115+
<input
116+
type="number"
117+
value={localSettings?.viewport?.y ?? 0}
118+
onChange={(e) => setLocalSettings(settings => ({
119+
...settings,
120+
viewport: { ...settings.viewport, y: parseFloat(e.target.value) || 0, x: settings.viewport?.x ?? 0, zoom: settings.viewport?.zoom ?? 1 }
121+
}))}
122+
style={{ width: '100%' }}
123+
/>
124+
</div>
125+
<div style={{ flex: 1 }}>
126+
<label style={{ fontSize: '0.875rem', color: '#666' }}>{t.viewportZoom}</label>
127+
<input
128+
type="number"
129+
step="0.1"
130+
min="0.1"
131+
max="10"
132+
value={localSettings?.viewport?.zoom ?? 1}
133+
onChange={(e) => setLocalSettings(settings => ({
134+
...settings,
135+
viewport: { ...settings.viewport, zoom: parseFloat(e.target.value) || 1, x: settings.viewport?.x ?? 0, y: settings.viewport?.y ?? 0 }
136+
}))}
137+
style={{ width: '100%' }}
138+
/>
139+
</div>
140+
</div>
141+
<button
142+
onClick={handleUseCurrentViewport}
143+
className="secondary-button"
144+
style={{ marginTop: '8px', width: '100%' }}
145+
type="button"
146+
>
147+
{t.useCurrentViewport}
148+
</button>
149+
</div>
85150
</div>
86151

87152
<div className="drawer-footer">

packages/learningmap/src/translations.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,13 @@ export interface Translations {
184184
languageEnglish: string;
185185
languageGerman: string;
186186

187+
// Viewport settings
188+
initialViewport: string;
189+
viewportX: string;
190+
viewportY: string;
191+
viewportZoom: string;
192+
useCurrentViewport: string;
193+
187194
// Welcome message
188195
welcomeTitle: string;
189196
welcomeSubtitle: string;
@@ -380,6 +387,13 @@ const en: Translations = {
380387
languageEnglish: "English",
381388
languageGerman: "German",
382389

390+
// Viewport settings
391+
initialViewport: "Initial Viewport",
392+
viewportX: "X Position",
393+
viewportY: "Y Position",
394+
viewportZoom: "Zoom",
395+
useCurrentViewport: "Use Current Viewport",
396+
383397
// Welcome message
384398
welcomeTitle: "Learningmap",
385399
welcomeSubtitle: "All data is stored locally in your browser",
@@ -578,6 +592,13 @@ const de: Translations = {
578592
languageEnglish: "Englisch",
579593
languageGerman: "Deutsch",
580594

595+
// Viewport settings
596+
initialViewport: "Initialer Ansichtsbereich",
597+
viewportX: "X-Position",
598+
viewportY: "Y-Position",
599+
viewportZoom: "Zoom",
600+
useCurrentViewport: "Aktuellen Ansichtsbereich verwenden",
601+
581602
// Welcome message
582603
welcomeTitle: "Learningmap",
583604
welcomeSubtitle: "Alle Daten werden lokal in Ihrem Browser gespeichert",

0 commit comments

Comments
 (0)