Skip to content

Commit c6cbe4f

Browse files
authored
Show button instead of event handler block in toolbox if the event handler has already been used (#222)
* Added class Button to toolbox/items.ts. Added support for making a category initially expanded. Cleaned up other classes in toolbox/items.ts. Modified getHardwareCategory in toolbox/hardware_category.ts so the Hardware or Robot category are initially expanded. Modified getToolboxJSON in toolbox/toolbox.ts so it prevent a problem with blockly choking on our Category class. Modified code that puts event handler blocks in the toolbox so that if an event handler is already on the workspace, a button for that event is put in the toolbox. If the user hits the button, an info message tells the user that the event handler is already on the workspace. * Use Blockly.Msg.When instead of 'when'. If the blockly workspace is RTL, reverse the words in the button text. * Generate the css classes for event handler buttons with the correct color for each theme.
1 parent a64a6aa commit c6cbe4f

File tree

12 files changed

+176
-87
lines changed

12 files changed

+176
-87
lines changed

src/App.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import * as CustomBlocks from './blocks/setup_custom_blocks';
4646

4747
import { initialize as initializePythonBlocks } from './blocks/utils/python';
4848
import * as ChangeFramework from './blocks/utils/change_framework'
49+
import { registerToolboxButton } from './blocks/mrc_event_handler'
4950
import { mutatorOpenListener } from './blocks/mrc_param_container'
5051
import { TOOLBOX_UPDATE_EVENT } from './blocks/mrc_mechanism_component_holder';
5152
import { antdThemeFromString } from './reactComponents/ThemeModal';
@@ -432,6 +433,9 @@ const AppContent: React.FC<AppContentProps> = ({ project, setProject }): React.J
432433
ChangeFramework.setup(newWorkspace);
433434
newWorkspace.addChangeListener(mutatorOpenListener);
434435
newWorkspace.addChangeListener(handleBlocksChanged);
436+
437+
registerToolboxButton(newWorkspace, messageApi);
438+
435439
generatorContext.current = createGeneratorContext();
436440

437441
if (currentModule) {

src/blocks/mrc_event_handler.ts

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import * as Blockly from 'blockly';
2424
import {Order} from 'blockly/python';
2525

26+
import type { MessageInstance } from 'antd/es/message/interface';
2627
import { Editor } from '../editor/editor';
2728
import { ExtendedPythonGenerator } from '../editor/extended_python_generator';
2829
import { createFieldFlydown } from '../fields/field_flydown';
@@ -34,6 +35,9 @@ import * as storageModuleContent from '../storage/module_content';
3435

3536
export const BLOCK_NAME = 'mrc_event_handler';
3637

38+
const BUTTON_CALLBACK_KEY = 'EVENT_HANDLER_ALREADY_ON_WORKSPACE';
39+
const BUTTON_STYLE_PREFIX = 'eventHandlerButtonStyle_';
40+
3741
const FIELD_SENDER = 'SENDER';
3842
const FIELD_EVENT_NAME = 'EVENT_NAME';
3943

@@ -48,6 +52,7 @@ export interface Parameter {
4852
type?: string;
4953
}
5054

55+
const SENDER_VALUE_ROBOT = 'robot';
5156
const WARNING_ID_EVENT_CHANGED = 'event changed';
5257

5358
export type EventHandlerBlock = Blockly.Block & EventHandlerMixin & Blockly.BlockSvg;
@@ -404,10 +409,22 @@ function generateRegisterEventHandler(
404409
// Functions used for creating blocks for the toolbox.
405410

406411
export function addRobotEventHandlerBlocks(
412+
workspace: Blockly.WorkspaceSvg,
407413
events: storageModuleContent.Event[],
414+
eventHandlerBlocks: EventHandlerBlock[],
408415
contents: toolboxItems.ContentsType[]) {
416+
// Collect the ids of events for which there is already an event handler.
417+
const eventIds: string[] = [];
418+
eventHandlerBlocks.forEach(eventHandlerBlock => {
419+
eventIds.push(eventHandlerBlock.getEventId());
420+
});
409421
events.forEach(event => {
410-
contents.push(createRobotEventHandlerBlock(event));
422+
if (eventIds.includes(event.eventId)) {
423+
// If there is already an event handler for this event, put a button in the toolbox.
424+
contents.push(createButton(workspace, SENDER_VALUE_ROBOT, event.name));
425+
} else {
426+
contents.push(createRobotEventHandlerBlock(event));
427+
}
411428
});
412429
}
413430

@@ -425,21 +442,44 @@ function createRobotEventHandlerBlock(
425442
});
426443
});
427444
const fields: {[key: string]: any} = {};
428-
fields[FIELD_SENDER] = 'robot';
445+
fields[FIELD_SENDER] = SENDER_VALUE_ROBOT;
429446
fields[FIELD_EVENT_NAME] = event.name;
430447
const inputs: {[key: string]: any} = {};
431448
return new toolboxItems.Block(BLOCK_NAME, extraState, fields, Object.keys(inputs).length ? inputs : null);
432449
}
433450

