Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 3 commits into from
Apr 18, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 53 additions & 63 deletions blocks/loops.js → blocks/loops.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,44 +6,33 @@

/**
* @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 */
const AbstractEvent = goog.requireType('Blockly.Events.Abstract');
const ContextMenu = goog.require('Blockly.ContextMenu');
const Events = goog.require('Blockly.Events');
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.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');
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 * as Events from '../core/events/events.js';
import * as Extensions from '../core/extensions.js';
import * as Variables from '../core/variables.js';
import * as xmlUtils from '../core/utils/xml.js';
import {Msg} from '../core/msg.js';
import {createBlockDefinitionsFromJsonArray, defineBlocks} from '../core/common.js';
import '../core/field_dropdown.js';
import '../core/field_label.js';
import '../core/field_number.js';
import '../core/field_variable.js';
import '../core/warning.js';
import {FieldVariable} from '../core/field_variable.js';
import {WorkspaceSvg} from '../core/workspace_svg.js';


/**
* 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).
{
'type': 'controls_repeat_ext',
Expand Down Expand Up @@ -213,12 +202,11 @@ const blocks = createBlockDefinitionsFromJsonArray([
],
},
]);
exports.blocks = blocks;

/**
* Tooltips for the 'controls_whileUntil' block, keyed by MODE value.
*
* @see {Extensions#buildTooltipForDropdown}
* @readonly
*/
const WHILE_UNTIL_TOOLTIPS = {
'WHILE': '%{BKY_CONTROLS_WHILEUNTIL_TOOLTIP_WHILE}',
Expand All @@ -231,8 +219,8 @@ Extensions.register(

/**
* Tooltips for the 'controls_flow_statements' block, keyed by FLOW value.
*
* @see {Extensions#buildTooltipForDropdown}
* @readonly
*/
const BREAK_CONTINUE_TOOLTIPS = {
'BREAK': '%{BKY_CONTROLS_FLOW_STATEMENTS_TOOLTIP_BREAK}',
Expand All @@ -243,36 +231,42 @@ Extensions.register(
'controls_flow_tooltip',
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.
* Used by blocks 'controls_for' and 'controls_forEach'.
* @mixin
* @augments Block
* @package
* @readonly
*/
const CUSTOM_CONTEXT_MENU_CREATE_VARIABLES_GET_MIXIN = {
/**
* Add context menu option to create getter block for the loop's variable.
* (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) {
return;
}
const variable = this.getField('VAR').getVariable();
const varField = this.getField('VAR') as FieldVariable;
const variable = varField.getVariable()!;
const varName = variable.name;
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 xmlBlock = xmlUtils.createElement('block');
xmlBlock.setAttribute('type', 'variables_get');
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)
});
}
},
};
Expand Down Expand Up @@ -304,34 +298,32 @@ Extensions.register(
*
* // Else if using blockly_compressed + blockss_compressed.js in browser:
* Blockly.libraryBlocks.loopTypes.add('custom_loop');
*
* @type {!Set<string>}
*/
const loopTypes = new Set([
export const loopTypes: Set<string> = new Set([
'controls_repeat',
'controls_repeat_ext',
'controls_forEach',
'controls_for',
'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
* 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 = {
/**
* 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() {
let block = this;
getSurroundLoop: function(this: ControlFlowInLoopBlock): Block | null {
let block: Block|null = this;
do {
if (loopTypes.has(block.type)) {
return block;
Expand All @@ -344,18 +336,16 @@ const CONTROL_FLOW_IN_LOOP_CHECK_MIXIN = {
/**
* Called whenever anything on the workspace changes.
* 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:
// * It's at the start of a drag.
// * It's not a move event.
if (!this.workspace.isDragging || this.workspace.isDragging() ||
e.type !== Events.BLOCK_MOVE) {
if (!ws.isDragging || ws.isDragging() || e.type !== Events.BLOCK_MOVE) {
return;
}
const enabled = this.getSurroundLoop(this);
const enabled = !!this.getSurroundLoop();
rachel-fenichel marked this conversation as resolved.
Show resolved Hide resolved
this.setWarningText(
enabled ? null : Msg['CONTROLS_FLOW_STATEMENTS_WARNING']);
if (!this.isInFlyout) {
Expand Down