Skip to content

Commit 4101953

Browse files
committed
In App.tsx:
Replaced blocksEditor and blocklyComponent with modulePaths, modulePathToBocklyComponent and modulePathToEditor. modulePaths controls how BlocklyComponents are created. Changed code that used blocksEditor and blocklyComponent. Added activeEditor function that is called when currentModule changes. Added setupBlocklyComponent, a callback that is a called when a new BlocklyComponent is created. In BlocklyComponent.tsx: Renamed onWorkspaceRecreated to onWorkspaceCreated Added modulePath to BlocklyComponentProps. Pass modulePath to onBlocklyComponentCreated and onWorkspaceCreated callbacks. Added fields for parentDiv and savedScrollX/Y. Only call Blockly.svgResize if the workspace is visible and is the main workspace. Added setActive function and include it in BlocklyComponentType. In setActive, handle either the BlocklyComponenent becoming inactive or active by hiding or showing the blockly workspace. Update scroll position if necessary when going from inactive to active. In editor.ts: Make many fields readonly since an editor is no longer reused for different modules. Check whether the blockly workspace has been abandoned in many places.
1 parent 1c1f494 commit 4101953

File tree

3 files changed

+329
-171
lines changed

3 files changed

+329
-171
lines changed

src/App.tsx

Lines changed: 131 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)