refactor(blocks): Migrate blocks/logic.js to TypeScript (#7003)

* refactor(blocks): Auto-migration of blocks/logic.js to ts

* fix(blocks): Manually migrate & fix types in logic.ts

* chore(blocks): Fix import order

* chore(blocks): Fixes for PR #7003

* chore(blocks): Lint removal

* chore(blocks): Format blocks/logic.ts
This commit is contained in:
Christopher Allen
2023-06-16 21:13:41 +01:00
committed by GitHub
parent d7ccf8a5ee
commit 95dd63b2bd

View File

@@ -4,44 +4,30 @@
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
/** import * as goog from '../closure/goog/goog.js';
* @fileoverview Logic blocks for Blockly. goog.declareModuleId('Blockly.libraryBlocks.logic');
* @suppress {checkTypes}
*/
'use strict';
goog.module('Blockly.libraryBlocks.logic');
/* eslint-disable-next-line no-unused-vars */
const AbstractEvent = goog.requireType('Blockly.Events.Abstract');
const Events = goog.require('Blockly.Events');
const Extensions = goog.require('Blockly.Extensions');
const xmlUtils = goog.require('Blockly.utils.xml');
/* eslint-disable-next-line no-unused-vars */
const {Block} = goog.requireType('Blockly.Block');
// const {BlockDefinition} = goog.requireType('Blockly.blocks');
// TODO (6248): Properly import the BlockDefinition type.
/* eslint-disable-next-line no-unused-vars */
const BlockDefinition = Object;
const {Msg} = goog.require('Blockly.Msg');
/* eslint-disable-next-line no-unused-vars */
const {RenderedConnection} = goog.requireType('Blockly.RenderedConnection');
/* eslint-disable-next-line no-unused-vars */
const {Workspace} = goog.requireType('Blockly.Workspace');
const {createBlockDefinitionsFromJsonArray, defineBlocks} = goog.require('Blockly.common');
/** @suppress {extraRequire} */
goog.require('Blockly.Mutator');
/** @suppress {extraRequire} */
goog.require('Blockly.FieldDropdown');
/** @suppress {extraRequire} */
goog.require('Blockly.FieldLabel');
import * as Events from '../core/events/events.js';
import * as Extensions from '../core/extensions.js';
import * as xmlUtils from '../core/utils/xml.js';
import type {Abstract as AbstractEvent} from '../core/events/events_abstract.js';
import type {Block} from '../core/block.js';
import type {BlockSvg} from '../core/block_svg.js';
import type {Connection} from '../core/connection.js';
import {Msg} from '../core/msg.js';
import type {Workspace} from '../core/workspace.js';
import {
createBlockDefinitionsFromJsonArray,
defineBlocks,
} from '../core/common.js';
import '../core/field_dropdown.js';
import '../core/field_label.js';
import '../core/icons/mutator_icon.js';
/** /**
* A dictionary of the block definitions provided by this module. * A dictionary of the block definitions provided by this module.
* @type {!Object<string, !BlockDefinition>}
*/ */
const blocks = createBlockDefinitionsFromJsonArray([ export const blocks = createBlockDefinitionsFromJsonArray([
// Block for boolean data type: true and false. // Block for boolean data type: true and false.
{ {
'type': 'logic_boolean', 'type': 'logic_boolean',
@@ -267,13 +253,12 @@ const blocks = createBlockDefinitionsFromJsonArray([
'tooltip': '%{BKY_CONTROLS_IF_ELSE_TOOLTIP}', 'tooltip': '%{BKY_CONTROLS_IF_ELSE_TOOLTIP}',
}, },
]); ]);
exports.blocks = blocks;
/** /**
* Tooltip text, keyed by block OP value. Used by logic_compare and * Tooltip text, keyed by block OP value. Used by logic_compare and
* logic_operation blocks. * logic_operation blocks.
*
* @see {Extensions#buildTooltipForDropdown} * @see {Extensions#buildTooltipForDropdown}
* @readonly
*/ */
const TOOLTIPS_BY_OP = { const TOOLTIPS_BY_OP = {
// logic_compare // logic_compare
@@ -290,14 +275,33 @@ const TOOLTIPS_BY_OP = {
}; };
Extensions.register( Extensions.register(
'logic_op_tooltip', 'logic_op_tooltip',
Extensions.buildTooltipForDropdown('OP', TOOLTIPS_BY_OP)); Extensions.buildTooltipForDropdown('OP', TOOLTIPS_BY_OP)
);
/** Type of a block that has CONTROLS_IF_MUTATOR_MIXIN */
type IfBlock = Block & IfMixin;
interface IfMixin extends IfMixinType {}
type IfMixinType = typeof CONTROLS_IF_MUTATOR_MIXIN;
// Types for quarks defined in JSON.
/** Type of a controls_if_if (if mutator container) block. */
interface ContainerBlock extends Block {}
/** Type of a controls_if_elseif or controls_if_else block. */
interface ClauseBlock extends Block {
valueConnection_?: Connection | null;
statementConnection_?: Connection | null;
}
/** Extra state for serialising controls_if blocks. */
type IfExtraState = {
elseIfCount?: number;
hasElse?: boolean;
};
/** /**
* Mutator methods added to controls_if blocks. * Mutator methods added to controls_if blocks.
* @mixin
* @augments Block
* @readonly
*/ */
const CONTROLS_IF_MUTATOR_MIXIN = { const CONTROLS_IF_MUTATOR_MIXIN = {
elseifCount_: 0, elseifCount_: 0,
@@ -306,39 +310,39 @@ const CONTROLS_IF_MUTATOR_MIXIN = {
/** /**
* Create XML to represent the number of else-if and else inputs. * Create XML to represent the number of else-if and else inputs.
* Backwards compatible serialization implementation. * Backwards compatible serialization implementation.
* @return {Element} XML storage element. *
* @this {Block} * @returns XML storage element.
*/ */
mutationToDom: function() { mutationToDom: function (this: IfBlock): Element | null {
if (!this.elseifCount_ && !this.elseCount_) { if (!this.elseifCount_ && !this.elseCount_) {
return null; return null;
} }
const container = xmlUtils.createElement('mutation'); const container = xmlUtils.createElement('mutation');
if (this.elseifCount_) { if (this.elseifCount_) {
container.setAttribute('elseif', this.elseifCount_); container.setAttribute('elseif', String(this.elseifCount_));
} }
if (this.elseCount_) { if (this.elseCount_) {
container.setAttribute('else', 1); container.setAttribute('else', '1');
} }
return container; return container;
}, },
/** /**
* Parse XML to restore the else-if and else inputs. * Parse XML to restore the else-if and else inputs.
* Backwards compatible serialization implementation. * Backwards compatible serialization implementation.
* @param {!Element} xmlElement XML storage element. *
* @this {Block} * @param xmlElement XML storage element.
*/ */
domToMutation: function(xmlElement) { domToMutation: function (this: IfBlock, xmlElement: Element) {
this.elseifCount_ = parseInt(xmlElement.getAttribute('elseif'), 10) || 0; this.elseifCount_ = parseInt(xmlElement.getAttribute('elseif')!, 10) || 0;
this.elseCount_ = parseInt(xmlElement.getAttribute('else'), 10) || 0; this.elseCount_ = parseInt(xmlElement.getAttribute('else')!, 10) || 0;
this.rebuildShape_(); this.rebuildShape_();
}, },
/** /**
* Returns the state of this block as a JSON serializable object. * Returns the state of this block as a JSON serializable object.
* @return {?{elseIfCount: (number|undefined), haseElse: (boolean|undefined)}} *
* The state of this block, ie the else if count and else state. * @returns The state of this block, ie the else if count and else state.
*/ */
saveExtraState: function() { saveExtraState: function (this: IfBlock): IfExtraState | null {
if (!this.elseifCount_ && !this.elseCount_) { if (!this.elseifCount_ && !this.elseCount_) {
return null; return null;
} }
@@ -353,86 +357,102 @@ const CONTROLS_IF_MUTATOR_MIXIN = {
}, },
/** /**
* Applies the given state to this block. * Applies the given state to this block.
* @param {*} state The state to apply to this block, ie the else if count and *
* @param state The state to apply to this block, ie the else if count
and
* else state. * else state.
*/ */
loadExtraState: function(state) { loadExtraState: function (this: IfBlock, state: IfExtraState) {
this.elseifCount_ = state['elseIfCount'] || 0; this.elseifCount_ = state['elseIfCount'] || 0;
this.elseCount_ = state['hasElse'] ? 1 : 0; this.elseCount_ = state['hasElse'] ? 1 : 0;
this.updateShape_(); this.updateShape_();
}, },
/** /**
* Populate the mutator's dialog with this block's components. * Populate the mutator's dialog with this block's components.
* @param {!Workspace} workspace Mutator's workspace. *
* @return {!Block} Root block in mutator. * @param workspace MutatorIcon's workspace.
* @this {Block} * @returns Root block in mutator.
*/ */
decompose: function(workspace) { decompose: function (this: IfBlock, workspace: Workspace): ContainerBlock {
const containerBlock = workspace.newBlock('controls_if_if'); const containerBlock = workspace.newBlock('controls_if_if');
containerBlock.initSvg(); (containerBlock as BlockSvg).initSvg();
let connection = containerBlock.nextConnection; let connection = containerBlock.nextConnection!;
for (let i = 1; i <= this.elseifCount_; i++) { for (let i = 1; i <= this.elseifCount_; i++) {
const elseifBlock = workspace.newBlock('controls_if_elseif'); const elseifBlock = workspace.newBlock('controls_if_elseif');
elseifBlock.initSvg(); (elseifBlock as BlockSvg).initSvg();
connection.connect(elseifBlock.previousConnection); connection.connect(elseifBlock.previousConnection!);
connection = elseifBlock.nextConnection; connection = elseifBlock.nextConnection!;
} }
if (this.elseCount_) { if (this.elseCount_) {
const elseBlock = workspace.newBlock('controls_if_else'); const elseBlock = workspace.newBlock('controls_if_else');
elseBlock.initSvg(); (elseBlock as BlockSvg).initSvg();
connection.connect(elseBlock.previousConnection); connection.connect(elseBlock.previousConnection!);
} }
return containerBlock; return containerBlock;
}, },
/** /**
* Reconfigure this block based on the mutator dialog's components. * Reconfigure this block based on the mutator dialog's components.
* @param {!Block} containerBlock Root block in mutator. *
* @this {Block} * @param containerBlock Root block in mutator.
*/ */
compose: function(containerBlock) { compose: function (this: IfBlock, containerBlock: ContainerBlock) {
let clauseBlock = containerBlock.nextConnection.targetBlock(); let clauseBlock =
containerBlock.nextConnection!.targetBlock() as ClauseBlock | null;
// Count number of inputs. // Count number of inputs.
this.elseifCount_ = 0; this.elseifCount_ = 0;
this.elseCount_ = 0; this.elseCount_ = 0;
const valueConnections = [null]; // Connections arrays are passed to .reconnectChildBlocks_() which
const statementConnections = [null]; // takes 1-based arrays, so are initialised with a dummy value at
let elseStatementConnection = null; // index 0 for convenience.
const valueConnections: Array<Connection | null> = [null];
const statementConnections: Array<Connection | null> = [null];
let elseStatementConnection: Connection | null = null;
while (clauseBlock) { while (clauseBlock) {
if (clauseBlock.isInsertionMarker()) { if (clauseBlock.isInsertionMarker()) {
clauseBlock = clauseBlock.getNextBlock(); clauseBlock = clauseBlock.getNextBlock() as ClauseBlock | null;
continue; continue;
} }
switch (clauseBlock.type) { switch (clauseBlock.type) {
case 'controls_if_elseif': case 'controls_if_elseif':
this.elseifCount_++; this.elseifCount_++;
valueConnections.push(clauseBlock.valueConnection_); // TODO(#6920): null valid, undefined not.
statementConnections.push(clauseBlock.statementConnection_); valueConnections.push(
clauseBlock.valueConnection_ as Connection | null
);
statementConnections.push(
clauseBlock.statementConnection_ as Connection | null
);
break; break;
case 'controls_if_else': case 'controls_if_else':
this.elseCount_++; this.elseCount_++;
elseStatementConnection = clauseBlock.statementConnection_; elseStatementConnection =
clauseBlock.statementConnection_ as Connection | null;
break; break;
default: default:
throw TypeError('Unknown block type: ' + clauseBlock.type); throw TypeError('Unknown block type: ' + clauseBlock.type);
} }
clauseBlock = clauseBlock.getNextBlock(); clauseBlock = clauseBlock.getNextBlock() as ClauseBlock | null;
} }
this.updateShape_(); this.updateShape_();
// Reconnect any child blocks. // Reconnect any child blocks.
this.reconnectChildBlocks_( this.reconnectChildBlocks_(
valueConnections, statementConnections, elseStatementConnection); valueConnections,
statementConnections,
elseStatementConnection
);
}, },
/** /**
* Store pointers to any connected child blocks. * Store pointers to any connected child blocks.
* @param {!Block} containerBlock Root block in mutator. *
* @this {Block} * @param containerBlock Root block in mutator.
*/ */
saveConnections: function(containerBlock) { saveConnections: function (this: IfBlock, containerBlock: ContainerBlock) {
let clauseBlock = containerBlock.nextConnection.targetBlock(); let clauseBlock =
containerBlock!.nextConnection!.targetBlock() as ClauseBlock | null;
let i = 1; let i = 1;
while (clauseBlock) { while (clauseBlock) {
if (clauseBlock.isInsertionMarker()) { if (clauseBlock.isInsertionMarker()) {
clauseBlock = clauseBlock.getNextBlock(); clauseBlock = clauseBlock.getNextBlock() as ClauseBlock | null;
continue; continue;
} }
switch (clauseBlock.type) { switch (clauseBlock.type) {
@@ -440,53 +460,53 @@ const CONTROLS_IF_MUTATOR_MIXIN = {
const inputIf = this.getInput('IF' + i); const inputIf = this.getInput('IF' + i);
const inputDo = this.getInput('DO' + i); const inputDo = this.getInput('DO' + i);
clauseBlock.valueConnection_ = clauseBlock.valueConnection_ =
inputIf && inputIf.connection.targetConnection; inputIf && inputIf.connection!.targetConnection;
clauseBlock.statementConnection_ = clauseBlock.statementConnection_ =
inputDo && inputDo.connection.targetConnection; inputDo && inputDo.connection!.targetConnection;
i++; i++;
break; break;
} }
case 'controls_if_else': { case 'controls_if_else': {
const inputDo = this.getInput('ELSE'); const inputDo = this.getInput('ELSE');
clauseBlock.statementConnection_ = clauseBlock.statementConnection_ =
inputDo && inputDo.connection.targetConnection; inputDo && inputDo.connection!.targetConnection;
break; break;
} }
default: default:
throw TypeError('Unknown block type: ' + clauseBlock.type); throw TypeError('Unknown block type: ' + clauseBlock.type);
} }
clauseBlock = clauseBlock.getNextBlock(); clauseBlock = clauseBlock.getNextBlock() as ClauseBlock | null;
} }
}, },
/** /**
* Reconstructs the block with all child blocks attached. * Reconstructs the block with all child blocks attached.
* @this {Block}
*/ */
rebuildShape_: function() { rebuildShape_: function (this: IfBlock) {
const valueConnections = [null]; const valueConnections: Array<Connection | null> = [null];
const statementConnections = [null]; const statementConnections: Array<Connection | null> = [null];
let elseStatementConnection = null; let elseStatementConnection: Connection | null = null;
if (this.getInput('ELSE')) { if (this.getInput('ELSE')) {
elseStatementConnection = elseStatementConnection =
this.getInput('ELSE').connection.targetConnection; this.getInput('ELSE')!.connection!.targetConnection;
} }
for (let i = 1; this.getInput('IF' + i); i++) { for (let i = 1; this.getInput('IF' + i); i++) {
const inputIf = this.getInput('IF' + i); const inputIf = this.getInput('IF' + i);
const inputDo = this.getInput('DO' + i); const inputDo = this.getInput('DO' + i);
valueConnections.push(inputIf.connection.targetConnection); valueConnections.push(inputIf!.connection!.targetConnection);
statementConnections.push(inputDo.connection.targetConnection); statementConnections.push(inputDo!.connection!.targetConnection);
} }
this.updateShape_(); this.updateShape_();
this.reconnectChildBlocks_( this.reconnectChildBlocks_(
valueConnections, statementConnections, elseStatementConnection); valueConnections,
statementConnections,
elseStatementConnection
);
}, },
/** /**
* Modify this block to have the correct number of inputs. * Modify this block to have the correct number of inputs.
* @this {Block}
* @private
*/ */
updateShape_: function() { updateShape_: function (this: IfBlock) {
// Delete everything. // Delete everything.
if (this.getInput('ELSE')) { if (this.getInput('ELSE')) {
this.removeInput('ELSE'); this.removeInput('ELSE');
@@ -497,28 +517,34 @@ const CONTROLS_IF_MUTATOR_MIXIN = {
} }
// Rebuild block. // Rebuild block.
for (let i = 1; i <= this.elseifCount_; i++) { for (let i = 1; i <= this.elseifCount_; i++) {
this.appendValueInput('IF' + i).setCheck('Boolean').appendField( this.appendValueInput('IF' + i)
Msg['CONTROLS_IF_MSG_ELSEIF']); .setCheck('Boolean')
.appendField(Msg['CONTROLS_IF_MSG_ELSEIF']);
this.appendStatementInput('DO' + i).appendField( this.appendStatementInput('DO' + i).appendField(
Msg['CONTROLS_IF_MSG_THEN']); Msg['CONTROLS_IF_MSG_THEN']
);
} }
if (this.elseCount_) { if (this.elseCount_) {
this.appendStatementInput('ELSE').appendField( this.appendStatementInput('ELSE').appendField(
Msg['CONTROLS_IF_MSG_ELSE']); Msg['CONTROLS_IF_MSG_ELSE']
);
} }
}, },
/** /**
* Reconnects child blocks. * Reconnects child blocks.
* @param {!Array<?RenderedConnection>} valueConnections List of *
* value connections for 'if' input. * @param valueConnections 1-based array of value connections for
* @param {!Array<?RenderedConnection>} statementConnections List of * 'if' input. Value at index [0] ignored.
* statement connections for 'do' input. * @param statementConnections 1-based array of statement
* @param {?RenderedConnection} elseStatementConnection Statement * connections for 'do' input. Value at index [0] ignored.
* connection for else input. * @param elseStatementConnection Statement connection for else input.
* @this {Block}
*/ */
reconnectChildBlocks_: function( reconnectChildBlocks_: function (
valueConnections, statementConnections, elseStatementConnection) { this: IfBlock,
valueConnections: Array<Connection | null>,
statementConnections: Array<Connection | null>,
elseStatementConnection: Connection | null
) {
for (let i = 1; i <= this.elseifCount_; i++) { for (let i = 1; i <= this.elseifCount_; i++) {
valueConnections[i]?.reconnect(this, 'IF' + i); valueConnections[i]?.reconnect(this, 'IF' + i);
statementConnections[i]?.reconnect(this, 'DO' + i); statementConnections[i]?.reconnect(this, 'DO' + i);
@@ -528,55 +554,70 @@ const CONTROLS_IF_MUTATOR_MIXIN = {
}; };
Extensions.registerMutator( Extensions.registerMutator(
'controls_if_mutator', CONTROLS_IF_MUTATOR_MIXIN, null, 'controls_if_mutator',
['controls_if_elseif', 'controls_if_else']); CONTROLS_IF_MUTATOR_MIXIN,
null as unknown as undefined, // TODO(#6920)
['controls_if_elseif', 'controls_if_else']
);
/** /**
* "controls_if" extension function. Adds mutator, shape updating methods, and * "controls_if" extension function. Adds mutator, shape updating methods,
* dynamic tooltip to "controls_if" blocks. * and dynamic tooltip to "controls_if" blocks.
* @this {Block}
*/ */
const CONTROLS_IF_TOOLTIP_EXTENSION = function() { const CONTROLS_IF_TOOLTIP_EXTENSION = function (this: IfBlock) {
this.setTooltip(function() { this.setTooltip(
if (!this.elseifCount_ && !this.elseCount_) { function (this: IfBlock) {
return Msg['CONTROLS_IF_TOOLTIP_1']; if (!this.elseifCount_ && !this.elseCount_) {
} else if (!this.elseifCount_ && this.elseCount_) { return Msg['CONTROLS_IF_TOOLTIP_1'];
return Msg['CONTROLS_IF_TOOLTIP_2']; } else if (!this.elseifCount_ && this.elseCount_) {
} else if (this.elseifCount_ && !this.elseCount_) { return Msg['CONTROLS_IF_TOOLTIP_2'];
return Msg['CONTROLS_IF_TOOLTIP_3']; } else if (this.elseifCount_ && !this.elseCount_) {
} else if (this.elseifCount_ && this.elseCount_) { return Msg['CONTROLS_IF_TOOLTIP_3'];
return Msg['CONTROLS_IF_TOOLTIP_4']; } else if (this.elseifCount_ && this.elseCount_) {
} return Msg['CONTROLS_IF_TOOLTIP_4'];
return ''; }
}.bind(this)); return '';
}.bind(this)
);
}; };
Extensions.register('controls_if_tooltip', CONTROLS_IF_TOOLTIP_EXTENSION); Extensions.register('controls_if_tooltip', CONTROLS_IF_TOOLTIP_EXTENSION);
/** Type of a block that has LOGIC_COMPARE_ONCHANGE_MIXIN */
type CompareBlock = Block & CompareMixin;
interface CompareMixin extends CompareMixinType {
prevBlocks_?: Array<Block | null>;
}
type CompareMixinType = typeof LOGIC_COMPARE_ONCHANGE_MIXIN;
/** /**
* Adds dynamic type validation for the left and right sides of a logic_compare * Adds dynamic type validation for the left and right sides of a
* block. * logic_compare block.
* @mixin
* @augments Block
* @readonly
*/ */
const LOGIC_COMPARE_ONCHANGE_MIXIN = { const LOGIC_COMPARE_ONCHANGE_MIXIN = {
/** /**
* Called whenever anything on the workspace changes. * Called whenever anything on the workspace changes.
* Prevent mismatched types from being compared. * Prevent mismatched types from being compared.
* @param {!AbstractEvent} e Change event. *
* @this {Block} * @param e Change event.
*/ */
onchange: function(e) { onchange: function (this: CompareBlock, e: AbstractEvent) {
if (!this.prevBlocks_) { if (!this.prevBlocks_) {
this.prevBlocks_ = [null, null]; this.prevBlocks_ = [null, null];
} }
const blockA = this.getInputTargetBlock('A'); const blockA = this.getInputTargetBlock('A');
const blockB = this.getInputTargetBlock('B'); const blockB = this.getInputTargetBlock('B');
// Disconnect blocks that existed prior to this change if they don't match. // Disconnect blocks that existed prior to this change if they don't
if (blockA && blockB && // match.
!this.workspace.connectionChecker.doTypeChecks( if (
blockA.outputConnection, blockB.outputConnection)) { blockA &&
blockB &&
!this.workspace.connectionChecker.doTypeChecks(
blockA.outputConnection!,
blockB.outputConnection!
)
) {
// Mismatch between two inputs. Revert the block connections, // Mismatch between two inputs. Revert the block connections,
// bumping away the newly connected block(s). // bumping away the newly connected block(s).
Events.setGroup(e.group); Events.setGroup(e.group);
@@ -585,7 +626,7 @@ const LOGIC_COMPARE_ONCHANGE_MIXIN = {
blockA.unplug(); blockA.unplug();
if (prevA && !prevA.isDisposed() && !prevA.isShadow()) { if (prevA && !prevA.isDisposed() && !prevA.isShadow()) {
// The shadow block is automatically replaced during unplug(). // The shadow block is automatically replaced during unplug().
this.getInput('A').connection.connect(prevA.outputConnection); this.getInput('A')!.connection!.connect(prevA.outputConnection!);
} }
} }
const prevB = this.prevBlocks_[1]; const prevB = this.prevBlocks_[1];
@@ -593,7 +634,7 @@ const LOGIC_COMPARE_ONCHANGE_MIXIN = {
blockB.unplug(); blockB.unplug();
if (prevB && !prevB.isDisposed() && !prevB.isShadow()) { if (prevB && !prevB.isDisposed() && !prevB.isShadow()) {
// The shadow block is automatically replaced during unplug(). // The shadow block is automatically replaced during unplug().
this.getInput('B').connection.connect(prevB.outputConnection); this.getInput('B')!.connection!.connect(prevB.outputConnection!);
} }
} }
this.bumpNeighbours(); this.bumpNeighbours();
@@ -607,43 +648,47 @@ const LOGIC_COMPARE_ONCHANGE_MIXIN = {
/** /**
* "logic_compare" extension function. Adds type left and right side type * "logic_compare" extension function. Adds type left and right side type
* checking to "logic_compare" blocks. * checking to "logic_compare" blocks.
* @this {Block}
* @readonly
*/ */
const LOGIC_COMPARE_EXTENSION = function() { const LOGIC_COMPARE_EXTENSION = function (this: CompareBlock) {
// Add onchange handler to ensure types are compatible. // Add onchange handler to ensure types are compatible.
this.mixin(LOGIC_COMPARE_ONCHANGE_MIXIN); this.mixin(LOGIC_COMPARE_ONCHANGE_MIXIN);
}; };
Extensions.register('logic_compare', LOGIC_COMPARE_EXTENSION); Extensions.register('logic_compare', LOGIC_COMPARE_EXTENSION);
/** Type of a block that has LOGIC_TERNARY_ONCHANGE_MIXIN */
type TernaryBlock = Block & TernaryMixin;
interface TernaryMixin extends TernaryMixinType {}
type TernaryMixinType = typeof LOGIC_TERNARY_ONCHANGE_MIXIN;
/** /**
* Adds type coordination between inputs and output. * Adds type coordination between inputs and output.
* @mixin
* @augments Block
* @readonly
*/ */
const LOGIC_TERNARY_ONCHANGE_MIXIN = { const LOGIC_TERNARY_ONCHANGE_MIXIN = {
prevParentConnection_: null, prevParentConnection_: null as Connection | null,
/** /**
* Called whenever anything on the workspace changes. * Called whenever anything on the workspace changes.
* Prevent mismatched types. * Prevent mismatched types.
* @param {!AbstractEvent} e Change event.
* @this {Block}
*/ */
onchange: function(e) { onchange: function (this: TernaryBlock, e: AbstractEvent) {
const blockA = this.getInputTargetBlock('THEN'); const blockA = this.getInputTargetBlock('THEN');
const blockB = this.getInputTargetBlock('ELSE'); const blockB = this.getInputTargetBlock('ELSE');
const parentConnection = this.outputConnection.targetConnection; const parentConnection = this.outputConnection!.targetConnection;
// Disconnect blocks that existed prior to this change if they don't match. // Disconnect blocks that existed prior to this change if they don't
// match.
if ((blockA || blockB) && parentConnection) { if ((blockA || blockB) && parentConnection) {
for (let i = 0; i < 2; i++) { for (let i = 0; i < 2; i++) {
const block = (i === 1) ? blockA : blockB; const block = i === 1 ? blockA : blockB;
if (block && if (
!block.workspace.connectionChecker.doTypeChecks( block &&
block.outputConnection, parentConnection)) { !block.workspace.connectionChecker.doTypeChecks(
// Ensure that any disconnections are grouped with the causing event. block.outputConnection!,
parentConnection
)
) {
// Ensure that any disconnections are grouped with the causing
// event.
Events.setGroup(e.group); Events.setGroup(e.group);
if (parentConnection === this.prevParentConnection_) { if (parentConnection === this.prevParentConnection_) {
this.unplug(); this.unplug();