@@ -14,13 +14,10 @@ import {
1414 ControlButton ,
1515 OnNodesChange ,
1616 OnEdgesChange ,
17- getNodesBounds ,
18- getViewportForBounds ,
1917 Panel ,
2018 OnSelectionChangeFunc ,
2119 ReactFlowProvider ,
2220} from "@xyflow/react" ;
23- import { toSvg } from "html-to-image" ;
2421import { EditorDrawer } from "./EditorDrawer" ;
2522import { EdgeDrawer } from "./EdgeDrawer" ;
2623import { TaskNode } from "./nodes/TaskNode" ;
@@ -56,6 +53,12 @@ export interface LearningMapEditorProps {
5653 onChange ?: ( data : RoadmapData ) => void ;
5754}
5855
56+ const getDefaultFilename = ( ) => {
57+ const now = new Date ( ) ;
58+ const pad = ( n : number ) => n . toString ( ) . padStart ( 2 , '0' ) ;
59+ return `${ now . getFullYear ( ) } -${ pad ( now . getMonth ( ) + 1 ) } -${ pad ( now . getDate ( ) ) } -${ pad ( now . getHours ( ) ) } ${ pad ( now . getMinutes ( ) ) } ` ;
60+ } ;
61+
5962export function LearningMapEditor ( {
6063 roadmapData,
6164 language = "en" ,
@@ -111,9 +114,6 @@ export function LearningMapEditor({
111114 const [ selectedEdge , setSelectedEdge ] = useState < Edge | null > ( null ) ;
112115 const [ edgeDrawerOpen , setEdgeDrawerOpen ] = useState ( false ) ;
113116
114- // Track Shift key state
115- const [ shiftPressed , setShiftPressed ] = useState ( false ) ;
116-
117117 useEffect ( ( ) => {
118118 const parsedRoadmap = parseRoadmapData ( roadmapData || "" ) ;
119119 loadRoadmapStateIntoReactFlowState ( parsedRoadmap ) ;
@@ -129,6 +129,7 @@ export function LearningMapEditor({
129129 const rawNodes = nodesArr . map ( ( n ) => ( {
130130 ...n ,
131131 draggable : true ,
132+ className : n . data . color ? n . data . color : n . className ,
132133 data : { ...n . data } ,
133134 } ) ) ;
134135
@@ -413,7 +414,7 @@ export function LearningMapEditor({
413414 const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent ( JSON . stringify ( roadmapState , null , 2 ) ) ;
414415 const downloadAnchorNode = document . createElement ( 'a' ) ;
415416 downloadAnchorNode . setAttribute ( "href" , dataStr ) ;
416- downloadAnchorNode . setAttribute ( "download" , `${ roadmapState . settings . title ?? new Date ( ) . toString ( ) } .learningmap` ) ;
417+ downloadAnchorNode . setAttribute ( "download" , `${ roadmapState . settings . title ?. trim ( ) ?? getDefaultFilename ( ) } .learningmap` ) ;
417418 document . body . appendChild ( downloadAnchorNode ) ; // required for firefox
418419 downloadAnchorNode . click ( ) ;
419420 downloadAnchorNode . remove ( ) ;
@@ -428,42 +429,10 @@ export function LearningMapEditor({
428429 type : "default" ,
429430 } ;
430431
431- const handleExportSVG = useCallback ( async ( ) => {
432- const nodesBounds = getNodesBounds ( nodes ) ;
433- const imageWidth = nodesBounds . width ;
434- const imageHeight = nodesBounds . height ;
435- let viewport = getViewportForBounds ( nodesBounds , imageWidth , imageHeight , 0.1 , 5 ) ;
436-
437- const dom = document . querySelector ( ".react-flow__viewport" ) as HTMLElement ;
438- if ( ! dom ) return ;
439-
440- toSvg ( dom , {
441- backgroundColor : settings ?. background ?. color || "#ffffff" ,
442- width : imageWidth ,
443- height : imageHeight ,
444- style : {
445- transform : `translate(${ viewport . x / 2.0 } px, ${ viewport . y / 2.0 } px) scale(${ viewport . zoom } )` ,
446- width : `${ imageWidth } px` ,
447- height : `${ imageHeight } px` ,
448- }
449- } ) . then ( ( dataUrl ) => {
450- const downloadAnchorNode = document . createElement ( 'a' ) ;
451- downloadAnchorNode . setAttribute ( "href" , dataUrl ) ;
452- downloadAnchorNode . setAttribute ( "download" , "roadmap.svg" ) ;
453- document . body . appendChild ( downloadAnchorNode ) ; // required for firefox
454- downloadAnchorNode . click ( ) ;
455- downloadAnchorNode . remove ( ) ;
456-
457- // Restore old viewport
458- } ) . catch ( ( err ) => {
459- alert ( t . failedToExportSVG + err . message ) ;
460- } ) ;
461- } , [ nodes , roadmapState , t ] ) ;
462-
463432 const handleOpen = useCallback ( ( ) => {
464433 const input = document . createElement ( 'input' ) ;
465434 input . type = 'file' ;
466- input . accept = '.json ,application/json' ;
435+ input . accept = '.learningmap ,application/json' ;
467436 input . onchange = ( e : any ) => {
468437 const file = e . target . files [ 0 ] ;
469438 if ( ! file ) return ;
@@ -544,7 +513,6 @@ export function LearningMapEditor({
544513
545514 useEffect ( ( ) => {
546515 const handleKeyDown = ( e : KeyboardEvent ) => {
547- if ( e . key === "Shift" ) setShiftPressed ( true ) ;
548516 //save shortcut
549517 if ( ( e . ctrlKey || e . metaKey ) && e . key === 's' && ! e . shiftKey ) {
550518 e . preventDefault ( ) ;
@@ -600,21 +568,15 @@ export function LearningMapEditor({
600568 setHelpOpen ( false ) ;
601569 }
602570 } ;
603- const handleKeyUp = ( e : KeyboardEvent ) => {
604- if ( e . key === "Shift" ) setShiftPressed ( false ) ;
605- } ;
606571 window . addEventListener ( "keydown" , handleKeyDown ) ;
607- window . addEventListener ( "keyup" , handleKeyUp ) ;
608572 return ( ) => {
609573 window . removeEventListener ( "keydown" , handleKeyDown ) ;
610- window . removeEventListener ( "keyup" , handleKeyUp ) ;
611574 } ;
612575 } , [ handleSave , handleUndo , handleRedo , addNewNode , helpOpen , setHelpOpen , togglePreviewMode , toggleDebugMode ] ) ;
613576
614577 return (
615578 < >
616579 < EditorToolbar
617- saved = { saved }
618580 debugMode = { debugMode }
619581 previewMode = { previewMode }
620582 showCompletionNeeds = { showCompletionNeeds }
@@ -627,10 +589,8 @@ export function LearningMapEditor({
627589 onSetShowUnlockAfter = { handleSetShowUnlockAfter }
628590 onAddNewNode = { addNewNode }
629591 onOpenSettingsDrawer = { handleOpenSettingsDrawer }
630- onSave = { handleSave }
631592 onDownlad = { handleDownload }
632593 onOpen = { handleOpen }
633- onExportSVG = { handleExportSVG }
634594 language = { effectiveLanguage }
635595 />
636596 { previewMode && < LearningMap roadmapData = { roadmapState } language = { effectiveLanguage } /> }
@@ -650,16 +610,7 @@ export function LearningMapEditor({
650610 />
651611 ) }
652612 < ReactFlow
653- nodes = { nodes . map ( n => {
654- const className = [ ] ;
655- if ( n . data ?. color ) {
656- className . push ( n . data . color ) ;
657- }
658- return {
659- ...n ,
660- className : className . join ( " " )
661- } ;
662- } ) }
613+ nodes = { nodes }
663614 edges = { edges }
664615 onEdgesChange = { handleEdgesChange }
665616 onNodeDoubleClick = { onNodeClick }
@@ -671,7 +622,6 @@ export function LearningMapEditor({
671622 selectionOnDrag = { false }
672623 edgeTypes = { edgeTypes }
673624 fitView
674- snapToGrid = { ! shiftPressed }
675625 proOptions = { { hideAttribution : true } }
676626 defaultEdgeOptions = { defaultEdgeOptions }
677627 nodesDraggable = { true }
@@ -694,8 +644,8 @@ export function LearningMapEditor({
694644 < Info />
695645 </ ControlButton >
696646 </ Controls >
697- { ! saved && < Panel position = "top -right" title = { t . unsavedChanges } onClick = { ( ) => { handleSave ( ) ; } } >
698- < ShieldAlert size = { 32 } color = "red " />
647+ { ! saved && < Panel position = "bottom -right" title = { t . unsavedChanges } onClick = { ( ) => { handleSave ( ) ; } } >
648+ < ShieldAlert size = { 32 } color = "var(--learningmap-color-coal) " />
699649 </ Panel > }
700650 { selectedNodeIds . length > 1 && < MultiNodePanel nodes = { nodes . filter ( n => selectedNodeIds . includes ( n . id ) ) } onUpdate = { updateNodes } /> }
701651 </ ReactFlow >
0 commit comments