Skip to content

Commit 8807560

Browse files
authored
Pr parameter fixes (#363)
* Make get parameter a dropdown * Add warning when parameter is deleted * Change to use i18n instead of hardcoded strings * Remove check from loadExtraState * Pulling checkParameterBlocks out * Added a comment to make it clearer why the custom dropdown is needed * Fixes #369 This adds the custom piece to FieldDropdown so it is shared between jump to step and get parameter blocks * Move (no parameters) to i18n * Fixed redundant check
1 parent ef11e08 commit 8807560

9 files changed

Lines changed: 148 additions & 21 deletions

File tree

src/blocks/mrc_class_method_def.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import { FunctionData } from './utils/python_json_types';
3737
import { findConnectedBlocksOfType } from './utils/find_connected_blocks';
3838
import { makeLegalName } from './utils/validator';
3939
import { NONCOPYABLE_BLOCK } from './noncopyable_block';
40-
import { BLOCK_NAME as MRC_GET_PARAMETER_BLOCK_NAME } from './mrc_get_parameter';
40+
import { checkParameterBlocks, BLOCK_NAME as MRC_GET_PARAMETER_BLOCK_NAME } from './mrc_get_parameter';
4141
import * as paramContainer from './mrc_param_container'
4242

4343
export const BLOCK_NAME = 'mrc_class_method_def';
@@ -226,6 +226,9 @@ const CLASS_METHOD_DEF = {
226226
mutateMethodCallers(this.workspace, this.mrcMethodId, methodForWithin);
227227
}
228228
}
229+
230+
// Update all mrc_get_parameter blocks to recheck validity
231+
checkParameterBlocks(this.getInputTargetBlock(INPUT_STACK));
229232
},
230233
decompose: function (this: ClassMethodDefBlock, workspace: Blockly.Workspace) {
231234
const parameterNames: string[] = [];

src/blocks/mrc_event_handler.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import { MRC_STYLE_EVENT_HANDLER } from '../themes/styles';
3333
import * as toolboxItems from '../toolbox/items';
3434
import * as storageModule from '../storage/module';
3535
import * as storageModuleContent from '../storage/module_content';
36+
import { checkParameterBlocks } from './mrc_get_parameter';
3637

3738
export const BLOCK_NAME = 'mrc_event_handler';
3839

@@ -265,7 +266,9 @@ const EVENT_HANDLER = {
265266
type: arg.type,
266267
});
267268
});
268-
this.mrcUpdateParams();
269+
this.mrcUpdateParams();
270+
// Update all mrc_get_parameter blocks to recheck validity
271+
this.mrcCheckParameterBlocks();
269272

270273
// Since we found the mechanism event, we can break out of the loop.
271274
break;
@@ -328,6 +331,14 @@ const EVENT_HANDLER = {
328331
});
329332
return parameterNames;
330333
},
334+
335+
/**
336+
* Checks all mrc_get_parameter blocks within this event handler to revalidate
337+
* that their parameter names are still valid.
338+
*/
339+
mrcCheckParameterBlocks: function(this: EventHandlerBlock): void {
340+
checkParameterBlocks(this.getInputTargetBlock('DO'));
341+
},
331342
};
332343

