Skip to content

Commit 06b177f

Browse files
authored
Changed mechanism, component, and event blocks to use functions mrcOnMove for handling events from blockly (#272)
* Rename mrcValidate to mrcOnModuleCurrent because it is called when the module becomes the current module. * Added code to mrc_component.ts and mrc_mechanism.ts to add a warning if the block is not in the holder. Updated code in mrc_event.ts (which already existed) to be similar. * Added reason parameter to mrcOnLoad methods. In mrc_mechanism_component_holder.ts: Removed code that uses change framework. Replaced function setName with method MechanismComponentHolderBlock.setNameOfChildBlock. Replaced function updateToolboxAfterDelay with method MechanismComponentHolderBlock.updateToolboxAfterDelay. Added function mrcDescenantsMayHaveChanged, which calls method MechanismComponentHolderBlock.mrcDescenantsMayHaveChanged. MechanismComponentHolderBlock.mrcDescenantsMayHaveChanged collects the ids of mechanisms, components, private components, and events and if they have changed, calls updateToolboxAfterDelay. Updated mrc_mechanism, mrc_component, and mrc_event to call MechanismComponentHolderBlock.setNameOfChildBlock when they are connected to the holder. Update mrc_mechanism, mrc_component, and mrc_event to call mrcDescendantsMayHaveChanged when they are moved. * Removed unplug call on mechanism, component, or event blocks if they aren't in the holder. If there are more than one of these in a stack and the user drags the whole stack out of the holder, the unplug will unplug the top one from the stack which is alarming. In mrc_mechanism_component_holder: Changed mrcMechanismIds to mrcMechanismBlockIds, mrcComponentIds to mrcComponentBlockIds, mrcPrivateComponentIds to mrcPrivateComponentBlockIds, and mrcEventIds to mrcEventBlockIds. Made them strings, which makes the comparing much more simple. Added mrcOnDescendantDisconnected, which is called when a mechanism, component, or event block is deleted. In editor, added code to handle move event for disconnect and call mrcOnDescendantDisconnect on the root block that the block was disconnected from.
1 parent 3bef9e0 commit 06b177f

13 files changed

+344
-110
lines changed

src/App.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ import * as clientSideStorage from './storage/client_side_storage';
4545
import * as CustomBlocks from './blocks/setup_custom_blocks';
4646

4747
import { initialize as initializePythonBlocks } from './blocks/utils/python';
48-
import * as ChangeFramework from './blocks/utils/change_framework'
4948
import { registerToolboxButton } from './blocks/mrc_event_handler'
5049
import { mutatorOpenListener } from './blocks/mrc_param_container'
5150
import { TOOLBOX_UPDATE_EVENT } from './blocks/mrc_mechanism_component_holder';
@@ -487,7 +486,6 @@ const AppContent: React.FC<AppContentProps> = ({ project, setProject }): React.J
487486
return;
488487
}
489488

490-
ChangeFramework.setup(newWorkspace);
491489
newWorkspace.addChangeListener(mutatorOpenListener);
492490
newWorkspace.addChangeListener(handleBlocksChanged);
493491

