From 13647eb36439926e82cbe847a643f592702690ab Mon Sep 17 00:00:00 2001 From: Aaron Dodson Date: Wed, 24 Apr 2024 11:33:09 -0700 Subject: [PATCH] fix: add support for Scratch-style procedures (#39) * refactor: move procedures.js into src * fix: add support for Scratch-style procedures * chore: remove errant logging * refactor: remove underscore from exported callback attribute --- blocks_vertical/procedures.js | 306 ++++++++++++------------ blocks_vertical/vertical_extensions.js | 7 +- core/field_textinput_removable.js | 107 ++++----- src/constants.js | 21 ++ src/index.js | 2 + {core => src}/procedures.js | 315 ++++++------------------- 6 files changed, 293 insertions(+), 465 deletions(-) rename {core => src}/procedures.js (54%) diff --git a/blocks_vertical/procedures.js b/blocks_vertical/procedures.js index 1d2d2153a6..941acb8d5e 100644 --- a/blocks_vertical/procedures.js +++ b/blocks_vertical/procedures.js @@ -21,14 +21,10 @@ /** * @fileoverview Procedure blocks for Scratch. */ -'use strict'; -goog.provide('Blockly.ScratchBlocks.ProcedureUtils'); - -goog.require('Blockly.Blocks'); -goog.require('Blockly.Colours'); -goog.require('Blockly.constants'); -goog.require('Blockly.ScratchBlocks.VerticalExtensions'); +import * as Blockly from 'blockly/core'; +import {Colours} from '../core/colours.js'; +import {FieldTextInputRemovable} from '../core/field_textinput_removable.js'; // Serialization and deserialization. @@ -38,13 +34,13 @@ goog.require('Blockly.ScratchBlocks.VerticalExtensions'); * @return {!Element} XML storage element. * @this Blockly.Block */ -Blockly.ScratchBlocks.ProcedureUtils.callerMutationToDom = function() { +function callerMutationToDom() { var container = document.createElement('mutation'); container.setAttribute('proccode', this.procCode_); container.setAttribute('argumentids', JSON.stringify(this.argumentIds_)); container.setAttribute('warp', JSON.stringify(this.warp_)); return container; -}; +} /** * Parse XML to restore the (non-editable) name and arguments of a procedure @@ -52,14 +48,14 @@ Blockly.ScratchBlocks.ProcedureUtils.callerMutationToDom = function() { * @param {!Element} xmlElement XML storage element. * @this Blockly.Block */ -Blockly.ScratchBlocks.ProcedureUtils.callerDomToMutation = function(xmlElement) { +function callerDomToMutation(xmlElement) { this.procCode_ = xmlElement.getAttribute('proccode'); this.generateShadows_ = JSON.parse(xmlElement.getAttribute('generateshadows')); this.argumentIds_ = JSON.parse(xmlElement.getAttribute('argumentids')); this.warp_ = JSON.parse(xmlElement.getAttribute('warp')); this.updateDisplay_(); -}; +} /** * Create XML to represent the (non-editable) name and arguments of a @@ -69,7 +65,7 @@ Blockly.ScratchBlocks.ProcedureUtils.callerDomToMutation = function(xmlElement) * @return {!Element} XML storage element. * @this Blockly.Block */ -Blockly.ScratchBlocks.ProcedureUtils.definitionMutationToDom = function( +function definitionMutationToDom( opt_generateShadows) { var container = document.createElement('mutation'); @@ -83,7 +79,7 @@ Blockly.ScratchBlocks.ProcedureUtils.definitionMutationToDom = function( JSON.stringify(this.argumentDefaults_)); container.setAttribute('warp', JSON.stringify(this.warp_)); return container; -}; +} /** * Parse XML to restore the (non-editable) name and arguments of a @@ -91,7 +87,7 @@ Blockly.ScratchBlocks.ProcedureUtils.definitionMutationToDom = function( * @param {!Element} xmlElement XML storage element. * @this Blockly.Block */ -Blockly.ScratchBlocks.ProcedureUtils.definitionDomToMutation = function(xmlElement) { +function definitionDomToMutation(xmlElement) { this.procCode_ = xmlElement.getAttribute('proccode'); this.warp_ = JSON.parse(xmlElement.getAttribute('warp')); @@ -106,7 +102,7 @@ Blockly.ScratchBlocks.ProcedureUtils.definitionDomToMutation = function(xmlEleme if (this.updateArgumentReporterNames_) { this.updateArgumentReporterNames_(prevArgIds, prevDisplayNames); } -}; +} // End of serialization and deserialization. @@ -118,9 +114,9 @@ Blockly.ScratchBlocks.ProcedureUtils.definitionDomToMutation = function(xmlEleme * @return {string} Procedure name. * @this Blockly.Block */ -Blockly.ScratchBlocks.ProcedureUtils.getProcCode = function() { +function getProcCode() { return this.procCode_; -}; +} /** * Update the block's structure and appearance to match the internally stored @@ -128,7 +124,7 @@ Blockly.ScratchBlocks.ProcedureUtils.getProcCode = function() { * @private * @this Blockly.Block */ -Blockly.ScratchBlocks.ProcedureUtils.updateDisplay_ = function() { +function updateDisplay_() { var wasRendered = this.rendered; this.rendered = false; @@ -143,7 +139,7 @@ Blockly.ScratchBlocks.ProcedureUtils.updateDisplay_ = function() { this.initSvg(); this.render(); } -}; +} /** * Disconnect old blocks from all value inputs on this block, but hold onto them @@ -155,7 +151,7 @@ Blockly.ScratchBlocks.ProcedureUtils.updateDisplay_ = function() { * @private * @this Blockly.Block */ -Blockly.ScratchBlocks.ProcedureUtils.disconnectOldBlocks_ = function() { +function disconnectOldBlocks_() { // Remove old stuff var connectionMap = {}; for (var i = 0, input; input = this.inputList[i]; i++) { @@ -167,17 +163,13 @@ Blockly.ScratchBlocks.ProcedureUtils.disconnectOldBlocks_ = function() { }; connectionMap[input.name] = saveInfo; - // Remove the shadow DOM, then disconnect the block. Otherwise a shadow - // block will respawn instantly, and we'd have to remove it when we remove - // the input. - input.connection.setShadowDom(null); if (target) { input.connection.disconnect(); } } } return connectionMap; -}; +} /** * Remove all inputs on the block, including dummy inputs. @@ -185,14 +177,14 @@ Blockly.ScratchBlocks.ProcedureUtils.disconnectOldBlocks_ = function() { * @private * @this Blockly.Block */ -Blockly.ScratchBlocks.ProcedureUtils.removeAllInputs_ = function() { +function removeAllInputs_() { // Delete inputs directly instead of with block.removeInput to avoid splicing // out of the input list at every index. for (var i = 0, input; input = this.inputList[i]; i++) { input.dispose(); } this.inputList = []; -}; +} /** * Create all inputs specified by the new procCode, and populate them with @@ -202,7 +194,7 @@ Blockly.ScratchBlocks.ProcedureUtils.removeAllInputs_ = function() { * @private * @this Blockly.Block */ -Blockly.ScratchBlocks.ProcedureUtils.createAllInputs_ = function(connectionMap) { +function createAllInputs_(connectionMap) { // Split the proc into components, by %n, %b, and %s (ignoring escaped). var procComponents = this.procCode_.split(/(?=[^\\]%[nbs])/); procComponents = procComponents.map(function(c) { @@ -234,7 +226,7 @@ Blockly.ScratchBlocks.ProcedureUtils.createAllInputs_ = function(connectionMap) } this.addProcedureLabel_(labelText.replace(/\\%/, '%')); } -}; +} /** * Delete all shadow blocks in the given map. @@ -244,7 +236,7 @@ Blockly.ScratchBlocks.ProcedureUtils.createAllInputs_ = function(connectionMap) * @private * @this Blockly.Block */ -Blockly.ScratchBlocks.ProcedureUtils.deleteShadows_ = function(connectionMap) { +function deleteShadows_(connectionMap) { // Get rid of all of the old shadow blocks if they aren't connected. if (connectionMap) { for (var id in connectionMap) { @@ -260,7 +252,7 @@ Blockly.ScratchBlocks.ProcedureUtils.deleteShadows_ = function(connectionMap) { } } } -}; +} // End of shared code. /** @@ -269,9 +261,9 @@ Blockly.ScratchBlocks.ProcedureUtils.deleteShadows_ = function(connectionMap) { * @param {string} text The label text. * @private */ -Blockly.ScratchBlocks.ProcedureUtils.addLabelField_ = function(text) { +function addLabelField_(text) { this.appendDummyInput().appendField(text); -}; +} /** * Add a label editor with the given text to a procedures_declaration @@ -280,12 +272,12 @@ Blockly.ScratchBlocks.ProcedureUtils.addLabelField_ = function(text) { * @param {string} text The label text. * @private */ -Blockly.ScratchBlocks.ProcedureUtils.addLabelEditor_ = function(text) { +function addLabelEditor_(text) { if (text) { - this.appendDummyInput(Blockly.utils.genUid()). - appendField(new Blockly.FieldTextInputRemovable(text)); + this.appendDummyInput(Blockly.utils.idGenerator.genUid()). + appendField(new FieldTextInputRemovable(text)); } -}; +} /** * Build a DOM node representing a shadow block of the given type. @@ -294,7 +286,7 @@ Blockly.ScratchBlocks.ProcedureUtils.addLabelEditor_ = function(text) { * @private * @this Blockly.Block */ -Blockly.ScratchBlocks.ProcedureUtils.buildShadowDom_ = function(type) { +function buildShadowDom_(type) { var shadowDom = goog.dom.createDom('shadow'); if (type == 'n') { var shadowType = 'math_number'; @@ -310,7 +302,7 @@ Blockly.ScratchBlocks.ProcedureUtils.buildShadowDom_ = function(type) { fieldDom.setAttribute('name', fieldName); shadowDom.appendChild(fieldDom); return shadowDom; -}; +} /** * Create a new shadow block and attach it to the given input. @@ -320,7 +312,7 @@ Blockly.ScratchBlocks.ProcedureUtils.buildShadowDom_ = function(type) { * @private * @this Blockly.Block */ -Blockly.ScratchBlocks.ProcedureUtils.attachShadow_ = function(input, +function attachShadow_(input, argumentType) { if (argumentType == 'n' || argumentType == 's') { var blockType = argumentType == 'n' ? 'math_number' : 'text'; @@ -341,11 +333,11 @@ Blockly.ScratchBlocks.ProcedureUtils.attachShadow_ = function(input, Blockly.Events.enable(); } if (Blockly.Events.isEnabled()) { - Blockly.Events.fire(new Blockly.Events.BlockCreate(newBlock)); + Blockly.Events.fire(new (Blockly.Events.get(Blockly.Events.BLOCK_CREATE))(newBlock)); } newBlock.outputConnection.connect(input.connection); } -}; +} /** * Create a new argument reporter block. @@ -357,7 +349,7 @@ Blockly.ScratchBlocks.ProcedureUtils.attachShadow_ = function(input, * @private * @this Blockly.Block */ -Blockly.ScratchBlocks.ProcedureUtils.createArgumentReporter_ = function( +function createArgumentReporter_( argumentType, displayName) { if (argumentType == 'n' || argumentType == 's') { var blockType = 'argument_reporter_string_number'; @@ -377,10 +369,10 @@ Blockly.ScratchBlocks.ProcedureUtils.createArgumentReporter_ = function( Blockly.Events.enable(); } if (Blockly.Events.isEnabled()) { - Blockly.Events.fire(new Blockly.Events.BlockCreate(newBlock)); + Blockly.Events.fire(new (Blockly.Events.get(Blockly.Events.BLOCK_CREATE))(newBlock)); } return newBlock; -}; +} /** * Populate the argument by attaching the correct child block or shadow to the @@ -394,7 +386,7 @@ Blockly.ScratchBlocks.ProcedureUtils.createArgumentReporter_ = function( * @private * @this Blockly.Block */ -Blockly.ScratchBlocks.ProcedureUtils.populateArgumentOnCaller_ = function(type, +function populateArgumentOnCaller_(type, index, connectionMap, id, input) { var oldBlock = null; var oldShadow = null; @@ -416,7 +408,7 @@ Blockly.ScratchBlocks.ProcedureUtils.populateArgumentOnCaller_ = function(type, } else if (this.generateShadows_) { this.attachShadow_(input, type); } -}; +} /** * Populate the argument by attaching the correct argument reporter to the given @@ -431,7 +423,7 @@ Blockly.ScratchBlocks.ProcedureUtils.populateArgumentOnCaller_ = function(type, * @private * @this Blockly.Block */ -Blockly.ScratchBlocks.ProcedureUtils.populateArgumentOnPrototype_ = function( +function populateArgumentOnPrototype_( type, index, connectionMap, id, input) { var oldBlock = null; if (connectionMap && (id in connectionMap)) { @@ -439,8 +431,7 @@ Blockly.ScratchBlocks.ProcedureUtils.populateArgumentOnPrototype_ = function( oldBlock = saveInfo['block']; } - var oldTypeMatches = - Blockly.ScratchBlocks.ProcedureUtils.checkOldTypeMatches_(oldBlock, type); + var oldTypeMatches = checkOldTypeMatches_(oldBlock, type); var displayName = this.displayNames_[index]; // Decide which block to attach. @@ -456,7 +447,7 @@ Blockly.ScratchBlocks.ProcedureUtils.populateArgumentOnPrototype_ = function( // Attach the block. input.connection.connect(argumentReporter.outputConnection); -}; +} /** * Populate the argument by attaching the correct argument editor to the given @@ -471,7 +462,7 @@ Blockly.ScratchBlocks.ProcedureUtils.populateArgumentOnPrototype_ = function( * @private * @this Blockly.Block */ -Blockly.ScratchBlocks.ProcedureUtils.populateArgumentOnDeclaration_ = function( +function populateArgumentOnDeclaration_( type, index, connectionMap, id, input) { var oldBlock = null; @@ -483,8 +474,7 @@ Blockly.ScratchBlocks.ProcedureUtils.populateArgumentOnDeclaration_ = function( // TODO: This always returns false, because it checks for argument reporter // blocks instead of argument editor blocks. Create a new version for argument // editors. - var oldTypeMatches = - Blockly.ScratchBlocks.ProcedureUtils.checkOldTypeMatches_(oldBlock, type); + var oldTypeMatches = checkOldTypeMatches_(oldBlock, type); var displayName = this.displayNames_[index]; // Decide which block to attach. @@ -498,7 +488,7 @@ Blockly.ScratchBlocks.ProcedureUtils.populateArgumentOnDeclaration_ = function( // Attach the block. input.connection.connect(argumentEditor.outputConnection); -}; +} /** * Check whether the type of the old block corresponds to the given argument @@ -507,7 +497,7 @@ Blockly.ScratchBlocks.ProcedureUtils.populateArgumentOnDeclaration_ = function( * @param {string} type The argument type. One of 'n', 'n', or 's'. * @return {boolean} True if the type matches, false otherwise. */ -Blockly.ScratchBlocks.ProcedureUtils.checkOldTypeMatches_ = function(oldBlock, +function checkOldTypeMatches_(oldBlock, type) { if (!oldBlock) { return false; @@ -520,7 +510,7 @@ Blockly.ScratchBlocks.ProcedureUtils.checkOldTypeMatches_ = function(oldBlock, return true; } return false; -}; +} /** * Create an argument editor. @@ -534,7 +524,7 @@ Blockly.ScratchBlocks.ProcedureUtils.checkOldTypeMatches_ = function(oldBlock, * @private * @this Blockly.Block */ -Blockly.ScratchBlocks.ProcedureUtils.createArgumentEditor_ = function( +function createArgumentEditor_( argumentType, displayName) { Blockly.Events.disable(); try { @@ -547,22 +537,22 @@ Blockly.ScratchBlocks.ProcedureUtils.createArgumentEditor_ = function( newBlock.setShadow(true); if (!this.isInsertionMarker()) { newBlock.initSvg(); - newBlock.render(false); + newBlock.queueRender(); } } finally { Blockly.Events.enable(); } if (Blockly.Events.isEnabled()) { - Blockly.Events.fire(new Blockly.Events.BlockCreate(newBlock)); + Blockly.Events.fire(new (Blockly.Events.get(Blockly.Events.BLOCK_CREATE))(newBlock)); } return newBlock; -}; +} /** * Update the serializable information on the block based on the existing inputs * and their text. */ -Blockly.ScratchBlocks.ProcedureUtils.updateDeclarationProcCode_ = function() { +function updateDeclarationProcCode_() { this.procCode_ = ''; this.displayNames_ = []; this.argumentIds_ = []; @@ -588,13 +578,13 @@ Blockly.ScratchBlocks.ProcedureUtils.updateDeclarationProcCode_ = function() { 'Unexpected input type on a procedure mutator root: ' + input.type); } } -}; +} /** * Focus on the last argument editor or label editor on the block. * @private */ -Blockly.ScratchBlocks.ProcedureUtils.focusLastEditor_ = function() { +function focusLastEditor_() { if (this.inputList.length > 0) { var newInput = this.inputList[this.inputList.length - 1]; if (newInput.type == Blockly.DUMMY_INPUT) { @@ -605,73 +595,73 @@ Blockly.ScratchBlocks.ProcedureUtils.focusLastEditor_ = function() { target.getField('TEXT').showEditor_(); } } -}; +} /** * Externally-visible function to add a label to the procedure declaration. * @public */ -Blockly.ScratchBlocks.ProcedureUtils.addLabelExternal = function() { +function addLabelExternal() { Blockly.WidgetDiv.hide(true); this.procCode_ = this.procCode_ + ' label text'; this.updateDisplay_(); this.focusLastEditor_(); -}; +} /** * Externally-visible function to add a boolean argument to the procedure * declaration. * @public */ -Blockly.ScratchBlocks.ProcedureUtils.addBooleanExternal = function() { +function addBooleanExternal() { Blockly.WidgetDiv.hide(true); this.procCode_ = this.procCode_ + ' %b'; this.displayNames_.push('boolean'); - this.argumentIds_.push(Blockly.utils.genUid()); + this.argumentIds_.push(Blockly.utils.idGenerator.genUid()); this.argumentDefaults_.push('false'); this.updateDisplay_(); this.focusLastEditor_(); -}; +} /** * Externally-visible function to add a string/number argument to the procedure * declaration. * @public */ -Blockly.ScratchBlocks.ProcedureUtils.addStringNumberExternal = function() { +function addStringNumberExternal() { Blockly.WidgetDiv.hide(true); this.procCode_ = this.procCode_ + ' %s'; this.displayNames_.push('number or text'); - this.argumentIds_.push(Blockly.utils.genUid()); + this.argumentIds_.push(Blockly.utils.idGenerator.genUid()); this.argumentDefaults_.push(''); this.updateDisplay_(); this.focusLastEditor_(); -}; +} /** * Externally-visible function to get the warp on procedure declaration. * @return {boolean} The value of the warp_ property. * @public */ -Blockly.ScratchBlocks.ProcedureUtils.getWarp = function() { +function getWarp() { return this.warp_; -}; +} /** * Externally-visible function to set the warp on procedure declaration. * @param {boolean} warp The value of the warp_ property. * @public */ -Blockly.ScratchBlocks.ProcedureUtils.setWarp = function(warp) { +function setWarp(warp) { this.warp_ = warp; -}; +} /** * Callback to remove a field, only for the declaration block. * @param {Blockly.Field} field The field being removed. * @public */ -Blockly.ScratchBlocks.ProcedureUtils.removeFieldCallback = function(field) { +function removeFieldCallback(field) { // Do not delete if there is only one input if (this.inputList.length === 1) { return; @@ -681,7 +671,7 @@ Blockly.ScratchBlocks.ProcedureUtils.removeFieldCallback = function(field) { var input = this.inputList[n]; if (input.connection) { var target = input.connection.targetBlock(); - if (target.getField(field.name) == field) { + if (field.name && target.getField(field.name) == field) { inputNameToRemove = input.name; } } else { @@ -698,19 +688,19 @@ Blockly.ScratchBlocks.ProcedureUtils.removeFieldCallback = function(field) { this.onChangeFn(); this.updateDisplay_(); } -}; +} /** * Callback to pass removeField up to the declaration block from arguments. * @param {Blockly.Field} field The field being removed. * @public */ -Blockly.ScratchBlocks.ProcedureUtils.removeArgumentCallback_ = function( +function removeArgumentCallback_( field) { if (this.parentBlock_ && this.parentBlock_.removeFieldCallback) { this.parentBlock_.removeFieldCallback(field); } -}; +} /** * Update argument reporter field values after an edit to the prototype mutation @@ -725,7 +715,7 @@ Blockly.ScratchBlocks.ProcedureUtils.removeArgumentCallback_ = function( * @param {!Array} prevDisplayNames The previous argument names. * @this Blockly.Block */ -Blockly.ScratchBlocks.ProcedureUtils.updateArgumentReporterNames_ = function(prevArgIds, prevDisplayNames) { +function updateArgumentReporterNames_(prevArgIds, prevDisplayNames) { var nameChanges = []; var argReporters = []; var definitionBlock = this.getParent(); @@ -766,7 +756,7 @@ Blockly.ScratchBlocks.ProcedureUtils.updateArgumentReporterNames_ = function(pre block.setFieldValue(nameChange.newName, 'VALUE'); } } -}; +} Blockly.Blocks['procedures_definition'] = { /** @@ -799,24 +789,25 @@ Blockly.Blocks['procedures_call'] = { this.procCode_ = ''; this.argumentIds_ = []; this.warp_ = false; + + // Shared. + this.getProcCode = getProcCode.bind(this); + this.removeAllInputs_ = removeAllInputs_.bind(this); + this.disconnectOldBlocks_ = disconnectOldBlocks_.bind(this); + this.deleteShadows_ = deleteShadows_.bind(this); + this.createAllInputs_ = createAllInputs_.bind(this); + this.updateDisplay_ = updateDisplay_.bind(this); + + // Exist on all three blocks, but have different implementations. + this.mutationToDom = callerMutationToDom.bind(this); + this.domToMutation = callerDomToMutation.bind(this); + this.populateArgument_ = populateArgumentOnCaller_.bind(this); + this.addProcedureLabel_ = addLabelField_.bind(this); + + // Only exists on the external caller. + this.attachShadow_ = attachShadow_.bind(this); + this.buildShadowDom_ = buildShadowDom_.bind(this); }, - // Shared. - getProcCode: Blockly.ScratchBlocks.ProcedureUtils.getProcCode, - removeAllInputs_: Blockly.ScratchBlocks.ProcedureUtils.removeAllInputs_, - disconnectOldBlocks_: Blockly.ScratchBlocks.ProcedureUtils.disconnectOldBlocks_, - deleteShadows_: Blockly.ScratchBlocks.ProcedureUtils.deleteShadows_, - createAllInputs_: Blockly.ScratchBlocks.ProcedureUtils.createAllInputs_, - updateDisplay_: Blockly.ScratchBlocks.ProcedureUtils.updateDisplay_, - - // Exist on all three blocks, but have different implementations. - mutationToDom: Blockly.ScratchBlocks.ProcedureUtils.callerMutationToDom, - domToMutation: Blockly.ScratchBlocks.ProcedureUtils.callerDomToMutation, - populateArgument_: Blockly.ScratchBlocks.ProcedureUtils.populateArgumentOnCaller_, - addProcedureLabel_: Blockly.ScratchBlocks.ProcedureUtils.addLabelField_, - - // Only exists on the external caller. - attachShadow_: Blockly.ScratchBlocks.ProcedureUtils.attachShadow_, - buildShadowDom_: Blockly.ScratchBlocks.ProcedureUtils.buildShadowDom_ }; Blockly.Blocks['procedures_prototype'] = { @@ -836,24 +827,24 @@ Blockly.Blocks['procedures_prototype'] = { this.argumentIds_ = []; this.argumentDefaults_ = []; this.warp_ = false; + + // Shared. + this.getProcCode = getProcCode.bind(this); + this.removeAllInputs_ = removeAllInputs_.bind(this); + this.disconnectOldBlocks_ = disconnectOldBlocks_.bind(this); + this.deleteShadows_ = deleteShadows_.bind(this); + this.createAllInputs_ = createAllInputs_.bind(this); + this.updateDisplay_ = updateDisplay_.bind(this); + // Exist on all three blocks, but have different implementations. + this.mutationToDom = definitionMutationToDom.bind(this); + this.domToMutation = definitionDomToMutation.bind(this); + this.populateArgument_ = populateArgumentOnPrototype_.bind(this); + this.addProcedureLabel_ = addLabelField_.bind(this); + + // Only exists on procedures_prototype. + this.createArgumentReporter_ = createArgumentReporter_.bind(this); + this.updateArgumentReporterNames_ = updateArgumentReporterNames_.bind(this); }, - // Shared. - getProcCode: Blockly.ScratchBlocks.ProcedureUtils.getProcCode, - removeAllInputs_: Blockly.ScratchBlocks.ProcedureUtils.removeAllInputs_, - disconnectOldBlocks_: Blockly.ScratchBlocks.ProcedureUtils.disconnectOldBlocks_, - deleteShadows_: Blockly.ScratchBlocks.ProcedureUtils.deleteShadows_, - createAllInputs_: Blockly.ScratchBlocks.ProcedureUtils.createAllInputs_, - updateDisplay_: Blockly.ScratchBlocks.ProcedureUtils.updateDisplay_, - - // Exist on all three blocks, but have different implementations. - mutationToDom: Blockly.ScratchBlocks.ProcedureUtils.definitionMutationToDom, - domToMutation: Blockly.ScratchBlocks.ProcedureUtils.definitionDomToMutation, - populateArgument_: Blockly.ScratchBlocks.ProcedureUtils.populateArgumentOnPrototype_, - addProcedureLabel_: Blockly.ScratchBlocks.ProcedureUtils.addLabelField_, - - // Only exists on procedures_prototype. - createArgumentReporter_: Blockly.ScratchBlocks.ProcedureUtils.createArgumentReporter_, - updateArgumentReporterNames_: Blockly.ScratchBlocks.ProcedureUtils.updateArgumentReporterNames_ }; Blockly.Blocks['procedures_declaration'] = { @@ -871,33 +862,34 @@ Blockly.Blocks['procedures_declaration'] = { this.argumentIds_ = []; this.argumentDefaults_ = []; this.warp_ = false; + + // Shared. + this.getProcCode = getProcCode.bind(this); + this.removeAllInputs_ = removeAllInputs_.bind(this); + this.disconnectOldBlocks_ = disconnectOldBlocks_.bind(this); + this.deleteShadows_ = deleteShadows_.bind(this); + this.createAllInputs_ = createAllInputs_.bind(this); + this.updateDisplay_ = updateDisplay_.bind(this); + + // Exist on all three blocks, but have different implementations. + this.mutationToDom = definitionMutationToDom.bind(this); + this.domToMutation = definitionDomToMutation.bind(this); + this.populateArgument_ = populateArgumentOnDeclaration_.bind(this); + this.addProcedureLabel_ = addLabelEditor_.bind(this); + + // Exist on declaration and arguments editors, with different implementations. + this.removeFieldCallback = removeFieldCallback.bind(this); + + // Only exist on procedures_declaration. + this.createArgumentEditor_ = createArgumentEditor_.bind(this); + this.focusLastEditor_ = focusLastEditor_.bind(this); + this.getWarp = getWarp.bind(this); + this.setWarp = setWarp.bind(this); + this.addLabelExternal = addLabelExternal.bind(this); + this.addBooleanExternal = addBooleanExternal.bind(this); + this.addStringNumberExternal = addStringNumberExternal.bind(this); + this.onChangeFn = updateDeclarationProcCode_.bind(this); }, - // Shared. - getProcCode: Blockly.ScratchBlocks.ProcedureUtils.getProcCode, - removeAllInputs_: Blockly.ScratchBlocks.ProcedureUtils.removeAllInputs_, - disconnectOldBlocks_: Blockly.ScratchBlocks.ProcedureUtils.disconnectOldBlocks_, - deleteShadows_: Blockly.ScratchBlocks.ProcedureUtils.deleteShadows_, - createAllInputs_: Blockly.ScratchBlocks.ProcedureUtils.createAllInputs_, - updateDisplay_: Blockly.ScratchBlocks.ProcedureUtils.updateDisplay_, - - // Exist on all three blocks, but have different implementations. - mutationToDom: Blockly.ScratchBlocks.ProcedureUtils.definitionMutationToDom, - domToMutation: Blockly.ScratchBlocks.ProcedureUtils.definitionDomToMutation, - populateArgument_: Blockly.ScratchBlocks.ProcedureUtils.populateArgumentOnDeclaration_, - addProcedureLabel_: Blockly.ScratchBlocks.ProcedureUtils.addLabelEditor_, - - // Exist on declaration and arguments editors, with different implementations. - removeFieldCallback: Blockly.ScratchBlocks.ProcedureUtils.removeFieldCallback, - - // Only exist on procedures_declaration. - createArgumentEditor_: Blockly.ScratchBlocks.ProcedureUtils.createArgumentEditor_, - focusLastEditor_: Blockly.ScratchBlocks.ProcedureUtils.focusLastEditor_, - getWarp: Blockly.ScratchBlocks.ProcedureUtils.getWarp, - setWarp: Blockly.ScratchBlocks.ProcedureUtils.setWarp, - addLabelExternal: Blockly.ScratchBlocks.ProcedureUtils.addLabelExternal, - addBooleanExternal: Blockly.ScratchBlocks.ProcedureUtils.addBooleanExternal, - addStringNumberExternal: Blockly.ScratchBlocks.ProcedureUtils.addStringNumberExternal, - onChangeFn: Blockly.ScratchBlocks.ProcedureUtils.updateDeclarationProcCode_ }; Blockly.Blocks['argument_reporter_boolean'] = { @@ -940,15 +932,16 @@ Blockly.Blocks['argument_editor_boolean'] = { "text": "foo" } ], - "colour": Blockly.Colours.textField, - "colourSecondary": Blockly.Colours.textField, - "colourTertiary": Blockly.Colours.textField, - "colourQuaternary": Blockly.Colours.textField, + "colour": Colours.textField, + "colourSecondary": Colours.textField, + "colourTertiary": Colours.textField, + "colourQuaternary": Colours.textField, "extensions": ["output_boolean"] }); + + // Exist on declaration and arguments editors, with different implementations. + this.removeFieldCallback = removeArgumentCallback_.bind(this); }, - // Exist on declaration and arguments editors, with different implementations. - removeFieldCallback: Blockly.ScratchBlocks.ProcedureUtils.removeArgumentCallback_ }; Blockly.Blocks['argument_editor_string_number'] = { @@ -961,13 +954,14 @@ Blockly.Blocks['argument_editor_string_number'] = { "text": "foo" } ], - "colour": Blockly.Colours.textField, - "colourSecondary": Blockly.Colours.textField, - "colourTertiary": Blockly.Colours.textField, - "colourQuaternary": Blockly.Colours.textField, + "colour": Colours.textField, + "colourSecondary": Colours.textField, + "colourTertiary": Colours.textField, + "colourQuaternary": Colours.textField, "extensions": ["output_number", "output_string"] }); + + // Exist on declaration and arguments editors, with different implementations. + this.removeFieldCallback = removeArgumentCallback_.bind(this); }, - // Exist on declaration and arguments editors, with different implementations. - removeFieldCallback: Blockly.ScratchBlocks.ProcedureUtils.removeArgumentCallback_ }; diff --git a/blocks_vertical/vertical_extensions.js b/blocks_vertical/vertical_extensions.js index 9eb7504a66..54378d5e46 100644 --- a/blocks_vertical/vertical_extensions.js +++ b/blocks_vertical/vertical_extensions.js @@ -27,6 +27,7 @@ */ import * as Blockly from 'blockly/core'; import {Colours} from '../core/colours.js'; +import {ScratchProcedures} from '../src/procedures.js'; const VerticalExtensions = {}; /** @@ -158,7 +159,7 @@ VerticalExtensions.PROCEDURE_DEF_CONTEXTMENU = { */ customContextMenu: function(menuOptions) { // Add the edit option at the end. - menuOptions.push(Blockly.Procedures.makeEditOption(this)); + menuOptions.push(ScratchProcedures.makeEditOption(this)); // Find the delete option and update its callback to be specific to // functions. @@ -173,7 +174,7 @@ VerticalExtensions.PROCEDURE_DEF_CONTEXTMENU = { } var rootBlock = this; option.callback = function() { - var didDelete = Blockly.Procedures.deleteProcedureDefCallback( + var didDelete = ScratchProcedures.deleteProcedureDefCallback( procCode, rootBlock); if (!didDelete) { alert(Blockly.Msg.PROCEDURE_USED); @@ -207,7 +208,7 @@ VerticalExtensions.PROCEDURE_CALL_CONTEXTMENU = { * @this Blockly.Block */ customContextMenu: function(menuOptions) { - menuOptions.push(Blockly.Procedures.makeEditOption(this)); + menuOptions.push(ScratchProcedures.makeEditOption(this)); } }; diff --git a/core/field_textinput_removable.js b/core/field_textinput_removable.js index a37e071845..7549e9ff90 100644 --- a/core/field_textinput_removable.js +++ b/core/field_textinput_removable.js @@ -22,17 +22,7 @@ * @fileoverview Text input field with floating "remove" button. * @author pkaplan@media.mit.edu (Paul Kaplan) */ -'use strict'; - -goog.provide('Blockly.FieldTextInputRemovable'); - -goog.require('Blockly.BlockSvg.render'); -goog.require('Blockly.Colours'); -goog.require('Blockly.FieldTextInput'); -goog.require('Blockly.Msg'); -goog.require('Blockly.utils'); -goog.require('goog.dom'); -goog.require('goog.dom.TagName'); +import * as Blockly from 'blockly/core'; /** * Class for an editable text field displaying a deletion icon when selected. @@ -47,59 +37,54 @@ goog.require('goog.dom.TagName'); * @extends {Blockly.FieldTextInput} * @constructor */ -Blockly.FieldTextInputRemovable = function(text, opt_validator, opt_restrictor) { - Blockly.FieldTextInputRemovable.superClass_.constructor.call(this, text, - opt_validator, opt_restrictor); -}; -goog.inherits(Blockly.FieldTextInputRemovable, Blockly.FieldTextInput); - -/** - * Show the inline free-text editor on top of the text with the remove button. - * @private - */ -Blockly.FieldTextInputRemovable.prototype.showEditor_ = function() { - Blockly.FieldTextInputRemovable.superClass_.showEditor_.call(this); +export class FieldTextInputRemovable extends Blockly.FieldTextInput { + /** + * Show the inline free-text editor on top of the text with the remove button. + * @private + */ + showEditor_() { + super.showEditor_(); - var div = Blockly.WidgetDiv.DIV; - div.className += ' removableTextInput'; - var removeButton = - goog.dom.createDom(goog.dom.TagName.IMG, 'blocklyTextRemoveIcon'); - removeButton.setAttribute('src', - Blockly.mainWorkspace.options.pathToMedia + 'icons/remove.svg'); - this.removeButtonMouseWrapper_ = Blockly.bindEvent_(removeButton, - 'mousedown', this, this.removeCallback_); - div.appendChild(removeButton); -}; + const div = Blockly.WidgetDiv.getDiv(); + div.className += ' removableTextInput'; + const removeButton = document.createElement('img'); + removeButton.className = 'blocklyTextRemoveIcon'; + removeButton.setAttribute('src', + this.sourceBlock_.workspace.options.pathToMedia + 'icons/remove.svg'); + this.removeButtonMouseWrapper_ = Blockly.browserEvents.bind(removeButton, + 'mousedown', this, this.removeCallback_); + div.appendChild(removeButton); + } -/** - * Function to call when remove button is called. Checks for removeFieldCallback - * on sourceBlock and calls it if possible. - * @private - */ -Blockly.FieldTextInputRemovable.prototype.removeCallback_ = function() { - if (this.sourceBlock_ && this.sourceBlock_.removeFieldCallback) { - this.sourceBlock_.removeFieldCallback(this); - } else { - console.warn('Expected a source block with removeFieldCallback'); + /** + * Function to call when remove button is called. Checks for removeFieldCallback + * on sourceBlock and calls it if possible. + * @private + */ + removeCallback_() { + if (this.sourceBlock_ && this.sourceBlock_.removeFieldCallback) { + this.sourceBlock_.removeFieldCallback(this); + } else { + console.warn('Expected a source block with removeFieldCallback'); + } } -}; -/** - * Helper function to construct a FieldTextInputRemovable from a JSON arg object, - * dereferencing any string table references. - * @param {!Object} options A JSON object with options (text, class, and - * spellcheck). - * @returns {!Blockly.FieldTextInputRemovable} The new text input. - * @public - */ -Blockly.FieldTextInputRemovable.fromJson = function(options) { - var text = Blockly.utils.replaceMessageReferences(options['text']); - var field = new Blockly.FieldTextInputRemovable(text, options['class']); - if (typeof options['spellcheck'] == 'boolean') { - field.setSpellcheck(options['spellcheck']); + /** + * Helper function to construct a FieldTextInputRemovable from a JSON arg object, + * dereferencing any string table references. + * @param {!Object} options A JSON object with options (text, class, and + * spellcheck). + * @returns {!Blockly.FieldTextInputRemovable} The new text input. + * @public + */ + fromJson(options) { + const text = Blockly.utils.replaceMessageReferences(options['text']); + const field = new FieldTextInputRemovable(text, null, options); + if (typeof options['spellcheck'] == 'boolean') { + field.setSpellcheck(options['spellcheck']); + } + return field; } - return field; -}; +} -Blockly.Field.register( - 'field_input_removable', Blockly.FieldTextInputRemovable); +Blockly.fieldRegistry.register('field_input_removable', FieldTextInputRemovable); diff --git a/src/constants.js b/src/constants.js index 0d6f6e3a44..b1bf5d3da4 100644 --- a/src/constants.js +++ b/src/constants.js @@ -16,3 +16,24 @@ export {BROADCAST_MESSAGE_VARIABLE_TYPE}; */ const LIST_VARIABLE_TYPE = 'list'; export {LIST_VARIABLE_TYPE}; + +/* + * The type of all procedure definition blocks. + * @const {string} + */ +const PROCEDURES_DEFINITION_BLOCK_TYPE = 'procedures_definition'; +export {PROCEDURES_DEFINITION_BLOCK_TYPE}; + +/** + * The type of all procedure prototype blocks. + * @const {string} + */ +const PROCEDURES_PROTOTYPE_BLOCK_TYPE = 'procedures_prototype'; +export {PROCEDURES_PROTOTYPE_BLOCK_TYPE}; + +/** + * The type of all procedure call blocks. + * @const {string} + */ +const PROCEDURES_CALL_BLOCK_TYPE = 'procedures_call'; +export {PROCEDURES_CALL_BLOCK_TYPE}; diff --git a/src/index.js b/src/index.js index ac7110eebc..b77e2b5f79 100644 --- a/src/index.js +++ b/src/index.js @@ -14,6 +14,7 @@ import '../blocks_vertical/event.js'; import '../blocks_vertical/looks.js'; import '../blocks_vertical/motion.js'; import '../blocks_vertical/operators.js'; +import '../blocks_vertical/procedures.js'; import '../blocks_vertical/sensing.js'; import '../blocks_vertical/sound.js'; import * as scratchBlocksUtils from '../core/scratch_blocks_utils.js'; @@ -26,6 +27,7 @@ import { export * from 'blockly'; export * from './categories.js'; +export * from './procedures.js'; export * from '../core/colours.js'; export * from '../msg/scratch_msgs.js'; export {scratchBlocksUtils}; diff --git a/core/procedures.js b/src/procedures.js similarity index 54% rename from core/procedures.js rename to src/procedures.js index 5c4f81ef2e..0c471500c7 100644 --- a/core/procedures.js +++ b/src/procedures.js @@ -22,57 +22,10 @@ * @fileoverview Utility functions for handling procedures. * @author fraser@google.com (Neil Fraser) */ -'use strict'; -/** - * @name Blockly.Procedures - * @namespace - **/ -goog.provide('Blockly.Procedures'); - -goog.require('Blockly.Blocks'); -goog.require('Blockly.constants'); -goog.require('Blockly.Events.BlockChange'); -goog.require('Blockly.Field'); -goog.require('Blockly.Names'); -goog.require('Blockly.Workspace'); - - -/** - * Constant to separate procedure names from variables and generated functions - * when running generators. - * @deprecated Use Blockly.PROCEDURE_CATEGORY_NAME - */ -Blockly.Procedures.NAME_TYPE = Blockly.PROCEDURE_CATEGORY_NAME; - -/** - * Find all user-created procedure definitions in a workspace. - * @param {!Blockly.Workspace} root Root workspace. - * @return {!Array.>} Pair of arrays, the - * first contains procedures without return variables, the second with. - * Each procedure is defined by a three-element list of name, parameter - * list, and return value boolean. - */ -Blockly.Procedures.allProcedures = function(root) { - var blocks = root.getAllBlocks(); - var proceduresReturn = []; - var proceduresNoReturn = []; - for (var i = 0; i < blocks.length; i++) { - if (blocks[i].getProcedureDef) { - var tuple = blocks[i].getProcedureDef(); - if (tuple) { - if (tuple[2]) { - proceduresReturn.push(tuple); - } else { - proceduresNoReturn.push(tuple); - } - } - } - } - proceduresNoReturn.sort(Blockly.Procedures.procTupleComparator_); - proceduresReturn.sort(Blockly.Procedures.procTupleComparator_); - return [proceduresNoReturn, proceduresReturn]; -}; +import * as Blockly from 'blockly/core'; +import * as Constants from './constants.js'; +import * as scratchBlocksUtils from '../core/scratch_blocks_utils.js'; /** * Find all user-created procedure definition mutations in a workspace. @@ -80,11 +33,11 @@ Blockly.Procedures.allProcedures = function(root) { * @return {!Array.} Array of mutation xml elements. * @package */ -Blockly.Procedures.allProcedureMutations = function(root) { +function allProcedureMutations(root) { var blocks = root.getAllBlocks(); var mutations = []; for (var i = 0; i < blocks.length; i++) { - if (blocks[i].type == Blockly.PROCEDURES_PROTOTYPE_BLOCK_TYPE) { + if (blocks[i].type == Constants.PROCEDURES_PROTOTYPE_BLOCK_TYPE) { var mutation = blocks[i].mutationToDom(/* opt_generateShadows */ true); if (mutation) { mutations.push(mutation); @@ -92,7 +45,7 @@ Blockly.Procedures.allProcedureMutations = function(root) { } } return mutations; -}; +} /** * Sorts an array of procedure definition mutations alphabetically. @@ -101,144 +54,45 @@ Blockly.Procedures.allProcedureMutations = function(root) { * @return {!Array.} Sorted array of mutation xml elements. * @private */ -Blockly.Procedures.sortProcedureMutations_ = function(mutations) { +function sortProcedureMutations_(mutations) { var newMutations = mutations.slice(); newMutations.sort(function(a, b) { var procCodeA = a.getAttribute('proccode'); var procCodeB = b.getAttribute('proccode'); - return Blockly.scratchBlocksUtils.compareStrings(procCodeA, procCodeB); + return scratchBlocksUtils.compareStrings(procCodeA, procCodeB); }); return newMutations; -}; - -/** - * Comparison function for case-insensitive sorting of the first element of - * a tuple. - * @param {!Array} ta First tuple. - * @param {!Array} tb Second tuple. - * @return {number} -1, 0, or 1 to signify greater than, equality, or less than. - * @private - */ -Blockly.Procedures.procTupleComparator_ = function(ta, tb) { - return Blockly.scratchBlocksUtils.compareStrings(ta[0], tb[0]); -}; - -/** - * Ensure two identically-named procedures don't exist. - * @param {string} name Proposed procedure name. - * @param {!Blockly.Block} block Block to disambiguate. - * @return {string} Non-colliding name. - */ -Blockly.Procedures.findLegalName = function(name, block) { - if (block.isInFlyout) { - // Flyouts can have multiple procedures called 'do something'. - return name; - } - while (!Blockly.Procedures.isLegalName_(name, block.workspace, block)) { - // Collision with another procedure. - var r = name.match(/^(.*?)(\d+)$/); - if (!r) { - name += '2'; - } else { - name = r[1] + (parseInt(r[2], 10) + 1); - } - } - return name; -}; - -/** - * Does this procedure have a legal name? Illegal names include names of - * procedures already defined. - * @param {string} name The questionable name. - * @param {!Blockly.Workspace} workspace The workspace to scan for collisions. - * @param {Blockly.Block=} opt_exclude Optional block to exclude from - * comparisons (one doesn't want to collide with oneself). - * @return {boolean} True if the name is legal. - * @private - */ -Blockly.Procedures.isLegalName_ = function(name, workspace, opt_exclude) { - return !Blockly.Procedures.isNameUsed(name, workspace, opt_exclude); -}; - -/** - * Return if the given name is already a procedure name. - * @param {string} name The questionable name. - * @param {!Blockly.Workspace} workspace The workspace to scan for collisions. - * @param {Blockly.Block=} opt_exclude Optional block to exclude from - * comparisons (one doesn't want to collide with oneself). - * @return {boolean} True if the name is used, otherwise return false. - */ -Blockly.Procedures.isNameUsed = function(name, workspace, opt_exclude) { - var blocks = workspace.getAllBlocks(); - // Iterate through every block and check the name. - for (var i = 0; i < blocks.length; i++) { - if (blocks[i] == opt_exclude) { - continue; - } - if (blocks[i].getProcedureDef) { - var procName = blocks[i].getProcedureDef(); - if (Blockly.Names.equals(procName[0], name)) { - return false; - } - } - } - return true; -}; - -/** - * Rename a procedure. Called by the editable field. - * @param {string} name The proposed new name. - * @return {string} The accepted name. - * @this {Blockly.Field} - */ -Blockly.Procedures.rename = function(name) { - // Strip leading and trailing whitespace. Beyond this, all names are legal. - name = name.replace(/^[\s\xa0]+|[\s\xa0]+$/g, ''); - - // Ensure two identically-named procedures don't exist. - var legalName = Blockly.Procedures.findLegalName(name, this.sourceBlock_); - var oldName = this.text_; - if (oldName != name && oldName != legalName) { - // Rename any callers. - var blocks = this.sourceBlock_.workspace.getAllBlocks(); - for (var i = 0; i < blocks.length; i++) { - if (blocks[i].renameProcedure) { - blocks[i].renameProcedure(oldName, legalName); - } - } - } - return legalName; -}; +} /** * Construct the blocks required by the flyout for the procedure category. - * @param {!Blockly.Workspace} workspace The workspace contianing procedures. + * @param {!Blockly.Workspace} workspace The workspace containing procedures. * @return {!Array.} Array of XML block elements. */ -Blockly.Procedures.flyoutCategory = function(workspace) { +function getProceduresCategory(workspace) { var xmlList = []; - Blockly.Procedures.addCreateButton_(workspace, xmlList); + addCreateButton_(workspace, xmlList); // Create call blocks for each procedure defined in the workspace - var mutations = Blockly.Procedures.allProcedureMutations(workspace); - mutations = Blockly.Procedures.sortProcedureMutations_(mutations); + var mutations = allProcedureMutations(workspace); + mutations = sortProcedureMutations_(mutations); for (var i = 0; i < mutations.length; i++) { var mutation = mutations[i]; // // // - var block = goog.dom.createDom('block'); + var block = document.createElement('block'); block.setAttribute('type', 'procedures_call'); block.setAttribute('gap', 16); block.appendChild(mutation); xmlList.push(block); } return xmlList; -}; +} /** * Create the "Make a Block..." button. @@ -246,18 +100,18 @@ Blockly.Procedures.flyoutCategory = function(workspace) { * @param {!Array.} xmlList Array of XML block elements to add to. * @private */ -Blockly.Procedures.addCreateButton_ = function(workspace, xmlList) { - var button = goog.dom.createDom('button'); +function addCreateButton_(workspace, xmlList) { + var button = document.createElement('button'); var msg = Blockly.Msg.NEW_PROCEDURE; var callbackKey = 'CREATE_PROCEDURE'; var callback = function() { - Blockly.Procedures.createProcedureDefCallback_(workspace); + createProcedureDefCallback_(workspace); }; button.setAttribute('text', msg); button.setAttribute('callbackKey', callbackKey); workspace.registerButtonCallback(callbackKey, callback); xmlList.push(button); -}; +} /** * Find all callers of a named procedure. @@ -271,7 +125,7 @@ Blockly.Procedures.addCreateButton_ = function(workspace, xmlList) { * @return {!Array.} Array of caller blocks. * @package */ -Blockly.Procedures.getCallers = function(name, ws, definitionRoot, +function getCallers(name, ws, definitionRoot, allowRecursive) { var allBlocks = []; var topBlocks = ws.getTopBlocks(); @@ -288,7 +142,7 @@ Blockly.Procedures.getCallers = function(name, ws, definitionRoot, var callers = []; for (var i = 0; i < allBlocks.length; i++) { var block = allBlocks[i]; - if (block.type == Blockly.PROCEDURES_CALL_BLOCK_TYPE ) { + if (block.type == Constants.PROCEDURES_CALL_BLOCK_TYPE ) { var procCode = block.getProcCode(); if (procCode && procCode == name) { callers.push(block); @@ -296,7 +150,7 @@ Blockly.Procedures.getCallers = function(name, ws, definitionRoot, } } return callers; -}; +} /** * Find and edit all callers with a procCode using a new mutation. @@ -305,11 +159,11 @@ Blockly.Procedures.getCallers = function(name, ws, definitionRoot, * @param {!Element} mutation New mutation for the callers. * @package */ -Blockly.Procedures.mutateCallersAndPrototype = function(name, ws, mutation) { - var defineBlock = Blockly.Procedures.getDefineBlock(name, ws); - var prototypeBlock = Blockly.Procedures.getPrototypeBlock(name, ws); +function mutateCallersAndPrototype(name, ws, mutation) { + var defineBlock = getDefineBlock(name, ws); + var prototypeBlock = getPrototypeBlock(name, ws); if (defineBlock && prototypeBlock) { - var callers = Blockly.Procedures.getCallers(name, + var callers = getCallers(name, defineBlock.workspace, defineBlock, true /* allowRecursive */); callers.push(prototypeBlock); Blockly.Events.setGroup(true); @@ -328,7 +182,7 @@ Blockly.Procedures.mutateCallersAndPrototype = function(name, ws, mutation) { } else { alert('No define block on workspace'); // TODO decide what to do about this. } -}; +} /** * Find the definition block for the named procedure. @@ -337,11 +191,11 @@ Blockly.Procedures.mutateCallersAndPrototype = function(name, ws, mutation) { * @return {Blockly.Block} The procedure definition block, or null not found. * @package */ -Blockly.Procedures.getDefineBlock = function(procCode, workspace) { +function getDefineBlock(procCode, workspace) { // Assume that a procedure definition is a top block. var blocks = workspace.getTopBlocks(false); for (var i = 0; i < blocks.length; i++) { - if (blocks[i].type == Blockly.PROCEDURES_DEFINITION_BLOCK_TYPE) { + if (blocks[i].type == Constants.PROCEDURES_DEFINITION_BLOCK_TYPE) { var prototypeBlock = blocks[i].getInput('custom_block').connection.targetBlock(); if (prototypeBlock.getProcCode && prototypeBlock.getProcCode() == procCode) { return blocks[i]; @@ -349,7 +203,7 @@ Blockly.Procedures.getDefineBlock = function(procCode, workspace) { } } return null; -}; +} /** * Find the prototype block for the named procedure. @@ -358,20 +212,20 @@ Blockly.Procedures.getDefineBlock = function(procCode, workspace) { * @return {Blockly.Block} The procedure prototype block, or null not found. * @package */ -Blockly.Procedures.getPrototypeBlock = function(procCode, workspace) { - var defineBlock = Blockly.Procedures.getDefineBlock(procCode, workspace); +function getPrototypeBlock(procCode, workspace) { + var defineBlock = getDefineBlock(procCode, workspace); if (defineBlock) { return defineBlock.getInput('custom_block').connection.targetBlock(); } return null; -}; +} /** * Create a mutation for a brand new custom procedure. * @return {Element} The mutation for a new custom procedure * @package */ -Blockly.Procedures.newProcedureMutation = function() { +function newProcedureMutation() { var mutationText = '' + '' + '' + ''; - return Blockly.Xml.textToDom(mutationText).firstChild; -}; + return Blockly.utils.xml.textToDom(mutationText).firstChild; +} /** * Callback to create a new procedure custom command block. * @param {!Blockly.Workspace} workspace The workspace to create the new procedure on. * @private */ -Blockly.Procedures.createProcedureDefCallback_ = function(workspace) { - Blockly.Procedures.externalProcedureDefCallback( - Blockly.Procedures.newProcedureMutation(), - Blockly.Procedures.createProcedureCallbackFactory_(workspace) +function createProcedureDefCallback_(workspace) { + ScratchProcedures.externalProcedureDefCallback( + newProcedureMutation(), + createProcedureCallbackFactory_(workspace) ); -}; +} /** * Callback factory for adding a new custom procedure from a mutation. @@ -402,7 +256,7 @@ Blockly.Procedures.createProcedureDefCallback_ = function(workspace) { * @return {function(?Element)} callback for creating the new custom procedure. * @private */ -Blockly.Procedures.createProcedureCallbackFactory_ = function(workspace) { +function createProcedureCallbackFactory_(workspace) { return function(mutation) { if (mutation) { var blockText = '' + @@ -414,7 +268,7 @@ Blockly.Procedures.createProcedureCallbackFactory_ = function(workspace) { '' + '' + ''; - var blockDom = Blockly.Xml.textToDom(blockText).firstChild; + var blockDom = Blockly.utils.xml.textToDom(blockText).firstChild; Blockly.Events.setGroup(true); var block = Blockly.Xml.domToBlock(blockDom, workspace); var scale = workspace.scale; // To convert from pixel units to workspace units @@ -431,17 +285,17 @@ Blockly.Procedures.createProcedureCallbackFactory_ = function(workspace) { Blockly.Events.setGroup(false); } }; -}; +} /** * Callback to open the modal for editing custom procedures. * @param {!Blockly.Block} block The block that was right-clicked. * @private */ -Blockly.Procedures.editProcedureCallback_ = function(block) { +function editProcedureCallback_(block) { // Edit can come from one of three block types (call, define, prototype) // Normalize by setting the block to the prototype block for the procedure. - if (block.type == Blockly.PROCEDURES_DEFINITION_BLOCK_TYPE) { + if (block.type == Constants.PROCEDURES_DEFINITION_BLOCK_TYPE) { var input = block.getInput('custom_block'); if (!input) { alert('Bad input'); // TODO: Decide what to do about this. @@ -454,25 +308,25 @@ Blockly.Procedures.editProcedureCallback_ = function(block) { } var innerBlock = conn.targetBlock(); if (!innerBlock || - !innerBlock.type == Blockly.PROCEDURES_PROTOTYPE_BLOCK_TYPE) { + !innerBlock.type == Constants.PROCEDURES_PROTOTYPE_BLOCK_TYPE) { alert('Bad inner block'); // TODO: Decide what to do about this. return; } block = innerBlock; - } else if (block.type == Blockly.PROCEDURES_CALL_BLOCK_TYPE) { + } else if (block.type == Constants.PROCEDURES_CALL_BLOCK_TYPE) { // This is a call block, find the prototype corresponding to the procCode. // Make sure to search the correct workspace, call block can be in flyout. var workspaceToSearch = block.workspace.isFlyout ? block.workspace.targetWorkspace : block.workspace; - block = Blockly.Procedures.getPrototypeBlock( + block = getPrototypeBlock( block.getProcCode(), workspaceToSearch); } // Block now refers to the procedure prototype block, it is safe to proceed. - Blockly.Procedures.externalProcedureDefCallback( + ScratchProcedures.externalProcedureDefCallback( block.mutationToDom(), - Blockly.Procedures.editProcedureCallbackFactory_(block) + editProcedureCallbackFactory_(block) ); -}; +} /** * Callback factory for editing an existing custom procedure. @@ -480,22 +334,14 @@ Blockly.Procedures.editProcedureCallback_ = function(block) { * @return {function(?Element)} Callback for editing the custom procedure. * @private */ -Blockly.Procedures.editProcedureCallbackFactory_ = function(block) { +function editProcedureCallbackFactory_(block) { return function(mutation) { if (mutation) { - Blockly.Procedures.mutateCallersAndPrototype(block.getProcCode(), + mutateCallersAndPrototype(block.getProcCode(), block.workspace, mutation); } }; -}; - -/** - * Callback to create a new procedure custom command block. - * @public - */ -Blockly.Procedures.externalProcedureDefCallback = function(/** mutator, callback */) { - alert('External procedure editor must be override Blockly.Procedures.externalProcedureDefCallback'); -}; +} /** * Make a context menu option for editing a custom procedure. @@ -505,46 +351,16 @@ Blockly.Procedures.externalProcedureDefCallback = function(/** mutator, callback * @return {!Object} A menu option, containing text, enabled, and a callback. * @package */ -Blockly.Procedures.makeEditOption = function(block) { +function makeEditOption(block) { var editOption = { enabled: true, text: Blockly.Msg.EDIT_PROCEDURE, callback: function() { - Blockly.Procedures.editProcedureCallback_(block); + editProcedureCallback_(block); } }; return editOption; -}; - -/** - * Callback to show the procedure definition corresponding to a custom command - * block. - * TODO(#1136): Implement. - * @param {!Blockly.Block} block The block that was right-clicked. - * @private - */ -Blockly.Procedures.showProcedureDefCallback_ = function(block) { - alert('TODO(#1136): implement showing procedure definition (procCode was "' + - block.procCode_ + '")'); -}; - -/** - * Make a context menu option for showing the definition for a custom procedure, - * based on a right-click on a custom command block. - * @param {!Blockly.BlockSvg} block The block where the right-click originated. - * @return {!Object} A menu option, containing text, enabled, and a callback. - * @package - */ -Blockly.Procedures.makeShowDefinitionOption = function(block) { - var option = { - enabled: true, - text: Blockly.Msg.SHOW_PROCEDURE_DEFINITION, - callback: function() { - Blockly.Procedures.showProcedureDefCallback_(block); - } - }; - return option; -}; +} /** * Callback to try to delete a custom block definitions. @@ -554,9 +370,9 @@ Blockly.Procedures.makeShowDefinitionOption = function(block) { * @return {boolean} True if the custom procedure was deleted, false otherwise. * @package */ -Blockly.Procedures.deleteProcedureDefCallback = function(procCode, +function deleteProcedureDefCallback(procCode, definitionRoot) { - var callers = Blockly.Procedures.getCallers(procCode, + var callers = getCallers(procCode, definitionRoot.workspace, definitionRoot, false /* allowRecursive */); if (callers.length > 0) { return false; @@ -574,4 +390,13 @@ Blockly.Procedures.deleteProcedureDefCallback = function(procCode, workspace.refreshToolboxSelection_(); return true; +} + +const ScratchProcedures = { + externalProcedureDefCallback: null, + createProcedureDefCallback, + deleteProcedureDefCallback, + getProceduresCategory, + makeEditOption, }; +export {ScratchProcedures};