Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ import * as clientSideStorage from './storage/client_side_storage';
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 { registerToolboxButton } from './blocks/mrc_event_handler'
import { mutatorOpenListener } from './blocks/mrc_param_container'
import { TOOLBOX_UPDATE_EVENT } from './blocks/mrc_mechanism_component_holder';
Expand Down Expand Up @@ -487,7 +486,6 @@ const AppContent: React.FC<AppContentProps> = ({ project, setProject }): React.J
return;
}

ChangeFramework.setup(newWorkspace);
newWorkspace.addChangeListener(mutatorOpenListener);
newWorkspace.addChangeListener(handleBlocksChanged);

Expand Down
19 changes: 13 additions & 6 deletions src/blocks/mrc_call_python_function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ const CALL_PYTHON_FUNCTION = {
this.mrcMechanismId = extraState.mechanismId ? extraState.mechanismId : '';
this.mrcComponentClassName = extraState.componentClassName ? extraState.componentClassName : '';
this.mrcMechanismClassName = extraState.mechanismClassName ? extraState.mechanismClassName : '';
// Initialize mrcMapComponentNameToId here. It will be filled during mrcValidate.
// Initialize mrcMapComponentNameToId here. It will be filled during checkFunction.
this.mrcMapComponentNameToId = {};
this.updateBlock_();
},
Expand Down Expand Up @@ -426,7 +426,7 @@ const CALL_PYTHON_FUNCTION = {
.appendField('.');
}
// Here we create a text field for the component name.
// Later, in mrcValidate, we will replace it with a dropdown.
// Later, in checkFunction, we will replace it with a dropdown.
titleInput
.appendField(createFieldNonEditableText(''), FIELD_COMPONENT_NAME)
.appendField('.')
Expand Down Expand Up @@ -586,18 +586,25 @@ const CALL_PYTHON_FUNCTION = {
}
return components;
},