src/blocks/mrc_call_python_function.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,7 @@ const CALL_PYTHON_FUNCTION = {
340340
this.mrcMechanismId = extraState.mechanismId ? extraState.mechanismId : '';
341341
this.mrcComponentClassName = extraState.componentClassName ? extraState.componentClassName : '';
342342
this.mrcMechanismClassName = extraState.mechanismClassName ? extraState.mechanismClassName : '';
343-
// Initialize mrcMapComponentNameToId here. It will be filled during mrcValidate.
343+
// Initialize mrcMapComponentNameToId here. It will be filled during checkFunction.
344344
this.mrcMapComponentNameToId = {};
345345
this.updateBlock_();
346346
},
@@ -426,7 +426,7 @@ const CALL_PYTHON_FUNCTION = {
426426
.appendField('.');
427427
}
428428
// Here we create a text field for the component name.
429-
// Later, in mrcValidate, we will replace it with a dropdown.
429+
// Later, in checkFunction, we will replace it with a dropdown.
430430
titleInput
431431
.appendField(createFieldNonEditableText(''), FIELD_COMPONENT_NAME)
432432
.appendField('.')
@@ -586,18 +586,25 @@ const CALL_PYTHON_FUNCTION = {
586586
}
587587
return components;
588588
},
589+
590+
/**
591+
* mrcOnModuleCurrent is called for each CallPythonFunctionBlock when the module becomes the current module.
592+
*/
593+
mrcOnModuleCurrent: function(this: CallPythonFunctionBlock): void {
594+
this.checkFunction();
595+
},
589596
/**
590597
* mrcOnLoad is called for each CallPythonFunctionBlock when the blocks are loaded in the blockly
591598
* workspace.
592599
*/
593600
mrcOnLoad: function(this: CallPythonFunctionBlock): void {
594-
this.mrcValidate();
601+
this.checkFunction();
595602
},
596603
/**
597-
* mrcValidate checks the block, updates it, and/or adds a warning balloon if necessary.
598-
* It is called from mrcOnLoad above and from Editor.makeCurrent.
604+
* checkFunction checks the block, updates it, and/or adds a warning balloon if necessary.
605+
* It is called from mrcOnModuleCurrent and mrcOnLoad above.
599606
*/
600-
mrcValidate: function(this: CallPythonFunctionBlock): void {
607+
checkFunction: function(this: CallPythonFunctionBlock): void {
601608
const editor = Editor.getEditorForBlocklyWorkspace(this.workspace, true /* returnCurrentIfNotFound */);
602609
if (!editor) {
603610
return;

src/blocks/mrc_component.ts

Lines changed: 60 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ import { getAllowedTypesForSetCheck, getClassData, getSubclassNames } from './ut
3030
import * as toolboxItems from '../toolbox/items';
3131
import * as storageModule from '../storage/module';
3232
import * as storageModuleContent from '../storage/module_content';
33+
import {
34+
BLOCK_NAME as MRC_MECHANISM_COMPONENT_HOLDER,
35+
MechanismComponentHolderBlock,
36+
mrcDescendantsMayHaveChanged } from './mrc_mechanism_component_holder';
3337
import { createPort } from './mrc_port';
3438
import { ClassData, FunctionData } from './utils/python_json_types';
3539
import { renameMethodCallers } from './mrc_call_python_function'
@@ -41,6 +45,8 @@ export const OUTPUT_NAME = 'mrc_component';
4145
export const FIELD_NAME = 'NAME';
4246
export const FIELD_TYPE = 'TYPE';
4347

48+
const WARNING_ID_NOT_IN_HOLDER = 'not in holder';
49+
4450
type ConstructorArg = {
4551
name: string,
4652
type: string,
@@ -60,6 +66,15 @@ interface ComponentMixin extends ComponentMixinType {
6066
mrcArgs: ConstructorArg[],
6167
mrcImportModule: string,
6268
mrcStaticFunctionName: string,
69+
70+
/**
71+
* mrcHasNotInHolderWarning is set to true if we set the NOT_IN_HOLDER warning text on the block.
72+
* It is checked to avoid adding a warning if there already is one. Otherwise, if we get two move
73+
* events (one for drag and one for snap), and we call setWarningText for both events, we get a
74+
* detached warning balloon.
75+
* See https://github.com/wpilibsuite/systemcore-blocks-interface/issues/248.
76+
*/
77+
mrcHasNotInHolderWarning: boolean,
6378
}
6479
type ComponentMixinType = typeof COMPONENT;
6580

@@ -68,6 +83,7 @@ const COMPONENT = {
6883
* Block initialization.
6984
*/
7085
init: function (this: ComponentBlock): void {
86+
this.mrcHasNotInHolderWarning = false;
7187
this.setStyle(MRC_STYLE_COMPONENTS);
7288
const nameField = new Blockly.FieldTextInput('')
7389
nameField.setValidator(this.mrcNameFieldValidator.bind(this, nameField));
@@ -90,8 +106,8 @@ const COMPONENT = {
90106
if (this.mrcArgs){
91107
this.mrcArgs.forEach((arg) => {
92108
extraState.params!.push({
93-
'name': arg.name,
94-
'type': arg.type,
109+
name: arg.name,
110+
type: arg.type,
95111
});
96112
});
97113
}
@@ -115,12 +131,11 @@ const COMPONENT = {
115131
if (extraState.params) {
116132
extraState.params.forEach((arg) => {
117133
this.mrcArgs.push({
118-
'name': arg.name,
119-
'type': arg.type,
134+
name: arg.name,
135+
type: arg.type,
120136
});
121137
});
122138
}
123-
this.mrcArgs = extraState.params ? extraState.params : [];
124139
this.updateBlock_();
125140
},
126141
/**
@@ -167,15 +182,51 @@ const COMPONENT = {
167182
getArgName: function (this: ComponentBlock, _: number): string {
168183
return this.getFieldValue(FIELD_NAME) + '__' + 'port';
169184
},
170-
171-
172185
getComponentPorts: function (this: ComponentBlock, ports: {[argName: string]: string}): void {
173186
// Collect the ports for this component block.
174187
for (let i = 0; i < this.mrcArgs.length; i++) {
175188
const argName = this.getArgName(i);
176189
ports[argName] = this.mrcArgs[i].name;
177190
}
178191
},
192+
/**
193+
* mrcOnLoad is called for each ComponentBlock when the blocks are loaded in the blockly workspace.
194+
*/
195+
mrcOnLoad: function(this: ComponentBlock): void {
196+
this.checkBlockIsInHolder();
197+
},
198+
/**
199+
* mrcOnMove is called when a ComponentBlock is moved.
200+
*/
201+
mrcOnMove: function(this: ComponentBlock, reason: string[]): void {
202+
this.checkBlockIsInHolder();
203+
if (reason.includes('connect')) {
204+
const rootBlock: Blockly.Block | null = this.getRootBlock();
205+
if (rootBlock && rootBlock.type === MRC_MECHANISM_COMPONENT_HOLDER) {
206+
(rootBlock as MechanismComponentHolderBlock).setNameOfChildBlock(this);
207+
}
208+
}
209+
mrcDescendantsMayHaveChanged(this.workspace);
210+
},
211+
checkBlockIsInHolder: function(this: ComponentBlock): void {
212+
const rootBlock: Blockly.Block | null = this.getRootBlock();
213+
if (rootBlock && rootBlock.type === MRC_MECHANISM_COMPONENT_HOLDER) {
214+
// If the root block is the mechanism_component_holder, the component block is allowed to stay.
215+
// Remove any previous warning.
216+
this.setWarningText(null, WARNING_ID_NOT_IN_HOLDER);
217+
this.mrcHasNotInHolderWarning = false;
218+
} else {
219+
// Otherwise, add a warning to the block.
220+
if (!this.mrcHasNotInHolderWarning) {
221+
this.setWarningText(Blockly.Msg.WARNING_COMPONENT_NOT_IN_HOLDER, WARNING_ID_NOT_IN_HOLDER);
222+
const icon = this.getIcon(Blockly.icons.IconType.WARNING);
223+
if (icon) {
224+
icon.setBubbleVisible(true);
225+
}
226+
this.mrcHasNotInHolderWarning = true;
227+
}
228+
}
229+
},
179230
/**
180231
* mrcChangeIds is called when a module is copied so that the copy has different ids than the original.
181232
*/
@@ -253,8 +304,8 @@ function createComponentBlock(
253304

254305
if (constructorData.expectedPortType) {
255306
extraState.params!.push({
256-
'name': constructorData.expectedPortType,
257-
'type': 'Port',
307+
name: constructorData.expectedPortType,
308+
type: 'Port',
258309
});
259310
if ( moduleType == storageModule.ModuleType.ROBOT ) {
260311
inputs['ARG0'] = createPort(constructorData.expectedPortType);

src/blocks/mrc_event.ts

Lines changed: 36 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@ import { MRC_STYLE_EVENTS } from '../themes/styles'
2525
import { Parameter } from './mrc_class_method_def';
2626
import { ExtendedPythonGenerator } from '../editor/extended_python_generator';
2727
import { MUTATOR_BLOCK_NAME, PARAM_CONTAINER_BLOCK_NAME, MethodMutatorArgBlock } from './mrc_param_container'
28-
import { BLOCK_NAME as MRC_MECHANISM_COMPONENT_HOLDER } from './mrc_mechanism_component_holder';
28+
import {
29+
BLOCK_NAME as MRC_MECHANISM_COMPONENT_HOLDER,
30+
MechanismComponentHolderBlock,
31+
mrcDescendantsMayHaveChanged } from './mrc_mechanism_component_holder';
2932
import * as toolboxItems from '../toolbox/items';
3033
import * as storageModuleContent from '../storage/module_content';
3134
import { renameMethodCallers, mutateMethodCallers } from './mrc_call_python_function'
@@ -51,12 +54,13 @@ interface EventMixin extends EventMixinType {
5154
mrcParameters: Parameter[],
5255

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

@@ -65,6 +69,7 @@ const EVENT = {
6569
* Block initialization.
6670
*/
6771
init: function (this: EventBlock): void {
72+
this.mrcHasNotInHolderWarning = false;
6873
this.setStyle(MRC_STYLE_EVENTS);
6974
this.appendDummyInput(INPUT_TITLE)
7075
.appendField(new Blockly.FieldTextInput('my_event'), FIELD_EVENT_NAME);
@@ -84,8 +89,8 @@ const EVENT = {
8489
if (this.mrcParameters) {
8590
this.mrcParameters.forEach((arg) => {
8691
extraState.params!.push({
87-
'name': arg.name,
88-
'type': arg.type,
92+
name: arg.name,
93+
type: arg.type,
8994
});
9095
});
9196
}
@@ -97,17 +102,14 @@ const EVENT = {
97102
loadExtraState: function (this: EventBlock, extraState: EventExtraState): void {
98103
this.mrcEventId = extraState.eventId ? extraState.eventId : this.id;
99104
this.mrcParameters = [];
100-
this.mrcHasWarning = false;
101-
102105
if (extraState.params) {
103106
extraState.params.forEach((arg) => {
104107
this.mrcParameters.push({
105-
'name': arg.name,
106-
'type': arg.type,
108+
name: arg.name,
109+
type: arg.type,
107110
});
108111
});
109112
}
110-
this.mrcParameters = extraState.params ? extraState.params : [];
111113
this.updateBlock_();
112114
},
113115
/**
@@ -207,28 +209,37 @@ const EVENT = {
207209
* mrcOnLoad is called for each EventBlock when the blocks are loaded in the blockly workspace.
208210
*/
209211
mrcOnLoad: function(this: EventBlock): void {
210-
this.checkParentIsHolder();
212+
this.checkBlockIsInHolder();
211213
},
212214
/**
213215
* mrcOnMove is called when an EventBlock is moved.
214216
*/
215-
mrcOnMove: function(this: EventBlock): void {
216-
this.checkParentIsHolder();
217+
mrcOnMove: function(this: EventBlock, reason: string[]): void {
218+
this.checkBlockIsInHolder();
219+
if (reason.includes('connect')) {
220+
const rootBlock: Blockly.Block | null = this.getRootBlock();
221+
if (rootBlock && rootBlock.type === MRC_MECHANISM_COMPONENT_HOLDER) {
222+
(rootBlock as MechanismComponentHolderBlock).setNameOfChildBlock(this);
223+
}
224+
}
225+
mrcDescendantsMayHaveChanged(this.workspace);
217226
},
218-
checkParentIsHolder: function(this: EventBlock): void {
219-
const parentBlock = this.getParent();
220-
if (parentBlock && parentBlock.type === MRC_MECHANISM_COMPONENT_HOLDER) {
221-
// If the parent block is the mechanism_component_holder, the event block is allowed to stay.
227+
checkBlockIsInHolder: function(this: EventBlock): void {
228+
const rootBlock: Blockly.Block | null = this.getRootBlock();
229+
if (rootBlock && rootBlock.type === MRC_MECHANISM_COMPONENT_HOLDER) {
230+
// If the root block is the mechanism_component_holder, the event block is allowed to stay.
222231
// Remove any previous warning.
223232
this.setWarningText(null, WARNING_ID_NOT_IN_HOLDER);
224-
this.mrcHasWarning = false;
233+
this.mrcHasNotInHolderWarning = false;
225234
} else {
226235
// Otherwise, add a warning to the block.
227-
this.unplug(true);
228-
if (!this.mrcHasWarning) {
236+
if (!this.mrcHasNotInHolderWarning) {
229237
this.setWarningText(Blockly.Msg.WARNING_EVENT_NOT_IN_HOLDER, WARNING_ID_NOT_IN_HOLDER);
230-
this.getIcon(Blockly.icons.IconType.WARNING)!.setBubbleVisible(true);
231-
this.mrcHasWarning = true;
238+
const icon = this.getIcon(Blockly.icons.IconType.WARNING);
239+
if (icon) {
240+
icon.setBubbleVisible(true);
241+
}
242+
this.mrcHasNotInHolderWarning = true;
232243
}
233244
}
234245
},

src/blocks/mrc_event_handler.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -167,18 +167,24 @@ const EVENT_HANDLER = {
167167
});
168168
},
169169

170+
/**
171+
* mrcOnModuleCurrent is called for each EventHandlerBlock when the module becomes the current module.
172+
*/
173+
mrcOnModuleCurrent: function(this: EventHandlerBlock): void {
174+
this.checkEvent();
175+
},
170176
/**
171177
* mrcOnLoad is called for each EventHandlerBlock when the blocks are loaded in the blockly
172178
* workspace.
173179
*/
174180
mrcOnLoad: function(this: EventHandlerBlock): void {
175-
this.mrcValidate();
181+
this.checkEvent();
176182
},
177183
/**
178-
* mrcValidate checks the block, updates it, and/or adds a warning balloon if necessary.
179-
* It is called from mrcOnLoad above and from Editor.makeCurrent.
184+
* checkEvent checks the block, updates it, and/or adds a warning balloon if necessary.
185+
* It is called from mrcOnModuleCurrent and mrcOnLoad above.
180186
*/
181-
mrcValidate: function(this: EventHandlerBlock): void {
187+
checkEvent: function(this: EventHandlerBlock): void {
182188
const warnings: string[] = [];
183189

184190
const editor = Editor.getEditorForBlocklyWorkspace(this.workspace, true /* returnCurrentIfNotFound */);

src/blocks/mrc_get_parameter.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ const GET_PARAMETER_BLOCK = {
8282
/**
8383
* mrcOnMove is called when an EventBlock is moved.
8484
*/
85-
mrcOnMove: function(this: GetParameterBlock): void {
85+
mrcOnMove: function(this: GetParameterBlock, _reason: string[]): void {
8686
this.checkBlockPlacement();
8787
},
8888
mrcOnAncestorMove: function(this: GetParameterBlock): void {
@@ -91,7 +91,7 @@ const GET_PARAMETER_BLOCK = {
9191
checkBlockPlacement: function(this: GetParameterBlock): void {
9292
const legalParameterNames: string[] = [];
9393

94-
const rootBlock: Blockly.Block = this.getRootBlock();
94+
const rootBlock: Blockly.Block | null = this.getRootBlock();
9595
if (rootBlock.type === MRC_CLASS_METHOD_DEF) {
9696
// This block is within a class method definition.
9797
const classMethodDefBlock = rootBlock as ClassMethodDefBlock;

0 commit comments

Comments
 (0)