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
93 changes: 88 additions & 5 deletions src/blocks/mrc_jump_to_step.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import * as Blockly from 'blockly';

import { Editor } from '../editor/editor';
import { ExtendedPythonGenerator } from '../editor/extended_python_generator';
import { createFieldNonEditableText } from '../fields/FieldNonEditableText';
import { MRC_STYLE_VARIABLES } from '../themes/styles';
import { BLOCK_NAME as MRC_STEPS, StepsBlock } from './mrc_steps'

Expand All @@ -47,19 +46,80 @@ const JUMP_TO_STEP_BLOCK = {
* Block initialization.
*/
init: function (this: JumpToStepBlock): void {
this.mrcHasWarning = false;

this.setStyle(MRC_STYLE_VARIABLES);

// Create a custom dropdown that accepts any value and displays it correctly
class CustomStepDropdown extends Blockly.FieldDropdown {
override doClassValidation_(newValue?: string): string | null {
// Always accept the value, even if it's not in the current options
return newValue ?? null;
}

override getText_(): string {
// Always return the current value, even if not in options
return this.value_ || '';
}
}

const blockRef = this;
// Use a function to dynamically generate options when dropdown opens
const dropdown: Blockly.Field = new CustomStepDropdown(
function() {
// This function will be called to regenerate options when dropdown opens
return blockRef.getStepOptions();
}
);

dropdown.setValidator(this.validateStepSelection.bind(this));

this.appendDummyInput()
.appendField(Blockly.Msg.JUMP_TO)
.appendField(createFieldNonEditableText(''), FIELD_STEP_NAME);
.appendField(dropdown, FIELD_STEP_NAME);
this.setPreviousStatement(true, null);
this.setInputsInline(true);
this.setStyle(MRC_STYLE_VARIABLES);
this.setTooltip(() => {
const stepName = this.getFieldValue(FIELD_STEP_NAME);
let tooltip = Blockly.Msg.JUMP_TO_STEP_TOOLTIP;
tooltip = tooltip.replace('{{stepName}}', stepName);
return tooltip;
});
},
getStepOptions: function(this: JumpToStepBlock): [string, string][] {
const legalStepNames: string[] = [];

const rootBlock: Blockly.Block | null = this.getRootBlock();
if (rootBlock && rootBlock.type === MRC_STEPS) {
const stepsBlock = rootBlock as StepsBlock;
legalStepNames.push(...stepsBlock.mrcGetStepNames());
}

// Get the field to check its value
const field = this.getField(FIELD_STEP_NAME) as Blockly.FieldDropdown | null;
const currentValue = field?.getValue();

// Always include the current field value if it exists and isn't already in the list
if (currentValue && currentValue !== '' && !legalStepNames.includes(currentValue)) {
legalStepNames.unshift(currentValue);
}

if (legalStepNames.length === 0) {
return [[Blockly.Msg.NO_STEPS, '']];
}

return legalStepNames.map(name => [name, name]);
},
validateStepSelection: function(this: JumpToStepBlock, newValue: string): string {
// Clear any previous warnings
this.setWarningText(null, WARNING_ID_NOT_IN_STEP);
this.mrcHasWarning = false;

// Options will be regenerated automatically on next dropdown open
// via the function passed to the CustomStepDropdown constructor

return newValue;
},
/**
* mrcOnMove is called when a JumpToStepBlock is moved.
*/
Expand All @@ -80,14 +140,31 @@ const JUMP_TO_STEP_BLOCK = {
legalStepNames.push(...stepsBlock.mrcGetStepNames());
}

if (legalStepNames.includes(this.getFieldValue(FIELD_STEP_NAME))) {
const currentStepName = this.getFieldValue(FIELD_STEP_NAME);

if (legalStepNames.includes(currentStepName)) {
// If this blocks's step name is in legalStepNames, it's good.
this.setWarningText(null, WARNING_ID_NOT_IN_STEP);
this.mrcHasWarning = false;
} else {
// Otherwise, add a warning to this block.
if (!this.mrcHasWarning) {
this.setWarningText(Blockly.Msg.JUMP_CAN_ONLY_GO_IN_THEIR_STEPS_BLOCK, WARNING_ID_NOT_IN_STEP);
// Provide a more specific message depending on the situation
let warningMessage: string;
if (rootBlock.type === MRC_STEPS) {
// We're in a steps block but the step doesn't exist
if (currentStepName && currentStepName !== '') {
const messageTemplate = Blockly.Msg.STEP_DOES_NOT_EXIST_IN_STEPS;
warningMessage = messageTemplate.replace('%1', currentStepName);
} else {
warningMessage = Blockly.Msg.NO_STEP_SELECTED;
}
} else {
// We're not even in a steps block
warningMessage = Blockly.Msg.JUMP_CAN_ONLY_GO_IN_THEIR_STEPS_BLOCK;
}

this.setWarningText(warningMessage, WARNING_ID_NOT_IN_STEP);
const icon = this.getIcon(Blockly.icons.IconType.WARNING);
if (icon) {
icon.setBubbleVisible(true);
Expand All @@ -96,6 +173,12 @@ const JUMP_TO_STEP_BLOCK = {
}
}
},
/**
* Called to recheck step validity. Used when steps are changed.
*/
mrcCheckStep: function(this: JumpToStepBlock): void {
this.checkBlockPlacement();
},
};

export const setup = function () {
Expand Down
25 changes: 24 additions & 1 deletion src/blocks/mrc_steps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ import { MRC_STYLE_STEPS } from '../themes/styles';
import { ExtendedPythonGenerator } from '../editor/extended_python_generator';
import { createStepFieldFlydown } from '../fields/field_flydown';
import { NONCOPYABLE_BLOCK } from './noncopyable_block';
import { renameSteps as updateJumpToStepBlocks } from './mrc_jump_to_step';
import { renameSteps as updateJumpToStepBlocks, BLOCK_NAME as MRC_JUMP_TO_STEP } from './mrc_jump_to_step';
import * as stepContainer from './mrc_step_container'
import { findConnectedBlocksOfType } from './utils/find_connected_blocks';
import { createBooleanShadowValue } from './utils/value';
import * as toolboxItems from '../toolbox/items';

Expand Down Expand Up @@ -152,6 +153,28 @@ const STEPS = {
// Update jump blocks for any renamed steps.
updateJumpToStepBlocks(this.workspace, mapOldStepNameToNewStepName);
}

// Update all mrc_jump_to_step blocks to recheck validity
this.mrcCheckJumpBlocks();
},
/**
* Checks all mrc_jump_to_step blocks within this steps block to revalidate
* that their step names are still valid.
*/
mrcCheckJumpBlocks: function(this: StepsBlock): void {
// Check each statement input for jump blocks
for (let i = 0; i < this.mrcStepNames.length; i++) {
const statementInput = this.getInput(INPUT_STATEMENT_PREFIX + i);
const nextBlock = statementInput?.connection?.targetBlock();
if (nextBlock) {
const jumpBlocks = findConnectedBlocksOfType(nextBlock, MRC_JUMP_TO_STEP);
jumpBlocks.forEach((block) => {
if ('mrcCheckStep' in block && typeof block.mrcCheckStep === 'function') {
block.mrcCheckStep();
}
});
}
}
},
/**
* mrcOnMutatorOpen is called when the mutator on an StepsBlock is opened.
Expand Down
3 changes: 3 additions & 0 deletions src/blocks/tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,9 @@ export function customTokens(t: (key: string) => string): typeof Blockly.Msg {
JUMP_TO: t('BLOCKLY.JUMP_TO'),
JUMP_TO_STEP_TOOLTIP: t('BLOCKLY.TOOLTIP.JUMP_TO_STEP'),
JUMP_CAN_ONLY_GO_IN_THEIR_STEPS_BLOCK: t('BLOCKLY.JUMP_CAN_ONLY_GO_IN_THEIR_STEPS_BLOCK'),
STEP_DOES_NOT_EXIST_IN_STEPS: t('BLOCKLY.STEP_DOES_NOT_EXIST_IN_STEPS'),
NO_STEP_SELECTED: t('BLOCKLY.NO_STEP_SELECTED'),
NO_STEPS: t('BLOCKLY.NO_STEPS'),
}
};

Expand Down
3 changes: 3 additions & 0 deletions src/i18n/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,9 @@
"PARAMETERS": "Parameters",
"PARAMETERS_CAN_ONLY_GO_IN_THEIR_METHODS_BLOCK": "Parameters can only go in their method's block",
"JUMP_CAN_ONLY_GO_IN_THEIR_STEPS_BLOCK": "Jump can only go in their step's block",
"STEP_DOES_NOT_EXIST_IN_STEPS": "Step \"%1\" does not exist in this steps block.",
"NO_STEP_SELECTED": "No step selected.",
"NO_STEPS": "(no steps)",
"CLASS_METHOD_DEF_ALREADY_ON_WORKSPACE": "This method is already on the workspace.",
"EVENT_HANDLER_ALREADY_ON_WORKSPACE": "This event handler is already on the workspace.",
"EVENT_HANDLER_ROBOT_EVENT_NOT_FOUND": "This block is an event handler for an event that no longer exists.",
Expand Down
3 changes: 3 additions & 0 deletions src/i18n/locales/es/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@
"PARAMETERS": "Parámetros",
"PARAMETERS_CAN_ONLY_GO_IN_THEIR_METHODS_BLOCK": "Los parámetros solo pueden ir en el bloque de su método",
"JUMP_CAN_ONLY_GO_IN_THEIR_STEPS_BLOCK": "El salto solo puede ir en el bloque de su paso",
"STEP_DOES_NOT_EXIST_IN_STEPS": "El paso \"%1\" no existe en este bloque de pasos.",
"NO_STEP_SELECTED": "No se ha seleccionado ningún paso.",
"NO_STEPS": "(sin pasos)",
"CLASS_METHOD_DEF_ALREADY_ON_WORKSPACE": "Este método ya está en el espacio de trabajo.",
"EVENT_HANDLER_ALREADY_ON_WORKSPACE": "Este controlador de eventos ya está en el espacio de trabajo.",
"EVENT_HANDLER_ROBOT_EVENT_NOT_FOUND": "Este bloque es un controlador de eventos para un evento que ya no existe.",
Expand Down
3 changes: 3 additions & 0 deletions src/i18n/locales/he/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,9 @@
"PARAMETERS": "פרמטרים",
"PARAMETERS_CAN_ONLY_GO_IN_THEIR_METHODS_BLOCK": "פרמטרים יכולים ללכת רק בבלוק השיטה שלהם",
"JUMP_CAN_ONLY_GO_IN_THEIR_STEPS_BLOCK": "קפיצה יכולה ללכת רק בבלוק הצעד שלה",
"STEP_DOES_NOT_EXIST_IN_STEPS": "הצעד \"%1\" לא קיים בבלוק הצעדים הזה.",
"NO_STEP_SELECTED": "לא נבחר צעד.",
"NO_STEPS": "(אין צעדים)",
"CLASS_METHOD_DEF_ALREADY_ON_WORKSPACE": "שיטה זו כבר קיימת בסביבת העבודה.",
"EVENT_HANDLER_ALREADY_ON_WORKSPACE": "מטפל אירועים זה כבר נמצא במרחב העבודה.",
"EVENT_HANDLER_ROBOT_EVENT_NOT_FOUND": "הבלוק הזה הוא מנהל אירועים לאירוע שכבר לא קיים.",
Expand Down