From 96ecfea8574f0ac71da8f10a7fa48b098e4876e3 Mon Sep 17 00:00:00 2001 From: Rachel Fenichel Date: Mon, 24 Apr 2023 13:09:02 -0700 Subject: [PATCH] refactor(blocks): Migrate `blocks/variables.js` and `blocks/variables_dynamic.js` to TypeScript (#7001) * refactor: run js2ts on variables * refactor: clean up conversion of variables blocks to typescript * refactor: add types for custom context menu options * refactor: run js2ts on blocks/variables_dynamic.js * refactor: clean up types in variables_dynamic * chore: respond to PR comments * chore: format --- blocks/loops.ts | 4 +- blocks/{variables.js => variables.ts} | 88 ++++++++------- ...iables_dynamic.js => variables_dynamic.ts} | 105 +++++++++--------- core/contextmenu.ts | 2 +- 4 files changed, 103 insertions(+), 96 deletions(-) rename blocks/{variables.js => variables.ts} (61%) rename blocks/{variables_dynamic.js => variables_dynamic.ts} (61%) diff --git a/blocks/loops.ts b/blocks/loops.ts index 605128c64..64e011d97 100644 --- a/blocks/loops.ts +++ b/blocks/loops.ts @@ -14,6 +14,7 @@ goog.declareModuleId('Blockly.libraryBlocks.loops'); import type {Abstract as AbstractEvent} from '../core/events/events_abstract.js'; import type {Block} from '../core/block.js'; import * as ContextMenu from '../core/contextmenu.js'; +import type {ContextMenuOption, LegacyContextMenuOption} from '../core/contextmenu_registry.js'; import * as Events from '../core/events/events.js'; import * as Extensions from '../core/extensions.js'; import * as Variables from '../core/variables.js'; @@ -249,7 +250,8 @@ const CUSTOM_CONTEXT_MENU_CREATE_VARIABLES_GET_MIXIN = { * @param options List of menu options to add to. */ customContextMenu: function( - this: CustomContextMenuBlock, options: Array) { + this: CustomContextMenuBlock, + options: Array) { if (this.isInFlyout) { return; } diff --git a/blocks/variables.js b/blocks/variables.ts similarity index 61% rename from blocks/variables.js rename to blocks/variables.ts index a3bbd1a8d..97c53fbb9 100644 --- a/blocks/variables.js +++ b/blocks/variables.ts @@ -8,33 +8,27 @@ * @fileoverview Variable blocks for Blockly. * @suppress {checkTypes} */ -'use strict'; -goog.module('Blockly.libraryBlocks.variables'); +import * as goog from '../closure/goog/goog.js'; +goog.declareModuleId('Blockly.libraryBlocks.variables'); -const ContextMenu = goog.require('Blockly.ContextMenu'); -const Extensions = goog.require('Blockly.Extensions'); -const Variables = goog.require('Blockly.Variables'); -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'); -const {createBlockDefinitionsFromJsonArray, defineBlocks} = goog.require('Blockly.common'); -/** @suppress {extraRequire} */ -goog.require('Blockly.FieldLabel'); -/** @suppress {extraRequire} */ -goog.require('Blockly.FieldVariable'); +import * as ContextMenu from '../core/contextmenu.js'; +import * as Extensions from '../core/extensions.js'; +import * as Variables from '../core/variables.js'; +import * as xmlUtils from '../core/utils/xml.js'; +import type {Block} from '../core/block.js'; +import type {ContextMenuOption, LegacyContextMenuOption} from '../core/contextmenu_registry.js'; +import {FieldVariable} from '../core/field_variable.js'; +import {Msg} from '../core/msg.js'; +import type {WorkspaceSvg} from '../core/workspace_svg.js'; +import {createBlockDefinitionsFromJsonArray, defineBlocks} from '../core/common.js'; +import '../core/field_label.js'; /** * A dictionary of the block definitions provided by this module. - * @type {!Object} */ -const blocks = createBlockDefinitionsFromJsonArray([ +export const blocks = createBlockDefinitionsFromJsonArray([ // Block for variable getter. { 'type': 'variables_get', @@ -75,25 +69,27 @@ const blocks = createBlockDefinitionsFromJsonArray([ 'extensions': ['contextMenu_variableSetterGetter'], }, ]); -exports.blocks = blocks; +/** Type of a block that has CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN */ +type VariableBlock = Block&VariableMixin; +interface VariableMixin extends VariableMixinType {} +type VariableMixinType = + typeof CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN; /** * Mixin to add context menu items to create getter/setter blocks for this * setter/getter. * Used by blocks 'variables_set' and 'variables_get'. - * @mixin - * @augments Block - * @package - * @readonly */ const CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN = { /** * Add menu option to create getter/setter block for this setter/getter. - * @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: VariableBlock, + options: Array) { if (!this.isInFlyout) { let oppositeType; let contextMenuMsg; @@ -106,17 +102,19 @@ const CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN = { contextMenuMsg = Msg['VARIABLES_SET_CREATE_GET']; } - const option = {enabled: this.workspace.remainingCapacity() > 0}; - const name = this.getField('VAR').getText(); - option.text = contextMenuMsg.replace('%1', name); + const name = this.getField('VAR')!.getText(); const xmlField = xmlUtils.createElement('field'); xmlField.setAttribute('name', 'VAR'); xmlField.appendChild(xmlUtils.createTextNode(name)); const xmlBlock = xmlUtils.createElement('block'); xmlBlock.setAttribute('type', oppositeType); xmlBlock.appendChild(xmlField); - option.callback = ContextMenu.callbackFactory(this, xmlBlock); - options.push(option); + + options.push({ + enabled: this.workspace.remainingCapacity() > 0, + text: contextMenuMsg.replace('%1', name), + callback: ContextMenu.callbackFactory(this, xmlBlock) + }); // Getter blocks have the option to rename or delete that variable. } else { if (this.type === 'variables_get' || @@ -126,7 +124,7 @@ const CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN = { enabled: true, callback: renameOptionCallbackFactory(this), }; - const name = this.getField('VAR').getText(); + const name = this.getField('VAR')!.getText(); const deleteOption = { text: Msg['DELETE_VARIABLE'].replace('%1', name), enabled: true, @@ -142,13 +140,15 @@ const CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN = { /** * Factory for callbacks for rename variable dropdown menu option * associated with a variable getter block. - * @param {!Block} block The block with the variable to rename. - * @return {!function()} A function that renames the variable. + * + * @param block The block with the variable to rename. + * @returns A function that renames the variable. */ -const renameOptionCallbackFactory = function(block) { +const renameOptionCallbackFactory = function(block: VariableBlock): () => void { return function() { const workspace = block.workspace; - const variable = block.getField('VAR').getVariable(); + const variableField = block.getField('VAR') as FieldVariable; + const variable = variableField.getVariable()!; Variables.renameVariable(workspace, variable); }; }; @@ -156,15 +156,17 @@ const renameOptionCallbackFactory = function(block) { /** * Factory for callbacks for delete variable dropdown menu option * associated with a variable getter block. - * @param {!Block} block The block with the variable to delete. - * @return {!function()} A function that deletes the variable. + * + * @param block The block with the variable to delete. + * @returns A function that deletes the variable. */ -const deleteOptionCallbackFactory = function(block) { +const deleteOptionCallbackFactory = function(block: VariableBlock): () => void { return function() { const workspace = block.workspace; - const variable = block.getField('VAR').getVariable(); + const variableField = block.getField('VAR') as FieldVariable; + const variable = variableField.getVariable()!; workspace.deleteVariableById(variable.getId()); - workspace.refreshToolboxSelection(); + (workspace as WorkspaceSvg).refreshToolboxSelection(); }; }; diff --git a/blocks/variables_dynamic.js b/blocks/variables_dynamic.ts similarity index 61% rename from blocks/variables_dynamic.js rename to blocks/variables_dynamic.ts index 4ee7dcadc..69b630d17 100644 --- a/blocks/variables_dynamic.js +++ b/blocks/variables_dynamic.ts @@ -8,35 +8,28 @@ * @fileoverview Variable blocks for Blockly. * @suppress {checkTypes} */ -'use strict'; -goog.module('Blockly.libraryBlocks.variablesDynamic'); +import * as goog from '../closure/goog/goog.js'; +goog.declareModuleId('Blockly.libraryBlocks.variablesDynamic'); -/* eslint-disable-next-line no-unused-vars */ -const AbstractEvent = goog.requireType('Blockly.Events.Abstract'); -const ContextMenu = goog.require('Blockly.ContextMenu'); -const Extensions = goog.require('Blockly.Extensions'); -const Variables = goog.require('Blockly.Variables'); -const xml = 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'); -const {createBlockDefinitionsFromJsonArray, defineBlocks} = goog.require('Blockly.common'); -/** @suppress {extraRequire} */ -goog.require('Blockly.FieldLabel'); -/** @suppress {extraRequire} */ -goog.require('Blockly.FieldVariable'); +import * as ContextMenu from '../core/contextmenu.js'; +import * as Extensions from '../core/extensions.js'; +import * as Variables from '../core/variables.js'; +import * as xml from '../core/utils/xml.js'; +import {Abstract as AbstractEvent} from '../core/events/events_abstract.js'; +import type {Block} from '../core/block.js'; +import type {ContextMenuOption, LegacyContextMenuOption} from '../core/contextmenu_registry.js'; +import {FieldVariable} from '../core/field_variable.js'; +import {Msg} from '../core/msg.js'; +import type {WorkspaceSvg} from '../core/workspace_svg.js'; +import {createBlockDefinitionsFromJsonArray, defineBlocks} from '../core/common.js'; +import '../core/field_label.js'; /** * A dictionary of the block definitions provided by this module. - * @type {!Object} */ -const blocks = createBlockDefinitionsFromJsonArray([ +export const blocks = createBlockDefinitionsFromJsonArray([ // Block for variable getter. { 'type': 'variables_get_dynamic', @@ -75,30 +68,34 @@ const blocks = createBlockDefinitionsFromJsonArray([ 'extensions': ['contextMenu_variableDynamicSetterGetter'], }, ]); -exports.blocks = blocks; + +/** Type of a block that has CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN */ +type VariableBlock = Block&VariableMixin; +interface VariableMixin extends VariableMixinType {} +type VariableMixinType = + typeof CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN; /** * Mixin to add context menu items to create getter/setter blocks for this * setter/getter. * Used by blocks 'variables_set_dynamic' and 'variables_get_dynamic'. - * @mixin - * @augments Block - * @readonly */ const CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN = { /** * Add menu option to create getter/setter block for this setter/getter. - * @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: VariableBlock, + options: Array) { // Getter blocks have the option to create a setter block, and vice versa. if (!this.isInFlyout) { let oppositeType; let contextMenuMsg; const id = this.getFieldValue('VAR'); const variableModel = this.workspace.getVariableById(id); - const varType = variableModel.type; + const varType = variableModel!.type; if (this.type === 'variables_get_dynamic') { oppositeType = 'variables_set_dynamic'; contextMenuMsg = Msg['VARIABLES_GET_CREATE_SET']; @@ -107,9 +104,7 @@ const CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN = { contextMenuMsg = Msg['VARIABLES_SET_CREATE_GET']; } - const option = {enabled: this.workspace.remainingCapacity() > 0}; - const name = this.getField('VAR').getText(); - option.text = contextMenuMsg.replace('%1', name); + const name = this.getField('VAR')!.getText(); const xmlField = xml.createElement('field'); xmlField.setAttribute('name', 'VAR'); xmlField.setAttribute('variabletype', varType); @@ -117,8 +112,12 @@ const CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN = { const xmlBlock = xml.createElement('block'); xmlBlock.setAttribute('type', oppositeType); xmlBlock.appendChild(xmlField); - option.callback = ContextMenu.callbackFactory(this, xmlBlock); - options.push(option); + + options.push({ + enabled: this.workspace.remainingCapacity() > 0, + text: contextMenuMsg.replace('%1', name), + callback: ContextMenu.callbackFactory(this, xmlBlock) + }); } else { if (this.type === 'variables_get_dynamic' || this.type === 'variables_get_reporter_dynamic') { @@ -127,7 +126,7 @@ const CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN = { enabled: true, callback: renameOptionCallbackFactory(this), }; - const name = this.getField('VAR').getText(); + const name = this.getField('VAR')!.getText(); const deleteOption = { text: Msg['DELETE_VARIABLE'].replace('%1', name), enabled: true, @@ -141,16 +140,16 @@ const CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN = { /** * Called whenever anything on the workspace changes. * Set the connection type for this block. - * @param {AbstractEvent} _e Change event. - * @this {Block} + * + * @param _e Change event. */ - onchange: function(_e) { + onchange: function(this: VariableBlock, _e: AbstractEvent) { const id = this.getFieldValue('VAR'); - const variableModel = Variables.getVariable(this.workspace, id); + const variableModel = Variables.getVariable(this.workspace, id)!; if (this.type === 'variables_get_dynamic') { - this.outputConnection.setCheck(variableModel.type); + this.outputConnection!.setCheck(variableModel.type); } else { - this.getInput('VALUE').connection.setCheck(variableModel.type); + this.getInput('VALUE')!.connection!.setCheck(variableModel.type); } }, }; @@ -158,13 +157,15 @@ const CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN = { /** * Factory for callbacks for rename variable dropdown menu option * associated with a variable getter block. - * @param {!Block} block The block with the variable to rename. - * @return {!function()} A function that renames the variable. + * + * @param block The block with the variable to rename. + * @returns A function that renames the variable. */ -const renameOptionCallbackFactory = function(block) { +const renameOptionCallbackFactory = function(block: VariableBlock) { return function() { const workspace = block.workspace; - const variable = block.getField('VAR').getVariable(); + const variableField = block.getField('VAR') as FieldVariable; + const variable = variableField.getVariable()!; Variables.renameVariable(workspace, variable); }; }; @@ -172,15 +173,17 @@ const renameOptionCallbackFactory = function(block) { /** * Factory for callbacks for delete variable dropdown menu option * associated with a variable getter block. - * @param {!Block} block The block with the variable to delete. - * @return {!function()} A function that deletes the variable. + * + * @param block The block with the variable to delete. + * @returns A function that deletes the variable. */ -const deleteOptionCallbackFactory = function(block) { +const deleteOptionCallbackFactory = function(block: VariableBlock) { return function() { const workspace = block.workspace; - const variable = block.getField('VAR').getVariable(); + const variableField = block.getField('VAR') as FieldVariable; + const variable = variableField.getVariable()!; workspace.deleteVariableById(variable.getId()); - workspace.refreshToolboxSelection(); + (workspace as WorkspaceSvg).refreshToolboxSelection(); }; }; diff --git a/core/contextmenu.ts b/core/contextmenu.ts index f9015a57e..a9cae417a 100644 --- a/core/contextmenu.ts +++ b/core/contextmenu.ts @@ -220,7 +220,7 @@ export function dispose() { * @param xml XML representation of new block. * @returns Function that creates a block. */ -export function callbackFactory(block: Block, xml: Element): Function { +export function callbackFactory(block: Block, xml: Element): () => void { return () => { eventUtils.disable(); let newBlock;