@@ -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 >
0 commit comments