@@ -174,8 +174,10 @@ const AppContent: React.FC<AppContentProps> = ({ project, setProject }): React.J
174174
175175 const generatorContext = React . useRef < GeneratorContext | null > ( null ) ;
176176
177- const blocksEditor = React . useRef < editor . Editor | null > ( null ) ;
178- const blocklyComponent = React . useRef < BlocklyComponentType | null > ( null ) ;
177+ /** modulePaths controls how BlocklyComponents are created. */
178+ const modulePaths = React . useRef < string [ ] > ( [ ] ) ;
179+ const modulePathToBlocklyComponent = React . useRef < { [ modulePath : string ] : BlocklyComponentType } > ( { } ) ;
180+ const modulePathToEditor = React . useRef < { [ modulePath : string ] : editor . Editor } > ( { } ) ;
179181
180182 /** Initialize language from UserSettings when app first starts. */
181183 React . useEffect ( ( ) => {
@@ -224,9 +226,10 @@ const AppContent: React.FC<AppContentProps> = ({ project, setProject }): React.J
224226 }
225227 }
226228
227- // Update toolbox after language change
228- if ( blocksEditor . current ) {
229- blocksEditor . current . updateToolbox ( shownPythonToolboxCategories ) ;
229+ // Update toolbox in all editors after language change.
230+ for ( const modulePath in modulePathToEditor . current ) {
231+ const editor = modulePathToEditor . current [ modulePath ] ;
232+ editor . updateToolbox ( shownPythonToolboxCategories ) ;
230233 }
231234 } ;
232235
@@ -300,20 +303,31 @@ const AppContent: React.FC<AppContentProps> = ({ project, setProject }): React.J
300303 return ;
301304 }
302305
306+ // Check whether this blockly workspace is for the current module.
307+ if ( ! currentModule ||
308+ ! ( currentModule . modulePath in modulePathToBlocklyComponent . current ) ) {
309+ return ;
310+ }
311+ const blocklyComponent = modulePathToBlocklyComponent . current [ currentModule . modulePath ] ;
312+ if ( event . workspaceId != blocklyComponent . getBlocklyWorkspace ( ) . id ) {
313+ return ;
314+ }
315+
303316 setTriggerPythonRegeneration ( Date . now ( ) ) ;
304317 } ;
305318
306319 /** Saves blocks to storage with success/error messaging. */
307320 const saveModule = async ( ) : Promise < boolean > => {
308321 return new Promise ( async ( resolve , reject ) => {
309322 if ( ! currentModule ||
310- ! blocksEditor . current ) {
323+ ! ( currentModule . modulePath in modulePathToEditor . current ) ) {
311324 reject ( new Error ( 'Blocks editor not initialized' ) ) ;
312325 return ;
313326 }
327+ const editor = modulePathToEditor . current [ currentModule . modulePath ] ;
314328
315329 try {
316- const moduleContentText = await blocksEditor . current . saveModule ( ) ;
330+ const moduleContentText = await editor . saveModule ( ) ;
317331 modulePathToContentText [ currentModule . modulePath ] = moduleContentText ;
318332 messageApi . open ( {
319333 type : 'success' ,
@@ -343,7 +357,12 @@ const AppContent: React.FC<AppContentProps> = ({ project, setProject }): React.J
343357
344358 /** Checks if blocks have been modified. */
345359 const areBlocksModified = ( ) : boolean => {
346- return blocksEditor . current ? blocksEditor . current . isModified ( ) : false ;
360+ if ( currentModule &&
361+ currentModule . modulePath in modulePathToEditor . current ) {
362+ const editor = modulePathToEditor . current [ currentModule . modulePath ] ;
363+ return editor . isModified ( ) ;
364+ }
365+ return false ;
347366 } ;
348367
349368 /** Changes current module with automatic saving if modified. */
@@ -427,75 +446,98 @@ const AppContent: React.FC<AppContentProps> = ({ project, setProject }): React.J
427446 if ( generatorContext . current ) {
428447 generatorContext . current . setModule ( currentModule ) ;
429448 }
430- if ( blocksEditor . current && currentModule ) {
431- blocksEditor . current . loadModuleBlocks ( currentModule , project , modulePathToContentText ) ;
449+ if ( currentModule ) {
450+ if ( modulePaths . current . includes ( currentModule . modulePath ) ) {
451+ activateEditor ( ) ;
452+ } else {
453+ // Add the module path to modulePaths to create a new BlocklyComponent.
454+ modulePaths . current . push ( currentModule . modulePath ) ;
455+ }
432456 }
433457 } , [ currentModule ] ) ;
434458
435- const setupBlocklyComponent = ( newBlocklyComponent : BlocklyComponentType ) => {
436- blocklyComponent . current = newBlocklyComponent ;
459+ const activateEditor = ( ) => {
460+ if ( ! project || ! currentModule ) {
461+ return ;
462+ }
463+ if ( generatorContext . current ) {
464+ generatorContext . current . setModule ( currentModule ) ;
465+ }
466+ for ( const modulePath in modulePathToBlocklyComponent . current ) {
467+ const blocklyComponent = modulePathToBlocklyComponent . current [ modulePath ] ;
468+ const active = ( modulePath === currentModule . modulePath ) ;
469+ const workspaceIsVisible = blocklyComponent . getBlocklyWorkspace ( ) ! . isVisible ( ) ;
470+ if ( active != workspaceIsVisible ) {
471+ blocklyComponent . setActive ( active ) ;
472+ }
473+ }
474+ if ( currentModule . modulePath in modulePathToEditor . current ) {
475+ const editor = modulePathToEditor . current [ currentModule . modulePath ] ;
476+ editor . makeCurrent ( project , modulePathToContentText ) ;
477+ }
478+ } ;
479+
480+ const setupBlocklyComponent = ( modulePath : string , newBlocklyComponent : BlocklyComponentType ) => {
481+ modulePathToBlocklyComponent . current [ modulePath ] = newBlocklyComponent ;
482+ if ( currentModule ) {
483+ newBlocklyComponent . setActive ( modulePath === currentModule . modulePath ) ;
484+ }
437485 } ;
438486
439- const setupWorkspace = ( newWorkspace : Blockly . WorkspaceSvg ) => {
440- if ( ! blocklyComponent . current || ! storage || ! generatorContext . current ) {
487+ const setupWorkspace = ( modulePath : string , newWorkspace : Blockly . WorkspaceSvg ) => {
488+ if ( ! project || ! storage || ! generatorContext . current ) {
441489 return ;
442490 }
443- // Recreate workspace when Blockly component is ready
491+ const module = storageProject . findModuleByModulePath ( project , modulePath ) ;
492+ if ( ! module ) {
493+ console . error ( "setupWorkspace called for unknown module path " + modulePath ) ;
494+ return ;
495+ }
496+
444497 ChangeFramework . setup ( newWorkspace ) ;
445498 newWorkspace . addChangeListener ( mutatorOpenListener ) ;
446499 newWorkspace . addChangeListener ( handleBlocksChanged ) ;
447500
448501 registerToolboxButton ( newWorkspace , messageApi ) ;
449502
450- if ( currentModule ) {
451- generatorContext . current . setModule ( currentModule ) ;
503+ const oldEditor = modulePathToEditor . current [ modulePath ] ;
504+ if ( oldEditor ) {
505+ oldEditor . abandon ( ) ;
452506 }
453507
454- if ( blocksEditor . current ) {
455- blocksEditor . current . abandon ( ) ;
456- }
457- blocksEditor . current = new editor . Editor ( newWorkspace , generatorContext . current , storage ) ;
458- blocksEditor . current . makeCurrent ( ) ;
508+ const newEditor = new editor . Editor (
509+ newWorkspace , module , project , generatorContext . current , storage , modulePathToContentText ) ;
510+ modulePathToEditor . current [ modulePath ] = newEditor ;
511+ newEditor . loadModuleBlocks ( ) ;
512+ newEditor . updateToolbox ( shownPythonToolboxCategories ) ;
459513
460- // Set the current module in the editor after creating it
461- if ( currentModule ) {
462- blocksEditor . current . loadModuleBlocks ( currentModule , project , modulePathToContentText ) ;
514+ if ( currentModule && currentModule . modulePath === modulePath ) {
515+ activateEditor ( ) ;
463516 }
464-
465- blocksEditor . current . updateToolbox ( shownPythonToolboxCategories ) ;
466517 } ;
467518
468- // Initialize Blockly workspace and editor when component and storage are ready
469- React . useEffect ( ( ) => {
470- if ( ! blocklyComponent . current || ! storage ) {
471- return ;
472- }
473-
474- const blocklyWorkspace = blocklyComponent . current . getBlocklyWorkspace ( ) ;
475- if ( blocklyWorkspace ) {
476- setupWorkspace ( blocklyWorkspace ) ;
477- }
478- } , [ blocklyComponent , storage ] ) ;
479-
480519 // Generate code when module or regeneration trigger changes
481520 React . useEffect ( ( ) => {
482- if ( currentModule && blocklyComponent . current && generatorContext . current ) {
483- const blocklyWorkspace = blocklyComponent . current . getBlocklyWorkspace ( ) ;
484- setGeneratedCode ( extendedPythonGenerator . mrcWorkspaceToCode (
485- blocklyWorkspace ,
486- generatorContext . current
487- ) ) ;
488- } else {
489- setGeneratedCode ( '' ) ;
521+ let generatedCode = '' ;
522+ if ( currentModule && generatorContext . current ) {
523+ if ( currentModule . modulePath in modulePathToBlocklyComponent . current ) {
524+ const blocklyComponent = modulePathToBlocklyComponent . current [ currentModule . modulePath ] ;
525+ generatedCode = extendedPythonGenerator . mrcWorkspaceToCode (
526+ blocklyComponent . getBlocklyWorkspace ( ) , generatorContext . current ) ;
527+ }
490528 }
491- } , [ currentModule , project , triggerPythonRegeneration , blocklyComponent ] ) ;
529+ setGeneratedCode ( generatedCode ) ;
530+ } , [ currentModule , project , triggerPythonRegeneration ] ) ;
492531
493- // Update toolbox when module or categories change
532+ // Update toolbox when categories change
494533 React . useEffect ( ( ) => {
495- if ( blocksEditor . current ) {
496- blocksEditor . current . updateToolbox ( shownPythonToolboxCategories ) ;
534+ if ( currentModule ) {
535+ if ( currentModule . modulePath in modulePathToEditor . current ) {
536+ const editor = modulePathToEditor . current [ currentModule . modulePath ] ;
537+ editor . updateToolbox ( shownPythonToolboxCategories ) ;
538+ }
497539 }
498- } , [ currentModule , shownPythonToolboxCategories ] ) ;
540+ } , [ shownPythonToolboxCategories ] ) ;
499541
500542 // Fetch modules when project changes.
501543 React . useEffect ( ( ) => {
@@ -509,13 +551,38 @@ const AppContent: React.FC<AppContentProps> = ({ project, setProject }): React.J
509551 project . opModes . forEach ( opmode => {
510552 promises [ opmode . modulePath ] = storage . fetchFileContentText ( opmode . modulePath ) ;
511553 } ) ;
512- const modulePathToContentText : { [ modulePath : string ] : string } = { } ; // value is module content text
554+ const updatedModulePathToContentText : { [ modulePath : string ] : string } = { } ; // value is module content text
513555 await Promise . all (
514556 Object . entries ( promises ) . map ( async ( [ modulePath , promise ] ) => {
515- modulePathToContentText [ modulePath ] = await promise ;
557+ updatedModulePathToContentText [ modulePath ] = await promise ;
516558 } )
517559 ) ;
518- setModulePathToContentText ( modulePathToContentText ) ;
560+ const oldModulePathToContentText = modulePathToContentText ;
561+ setModulePathToContentText ( updatedModulePathToContentText ) ;
562+
563+ // Remove any deleted modules from modulePaths, modulePathToBlocklyComponent, and
564+ // modulePathToEditor. Update currentModule if the current module was deleted.
565+ for ( const modulePath in oldModulePathToContentText ) {
566+ if ( modulePath in updatedModulePathToContentText ) {
567+ continue ;
568+ }
569+ if ( currentModule && currentModule . modulePath === modulePath ) {
570+ setCurrentModule ( project . robot ) ;
571+ setActiveTab ( project . robot . modulePath ) ;
572+ }
573+ const indexToRemove : number = modulePaths . current . indexOf ( modulePath ) ;
574+ if ( indexToRemove !== - 1 ) {
575+ modulePaths . current . splice ( indexToRemove , 1 ) ;
576+ }
577+ if ( modulePath in modulePathToBlocklyComponent . current ) {
578+ delete modulePathToBlocklyComponent . current [ modulePath ] ;
579+ }
580+ if ( modulePath in modulePathToEditor . current ) {
581+ const editor = modulePathToEditor . current [ modulePath ] ;
582+ editor . abandon ( ) ;
583+ delete modulePathToEditor . current [ modulePath ] ;
584+ }
585+ }
519586 } ;
520587 fetchModules ( ) ;
521588 }
@@ -579,11 +646,15 @@ const AppContent: React.FC<AppContentProps> = ({ project, setProject }): React.J
579646 />
580647 < Antd . Layout >
581648 < Content >
582- < BlocklyComponent
583- onBlocklyComponentCreated = { setupBlocklyComponent }
584- theme = { theme }
585- onWorkspaceRecreated = { setupWorkspace }
586- />
649+ { modulePaths . current . map ( ( modulePath ) => (
650+ < BlocklyComponent
651+ key = { modulePath }
652+ modulePath = { modulePath }
653+ onBlocklyComponentCreated = { setupBlocklyComponent }
654+ theme = { theme }
655+ onWorkspaceCreated = { setupWorkspace }
656+ />
657+ ) ) }
587658 </ Content >
588659 < Sider
589660 collapsible
0 commit comments