434451
export function addMechanismEventHandlerBlocks(
452+
workspace: Blockly.WorkspaceSvg,
435453
mechanismInRobot: storageModuleContent.MechanismInRobot,
436454
events: storageModuleContent.Event[],
455+
eventHandlerBlocks: EventHandlerBlock[],
437456
contents: toolboxItems.ContentsType[]) {
457+
// Collect the ids of events for which there is already an event handler.
458+
const eventIds: string[] = [];
459+
eventHandlerBlocks.forEach(eventHandlerBlock => {
460+
eventIds.push(eventHandlerBlock.getEventId());
461+
});
438462
events.forEach(event => {
439-
contents.push(createMechanismEventHandlerBlock(mechanismInRobot, event));
463+
if (eventIds.includes(event.eventId)) {
464+
// If there is already an event handler for this event, put a button in the toolbox.
465+
contents.push(createButton(workspace, mechanismInRobot.name, event.name));
466+
} else {
467+
contents.push(createMechanismEventHandlerBlock(mechanismInRobot, event));
468+
}
440469
});
441470
}
442471

472+
function createButton(
473+
workspace: Blockly.WorkspaceSvg, senderName: string, eventName: string): toolboxItems.Button {
474+
// Use non-breakable spaces so it looks more like an event handler block.
475+
const spaces = '\u00A0\u00A0';
476+
const text = workspace.RTL
477+
? (spaces + eventName + spaces + senderName + spaces + Blockly.Msg.WHEN + spaces)
478+
: (spaces + Blockly.Msg.WHEN + spaces + senderName + spaces + eventName + spaces);
479+
const style = BUTTON_STYLE_PREFIX + workspace.getTheme().name;
480+
return new toolboxItems.Button(text, BUTTON_CALLBACK_KEY, style);
481+
}
482+
443483
function createMechanismEventHandlerBlock(
444484
mechanismInRobot: storageModuleContent.MechanismInRobot,
445485
event: storageModuleContent.Event): toolboxItems.Block {
@@ -502,3 +542,9 @@ export function renameMechanismName(workspace: Blockly.Workspace, mechanismId: s
502542
(block as EventHandlerBlock).renameMechanismName(mechanismId, newName);
503543
});
504544
}
545+
546+
export function registerToolboxButton(workspace: Blockly.WorkspaceSvg, messageApi: MessageInstance) {
547+
workspace.registerButtonCallback(BUTTON_CALLBACK_KEY, function (_button) {
548+
messageApi.info(Blockly.Msg.EVENT_HANDLER_ALREADY_ON_WORKSPACE);
549+
});
550+
}

