From 3a9a9bd24e62e1b1262ea1dc329870bd21734c8a Mon Sep 17 00:00:00 2001 From: Beka Westberg Date: Thu, 4 May 2023 08:50:45 -0700 Subject: [PATCH] feat: break input types into separate classes (#7019) * chore: move input and input types into new directory * feat: define and export new input types * feat: modify blocks to construct individual inputs * chore: transition code to use actual type checks * chore: fixup input type type * chore: format * chore: fixup PR comments * chore: fix build --- blocks/lists.ts | 46 ++++++++------- blocks/loops.ts | 21 +++---- blocks/math.ts | 16 ++--- blocks/text.ts | 7 ++- core/block.ts | 58 ++++++------------- core/block_svg.ts | 15 ++--- core/blockly.ts | 14 ++++- core/connection.ts | 2 +- core/contextmenu_items.ts | 6 +- core/field.ts | 2 +- core/inputs/dummy_input.ts | 24 ++++++++ core/{ => inputs}/input.ts | 32 +++++----- core/{ => inputs}/input_types.ts | 4 +- core/inputs/statement_input.ts | 30 ++++++++++ core/inputs/value_input.ts | 30 ++++++++++ core/keyboard_nav/ast_node.ts | 2 +- core/registry.ts | 2 +- core/renderers/common/info.ts | 31 +++++----- core/renderers/geras/info.ts | 23 ++++---- .../geras/measurables/inline_input.ts | 2 +- .../geras/measurables/statement_input.ts | 2 +- .../measurables/external_value_input.ts | 2 +- core/renderers/measurables/field.ts | 2 +- core/renderers/measurables/inline_input.ts | 2 +- .../renderers/measurables/input_connection.ts | 2 +- core/renderers/measurables/statement_input.ts | 2 +- core/renderers/zelos/info.ts | 21 ++++--- core/renderers/zelos/measurables/inputs.ts | 2 +- core/serialization/blocks.ts | 6 +- core/xml.ts | 2 +- tests/mocha/block_json_test.js | 2 +- 31 files changed, 243 insertions(+), 169 deletions(-) create mode 100644 core/inputs/dummy_input.ts rename core/{ => inputs}/input.ts (90%) rename core/{ => inputs}/input_types.ts (84%) create mode 100644 core/inputs/statement_input.ts create mode 100644 core/inputs/value_input.ts diff --git a/blocks/lists.ts b/blocks/lists.ts index 2344b25fa..97da6cbde 100644 --- a/blocks/lists.ts +++ b/blocks/lists.ts @@ -14,7 +14,7 @@ goog.declareModuleId('Blockly.libraryBlocks.lists'); import * as fieldRegistry from '../core/field_registry.js'; import * as xmlUtils from '../core/utils/xml.js'; -import {Align} from '../core/input.js'; +import {Align} from '../core/inputs/input.js'; import type {Block} from '../core/block.js'; import type {Connection} from '../core/connection.js'; import type {BlockSvg} from '../core/block_svg.js'; @@ -26,6 +26,7 @@ import {Mutator} from '../core/mutator.js'; import type {Workspace} from '../core/workspace.js'; import {createBlockDefinitionsFromJsonArray, defineBlocks} from '../core/common.js'; import '../core/field_dropdown.js'; +import {ValueInput} from '../core/inputs/value_input.js'; /** @@ -185,23 +186,24 @@ const LISTS_CREATE_WITH = { * @param workspace Mutator's workspace. * @return Root block in mutator. */ - decompose: function(this: CreateWithBlock, workspace: Workspace): ContainerBlock { - const containerBlock = - workspace.newBlock('lists_create_with_container') as ContainerBlock; - (containerBlock as BlockSvg).initSvg(); - let connection = containerBlock.getInput('STACK')!.connection; - for (let i = 0; i < this.itemCount_; i++) { - const itemBlock = - workspace.newBlock('lists_create_with_item') as ItemBlock; - (itemBlock as BlockSvg).initSvg(); - if (!itemBlock.previousConnection) { - throw new Error('itemBlock has no previousConnection'); - } - connection!.connect(itemBlock.previousConnection); - connection = itemBlock.nextConnection; - } - return containerBlock; - }, + decompose: function(this: CreateWithBlock, workspace: Workspace): + ContainerBlock { + const containerBlock = + workspace.newBlock('lists_create_with_container') as ContainerBlock; + (containerBlock as BlockSvg).initSvg(); + let connection = containerBlock.getInput('STACK')!.connection; + for (let i = 0; i < this.itemCount_; i++) { + const itemBlock = + workspace.newBlock('lists_create_with_item') as ItemBlock; + (itemBlock as BlockSvg).initSvg(); + if (!itemBlock.previousConnection) { + throw new Error('itemBlock has no previousConnection'); + } + connection!.connect(itemBlock.previousConnection); + connection = itemBlock.nextConnection; + } + return containerBlock; + }, /** * Reconfigure this block based on the mutator dialog's components. * @@ -478,7 +480,7 @@ const LISTS_GETINDEX = { const container = xmlUtils.createElement('mutation'); const isStatement = !this.outputConnection; container.setAttribute('statement', String(isStatement)); - const isAt = this.getInput('AT')!.type === ConnectionType.INPUT_VALUE; + const isAt = this.getInput('AT') instanceof ValueInput; container.setAttribute('at', String(isAt)); return container; }, @@ -689,7 +691,7 @@ const LISTS_SETINDEX = { */ mutationToDom: function(this: SetIndexBlock): Element { const container = xmlUtils.createElement('mutation'); - const isAt = this.getInput('AT')!.type === ConnectionType.INPUT_VALUE; + const isAt = this.getInput('AT') instanceof ValueInput; container.setAttribute('at', String(isAt)); return container; }, @@ -821,9 +823,9 @@ const LISTS_GETSUBLIST = { */ mutationToDom: function(this: GetSublistBlock): Element { const container = xmlUtils.createElement('mutation'); - const isAt1 = this.getInput('AT1')!.type === ConnectionType.INPUT_VALUE; + const isAt1 = this.getInput('AT1') instanceof ValueInput; container.setAttribute('at1', String(isAt1)); - const isAt2 = this.getInput('AT2')!.type === ConnectionType.INPUT_VALUE; + const isAt2 = this.getInput('AT2') instanceof ValueInput; container.setAttribute('at2', String(isAt2)); return container; }, diff --git a/blocks/loops.ts b/blocks/loops.ts index 64e011d97..59f6d00b0 100644 --- a/blocks/loops.ts +++ b/blocks/loops.ts @@ -324,16 +324,17 @@ const CONTROL_FLOW_IN_LOOP_CHECK_MIXIN = { * * @returns The nearest surrounding loop, or null if none. */ - getSurroundLoop: function(this: ControlFlowInLoopBlock): Block | null { - let block: Block|null = this; - do { - if (loopTypes.has(block.type)) { - return block; - } - block = block.getSurroundParent(); - } while (block); - return null; - }, + getSurroundLoop: function(this: ControlFlowInLoopBlock): Block | + null { + let block: Block|null = this; + do { + if (loopTypes.has(block.type)) { + return block; + } + block = block.getSurroundParent(); + } while (block); + return null; + }, /** * Called whenever anything on the workspace changes. diff --git a/blocks/math.ts b/blocks/math.ts index 1fdaf3807..6b899d50a 100644 --- a/blocks/math.ts +++ b/blocks/math.ts @@ -433,7 +433,8 @@ Extensions.register( /** Type of a block that has IS_DIVISBLEBY_MUTATOR_MIXIN */ type DivisiblebyBlock = Block&DivisiblebyMixin; -interface DivisiblebyMixin extends DivisiblebyMixinType {}; +interface DivisiblebyMixin extends DivisiblebyMixinType {} +; type DivisiblebyMixinType = typeof IS_DIVISIBLEBY_MUTATOR_MIXIN; /** @@ -517,7 +518,8 @@ Extensions.register( /** Type of a block that has LIST_MODES_MUTATOR_MIXIN */ type ListModesBlock = Block&ListModesMixin; -interface ListModesMixin extends ListModesMixinType {}; +interface ListModesMixin extends ListModesMixinType {} +; type ListModesMixinType = typeof LIST_MODES_MUTATOR_MIXIN; /** @@ -571,11 +573,11 @@ const LIST_MODES_MUTATOR_MIXIN = { * modes operation (outputs a list of numbers). */ const LIST_MODES_MUTATOR_EXTENSION = function(this: ListModesBlock) { - this.getField('OP')!.setValidator( - function(this: ListModesBlock, newOp: string) { - this.updateType_(newOp); - return undefined; - }.bind(this)); + this.getField( + 'OP')!.setValidator(function(this: ListModesBlock, newOp: string) { + this.updateType_(newOp); + return undefined; + }.bind(this)); }; Extensions.registerMutator( diff --git a/blocks/text.ts b/blocks/text.ts index afaca741d..cc8b49bbc 100644 --- a/blocks/text.ts +++ b/blocks/text.ts @@ -14,7 +14,7 @@ goog.declareModuleId('Blockly.libraryBlocks.texts'); import * as Extensions from '../core/extensions.js'; import * as fieldRegistry from '../core/field_registry.js'; import * as xmlUtils from '../core/utils/xml.js'; -import {Align} from '../core/input.js'; +import {Align} from '../core/inputs/input.js'; import type {Block} from '../core/block.js'; import type {BlockSvg} from '../core/block_svg.js'; import {Connection} from '../core/connection.js'; @@ -28,6 +28,7 @@ import type {Workspace} from '../core/workspace.js'; import {createBlockDefinitionsFromJsonArray, defineBlocks} from '../core/common.js'; import '../core/field_multilineinput.js'; import '../core/field_variable.js'; +import { ValueInput } from '../core/inputs/value_input.js'; /** @@ -284,9 +285,9 @@ const GET_SUBSTRING_BLOCK = { */ mutationToDom: function(this: GetSubstringBlock): Element { const container = xmlUtils.createElement('mutation'); - const isAt1 = this.getInput('AT1')!.type === ConnectionType.INPUT_VALUE; + const isAt1 = this.getInput('AT1') instanceof ValueInput; container.setAttribute('at1', `${isAt1}`); - const isAt2 = this.getInput('AT2')!.type === ConnectionType.INPUT_VALUE; + const isAt2 = this.getInput('AT2') instanceof ValueInput; container.setAttribute('at2', `${isAt2}`); return container; }, diff --git a/core/block.ts b/core/block.ts index ada8ef9ea..c674501f9 100644 --- a/core/block.ts +++ b/core/block.ts @@ -31,8 +31,7 @@ import * as eventUtils from './events/utils.js'; import * as Extensions from './extensions.js'; import type {Field} from './field.js'; import * as fieldRegistry from './field_registry.js'; -import {Align, Input} from './input.js'; -import {inputTypes} from './input_types.js'; +import {Align, Input} from './inputs/input.js'; import type {IASTNodeLocation} from './interfaces/i_ast_node_location.js'; import type {IDeletable} from './interfaces/i_deletable.js'; import type {Mutator} from './mutator.js'; @@ -45,6 +44,9 @@ import * as registry from './registry.js'; import {Size} from './utils/size.js'; import type {VariableModel} from './variable_model.js'; import type {Workspace} from './workspace.js'; +import {DummyInput} from './inputs/dummy_input.js'; +import {ValueInput} from './inputs/value_input.js'; +import {StatementInput} from './inputs/statement_input.js'; /** @@ -1298,15 +1300,15 @@ export class Block implements IASTNodeLocation, IDeletable { } // Not defined explicitly. Figure out what would look best. for (let i = 1; i < this.inputList.length; i++) { - if (this.inputList[i - 1].type === inputTypes.DUMMY && - this.inputList[i].type === inputTypes.DUMMY) { + if (this.inputList[i - 1] instanceof DummyInput && + this.inputList[i] instanceof DummyInput) { // Two dummy inputs in a row. Don't inline them. return false; } } for (let i = 1; i < this.inputList.length; i++) { - if (this.inputList[i - 1].type === inputTypes.VALUE && - this.inputList[i].type === inputTypes.DUMMY) { + if (this.inputList[i - 1] instanceof ValueInput && + this.inputList[i] instanceof DummyInput) { // Dummy input after a value input. Inline them. return true; } @@ -1490,7 +1492,8 @@ export class Block implements IASTNodeLocation, IDeletable { * @returns The input object created. */ appendValueInput(name: string): Input { - return this.appendInput_(inputTypes.VALUE, name); + return this.appendInput(new ValueInput( + name, this, this.makeConnection_(ConnectionType.INPUT_VALUE))); } /** @@ -1501,18 +1504,20 @@ export class Block implements IASTNodeLocation, IDeletable { * @returns The input object created. */ appendStatementInput(name: string): Input { - return this.appendInput_(inputTypes.STATEMENT, name); + this.statementInputCount++; + return this.appendInput(new StatementInput( + name, this, this.makeConnection_(ConnectionType.NEXT_STATEMENT))); } /** * Appends a dummy input row. * - * @param opt_name Language-neutral identifier which may used to find this - * input again. Should be unique to this block. + * @param name Optional language-neutral identifier which may used to find + * this input again. Should be unique to this block. * @returns The input object created. */ - appendDummyInput(opt_name?: string): Input { - return this.appendInput_(inputTypes.DUMMY, opt_name || ''); + appendDummyInput(name = ''): Input { + return this.appendInput(new DummyInput(name, this)); } /** @@ -1538,8 +1543,7 @@ export class Block implements IASTNodeLocation, IDeletable { const inputConstructor = registry.getClass(registry.Type.INPUT, type, false); if (!inputConstructor) return null; - return this.appendInput( - new inputConstructor(inputTypes.CUSTOM, name, this, null)); + return this.appendInput(new inputConstructor(name, this, null)); } /** @@ -1934,28 +1938,6 @@ export class Block implements IASTNodeLocation, IDeletable { return null; } - /** - * Add a value input, statement input or local variable to this block. - * - * @param type One of Blockly.inputTypes. - * @param name Language-neutral identifier which may used to find this input - * again. Should be unique to this block. - * @returns The input object created. - */ - protected appendInput_(type: number, name: string): Input { - let connection = null; - if (type === inputTypes.VALUE || type === inputTypes.STATEMENT) { - connection = this.makeConnection_(type); - } - if (type === inputTypes.STATEMENT) { - this.statementInputCount++; - } - const input = new Input(type, name, this, connection); - // Append input to list. - this.inputList.push(input); - return input; - } - /** * Move a named input to a different location on this block. * @@ -2031,9 +2013,7 @@ export class Block implements IASTNodeLocation, IDeletable { removeInput(name: string, opt_quiet?: boolean): boolean { for (let i = 0, input; input = this.inputList[i]; i++) { if (input.name === name) { - if (input.type === inputTypes.STATEMENT) { - this.statementInputCount--; - } + if (input instanceof StatementInput) this.statementInputCount--; input.dispose(); this.inputList.splice(i, 1); return true; diff --git a/core/block_svg.ts b/core/block_svg.ts index 5939ce44e..dd31152ad 100644 --- a/core/block_svg.ts +++ b/core/block_svg.ts @@ -31,7 +31,7 @@ import * as eventUtils from './events/utils.js'; import type {Field} from './field.js'; import {FieldLabel} from './field_label.js'; import type {Icon} from './icon.js'; -import type {Input} from './input.js'; +import type {Input} from './inputs/input.js'; import type {IASTNodeLocationSvg} from './interfaces/i_ast_node_location_svg.js'; import type {IBoundedElement} from './interfaces/i_bounded_element.js'; import type {CopyData, ICopyable} from './interfaces/i_copyable.js'; @@ -1342,16 +1342,9 @@ export class BlockSvg extends Block implements IASTNodeLocationSvg, } } - /** - * Add a value input, statement input or local variable to this block. - * - * @param type One of Blockly.inputTypes. - * @param name Language-neutral identifier which may used to find this input - * again. Should be unique to this block. - * @returns The input object created. - */ - protected override appendInput_(type: number, name: string): Input { - const input = super.appendInput_(type, name); + /** @override */ + override appendInput(input: Input): Input { + super.appendInput(input); if (this.rendered) { this.queueRender(); diff --git a/core/blockly.ts b/core/blockly.ts index 11d46c27c..08a5d1c99 100644 --- a/core/blockly.ts +++ b/core/blockly.ts @@ -71,8 +71,11 @@ import {Gesture} from './gesture.js'; import {Grid} from './grid.js'; import {Icon} from './icon.js'; import {inject} from './inject.js'; -import {Align, Input} from './input.js'; -import {inputTypes} from './input_types.js'; +import {Align, Input} from './inputs/input.js'; +import {inputTypes} from './inputs/input_types.js'; +import {DummyInput} from './inputs/dummy_input.js'; +import {StatementInput} from './inputs/statement_input.js'; +import {ValueInput} from './inputs/value_input.js'; import {InsertionMarkerManager} from './insertion_marker_manager.js'; import {IASTNodeLocation} from './interfaces/i_ast_node_location.js'; import {IASTNodeLocationSvg} from './interfaces/i_ast_node_location_svg.js'; @@ -692,6 +695,13 @@ export {IKeyboardAccessible}; export {IMetricsManager}; export {IMovable}; export {Input}; +export const inputs = { + Input, + inputTypes, + DummyInput, + StatementInput, + ValueInput, +}; export {InsertionMarkerManager}; export {IObservable, isObservable}; export {IPositionable}; diff --git a/core/connection.ts b/core/connection.ts index bc7a5931e..5b4d86588 100644 --- a/core/connection.ts +++ b/core/connection.ts @@ -16,7 +16,7 @@ import type {Block} from './block.js'; import {ConnectionType} from './connection_type.js'; import type {BlockMove} from './events/events_block_move.js'; import * as eventUtils from './events/utils.js'; -import type {Input} from './input.js'; +import type {Input} from './inputs/input.js'; import type {IASTNodeLocationWithBlock} from './interfaces/i_ast_node_location_with_block.js'; import type {IConnectionChecker} from './interfaces/i_connection_checker.js'; import * as blocks from './serialization/blocks.js'; diff --git a/core/contextmenu_items.ts b/core/contextmenu_items.ts index 67d035307..9b15d71cf 100644 --- a/core/contextmenu_items.ts +++ b/core/contextmenu_items.ts @@ -13,8 +13,8 @@ import {ContextMenuRegistry, RegistryItem, Scope} from './contextmenu_registry.j import * as dialog from './dialog.js'; import * as Events from './events/events.js'; import * as eventUtils from './events/utils.js'; -import {inputTypes} from './input_types.js'; import {Msg} from './msg.js'; +import {StatementInput} from './renderers/zelos/zelos.js'; import type {WorkspaceSvg} from './workspace_svg.js'; @@ -386,8 +386,8 @@ export function registerInline() { for (let i = 1; i < block!.inputList.length; i++) { // Only display this option if there are two value or dummy inputs // next to each other. - if (block!.inputList[i - 1].type !== inputTypes.STATEMENT && - block!.inputList[i].type !== inputTypes.STATEMENT) { + if (!(block!.inputList[i - 1] instanceof StatementInput) && + !(block!.inputList[i] instanceof StatementInput)) { return 'enabled'; } } diff --git a/core/field.ts b/core/field.ts index 4c5058464..43558ce40 100644 --- a/core/field.ts +++ b/core/field.ts @@ -22,7 +22,7 @@ import type {BlockSvg} from './block_svg.js'; import * as browserEvents from './browser_events.js'; import * as dropDownDiv from './dropdowndiv.js'; import * as eventUtils from './events/utils.js'; -import type {Input} from './input.js'; +import type {Input} from './inputs/input.js'; import type {IASTNodeLocationSvg} from './interfaces/i_ast_node_location_svg.js'; import type {IASTNodeLocationWithBlock} from './interfaces/i_ast_node_location_with_block.js'; import type {IKeyboardAccessible} from './interfaces/i_keyboard_accessible.js'; diff --git a/core/inputs/dummy_input.ts b/core/inputs/dummy_input.ts new file mode 100644 index 000000000..558fc6d6f --- /dev/null +++ b/core/inputs/dummy_input.ts @@ -0,0 +1,24 @@ +/** + * @license + * Copyright 2023 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import type {Block} from '../block.js'; +import {Input} from './input.js'; +import {inputTypes} from './input_types.js'; + + +/** Represents an input on a block with no connection. */ +export class DummyInput extends Input { + readonly type = inputTypes.DUMMY; + + /** + * @param name Language-neutral identifier which may used to find this input + * again. + * @param block The block containing this input. + */ + constructor(public name: string, block: Block) { + super(name, block, null); + } +} diff --git a/core/input.ts b/core/inputs/input.ts similarity index 90% rename from core/input.ts rename to core/inputs/input.ts index e0ad29537..990d644ff 100644 --- a/core/input.ts +++ b/core/inputs/input.ts @@ -9,26 +9,25 @@ * * @class */ -import * as goog from '../closure/goog/goog.js'; +import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.Input'); // Unused import preserved for side-effects. Remove if unneeded. -import './field_label.js'; +import '../field_label.js'; -import type {Block} from './block.js'; -import type {BlockSvg} from './block_svg.js'; -import type {Connection} from './connection.js'; -import type {Field} from './field.js'; -import * as fieldRegistry from './field_registry.js'; +import type {Block} from '../block.js'; +import type {BlockSvg} from '../block_svg.js'; +import type {Connection} from '../connection.js'; +import type {Field} from '../field.js'; +import * as fieldRegistry from '../field_registry.js'; +import type {RenderedConnection} from '../rendered_connection.js'; import {inputTypes} from './input_types.js'; -import type {RenderedConnection} from './rendered_connection.js'; /** * Class for an input with an optional field. */ export class Input { - private sourceBlock: Block; fieldRow: Field[] = []; /** Alignment of input's fields (left, right or centre). */ align = Align.LEFT; @@ -36,24 +35,19 @@ export class Input { /** Is the input visible? */ private visible = true; + public readonly type: inputTypes = inputTypes.CUSTOM; + /** - * @param type The type of the input. * @param name Language-neutral identifier which may used to find this input * again. - * @param block The block containing this input. + * @param sourceBlock The block containing this input. * @param connection Optional connection for this input. If this is a custom * input, `null` will always be passed, and then the subclass can * optionally construct a connection. */ constructor( - public type: number, public name: string, block: Block, - public connection: Connection|null) { - if ((type === inputTypes.VALUE || type === inputTypes.STATEMENT) && !name) { - throw Error( - 'Value inputs and statement inputs must have non-empty name.'); - } - this.sourceBlock = block; - } + public name: string, private sourceBlock: Block, + public connection: Connection|null) {} /** * Get the source block for this input. diff --git a/core/input_types.ts b/core/inputs/input_types.ts similarity index 84% rename from core/input_types.ts rename to core/inputs/input_types.ts index 039bc6b70..a7d30131e 100644 --- a/core/input_types.ts +++ b/core/inputs/input_types.ts @@ -4,10 +4,10 @@ * SPDX-License-Identifier: Apache-2.0 */ -import * as goog from '../closure/goog/goog.js'; +import * as goog from '../../closure/goog/goog.js'; goog.declareModuleId('Blockly.inputTypes'); -import {ConnectionType} from './connection_type.js'; +import {ConnectionType} from '../connection_type.js'; /** diff --git a/core/inputs/statement_input.ts b/core/inputs/statement_input.ts new file mode 100644 index 000000000..fbfb5d7f7 --- /dev/null +++ b/core/inputs/statement_input.ts @@ -0,0 +1,30 @@ +/** + * @license + * Copyright 2023 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import type {Block} from '../block.js'; +import type {Connection} from '../connection.js'; +import {Input} from './input.js'; +import {inputTypes} from './input_types.js'; + + +/** Represents an input on a block with a statement connection. */ +export class StatementInput extends Input { + readonly type = inputTypes.STATEMENT; + + /** + * @param name Language-neutral identifier which may used to find this input + * again. + * @param block The block containing this input. + * @param connection The statement connection for this input. + */ + constructor( + public name: string, block: Block, public connection: Connection) { + // Errors are maintained for people not using typescript. + if (!name) throw new Error('Statement inputs must have a non-empty name'); + if (!connection) throw new Error('Value inputs must have a connection'); + super(name, block, connection); + } +} diff --git a/core/inputs/value_input.ts b/core/inputs/value_input.ts new file mode 100644 index 000000000..905342441 --- /dev/null +++ b/core/inputs/value_input.ts @@ -0,0 +1,30 @@ +/** + * @license + * Copyright 2023 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import type {Block} from '../block.js'; +import type {Connection} from '../connection.js'; +import {Input} from './input.js'; +import {inputTypes} from './input_types.js'; + + +/** Represents an input on a block with a value connection. */ +export class ValueInput extends Input { + readonly type = inputTypes.VALUE; + + /** + * @param name Language-neutral identifier which may used to find this input + * again. + * @param block The block containing this input. + * @param connection The value connection for this input. + */ + constructor( + public name: string, block: Block, public connection: Connection) { + // Errors are maintained for people not using typescript. + if (!name) throw new Error('Value inputs must have a non-empty name'); + if (!connection) throw new Error('Value inputs must have a connection'); + super(name, block, connection); + } +} diff --git a/core/keyboard_nav/ast_node.ts b/core/keyboard_nav/ast_node.ts index abffd77e4..769fd9e0e 100644 --- a/core/keyboard_nav/ast_node.ts +++ b/core/keyboard_nav/ast_node.ts @@ -17,7 +17,7 @@ import type {Block} from '../block.js'; import type {Connection} from '../connection.js'; import {ConnectionType} from '../connection_type.js'; import type {Field} from '../field.js'; -import type {Input} from '../input.js'; +import type {Input} from '../inputs/input.js'; import type {IASTNodeLocation} from '../interfaces/i_ast_node_location.js'; import type {IASTNodeLocationWithBlock} from '../interfaces/i_ast_node_location_with_block.js'; import {Coordinate} from '../utils/coordinate.js'; diff --git a/core/registry.ts b/core/registry.ts index a7839916d..74cf8411b 100644 --- a/core/registry.ts +++ b/core/registry.ts @@ -13,7 +13,7 @@ import type {IBlockDragger} from './interfaces/i_block_dragger.js'; import type {IConnectionChecker} from './interfaces/i_connection_checker.js'; import type {IFlyout} from './interfaces/i_flyout.js'; import type {IMetricsManager} from './interfaces/i_metrics_manager.js'; -import type {Input} from './input.js'; +import type {Input} from './inputs/input.js'; import type {ISerializer} from './interfaces/i_serializer.js'; import type {IToolbox} from './interfaces/i_toolbox.js'; import type {Cursor} from './keyboard_nav/cursor.js'; diff --git a/core/renderers/common/info.ts b/core/renderers/common/info.ts index 1a162a676..239f02f63 100644 --- a/core/renderers/common/info.ts +++ b/core/renderers/common/info.ts @@ -8,11 +8,11 @@ import * as goog from '../../../closure/goog/goog.js'; goog.declareModuleId('Blockly.blockRendering.RenderInfo'); import type {BlockSvg} from '../../block_svg.js'; -import {Align, Input} from '../../input.js'; -import {inputTypes} from '../../input_types.js'; +import {Align, Input} from '../../inputs/input.js'; import type {RenderedConnection} from '../../rendered_connection.js'; import type {Measurable} from '../measurables/base.js'; import {BottomRow} from '../measurables/bottom_row.js'; +import {DummyInput} from '../../inputs/dummy_input.js'; import {ExternalValueInput} from '../measurables/external_value_input.js'; import {Field} from '../measurables/field.js'; import {Hat} from '../measurables/hat.js'; @@ -28,9 +28,11 @@ import {RoundCorner} from '../measurables/round_corner.js'; import type {Row} from '../measurables/row.js'; import {SpacerRow} from '../measurables/spacer_row.js'; import {SquareCorner} from '../measurables/square_corner.js'; -import {StatementInput} from '../measurables/statement_input.js'; +import {StatementInput as StatementInputMeasurable} from '../measurables/statement_input.js'; +import {StatementInput} from '../../inputs/statement_input.js'; import {TopRow} from '../measurables/top_row.js'; import {Types} from '../measurables/types.js'; +import {ValueInput} from '../../inputs/value_input.js'; import type {ConstantProvider} from './constants.js'; import type {Renderer} from './renderer.js'; @@ -241,7 +243,7 @@ export class RenderInfo { } const precedesStatement = this.block_.inputList.length && - this.block_.inputList[0].type === inputTypes.STATEMENT; + this.block_.inputList[0] instanceof StatementInput; // This is the minimum height for the row. If one of its elements has a // greater height it will be overwritten in the compute pass. @@ -264,8 +266,8 @@ export class RenderInfo { this.bottomRow.hasNextConnection = !!this.block_.nextConnection; const followsStatement = this.block_.inputList.length && - this.block_.inputList[this.block_.inputList.length - 1].type === - inputTypes.STATEMENT; + this.block_.inputList[this.block_.inputList.length - 1] instanceof + StatementInput; // This is the minimum height for the row. If one of its elements has a // greater height it will be overwritten in the compute pass. @@ -308,16 +310,17 @@ export class RenderInfo { */ protected addInput_(input: Input, activeRow: Row) { // Non-dummy inputs have visual representations onscreen. - if (this.isInline && input.type === inputTypes.VALUE) { + if (this.isInline && input instanceof ValueInput) { activeRow.elements.push(new InlineInput(this.constants_, input)); activeRow.hasInlineInput = true; - } else if (input.type === inputTypes.STATEMENT) { - activeRow.elements.push(new StatementInput(this.constants_, input)); + } else if (input instanceof StatementInput) { + activeRow.elements.push( + new StatementInputMeasurable(this.constants_, input)); activeRow.hasStatement = true; - } else if (input.type === inputTypes.VALUE) { + } else if (input instanceof ValueInput) { activeRow.elements.push(new ExternalValueInput(this.constants_, input)); activeRow.hasExternalInput = true; - } else if (input.type === inputTypes.DUMMY) { + } else if (input instanceof DummyInput) { // Dummy inputs have no visual representation, but the information is // still important. activeRow.minHeight = Math.max( @@ -346,12 +349,12 @@ export class RenderInfo { return false; } // A statement input or an input following one always gets a new row. - if (input.type === inputTypes.STATEMENT || - lastInput.type === inputTypes.STATEMENT) { + if (input instanceof StatementInput || + lastInput instanceof StatementInput) { return true; } // Value and dummy inputs get new row if inputs are not inlined. - if (input.type === inputTypes.VALUE || input.type === inputTypes.DUMMY) { + if (input instanceof ValueInput || input instanceof DummyInput) { return !this.isInline; } return false; diff --git a/core/renderers/geras/info.ts b/core/renderers/geras/info.ts index 78262cdb9..d2421395f 100644 --- a/core/renderers/geras/info.ts +++ b/core/renderers/geras/info.ts @@ -8,22 +8,24 @@ import * as goog from '../../../closure/goog/goog.js'; goog.declareModuleId('Blockly.geras.RenderInfo'); import type {BlockSvg} from '../../block_svg.js'; -import type {Input} from '../../input.js'; -import {inputTypes} from '../../input_types.js'; +import type {Input} from '../../inputs/input.js'; import {RenderInfo as BaseRenderInfo} from '../common/info.js'; import type {Measurable} from '../measurables/base.js'; import type {BottomRow} from '../measurables/bottom_row.js'; +import {DummyInput} from '../../inputs/dummy_input.js'; import {ExternalValueInput} from '../measurables/external_value_input.js'; import type {Field} from '../measurables/field.js'; import {InRowSpacer} from '../measurables/in_row_spacer.js'; import type {InputRow} from '../measurables/input_row.js'; import type {Row} from '../measurables/row.js'; +import {StatementInput} from '../../inputs/statement_input.js'; import type {TopRow} from '../measurables/top_row.js'; import {Types} from '../measurables/types.js'; +import {ValueInput} from '../../inputs/value_input.js'; import type {ConstantProvider} from './constants.js'; import {InlineInput} from './measurables/inline_input.js'; -import {StatementInput} from './measurables/statement_input.js'; +import {StatementInput as StatementInputMeasurable} from './measurables/statement_input.js'; import type {Renderer} from './renderer.js'; @@ -63,8 +65,8 @@ export class RenderInfo extends BaseRenderInfo { super.populateBottomRow_(); const followsStatement = this.block_.inputList.length && - this.block_.inputList[this.block_.inputList.length - 1].type === - inputTypes.STATEMENT; + this.block_.inputList[this.block_.inputList.length - 1] instanceof + StatementInput; // The minimum height of the bottom row is smaller in Geras than in other // renderers, because the dark path adds a pixel. // If one of the row's elements has a greater height this will be @@ -77,16 +79,17 @@ export class RenderInfo extends BaseRenderInfo { override addInput_(input: Input, activeRow: Row) { // Non-dummy inputs have visual representations onscreen. - if (this.isInline && input.type === inputTypes.VALUE) { + if (this.isInline && input instanceof ValueInput) { activeRow.elements.push(new InlineInput(this.constants_, input)); activeRow.hasInlineInput = true; - } else if (input.type === inputTypes.STATEMENT) { - activeRow.elements.push(new StatementInput(this.constants_, input)); + } else if (input instanceof StatementInput) { + activeRow.elements.push( + new StatementInputMeasurable(this.constants_, input)); activeRow.hasStatement = true; - } else if (input.type === inputTypes.VALUE) { + } else if (input instanceof ValueInput) { activeRow.elements.push(new ExternalValueInput(this.constants_, input)); activeRow.hasExternalInput = true; - } else if (input.type === inputTypes.DUMMY) { + } else if (input instanceof DummyInput) { // Dummy inputs have no visual representation, but the information is // still important. activeRow.minHeight = diff --git a/core/renderers/geras/measurables/inline_input.ts b/core/renderers/geras/measurables/inline_input.ts index b361528f0..ebac1e175 100644 --- a/core/renderers/geras/measurables/inline_input.ts +++ b/core/renderers/geras/measurables/inline_input.ts @@ -8,7 +8,7 @@ import * as goog from '../../../../closure/goog/goog.js'; goog.declareModuleId('Blockly.geras.InlineInput'); /* eslint-disable-next-line no-unused-vars */ -import type {Input} from '../../../input.js'; +import type {Input} from '../../../inputs/input.js'; import type {ConstantProvider as BaseConstantProvider} from '../../../renderers/common/constants.js'; import {InlineInput as BaseInlineInput} from '../../../renderers/measurables/inline_input.js'; import type {ConstantProvider as GerasConstantProvider} from '../constants.js'; diff --git a/core/renderers/geras/measurables/statement_input.ts b/core/renderers/geras/measurables/statement_input.ts index 3720fce01..a307ff027 100644 --- a/core/renderers/geras/measurables/statement_input.ts +++ b/core/renderers/geras/measurables/statement_input.ts @@ -8,7 +8,7 @@ import * as goog from '../../../../closure/goog/goog.js'; goog.declareModuleId('Blockly.geras.StatementInput'); /* eslint-disable-next-line no-unused-vars */ -import type {Input} from '../../../input.js'; +import type {Input} from '../../../inputs/input.js'; import type {ConstantProvider as BaseConstantProvider} from '../../../renderers/common/constants.js'; import {StatementInput as BaseStatementInput} from '../../../renderers/measurables/statement_input.js'; import type {ConstantProvider as GerasConstantProvider} from '../constants.js'; diff --git a/core/renderers/measurables/external_value_input.ts b/core/renderers/measurables/external_value_input.ts index bcb0aea4d..4bdc3dd35 100644 --- a/core/renderers/measurables/external_value_input.ts +++ b/core/renderers/measurables/external_value_input.ts @@ -8,7 +8,7 @@ import * as goog from '../../../closure/goog/goog.js'; goog.declareModuleId('Blockly.blockRendering.ExternalValueInput'); /* eslint-disable-next-line no-unused-vars */ -import type {Input} from '../../input.js'; +import type {Input} from '../../inputs/input.js'; import type {ConstantProvider} from '../common/constants.js'; import {InputConnection} from './input_connection.js'; diff --git a/core/renderers/measurables/field.ts b/core/renderers/measurables/field.ts index 46c043d69..5b0408227 100644 --- a/core/renderers/measurables/field.ts +++ b/core/renderers/measurables/field.ts @@ -9,7 +9,7 @@ goog.declareModuleId('Blockly.blockRendering.Field'); /* eslint-disable-next-line no-unused-vars */ import type {Field as BlocklyField} from '../../field.js'; -import type {Input} from '../../input.js'; +import type {Input} from '../../inputs/input.js'; import type {ConstantProvider} from '../common/constants.js'; import {Measurable} from './base.js'; diff --git a/core/renderers/measurables/inline_input.ts b/core/renderers/measurables/inline_input.ts index 5b04370fc..3a27e7f34 100644 --- a/core/renderers/measurables/inline_input.ts +++ b/core/renderers/measurables/inline_input.ts @@ -8,7 +8,7 @@ import * as goog from '../../../closure/goog/goog.js'; goog.declareModuleId('Blockly.blockRendering.InlineInput'); /* eslint-disable-next-line no-unused-vars */ -import type {Input} from '../../input.js'; +import type {Input} from '../../inputs/input.js'; import type {ConstantProvider} from '../common/constants.js'; import {InputConnection} from './input_connection.js'; diff --git a/core/renderers/measurables/input_connection.ts b/core/renderers/measurables/input_connection.ts index 64d0769be..2cf0b001d 100644 --- a/core/renderers/measurables/input_connection.ts +++ b/core/renderers/measurables/input_connection.ts @@ -8,7 +8,7 @@ import * as goog from '../../../closure/goog/goog.js'; goog.declareModuleId('Blockly.blockRendering.InputConnection'); import type {BlockSvg} from '../../block_svg.js'; -import type {Input} from '../../input.js'; +import type {Input} from '../../inputs/input.js'; import type {RenderedConnection} from '../../rendered_connection.js'; import type {ConstantProvider} from '../common/constants.js'; diff --git a/core/renderers/measurables/statement_input.ts b/core/renderers/measurables/statement_input.ts index ac5a0bfb2..de72d8739 100644 --- a/core/renderers/measurables/statement_input.ts +++ b/core/renderers/measurables/statement_input.ts @@ -8,7 +8,7 @@ import * as goog from '../../../closure/goog/goog.js'; goog.declareModuleId('Blockly.blockRendering.StatementInput'); /* eslint-disable-next-line no-unused-vars */ -import type {Input} from '../../input.js'; +import type {Input} from '../../inputs/input.js'; import type {ConstantProvider} from '../common/constants.js'; import {InputConnection} from './input_connection.js'; diff --git a/core/renderers/zelos/info.ts b/core/renderers/zelos/info.ts index 235b92e4a..15f459ad7 100644 --- a/core/renderers/zelos/info.ts +++ b/core/renderers/zelos/info.ts @@ -8,23 +8,25 @@ import * as goog from '../../../closure/goog/goog.js'; goog.declareModuleId('Blockly.zelos.RenderInfo'); import type {BlockSvg} from '../../block_svg.js'; +import {DummyInput} from '../../inputs/dummy_input.js'; import {FieldImage} from '../../field_image.js'; import {FieldLabel} from '../../field_label.js'; import {FieldTextInput} from '../../field_textinput.js'; -import {Align, Input} from '../../input.js'; -import {inputTypes} from '../../input_types.js'; +import {Align, Input} from '../../inputs/input.js'; import {RenderInfo as BaseRenderInfo} from '../common/info.js'; import type {Measurable} from '../measurables/base.js'; import {Field} from '../measurables/field.js'; import {InRowSpacer} from '../measurables/in_row_spacer.js'; import {InputConnection} from '../measurables/input_connection.js'; +import {StatementInput} from '../../inputs/statement_input.js'; import type {Row} from '../measurables/row.js'; import type {SpacerRow} from '../measurables/spacer_row.js'; import {Types} from '../measurables/types.js'; +import {ValueInput} from '../../inputs/value_input.js'; import type {ConstantProvider, InsideCorners} from './constants.js'; import {BottomRow} from './measurables/bottom_row.js'; -import {StatementInput} from './measurables/inputs.js'; +import {StatementInput as StatementInputMeasurable} from './measurables/inputs.js'; import {RightConnectionShape} from './measurables/row_elements.js'; import {TopRow} from './measurables/top_row.js'; import type {PathObject} from './path_object.js'; @@ -123,12 +125,12 @@ export class RenderInfo extends BaseRenderInfo { return false; } // A statement input or an input following one always gets a new row. - if (input.type === inputTypes.STATEMENT || - lastInput.type === inputTypes.STATEMENT) { + if (input instanceof StatementInput || + lastInput instanceof StatementInput) { return true; } // Value and dummy inputs get new row if inputs are not inlined. - if (input.type === inputTypes.VALUE || input.type === inputTypes.DUMMY) { + if (input instanceof ValueInput || input instanceof DummyInput) { return !this.isInline || this.isMultiRow; } return false; @@ -244,12 +246,13 @@ export class RenderInfo extends BaseRenderInfo { // If we have two dummy inputs on the same row, one aligned left and the // other right, keep track of the right aligned dummy input so we can add // padding later. - if (input.type === inputTypes.DUMMY && activeRow.hasDummyInput && + if (input instanceof DummyInput && activeRow.hasDummyInput && activeRow.align === Align.LEFT && input.align === Align.RIGHT) { this.rightAlignedDummyInputs.set(activeRow, input); - } else if (input.type === inputTypes.STATEMENT) { + } else if (input instanceof StatementInput) { // Handle statements without next connections correctly. - activeRow.elements.push(new StatementInput(this.constants_, input)); + activeRow.elements.push( + new StatementInputMeasurable(this.constants_, input)); activeRow.hasStatement = true; if (activeRow.align === null) { diff --git a/core/renderers/zelos/measurables/inputs.ts b/core/renderers/zelos/measurables/inputs.ts index 4699bdd64..e90b166d2 100644 --- a/core/renderers/zelos/measurables/inputs.ts +++ b/core/renderers/zelos/measurables/inputs.ts @@ -8,7 +8,7 @@ import * as goog from '../../../../closure/goog/goog.js'; goog.declareModuleId('Blockly.zelos.StatementInput'); /* eslint-disable-next-line no-unused-vars */ -import type {Input} from '../../../input.js'; +import type {Input} from '../../../inputs/input.js'; import type {ConstantProvider} from '../../../renderers/common/constants.js'; import {StatementInput as BaseStatementInput} from '../../../renderers/measurables/statement_input.js'; diff --git a/core/serialization/blocks.ts b/core/serialization/blocks.ts index e9bb0bd0c..a203bec2a 100644 --- a/core/serialization/blocks.ts +++ b/core/serialization/blocks.ts @@ -11,7 +11,7 @@ import type {Block} from '../block.js'; import type {BlockSvg} from '../block_svg.js'; import type {Connection} from '../connection.js'; import * as eventUtils from '../events/utils.js'; -import {inputTypes} from '../input_types.js'; +import {inputTypes} from '../inputs/input_types.js'; import type {ISerializer} from '../interfaces/i_serializer.js'; import {Size} from '../utils/size.js'; import * as utilsXml from '../utils/xml.js'; @@ -251,9 +251,7 @@ function saveInputBlocks( const inputs = Object.create(null); for (let i = 0; i < block.inputList.length; i++) { const input = block.inputList[i]; - if (input.type === inputTypes.DUMMY) { - continue; - } + if (!input.connection) continue; const connectionState = saveConnection(input.connection as Connection, doFullSerialization); if (connectionState) { diff --git a/core/xml.ts b/core/xml.ts index c947896b7..e0ef681de 100644 --- a/core/xml.ts +++ b/core/xml.ts @@ -13,7 +13,7 @@ import type {Connection} from './connection.js'; import * as deprecation from './utils/deprecation.js'; import * as eventUtils from './events/utils.js'; import type {Field} from './field.js'; -import {inputTypes} from './input_types.js'; +import {inputTypes} from './inputs/input_types.js'; import * as dom from './utils/dom.js'; import {Size} from './utils/size.js'; import * as utilsXml from './utils/xml.js'; diff --git a/tests/mocha/block_json_test.js b/tests/mocha/block_json_test.js index 894e1dacc..31b903f6a 100644 --- a/tests/mocha/block_json_test.js +++ b/tests/mocha/block_json_test.js @@ -6,7 +6,7 @@ goog.declareModuleId('Blockly.test.blockJson'); -import {Align} from '../../build/src/core/input.js'; +import {Align} from '../../build/src/core/inputs/input.js'; import {sharedTestSetup, sharedTestTeardown} from './test_helpers/setup_teardown.js'; suite('Block JSON initialization', function() {