Skip to content

Commit 05979cd

Browse files
authored
When building the Methods category, use a button block instead of an mrc_class_method_def block to represent base class methods that are already on the workspace. (#314)
1 parent 3fe6f5c commit 05979cd

File tree

10 files changed

+102
-94
lines changed

10 files changed

+102
-94
lines changed

src/blocks/mrc_class_method_def.ts

Lines changed: 40 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,14 @@
1919
* @fileoverview Blocks for class method definition
2020
* @author [email protected] (Alan Smith)
2121
*/
22+
2223
import * as Blockly from 'blockly';
24+
import { Order } from 'blockly/python';
25+
26+
import type { MessageInstance } from 'antd/es/message/interface';
2327
import { MRC_STYLE_FUNCTIONS } from '../themes/styles';
2428
import { createFieldNonEditableText } from '../fields/FieldNonEditableText'
2529
import { createParameterFieldFlydown } from '../fields/field_flydown';
26-
import { Order } from 'blockly/python';
2730
import { ExtendedPythonGenerator } from '../editor/extended_python_generator';
2831
import * as storageModule from '../storage/module';
2932
import * as storageModuleContent from '../storage/module_content';
@@ -37,11 +40,14 @@ import * as paramContainer from './mrc_param_container'
3740

3841
export const BLOCK_NAME = 'mrc_class_method_def';
3942

43+
const BUTTON_CALLBACK_KEY = 'CLASS_METHOD_DEF_ALREADY_ON_WORKSPACE';
44+
const BUTTON_STYLE_PREFIX = 'classMethodDefButtonStyle_';
45+
4046
const NO_RETURN_VALUE = 'None';
4147
const UNTYPED_RETURN_VALUE = '';
4248

4349
const INPUT_TITLE = 'TITLE';
44-
export const FIELD_METHOD_NAME = 'NAME';
50+
const FIELD_METHOD_NAME = 'NAME';
4551
const FIELD_PARAM_PREFIX = 'PARAM_';
4652
const INPUT_STACK = 'STACK';
4753
export const INPUT_RETURN = 'RETURN';
@@ -542,31 +548,29 @@ export function createCustomMethodBlockWithReturn(): toolboxItems.Block {
542548
}
543549

544550
export function getBaseClassBlocks(
545-
baseClassName: string): toolboxItems.Block[] {
546-
const blocks: toolboxItems.Block[] = [];
551+
workspace: Blockly.WorkspaceSvg, baseClassName: string, methodNamesNotOverrideable: string[],
552+
methodNamesAlreadyOverridden: string[]): toolboxItems.ContentsType[] {
553+
const contents: toolboxItems.ContentsType[] = [];
547554
const classData = getClassData(baseClassName);
548555
if (classData) {
549-
classData.instanceMethods.forEach(functionData => {
550-
blocks.push(createClassMethodDefBlock(
551-
functionData,
552-
/* canChangeSignature */ false,
553-
/* canBeCalledWithinClass */ false,
554-
/* canBeCalledOutsideClass */ false,
555-
));
556-
});
556+
classData.instanceMethods
557+
.filter(functionData => !methodNamesNotOverrideable.includes(functionData.functionName))
558+
.forEach(functionData => {
559+
if (methodNamesAlreadyOverridden.includes(functionData.functionName)) {
560+
contents.push(createButton(workspace, functionData));
561+
} else {
562+
contents.push(createClassMethodDefBlock(functionData));
563+
}
564+
});
557565
}
558-
return blocks;
566+
return contents;
559567
}
560568

561-
function createClassMethodDefBlock(
562-
functionData: FunctionData,
563-
canChangeSignature: boolean,
564-
canBeCalledWithinClass: boolean,
565-
canBeCalledOutsideClass: boolean): toolboxItems.Block {
569+
function createClassMethodDefBlock(functionData: FunctionData): toolboxItems.Block {
566570
const extraState: ClassMethodDefExtraState = {
567-
canChangeSignature,
568-
canBeCalledWithinClass,
569-
canBeCalledOutsideClass,
571+
canChangeSignature: false,
572+
canBeCalledWithinClass: false,
573+
canBeCalledOutsideClass: false,
570574
returnType: functionData.returnType,
571575
params: [],
572576
};
@@ -581,6 +585,15 @@ function createClassMethodDefBlock(
581585
return new toolboxItems.Block(BLOCK_NAME, extraState, fields, null);
582586
}
583587

588+
function createButton(
589+
workspace: Blockly.WorkspaceSvg, functionData: FunctionData): toolboxItems.Button {
590+
// Use non-breakable spaces so it looks more like an class method def block.
591+
const spaces = '\u00A0\u00A0';
592+
const text = spaces + functionData.functionName + spaces;
593+
const style = BUTTON_STYLE_PREFIX + workspace.getTheme().name;
594+
return new toolboxItems.Button(text, BUTTON_CALLBACK_KEY, style);
595+
}
596+
584597
// Misc
585598

586599
export function getMethodsForWithin(
@@ -618,6 +631,12 @@ export function getMethodNamesAlreadyOverriddenInWorkspace(
618631
});
619632
}
620633

634+
export function registerToolboxButton(workspace: Blockly.WorkspaceSvg, messageApi: MessageInstance) {
635+
workspace.registerButtonCallback(BUTTON_CALLBACK_KEY, function (_button) {
636+
messageApi.info(Blockly.Msg.CLASS_METHOD_DEF_ALREADY_ON_WORKSPACE);
637+
});
638+
}
639+
621640
/**
622641
* Upgrades the ClassMethodDefBlocks in the given workspace from version 002 to 003.
623642
* This function should only be called when upgrading old projects.

src/blocks/tokens.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ export function customTokens(t: (key: string) => string): typeof Blockly.Msg {
3636
PARAMETERS: t('BLOCKLY.PARAMETERS'),
3737
PARAMETERS_CAN_ONLY_GO_IN_THEIR_METHODS_BLOCK:
3838
t('BLOCKLY.PARAMETERS_CAN_ONLY_GO_IN_THEIR_METHODS_BLOCK'),
39+
CLASS_METHOD_DEF_ALREADY_ON_WORKSPACE:
40+
t('BLOCKLY.CLASS_METHOD_DEF_ALREADY_ON_WORKSPACE'),
3941
EVENT_HANDLER_ALREADY_ON_WORKSPACE:
4042
t('BLOCKLY.EVENT_HANDLER_ALREADY_ON_WORKSPACE'),
4143
EVENT_HANDLER_ROBOT_EVENT_NOT_FOUND:
@@ -148,4 +150,4 @@ export function replaceTokens(template: string, values: Record<string, string>):
148150
return template.replace(/\{\{(\w+)\}\}/g, (match, token) => {
149151
return values[token] !== undefined ? values[token] : match;
150152
});
151-
}
153+
}

src/blocks/utils/python.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,25 @@ import * as SetPythonVariable from "../mrc_set_python_variable";
3232
// Utilities related to blocks for python modules and classes, including those from RobotPy, external samples, etc.
3333

3434
export const MODULE_NAME_BLOCKS_BASE_CLASSES = 'blocks_base_classes';
35+
3536
export const CLASS_NAME_ROBOT_BASE = MODULE_NAME_BLOCKS_BASE_CLASSES + '.RobotBase';
37+
export const ROBOT_METHOD_NAMES_NOT_OVERRIDEABLE: string[] = [
38+
'define_hardware',
39+
'fire_event',
40+
'register_event_handler',
41+
'unregister_event_handler',
42+
];
43+
3644
export const CLASS_NAME_MECHANISM = MODULE_NAME_BLOCKS_BASE_CLASSES + '.Mechanism';
45+
export const MECHANISM_METHOD_NAMES_NOT_OVERRIDEABLE: string[] = [
46+
'fire_event',
47+
'register_event_handler',
48+
'unregister_event_handler',
49+
];
3750

3851
// TODO(lizlooney): what about PeriodicOpMode and LinearOpMode?
3952
export const CLASS_NAME_OPMODE = MODULE_NAME_BLOCKS_BASE_CLASSES + '.OpMode';
53+
export const OPMODE_METHOD_NAMES_NOT_OVERRIDEABLE: string[] = [];
4054
// TODO(lizlooney): Make sure to update the value of PERIODIC_METHOD_NAME when we update wpilib.
4155
export const PERIODIC_METHOD_NAME = 'loop';
4256

src/i18n/locales/en/translation.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@
126126
"PARAMETERS": "Parameters",
127127
"PARAMETERS_CAN_ONLY_GO_IN_THEIR_METHODS_BLOCK": "Parameters can only go in their method's block",
128128
"JUMP_CAN_ONLY_GO_IN_THEIR_STEPS_BLOCK": "Jump can only go in their step's block",
129+
"CLASS_METHOD_DEF_ALREADY_ON_WORKSPACE": "This method is already on the workspace.",
129130
"EVENT_HANDLER_ALREADY_ON_WORKSPACE": "This event handler is already on the workspace.",
130131
"EVENT_HANDLER_ROBOT_EVENT_NOT_FOUND": "This block is an event handler for an event that no longer exists.",
131132
"EVENT_HANDLER_MECHANISM_EVENT_NOT_FOUND": "This block is an event handler for an event that no longer exists.",

src/i18n/locales/es/translation.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@
127127
"PARAMETERS": "Parámetros",
128128
"PARAMETERS_CAN_ONLY_GO_IN_THEIR_METHODS_BLOCK": "Los parámetros solo pueden ir en el bloque de su método",
129129
"JUMP_CAN_ONLY_GO_IN_THEIR_STEPS_BLOCK": "El salto solo puede ir en el bloque de su paso",
130+
"CLASS_METHOD_DEF_ALREADY_ON_WORKSPACE": "Este método ya está en el espacio de trabajo.",
130131
"EVENT_HANDLER_ALREADY_ON_WORKSPACE": "Este controlador de eventos ya está en el espacio de trabajo.",
131132
"EVENT_HANDLER_ROBOT_EVENT_NOT_FOUND": "Este bloque es un controlador de eventos para un evento que ya no existe.",
132133
"EVENT_HANDLER_MECHANISM_EVENT_NOT_FOUND": "Este bloque es un controlador de eventos para un evento que ya no existe.",

src/i18n/locales/he/translation.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@
126126
"PARAMETERS": "פרמטרים",
127127
"PARAMETERS_CAN_ONLY_GO_IN_THEIR_METHODS_BLOCK": "פרמטרים יכולים ללכת רק בבלוק השיטה שלהם",
128128
"JUMP_CAN_ONLY_GO_IN_THEIR_STEPS_BLOCK": "קפיצה יכולה ללכת רק בבלוק הצעד שלה",
129+
"CLASS_METHOD_DEF_ALREADY_ON_WORKSPACE": "שיטה זו כבר קיימת בסביבת העבודה.",
129130
"EVENT_HANDLER_ALREADY_ON_WORKSPACE": "מטפל אירועים זה כבר נמצא במרחב העבודה.",
130131
"EVENT_HANDLER_ROBOT_EVENT_NOT_FOUND": "הבלוק הזה הוא מנהל אירועים לאירוע שכבר לא קיים.",
131132
"EVENT_HANDLER_MECHANISM_EVENT_NOT_FOUND": "הבלוק הזה הוא מנהל אירועים לאירוע שכבר לא קיים.",

src/reactComponents/TabContent.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ import { extendedPythonGenerator } from '../editor/extended_python_generator';
3030
import * as storageModule from '../storage/module';
3131
import * as storageProject from '../storage/project';
3232
import * as commonStorage from '../storage/common_storage';
33-
import { registerToolboxButton } from '../blocks/mrc_event_handler';
33+
import * as classMethodDef from '../blocks/mrc_class_method_def'
34+
import * as eventHandler from '../blocks/mrc_event_handler'
3435
import { Content } from 'antd/es/layout/layout';
3536

3637
/** Default size for code panel. */
@@ -130,7 +131,8 @@ export const TabContent = React.forwardRef<TabContentRef, TabContentProps>(({
130131
/** Called when workspace is created. */
131132
const setupWorkspace = React.useCallback((_modulePath: string, newWorkspace: Blockly.WorkspaceSvg) => {
132133
newWorkspace.addChangeListener(handleBlocksChanged);
133-
registerToolboxButton(newWorkspace, messageApi);
134+
classMethodDef.registerToolboxButton(newWorkspace, messageApi);
135+
eventHandler.registerToolboxButton(newWorkspace, messageApi);
134136

135137
const newEditor = new editor.Editor(
136138
newWorkspace,

src/themes/mrc_themes.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ const create_themes = function (): Blockly.Theme[] {
6666
cssClasses +=
6767
'.eventHandlerButtonStyle_' + theme.name + ' {\n' +
6868
' fill: ' + fill + ';\n' +
69+
'}\n' +
70+
'.classMethodDefButtonStyle_' + theme.name + ' {\n' +
71+
' fill: ' + fill + ';\n' +
6972
'}\n';
7073
});
7174
const styleElement = document.createElement('style');

src/toolbox/event_category.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,8 @@ class EventsCategory {
6363

6464
// Add a block that lets the user define a new event.
6565
contents.push(
66-
{
67-
kind: 'label',
68-
text: Blockly.Msg['CUSTOM_EVENTS_LABEL'],
69-
},
70-
createCustomEventBlock(storageNames.makeUniqueName('my_event', eventNames))
71-
);
66+
new toolboxItems.Label(Blockly.Msg['CUSTOM_EVENTS_LABEL']),
67+
createCustomEventBlock(storageNames.makeUniqueName('my_event', eventNames)));
7268

7369
// Get blocks for firing events defined in the current workspace.
7470
addFireEventBlocks(eventsFromWorkspace, contents);

src/toolbox/methods_category.ts

Lines changed: 33 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,18 @@ import * as Blockly from 'blockly/core';
2424
import * as toolboxItems from './items';
2525
import * as storageModule from '../storage/module';
2626
import { MRC_CATEGORY_STYLE_METHODS } from '../themes/styles';
27-
import { CLASS_NAME_ROBOT_BASE, CLASS_NAME_OPMODE, CLASS_NAME_MECHANISM } from '../blocks/utils/python';
27+
import {
28+
CLASS_NAME_MECHANISM,
29+
CLASS_NAME_OPMODE,
30+
CLASS_NAME_ROBOT_BASE,
31+
MECHANISM_METHOD_NAMES_NOT_OVERRIDEABLE,
32+
OPMODE_METHOD_NAMES_NOT_OVERRIDEABLE,
33+
ROBOT_METHOD_NAMES_NOT_OVERRIDEABLE } from '../blocks/utils/python';
2834
import { addInstanceWithinBlocks } from '../blocks/mrc_call_python_function';
29-
import { createCustomMethodBlock, getBaseClassBlocks, FIELD_METHOD_NAME, createCustomMethodBlockWithReturn } from '../blocks/mrc_class_method_def';
35+
import {
36+
createCustomMethodBlock,
37+
createCustomMethodBlockWithReturn,
38+
getBaseClassBlocks } from '../blocks/mrc_class_method_def';
3039
import { createStepsBlock } from '../blocks/mrc_steps';
3140
import { Editor } from '../editor/editor';
3241

@@ -50,10 +59,6 @@ export function getCategory(editor: Editor): toolboxItems.Category {
5059
}
5160

5261
class MethodsCategory {
53-
private robotClassBlocks = getBaseClassBlocks(CLASS_NAME_ROBOT_BASE);
54-
private mechanismClassBlocks = getBaseClassBlocks(CLASS_NAME_MECHANISM);
55-
private opmodeClassBlocks = getBaseClassBlocks(CLASS_NAME_OPMODE);
56-
5762
public methodsFlyout(workspace: Blockly.WorkspaceSvg) {
5863
const editor = Editor.getEditorForBlocklyWorkspace(workspace);
5964
if (!editor) {
@@ -71,55 +76,34 @@ class MethodsCategory {
7176

7277
switch (editor.getModuleType()) {
7378
case storageModule.ModuleType.ROBOT:
74-
// TODO(lizlooney): We need a way to mark a method in python as not overridable.
75-
// For example, in RobotBase, define_hardware, register_event_handler,
76-
// unregister_event_handler, and fire_event should not be overridden in a user's robot.
77-
const robotMethodNamesNotOverrideable: string[] = [
78-
'define_hardware',
79-
'fire_event',
80-
'register_event_handler',
81-
'unregister_event_handler',
82-
];
8379
// Add the methods for a Robot.
84-
this.addClassBlocksForCurrentModule(
85-
Blockly.Msg['MORE_ROBOT_METHODS_LABEL'], this.robotClassBlocks, robotMethodNamesNotOverrideable,
86-
methodNamesAlreadyOverridden, contents);
80+
this.addBaseClassBlocksForCurrentModule(
81+
workspace, Blockly.Msg['MORE_ROBOT_METHODS_LABEL'], CLASS_NAME_ROBOT_BASE,
82+
ROBOT_METHOD_NAMES_NOT_OVERRIDEABLE, methodNamesAlreadyOverridden, contents);
8783
break;
8884
case storageModule.ModuleType.MECHANISM:
89-
// TODO(lizlooney): We need a way to mark a method in python as not overridable.
90-
// For example, in Mechanism, register_event_handler, unregister_event_handler, and
91-
// fire_event should not be overridden in a user's mechamism.
92-
const mechanismMethodNamesNotOverrideable: string[] = [
93-
'fire_event',
94-
'register_event_handler',
95-
'unregister_event_handler',
96-
];
9785
// Add the methods for a Mechanism.
98-
this.addClassBlocksForCurrentModule(
99-
Blockly.Msg['MORE_MECHANISM_METHODS_LABEL'], this.mechanismClassBlocks, mechanismMethodNamesNotOverrideable,
100-
methodNamesAlreadyOverridden, contents);
86+
this.addBaseClassBlocksForCurrentModule(
87+
workspace, Blockly.Msg['MORE_MECHANISM_METHODS_LABEL'], CLASS_NAME_MECHANISM,
88+
MECHANISM_METHOD_NAMES_NOT_OVERRIDEABLE, methodNamesAlreadyOverridden, contents);
10189
break;
10290
case storageModule.ModuleType.OPMODE:
10391
const hasSteps = editor.isStepsInWorkspace();
10492
if (!hasSteps) {
10593
contents.push(createStepsBlock());
10694
}
10795
// Add the methods for an OpMode.
108-
this.addClassBlocksForCurrentModule(
109-
Blockly.Msg['MORE_OPMODE_METHODS_LABEL'], this.opmodeClassBlocks, [],
110-
methodNamesAlreadyOverridden, contents);
96+
this.addBaseClassBlocksForCurrentModule(
97+
workspace, Blockly.Msg['MORE_OPMODE_METHODS_LABEL'], CLASS_NAME_OPMODE,
98+
OPMODE_METHOD_NAMES_NOT_OVERRIDEABLE, methodNamesAlreadyOverridden, contents);
11199
break;
112100
}
113101

114102
// Add a block that lets the user define a new method.
115103
contents.push(
116-
{
117-
kind: 'label',
118-
text: Blockly.Msg['CUSTOM_METHODS_LABEL'],
119-
},
120-
createCustomMethodBlock(),
121-
createCustomMethodBlockWithReturn()
122-
);
104+
new toolboxItems.Label(Blockly.Msg['CUSTOM_METHODS_LABEL']),
105+
createCustomMethodBlock(),
106+
createCustomMethodBlockWithReturn());
123107

124108
// Get blocks for calling methods defined in the current workspace.
125109
const methodsFromWorkspace = editor.getMethodsForWithinFromWorkspace();
@@ -132,31 +116,16 @@ class MethodsCategory {
132116
return toolboxInfo;
133117
}
134118

135-
private addClassBlocksForCurrentModule(
136-
label: string, classBlocks: toolboxItems.Block[],
137-
methodNamesNotOverrideable: string[],
138-
methodNamesAlreadyOverridden: string[], contents: toolboxItems.ContentsType[]) {
139-
let labelAdded = false;
140-
for (const blockInfo of classBlocks) {
141-
if (blockInfo.fields) {
142-
const methodName = blockInfo.fields[FIELD_METHOD_NAME];
143-
if (methodNamesNotOverrideable.includes(methodName)) {
144-
continue;
145-
}
146-
if (methodNamesAlreadyOverridden.includes(methodName)) {
147-
continue;
148-
}
149-
if (!labelAdded) {
150-
contents.push(
151-
{
152-
kind: 'label',
153-
text: label,
154-
},
155-
);
156-
labelAdded = true;
157-
}
158-
contents.push(blockInfo);
159-
}
119+
private addBaseClassBlocksForCurrentModule(
120+
workspace: Blockly.WorkspaceSvg, label: string, baseClassName: string,
121+
methodNamesNotOverrideable: string[], methodNamesAlreadyOverridden: string[],
122+
contents: toolboxItems.ContentsType[]) {
123+
const baseClassBlocks: toolboxItems.ContentsType[] = getBaseClassBlocks(
124+
workspace, baseClassName, methodNamesNotOverrideable, methodNamesAlreadyOverridden);
125+
if (baseClassBlocks.length) {
126+
contents.push(
127+
new toolboxItems.Label(label),
128+
...baseClassBlocks);
160129
}
161130
}
162131
}

0 commit comments

Comments
 (0)