/**
* mrcOnModuleCurrent is called for each CallPythonFunctionBlock when the module becomes the current module.
*/
mrcOnModuleCurrent: function(this: CallPythonFunctionBlock): void {
this.checkFunction();
},
/**
* mrcOnLoad is called for each CallPythonFunctionBlock when the blocks are loaded in the blockly
* workspace.
*/
mrcOnLoad: function(this: CallPythonFunctionBlock): void {
this.mrcValidate();
this.checkFunction();
},
/**
* mrcValidate checks the block, updates it, and/or adds a warning balloon if necessary.
* It is called from mrcOnLoad above and from Editor.makeCurrent.
* checkFunction checks the block, updates it, and/or adds a warning balloon if necessary.
* It is called from mrcOnModuleCurrent and mrcOnLoad above.
*/
mrcValidate: function(this: CallPythonFunctionBlock): void {
checkFunction: function(this: CallPythonFunctionBlock): void {
const editor = Editor.getEditorForBlocklyWorkspace(this.workspace, true /* returnCurrentIfNotFound */);
if (!editor) {
return;
Expand Down
69 changes: 60 additions & 9 deletions src/blocks/mrc_component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ import { getAllowedTypesForSetCheck, getClassData, getSubclassNames } from './ut
import * as toolboxItems from '../toolbox/items';
import * as storageModule from '../storage/module';
import * as storageModuleContent from '../storage/module_content';
import {
BLOCK_NAME as MRC_MECHANISM_COMPONENT_HOLDER,
MechanismComponentHolderBlock,
mrcDescendantsMayHaveChanged } from './mrc_mechanism_component_holder';
import { createPort } from './mrc_port';
import { ClassData, FunctionData } from './utils/python_json_types';
import { renameMethodCallers } from './mrc_call_python_function'
Expand All @@ -41,6 +45,8 @@ export const OUTPUT_NAME = 'mrc_component';
export const FIELD_NAME = 'NAME';
export const FIELD_TYPE = 'TYPE';

const WARNING_ID_NOT_IN_HOLDER = 'not in holder';

type ConstructorArg = {
name: string,
type: string,
Expand All @@ -60,6 +66,15 @@ interface ComponentMixin extends ComponentMixinType {
mrcArgs: ConstructorArg[],
mrcImportModule: string,
mrcStaticFunctionName: string,

/**
* mrcHasNotInHolderWarning is set to true if we set the NOT_IN_HOLDER warning text on the block.
* It is checked to avoid adding a warning if there already is one. Otherwise, if we get two move
* events (one for drag and one for snap), and we call setWarningText for both events, we get a
* detached warning balloon.
* See https://github.com/wpilibsuite/systemcore-blocks-interface/issues/248.
*/
mrcHasNotInHolderWarning: boolean,
}
type ComponentMixinType = typeof COMPONENT;

Expand All @@ -68,6 +83,7 @@ const COMPONENT = {
* Block initialization.
*/
init: function (this: ComponentBlock): void {
this.mrcHasNotInHolderWarning = false;
this.setStyle(MRC_STYLE_COMPONENTS);
const nameField = new Blockly.FieldTextInput('')
nameField.setValidator(this.mrcNameFieldValidator.bind(this, nameField));
Expand All @@ -90,8 +106,8 @@ const COMPONENT = {
if (this.mrcArgs){
this.mrcArgs.forEach((arg) => {
extraState.params!.push({
'name': arg.name,
'type': arg.type,
name: arg.name,
type: arg.type,
});
});
}
Expand All @@ -115,12 +131,11 @@ const COMPONENT = {
if (extraState.params) {
extraState.params.forEach((arg) => {
this.mrcArgs.push({
'name': arg.name,
'type': arg.type,
name: arg.name,
type: arg.type,
});
});
}
this.mrcArgs = extraState.params ? extraState.params : [];
this.updateBlock_();
},
/**
Expand Down Expand Up @@ -167,15 +182,51 @@ const COMPONENT = {
getArgName: function (this: ComponentBlock, _: number): string {
return this.getFieldValue(FIELD_NAME) + '__' + 'port';
},


getComponentPorts: function (this: ComponentBlock, ports: {[argName: string]: string}): void {
// Collect the ports for this component block.
for (let i = 0; i < this.mrcArgs.length; i++) {
const argName = this.getArgName(i);
ports[argName] = this.mrcArgs[i].name;
}
},
/**
* mrcOnLoad is called for each ComponentBlock when the blocks are loaded in the blockly workspace.
*/
mrcOnLoad: function(this: ComponentBlock): void {
this.checkBlockIsInHolder();
},
/**
* mrcOnMove is called when a ComponentBlock is moved.
*/
mrcOnMove: function(this: ComponentBlock, reason: string[]): void {
this.checkBlockIsInHolder();
if (reason.includes('connect')) {
const rootBlock: Blockly.Block | null = this.getRootBlock();
if (rootBlock && rootBlock.type === MRC_MECHANISM_COMPONENT_HOLDER) {
(rootBlock as MechanismComponentHolderBlock).setNameOfChildBlock(this);
}
}
mrcDescendantsMayHaveChanged(this.workspace);
},
checkBlockIsInHolder: function(this: ComponentBlock): void {
const rootBlock: Blockly.Block | null = this.getRootBlock();
if (rootBlock && rootBlock.type === MRC_MECHANISM_COMPONENT_HOLDER) {
// If the root block is the mechanism_component_holder, the component block is allowed to stay.
// Remove any previous warning.
this.setWarningText(null, WARNING_ID_NOT_IN_HOLDER);
this.mrcHasNotInHolderWarning = false;
} else {
// Otherwise, add a warning to the block.
if (!this.mrcHasNotInHolderWarning) {
this.setWarningText(Blockly.Msg.WARNING_COMPONENT_NOT_IN_HOLDER, WARNING_ID_NOT_IN_HOLDER);
const icon = this.getIcon(Blockly.icons.IconType.WARNING);
if (icon) {
icon.setBubbleVisible(true);
}
this.mrcHasNotInHolderWarning = true;
}
}
},
/**
* mrcChangeIds is called when a module is copied so that the copy has different ids than the original.
*/
Expand Down Expand Up @@ -253,8 +304,8 @@ function createComponentBlock(

if (constructorData.expectedPortType) {
extraState.params!.push({
'name': constructorData.expectedPortType,
'type': 'Port',
name: constructorData.expectedPortType,
type: 'Port',
});
if ( moduleType == storageModule.ModuleType.ROBOT ) {
inputs['ARG0'] = createPort(constructorData.expectedPortType);
Expand Down
61 changes: 36 additions & 25 deletions src/blocks/mrc_event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ import { MRC_STYLE_EVENTS } from '../themes/styles'
import { Parameter } from './mrc_class_method_def';
import { ExtendedPythonGenerator } from '../editor/extended_python_generator';
import { MUTATOR_BLOCK_NAME, PARAM_CONTAINER_BLOCK_NAME, MethodMutatorArgBlock } from './mrc_param_container'
import { BLOCK_NAME as MRC_MECHANISM_COMPONENT_HOLDER } from './mrc_mechanism_component_holder';
import {
BLOCK_NAME as MRC_MECHANISM_COMPONENT_HOLDER,
MechanismComponentHolderBlock,
mrcDescendantsMayHaveChanged } from './mrc_mechanism_component_holder';
import * as toolboxItems from '../toolbox/items';
import * as storageModuleContent from '../storage/module_content';
import { renameMethodCallers, mutateMethodCallers } from './mrc_call_python_function'
Expand All @@ -51,12 +54,13 @@ interface EventMixin extends EventMixinType {
mrcParameters: Parameter[],

/**
* mrcHasWarning is set to true if we set the warning text on the block. It is checked to avoid
* adding a warning if there already is one. Otherwise, if we get two move events (one for drag
* and one for snap), and we call setWarningText for both events, we get a detached warning
* balloon. See https://github.com/wpilibsuite/systemcore-blocks-interface/issues/248.
* mrcHasNotInHolderWarning is set to true if we set the NOT_IN_HOLDER warning text on the block.
* It is checked to avoid adding a warning if there already is one. Otherwise, if we get two move
* events (one for drag and one for snap), and we call setWarningText for both events, we get a
* detached warning balloon.
* See https://github.com/wpilibsuite/systemcore-blocks-interface/issues/248.
*/
mrcHasWarning: boolean,
mrcHasNotInHolderWarning: boolean,
}
type EventMixinType = typeof EVENT;

Expand All @@ -65,6 +69,7 @@ const EVENT = {
* Block initialization.
*/
init: function (this: EventBlock): void {
this.mrcHasNotInHolderWarning = false;
this.setStyle(MRC_STYLE_EVENTS);
this.appendDummyInput(INPUT_TITLE)
.appendField(new Blockly.FieldTextInput('my_event'), FIELD_EVENT_NAME);
Expand All @@ -84,8 +89,8 @@ const EVENT = {
if (this.mrcParameters) {
this.mrcParameters.forEach((arg) => {
extraState.params!.push({
'name': arg.name,
'type': arg.type,
name: arg.name,
type: arg.type,
});
});
}
Expand All @@ -97,17 +102,14 @@ const EVENT = {
loadExtraState: function (this: EventBlock, extraState: EventExtraState): void {
this.mrcEventId = extraState.eventId ? extraState.eventId : this.id;
this.mrcParameters = [];
this.mrcHasWarning = false;

if (extraState.params) {
extraState.params.forEach((arg) => {
this.mrcParameters.push({
'name': arg.name,
'type': arg.type,
name: arg.name,
type: arg.type,
});
});
}
this.mrcParameters = extraState.params ? extraState.params : [];
this.updateBlock_();
},
/**
Expand Down Expand Up @@ -207,28 +209,37 @@ const EVENT = {
* mrcOnLoad is called for each EventBlock when the blocks are loaded in the blockly workspace.
*/
mrcOnLoad: function(this: EventBlock): void {
this.checkParentIsHolder();
this.checkBlockIsInHolder();
},
/**
* mrcOnMove is called when an EventBlock is moved.
*/
mrcOnMove: function(this: EventBlock): void {
this.checkParentIsHolder();
mrcOnMove: function(this: EventBlock, reason: string[]): void {
this.checkBlockIsInHolder();
if (reason.includes('connect')) {
const rootBlock: Blockly.Block | null = this.getRootBlock();
if (rootBlock && rootBlock.type === MRC_MECHANISM_COMPONENT_HOLDER) {
(rootBlock as MechanismComponentHolderBlock).setNameOfChildBlock(this);
}
}
mrcDescendantsMayHaveChanged(this.workspace);
},
checkParentIsHolder: function(this: EventBlock): void {
const parentBlock = this.getParent();
if (parentBlock && parentBlock.type === MRC_MECHANISM_COMPONENT_HOLDER) {
// If the parent block is the mechanism_component_holder, the event block is allowed to stay.
checkBlockIsInHolder: function(this: EventBlock): void {
const rootBlock: Blockly.Block | null = this.getRootBlock();
if (rootBlock && rootBlock.type === MRC_MECHANISM_COMPONENT_HOLDER) {
// If the root block is the mechanism_component_holder, the event block is allowed to stay.
// Remove any previous warning.
this.setWarningText(null, WARNING_ID_NOT_IN_HOLDER);
this.mrcHasWarning = false;
this.mrcHasNotInHolderWarning = false;
} else {
// Otherwise, add a warning to the block.
this.unplug(true);
if (!this.mrcHasWarning) {
if (!this.mrcHasNotInHolderWarning) {
this.setWarningText(Blockly.Msg.WARNING_EVENT_NOT_IN_HOLDER, WARNING_ID_NOT_IN_HOLDER);
this.getIcon(Blockly.icons.IconType.WARNING)!.setBubbleVisible(true);
this.mrcHasWarning = true;
const icon = this.getIcon(Blockly.icons.IconType.WARNING);
if (icon) {
icon.setBubbleVisible(true);
}
this.mrcHasNotInHolderWarning = true;
}
}
},
Expand Down
14 changes: 10 additions & 4 deletions src/blocks/mrc_event_handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,18 +167,24 @@ const EVENT_HANDLER = {
});
},

/**
* mrcOnModuleCurrent is called for each EventHandlerBlock when the module becomes the current module.
*/
mrcOnModuleCurrent: function(this: EventHandlerBlock): void {
this.checkEvent();
},
/**
* mrcOnLoad is called for each EventHandlerBlock when the blocks are loaded in the blockly
* workspace.
*/
mrcOnLoad: function(this: EventHandlerBlock): void {
this.mrcValidate();
this.checkEvent();
},
/**
* mrcValidate checks the block, updates it, and/or adds a warning balloon if necessary.
* It is called from mrcOnLoad above and from Editor.makeCurrent.
* checkEvent checks the block, updates it, and/or adds a warning balloon if necessary.
* It is called from mrcOnModuleCurrent and mrcOnLoad above.
*/
mrcValidate: function(this: EventHandlerBlock): void {
checkEvent: function(this: EventHandlerBlock): void {
const warnings: string[] = [];

const editor = Editor.getEditorForBlocklyWorkspace(this.workspace, true /* returnCurrentIfNotFound */);
Expand Down
4 changes: 2 additions & 2 deletions src/blocks/mrc_get_parameter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ const GET_PARAMETER_BLOCK = {
/**
* mrcOnMove is called when an EventBlock is moved.
*/
mrcOnMove: function(this: GetParameterBlock): void {
mrcOnMove: function(this: GetParameterBlock, _reason: string[]): void {
this.checkBlockPlacement();
},
mrcOnAncestorMove: function(this: GetParameterBlock): void {
Expand All @@ -91,7 +91,7 @@ const GET_PARAMETER_BLOCK = {
checkBlockPlacement: function(this: GetParameterBlock): void {
const legalParameterNames: string[] = [];

const rootBlock: Blockly.Block = this.getRootBlock();
const rootBlock: Blockly.Block | null = this.getRootBlock();
if (rootBlock.type === MRC_CLASS_METHOD_DEF) {
// This block is within a class method definition.
const classMethodDefBlock = rootBlock as ClassMethodDefBlock;
Expand Down
Loading