diff --git a/.gitignore b/.gitignore index 70be6638..d2f50d20 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,10 @@ # production /build +# python stuff +__pycache__ +*.egg-info + # misc .DS_Store .env.local diff --git a/server_python_scripts/blocks_base_classes/__init__.py b/server_python_scripts/blocks_base_classes/__init__.py new file mode 100644 index 00000000..d59688f0 --- /dev/null +++ b/server_python_scripts/blocks_base_classes/__init__.py @@ -0,0 +1,8 @@ +"""Base classes for SystemCore blocks interface.""" + +from .opmode import OpMode, Teleop, Auto, Test, Name, Group +from .mechanism import Mechanism +from .robot_base import RobotBase + +__all__ = ['OpMode', 'Teleop', 'Auto', 'Test', 'Name', 'Group' + 'Mechanism', 'RobotBase'] \ No newline at end of file diff --git a/server_python_scripts/blocks_base_classes/mechanism.py b/server_python_scripts/blocks_base_classes/mechanism.py new file mode 100644 index 00000000..dbf89a1d --- /dev/null +++ b/server_python_scripts/blocks_base_classes/mechanism.py @@ -0,0 +1,14 @@ +# This is the class all mechanisms derive from + +class Mechanism: + def __init__(self): + self.hardware = [] + def start(self): + for hardware in self.hardware: + hardware.start() + def update(self): + for hardware in self.hardware: + hardware.update() + def stop(self): + for hardware in self.hardware: + hardware.stop() \ No newline at end of file diff --git a/server_python_scripts/blocks_base_classes/opmode.py b/server_python_scripts/blocks_base_classes/opmode.py new file mode 100644 index 00000000..c4f123f8 --- /dev/null +++ b/server_python_scripts/blocks_base_classes/opmode.py @@ -0,0 +1,26 @@ +# This is the base class that all OpModes derive from +class OpMode: + def __init__(self, robot): + self.robot = robot + def start(self): + self.robot.start() + def loop(self): + self.robot.update() + def stop(self): + self.robot.stop() + +# For now this does nothing but it lets the decorator work +def Teleop(OpMode): + return OpMode + +def Auto(OpMode): + return OpMode + +def Test(OpMode): + return OpMode + +def Name(OpMode, str): + return OpMode + +def Group(OpMode, str): + return OpMode \ No newline at end of file diff --git a/server_python_scripts/blocks_base_classes/pyproject.toml b/server_python_scripts/blocks_base_classes/pyproject.toml new file mode 100644 index 00000000..0cd5784b --- /dev/null +++ b/server_python_scripts/blocks_base_classes/pyproject.toml @@ -0,0 +1,21 @@ +[build-system] +requires = ["setuptools>=61.0", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "blocks_base_classes" +version = "0.1.0" +description = "Base classes for SystemCore blocks interface" +requires-python = ">=3.8" +license = {text = "Apache-2.0"} +authors = [ + {name = "Alan Smith", email = "alan@porpoiseful.com"} +] +dependencies = [] + +[tool.setuptools.packages.find] +where = ["."] +include = ["blocks_base_classes*"] + +[tool.setuptools.package-dir] +"blocks_base_classes" = "." \ No newline at end of file diff --git a/server_python_scripts/blocks_base_classes/robot_base.py b/server_python_scripts/blocks_base_classes/robot_base.py new file mode 100644 index 00000000..e122d85e --- /dev/null +++ b/server_python_scripts/blocks_base_classes/robot_base.py @@ -0,0 +1,14 @@ +# This is the class all robots derive from + +class RobotBase: + def __init__(self): + self.hardware = [] + def start(self): + for hardware in self.hardware: + hardware.start() + def update(self): + for hardware in self.hardware: + hardware.update() + def stop(self): + for hardware in self.hardware: + hardware.stop() \ No newline at end of file diff --git a/server_python_scripts/opmode.py b/server_python_scripts/opmode.py deleted file mode 100644 index b4ce7bf4..00000000 --- a/server_python_scripts/opmode.py +++ /dev/null @@ -1,9 +0,0 @@ -class OpMode: - def __init__(self, robot): - self.robot = robot - def start(): - pass - def loop(): - pass - def stop(): - pass \ No newline at end of file diff --git a/server_python_scripts/run_opmode.py b/server_python_scripts/run_opmode.py old mode 100644 new mode 100755 index f663c9e8..fd79eafe --- a/server_python_scripts/run_opmode.py +++ b/server_python_scripts/run_opmode.py @@ -15,10 +15,8 @@ import argparse from pathlib import Path -# Add the current directory to Python path to import local modules -sys.path.insert(0, str(Path(__file__).parent)) +from blocks_base_classes import OpMode -from opmode import OpMode from robot import Robot diff --git a/src/blocks/mrc_call_python_function.ts b/src/blocks/mrc_call_python_function.ts index 63d9dad1..3ec39254 100644 --- a/src/blocks/mrc_call_python_function.ts +++ b/src/blocks/mrc_call_python_function.ts @@ -626,7 +626,7 @@ const CALL_PYTHON_FUNCTION = { break; } case FunctionKind.INSTANCE_COMPONENT: { - const componentNameChoices = []; + const componentNameChoices : string[] = []; this.mrcComponents.forEach(component => componentNameChoices.push(component.name)); if (!componentNameChoices.includes(this.mrcComponentName)) { componentNameChoices.push(this.mrcComponentName); @@ -723,7 +723,7 @@ const CALL_PYTHON_FUNCTION = { } } if (indexOfComponentName != -1) { - const componentNameChoices = []; + const componentNameChoices : string[] = []; this.mrcComponents.forEach(component => componentNameChoices.push(component.name)); titleInput.removeField(FIELD_COMPONENT_NAME); titleInput.insertFieldAt(indexOfComponentName, diff --git a/src/blocks/mrc_class_method_def.ts b/src/blocks/mrc_class_method_def.ts index 80aea5c3..5d213c76 100644 --- a/src/blocks/mrc_class_method_def.ts +++ b/src/blocks/mrc_class_method_def.ts @@ -214,7 +214,7 @@ const CLASS_METHOD_DEF = { mrcRenameParameter: function (this: ClassMethodDefBlock, oldName: string, newName: string) { let nextBlock = this.getInputTargetBlock('STACK'); - if(nextBlock){ + if (nextBlock){ findConnectedBlocksOfType(nextBlock, MRC_GET_PARAMETER_BLOCK_NAME).forEach((block) => { if (block.getFieldValue('PARAMETER_NAME') === oldName) { block.setFieldValue(newName, 'PARAMETER_NAME'); @@ -362,12 +362,12 @@ export const pythonFromBlock = function ( } if (block.mrcPythonMethodName == '__init__') { let class_specific = generator.getClassSpecificForInit(); - branch = generator.INDENT + 'super.__init__(' + class_specific + ')\n' + + branch = generator.INDENT + 'super().__init__(' + class_specific + ')\n' + generator.defineClassVariables() + branch; } - else if (funcName == 'update'){ + else if (generator.inBaseClassMethod(blocklyName)){ // Special case for update, to also call the update method of the base class - branch = generator.INDENT + 'super.update()\n' + branch; + branch = generator.INDENT + 'super().' + blocklyName + '()\n' + branch; } if (returnValue) { returnValue = generator.INDENT + 'return ' + returnValue + '\n'; diff --git a/src/blocks/mrc_get_parameter.ts b/src/blocks/mrc_get_parameter.ts index d489424d..248ce00c 100644 --- a/src/blocks/mrc_get_parameter.ts +++ b/src/blocks/mrc_get_parameter.ts @@ -66,7 +66,7 @@ const GET_PARAMETER_BLOCK = { if (blockEvent.type === Blockly.Events.BLOCK_MOVE) { let parent = blockBlock.getRootBlock(); - if( parent.type === MRC_CLASS_METHOD_DEF) { + if ( parent.type === MRC_CLASS_METHOD_DEF) { // It is a class method definition, so we see if this variable is in it. const classMethodDefBlock = parent as ClassMethodDefBlock; for (const parameter of classMethodDefBlock.mrcParameters) { diff --git a/src/blocks/mrc_mechanism_component_holder.ts b/src/blocks/mrc_mechanism_component_holder.ts index b83dfde0..7e3cf584 100644 --- a/src/blocks/mrc_mechanism_component_holder.ts +++ b/src/blocks/mrc_mechanism_component_holder.ts @@ -178,7 +178,7 @@ export const setup = function () { } function pythonFromBlockInRobot(block: MechanismComponentHolderBlock, generator: ExtendedPythonGenerator) { - let code = 'def define_hardware(self):\n' + generator.INDENT + 'self.hardware = []\n'; + let code = 'def define_hardware(self):\n'; let mechanisms = ''; let components = ''; @@ -189,9 +189,8 @@ function pythonFromBlockInRobot(block: MechanismComponentHolderBlock, generator: const body = mechanisms + components; if (body != '') { code += body; + generator.addClassMethodDefinition('define_hardware', code); } - - generator.addClassMethodDefinition('define_hardware', code); } function pythonFromBlockInMechanism(block: MechanismComponentHolderBlock, generator: ExtendedPythonGenerator) { @@ -199,13 +198,12 @@ function pythonFromBlockInMechanism(block: MechanismComponentHolderBlock, genera components = generator.statementToCode(block, 'COMPONENTS'); - let code = 'def define_hardware(self' + generator.getListOfPorts(false) + '):\n' + - generator.INDENT + 'self.hardware = []\n'; + let code = 'def define_hardware(self' + generator.getListOfPorts(false) + '):\n'; if (components != '') { code += components; - } - generator.addClassMethodDefinition('define_hardware', code); + generator.addClassMethodDefinition('define_hardware', code); + } } export const pythonFromBlock = function ( diff --git a/src/blocks/mrc_print.ts b/src/blocks/mrc_print.ts new file mode 100644 index 00000000..1589efa7 --- /dev/null +++ b/src/blocks/mrc_print.ts @@ -0,0 +1,46 @@ +/** + * @license + * Copyright 2025 Porpoiseful 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. + */ + +/** + * @fileoverview Create a component that does a simple print statement + * @author alan@porpoiseful.com (Alan Smith) + */ +import * as Blockly from 'blockly'; +import { PythonGenerator } from 'blockly/python'; +import { Order } from 'blockly/python'; + +export const BLOCK_NAME = 'mrc_print'; + +export const setup = function() { + Blockly.Blocks[BLOCK_NAME] = { + init: function() { + this.appendValueInput('TEXT') + .setCheck('String') + .appendField(Blockly.Msg['MRC_PRINT']); + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setStyle('text_blocks'); + }, + }; +}; + +export const pythonFromBlock = function( + block: Blockly.Block, + generator: PythonGenerator, +) { + return 'print(' + generator.valueToCode(block, 'TEXT', Order.NONE) + ')\n'; +}; \ No newline at end of file diff --git a/src/blocks/setup_custom_blocks.ts b/src/blocks/setup_custom_blocks.ts index 753cc2d3..3a303460 100644 --- a/src/blocks/setup_custom_blocks.ts +++ b/src/blocks/setup_custom_blocks.ts @@ -17,6 +17,7 @@ import * as Event from './mrc_event'; import * as GetParameter from './mrc_get_parameter'; import * as ParameterMutator from './mrc_param_container' import * as EventHandler from './mrc_event_handler'; +import * as Print from './mrc_print'; const customBlocks = [ CallPythonFunction, @@ -37,13 +38,14 @@ const customBlocks = [ GetParameter, ParameterMutator, EventHandler, + Print ]; export const setup = function(forBlock: any) { customBlocks.forEach(block => { block.setup(); const maybeBlock = block as { pythonFromBlock?: any; BLOCK_NAME?: string }; - if(maybeBlock.pythonFromBlock && maybeBlock.BLOCK_NAME) { + if (maybeBlock.pythonFromBlock && maybeBlock.BLOCK_NAME) { forBlock[maybeBlock.BLOCK_NAME] = maybeBlock.pythonFromBlock; } }); diff --git a/src/blocks/tokens.ts b/src/blocks/tokens.ts index ee399a04..a6f193df 100644 --- a/src/blocks/tokens.ts +++ b/src/blocks/tokens.ts @@ -1,7 +1,33 @@ +/** + * @license + * Copyright 2025 Porpoiseful 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. + */ + +/** + * @fileoverview Exposes translatable strings for use as Blockly.Msg tokens. + * @author alan@porpoiseful.com (Alan Smith) + */ + import * as Blockly from 'blockly/core'; -import { M } from 'vitest/dist/chunks/reporters.d.BFLkQcL6.js'; -export const customTokens = (t: (key: string) => string): typeof Blockly.Msg => { +/** + * Creates custom translation tokens for Blockly messages. + * @param t Translation function that takes a key and returns translated string. + * @return Object containing translated Blockly message tokens. + */ +export function customTokens(t: (key: string) => string): typeof Blockly.Msg { return { ADD_COMMENT: t('BLOCKLY.ADD_COMMENT'), REMOVE_COMMENT: t('BLOCKLY.REMOVE_COMMENT'), @@ -10,12 +36,15 @@ export const customTokens = (t: (key: string) => string): typeof Blockly.Msg => WITH: t('BLOCKLY.WITH'), WHEN: t('BLOCKLY.WHEN'), PARAMETER: t('BLOCKLY.PARAMETER'), - PARAMETERS_CAN_ONLY_GO_IN_THEIR_METHODS_BLOCK: t('BLOCKLY.PARAMETERS_CAN_ONLY_GO_IN_THEIR_METHODS_BLOCK'), - MECHANISMS: t('BLOCKLY.MECHANISMS'), + PARAMETERS_CAN_ONLY_GO_IN_THEIR_METHODS_BLOCK: + t('BLOCKLY.PARAMETERS_CAN_ONLY_GO_IN_THEIR_METHODS_BLOCK'), + MECHANISMS: t('MECHANISMS'), + OPMODES: t('OPMODES'), COMPONENTS: t('BLOCKLY.COMPONENTS'), EVENTS: t('BLOCKLY.EVENTS'), EVALUATE_BUT_IGNORE_RESULT: t('BLOCKLY.EVALUATE_BUT_IGNORE_RESULT'), - EVALUATE_BUT_IGNORE_RESULT_TOOLTIP: t('BLOCKLY.EVALUATE_BUT_IGNORE_RESULT_TOOLTIP'), + EVALUATE_BUT_IGNORE_RESULT_TOOLTIP: + t('BLOCKLY.EVALUATE_BUT_IGNORE_RESULT_TOOLTIP'), AUTO: t('BLOCKLY.AUTO'), TELEOP: t('BLOCKLY.TELEOP'), TEST: t('BLOCKLY.TEST'), @@ -42,5 +71,7 @@ export const customTokens = (t: (key: string) => string): typeof Blockly.Msg => MRC_CATEGORY_EVENTS: t('BLOCKLY.CATEGORY.EVENTS'), MRC_CATEGORY_ADD_MECHANISM: t('BLOCKLY.CATEGORY.ADD_MECHANISM'), MRC_CATEGORY_ADD_COMPONENT: t('BLOCKLY.CATEGORY.ADD_COMPONENT'), - }; -}; \ No newline at end of file + MRC_CATEGORY_TEST: t('BLOCKLY.CATEGORY.TEST'), + MRC_PRINT: t('BLOCKLY.PRINT'), + } +}; \ No newline at end of file diff --git a/src/blocks/utils/change_framework.ts b/src/blocks/utils/change_framework.ts index b4b78060..d8138199 100644 --- a/src/blocks/utils/change_framework.ts +++ b/src/blocks/utils/change_framework.ts @@ -37,7 +37,7 @@ export function registerCallback(blockType : string, blockEvents : string[], fun export function getParentOfType(block : Blockly.Block | null, type : string) : Blockly.Block | null{ let parentBlock = block?.getParent(); while(parentBlock){ - if(parentBlock.type == type){ + if (parentBlock.type == type){ return parentBlock; } parentBlock = parentBlock.getParent(); @@ -50,14 +50,14 @@ function changeListener(e: Blockly.Events.Abstract){ let eventBlockBase = (e as Blockly.Events.BlockBase); let workspace = Blockly.Workspace.getById(eventBlockBase.workspaceId!) let block = (workspace?.getBlockById(eventBlockBase.blockId!)! as Blockly.BlockSvg) - if(!block){ + if (!block){ return; } let callbackInfo = registeredCallbacks.get(block.type); - if(!callbackInfo){ + if (!callbackInfo){ return; } - if(callbackInfo[0].includes(e.type)){ + if (callbackInfo[0].includes(e.type)){ callbackInfo[1](block, eventBlockBase); } } diff --git a/src/blocks/utils/connection_checker.ts b/src/blocks/utils/connection_checker.ts index 06bbb692..00a0c17f 100644 --- a/src/blocks/utils/connection_checker.ts +++ b/src/blocks/utils/connection_checker.ts @@ -42,17 +42,17 @@ export class MethodConnectionChecker extends Blockly.ConnectionChecker { if (!checkArrayOne || !checkArrayTwo) { // if either one has mrc_component, they must match - if((checkArrayOne && (checkArrayOne.indexOf(COMPONENT_OUTPUT) != -1)) || + if ((checkArrayOne && (checkArrayOne.indexOf(COMPONENT_OUTPUT) != -1)) || (checkArrayTwo && (checkArrayTwo.indexOf(COMPONENT_OUTPUT) != -1))){ return false; } // if either one has mrc_mechanism, they must match - if((checkArrayOne && (checkArrayOne.indexOf(MECHANISM_OUTPUT) != -1)) || + if ((checkArrayOne && (checkArrayOne.indexOf(MECHANISM_OUTPUT) != -1)) || (checkArrayTwo && (checkArrayTwo.indexOf(MECHANISM_OUTPUT) != -1))){ return false; } // if either one has mrc_event, they must match - if((checkArrayOne && (checkArrayOne.indexOf(EVENT_OUTPUT) != -1)) || + if ((checkArrayOne && (checkArrayOne.indexOf(EVENT_OUTPUT) != -1)) || (checkArrayTwo && (checkArrayTwo.indexOf(EVENT_OUTPUT) != -1))){ return false; } diff --git a/src/blocks/utils/python.ts b/src/blocks/utils/python.ts index 30fd8957..fa5c6227 100644 --- a/src/blocks/utils/python.ts +++ b/src/blocks/utils/python.ts @@ -262,10 +262,10 @@ export function getLegalName(proposedName: string, existingNames: string[]){ while (existingNames.includes(newName)){ const match = /(.*?)(\d+)$/.exec(newName) - if(match){ + if (match) { let lastNumber = +match[2] newName = match[1] + (lastNumber + 1) - }else{ + } else { newName += "2" } } diff --git a/src/editor/extended_python_generator.ts b/src/editor/extended_python_generator.ts index 4233d87c..1bb16c3e 100644 --- a/src/editor/extended_python_generator.ts +++ b/src/editor/extended_python_generator.ts @@ -26,20 +26,35 @@ import * as MechanismContainerHolder from '../blocks/mrc_mechanism_component_hol export class OpModeDetails { constructor(private name: string, private group : string, private enabled : boolean, private type : string) {} - annotations() : string{ + decorations(className : string) : string{ let code = ''; if (this.enabled){ code += '@' + this.type + "\n"; + if (this.name){ - code += '@name("' + this.name + '")\n'; + code += '@Name(' + className + ', "' + this.name + '")\n'; } if (this.group){ - code += '@group("' + this.group + '")\n'; + code += '@Group(' + className + ', "' + this.group + '")\n'; } } return code; } + imports() : string{ + let code = ''; + if (this.enabled){ + code += 'from blocks_base_classes import ' + this.type; + if (this.name){ + code += ', Name'; + } + if (this.group){ + code += ', Group'; + } + } + + return code; + } } // Extends the python generator to collect some information about functions and @@ -84,9 +99,11 @@ export class ExtendedPythonGenerator extends PythonGenerator { let variableDefinitions = ''; if (this.context?.getHasHardware()) { - variableDefinitions += this.INDENT + "self.define_hardware("; - variableDefinitions += this.getListOfPorts(true); - variableDefinitions += ')\n'; + if ('define_hardware' in this.classMethods) { + variableDefinitions += this.INDENT + "self.define_hardware("; + variableDefinitions += this.getListOfPorts(true); + variableDefinitions += ')\n'; + } if (this.events && Object.keys(this.events).length > 0){ variableDefinitions += this.INDENT + "self.register_events()\n"; } @@ -129,7 +146,14 @@ export class ExtendedPythonGenerator extends PythonGenerator { * Add an import statement for a python module. */ addImport(importModule: string): void { - this.definitions_['import_' + importModule] = 'import ' + importModule; + const baseClasses = ['RobotBase', 'OpMode', 'Mechanism']; + if (baseClasses.includes(importModule)) { + this.definitions_['import_' + importModule] = 'from blocks_base_classes import ' + importModule; + } + else{ + this.definitions_['import_' + importModule] = 'import ' + importModule; + } + } /** @@ -171,7 +195,13 @@ export class ExtendedPythonGenerator extends PythonGenerator { if (this.context && this.workspace) { const className = this.context.getClassName(); const classParent = this.context.getClassParent(); - const annotations = this.details?.annotations(); + const decorations = this.details?.decorations(className); + const import_decorations = this.details?.imports(); + + if (import_decorations){ + this.definitions_['import_decorations'] = import_decorations; + } + this.addImport(classParent); const classDef = 'class ' + className + '(' + classParent + '):\n'; @@ -192,8 +222,8 @@ export class ExtendedPythonGenerator extends PythonGenerator { this.classMethods = Object.create(null); this.ports = Object.create(null); code = classDef + this.prefixLines(classMethods.join('\n\n'), this.INDENT); - if (annotations){ - code = annotations + code; + if (decorations){ + code = decorations + code; } this.details = null; } @@ -212,6 +242,34 @@ export class ExtendedPythonGenerator extends PythonGenerator { } return '' } + + /** + * This returns the list of methods that are derived from so that mrc_class_method_def + * knows whether to call super() or not. + * @returns list of method names + */ + getBaseClassMethods() : string[] { + let classParent = this.context?.getClassParent(); + if (classParent == 'OpMode'){ + return ['start', 'loop', 'stop']; + } + else if (classParent == 'Mechanism') { + return ['start', 'update', 'stop']; + } + else if (classParent == 'RobotBase'){ + return ['start', 'update', 'stop']; + } + return []; + } + + /** + * Returns true if the method is in the base class. + * @param methodName the name of the method to check + */ + inBaseClassMethod(methodName: string): boolean { + const baseMethods = this.getBaseClassMethods(); + return baseMethods.includes(methodName); + } } export const extendedPythonGenerator = new ExtendedPythonGenerator(); diff --git a/src/fields/field_flydown.ts b/src/fields/field_flydown.ts index 47e19757..cf75e2ff 100644 --- a/src/fields/field_flydown.ts +++ b/src/fields/field_flydown.ts @@ -34,12 +34,35 @@ class CustomFlyout extends Blockly.VerticalFlyout { protected x: number = 0; protected y: number = 0; - public setPosition(x: number, y: number): void { - this.x = x; - this.y = y; + public setPosition(fieldRect: DOMRect, workspaceRect: DOMRect, location: FlydownLocation): void { + // Calculate position based on field element, workspace, and desired location + switch (location) { + case FlydownLocation.DISPLAY_BELOW: + this.x = fieldRect.left - workspaceRect.left; + this.y = fieldRect.bottom - workspaceRect.top; + break; + + case FlydownLocation.DISPLAY_ABOVE: + this.x = fieldRect.left - workspaceRect.left; + this.y = fieldRect.top - workspaceRect.top - this.getHeight(); + break; + + case FlydownLocation.DISPLAY_LEFT: + this.x = fieldRect.left - workspaceRect.left - this.width_; + this.y = fieldRect.top - workspaceRect.top; + break; + + case FlydownLocation.DISPLAY_RIGHT: + default: + this.x = fieldRect.right - workspaceRect.left; + this.y = fieldRect.top - workspaceRect.top; + break; + } + + // Position the flyout at the calculated coordinates this.position(); } - + override position() { if (!this.isVisible() || !this.targetWorkspace!.isVisible()) { return; @@ -145,7 +168,7 @@ export class FieldFlydown extends Blockly.FieldTextInput { * @type {number} * @const */ - static TIME_OUT = 500; + static TIME_OUT = 250; private displayLocation_: FlydownLocation; @@ -157,7 +180,7 @@ export class FieldFlydown extends Blockly.FieldTextInput { private showTimeout_: number | null = null; private hideTimeout_: number | null = null; - constructor(value: string, isEditable: boolean, displayLocation: FlydownLocation = FlydownLocation.DISPLAY_BELOW) { + constructor(value: string, isEditable: boolean, displayLocation: FlydownLocation = FlydownLocation.DISPLAY_RIGHT) { super(value); this.EDITABLE = isEditable; this.displayLocation_ = displayLocation; @@ -198,13 +221,13 @@ export class FieldFlydown extends Blockly.FieldTextInput { // Add small delay to prevent flickering this.showTimeout_ = window.setTimeout(() => { this.showFlydown_(); - }, 250); // 250ms delay + }, 100); // 100ms delay // This event has been handled. No need to bubble up to the document. e.stopPropagation(); } - private onMouseOut_(e: Event) { + private onMouseOut_(_e: Event) { // Clear any pending show timeout if (this.showTimeout_) { clearTimeout(this.showTimeout_); @@ -244,12 +267,8 @@ export class FieldFlydown extends Blockly.FieldTextInput { const fieldRect = fieldElement.getBoundingClientRect(); const workspaceRect = mainWorkspace.getParentSvg().getBoundingClientRect(); - const x = fieldRect.right - workspaceRect.left; - const y = fieldRect.top - workspaceRect.top; - - // Set flydown position - this.flydown_.setPosition(x, y); + this.flydown_.setPosition(fieldRect, workspaceRect, this.displayLocation_); // Create the flydown without explicit DOM manipulation this.flydown_.setVisible(true); @@ -279,11 +298,8 @@ export class FieldFlydown extends Blockly.FieldTextInput { const fieldRect = fieldElement!.getBoundingClientRect(); const workspaceRect = mainWorkspace.getParentSvg().getBoundingClientRect(); - const x = fieldRect.right - workspaceRect.left; - const y = fieldRect.top - workspaceRect.top; - // Set flydown position - this.flydown_.setPosition(x, y); + this.flydown_.setPosition(fieldRect, workspaceRect, this.displayLocation_); // Show the flydown with your blocks this.flydown_.show(this.getBlocksForFlydown_()); diff --git a/src/i18n/locales/en/translation.json b/src/i18n/locales/en/translation.json index 462330d2..50be4fd8 100644 --- a/src/i18n/locales/en/translation.json +++ b/src/i18n/locales/en/translation.json @@ -37,13 +37,14 @@ "BLOCKS": "Blocks", "CODE": "Code", "COPY": "Copy", + "MECHANISMS": "Mechanisms", + "OPMODES": "OpModes", "BLOCKLY":{ "OF_TYPE": "of type", "WITH": "with", "WHEN": "when", "PARAMETER": "parameter", "PARAMETERS_CAN_ONLY_GO_IN_THEIR_METHODS_BLOCK": "Parameters can only go in their method's block", - "MECHANISMS": "Mechanisms", "COMPONENTS": "Components", "EVENTS": "Events", "EVALUATE_BUT_IGNORE_RESULT": "evaluate but ignore result", @@ -51,6 +52,7 @@ "ENABLED": "Enabled", "DISPLAY_NAME": "Display Name", "DISPLAY_GROUP": "Display Group", + "PRINT": "print", "TOOLTIP":{ "EVALUATE_BUT_IGNORE_RESULT": "Executes the connected block and ignores the result. Allows you to call a function and ignore the return value.", "OPMODE_TYPE": "What sort of OpMode this is", @@ -73,7 +75,8 @@ "METHODS": "Methods", "EVENTS": "Events", "ADD_MECHANISM": "+ Mechanism", - "ADD_COMPONENT": "+ Component" + "ADD_COMPONENT": "+ Component", + "TEST": "Test" } } } diff --git a/src/i18n/locales/es/translation.json b/src/i18n/locales/es/translation.json index 0f23c12d..8f0601ed 100644 --- a/src/i18n/locales/es/translation.json +++ b/src/i18n/locales/es/translation.json @@ -38,13 +38,14 @@ "newItemPlaceholder": "Agregar Módulo", "search": "Buscar..." }, + "MECHANISMS": "Mecanismos", + "OPMODES": "OpModes", "BLOCKLY": { "OF_TYPE": "de tipo", "WITH": "con", "WHEN": "cuando", "PARAMETER": "parámetro", "PARAMETERS_CAN_ONLY_GO_IN_THEIR_METHODS_BLOCK": "Los parámetros solo pueden ir en el bloque de su método", - "MECHANISMS": "Mecanismos", "COMPONENTS": "Componentes", "EVENTS": "Eventos", "EVALUATE_BUT_IGNORE_RESULT": "evaluar pero ignorar resultado", @@ -74,7 +75,8 @@ "METHODS": "Métodos", "EVENTS": "Eventos", "ADD_MECHANISM": "+ Mecanismo", - "ADD_COMPONENT": "+ Componente" + "ADD_COMPONENT": "+ Componente", + "TEST": "Prueba" } } } \ No newline at end of file diff --git a/src/modules/robot_start.json b/src/modules/robot_start.json index cb06cbf8..1a54456b 100644 --- a/src/modules/robot_start.json +++ b/src/modules/robot_start.json @@ -20,23 +20,6 @@ "NAME": "init" } }, - { - "type": "mrc_class_method_def", - "id": "ElbONc{46f,UprTW(|1C", - "x": 210, - "y": 210, - "deletable": false, - "extraState": { - "canChangeSignature": false, - "canBeCalledWithinClass": false, - "canBeCalledOutsideClass": false, - "returnType": "None", - "params": [] - }, - "fields": { - "NAME": "update" - } - }, { "type": "mrc_mechanism_component_holder", "id": "ElbOAb{)s(,UprTW(|1C", diff --git a/src/reactComponents/BlocklyComponent.tsx b/src/reactComponents/BlocklyComponent.tsx index d1a29ed0..bc50066b 100644 --- a/src/reactComponents/BlocklyComponent.tsx +++ b/src/reactComponents/BlocklyComponent.tsx @@ -140,9 +140,6 @@ const BlocklyComponent = React.forwardRef ({ + kind: 'category', + name: Blockly.Msg['MRC_CATEGORY_TEST'], + contents: [ + { + kind: 'block', + type: 'mrc_print', + }, + ], +}); \ No newline at end of file diff --git a/src/toolbox/toolbox_common.ts b/src/toolbox/toolbox_common.ts index 08573d7b..2bc8f83b 100644 --- a/src/toolbox/toolbox_common.ts +++ b/src/toolbox/toolbox_common.ts @@ -29,6 +29,7 @@ import { getCategory as textCategory } from './text_category'; import { getCategory as listsCategory } from './lists_category'; import { getCategory as miscCategory } from './misc_category'; import { getCategory as methodsCategory } from './methods_category'; +import { getCategory as testCategory } from './test_category'; export function getToolboxItems( @@ -45,6 +46,10 @@ export function getToolboxItems( },] ); } + const tCategory = testCategory(); + if (tCategory.contents.length > 0) { + contents.push.apply(contents, [tCategory]); + } contents.push.apply( contents, @@ -65,6 +70,7 @@ export function getToolboxItems( custom: 'VARIABLE', }, methodsCategory(), + ], ); return contents; diff --git a/src/toolbox/toolbox_tests.ts b/src/toolbox/toolbox_tests.ts index a9c155c1..8a124e46 100644 --- a/src/toolbox/toolbox_tests.ts +++ b/src/toolbox/toolbox_tests.ts @@ -45,7 +45,7 @@ class ToolboxTestData { this.blocklyWorkspace = new Blockly.Workspace(); this.blocklyWorkspace.MAX_UNDO = 0; this.jsonBlocks = []; - if(contents){ + if (contents){ this.collectBlocks(contents); } this.index = 0;