diff --git a/src/editor/editor.ts b/src/editor/editor.ts index 3f4b5ac7..1af14842 100644 --- a/src/editor/editor.ts +++ b/src/editor/editor.ts @@ -24,7 +24,10 @@ import * as Blockly from 'blockly/core'; import { extendedPythonGenerator } from './extended_python_generator'; import { GeneratorContext } from './generator_context'; import * as commonStorage from '../storage/common_storage'; -import { getToolboxJSON } from '../toolbox/toolbox'; +import * as toolboxOpmode from '../toolbox/toolbox_opmode'; +import * as toolboxMechanism from '../toolbox/toolbox_mechanism'; +import * as toolboxRobot from '../toolbox/toolbox_robot'; + //import { testAllBlocksInToolbox } from '../toolbox/toolbox_tests'; import { MethodsCategory} from '../toolbox/methods_category'; @@ -166,7 +169,7 @@ export class Editor { if (toolbox != this.toolbox) { this.toolbox = toolbox; this.blocklyWorkspace.updateToolbox(toolbox); - //testAllBlocksInToolbox(); + // testAllBlocksInToolbox(toolbox); } } @@ -182,13 +185,6 @@ export class Editor { public updateToolbox(shownPythonToolboxCategories: Set): void { if (this.currentModule) { - if (this.currentModule.moduleType === commonStorage.MODULE_TYPE_PROJECT) { - // If we are editing a Project, we don't add any additional blocks to - // the toolbox. - this.setToolbox(getToolboxJSON([], shownPythonToolboxCategories)); - return; - } - // Otherwise, we add the exported blocks from the Project. if (!this.projectContent) { // The Project content hasn't been fetched yet. Try again in a bit. setTimeout(() => { @@ -196,9 +192,23 @@ export class Editor { }, 50); return; } - const robotBlocks = commonStorage.extractExportedBlocks( - this.currentModule.projectName, this.projectContent); - this.setToolbox(getToolboxJSON(robotBlocks, shownPythonToolboxCategories)); + switch(this.currentModule.moduleType){ + case commonStorage.MODULE_TYPE_PROJECT: + this.setToolbox(toolboxRobot.getToolboxJSON(shownPythonToolboxCategories)); + break; + case commonStorage.MODULE_TYPE_MECHANISM: + this.setToolbox(toolboxMechanism.getToolboxJSON(shownPythonToolboxCategories)); + break; + case commonStorage.MODULE_TYPE_OPMODE: +/* + * TODO: When editing an opmode, we'll need to have blocks for all the methods that a robot has. + * Not sure what this will be replaced with, but it will need something. + * const robotBlocks = commonStorage.extractExportedBlocks( + * this.currentModule.projectName, this.projectContent); +*/ + this.setToolbox(toolboxOpmode.getToolboxJSON(shownPythonToolboxCategories)); + break; + } } } diff --git a/src/toolbox/hardware_category.ts b/src/toolbox/hardware_category.ts index 5895810c..cffc7ef5 100644 --- a/src/toolbox/hardware_category.ts +++ b/src/toolbox/hardware_category.ts @@ -1,4 +1,4 @@ -const category_robot = +export const category = { kind: 'category', name: 'Hardware', @@ -169,56 +169,4 @@ const category_robot = } }, ], -} - -const category_mechanism = -{ - kind: 'category', - name: 'Hardware', - contents: [ - { - kind: 'label', - text: 'Components', - }, - { - kind: 'block', - type: 'mrc_component', - fields: { - NAME: 'my_motor', - TYPE: 'SmartMotor' - }, - extraState: { - importModule: 'smart_motor', - params: [{ name: 'motor_port', type: 'int' }], - hideParams: true - }, - }, - { - kind: 'block', - type: 'mrc_component', - fields: { - NAME: 'my_color_range_sensor', - TYPE: 'ColorRangeSensor' - }, - extraState: { - importModule: 'color_range_sensor', - params: [{ name: 'i2c_port', type: 'int' }], - hideParams: true - }, - }, - { - kind: 'block', - type: 'mrc_component', - fields: { - NAME: 'my_touch_sensor', - TYPE: 'RevTouchSensor' - }, - extraState: { - importModule: 'rev_touch_sensor', - params: [{ name: 'smartIO_port', type: 'int' }], - hideParams: true - }, - }, - ], -} -export const category = category_mechanism; \ No newline at end of file +} \ No newline at end of file diff --git a/src/toolbox/hardware_component_category.ts b/src/toolbox/hardware_component_category.ts new file mode 100644 index 00000000..b7c580d4 --- /dev/null +++ b/src/toolbox/hardware_component_category.ts @@ -0,0 +1,50 @@ +export const category = +{ + kind: 'category', + name: 'Hardware', + contents: [ + { + kind: 'label', + text: 'Components', + }, + { + kind: 'block', + type: 'mrc_component', + fields: { + NAME: 'my_motor', + TYPE: 'SmartMotor' + }, + extraState: { + importModule: 'smart_motor', + params: [{ name: 'motor_port', type: 'int' }], + hideParams: true + }, + }, + { + kind: 'block', + type: 'mrc_component', + fields: { + NAME: 'my_color_range_sensor', + TYPE: 'ColorRangeSensor' + }, + extraState: { + importModule: 'color_range_sensor', + params: [{ name: 'i2c_port', type: 'int' }], + hideParams: true + }, + }, + { + kind: 'block', + type: 'mrc_component', + fields: { + NAME: 'my_touch_sensor', + TYPE: 'RevTouchSensor' + }, + extraState: { + importModule: 'rev_touch_sensor', + params: [{ name: 'smartIO_port', type: 'int' }], + hideParams: true + }, + }, + ], +} \ No newline at end of file diff --git a/src/toolbox/robotpy_toolbox.ts b/src/toolbox/robotpy_toolbox.ts index e07de0ce..0213257a 100644 --- a/src/toolbox/robotpy_toolbox.ts +++ b/src/toolbox/robotpy_toolbox.ts @@ -38,7 +38,7 @@ import { import * as toolboxItems from './items'; -export function getToolboxCategories(): toolboxItems.Category[] { +export function getToolboxCategories(shownPythonToolboxCategories: Set | null): toolboxItems.Category[] { const contents: toolboxItems.Category[] = []; const allCategories: {[key: string]: toolboxItems.Category} = {}; @@ -87,6 +87,9 @@ export function getToolboxCategories(): toolboxItems.Category[] { recursivelyRemoveEmptyCategories(contents); + // TODO: Maybe later there is a better way to do this... + filterRobotPyCategories(contents, shownPythonToolboxCategories); + return contents; } @@ -175,3 +178,72 @@ function recursivelyRemoveEmptyCategories(contents: toolboxItems.ContentsType[]) } } } + +function filterRobotPyCategories( + contents: toolboxItems.ContentsType[], shownPythonToolboxCategories: Set | null) { + contents.forEach((item) => { + if (item.kind === 'category') { + const category = item as toolboxItems.Category; + // Traverse the tree depth-first so we can easily identify and remove empty categories. + if (category.contents) { + filterRobotPyCategories(category.contents, shownPythonToolboxCategories); + } + if ((category as toolboxItems.PythonModuleCategory).moduleName) { + const moduleName = (item as toolboxItems.PythonModuleCategory).moduleName; + if (shownPythonToolboxCategories != null && !shownPythonToolboxCategories.has(moduleName)) { + if (category.contents) { + removeBlocksAndSeparators(category.contents); + } + } + } + if ((category as toolboxItems.PythonClassCategory).className) { + const className = (item as toolboxItems.PythonClassCategory).className; + if (shownPythonToolboxCategories != null && !shownPythonToolboxCategories.has(className)) { + if (category.contents) { + removeBlocksAndSeparators(category.contents); + } + } + } + } + }); + removeEmptyCategories(contents, shownPythonToolboxCategories); +} + +function removeBlocksAndSeparators(contents: toolboxItems.ContentsType[]) { + let i = 0; + while (i < contents.length) { + const remove = (contents[i].kind === 'block' || contents[i].kind === 'sep'); + if (remove) { + contents.splice(i, 1); + continue; + } + i++; + } +} + +function removeEmptyCategories( + contents: toolboxItems.ContentsType[], shownPythonToolboxCategories: Set | null) { + let i = 0; + while (i < contents.length) { + let remove = false; + if (contents[i].kind === 'category') { + const category = contents[i] as toolboxItems.Category; + let fullCategoryName = ''; + if ((category as toolboxItems.PythonModuleCategory).moduleName) { + fullCategoryName = (category as toolboxItems.PythonModuleCategory).moduleName + } else if ((category as toolboxItems.PythonClassCategory).className) { + fullCategoryName = (category as toolboxItems.PythonClassCategory).className; + } + if (category.contents && + category.contents.length == 0 && + shownPythonToolboxCategories != null && !shownPythonToolboxCategories.has(fullCategoryName)) { + remove = true; + } + } + if (remove) { + contents.splice(i, 1); + continue; + } + i++; + } +} diff --git a/src/toolbox/toolbox.ts b/src/toolbox/toolbox.ts deleted file mode 100644 index 88344db9..00000000 --- a/src/toolbox/toolbox.ts +++ /dev/null @@ -1,162 +0,0 @@ -/** - * @license - * Copyright 2024 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 robotPyToolbox from './robotpy_toolbox'; -import * as toolboxItems from './items'; -import {category as logicCategory} from './logic_category'; -import {category as loopCategory} from './loop_category'; -import {category as mathCategory} from './math_category'; -import {category as textCategory} from './text_category'; -import {category as listsCategory} from './lists_category'; -import {category as miscCategory} from './misc_category'; -import {category as methodsCategory} from './methods_category'; -import {category as componentSampleCategory} from './component_samples_category'; -import {category as hardwareCategory} from './hardware_category'; -import {category as robotCategory} from './robot_category'; - -export function getToolboxJSON( - opt_robotBlocks: toolboxItems.ContentsType[], - shownPythonToolboxCategories: Set | null) { - const contents: toolboxItems.ContentsType[] = []; - - const robotPyCategories: toolboxItems.ContentsType[] = robotPyToolbox.getToolboxCategories(); - filterRobotPyCategories(robotPyCategories, shownPythonToolboxCategories); - contents.push.apply(contents, robotPyCategories); - - if (opt_robotBlocks.length) { - contents.push.apply( - contents, - [ - { - kind: 'sep', - }, - { - kind: 'category', - name: 'Project', - contents: opt_robotBlocks, - }, - ]); - } - - contents.push.apply( - contents, - [ - { - kind: 'sep', - }, - robotCategory, - { - kind: 'sep', - }, - logicCategory, - loopCategory, - mathCategory, - textCategory, - listsCategory, - miscCategory, - { - kind: 'sep', - }, - { - kind: 'category', - name: 'Variables', - categorystyle: 'variable_category', - custom: 'VARIABLE', - }, - methodsCategory, - hardwareCategory, - //componentSampleCategory, - ]); - - return { - kind: 'categoryToolbox', - contents: contents - }; -} - -function filterRobotPyCategories( - contents: toolboxItems.ContentsType[], shownPythonToolboxCategories: Set | null) { - contents.forEach((item) => { - if (item.kind === 'category') { - const category = item as toolboxItems.Category; - // Traverse the tree depth-first so we can easily identify and remove empty categories. - if (category.contents) { - filterRobotPyCategories(category.contents, shownPythonToolboxCategories); - } - if ((category as toolboxItems.PythonModuleCategory).moduleName) { - const moduleName = (item as toolboxItems.PythonModuleCategory).moduleName; - if (shownPythonToolboxCategories != null && !shownPythonToolboxCategories.has(moduleName)) { - if (category.contents) { - removeBlocksAndSeparators(category.contents); - } - } - } - if ((category as toolboxItems.PythonClassCategory).className) { - const className = (item as toolboxItems.PythonClassCategory).className; - if (shownPythonToolboxCategories != null && !shownPythonToolboxCategories.has(className)) { - if (category.contents) { - removeBlocksAndSeparators(category.contents); - } - } - } - } - }); - removeEmptyCategories(contents, shownPythonToolboxCategories); -} - -function removeBlocksAndSeparators(contents: toolboxItems.ContentsType[]) { - let i = 0; - while (i < contents.length) { - const remove = (contents[i].kind === 'block' || contents[i].kind === 'sep'); - if (remove) { - contents.splice(i, 1); - continue; - } - i++; - } -} - -function removeEmptyCategories( - contents: toolboxItems.ContentsType[], shownPythonToolboxCategories: Set | null) { - let i = 0; - while (i < contents.length) { - let remove = false; - if (contents[i].kind === 'category') { - const category = contents[i] as toolboxItems.Category; - let fullCategoryName = ''; - if ((category as toolboxItems.PythonModuleCategory).moduleName) { - fullCategoryName = (category as toolboxItems.PythonModuleCategory).moduleName - } else if ((category as toolboxItems.PythonClassCategory).className) { - fullCategoryName = (category as toolboxItems.PythonClassCategory).className; - } - if (category.contents && - category.contents.length == 0 && - shownPythonToolboxCategories != null && !shownPythonToolboxCategories.has(fullCategoryName)) { - remove = true; - } - } - if (remove) { - contents.splice(i, 1); - continue; - } - i++; - } -} diff --git a/src/toolbox/toolbox_common.ts b/src/toolbox/toolbox_common.ts new file mode 100644 index 00000000..0cff3f95 --- /dev/null +++ b/src/toolbox/toolbox_common.ts @@ -0,0 +1,69 @@ +/** + * @license + * Copyright 2024 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 robotPyToolbox from './robotpy_toolbox'; +import * as toolboxItems from './items'; +import { category as logicCategory } from './logic_category'; +import { category as loopCategory } from './loop_category'; +import { category as mathCategory } from './math_category'; +import { category as textCategory } from './text_category'; +import { category as listsCategory } from './lists_category'; +import { category as miscCategory } from './misc_category'; +import { category as methodsCategory } from './methods_category'; + +export function getToolboxItems( + shownPythonToolboxCategories: Set | null) { + const contents: toolboxItems.ContentsType[] = []; + + const robotPyCategories: toolboxItems.ContentsType[] = robotPyToolbox.getToolboxCategories(shownPythonToolboxCategories); + + if (robotPyCategories.length) { + contents.push.apply(contents, robotPyCategories); + contents.push.apply(contents, [ + { + kind: 'sep', + },] + ); + } + + contents.push.apply( + contents, + [ + logicCategory, + loopCategory, + mathCategory, + textCategory, + listsCategory, + miscCategory, + { + kind: 'sep', + }, + { + kind: 'category', + name: 'Variables', + categorystyle: 'variable_category', + custom: 'VARIABLE', + }, + methodsCategory, + ], + ); + return contents; +} \ No newline at end of file diff --git a/src/toolbox/toolbox_mechanism.ts b/src/toolbox/toolbox_mechanism.ts new file mode 100644 index 00000000..3868559d --- /dev/null +++ b/src/toolbox/toolbox_mechanism.ts @@ -0,0 +1,15 @@ +import * as common from './toolbox_common' +import { category as hardwareComponentCategory } from './hardware_component_category'; + +export function getToolboxJSON( + shownPythonToolboxCategories: Set | null) { + + return { + kind: 'categoryToolbox', + contents: [ + hardwareComponentCategory, + { kind: 'sep' }, + ...common.getToolboxItems(shownPythonToolboxCategories) + ] + }; +} diff --git a/src/toolbox/toolbox_opmode.ts b/src/toolbox/toolbox_opmode.ts new file mode 100644 index 00000000..2b2e3139 --- /dev/null +++ b/src/toolbox/toolbox_opmode.ts @@ -0,0 +1,34 @@ + import * as common from './toolbox_common' + import {category as robotCategory} from './/robot_category'; + + export function getToolboxJSON( + shownPythonToolboxCategories: Set | null) { + + return { + kind: 'categoryToolbox', + contents: [ + robotCategory, + { kind: 'sep' }, + ...common.getToolboxItems(shownPythonToolboxCategories) + ] + }; + } + /** TODO: + * The opmode will need to have blocks for all the methods that are in the + * robot. This commented out code will have to be reworked in the future to + * do this. + */ + /*if (opt_robotBlocks.length) { + contents.push.apply( + contents, + [ + { + kind: 'sep', + }, + { + kind: 'category', + name: 'Project', + contents: opt_robotBlocks, + }, + ]); + }*/ \ No newline at end of file diff --git a/src/toolbox/toolbox_robot.ts b/src/toolbox/toolbox_robot.ts new file mode 100644 index 00000000..6cfef02b --- /dev/null +++ b/src/toolbox/toolbox_robot.ts @@ -0,0 +1,16 @@ + import * as common from './toolbox_common' + import {category as hardwareCategory} from './hardware_category'; + + export function getToolboxJSON( + shownPythonToolboxCategories: Set | null) { + + return { + kind: 'categoryToolbox', + contents: [ + hardwareCategory, + { kind: 'sep' }, + ...common.getToolboxItems(shownPythonToolboxCategories) + ] + }; + } + \ No newline at end of file diff --git a/src/toolbox/toolbox_tests.ts b/src/toolbox/toolbox_tests.ts index b67e953b..a9c155c1 100644 --- a/src/toolbox/toolbox_tests.ts +++ b/src/toolbox/toolbox_tests.ts @@ -22,13 +22,11 @@ import * as Blockly from 'blockly/core'; import { extendedPythonGenerator } from '../editor/extended_python_generator'; import * as toolboxItems from './items'; -import { getToolboxJSON } from '../toolbox/toolbox'; - // Tests -export function testAllBlocksInToolbox() { - const contents: toolboxItems.ContentsType[] = getToolboxJSON([], null).contents; +export function testAllBlocksInToolbox(toolbox : Blockly.utils.toolbox.ToolboxDefinition) { + const contents = (toolbox as toolboxItems.Category).contents; alert('Press OK to run tests on all blocks from the toolbox.'); const toolboxTestData = new ToolboxTestData(contents, () => { alert('Completed tests on all blocks in the toolbox. See console for any errors.'); @@ -42,12 +40,14 @@ class ToolboxTestData { jsonBlocks: toolboxItems.Block[]; index: number; - constructor(contents: toolboxItems.ContentsType[], onFinish: () => void) { + constructor(contents: toolboxItems.ContentsType[] | undefined, onFinish: () => void) { this.onFinish = onFinish; this.blocklyWorkspace = new Blockly.Workspace(); this.blocklyWorkspace.MAX_UNDO = 0; this.jsonBlocks = []; - this.collectBlocks(contents); + if(contents){ + this.collectBlocks(contents); + } this.index = 0; } @@ -55,7 +55,7 @@ class ToolboxTestData { contents.forEach((item) => { switch (item.kind) { default: - console.log('Error - item.kind is ' + item.kind + '. It must be block, category, or sep.'); + console.log('Error - item.kind is ' + item.kind + '. It must be block, category, label, or sep.'); break; case 'block': const block = item as toolboxItems.Block; @@ -68,6 +68,7 @@ class ToolboxTestData { } break; case 'sep': + case 'label': break; } });