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