diff --git a/src/App.tsx b/src/App.tsx index 971d69a2..ae738000 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -42,6 +42,7 @@ import * as CustomBlocks from './blocks/setup_custom_blocks'; import { initialize as initializePythonBlocks } from './blocks/utils/python'; import * as ChangeFramework from './blocks/utils/change_framework' import { mutatorOpenListener } from './blocks/mrc_param_container' +import { TOOLBOX_UPDATE_EVENT } from './blocks/mrc_mechanism_component_holder'; /** Storage key for shown toolbox categories. */ const SHOWN_TOOLBOX_CATEGORIES_KEY = 'shownPythonToolboxCategories'; @@ -253,6 +254,22 @@ const App: React.FC = (): React.JSX.Element => { return tabs; }; + /** Handles toolbox update requests from blocks */ + const handleToolboxUpdateRequest = React.useCallback(() => { + if (blocksEditor.current && currentModule) { + blocksEditor.current.updateToolbox(shownPythonToolboxCategories); + } + }, [currentModule, shownPythonToolboxCategories]); + + // Add event listener for toolbox updates + React.useEffect(() => { + window.addEventListener(TOOLBOX_UPDATE_EVENT, handleToolboxUpdateRequest); + + return () => { + window.removeEventListener(TOOLBOX_UPDATE_EVENT, handleToolboxUpdateRequest); + }; + }, [handleToolboxUpdateRequest]); + // Initialize storage and blocks when app loads React.useEffect(() => { openStorage(); @@ -269,13 +286,6 @@ const App: React.FC = (): React.JSX.Element => { } }, [currentModule]); - // Update toolbox when shown categories change - React.useEffect(() => { - if (blocksEditor.current) { - blocksEditor.current.updateToolbox(shownPythonToolboxCategories); - } - }, [shownPythonToolboxCategories]); - // Initialize Blockly workspace and editor when component and storage are ready React.useEffect(() => { if (!blocklyComponent.current || !storage) { diff --git a/src/blocks/mrc_mechanism_component_holder.ts b/src/blocks/mrc_mechanism_component_holder.ts index 39dd7541..01c6f03c 100644 --- a/src/blocks/mrc_mechanism_component_holder.ts +++ b/src/blocks/mrc_mechanism_component_holder.ts @@ -37,6 +37,8 @@ export const BLOCK_NAME = 'mrc_mechanism_component_holder'; export const MECHANISM = 'mechanism'; export const COMPONENT = 'component'; +export const TOOLBOX_UPDATE_EVENT = 'toolbox-update-requested'; + type MechanismComponentHolderExtraState = { hideMechanisms?: boolean; } @@ -115,17 +117,33 @@ const MECHANISM_COMPONENT_HOLDER = { let blockMoveEvent = blockEvent as Blockly.Events.BlockMove; if (blockMoveEvent.reason?.includes('connect')) { setName(block); + updateToolboxAfterDelay(); } } else { if (blockEvent.type == Blockly.Events.BLOCK_CHANGE) { setName(block); + updateToolboxAfterDelay(); } } }, } +let toolboxUpdateTimeout: NodeJS.Timeout | null = null; +export function updateToolboxAfterDelay(){ + if (toolboxUpdateTimeout) { + clearTimeout(toolboxUpdateTimeout); + } + toolboxUpdateTimeout = setTimeout(() => { + const event = new CustomEvent(TOOLBOX_UPDATE_EVENT, { + detail: { timestamp: Date.now() } + }); + window.dispatchEvent(event); + toolboxUpdateTimeout = null; + }, 100); +} + export const setup = function () { Blockly.Blocks[BLOCK_NAME] = MECHANISM_COMPONENT_HOLDER; } diff --git a/src/toolbox/blocks_components.ts b/src/toolbox/blocks_components.ts index 84372a5a..f1e2ec83 100644 --- a/src/toolbox/blocks_components.ts +++ b/src/toolbox/blocks_components.ts @@ -16,19 +16,35 @@ */ /** - * @author alan@porpoiseful.com (Alan Smith) + * @fileoverview Component blocks for the toolbox. */ + import * as ToolboxItems from './items'; import * as ColorSensor from './hardware_components/color_sensor'; import * as SmartMotor from './hardware_components/smart_motor'; -import * as TouchSensor from './hardware_components/touch_sensor'; import * as Servo from './hardware_components/servo'; +import * as TouchSensor from './hardware_components/touch_sensor'; + +const ALL_COMPONENTS: Record ToolboxItems.ContentsType[]> = { + [ColorSensor.TYPE_NAME]: ColorSensor.getBlocks, + [SmartMotor.TYPE_NAME]: SmartMotor.getBlocks, + [TouchSensor.TYPE_NAME]: TouchSensor.getBlocks, + [Servo.TYPE_NAME]: Servo.getBlocks, +}; + +export function getAllPossibleComponents(hideParams: boolean): ToolboxItems.ContentsType[] { + return [ + SmartMotor.getDefinitionBlock(hideParams), + TouchSensor.getDefinitionBlock(hideParams), + ColorSensor.getDefinitionBlock(hideParams), + Servo.getDefinitionBlock(hideParams), + ]; +} -export function getAllPossibleComponents(hideParams : boolean): ToolboxItems.ContentsType[] { - return [ - SmartMotor.getDefinitionBlock(hideParams), - TouchSensor.getDefinitionBlock(hideParams), - ColorSensor.getDefinitionBlock(hideParams), - Servo.getDefinitionBlock(hideParams), - ]; +export function getBlocks(componentType: string, componentName: string): ToolboxItems.ContentsType[] { + const getBlocksFunction = ALL_COMPONENTS[componentType]; + if (getBlocksFunction) { + return getBlocksFunction(componentName); + } + return []; } diff --git a/src/toolbox/hardware_category.ts b/src/toolbox/hardware_category.ts index f5b9d8c2..61b2f490 100644 --- a/src/toolbox/hardware_category.ts +++ b/src/toolbox/hardware_category.ts @@ -29,9 +29,8 @@ import * as Blockly from 'blockly/core'; import * as toolboxItems from './items'; import * as commonStorage from '../storage/common_storage'; import { getAllPossibleMechanisms } from './blocks_mechanisms'; -import { getAllPossibleComponents } from './blocks_components'; -import * as SmartMotor from './hardware_components/smart_motor'; -import * as TouchSensor from './hardware_components/touch_sensor'; +import { getAllPossibleComponents, getBlocks } from './blocks_components'; +import * as MechanismComponentHolder from '../blocks/mrc_mechanism_component_holder'; export function getHardwareCategory(currentModule: commonStorage.Module) { if (currentModule.moduleType === commonStorage.MODULE_TYPE_OPMODE) { @@ -243,22 +242,46 @@ function getRobotMethodsBlocks(currentModule: commonStorage.Module) { function getComponentsBlocks(currentModule: commonStorage.Module) { const contents = []; + + // Add the "+ Component" category contents.push({ kind: 'category', name: '+ Component', contents: getAllPossibleComponents(true) }); - contents.push({ - kind: 'category', - name: 'my_motor', - contents: SmartMotor.getBlocks('my_motor') - }, - { - kind: 'category', - name: 'my_touch_sensor', - contents: TouchSensor.getBlocks('my_touch_sensor') - }, - ); + + // Get components from the current workspace + const workspace = Blockly.getMainWorkspace(); + if (workspace) { + const holderBlocks = workspace.getBlocksByType(MechanismComponentHolder.BLOCK_NAME); + + holderBlocks.forEach(holderBlock => { + // Get component blocks from the COMPONENTS input + const componentsInput = holderBlock.getInput('COMPONENTS'); + if (componentsInput && componentsInput.connection) { + let componentBlock = componentsInput.connection.targetBlock(); + + // Walk through all connected component blocks + while (componentBlock) { + if (componentBlock.type === 'mrc_component') { + const componentName = componentBlock.getFieldValue('NAME'); + const componentType = componentBlock.getFieldValue('TYPE'); + + if (componentName && componentType) { + // Get the blocks for this specific component + contents.push({ + kind: 'category', + name: componentName, + contents: getBlocks(componentType, componentName), + }); + } + } + // Move to the next block in the chain + componentBlock = componentBlock.getNextBlock(); + } + } + }); + } return { kind: 'category', name: 'Components',