mirror of
https://github.com/google/blockly.git
synced 2026-01-07 00:50:27 +01:00
fix: make getSourceBlock nullable again (#6542)
* fix: make getSourceBlock nullable again * chore: format * chore: move to a specific error * chore: also update procedures with new error * chore: format
This commit is contained in:
@@ -255,10 +255,7 @@ export abstract class Field implements IASTNodeLocationSvg,
|
||||
* @returns The block containing this field.
|
||||
* @throws An error if the source block is not defined.
|
||||
*/
|
||||
getSourceBlock(): Block {
|
||||
if (!this.sourceBlock_) {
|
||||
throw new Error(`The source block is ${this.sourceBlock_}.`);
|
||||
}
|
||||
getSourceBlock(): Block|null {
|
||||
return this.sourceBlock_;
|
||||
}
|
||||
|
||||
@@ -476,10 +473,11 @@ export abstract class Field implements IASTNodeLocationSvg,
|
||||
/** Add or remove the UI indicating if this field is editable or not. */
|
||||
updateEditable() {
|
||||
const group = this.fieldGroup_;
|
||||
if (!this.EDITABLE || !group) {
|
||||
const block = this.getSourceBlock();
|
||||
if (!this.EDITABLE || !group || !block) {
|
||||
return;
|
||||
}
|
||||
if (this.enabled_ && this.getSourceBlock().isEditable()) {
|
||||
if (this.enabled_ && block.isEditable()) {
|
||||
dom.addClass(group, 'blocklyEditableText');
|
||||
dom.removeClass(group, 'blocklyNonEditableText');
|
||||
group.style.cursor = this.CURSOR;
|
||||
@@ -756,7 +754,7 @@ export abstract class Field implements IASTNodeLocationSvg,
|
||||
this.textElement_.setAttribute(
|
||||
'x',
|
||||
`${
|
||||
this.getSourceBlock().RTL ?
|
||||
this.getSourceBlock()?.RTL ?
|
||||
this.size_.width - contentWidth - xOffset :
|
||||
xOffset}`);
|
||||
this.textElement_.setAttribute(
|
||||
@@ -819,12 +817,17 @@ export abstract class Field implements IASTNodeLocationSvg,
|
||||
let scaledWidth;
|
||||
let scaledHeight;
|
||||
let xy;
|
||||
const block = this.getSourceBlock();
|
||||
if (!block) {
|
||||
throw new UnattachedFieldError();
|
||||
}
|
||||
|
||||
if (!this.borderRect_) {
|
||||
// Browsers are inconsistent in what they return for a bounding box.
|
||||
// - Webkit / Blink: fill-box / object bounding box
|
||||
// - Gecko: stroke-box
|
||||
const bBox = (this.sourceBlock_ as BlockSvg).getHeightWidth();
|
||||
const scale = (this.getSourceBlock().workspace as WorkspaceSvg).scale;
|
||||
const scale = (block.workspace as WorkspaceSvg).scale;
|
||||
xy = this.getAbsoluteXY_();
|
||||
scaledWidth = (bBox.width + 1) * scale;
|
||||
scaledHeight = (bBox.height + 1) * scale;
|
||||
@@ -1158,6 +1161,9 @@ export abstract class Field implements IASTNodeLocationSvg,
|
||||
getParentInput(): Input {
|
||||
let parentInput = null;
|
||||
const block = this.getSourceBlock();
|
||||
if (!block) {
|
||||
throw new UnattachedFieldError();
|
||||
}
|
||||
const inputs = block.inputList;
|
||||
|
||||
for (let idx = 0; idx < block.inputList.length; idx++) {
|
||||
@@ -1241,7 +1247,11 @@ export abstract class Field implements IASTNodeLocationSvg,
|
||||
|
||||
/** Redraw any attached marker or cursor svgs if needed. */
|
||||
protected updateMarkers_() {
|
||||
const workspace = this.getSourceBlock().workspace as WorkspaceSvg;
|
||||
const block = this.getSourceBlock();
|
||||
if (!block) {
|
||||
throw new UnattachedFieldError();
|
||||
}
|
||||
const workspace = block.workspace as WorkspaceSvg;
|
||||
if (workspace.keyboardAccessibilityMode && this.cursorSvg_) {
|
||||
workspace.getCursor()!.draw();
|
||||
}
|
||||
@@ -1264,3 +1274,17 @@ export interface FieldConfig {
|
||||
* in descendants, though they should contain all of Field's prototype methods.
|
||||
*/
|
||||
export type FieldProto = Pick<typeof Field, 'prototype'>;
|
||||
|
||||
/**
|
||||
* Represents an error where the field is trying to access its block or
|
||||
* information about its block before it has actually been attached to said
|
||||
* block.
|
||||
*/
|
||||
export class UnattachedFieldError extends Error {
|
||||
/** @internal */
|
||||
constructor() {
|
||||
super(
|
||||
'The field has not yet been attached to its input. ' +
|
||||
'Call appendField to attach it.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import {BlockSvg} from './block_svg.js';
|
||||
import * as browserEvents from './browser_events.js';
|
||||
import * as Css from './css.js';
|
||||
import * as dropDownDiv from './dropdowndiv.js';
|
||||
import {Field} from './field.js';
|
||||
import {Field, UnattachedFieldError} from './field.js';
|
||||
import * as fieldRegistry from './field_registry.js';
|
||||
import {FieldTextInputConfig, FieldTextInput} from './field_textinput.js';
|
||||
import * as dom from './utils/dom.js';
|
||||
@@ -412,15 +412,19 @@ export class FieldAngle extends FieldTextInput {
|
||||
*/
|
||||
protected override onHtmlInputKeyDown_(e: Event) {
|
||||
super.onHtmlInputKeyDown_(e);
|
||||
const block = this.getSourceBlock();
|
||||
if (!block) {
|
||||
throw new UnattachedFieldError();
|
||||
}
|
||||
|
||||
const keyboardEvent = e as KeyboardEvent;
|
||||
let multiplier;
|
||||
if (keyboardEvent.keyCode === KeyCodes.LEFT) {
|
||||
// decrement (increment in RTL)
|
||||
multiplier = this.getSourceBlock().RTL ? 1 : -1;
|
||||
multiplier = block.RTL ? 1 : -1;
|
||||
} else if (keyboardEvent.keyCode === KeyCodes.RIGHT) {
|
||||
// increment (decrement in RTL)
|
||||
multiplier = this.getSourceBlock().RTL ? -1 : 1;
|
||||
multiplier = block.RTL ? -1 : 1;
|
||||
} else if (keyboardEvent.keyCode === KeyCodes.DOWN) {
|
||||
// decrement
|
||||
multiplier = -1;
|
||||
|
||||
@@ -16,7 +16,7 @@ goog.declareModuleId('Blockly.FieldDropdown');
|
||||
|
||||
import type {BlockSvg} from './block_svg.js';
|
||||
import * as dropDownDiv from './dropdowndiv.js';
|
||||
import {FieldConfig, Field} from './field.js';
|
||||
import {FieldConfig, Field, UnattachedFieldError} from './field.js';
|
||||
import * as fieldRegistry from './field_registry.js';
|
||||
import {Menu} from './menu.js';
|
||||
import {MenuItem} from './menuitem.js';
|
||||
@@ -217,16 +217,16 @@ export class FieldDropdown extends Field {
|
||||
protected shouldAddBorderRect_(): boolean {
|
||||
return !this.getConstants()!.FIELD_DROPDOWN_NO_BORDER_RECT_SHADOW ||
|
||||
this.getConstants()!.FIELD_DROPDOWN_NO_BORDER_RECT_SHADOW &&
|
||||
!this.getSourceBlock().isShadow();
|
||||
!this.getSourceBlock()?.isShadow();
|
||||
}
|
||||
|
||||
/** Create a tspan based arrow. */
|
||||
protected createTextArrow_() {
|
||||
this.arrow_ = dom.createSvgElement(Svg.TSPAN, {}, this.textElement_);
|
||||
this.arrow_!.appendChild(document.createTextNode(
|
||||
this.getSourceBlock().RTL ? FieldDropdown.ARROW_CHAR + ' ' :
|
||||
' ' + FieldDropdown.ARROW_CHAR));
|
||||
if (this.getSourceBlock().RTL) {
|
||||
this.getSourceBlock()?.RTL ? FieldDropdown.ARROW_CHAR + ' ' :
|
||||
' ' + FieldDropdown.ARROW_CHAR));
|
||||
if (this.getSourceBlock()?.RTL) {
|
||||
// AnyDuringMigration because: Argument of type 'SVGTSpanElement | null'
|
||||
// is not assignable to parameter of type 'Node'.
|
||||
this.getTextElement().insertBefore(
|
||||
@@ -258,6 +258,10 @@ export class FieldDropdown extends Field {
|
||||
* undefined if triggered programmatically.
|
||||
*/
|
||||
protected override showEditor_(opt_e?: Event) {
|
||||
const block = this.getSourceBlock();
|
||||
if (!block) {
|
||||
throw new UnattachedFieldError();
|
||||
}
|
||||
this.dropdownCreate_();
|
||||
// AnyDuringMigration because: Property 'clientX' does not exist on type
|
||||
// 'Event'.
|
||||
@@ -279,11 +283,10 @@ export class FieldDropdown extends Field {
|
||||
dom.addClass(menuElement, 'blocklyDropdownMenu');
|
||||
|
||||
if (this.getConstants()!.FIELD_DROPDOWN_COLOURED_DIV) {
|
||||
const primaryColour = this.getSourceBlock().isShadow() ?
|
||||
this.getSourceBlock().getParent()!.getColour() :
|
||||
this.getSourceBlock().getColour();
|
||||
const borderColour = this.getSourceBlock().isShadow() ?
|
||||
(this.getSourceBlock().getParent() as BlockSvg).style.colourTertiary :
|
||||
const primaryColour =
|
||||
block.isShadow() ? block.getParent()!.getColour() : block.getColour();
|
||||
const borderColour = block.isShadow() ?
|
||||
(block.getParent() as BlockSvg).style.colourTertiary :
|
||||
(this.sourceBlock_ as BlockSvg).style.colourTertiary;
|
||||
dropDownDiv.setColour(primaryColour, borderColour);
|
||||
}
|
||||
@@ -304,6 +307,10 @@ export class FieldDropdown extends Field {
|
||||
|
||||
/** Create the dropdown editor. */
|
||||
private dropdownCreate_() {
|
||||
const block = this.getSourceBlock();
|
||||
if (!block) {
|
||||
throw new UnattachedFieldError();
|
||||
}
|
||||
const menu = new Menu();
|
||||
menu.setRole(aria.Role.LISTBOX);
|
||||
this.menu_ = menu;
|
||||
@@ -322,7 +329,7 @@ export class FieldDropdown extends Field {
|
||||
}
|
||||
const menuItem = new MenuItem(content, value);
|
||||
menuItem.setRole(aria.Role.OPTION);
|
||||
menuItem.setRightToLeft(this.getSourceBlock().RTL);
|
||||
menuItem.setRightToLeft(block.RTL);
|
||||
menuItem.setCheckable(true);
|
||||
menu.addChild(menuItem);
|
||||
menuItem.setChecked(value === this.value_);
|
||||
@@ -540,6 +547,10 @@ export class FieldDropdown extends Field {
|
||||
* @param imageJson Selected option that must be an image.
|
||||
*/
|
||||
private renderSelectedImage_(imageJson: ImageProperties) {
|
||||
const block = this.getSourceBlock();
|
||||
if (!block) {
|
||||
throw new UnattachedFieldError();
|
||||
}
|
||||
this.imageElement_!.style.display = '';
|
||||
this.imageElement_!.setAttributeNS(
|
||||
dom.XLINK_NS, 'xlink:href', imageJson.src);
|
||||
@@ -578,7 +589,7 @@ export class FieldDropdown extends Field {
|
||||
this.size_.height = height;
|
||||
|
||||
let arrowX = 0;
|
||||
if (this.getSourceBlock().RTL) {
|
||||
if (block.RTL) {
|
||||
const imageX = xPadding + arrowWidth;
|
||||
this.imageElement_!.setAttribute('x', imageX.toString());
|
||||
} else {
|
||||
@@ -634,12 +645,16 @@ export class FieldDropdown extends Field {
|
||||
if (!this.svgArrow_) {
|
||||
return 0;
|
||||
}
|
||||
const block = this.getSourceBlock();
|
||||
if (!block) {
|
||||
throw new UnattachedFieldError();
|
||||
}
|
||||
const hasBorder = !!this.borderRect_;
|
||||
const xPadding =
|
||||
hasBorder ? this.getConstants()!.FIELD_BORDER_RECT_X_PADDING : 0;
|
||||
const textPadding = this.getConstants()!.FIELD_DROPDOWN_SVG_ARROW_PADDING;
|
||||
const svgArrowSize = this.getConstants()!.FIELD_DROPDOWN_SVG_ARROW_SIZE;
|
||||
const arrowX = this.getSourceBlock().RTL ? xPadding : x + textPadding;
|
||||
const arrowX = block.RTL ? xPadding : x + textPadding;
|
||||
this.svgArrow_.setAttribute(
|
||||
'transform', 'translate(' + arrowX + ',' + y + ')');
|
||||
return svgArrowSize + textPadding;
|
||||
|
||||
@@ -13,7 +13,7 @@ import * as goog from '../closure/goog/goog.js';
|
||||
goog.declareModuleId('Blockly.FieldMultilineInput');
|
||||
|
||||
import * as Css from './css.js';
|
||||
import {Field} from './field.js';
|
||||
import {Field, UnattachedFieldError} from './field.js';
|
||||
import * as fieldRegistry from './field_registry.js';
|
||||
import {FieldTextInputConfig, FieldTextInput} from './field_textinput.js';
|
||||
import * as aria from './utils/aria.js';
|
||||
@@ -163,6 +163,10 @@ export class FieldMultilineInput extends FieldTextInput {
|
||||
* @returns Currently displayed text.
|
||||
*/
|
||||
protected override getDisplayText_(): string {
|
||||
const block = this.getSourceBlock();
|
||||
if (!block) {
|
||||
throw new UnattachedFieldError();
|
||||
}
|
||||
let textLines = this.getText();
|
||||
if (!textLines) {
|
||||
// Prevent the field from disappearing if empty.
|
||||
@@ -189,7 +193,7 @@ export class FieldMultilineInput extends FieldTextInput {
|
||||
textLines += '\n';
|
||||
}
|
||||
}
|
||||
if (this.getSourceBlock().RTL) {
|
||||
if (block.RTL) {
|
||||
// The SVG is LTR, force value to be RTL.
|
||||
textLines += '\u200F';
|
||||
}
|
||||
@@ -212,6 +216,10 @@ export class FieldMultilineInput extends FieldTextInput {
|
||||
|
||||
/** Updates the text of the textElement. */
|
||||
protected override render_() {
|
||||
const block = this.getSourceBlock();
|
||||
if (!block) {
|
||||
throw new UnattachedFieldError();
|
||||
}
|
||||
// Remove all text group children.
|
||||
let currentChild;
|
||||
while (currentChild = this.textGroup_.firstChild) {
|
||||
@@ -248,7 +256,7 @@ export class FieldMultilineInput extends FieldTextInput {
|
||||
this.updateSize_();
|
||||
|
||||
if (this.isBeingEdited_) {
|
||||
if (this.getSourceBlock().RTL) {
|
||||
if (block.RTL) {
|
||||
// in RTL, we need to let the browser reflow before resizing
|
||||
// in order to get the correct bounding box of the borderRect
|
||||
// avoiding issue #2777.
|
||||
|
||||
@@ -21,7 +21,7 @@ import * as dialog from './dialog.js';
|
||||
import * as dom from './utils/dom.js';
|
||||
import * as dropDownDiv from './dropdowndiv.js';
|
||||
import * as eventUtils from './events/utils.js';
|
||||
import {FieldConfig, Field} from './field.js';
|
||||
import {FieldConfig, Field, UnattachedFieldError} from './field.js';
|
||||
import * as fieldRegistry from './field_registry.js';
|
||||
import {Msg} from './msg.js';
|
||||
import * as aria from './utils/aria.js';
|
||||
@@ -127,13 +127,17 @@ export class FieldTextInput extends Field {
|
||||
|
||||
/** @internal */
|
||||
override initView() {
|
||||
const block = this.getSourceBlock();
|
||||
if (!block) {
|
||||
throw new UnattachedFieldError();
|
||||
}
|
||||
if (this.getConstants()!.FULL_BLOCK_FIELDS) {
|
||||
// Step one: figure out if this is the only field on this block.
|
||||
// Rendering is quite different in that case.
|
||||
let nFields = 0;
|
||||
let nConnections = 0;
|
||||
// Count the number of fields, excluding text fields
|
||||
for (let i = 0, input; input = this.getSourceBlock().inputList[i]; i++) {
|
||||
for (let i = 0, input; input = block.inputList[i]; i++) {
|
||||
for (let j = 0; input.fieldRow[j]; j++) {
|
||||
nFields++;
|
||||
}
|
||||
@@ -143,8 +147,8 @@ export class FieldTextInput extends Field {
|
||||
}
|
||||
// The special case is when this is the only non-label field on the block
|
||||
// and it has an output but no inputs.
|
||||
this.fullBlockClickTarget_ = nFields <= 1 &&
|
||||
this.getSourceBlock().outputConnection && !nConnections;
|
||||
this.fullBlockClickTarget_ =
|
||||
nFields <= 1 && block.outputConnection && !nConnections;
|
||||
} else {
|
||||
this.fullBlockClickTarget_ = false;
|
||||
}
|
||||
@@ -307,8 +311,11 @@ export class FieldTextInput extends Field {
|
||||
* @param quietInput True if editor should be created without focus.
|
||||
*/
|
||||
private showInlineEditor_(quietInput: boolean) {
|
||||
WidgetDiv.show(
|
||||
this, this.getSourceBlock().RTL, this.widgetDispose_.bind(this));
|
||||
const block = this.getSourceBlock();
|
||||
if (!block) {
|
||||
throw new UnattachedFieldError();
|
||||
}
|
||||
WidgetDiv.show(this, block.RTL, this.widgetDispose_.bind(this));
|
||||
this.htmlInput_ = this.widgetCreate_() as HTMLInputElement;
|
||||
this.isBeingEdited_ = true;
|
||||
|
||||
@@ -326,6 +333,10 @@ export class FieldTextInput extends Field {
|
||||
* @returns The newly created text input editor.
|
||||
*/
|
||||
protected widgetCreate_(): HTMLElement {
|
||||
const block = this.getSourceBlock();
|
||||
if (!block) {
|
||||
throw new UnattachedFieldError();
|
||||
}
|
||||
eventUtils.setGroup(true);
|
||||
const div = WidgetDiv.getDiv();
|
||||
|
||||
@@ -351,8 +362,8 @@ export class FieldTextInput extends Field {
|
||||
// Override border radius.
|
||||
borderRadius = (bBox.bottom - bBox.top) / 2 + 'px';
|
||||
// Pull stroke colour from the existing shadow block
|
||||
const strokeColour = this.getSourceBlock().getParent() ?
|
||||
(this.getSourceBlock().getParent() as BlockSvg).style.colourTertiary :
|
||||
const strokeColour = block.getParent() ?
|
||||
(block.getParent() as BlockSvg).style.colourTertiary :
|
||||
(this.sourceBlock_ as BlockSvg).style.colourTertiary;
|
||||
htmlInput.style.border = 1 * scale + 'px solid ' + strokeColour;
|
||||
div!.style.borderRadius = borderRadius;
|
||||
@@ -510,6 +521,10 @@ export class FieldTextInput extends Field {
|
||||
|
||||
/** Resize the editor to fit the text. */
|
||||
protected resizeEditor_() {
|
||||
const block = this.getSourceBlock();
|
||||
if (!block) {
|
||||
throw new UnattachedFieldError();
|
||||
}
|
||||
const div = WidgetDiv.getDiv();
|
||||
const bBox = this.getScaledBBox();
|
||||
div!.style.width = bBox.right - bBox.left + 'px';
|
||||
@@ -517,8 +532,7 @@ export class FieldTextInput extends Field {
|
||||
|
||||
// In RTL mode block fields and LTR input fields the left edge moves,
|
||||
// whereas the right edge is fixed. Reposition the editor.
|
||||
const x =
|
||||
this.getSourceBlock().RTL ? bBox.right - div!.offsetWidth : bBox.left;
|
||||
const x = block.RTL ? bBox.right - div!.offsetWidth : bBox.left;
|
||||
const xy = new Coordinate(x, bBox.top);
|
||||
|
||||
div!.style.left = xy.x + 'px';
|
||||
|
||||
@@ -16,7 +16,7 @@ goog.declareModuleId('Blockly.FieldVariable');
|
||||
import './events/events_block_change.js';
|
||||
|
||||
import type {Block} from './block.js';
|
||||
import {Field, FieldConfig} from './field.js';
|
||||
import {Field, FieldConfig, UnattachedFieldError} from './field.js';
|
||||
import {FieldDropdown} from './field_dropdown.js';
|
||||
import * as fieldRegistry from './field_registry.js';
|
||||
import * as internalConstants from './internal_constants.js';
|
||||
@@ -135,20 +135,27 @@ export class FieldVariable extends FieldDropdown {
|
||||
* @internal
|
||||
*/
|
||||
override initModel() {
|
||||
const block = this.getSourceBlock();
|
||||
if (!block) {
|
||||
throw new UnattachedFieldError();
|
||||
}
|
||||
if (this.variable_) {
|
||||
return; // Initialization already happened.
|
||||
}
|
||||
const variable = Variables.getOrCreateVariablePackage(
|
||||
this.getSourceBlock().workspace, null, this.defaultVariableName,
|
||||
this.defaultType_);
|
||||
block.workspace, null, this.defaultVariableName, this.defaultType_);
|
||||
// Don't call setValue because we don't want to cause a rerender.
|
||||
this.doValueUpdate_(variable.getId());
|
||||
}
|
||||
|
||||
override shouldAddBorderRect_() {
|
||||
const block = this.getSourceBlock();
|
||||
if (!block) {
|
||||
throw new UnattachedFieldError();
|
||||
}
|
||||
return super.shouldAddBorderRect_() &&
|
||||
(!this.getConstants()!.FIELD_DROPDOWN_NO_BORDER_RECT_SHADOW ||
|
||||
this.getSourceBlock().type !== 'variables_get');
|
||||
block.type !== 'variables_get');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -158,6 +165,10 @@ export class FieldVariable extends FieldDropdown {
|
||||
* field's state.
|
||||
*/
|
||||
override fromXml(fieldElement: Element) {
|
||||
const block = this.getSourceBlock();
|
||||
if (!block) {
|
||||
throw new UnattachedFieldError();
|
||||
}
|
||||
const id = fieldElement.getAttribute('id');
|
||||
const variableName = fieldElement.textContent;
|
||||
// 'variabletype' should be lowercase, but until July 2019 it was sometimes
|
||||
@@ -168,8 +179,7 @@ export class FieldVariable extends FieldDropdown {
|
||||
// AnyDuringMigration because: Argument of type 'string | null' is not
|
||||
// assignable to parameter of type 'string | undefined'.
|
||||
const variable = Variables.getOrCreateVariablePackage(
|
||||
this.getSourceBlock().workspace, id, variableName as AnyDuringMigration,
|
||||
variableType);
|
||||
block.workspace, id, variableName as AnyDuringMigration, variableType);
|
||||
|
||||
// This should never happen :)
|
||||
if (variableType !== null && variableType !== variable.type) {
|
||||
@@ -233,12 +243,16 @@ export class FieldVariable extends FieldDropdown {
|
||||
* @internal
|
||||
*/
|
||||
override loadState(state: AnyDuringMigration) {
|
||||
const block = this.getSourceBlock();
|
||||
if (!block) {
|
||||
throw new UnattachedFieldError();
|
||||
}
|
||||
if (this.loadLegacyState(FieldVariable, state)) {
|
||||
return;
|
||||
}
|
||||
// This is necessary so that blocks in the flyout can have custom var names.
|
||||
const variable = Variables.getOrCreateVariablePackage(
|
||||
this.getSourceBlock().workspace, state['id'] || null, state['name'],
|
||||
block.workspace, state['id'] || null, state['name'],
|
||||
state['type'] || '');
|
||||
this.setValue(variable.getId());
|
||||
}
|
||||
@@ -315,9 +329,12 @@ export class FieldVariable extends FieldDropdown {
|
||||
if (opt_newValue === null) {
|
||||
return null;
|
||||
}
|
||||
const block = this.getSourceBlock();
|
||||
if (!block) {
|
||||
throw new UnattachedFieldError();
|
||||
}
|
||||
const newId = opt_newValue as string;
|
||||
const variable =
|
||||
Variables.getVariable(this.getSourceBlock().workspace, newId);
|
||||
const variable = Variables.getVariable(block.workspace, newId);
|
||||
if (!variable) {
|
||||
console.warn(
|
||||
'Variable id doesn\'t point to a real variable! ' +
|
||||
@@ -343,8 +360,11 @@ export class FieldVariable extends FieldDropdown {
|
||||
* @param newId The value to be saved.
|
||||
*/
|
||||
protected override doValueUpdate_(newId: AnyDuringMigration) {
|
||||
this.variable_ =
|
||||
Variables.getVariable(this.getSourceBlock().workspace, newId as string);
|
||||
const block = this.getSourceBlock();
|
||||
if (!block) {
|
||||
throw new UnattachedFieldError();
|
||||
}
|
||||
this.variable_ = Variables.getVariable(block.workspace, newId as string);
|
||||
super.doValueUpdate_(newId);
|
||||
}
|
||||
|
||||
|
||||
@@ -28,5 +28,5 @@ export interface IASTNodeLocationWithBlock extends IASTNodeLocation {
|
||||
*
|
||||
* @returns The source block.
|
||||
*/
|
||||
getSourceBlock(): Block;
|
||||
getSourceBlock(): Block|null;
|
||||
}
|
||||
|
||||
@@ -176,6 +176,10 @@ export class ASTNode {
|
||||
const location = this.location_ as Field;
|
||||
const input = location.getParentInput();
|
||||
const block = location.getSourceBlock();
|
||||
if (!block) {
|
||||
throw new Error(
|
||||
'The current AST location is not associated with a block');
|
||||
}
|
||||
const curIdx = block.inputList.indexOf((input));
|
||||
let fieldIdx = input.fieldRow.indexOf(location) + 1;
|
||||
for (let i = curIdx; i < block.inputList.length; i++) {
|
||||
@@ -235,6 +239,10 @@ export class ASTNode {
|
||||
const location = this.location_ as Field;
|
||||
const parentInput = location.getParentInput();
|
||||
const block = location.getSourceBlock();
|
||||
if (!block) {
|
||||
throw new Error(
|
||||
'The current AST location is not associated with a block');
|
||||
}
|
||||
const curIdx = block.inputList.indexOf((parentInput));
|
||||
let fieldIdx = parentInput.fieldRow.indexOf(location) - 1;
|
||||
for (let i = curIdx; i >= 0; i--) {
|
||||
@@ -270,7 +278,10 @@ export class ASTNode {
|
||||
// TODO(#6097): Use instanceof checks to exit early for values of
|
||||
// curLocation that don't make sense.
|
||||
if ((curLocation as IASTNodeLocationWithBlock).getSourceBlock) {
|
||||
curLocation = (curLocation as IASTNodeLocationWithBlock).getSourceBlock();
|
||||
const block = (curLocation as IASTNodeLocationWithBlock).getSourceBlock();
|
||||
if (block) {
|
||||
curLocation = block;
|
||||
}
|
||||
}
|
||||
// TODO(#6097): Use instanceof checks to exit early for values of
|
||||
// curLocation that don't make sense.
|
||||
@@ -531,7 +542,12 @@ export class ASTNode {
|
||||
}
|
||||
case ASTNode.types.FIELD: {
|
||||
const field = this.location_ as Field;
|
||||
return ASTNode.createBlockNode(field.getSourceBlock());
|
||||
const block = field.getSourceBlock();
|
||||
if (!block) {
|
||||
throw new Error(
|
||||
'The current AST location is not associated with a block');
|
||||
}
|
||||
return ASTNode.createBlockNode(block);
|
||||
}
|
||||
case ASTNode.types.INPUT: {
|
||||
const connection = this.location_ as Connection;
|
||||
|
||||
@@ -22,7 +22,7 @@ import * as common from './common.js';
|
||||
import type {Abstract} from './events/events_abstract.js';
|
||||
import type {BubbleOpen} from './events/events_bubble_open.js';
|
||||
import * as eventUtils from './events/utils.js';
|
||||
import type {Field} from './field.js';
|
||||
import {Field, UnattachedFieldError} from './field.js';
|
||||
import {Msg} from './msg.js';
|
||||
import {Names} from './names.js';
|
||||
import * as utilsXml from './utils/xml.js';
|
||||
@@ -180,14 +180,19 @@ export function isNameUsed(
|
||||
* @alias Blockly.Procedures.rename
|
||||
*/
|
||||
export function rename(this: Field, name: string): string {
|
||||
const block = this.getSourceBlock();
|
||||
if (!block) {
|
||||
throw new UnattachedFieldError();
|
||||
}
|
||||
|
||||
// Strip leading and trailing whitespace. Beyond this, all names are legal.
|
||||
name = name.trim();
|
||||
|
||||
const legalName = findLegalName(name, (this.getSourceBlock()));
|
||||
const legalName = findLegalName(name, block);
|
||||
const oldName = this.getValue();
|
||||
if (oldName !== name && oldName !== legalName) {
|
||||
// Rename any callers.
|
||||
const blocks = this.getSourceBlock().workspace.getAllBlocks(false);
|
||||
const blocks = block.workspace.getAllBlocks(false);
|
||||
for (let i = 0; i < blocks.length; i++) {
|
||||
// Assume it is a procedure so we can check.
|
||||
const procedureBlock = blocks[i] as unknown as ProcedureBlock;
|
||||
|
||||
Reference in New Issue
Block a user