333344
export function setup(): void {

src/blocks/mrc_get_parameter.ts

Lines changed: 91 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,11 @@ import {Order} from 'blockly/python';
2525

2626
import { Editor } from '../editor/editor';
2727
import {ExtendedPythonGenerator} from '../editor/extended_python_generator';
28-
import {createFieldNonEditableText} from '../fields/FieldNonEditableText';
2928
import {MRC_STYLE_VARIABLES} from '../themes/styles';
3029
import {BLOCK_NAME as MRC_CLASS_METHOD_DEF, ClassMethodDefBlock} from './mrc_class_method_def';
3130
import {BLOCK_NAME as MRC_EVENT_HANDLER, EventHandlerBlock } from './mrc_event_handler';
31+
import { findConnectedBlocksOfType } from './utils/find_connected_blocks';
32+
import { CustomDropdownWithoutValidation } from '../fields/FieldDropdown';
3233

3334

3435
export const BLOCK_NAME = 'mrc_get_parameter';
@@ -70,11 +71,60 @@ const GET_PARAMETER_BLOCK = {
7071
this.mrcHasWarning = false;
7172

7273
this.setStyle(MRC_STYLE_VARIABLES);
74+
75+
const blockRef = this;
76+
// Use a dummy initial option - it will be replaced when setValue is called
77+
const dropdown: Blockly.Field = new CustomDropdownWithoutValidation(
78+
function() {
79+
// This function will be called to regenerate options when dropdown opens
80+
return blockRef.getParameterOptions();
81+
}
82+
);
83+
84+
dropdown.setValidator(this.validateParameterSelection.bind(this));
85+
7386
this.appendDummyInput()
7487
.appendField(Blockly.Msg.PARAMETER)
75-
.appendField(createFieldNonEditableText(''), FIELD_PARAMETER_NAME);
88+
.appendField(dropdown, FIELD_PARAMETER_NAME);
7689
this.setOutput(true, this.mrcParameterType);
7790
},
91+
getParameterOptions: function(this: GetParameterBlock): [string, string][] {
92+
const existingParameterNames: string[] = [];
93+
94+
const rootBlock: Blockly.Block | null = this.getRootBlock();
95+
if (rootBlock && rootBlock.type === MRC_CLASS_METHOD_DEF) {
96+
const classMethodDefBlock = rootBlock as ClassMethodDefBlock;
97+
existingParameterNames.push(...classMethodDefBlock.mrcGetParameterNames());
98+
} else if (rootBlock && rootBlock.type === MRC_EVENT_HANDLER) {
99+
const eventHandlerBlock = rootBlock as EventHandlerBlock;
100+
existingParameterNames.push(...eventHandlerBlock.mrcGetParameterNames());
101+
}
102+
103+
// Get the field to check its value
104+
const field = this.getField(FIELD_PARAMETER_NAME) as Blockly.FieldDropdown | null;
105+
const currentValue = field?.getValue();
106+
107+
// Always include the current field value if it exists and isn't already in the list
108+
if (currentValue && !existingParameterNames.includes(currentValue)) {
109+
existingParameterNames.unshift(currentValue);
110+
}
111+
112+
if (existingParameterNames.length === 0) {
113+
return [[Blockly.Msg.NO_PARAMETERS, '']];
114+
}
115+
116+
return existingParameterNames.map(name => [name, name]);
117+
},
118+
validateParameterSelection: function(this: GetParameterBlock, newValue: string): string {
119+
// Clear any previous warnings
120+
this.setWarningText(null, WARNING_ID_NOT_IN_METHOD);
121+
this.mrcHasWarning = false;
122+
123+
// Options will be regenerated automatically on next dropdown open
124+
// via the function passed to the CustomParameterDropdown constructor
125+
126+
return newValue;
127+
},
78128
setNameAndType: function(this: GetParameterBlock, name: string, type: string): void {
79129
this.setFieldValue(name, FIELD_PARAMETER_NAME);
80130
this.mrcParameterType = type;
@@ -105,21 +155,58 @@ const GET_PARAMETER_BLOCK = {
105155
legalParameterNames.push(...eventHandlerBlock.mrcGetParameterNames());
106156
}
107157

108-
if (legalParameterNames.includes(this.getFieldValue(FIELD_PARAMETER_NAME))) {
158+
const currentParameterName = this.getFieldValue(FIELD_PARAMETER_NAME);
159+
160+
if (legalParameterNames.includes(currentParameterName)) {
109161
// If this blocks's parameter name is in legalParameterNames, it's good.
110162
this.setWarningText(null, WARNING_ID_NOT_IN_METHOD);
111163
this.mrcHasWarning = false;
112164
} else {
113165
// Otherwise, add a warning to this block.
114166
if (!this.mrcHasWarning) {
115-
this.setWarningText(Blockly.Msg.PARAMETERS_CAN_ONLY_GO_IN_THEIR_METHODS_BLOCK, WARNING_ID_NOT_IN_METHOD);
167+
// Provide a more specific message depending on the situation
168+
let warningMessage: string;
169+
if (rootBlock.type === MRC_CLASS_METHOD_DEF || rootBlock.type === MRC_EVENT_HANDLER) {
170+
// We're in a method/handler but the parameter doesn't exist
171+
if (currentParameterName && currentParameterName !== '') {
172+
const messageTemplate = rootBlock.type === MRC_CLASS_METHOD_DEF
173+
? Blockly.Msg.PARAMETER_DOES_NOT_EXIST_IN_METHOD
174+
: Blockly.Msg.PARAMETER_DOES_NOT_EXIST_IN_EVENT_HANDLER;
175+
warningMessage = messageTemplate.replace('%1', currentParameterName);
176+
} else {
177+
warningMessage = Blockly.Msg.NO_PARAMETER_SELECTED;
178+
}
179+
} else {
180+
// We're not even in a method/handler
181+
warningMessage = Blockly.Msg.PARAMETERS_CAN_ONLY_GO_IN_THEIR_METHODS_BLOCK;
182+
}
183+
184+
this.setWarningText(warningMessage, WARNING_ID_NOT_IN_METHOD);
116185
this.getIcon(Blockly.icons.IconType.WARNING)!.setBubbleVisible(true);
117186
this.mrcHasWarning = true;
118187
}
119188
}
120189
},
190+
/**
191+
* Called to recheck parameter validity. Used when method parameters change.
192+
*/
193+
mrcCheckParameter: function(this: GetParameterBlock): void {
194+
this.checkBlockPlacement();
195+
},
121196
};
122197

198+
/*
199+
* Rechecks all Get Parameter blocks connected to the target block.
200+
*/
201+
export function checkParameterBlocks(targetBlock: Blockly.Block | null): void {
202+
if (targetBlock) {
203+
findConnectedBlocksOfType(targetBlock, BLOCK_NAME).forEach((block) => {
204+
const getParameterBlock = block as GetParameterBlock;
205+
getParameterBlock.mrcCheckParameter();
206+
});
207+
}
208+
}
209+
123210
export const setup = function() {
124211
Blockly.Blocks[BLOCK_NAME] = GET_PARAMETER_BLOCK;
125212
};

src/blocks/mrc_jump_to_step.ts

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { Editor } from '../editor/editor';
2525
import { ExtendedPythonGenerator } from '../editor/extended_python_generator';
2626
import { MRC_STYLE_VARIABLES } from '../themes/styles';
2727
import { BLOCK_NAME as MRC_STEPS, StepsBlock } from './mrc_steps'
28+
import { CustomDropdownWithoutValidation } from '../fields/FieldDropdown';
2829

2930
export const BLOCK_NAME = 'mrc_jump_to_step';
3031

@@ -49,23 +50,10 @@ const JUMP_TO_STEP_BLOCK = {
4950
this.mrcHasWarning = false;
5051

5152
this.setStyle(MRC_STYLE_VARIABLES);
52-
53-
// Create a custom dropdown that accepts any value and displays it correctly
54-
class CustomStepDropdown extends Blockly.FieldDropdown {
55-
override doClassValidation_(newValue?: string): string | null {
56-
// Always accept the value, even if it's not in the current options
57-
return newValue ?? null;
58-
}
59-
60-
override getText_(): string {
61-
// Always return the current value, even if not in options
62-
return this.value_ || '';
63-
}
64-
}
65-
53+
6654
const blockRef = this;
6755
// Use a function to dynamically generate options when dropdown opens
68-
const dropdown: Blockly.Field = new CustomStepDropdown(
56+
const dropdown: Blockly.Field = new CustomDropdownWithoutValidation(
6957
function() {
7058
// This function will be called to regenerate options when dropdown opens
7159
return blockRef.getStepOptions();

src/blocks/tokens.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ 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+
PARAMETER_DOES_NOT_EXIST_IN_METHOD:
40+
t('BLOCKLY.PARAMETER_DOES_NOT_EXIST_IN_METHOD'),
41+
PARAMETER_DOES_NOT_EXIST_IN_EVENT_HANDLER:
42+
t('BLOCKLY.PARAMETER_DOES_NOT_EXIST_IN_EVENT_HANDLER'),
43+
NO_PARAMETER_SELECTED:
44+
t('BLOCKLY.NO_PARAMETER_SELECTED'),
3945
CLASS_METHOD_DEF_ALREADY_ON_WORKSPACE:
4046
t('BLOCKLY.CLASS_METHOD_DEF_ALREADY_ON_WORKSPACE'),
4147
EVENT_HANDLER_ALREADY_ON_WORKSPACE:

src/fields/FieldDropdown.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,24 @@ export function createFieldDropdown(items: string[]): Blockly.Field {
3131
options.push([item, item]);
3232
});
3333
return new Blockly.FieldDropdown(options);
34+
}
35+
36+
/*
37+
* Create a custom dropdown that accepts any value and displays it correctly
38+
* This is necessary because we need to be able to force a parameter or step into the dropdown
39+
* when we drag from it before it goes into a method or event that defines that parameter or a
40+
* step container that contains that step.
41+
*
42+
* WARNING: This class relies on Blockly internals that are not part of the public API.
43+
*/
44+
export class CustomDropdownWithoutValidation extends Blockly.FieldDropdown {
45+
override doClassValidation_(newValue?: string): string | null {
46+
// Always accept the value, even if it's not in the current options
47+
return newValue ?? null;
48+
}
49+
50+
override getText_(): string {
51+
// Always return the current value, even if not in options
52+
return this.value_ || '';
53+
}
3454
}

src/i18n/locales/en/translation.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,10 @@
131131
"PARAMETER": "parameter",
132132
"PARAMETERS": "Parameters",
133133
"PARAMETERS_CAN_ONLY_GO_IN_THEIR_METHODS_BLOCK": "Parameters can only go in their method's block",
134+
"PARAMETER_DOES_NOT_EXIST_IN_METHOD": "Parameter \"%1\" does not exist in this method.",
135+
"PARAMETER_DOES_NOT_EXIST_IN_EVENT_HANDLER": "Parameter \"%1\" does not exist in this event handler.",
136+
"NO_PARAMETER_SELECTED": "No parameter selected.",
137+
"NO_PARAMETERS": "(no parameters)",
134138
"JUMP_CAN_ONLY_GO_IN_THEIR_STEPS_BLOCK": "Jump can only go in their step's block",
135139
"STEP_DOES_NOT_EXIST_IN_STEPS": "Step \"%1\" does not exist in this steps block.",
136140
"NO_STEP_SELECTED": "No step selected.",

src/i18n/locales/es/translation.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,10 @@
132132
"PARAMETER": "parámetro",
133133
"PARAMETERS": "Parámetros",
134134
"PARAMETERS_CAN_ONLY_GO_IN_THEIR_METHODS_BLOCK": "Los parámetros solo pueden ir en el bloque de su método",
135+
"PARAMETER_DOES_NOT_EXIST_IN_METHOD": "El parámetro \"%1\" no existe en este método.",
136+
"PARAMETER_DOES_NOT_EXIST_IN_EVENT_HANDLER": "El parámetro \"%1\" no existe en este controlador de eventos.",
137+
"NO_PARAMETER_SELECTED": "No se ha seleccionado ningún parámetro.",
138+
"NO_PARAMETERS": "(sin parámetros)",
135139
"JUMP_CAN_ONLY_GO_IN_THEIR_STEPS_BLOCK": "El salto solo puede ir en el bloque de su paso",
136140
"STEP_DOES_NOT_EXIST_IN_STEPS": "El paso \"%1\" no existe en este bloque de pasos.",
137141
"NO_STEP_SELECTED": "No se ha seleccionado ningún paso.",

src/i18n/locales/he/translation.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,10 @@
131131
"PARAMETER": "פרמטר",
132132
"PARAMETERS": "פרמטרים",
133133
"PARAMETERS_CAN_ONLY_GO_IN_THEIR_METHODS_BLOCK": "פרמטרים יכולים ללכת רק בבלוק השיטה שלהם",
134+
"PARAMETER_DOES_NOT_EXIST_IN_METHOD": "הפרמטר \"%1\" לא קיים בשיטה זו.",
135+
"PARAMETER_DOES_NOT_EXIST_IN_EVENT_HANDLER": "הפרמטר \"%1\" לא קיים במטפל אירועים זה.",
136+
"NO_PARAMETER_SELECTED": "לא נבחר פרמטר.",
137+
"NO_PARAMETERS": "(אין פרמטרים)",
134138
"JUMP_CAN_ONLY_GO_IN_THEIR_STEPS_BLOCK": "קפיצה יכולה ללכת רק בבלוק הצעד שלה",
135139
"STEP_DOES_NOT_EXIST_IN_STEPS": "הצעד \"%1\" לא קיים בבלוק הצעדים הזה.",
136140
"NO_STEP_SELECTED": "לא נבחר צעד.",

0 commit comments

Comments
 (0)