From 7356de75a0a29adc0e3b049825509c8098d34f0b Mon Sep 17 00:00:00 2001 From: Liz Looney Date: Mon, 27 Oct 2025 22:02:51 -0700 Subject: [PATCH 1/4] Added src/blocks/utils/workspaces.ts, which contains functions related to keeping track of blockly workspaces. Modified editor to call workspaces.addWorkspace and workspaces.removeWorkspace Modified existing code that use headless blockly workspaces to call workspaces.createHeadlessWorkspace and workspaces.destroyHeadlessWorkspace. --- src/blocks/utils/workspaces.ts | 53 ++++++++++++++++++++++++++++++ src/editor/editor.ts | 5 ++- src/storage/create_python_files.ts | 5 +-- src/storage/module_content.ts | 5 +-- src/storage/upgrade_project.ts | 14 +++++--- src/toolbox/toolbox_tests.ts | 17 ++++++---- 6 files changed, 83 insertions(+), 16 deletions(-) create mode 100644 src/blocks/utils/workspaces.ts diff --git a/src/blocks/utils/workspaces.ts b/src/blocks/utils/workspaces.ts new file mode 100644 index 00000000..30cd0d03 --- /dev/null +++ b/src/blocks/utils/workspaces.ts @@ -0,0 +1,53 @@ +/** + * @license + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @author lizlooney@google.com (Liz Looney) + */ + +import * as Blockly from 'blockly/core'; +import * as storageModule from '../../storage/module'; + +const workspaceIdToModuleType: { [workspaceId: string]: storageModule.ModuleType } = {}; + +export function addWorkspace(workspace: Blockly.Workspace, moduleType: storageModule.ModuleType): void { + workspaceIdToModuleType[workspace.id] = moduleType; +} + +export function removeWorkspace(workspace: Blockly.Workspace): void { + if (workspace.id in workspaceIdToModuleType) { + delete workspaceIdToModuleType[workspace.id]; + } +} + +export function getModuleTypeForWorkspace(workspace: Blockly.Workspace): storageModule.ModuleType { + if (workspace.id in workspaceIdToModuleType) { + return workspaceIdToModuleType[workspace.id]; + } + throw new Error('getModuleTypeForWorkspace: workspaceId not found: ' + workspace.id); +} + +export function createHeadlessWorkspace(moduleType: storageModule.ModuleType): Blockly.Workspace { + const workspace = new Blockly.Workspace(); + addWorkspace(workspace, moduleType); + return workspace; +} + +export function destroyHeadlessWorkspace(workspace: Blockly.Workspace): void { + removeWorkspace(workspace); + workspace.dispose(); +} diff --git a/src/editor/editor.ts b/src/editor/editor.ts index 50d7727c..91228e8e 100644 --- a/src/editor/editor.ts +++ b/src/editor/editor.ts @@ -30,6 +30,7 @@ import * as storageProject from '../storage/project'; import * as eventHandler from '../blocks/mrc_event_handler'; import * as classMethodDef from '../blocks/mrc_class_method_def'; import * as mechanismComponentHolder from '../blocks/mrc_mechanism_component_holder'; +import * as workspaces from '../blocks/utils/workspaces'; //import { testAllBlocksInToolbox } from '../toolbox/toolbox_tests'; import { applyExpandedCategories, getToolboxJSON } from '../toolbox/toolbox'; @@ -70,6 +71,7 @@ export class Editor { project: storageProject.Project, storage: commonStorage.Storage, modulePathToContentText: {[modulePath: string]: string}) { + workspaces.addWorkspace(blocklyWorkspace, module.moduleType); this.blocklyWorkspace = blocklyWorkspace; this.module = module; this.projectName = project.projectName; @@ -186,6 +188,7 @@ export class Editor { } public abandon(): void { + workspaces.removeWorkspace(this.blocklyWorkspace); if (Editor.currentEditor === this) { Editor.currentEditor = null; } @@ -250,7 +253,7 @@ export class Editor { if (toolbox != this.toolbox) { this.toolbox = toolbox; this.blocklyWorkspace.updateToolbox(toolbox); - // testAllBlocksInToolbox(toolbox); + // testAllBlocksInToolbox(toolbox, this.module.moduleType); } } diff --git a/src/storage/create_python_files.ts b/src/storage/create_python_files.ts index 8f591aa5..885f43e7 100644 --- a/src/storage/create_python_files.ts +++ b/src/storage/create_python_files.ts @@ -22,6 +22,7 @@ */ import * as Blockly from 'blockly/core'; +import * as workspaces from '../blocks/utils/workspaces'; import { extendedPythonGenerator } from '../editor/extended_python_generator'; import { Storage } from './common_storage'; import { Module } from './module'; @@ -61,7 +62,7 @@ async function generatePythonForModule(module: Module, storage: Storage): Promis const moduleContent = parseModuleContentText(moduleContentText); // Create a headless workspace - const workspace = new Blockly.Workspace(); + const workspace = workspaces.createHeadlessWorkspace(module.moduleType); // Parse and load the JSON into the workspace const blocks = moduleContent.getBlocks(); @@ -71,7 +72,7 @@ async function generatePythonForModule(module: Module, storage: Storage): Promis const pythonCode = extendedPythonGenerator.mrcWorkspaceToCode(workspace, module); // Clean up the workspace - workspace.dispose(); + workspaces.destroyHeadlessWorkspace(workspace); return { moduleName: moduleName, diff --git a/src/storage/module_content.ts b/src/storage/module_content.ts index 61a32eed..8969e774 100644 --- a/src/storage/module_content.ts +++ b/src/storage/module_content.ts @@ -25,6 +25,7 @@ import * as storageNames from './names'; import startingOpModeBlocks from '../modules/opmode_start.json'; import startingMechanismBlocks from '../modules/mechanism_start.json'; import startingRobotBlocks from '../modules/robot_start.json'; +import * as workspaces from '../blocks/utils/workspaces'; export type MethodArg = { name: string, @@ -253,7 +254,7 @@ export class ModuleContent { if (Object.keys(oldIdToNewId).length) { // Change the ids in the blocks. - const workspace = new Blockly.Workspace(); + const workspace = workspaces.createHeadlessWorkspace(this.moduleType); Blockly.serialization.workspaces.load(this.blocks, workspace); workspace.getAllBlocks().forEach(block => { if ('mrcChangeIds' in block && typeof block.mrcChangeIds === "function") { @@ -263,7 +264,7 @@ export class ModuleContent { this.blocks = Blockly.serialization.workspaces.save(workspace); // Clean up the workspace - workspace.dispose(); + workspaces.destroyHeadlessWorkspace(workspace); } } } diff --git a/src/storage/upgrade_project.ts b/src/storage/upgrade_project.ts index 1804d9c7..5b148256 100644 --- a/src/storage/upgrade_project.ts +++ b/src/storage/upgrade_project.ts @@ -29,6 +29,7 @@ import * as storageModuleContent from './module_content'; import * as storageNames from './names'; import * as storageProject from './project'; import { ClassMethodDefBlock, BLOCK_NAME as MRC_CLASS_METHOD_DEF_BLOCK_NAME } from '../blocks/mrc_class_method_def'; +import * as workspaces from '../blocks/utils/workspaces'; export const NO_VERSION = '0.0.0'; export const CURRENT_VERSION = '0.0.3'; @@ -82,14 +83,16 @@ async function upgradeFrom_001_to_002( // mrc_mechanism_component_holder block. const moduleContent = storageModuleContent.parseModuleContentText(moduleContentText); let blocks = moduleContent.getBlocks(); + // Create a temporary workspace to upgrade the blocks. - const headlessWorkspace = new Blockly.Workspace(); + const headlessWorkspace = workspaces.createHeadlessWorkspace(storageModule.ModuleType.ROBOT); + try { Blockly.serialization.workspaces.load(blocks, headlessWorkspace); mechanismComponentHolder.hidePrivateComponents(headlessWorkspace); blocks = Blockly.serialization.workspaces.save(headlessWorkspace); } finally { - headlessWorkspace.dispose(); + workspaces.destroyHeadlessWorkspace(headlessWorkspace); } moduleContent.setBlocks(blocks); moduleContentText = moduleContent.getModuleContentText(); @@ -116,8 +119,9 @@ async function upgradeFrom_002_to_003( const moduleContent = storageModuleContent.parseModuleContentText(moduleContentText); let blocks = moduleContent.getBlocks(); - // Create a temporary workspace to upgrade the blocks - const headlessWorkspace = new Blockly.Workspace(); + // Create a temporary workspace to upgrade the blocks. + const headlessWorkspace = workspaces.createHeadlessWorkspace(storageModule.ModuleType.ROBOT); + try { Blockly.serialization.workspaces.load(blocks, headlessWorkspace); @@ -127,7 +131,7 @@ async function upgradeFrom_002_to_003( }); blocks = Blockly.serialization.workspaces.save(headlessWorkspace); } finally { - headlessWorkspace.dispose(); + workspaces.destroyHeadlessWorkspace(headlessWorkspace); } moduleContent.setBlocks(blocks); diff --git a/src/toolbox/toolbox_tests.ts b/src/toolbox/toolbox_tests.ts index 9b0d7d1d..4001288f 100644 --- a/src/toolbox/toolbox_tests.ts +++ b/src/toolbox/toolbox_tests.ts @@ -22,13 +22,15 @@ import * as Blockly from 'blockly/core'; import { extendedPythonGenerator } from '../editor/extended_python_generator'; import * as toolboxItems from './items'; +import * as storageModule from '../storage/module'; +import * as workspaces from '../blocks/utils/workspaces'; // Tests -export function testAllBlocksInToolbox(toolbox : Blockly.utils.toolbox.ToolboxInfo) { - const contents = toolbox.contents; +export function testAllBlocksInToolbox(toolbox : Blockly.utils.toolbox.ToolboxInfo, moduleType: storageModule.ModuleType) { + const contents = toolbox.contents; alert('Press OK to run tests on all blocks from the toolbox.'); - const toolboxTestData = new ToolboxTestData(contents, () => { + const toolboxTestData = new ToolboxTestData(contents, moduleType, () => { alert('Completed tests on all blocks in the toolbox. See console for any errors.'); }); toolboxTestData.runTests(); @@ -40,9 +42,12 @@ class ToolboxTestData { jsonBlocks: toolboxItems.Block[]; index: number; - constructor(contents: toolboxItems.ContentsType[] | undefined, onFinish: () => void) { + constructor( + contents: toolboxItems.ContentsType[] | undefined, + moduleType: storageModule.ModuleType, + onFinish: () => void) { this.onFinish = onFinish; - this.blocklyWorkspace = new Blockly.Workspace(); + this.blocklyWorkspace = workspaces.createHeadlessWorkspace(moduleType); this.blocklyWorkspace.MAX_UNDO = 0; this.jsonBlocks = []; if (contents){ @@ -95,7 +100,7 @@ class ToolboxTestData { if (this.index < this.jsonBlocks.length) { setTimeout(this.testCallback.bind(this), 0); } else { - this.blocklyWorkspace.dispose(); + workspaces.destroyHeadlessWorkspace(this.blocklyWorkspace); if (this.onFinish) { this.onFinish(); } From a820f3dc0b96629b3ecbce46c1c8bf010c70f44a Mon Sep 17 00:00:00 2001 From: Liz Looney Date: Mon, 27 Oct 2025 22:48:24 -0700 Subject: [PATCH 2/4] Pass editor to mrcOnLoad and mrcOnModuleCurrent functions. --- src/blocks/mrc_call_python_function.ts | 69 ++++---- src/blocks/mrc_component.ts | 2 +- src/blocks/mrc_event.ts | 3 +- src/blocks/mrc_event_handler.ts | 167 +++++++++---------- src/blocks/mrc_mechanism.ts | 99 ++++++----- src/blocks/mrc_mechanism_component_holder.ts | 3 +- src/editor/editor.ts | 4 +- 7 files changed, 168 insertions(+), 179 deletions(-) diff --git a/src/blocks/mrc_call_python_function.ts b/src/blocks/mrc_call_python_function.ts index 24a53679..a688c384 100644 --- a/src/blocks/mrc_call_python_function.ts +++ b/src/blocks/mrc_call_python_function.ts @@ -549,66 +549,59 @@ const CALL_PYTHON_FUNCTION = { } this.updateBlock_(); }, - getComponents: function(this: CallPythonFunctionBlock): storageModuleContent.Component[] { + getComponents: function(this: CallPythonFunctionBlock, editor: Editor): storageModuleContent.Component[] { // Get the list of components whose type matches this.mrcComponentClassName. const components: storageModuleContent.Component[] = []; - const editor = Editor.getEditorForBlocklyWorkspace(this.workspace, true /* returnCurrentIfNotFound */); - if (editor) { - let componentsToConsider: storageModuleContent.Component[] = []; - if (this.mrcMechanismId) { - // Only consider components that belong to the mechanism. - // this.mrcMechanismId is the mechanismId from the MechanismInRobot. - // We need to get the MechanismInRobot with that id, then get the mechanism, and then get - // the public components defined in that mechanism. - for (const mechanismInRobot of editor.getMechanismsFromRobot()) { - if (mechanismInRobot.mechanismId === this.mrcMechanismId) { - for (const mechanism of editor.getMechanisms()) { - if (mechanism.moduleId === mechanismInRobot.moduleId) { - componentsToConsider = editor.getComponentsFromMechanism(mechanism); - break; - } + let componentsToConsider: storageModuleContent.Component[] = []; + if (this.mrcMechanismId) { + // Only consider components that belong to the mechanism. + // this.mrcMechanismId is the mechanismId from the MechanismInRobot. + // We need to get the MechanismInRobot with that id, then get the mechanism, and then get + // the public components defined in that mechanism. + for (const mechanismInRobot of editor.getMechanismsFromRobot()) { + if (mechanismInRobot.mechanismId === this.mrcMechanismId) { + for (const mechanism of editor.getMechanisms()) { + if (mechanism.moduleId === mechanismInRobot.moduleId) { + componentsToConsider = editor.getComponentsFromMechanism(mechanism); + break; } - break; } + break; } - } else if (editor.getModuleType() === storageModule.ModuleType.MECHANISM) { - // Only consider components (regular and private) in the current workspace. - componentsToConsider = editor.getAllComponentsFromWorkspace(); - } else { - // Only consider components in the robot. - componentsToConsider = editor.getComponentsFromRobot(); } - componentsToConsider.forEach(component => { - if (component.className === this.mrcComponentClassName) { - components.push(component); - } - }); + } else if (editor.getModuleType() === storageModule.ModuleType.MECHANISM) { + // Only consider components (regular and private) in the current workspace. + componentsToConsider = editor.getAllComponentsFromWorkspace(); + } else { + // Only consider components in the robot. + componentsToConsider = editor.getComponentsFromRobot(); } + componentsToConsider.forEach(component => { + if (component.className === this.mrcComponentClassName) { + components.push(component); + } + }); return components; }, /** * mrcOnModuleCurrent is called for each CallPythonFunctionBlock when the module becomes the current module. */ - mrcOnModuleCurrent: function(this: CallPythonFunctionBlock): void { - this.checkFunction(); + mrcOnModuleCurrent: function(this: CallPythonFunctionBlock, editor: Editor): void { + this.checkFunction(editor); }, /** * mrcOnLoad is called for each CallPythonFunctionBlock when the blocks are loaded in the blockly * workspace. */ - mrcOnLoad: function(this: CallPythonFunctionBlock): void { - this.checkFunction(); + mrcOnLoad: function(this: CallPythonFunctionBlock, editor: Editor): void { + this.checkFunction(editor); }, /** * checkFunction checks the block, updates it, and/or adds a warning balloon if necessary. * It is called from mrcOnModuleCurrent and mrcOnLoad above. */ - checkFunction: function(this: CallPythonFunctionBlock): void { - const editor = Editor.getEditorForBlocklyWorkspace(this.workspace, true /* returnCurrentIfNotFound */); - if (!editor) { - return; - } + checkFunction: function(this: CallPythonFunctionBlock, editor: Editor): void { const warnings: string[] = []; // If this block is calling a component method, check whether the component @@ -621,7 +614,7 @@ const CALL_PYTHON_FUNCTION = { if (this.mrcFunctionKind === FunctionKind.INSTANCE_COMPONENT) { const componentNames: string[] = []; this.mrcMapComponentNameToId = {} - this.getComponents().forEach(component => { + this.getComponents(editor).forEach(component => { componentNames.push(component.name); this.mrcMapComponentNameToId[component.name] = component.componentId; }); diff --git a/src/blocks/mrc_component.ts b/src/blocks/mrc_component.ts index 689c5324..4bc9c325 100644 --- a/src/blocks/mrc_component.ts +++ b/src/blocks/mrc_component.ts @@ -192,7 +192,7 @@ const COMPONENT = { /** * mrcOnLoad is called for each ComponentBlock when the blocks are loaded in the blockly workspace. */ - mrcOnLoad: function(this: ComponentBlock): void { + mrcOnLoad: function(this: ComponentBlock, _editor: Editor): void { this.checkBlockIsInHolder(); }, /** diff --git a/src/blocks/mrc_event.ts b/src/blocks/mrc_event.ts index 51752a98..70a2e9e0 100644 --- a/src/blocks/mrc_event.ts +++ b/src/blocks/mrc_event.ts @@ -24,6 +24,7 @@ import * as Blockly from 'blockly'; import { MRC_STYLE_EVENTS } from '../themes/styles' import { createFieldNonEditableText } from '../fields/FieldNonEditableText'; import { Parameter } from './mrc_class_method_def'; +import { Editor } from '../editor/editor'; import { ExtendedPythonGenerator } from '../editor/extended_python_generator'; import * as paramContainer from './mrc_param_container' import { @@ -196,7 +197,7 @@ const EVENT = { /** * mrcOnLoad is called for each EventBlock when the blocks are loaded in the blockly workspace. */ - mrcOnLoad: function(this: EventBlock): void { + mrcOnLoad: function(this: EventBlock, _editor: Editor): void { this.checkBlockIsInHolder(); }, /** diff --git a/src/blocks/mrc_event_handler.ts b/src/blocks/mrc_event_handler.ts index 3bb0aac2..abb5b002 100644 --- a/src/blocks/mrc_event_handler.ts +++ b/src/blocks/mrc_event_handler.ts @@ -170,114 +170,111 @@ const EVENT_HANDLER = { /** * mrcOnModuleCurrent is called for each EventHandlerBlock when the module becomes the current module. */ - mrcOnModuleCurrent: function(this: EventHandlerBlock): void { - this.checkEvent(); + mrcOnModuleCurrent: function(this: EventHandlerBlock, editor: Editor): void { + this.checkEvent(editor); }, /** * mrcOnLoad is called for each EventHandlerBlock when the blocks are loaded in the blockly * workspace. */ - mrcOnLoad: function(this: EventHandlerBlock): void { - this.checkEvent(); + mrcOnLoad: function(this: EventHandlerBlock, editor: Editor): void { + this.checkEvent(editor); }, /** * checkEvent checks the block, updates it, and/or adds a warning balloon if necessary. * It is called from mrcOnModuleCurrent and mrcOnLoad above. */ - checkEvent: function(this: EventHandlerBlock): void { + checkEvent: function(this: EventHandlerBlock, editor: Editor): void { const warnings: string[] = []; - const editor = Editor.getEditorForBlocklyWorkspace(this.workspace, true /* returnCurrentIfNotFound */); - if (editor) { - if (this.mrcSenderType === SenderType.ROBOT) { - // This block is an event handler for a robot event. - // Check whether the robot event still exists and whether it has been changed. - // If the robot event doesn't exist, put a visible warning on this block. - // If the robot event has changed, update the block if possible or put a - // visible warning on it. - let foundRobotEvent = false; - const robotEvents = editor.getEventsFromRobot(); - for (const robotEvent of robotEvents) { - if (robotEvent.eventId === this.mrcEventId) { - foundRobotEvent = true; - if (this.getFieldValue(FIELD_EVENT_NAME) !== robotEvent.name) { - this.setFieldValue(robotEvent.name, FIELD_EVENT_NAME); - } - this.mrcParameters = []; - robotEvent.args.forEach(arg => { - this.mrcParameters.push({ - name: arg.name, - type: arg.type, - }); + if (this.mrcSenderType === SenderType.ROBOT) { + // This block is an event handler for a robot event. + // Check whether the robot event still exists and whether it has been changed. + // If the robot event doesn't exist, put a visible warning on this block. + // If the robot event has changed, update the block if possible or put a + // visible warning on it. + let foundRobotEvent = false; + const robotEvents = editor.getEventsFromRobot(); + for (const robotEvent of robotEvents) { + if (robotEvent.eventId === this.mrcEventId) { + foundRobotEvent = true; + if (this.getFieldValue(FIELD_EVENT_NAME) !== robotEvent.name) { + this.setFieldValue(robotEvent.name, FIELD_EVENT_NAME); + } + this.mrcParameters = []; + robotEvent.args.forEach(arg => { + this.mrcParameters.push({ + name: arg.name, + type: arg.type, }); - this.mrcUpdateParams(); + }); + this.mrcUpdateParams(); - // Since we found the robot event, we can break out of the loop. - break; - } - } - if (!foundRobotEvent) { - warnings.push(Blockly.Msg.EVENT_HANDLER_ROBOT_EVENT_NOT_FOUND); + // Since we found the robot event, we can break out of the loop. + break; } } + if (!foundRobotEvent) { + warnings.push(Blockly.Msg.EVENT_HANDLER_ROBOT_EVENT_NOT_FOUND); + } + } - if (this.mrcSenderType === SenderType.MECHANISM) { - // This block is an event handler for a mechanism event. - // Check whether the mechanism still exists, whether it has been - // changed, whether the event still exists, and whether the event has - // been changed. - // If the mechanism doesn't exist, put a visible warning on this block. - // If the mechanism has changed, update the block if possible or put a - // visible warning on it. - // If the event doesn't exist, put a visible warning on this block. - // If the event has changed, update the block if possible or put a - // visible warning on it. - let foundMechanism = false; - const mechanismsInRobot = editor.getMechanismsFromRobot(); - for (const mechanismInRobot of mechanismsInRobot) { - if (mechanismInRobot.mechanismId === this.mrcMechanismId) { - foundMechanism = true; - - // If the mechanism name has changed, we can handle that. - if (this.getFieldValue(FIELD_SENDER) !== mechanismInRobot.name) { - this.setFieldValue(mechanismInRobot.name, FIELD_SENDER); - } + if (this.mrcSenderType === SenderType.MECHANISM) { + // This block is an event handler for a mechanism event. + // Check whether the mechanism still exists, whether it has been + // changed, whether the event still exists, and whether the event has + // been changed. + // If the mechanism doesn't exist, put a visible warning on this block. + // If the mechanism has changed, update the block if possible or put a + // visible warning on it. + // If the event doesn't exist, put a visible warning on this block. + // If the event has changed, update the block if possible or put a + // visible warning on it. + let foundMechanism = false; + const mechanismsInRobot = editor.getMechanismsFromRobot(); + for (const mechanismInRobot of mechanismsInRobot) { + if (mechanismInRobot.mechanismId === this.mrcMechanismId) { + foundMechanism = true; + + // If the mechanism name has changed, we can handle that. + if (this.getFieldValue(FIELD_SENDER) !== mechanismInRobot.name) { + this.setFieldValue(mechanismInRobot.name, FIELD_SENDER); + } - let foundMechanismEvent = false; - const mechanism = editor.getMechanism(mechanismInRobot); - const mechanismEvents: storageModuleContent.Event[] = mechanism - ? editor.getEventsFromMechanism(mechanism) : []; - for (const mechanismEvent of mechanismEvents) { - if (mechanismEvent.eventId === this.mrcEventId) { - foundMechanismEvent = true; - if (this.getFieldValue(FIELD_EVENT_NAME) !== mechanismEvent.name) { - this.setFieldValue(mechanismEvent.name, FIELD_EVENT_NAME); - } - - this.mrcParameters = []; - mechanismEvent.args.forEach(arg => { - this.mrcParameters.push({ - name: arg.name, - type: arg.type, - }); + let foundMechanismEvent = false; + const mechanism = editor.getMechanism(mechanismInRobot); + const mechanismEvents: storageModuleContent.Event[] = mechanism + ? editor.getEventsFromMechanism(mechanism) : []; + for (const mechanismEvent of mechanismEvents) { + if (mechanismEvent.eventId === this.mrcEventId) { + foundMechanismEvent = true; + if (this.getFieldValue(FIELD_EVENT_NAME) !== mechanismEvent.name) { + this.setFieldValue(mechanismEvent.name, FIELD_EVENT_NAME); + } + + this.mrcParameters = []; + mechanismEvent.args.forEach(arg => { + this.mrcParameters.push({ + name: arg.name, + type: arg.type, }); - this.mrcUpdateParams(); + }); + this.mrcUpdateParams(); - // Since we found the mechanism event, we can break out of the loop. - break; - } + // Since we found the mechanism event, we can break out of the loop. + break; } - if (!foundMechanismEvent) { - warnings.push(Blockly.Msg.EVENT_HANDLER_MECHANISM_EVENT_NOT_FOUND); - } - - // Since we found the mechanism, we can break out of the loop. - break; } + if (!foundMechanismEvent) { + warnings.push(Blockly.Msg.EVENT_HANDLER_MECHANISM_EVENT_NOT_FOUND); + } + + // Since we found the mechanism, we can break out of the loop. + break; } - if (!foundMechanism) { - warnings.push(Blockly.Msg.EVENT_HANDLER_MECHANISM_NOT_FOUND); - } + } + if (!foundMechanism) { + warnings.push(Blockly.Msg.EVENT_HANDLER_MECHANISM_NOT_FOUND); } } diff --git a/src/blocks/mrc_mechanism.ts b/src/blocks/mrc_mechanism.ts index c2dfa34d..62ea9c2a 100644 --- a/src/blocks/mrc_mechanism.ts +++ b/src/blocks/mrc_mechanism.ts @@ -193,16 +193,16 @@ const MECHANISM = { /** * mrcOnModuleCurrent is called for each MechanismBlock when the module becomes the current module. */ - mrcOnModuleCurrent: function(this: MechanismBlock): void { - this.checkMechanism(); + mrcOnModuleCurrent: function(this: MechanismBlock, editor: Editor): void { + this.checkMechanism(editor); }, /** * mrcOnLoad is called for each MechanismBlock when the blocks are loaded in the blockly * workspace. */ - mrcOnLoad: function(this: MechanismBlock): void { + mrcOnLoad: function(this: MechanismBlock, editor: Editor): void { this.checkBlockIsInHolder(); - this.checkMechanism(); + this.checkMechanism(editor); }, /** * mrcOnMove is called when a MechanismBlock is moved. @@ -240,62 +240,59 @@ const MECHANISM = { * checkMechanism checks the block, updates it, and/or adds a warning balloon if necessary. * It is called from mrcOnModuleCurrent and mrcOnLoad above. */ - checkMechanism: function(this: MechanismBlock): void { + checkMechanism: function(this: MechanismBlock, editor: Editor): void { const warnings: string[] = []; - const editor = Editor.getEditorForBlocklyWorkspace(this.workspace, true /* returnCurrentIfNotFound */); - if (editor) { - // Find the mechanism. - let foundMechanism: storageModule.Mechanism | null = null; + // Find the mechanism. + let foundMechanism: storageModule.Mechanism | null = null; - if (this.mrcMechanismModuleId) { - // Find the mechanism by module id. - for (const mechanism of editor.getMechanisms()) { - if (mechanism.moduleId === this.mrcMechanismModuleId) { - foundMechanism = mechanism; - break; - } + if (this.mrcMechanismModuleId) { + // Find the mechanism by module id. + for (const mechanism of editor.getMechanisms()) { + if (mechanism.moduleId === this.mrcMechanismModuleId) { + foundMechanism = mechanism; + break; } - } else { - // Find the mechanism by class name. - const className = this.getFieldValue(FIELD_TYPE); - for (const mechanism of editor.getMechanisms()) { - if (mechanism.className === className) { - // Grap the mechanism module id, so we have it for next time. - this.mrcMechanismModuleId = mechanism.moduleId; - foundMechanism = mechanism; - break; - } + } + } else { + // Find the mechanism by class name. + const className = this.getFieldValue(FIELD_TYPE); + for (const mechanism of editor.getMechanisms()) { + if (mechanism.className === className) { + // Grap the mechanism module id, so we have it for next time. + this.mrcMechanismModuleId = mechanism.moduleId; + foundMechanism = mechanism; + break; } } + } - if (foundMechanism) { - // Here we need all the components (regular and private) from the mechanism because we need - // to create port parameters for all the components. - const components = editor.getAllComponentsFromMechanism(foundMechanism); + if (foundMechanism) { + // Here we need all the components (regular and private) from the mechanism because we need + // to create port parameters for all the components. + const components = editor.getAllComponentsFromMechanism(foundMechanism); - // If the mechanism class name has changed, update this blcok. - if (this.getFieldValue(FIELD_TYPE) !== foundMechanism.className) { - this.setFieldValue(foundMechanism.className, FIELD_TYPE); - } - const importModule = storageNames.pascalCaseToSnakeCase(foundMechanism.className); - if (this.mrcImportModule !== importModule) { - this.mrcImportModule = importModule; - } - this.mrcParameters = []; - components.forEach(component => { - for (const port in component.ports) { - this.mrcParameters.push({ - name: port, - type: component.ports[port], - }); - } - }); - this.updateBlock_(); - } else { - // Did not find the mechanism. - warnings.push(Blockly.Msg['MECHANISM_NOT_FOUND_WARNING']); + // If the mechanism class name has changed, update this blcok. + if (this.getFieldValue(FIELD_TYPE) !== foundMechanism.className) { + this.setFieldValue(foundMechanism.className, FIELD_TYPE); + } + const importModule = storageNames.pascalCaseToSnakeCase(foundMechanism.className); + if (this.mrcImportModule !== importModule) { + this.mrcImportModule = importModule; } + this.mrcParameters = []; + components.forEach(component => { + for (const port in component.ports) { + this.mrcParameters.push({ + name: port, + type: component.ports[port], + }); + } + }); + this.updateBlock_(); + } else { + // Did not find the mechanism. + warnings.push(Blockly.Msg['MECHANISM_NOT_FOUND_WARNING']); } if (warnings.length) { diff --git a/src/blocks/mrc_mechanism_component_holder.ts b/src/blocks/mrc_mechanism_component_holder.ts index e4797204..0c59c273 100644 --- a/src/blocks/mrc_mechanism_component_holder.ts +++ b/src/blocks/mrc_mechanism_component_holder.ts @@ -23,6 +23,7 @@ import * as Blockly from 'blockly'; import { MRC_STYLE_MECHANISMS } from '../themes/styles'; import { getLegalName } from './utils/python'; +import { Editor } from '../editor/editor'; import { ExtendedPythonGenerator } from '../editor/extended_python_generator'; import * as storageModule from '../storage/module'; import * as storageModuleContent from '../storage/module_content'; @@ -131,7 +132,7 @@ const MECHANISM_COMPONENT_HOLDER = { * mrcOnLoad is called for each MechanismComponentHolderBlock when the blocks are loaded in the blockly * workspace. */ - mrcOnLoad: function(this: MechanismComponentHolderBlock): void { + mrcOnLoad: function(this: MechanismComponentHolderBlock, _editor: Editor): void { this.collectDescendants(false); }, /** diff --git a/src/editor/editor.ts b/src/editor/editor.ts index 91228e8e..a6902b51 100644 --- a/src/editor/editor.ts +++ b/src/editor/editor.ts @@ -107,7 +107,7 @@ export class Editor { const block = this.blocklyWorkspace.getBlockById(id); if (block) { if (MRC_ON_LOAD in block && typeof block[MRC_ON_LOAD] === 'function') { - block[MRC_ON_LOAD](); + block[MRC_ON_LOAD](this); } } }); @@ -182,7 +182,7 @@ export class Editor { // Go through all the blocks in the workspace and call their mrcOnModuleCurrent method. this.blocklyWorkspace.getAllBlocks().forEach(block => { if (MRC_ON_MODULE_CURRENT in block && typeof block[MRC_ON_MODULE_CURRENT] === 'function') { - block[MRC_ON_MODULE_CURRENT](); + block[MRC_ON_MODULE_CURRENT](this); } }); } From afcb85e452f2d02c1af6a3800b51546b11c1fec3 Mon Sep 17 00:00:00 2001 From: Liz Looney Date: Mon, 27 Oct 2025 22:49:28 -0700 Subject: [PATCH 3/4] Modified mrc_component updateBlock_ function to call workspaces.getModuleTypeForWorkspace instead of trying to get the corresponding editor. If the block is in a headless workspace, there is no corresponding editor. --- src/blocks/mrc_component.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/blocks/mrc_component.ts b/src/blocks/mrc_component.ts index 4bc9c325..0bd5b1ef 100644 --- a/src/blocks/mrc_component.ts +++ b/src/blocks/mrc_component.ts @@ -26,6 +26,7 @@ import { MRC_STYLE_COMPONENTS } from '../themes/styles' import { createFieldNonEditableText } from '../fields/FieldNonEditableText'; import { Editor } from '../editor/editor'; import { ExtendedPythonGenerator } from '../editor/extended_python_generator'; +import { getModuleTypeForWorkspace } from './utils/workspaces'; import { getAllowedTypesForSetCheck, getClassData, getSubclassNames } from './utils/python'; import * as toolboxItems from '../toolbox/items'; import * as storageModule from '../storage/module'; @@ -142,8 +143,8 @@ const COMPONENT = { * Update the block to reflect the newly loaded extra state. */ updateBlock_: function (this: ComponentBlock): void { - const editor = Editor.getEditorForBlocklyWorkspace(this.workspace, true /* returnCurrentIfNotFound */); - if (editor && editor.getModuleType() === storageModule.ModuleType.ROBOT) { + const moduleType = getModuleTypeForWorkspace(this.workspace); + if (moduleType === storageModule.ModuleType.ROBOT) { // Add input sockets for the arguments. for (let i = 0; i < this.mrcArgs.length; i++) { const input = this.appendValueInput('ARG' + i) From 166172820b56d6a66505349096288880d1803331 Mon Sep 17 00:00:00 2001 From: Liz Looney Date: Mon, 27 Oct 2025 22:54:57 -0700 Subject: [PATCH 4/4] Removed opt_returnCurrentIfNotFound parameter from getEditorForBlocklyWorkspace. --- src/editor/editor.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/editor/editor.ts b/src/editor/editor.ts index a6902b51..053ce2ff 100644 --- a/src/editor/editor.ts +++ b/src/editor/editor.ts @@ -572,7 +572,7 @@ export class Editor { throw new Error('getMethodsFromMechanism: mechanism not found: ' + mechanism.className); } - public static getEditorForBlocklyWorkspace(workspace: Blockly.Workspace, opt_returnCurrentIfNotFound?: boolean): Editor | null { + public static getEditorForBlocklyWorkspace(workspace: Blockly.Workspace): Editor | null { if (workspace.id in Editor.workspaceIdToEditor) { return Editor.workspaceIdToEditor[workspace.id]; } @@ -585,7 +585,8 @@ export class Editor { return Editor.workspaceIdToEditor[rootWorkspace.id]; } - return opt_returnCurrentIfNotFound ? Editor.currentEditor : null; + console.error('getEditorForBlocklyWorkspace: workspace with id ' + workspace.id + ' is not associated with an editor.'); + return null; } public static getEditorForBlocklyWorkspaceId(workspaceId: string): Editor | null {