From bfe5e42a6c84cdb3f479ef6469ad82c8057591f4 Mon Sep 17 00:00:00 2001 From: Alan Smith Date: Thu, 1 Jan 2026 15:18:17 -0500 Subject: [PATCH 1/2] Remove unneeded currentEditor --- src/editor/editor.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/editor/editor.ts b/src/editor/editor.ts index c6817f1c..0c1ecb88 100644 --- a/src/editor/editor.ts +++ b/src/editor/editor.ts @@ -51,7 +51,6 @@ const MRC_ON_MUTATOR_OPEN = 'mrcOnMutatorOpen'; export class Editor { private static workspaceIdToEditor: { [workspaceId: string]: Editor } = {}; - private static currentEditor: Editor | null = null; private readonly blocklyWorkspace: Blockly.WorkspaceSvg; private readonly module: storageModule.Module; @@ -208,8 +207,6 @@ export class Editor { public makeCurrent( project: storageProject.Project, modulePathToContentText: {[modulePath: string]: string}): void { - Editor.currentEditor = this; - // Parse modules since they might have changed. this.parseModules(project, modulePathToContentText); this.updateToolboxImpl(); @@ -234,9 +231,6 @@ export class Editor { public abandon(): void { workspaces.removeWorkspace(this.blocklyWorkspace); - if (Editor.currentEditor === this) { - Editor.currentEditor = null; - } if (this.blocklyWorkspace.id in Editor.workspaceIdToEditor) { delete Editor.workspaceIdToEditor[this.blocklyWorkspace.id]; } @@ -645,10 +639,6 @@ export class Editor { return workspace ? Editor.getEditorForBlocklyWorkspace(workspace) : null; } - public static getCurrentEditor(): Editor | null { - return Editor.currentEditor; - } - private setPasteLocation(): void { const copyData = Blockly.clipboard.getLastCopiedData(); if (copyData && copyData.paster === 'block') { From e49d3ea3a30233059ebdc49bfee85c64be24965d Mon Sep 17 00:00:00 2001 From: Alan Smith Date: Thu, 1 Jan 2026 15:23:15 -0500 Subject: [PATCH 2/2] Removed modulePathToContentText cache --- src/App.tsx | 46 ++---------------------------- src/editor/editor.ts | 30 ++++++++++++------- src/reactComponents/TabContent.tsx | 33 ++++++++++----------- src/reactComponents/Tabs.tsx | 2 -- 4 files changed, 39 insertions(+), 72 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 7cd1aa79..2e5dd23f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -139,7 +139,6 @@ const AppContent: React.FC = ({ project, setProject }): React.J const [alertErrorMessage, setAlertErrorMessage] = React.useState(''); const [messageApi, contextHolder] = Antd.message.useMessage(); const [toolboxSettingsModalIsOpen, setToolboxSettingsModalIsOpen] = React.useState(false); - const [modulePathToContentText, setModulePathToContentText] = React.useState<{[modulePath: string]: string}>({}); const [tabItems, setTabItems] = React.useState([]); const [isLoadingTabs, setIsLoadingTabs] = React.useState(false); const [shownPythonToolboxCategories, setShownPythonToolboxCategories] = React.useState>(new Set()); @@ -269,46 +268,6 @@ const AppContent: React.FC = ({ project, setProject }): React.J initializeShownPythonToolboxCategories(); }, [storage]); - // Fetch any unfetched modules when project changes. - React.useEffect(() => { - fetchModules(); - }, [project]); - - const fetchModules = async () => { - if (!project || !storage) { - return; - } - const promises: {[modulePath: string]: Promise} = {}; // value is promise of module content. - const updatedModulePathToContentText: {[modulePath: string]: string} = {}; // value is module content text - if (project.robot.modulePath in modulePathToContentText) { - updatedModulePathToContentText[project.robot.modulePath] = modulePathToContentText[project.robot.modulePath]; - } else { - promises[project.robot.modulePath] = storage.fetchFileContentText(project.robot.modulePath); - } - project.mechanisms.forEach(mechanism => { - if (mechanism.modulePath in modulePathToContentText) { - updatedModulePathToContentText[mechanism.modulePath] = modulePathToContentText[mechanism.modulePath]; - } else { - promises[mechanism.modulePath] = storage.fetchFileContentText(mechanism.modulePath); - } - }); - project.opModes.forEach(opmode => { - if (opmode.modulePath in modulePathToContentText) { - updatedModulePathToContentText[opmode.modulePath] = modulePathToContentText[opmode.modulePath]; - } else { - promises[opmode.modulePath] = storage.fetchFileContentText(opmode.modulePath); - } - }); - if (Object.keys(promises).length) { - await Promise.all( - Object.entries(promises).map(async ([modulePath, promise]) => { - updatedModulePathToContentText[modulePath] = await promise; - }) - ); - setModulePathToContentText(updatedModulePathToContentText); - } - }; - // Load saved tabs when project changes React.useEffect(() => { const loadSavedTabs = async () => { @@ -438,7 +397,7 @@ const AppContent: React.FC = ({ project, setProject }): React.J setTabItems(updatedTabs); } } - }, [modulePathToContentText]); + }, [project, tabItems]); // Save tabs when tab list changes (but not during initial loading) React.useEffect(() => { @@ -461,7 +420,7 @@ const AppContent: React.FC = ({ project, setProject }): React.J }, [tabItems, project?.projectName, isLoadingTabs]); const onProjectChanged = async (): Promise => { - await fetchModules(); + // No need to fetch modules anymore - each editor fetches what it needs }; const gotoTab = (tabKey: string): void => { @@ -543,7 +502,6 @@ const AppContent: React.FC = ({ project, setProject }): React.J storage={storage} theme={theme} shownPythonToolboxCategories={shownPythonToolboxCategories} - modulePathToContentText={modulePathToContentText} messageApi={messageApi} /> diff --git a/src/editor/editor.ts b/src/editor/editor.ts index 0c1ecb88..e1941713 100644 --- a/src/editor/editor.ts +++ b/src/editor/editor.ts @@ -73,7 +73,7 @@ export class Editor { module: storageModule.Module, project: storageProject.Project, storage: commonStorage.Storage, - modulePathToContentText: {[modulePath: string]: string}) { + moduleContentText: string) { workspaces.addWorkspace(blocklyWorkspace, module.moduleType); this.blocklyWorkspace = blocklyWorkspace; this.module = module; @@ -81,8 +81,8 @@ export class Editor { this.storage = storage; this.modulePath = module.modulePath; this.robotPath = project.robot.modulePath; - this.moduleContentText = modulePathToContentText[module.modulePath]; - this.parseModules(project, modulePathToContentText); + this.moduleContentText = moduleContentText; + // parseModules will be called async after construction Editor.workspaceIdToEditor[blocklyWorkspace.id] = this; } @@ -204,11 +204,9 @@ export class Editor { } } - public makeCurrent( - project: storageProject.Project, - modulePathToContentText: {[modulePath: string]: string}): void { + public async makeCurrent(project: storageProject.Project): Promise { // Parse modules since they might have changed. - this.parseModules(project, modulePathToContentText); + await this.parseModules(project); this.updateToolboxImpl(); // Go through all the blocks in the workspace and call their mrcOnModuleCurrent method. @@ -236,9 +234,21 @@ export class Editor { } } - private parseModules( - project: storageProject.Project, - modulePathToContentText: {[modulePath: string]: string}): void { + private async parseModules(project: storageProject.Project): Promise { + // Fetch all module content from storage + const modulePaths: string[] = [ + project.robot.modulePath, + ...project.mechanisms.map(m => m.modulePath), + ...project.opModes.map(o => o.modulePath), + ]; + + const modulePathToContentText: {[modulePath: string]: string} = {}; + await Promise.all( + modulePaths.map(async (modulePath) => { + modulePathToContentText[modulePath] = await this.storage.fetchFileContentText(modulePath); + }) + ); + // Parse the modules. this.modulePathToModuleContent = {} for (const modulePath in modulePathToContentText) { diff --git a/src/reactComponents/TabContent.tsx b/src/reactComponents/TabContent.tsx index 7c62bf7a..99760206 100644 --- a/src/reactComponents/TabContent.tsx +++ b/src/reactComponents/TabContent.tsx @@ -53,7 +53,6 @@ export interface TabContentProps { storage: commonStorage.Storage; theme: string; shownPythonToolboxCategories: Set; - modulePathToContentText: {[modulePath: string]: string}; messageApi: MessageInstance; setAlertErrorMessage: (message: string) => void; isActive: boolean; @@ -70,7 +69,6 @@ export const TabContent = React.forwardRef(({ storage, theme, shownPythonToolboxCategories, - modulePathToContentText, messageApi, setAlertErrorMessage, isActive, @@ -90,11 +88,7 @@ export const TabContent = React.forwardRef(({ React.useImperativeHandle(ref, () => ({ saveModule: async () => { if (editorInstance) { - const moduleContentText = await editorInstance.saveModule(); - // Update modulePathToContentText. - // modulePathToContentText is passed to Editor.makeCurrent so the active editor will know - // about changes to other modules. - modulePathToContentText[modulePath] = moduleContentText; + await editorInstance.saveModule(); // Mark as saved after successful save autosave.markAsSaved(); } @@ -138,23 +132,29 @@ export const TabContent = React.forwardRef(({ }, [isActive]); /** Called when workspace is created. */ - const setupWorkspace = React.useCallback((_modulePath: string, newWorkspace: Blockly.WorkspaceSvg) => { + const setupWorkspace = React.useCallback(async (_modulePath: string, newWorkspace: Blockly.WorkspaceSvg) => { newWorkspace.addChangeListener(handleBlocksChanged); classMethodDef.registerToolboxButton(newWorkspace, messageApi); eventHandler.registerToolboxButton(newWorkspace, messageApi); + // Fetch the module content from storage + const moduleContentText = await storage.fetchFileContentText(modulePath); + const newEditor = new editor.Editor( newWorkspace, module, project, storage, - modulePathToContentText + moduleContentText ); + // Parse modules after editor is created + await newEditor.makeCurrent(project); + setEditorInstance(newEditor); newEditor.loadModuleBlocks(); newEditor.updateToolbox(shownPythonToolboxCategories); - }, [module, project, storage, modulePathToContentText, shownPythonToolboxCategories, messageApi, handleBlocksChanged]); + }, [module, project, storage, modulePath, shownPythonToolboxCategories, messageApi, handleBlocksChanged]); /** Update editor toolbox when categories change. */ React.useEffect(() => { @@ -171,13 +171,14 @@ export const TabContent = React.forwardRef(({ if (editorInstance && isActive) { // Set flag to ignore changes during activation isInitialActivation.current = true; - editorInstance.makeCurrent(project, modulePathToContentText); - // Clear the flag after a brief delay to allow workspace to settle - setTimeout(() => { - isInitialActivation.current = false; - }, 100); + editorInstance.makeCurrent(project).then(() => { + // Clear the flag after a brief delay to allow workspace to settle + setTimeout(() => { + isInitialActivation.current = false; + }, 100); + }); } - }, [isActive, blocklyComponent, editorInstance, project, modulePathToContentText]); + }, [isActive, blocklyComponent, editorInstance, project]); /** Generate code when regeneration is triggered. */ React.useEffect(() => { diff --git a/src/reactComponents/Tabs.tsx b/src/reactComponents/Tabs.tsx index 501045ff..d817cc2b 100644 --- a/src/reactComponents/Tabs.tsx +++ b/src/reactComponents/Tabs.tsx @@ -62,7 +62,6 @@ export interface TabsProps { storage: commonStorage.Storage | null; theme: string; shownPythonToolboxCategories: Set; - modulePathToContentText: {[modulePath: string]: string}; messageApi: MessageInstance; } @@ -429,7 +428,6 @@ export const Component = React.forwardRef((props, ref): Reac storage={props.storage} theme={props.theme} shownPythonToolboxCategories={props.shownPythonToolboxCategories} - modulePathToContentText={props.modulePathToContentText} messageApi={props.messageApi} setAlertErrorMessage={props.setAlertErrorMessage} isActive={activeKey === tab.key}