mirror of
https://github.com/google/blockly.git
synced 2026-01-05 08:00:09 +01:00
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
This commit is contained in:
@@ -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;
|
||||
},
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
24
core/inputs/dummy_input.ts
Normal file
24
core/inputs/dummy_input.ts
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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';
|
||||
|
||||
|
||||
/**
|
||||
30
core/inputs/statement_input.ts
Normal file
30
core/inputs/statement_input.ts
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
30
core/inputs/value_input.ts
Normal file
30
core/inputs/value_input.ts
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user