src/blocks/tokens.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ export function customTokens(t: (key: string) => string): typeof Blockly.Msg {
3535
PARAMETER: t('BLOCKLY.PARAMETER'),
3636
PARAMETERS_CAN_ONLY_GO_IN_THEIR_METHODS_BLOCK:
3737
t('BLOCKLY.PARAMETERS_CAN_ONLY_GO_IN_THEIR_METHODS_BLOCK'),
38+
EVENT_HANDLER_ALREADY_ON_WORKSPACE:
39+
t('BLOCKLY.EVENT_HANDLER_ALREADY_ON_WORKSPACE'),
3840
MECHANISMS: t('MECHANISMS'),
3941
OPMODES: t('OPMODES'),
4042
COMPONENTS: t('BLOCKLY.COMPONENTS'),

src/editor/editor.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ export class Editor {
174174
private clearBlocklyWorkspace() {
175175
if (this.bindedOnChange) {
176176
this.blocklyWorkspace.removeChangeListener(this.bindedOnChange);
177+
this.bindedOnChange = null;
177178
}
178179
this.blocklyWorkspace.hideChaff();
179180
this.blocklyWorkspace.clear();

src/i18n/locales/en/translation.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
"WHEN": "when",
4646
"PARAMETER": "parameter",
4747
"PARAMETERS_CAN_ONLY_GO_IN_THEIR_METHODS_BLOCK": "Parameters can only go in their method's block",
48+
"EVENT_HANDLER_ALREADY_ON_WORKSPACE": "This event handler is already on the workspace.",
4849
"COMPONENTS": "Components",
4950
"PRIVATE_COMPONENTS": "Private Components",
5051
"EVENTS": "Events",

src/i18n/locales/es/translation.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
"WHEN": "cuando",
4747
"PARAMETER": "parámetro",
4848
"PARAMETERS_CAN_ONLY_GO_IN_THEIR_METHODS_BLOCK": "Los parámetros solo pueden ir en el bloque de su método",
49+
"EVENT_HANDLER_ALREADY_ON_WORKSPACE": "Este controlador de eventos ya está en el área de trabajo.",
4950
"COMPONENTS": "Componentes",
5051
"PRIVATE_COMPONENTS": "Componentes Privados",
5152
"EVENTS": "Eventos",

src/i18n/locales/he/translation.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
"WHEN": "כאשר",
4646
"PARAMETER": "פרמטר",
4747
"PARAMETERS_CAN_ONLY_GO_IN_THEIR_METHODS_BLOCK": "פרמטרים יכולים להיכנס רק בבלוק השיטה שלהם",
48+
"EVENT_HANDLER_ALREADY_ON_WORKSPACE": "מטפל אירועים זה כבר נמצא בסביבת העבודה.",
4849
"COMPONENTS": "רכיבים",
4950
"PRIVATE_COMPONENTS": "רכיבים פרטיים",
5051
"EVENTS": "אירועים",

src/themes/mrc_themes.ts

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import DeuteranopiaTheme from '@blockly/theme-deuteranopia';
44
import TritanopiaTheme from '@blockly/theme-tritanopia';
55
import HighContrastTheme from '@blockly/theme-highcontrast';
66

7-
import { add_mrc_styles } from './styles';
7+
import { add_mrc_styles, MRC_STYLE_EVENT_HANDLER } from './styles';
88

99
export const DARK_THEME_NAME = 'mrc_theme_dark';
1010
export const LIGHT_THEME_NAME = 'mrc_theme_light';
@@ -39,7 +39,7 @@ const create_theme = function (name: string, base: Blockly.Theme, dark: boolean
3939
};
4040

4141
const create_themes = function (): Blockly.Theme[] {
42-
return [
42+
const themes: Blockly.Theme[] = [
4343
create_theme(DARK_THEME_NAME, Blockly.Themes.Classic, true),
4444
create_theme(LIGHT_THEME_NAME, Blockly.Themes.Classic),
4545
create_theme(DEUTERANOPIA_THEME_NAME, DeuteranopiaTheme),
@@ -49,6 +49,30 @@ const create_themes = function (): Blockly.Theme[] {
4949
create_theme(TRITANOPIA_DARK_THEME_NAME, TritanopiaTheme, true),
5050
create_theme(HIGHCONTRAST_DARK_THEME_NAME, HighContrastTheme, true),
5151
];
52+
53+
// Create CSS classes for event handler buttons, which are placed in the toolbox when an event
54+
// handler already exists on the workspace.
55+
let cssClasses = '';
56+
themes.forEach(theme => {
57+
let fill = theme.blockStyles[MRC_STYLE_EVENT_HANDLER].colourPrimary;
58+
if (!fill.startsWith('#')) {
59+
try {
60+
fill = Blockly.utils.colour.hueToHex(Number(fill));
61+
} catch (e) {
62+
console.error(
63+
'Unable to determine event handler block color for theme ' + theme.name);
64+
}
65+
}
66+
cssClasses +=
67+
'.eventHandlerButtonStyle_' + theme.name + ' {\n' +
68+
' fill: ' + fill + ';\n' +
69+
'}\n';
70+
});
71+
const styleElement = document.createElement('style');
72+
styleElement.innerHTML = cssClasses;
73+
document.head.appendChild(styleElement);
74+
75+
return themes;
5276
};
5377

54-
export const themes = create_themes();
78+
export const themes = create_themes();

src/toolbox/event_handlers_category.ts

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -96,16 +96,8 @@ class EventHandlersCategory {
9696

9797
// Get the list of events from the robot.
9898
const eventsFromRobot = editor.getEventsFromRobot();
99-
// Remove events if there is already a corresponding handler in the workspace.
10099
const eventHandlerBlocks = editor.getRobotEventHandlersAlreadyInWorkspace();
101-
const eventIds: string[] = [];
102-
eventHandlerBlocks.forEach(eventHandlerBlock => {
103-
eventIds.push(eventHandlerBlock.getEventId());
104-
});
105-
const eventsToShow = eventsFromRobot.filter(event => {
106-
return !eventIds.includes(event.eventId);
107-
});
108-
addRobotEventHandlerBlocks(eventsToShow, contents);
100+
addRobotEventHandlerBlocks(workspace, eventsFromRobot, eventHandlerBlocks, contents);
109101

110102
const toolboxInfo = {
111103
contents: contents,
@@ -129,17 +121,10 @@ class EventHandlersCategory {
129121
const mechanism = editor.getMechanism(this.mechanismInRobot);
130122
if (mechanism) {
131123
const eventsFromMechanism = editor.getEventsFromMechanism(mechanism);
132-
// Remove events if there is already a corresponding handler in the workspace.
133124
const eventHandlerBlocks = editor.getMechanismEventHandlersAlreadyInWorkspace(
134125
this.mechanismInRobot);
135-
const eventIds: string[] = [];
136-
eventHandlerBlocks.forEach(eventHandlerBlock => {
137-
eventIds.push(eventHandlerBlock.getEventId());
138-
});
139-
const eventsToShow = eventsFromMechanism.filter(event => {
140-
return !eventIds.includes(event.eventId);
141-
});
142-
addMechanismEventHandlerBlocks(this.mechanismInRobot, eventsToShow, contents);
126+
addMechanismEventHandlerBlocks(
127+
workspace, this.mechanismInRobot, eventsFromMechanism, eventHandlerBlocks, contents);
143128
if (contents.length === 0) {
144129
const label : toolboxItems.Label = new toolboxItems.Label(Blockly.Msg['NO_MECHANISM_CONTENTS']);
145130
contents.push(label);

src/toolbox/hardware_category.ts

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -32,33 +32,29 @@ import {
3232
addInstanceMechanismBlocks } from '../blocks/mrc_call_python_function';
3333
import { Editor } from '../editor/editor';
3434

35-
export function getHardwareCategory(
36-
editor: Editor): toolboxItems.Category {
37-
35+
export function getHardwareCategory(editor: Editor): toolboxItems.Category {
3836
const moduleType = editor.getCurrentModuleType();
3937
switch (moduleType) {
4038
case storageModule.ModuleType.ROBOT:
41-
return {
42-
kind: 'category',
43-
name: Blockly.Msg['MRC_CATEGORY_HARDWARE'],
44-
contents: [
45-
getRobotMechanismsCategory(editor),
46-
getComponentsCategory(editor, moduleType),
47-
],
48-
};
39+
return new toolboxItems.Category(
40+
Blockly.Msg['MRC_CATEGORY_HARDWARE'],
41+
[
42+
getRobotMechanismsCategory(editor),
43+
getComponentsCategory(editor, moduleType),
44+
],
45+
toolboxItems.ExpandedState.EXPANDED);
4946
case storageModule.ModuleType.MECHANISM:
5047
return getComponentsCategory(editor, moduleType);
5148
case storageModule.ModuleType.OPMODE:
52-
return {
53-
kind: 'category',
54-
name: Blockly.Msg['MRC_CATEGORY_ROBOT'],
55-
contents: [
56-
getRobotMechanismsCategory(editor),
57-
getRobotComponentsCategory(editor),
58-
getRobotMethodsCategory(editor),
59-
getRobotEventHandlersCategory(editor),
60-
],
61-
};
49+
return new toolboxItems.Category(
50+
Blockly.Msg['MRC_CATEGORY_ROBOT'],
51+
[
52+
getRobotMechanismsCategory(editor),
53+
getRobotComponentsCategory(editor),
54+
getRobotMethodsCategory(editor),
55+
getRobotEventHandlersCategory(editor),
56+
],
57+
toolboxItems.ExpandedState.EXPANDED);
6258
}
6359
throw new Error('moduleType has unexpected value: ' + moduleType);
6460
}

0 commit comments

Comments
 (0)