refactor(blocks): Migrate blocks/loops.js to TypeScript (#6957)

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

* fix(blocks): Manually migrate types and fix imports in loops.ts

* chore: respond to PR comments
This commit is contained in:
Rachel Fenichel
2023-04-18 15:58:21 -07:00
committed by GitHub
parent 0177dff49c
commit a4d0b67985

View File

@@ -6,44 +6,33 @@
/** /**
* @fileoverview Loop blocks for Blockly. * @fileoverview Loop blocks for Blockly.
* @suppress {checkTypes}
*/ */
'use strict';
goog.module('Blockly.libraryBlocks.loops'); import * as goog from '../closure/goog/goog.js';
goog.declareModuleId('Blockly.libraryBlocks.loops');
/* eslint-disable-next-line no-unused-vars */ import type {Abstract as AbstractEvent} from '../core/events/events_abstract.js';
const AbstractEvent = goog.requireType('Blockly.Events.Abstract'); import type {Block} from '../core/block.js';
const ContextMenu = goog.require('Blockly.ContextMenu'); import * as ContextMenu from '../core/contextmenu.js';
const Events = goog.require('Blockly.Events'); import * as Events from '../core/events/events.js';
const Extensions = goog.require('Blockly.Extensions'); import * as Extensions from '../core/extensions.js';
const Variables = goog.require('Blockly.Variables'); import * as Variables from '../core/variables.js';
const xmlUtils = goog.require('Blockly.utils.xml'); import * as xmlUtils from '../core/utils/xml.js';
/* eslint-disable-next-line no-unused-vars */ import {Msg} from '../core/msg.js';
const {Block} = goog.requireType('Blockly.Block'); import {createBlockDefinitionsFromJsonArray, defineBlocks} from '../core/common.js';
// const {BlockDefinition} = goog.requireType('Blockly.blocks'); import '../core/field_dropdown.js';
// TODO (6248): Properly import the BlockDefinition type. import '../core/field_label.js';
/* eslint-disable-next-line no-unused-vars */ import '../core/field_number.js';
const BlockDefinition = Object; import '../core/field_variable.js';
const {Msg} = goog.require('Blockly.Msg'); import '../core/warning.js';
const {createBlockDefinitionsFromJsonArray, defineBlocks} = goog.require('Blockly.common'); import {FieldVariable} from '../core/field_variable.js';
/** @suppress {extraRequire} */ import {WorkspaceSvg} from '../core/workspace_svg.js';
goog.require('Blockly.FieldDropdown');
/** @suppress {extraRequire} */
goog.require('Blockly.FieldLabel');
/** @suppress {extraRequire} */
goog.require('Blockly.FieldNumber');
/** @suppress {extraRequire} */
goog.require('Blockly.FieldVariable');
/** @suppress {extraRequire} */
goog.require('Blockly.Warning');
/** /**
* 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 repeat n times (external number). // Block for repeat n times (external number).
{ {
'type': 'controls_repeat_ext', 'type': 'controls_repeat_ext',
@@ -213,12 +202,11 @@ const blocks = createBlockDefinitionsFromJsonArray([
], ],
}, },
]); ]);
exports.blocks = blocks;
/** /**
* Tooltips for the 'controls_whileUntil' block, keyed by MODE value. * Tooltips for the 'controls_whileUntil' block, keyed by MODE value.
*
* @see {Extensions#buildTooltipForDropdown} * @see {Extensions#buildTooltipForDropdown}
* @readonly
*/ */
const WHILE_UNTIL_TOOLTIPS = { const WHILE_UNTIL_TOOLTIPS = {
'WHILE': '%{BKY_CONTROLS_WHILEUNTIL_TOOLTIP_WHILE}', 'WHILE': '%{BKY_CONTROLS_WHILEUNTIL_TOOLTIP_WHILE}',
@@ -231,8 +219,8 @@ Extensions.register(
/** /**
* Tooltips for the 'controls_flow_statements' block, keyed by FLOW value. * Tooltips for the 'controls_flow_statements' block, keyed by FLOW value.
*
* @see {Extensions#buildTooltipForDropdown} * @see {Extensions#buildTooltipForDropdown}
* @readonly
*/ */
const BREAK_CONTINUE_TOOLTIPS = { const BREAK_CONTINUE_TOOLTIPS = {
'BREAK': '%{BKY_CONTROLS_FLOW_STATEMENTS_TOOLTIP_BREAK}', 'BREAK': '%{BKY_CONTROLS_FLOW_STATEMENTS_TOOLTIP_BREAK}',
@@ -243,36 +231,42 @@ Extensions.register(
'controls_flow_tooltip', 'controls_flow_tooltip',
Extensions.buildTooltipForDropdown('FLOW', BREAK_CONTINUE_TOOLTIPS)); Extensions.buildTooltipForDropdown('FLOW', BREAK_CONTINUE_TOOLTIPS));
/** Type of a block that has CUSTOM_CONTEXT_MENU_CREATE_VARIABLES_GET_MIXIN */
type CustomContextMenuBlock = Block&CustomContextMenuMixin;
interface CustomContextMenuMixin extends CustomContextMenuMixinType {}
type CustomContextMenuMixinType =
typeof CUSTOM_CONTEXT_MENU_CREATE_VARIABLES_GET_MIXIN;
/** /**
* Mixin to add a context menu item to create a 'variables_get' block. * Mixin to add a context menu item to create a 'variables_get' block.
* Used by blocks 'controls_for' and 'controls_forEach'. * Used by blocks 'controls_for' and 'controls_forEach'.
* @mixin
* @augments Block
* @package
* @readonly
*/ */
const CUSTOM_CONTEXT_MENU_CREATE_VARIABLES_GET_MIXIN = { const CUSTOM_CONTEXT_MENU_CREATE_VARIABLES_GET_MIXIN = {
/** /**
* Add context menu option to create getter block for the loop's variable. * Add context menu option to create getter block for the loop's variable.
* (customContextMenu support limited to web BlockSvg.) * (customContextMenu support limited to web BlockSvg.)
* @param {!Array} options List of menu options to add to. *
* @this {Block} * @param options List of menu options to add to.
*/ */
customContextMenu: function(options) { customContextMenu: function(
this: CustomContextMenuBlock, options: Array<any>) {
if (this.isInFlyout) { if (this.isInFlyout) {
return; return;
} }
const variable = this.getField('VAR').getVariable(); const varField = this.getField('VAR') as FieldVariable;
const variable = varField.getVariable()!;
const varName = variable.name; const varName = variable.name;
if (!this.isCollapsed() && varName !== null) { if (!this.isCollapsed() && varName !== null) {
const option = {enabled: true};
option.text = Msg['VARIABLES_SET_CREATE_GET'].replace('%1', varName);
const xmlField = Variables.generateVariableFieldDom(variable); const xmlField = Variables.generateVariableFieldDom(variable);
const xmlBlock = xmlUtils.createElement('block'); const xmlBlock = xmlUtils.createElement('block');
xmlBlock.setAttribute('type', 'variables_get'); xmlBlock.setAttribute('type', 'variables_get');
xmlBlock.appendChild(xmlField); xmlBlock.appendChild(xmlField);
option.callback = ContextMenu.callbackFactory(this, xmlBlock);
options.push(option); options.push({
enabled: true,
text: Msg['VARIABLES_SET_CREATE_GET'].replace('%1', varName),
callback: ContextMenu.callbackFactory(this, xmlBlock)
});
} }
}, },
}; };
@@ -304,34 +298,32 @@ Extensions.register(
* *
* // Else if using blockly_compressed + blockss_compressed.js in browser: * // Else if using blockly_compressed + blockss_compressed.js in browser:
* Blockly.libraryBlocks.loopTypes.add('custom_loop'); * Blockly.libraryBlocks.loopTypes.add('custom_loop');
*
* @type {!Set<string>}
*/ */
const loopTypes = new Set([ export const loopTypes: Set<string> = new Set([
'controls_repeat', 'controls_repeat',
'controls_repeat_ext', 'controls_repeat_ext',
'controls_forEach', 'controls_forEach',
'controls_for', 'controls_for',
'controls_whileUntil', 'controls_whileUntil',
]); ]);
exports.loopTypes = loopTypes;
/** Type of a block that has CONTROL_FLOW_IN_LOOP_CHECK_MIXIN */
type ControlFlowInLoopBlock = Block&ControlFlowInLoopMixin;
interface ControlFlowInLoopMixin extends ControlFlowInLoopMixinType {}
type ControlFlowInLoopMixinType = typeof CONTROL_FLOW_IN_LOOP_CHECK_MIXIN;
/** /**
* This mixin adds a check to make sure the 'controls_flow_statements' block * This mixin adds a check to make sure the 'controls_flow_statements' block
* is contained in a loop. Otherwise a warning is added to the block. * is contained in a loop. Otherwise a warning is added to the block.
* @mixin
* @augments Block
* @public
* @readonly
*/ */
const CONTROL_FLOW_IN_LOOP_CHECK_MIXIN = { const CONTROL_FLOW_IN_LOOP_CHECK_MIXIN = {
/** /**
* Is this block enclosed (at any level) by a loop? * Is this block enclosed (at any level) by a loop?
* @return {Block} The nearest surrounding loop, or null if none. *
* @this {Block} * @returns The nearest surrounding loop, or null if none.
*/ */
getSurroundLoop: function() { getSurroundLoop: function(this: ControlFlowInLoopBlock): Block | null {
let block = this; let block: Block|null = this;
do { do {
if (loopTypes.has(block.type)) { if (loopTypes.has(block.type)) {
return block; return block;
@@ -344,18 +336,16 @@ const CONTROL_FLOW_IN_LOOP_CHECK_MIXIN = {
/** /**
* Called whenever anything on the workspace changes. * Called whenever anything on the workspace changes.
* Add warning if this flow block is not nested inside a loop. * Add warning if this flow block is not nested inside a loop.
* @param {!AbstractEvent} e Move event.
* @this {Block}
*/ */
onchange: function(e) { onchange: function(this: ControlFlowInLoopBlock, e: AbstractEvent) {
const ws = this.workspace as WorkspaceSvg;
// Don't change state if: // Don't change state if:
// * It's at the start of a drag. // * It's at the start of a drag.
// * It's not a move event. // * It's not a move event.
if (!this.workspace.isDragging || this.workspace.isDragging() || if (!ws.isDragging || ws.isDragging() || e.type !== Events.BLOCK_MOVE) {
e.type !== Events.BLOCK_MOVE) {
return; return;
} }
const enabled = this.getSurroundLoop(this); const enabled = !!this.getSurroundLoop();
this.setWarningText( this.setWarningText(
enabled ? null : Msg['CONTROLS_FLOW_STATEMENTS_WARNING']); enabled ? null : Msg['CONTROLS_FLOW_STATEMENTS_WARNING']);
if (!this.isInFlyout) { if (!this.isInFlyout) {