Skip to content

Commit 7d379d1

Browse files
committed
improve ux
1 parent e6d237c commit 7d379d1

File tree

7 files changed

+300
-6
lines changed

7 files changed

+300
-6
lines changed

platforms/web/src/Landing.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,20 @@ import { LearningMap } from "@learningmap/learningmap";
66
import "@learningmap/learningmap/index.css";
77
import demoMapRaw from "./getting-started.learningmap?raw";
88
import type { RoadmapData } from "@learningmap/learningmap";
9+
import * as db from "./db";
910

1011
function Landing() {
1112
const navigate = useNavigate();
1213

1314
// Parse the demo map from raw JSON
1415
const demoMap: RoadmapData = JSON.parse(demoMapRaw);
1516

17+
const handleEditDemo = async () => {
18+
const demoId = "demo-getting-started";
19+
await db.addTeacherMap(demoId, demoMap);
20+
navigate(`/create#id=${demoId}`);
21+
};
22+
1623
return (
1724
<div className="landing-container">
1825
<Header>
@@ -77,6 +84,15 @@ function Landing() {
7784
<div className="demo-map-container">
7885
<LearningMap roadmapData={demoMap} />
7986
</div>
87+
<div style={{ textAlign: "center", marginTop: "1rem" }}>
88+
<button
89+
onClick={handleEditDemo}
90+
className="nav-button"
91+
style={{ display: "inline-block", width: "auto" }}
92+
>
93+
Edit This Map
94+
</button>
95+
</div>
8096
</section>
8197

8298
{/* Teacher-Student Use Case */}

platforms/web/src/Learn.css

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
display: flex;
55
flex-direction: column;
66
font-family: sans-serif;
7+
background: linear-gradient(to bottom, #f8f9fa 0%, #ffffff 100%);
78
}
89

910
/* Toolbar matching EditorToolbar design */
@@ -120,6 +121,7 @@
120121
min-height: 100vh;
121122
display: flex;
122123
flex-direction: column;
124+
background: linear-gradient(to bottom, #f8f9fa 0%, #ffffff 100%);
123125
}
124126

125127
.learn-list-content {

platforms/web/src/Learn.tsx

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,52 @@ function Learn() {
1717
const [currentMap, setCurrentMap] = useState<any>(null);
1818
const [allMaps, setAllMaps] = useState<any[]>([]);
1919
const [showAddDialog, setShowAddDialog] = useState(false);
20+
const [showDataDialog, setShowDataDialog] = useState(false);
2021
const updateTimeoutRef = useRef<number | null>(null);
2122

23+
const handleExport = async () => {
24+
try {
25+
const data = await db.exportAllData();
26+
const blob = new Blob([JSON.stringify(data, null, 2)], {
27+
type: "application/json",
28+
});
29+
const url = URL.createObjectURL(blob);
30+
const a = document.createElement("a");
31+
a.href = url;
32+
a.download = `learningmap-backup-${new Date().toISOString().split("T")[0]}.json`;
33+
document.body.appendChild(a);
34+
a.click();
35+
document.body.removeChild(a);
36+
URL.revokeObjectURL(url);
37+
setShowDataDialog(false);
38+
} catch (err) {
39+
alert("Failed to export data");
40+
}
41+
};
42+
43+
const handleImport = (event: React.ChangeEvent<HTMLInputElement>) => {
44+
const file = event.target.files?.[0];
45+
if (!file) return;
46+
47+
const reader = new FileReader();
48+
reader.onload = async (e) => {
49+
try {
50+
const content = e.target?.result as string;
51+
const data = JSON.parse(content);
52+
await db.importAllData(data);
53+
alert("Data imported successfully! Refresh the page to see all changes.");
54+
setShowDataDialog(false);
55+
// Refresh the maps list
56+
const maps = await db.getAllLearningMaps();
57+
setAllMaps(maps);
58+
} catch (err) {
59+
alert("Failed to import data. Please check the file format.");
60+
}
61+
};
62+
reader.readAsText(file);
63+
event.target.value = "";
64+
};
65+
2266
// Extract json ID from hash
2367
const jsonId = location.hash.startsWith("#json=")
2468
? location.hash.replace("#json=", "")
@@ -273,13 +317,62 @@ function Learn() {
273317
<button onClick={() => navigate("/learn")} className="toolbar-button">
274318
My Learningmaps
275319
</button>
320+
<button onClick={() => setShowDataDialog(true)} className="nav-button">
321+
Manage Data
322+
</button>
276323
</Header>
277324
<LearningMap
278325
key={storageKey}
279326
roadmapData={learningMap.roadmapData}
280327
initialState={learningMap.state}
281328
onChange={(state) => handleStateChange(state, storageKey)}
282329
/>
330+
331+
{showDataDialog && (
332+
<div className="dialog-overlay" onClick={() => setShowDataDialog(false)}>
333+
<div className="dialog-content" onClick={(e) => e.stopPropagation()}>
334+
<div className="dialog-header">
335+
<h2>Manage Your Data</h2>
336+
<button
337+
className="dialog-close"
338+
onClick={() => setShowDataDialog(false)}
339+
aria-label="Close dialog"
340+
>
341+
×
342+
</button>
343+
</div>
344+
<div className="dialog-body">
345+
<div style={{ marginBottom: "1.5rem" }}>
346+
<h3 style={{ marginBottom: "0.5rem" }}>💾 Backup Your Data</h3>
347+
<p style={{ marginBottom: "1rem", color: "#666" }}>
348+
Download all your learning maps and progress as a JSON file.
349+
Keep this file safe to restore your data later or transfer to another device.
350+
</p>
351+
<button onClick={handleExport} className="role-button role-button-primary">
352+
📥 Download Backup
353+
</button>
354+
</div>
355+
356+
<div style={{ borderTop: "1px solid #ddd", paddingTop: "1.5rem" }}>
357+
<h3 style={{ marginBottom: "0.5rem" }}>📤 Restore from Backup</h3>
358+
<p style={{ marginBottom: "1rem", color: "#666" }}>
359+
Upload a previously downloaded backup file to restore your learning maps
360+
and progress. This will merge with your existing data.
361+
</p>
362+
<label className="role-button role-button-secondary" style={{ cursor: "pointer", display: "inline-block" }}>
363+
📤 Upload Backup
364+
<input
365+
type="file"
366+
accept=".json,application/json"
367+
onChange={handleImport}
368+
style={{ display: "none" }}
369+
/>
370+
</label>
371+
</div>
372+
</div>
373+
</div>
374+
</div>
375+
)}
283376
</div>
284377
);
285378
}
@@ -293,6 +386,9 @@ function Learn() {
293386
>
294387
Add Map
295388
</button>
389+
<button onClick={() => setShowDataDialog(true)} className="nav-button">
390+
Manage Data
391+
</button>
296392
</Header>
297393

298394
{showAddDialog && (
@@ -344,6 +440,52 @@ function Learn() {
344440
</div>
345441
)}
346442

443+
{showDataDialog && (
444+
<div className="dialog-overlay" onClick={() => setShowDataDialog(false)}>
445+
<div className="dialog-content" onClick={(e) => e.stopPropagation()}>
446+
<div className="dialog-header">
447+
<h2>Manage Your Data</h2>
448+
<button
449+
className="dialog-close"
450+
onClick={() => setShowDataDialog(false)}
451+
aria-label="Close dialog"
452+
>
453+
×
454+
</button>
455+
</div>
456+
<div className="dialog-body">
457+
<div style={{ marginBottom: "1.5rem" }}>
458+
<h3 style={{ marginBottom: "0.5rem" }}>Backup Your Data</h3>
459+
<p style={{ marginBottom: "1rem", color: "#666" }}>
460+
Download all your learning maps and progress as a JSON file.
461+
Keep this file safe to restore your data later or transfer to another device.
462+
</p>
463+
<button onClick={handleExport} className="role-button role-button-primary" style={{ width: "100%" }}>
464+
Download Backup
465+
</button>
466+
</div>
467+
468+
<div style={{ borderTop: "1px solid #ddd", paddingTop: "1.5rem" }}>
469+
<h3 style={{ marginBottom: "0.5rem" }}>Restore from Backup</h3>
470+
<p style={{ marginBottom: "1rem", color: "#666" }}>
471+
Upload a previously downloaded backup file to restore your learning maps
472+
and progress. This will merge with your existing data.
473+
</p>
474+
<label className="role-button role-button-secondary" style={{ cursor: "pointer", display: "block", width: "100%", textAlign: "center", boxSizing: "border-box" }}>
475+
Upload Backup
476+
<input
477+
type="file"
478+
accept=".json,application/json"
479+
onChange={handleImport}
480+
style={{ display: "none" }}
481+
/>
482+
</label>
483+
</div>
484+
</div>
485+
</div>
486+
</div>
487+
)}
488+
347489
<div className="learn-list-content">
348490
<div className="page-header">
349491
<h2>

platforms/web/src/Teach.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
display: flex;
55
flex-direction: column;
66
font-family: sans-serif;
7+
background: linear-gradient(to bottom, #f8f9fa 0%, #ffffff 100%);
78
}
89

910
.teach-toolbar {

platforms/web/src/Teach.tsx

Lines changed: 98 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ function Teach() {
1313
const [loading, setLoading] = useState(false);
1414
const [error, setError] = useState<string | null>(null);
1515
const [showAddDialog, setShowAddDialog] = useState(false);
16+
const [showDataDialog, setShowDataDialog] = useState(false);
1617
const [showConflictDialog, setShowConflictDialog] = useState(false);
1718
const [conflictData, setConflictData] = useState<{
1819
storageId: string;
@@ -21,6 +22,49 @@ function Teach() {
2122
existingMap: TeacherMapEntry;
2223
} | null>(null);
2324

25+
const handleExport = async () => {
26+
try {
27+
const data = await db.exportAllData();
28+
const blob = new Blob([JSON.stringify(data, null, 2)], {
29+
type: "application/json",
30+
});
31+
const url = URL.createObjectURL(blob);
32+
const a = document.createElement("a");
33+
a.href = url;
34+
a.download = `learningmap-backup-${new Date().toISOString().split("T")[0]}.json`;
35+
document.body.appendChild(a);
36+
a.click();
37+
document.body.removeChild(a);
38+
URL.revokeObjectURL(url);
39+
setShowDataDialog(false);
40+
} catch (err) {
41+
alert("Failed to export data");
42+
}
43+
};
44+
45+
const handleImport = (event: React.ChangeEvent<HTMLInputElement>) => {
46+
const file = event.target.files?.[0];
47+
if (!file) return;
48+
49+
const reader = new FileReader();
50+
reader.onload = async (e) => {
51+
try {
52+
const content = e.target?.result as string;
53+
const data = JSON.parse(content);
54+
await db.importAllData(data);
55+
alert("Data imported successfully! Refresh the page to see all changes.");
56+
setShowDataDialog(false);
57+
// Refresh the maps list
58+
const maps = await db.getAllTeacherMaps();
59+
setAllMaps(maps);
60+
} catch (err) {
61+
alert("Failed to import data. Please check the file format.");
62+
}
63+
};
64+
reader.readAsText(file);
65+
event.target.value = "";
66+
};
67+
2468
useEffect(() => {
2569
db.getAllTeacherMaps().then(setAllMaps);
2670
}, []);
@@ -223,17 +267,20 @@ function Teach() {
223267
return (
224268
<div className="teach-container">
225269
<Header>
270+
<button
271+
onClick={() => navigate("/create")}
272+
className="toolbar-button toolbar-button-primary"
273+
>
274+
New Map
275+
</button>
226276
<button
227277
onClick={() => setShowAddDialog(true)}
228278
className="toolbar-button"
229279
>
230280
Add Map
231281
</button>
232-
<button
233-
onClick={() => navigate("/create")}
234-
className="toolbar-button toolbar-button-primary"
235-
>
236-
+ Create New Map
282+
<button onClick={() => setShowDataDialog(true)} className="nav-button">
283+
Manage Data
237284
</button>
238285
</Header>
239286

@@ -360,6 +407,52 @@ function Teach() {
360407
</div>
361408
)}
362409

410+
{showDataDialog && (
411+
<div className="dialog-overlay" onClick={() => setShowDataDialog(false)}>
412+
<div className="dialog-content" onClick={(e) => e.stopPropagation()}>
413+
<div className="dialog-header">
414+
<h2>Manage Your Data</h2>
415+
<button
416+
className="dialog-close"
417+
onClick={() => setShowDataDialog(false)}
418+
aria-label="Close dialog"
419+
>
420+
×
421+
</button>
422+
</div>
423+
<div className="dialog-body">
424+
<div style={{ marginBottom: "1.5rem" }}>
425+
<h3 style={{ marginBottom: "0.5rem" }}>Backup Your Data</h3>
426+
<p style={{ marginBottom: "1rem", color: "#666" }}>
427+
Download all your created maps and student learning maps as a JSON file.
428+
Keep this file safe to restore your data later or transfer to another device.
429+
</p>
430+
<button onClick={handleExport} className="role-button role-button-primary" style={{ width: "100%" }}>
431+
Download Backup
432+
</button>
433+
</div>
434+
435+
<div style={{ borderTop: "1px solid #ddd", paddingTop: "1.5rem" }}>
436+
<h3 style={{ marginBottom: "0.5rem" }}>Restore from Backup</h3>
437+
<p style={{ marginBottom: "1rem", color: "#666" }}>
438+
Upload a previously downloaded backup file to restore your maps.
439+
This will merge with your existing data.
440+
</p>
441+
<label className="role-button role-button-secondary" style={{ cursor: "pointer", display: "block", width: "100%", textAlign: "center", boxSizing: "border-box" }}>
442+
Upload Backup
443+
<input
444+
type="file"
445+
accept=".json,application/json"
446+
onChange={handleImport}
447+
style={{ display: "none" }}
448+
/>
449+
</label>
450+
</div>
451+
</div>
452+
</div>
453+
</div>
454+
)}
455+
363456
<div className="teach-content">
364457
<div className="page-header">
365458
<h2>

0 commit comments

Comments
 (0)