Skip to content

Commit

Permalink
fix: make getSourceBlock nullable again (google#6542)
Browse files Browse the repository at this point in the history
* fix: make getSourceBlock nullable again

* chore: format

* chore: move to a specific error

* chore: also update procedures with new error

* chore: format
  • Loading branch information
BeksOmega authored and cpcallen committed Oct 19, 2022
1 parent 5cdcd02 commit 5f42361
Show file tree
Hide file tree
Showing 9 changed files with 161 additions and 55 deletions.
42 changes: 33 additions & 9 deletions core/field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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_;
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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++) {
Expand Down Expand Up @@ -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();
}
Expand All @@ -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.');
}
}
10 changes: 7 additions & 3 deletions core/field_angle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -430,18 +430,22 @@ export class FieldAngle extends FieldTextInput {
*/
protected override onHtmlInputKeyDown_(e: Event) {
super.onHtmlInputKeyDown_(e);
const block = this.getSourceBlock();
if (!block) {
throw new UnattachedFieldError();
}

let multiplier;
// AnyDuringMigration because: Property 'keyCode' does not exist on type
// 'Event'.
if ((e as AnyDuringMigration).keyCode === KeyCodes.LEFT) {
// decrement (increment in RTL)
multiplier = this.getSourceBlock().RTL ? 1 : -1;
multiplier = block.RTL ? 1 : -1;
// AnyDuringMigration because: Property 'keyCode' does not exist on type
// 'Event'.
} else if ((e as AnyDuringMigration).keyCode === KeyCodes.RIGHT) {
// increment (decrement in RTL)
multiplier = this.getSourceBlock().RTL ? -1 : 1;
multiplier = block.RTL ? -1 : 1;
// AnyDuringMigration because: Property 'keyCode' does not exist on type
// 'Event'.
} else if ((e as AnyDuringMigration).keyCode === KeyCodes.DOWN) {
Expand Down
41 changes: 28 additions & 13 deletions core/field_dropdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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'.
Expand All @@ -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;
if (!borderColour) {
throw new Error(
Expand All @@ -308,6 +311,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;
Expand All @@ -326,7 +333,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_);
Expand Down Expand Up @@ -552,6 +559,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);
Expand Down Expand Up @@ -590,7 +601,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 {
Expand Down Expand Up @@ -646,12 +657,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;
Expand Down
14 changes: 11 additions & 3 deletions core/field_multilineinput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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.
Expand All @@ -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';
}
Expand All @@ -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) {
Expand Down Expand Up @@ -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.
Expand Down
34 changes: 24 additions & 10 deletions core/field_textinput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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++;
}
Expand All @@ -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;
}
Expand Down Expand Up @@ -311,8 +315,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;

Expand All @@ -330,6 +337,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();

Expand All @@ -355,8 +366,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;
Expand Down Expand Up @@ -514,15 +525,18 @@ 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';
div!.style.height = bBox.bottom - bBox.top + 'px';

// 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';
Expand Down
Loading

0 comments on commit 5f42361

Please sign in to comment.