app/assets/htmls/gs-element-blockly.html in gobstones-blockly-0.6.0 vs app/assets/htmls/gs-element-blockly.html in gobstones-blockly-0.8.5

- old
+ new

@@ -107,707 +107,10 @@ <block type="AsociacionDeTeclaCompletar"></block> </category> </xml> </template> - <script> - Polymer({ - is: 'gs-element-blockly', - - properties: { - /* - * `primitiveProcedures` lista de definiciones de procedimientos primitivos. - * Ej: ["Poner_FloresAl_"] Se transormara en un bloque con dos inputs inline, cuyo - * codigo sera Poner_FloresAl_(arg1, arg2). - */ - primitiveProcedures: { - type: Array, - observer: '_onPrimitiveProceduresChange' - }, - - /* - * `primitiveFunctions` lista de definiciones de funciones primitivas. - * Ej: ["cantidadDeFlores_en_"] Se transormara en un bloque con dos inputs inline, cuyo - * codigo sera cantidadDeFlores_en_(arg1, arg2). - */ - primitiveFunctions: { - type: Array, - observer: '_onPrimitiveFunctionsChange' - }, - - /* - * `toolbox` Definicion de visibilidad y habilitación de los bloques en el toolbox. - * Pueden nombrarse bloques individuales o categorias. - * - * Ej: - * - * ```json - * { - * "visible": ["Comandos primitivos", "Literales", "Expresiones"], - * "disabled": ["Poner", "hayBolitas", "Literales"] - * } - * ``` - * - * Los IDs de los bloques a utilizar pueden consultarse [en la wiki](https://github.com/Program-AR/gs-element-blockly/wiki/Block-IDs-for-toolbox) - * - * Nota: Los bloques de primitiveProcedures siempre aparecen en el toolbox, aunque no figuren - * en esta propiedad. - */ - toolbox: { - type: Object, - observer: '_onUpdateToolbox' - }, - - /* - * `workspaceXml` Código XML de los bloques en el workspace. - */ - workspaceXml: { - type: String, - observer: '_onWorkspaceUpdate', - notify: true - }, - - /* - * `workspaceCanEdit` Indica si el usuario puede modificar el workspace. - */ - workspaceCanEdit: { - type: Boolean, - }, - - /* - * `width` Ancho del elemento. - */ - width: { - type: Number, - observer: '_fixSize' - }, - - /* - * `height` Alto del elemento. - */ - height: { - type: Number, - observer: '_fixSize' - }, - - /* - * `media` path a media de blockly. - */ - media: { - type: String, - value: "../bower_components/blockly-package/media/" - }, - }, - - _onPrimitiveProceduresChange: function() { - if(typeof this.primitiveProcedures == 'string') { - this.primitiveProcedures = JSON.parse(this.primitiveProcedures); - return; - } - this._definePrimitiveProcedures(); - this._onUpdateToolbox(); - }, - - _onPrimitiveFunctionsChange: function() { - if(typeof this.primitiveFunctions == 'string') { - this.primitiveFunctions = JSON.parse(this.primitiveFunctions); - return; - } - this._definePrimitiveFunctions(); - this._onUpdateToolbox(); - }, - - _onUpdateToolbox: function() { - if(typeof this.toolbox == 'string') { - if(this.toolbox == '') { - this.toolbox = undefined; - } - else { - this.toolbox = JSON.parse(this.toolbox); - } - return; - } - let toolbox = this._createToolbox(); - this.workspace.updateToolbox(toolbox); - }, - - /** - * Retorna el arbol por defecto de bloques y categorias - */ - _defaultToolboxTree : function() { - let tree = [] - let toolboxDefault = this.$$('#toolbox'); - let toolboxDefaultLines = toolboxDefault.innerHTML.split("\n"); - let ignore_last = false; - let parent = tree; - let stack = []; - for(var i in toolboxDefaultLines) { - let line = toolboxDefaultLines[i]; - if(line.indexOf('<block') >= 0) { - let m = line.match('type="([^"]*)"') - if(m.length == 2) { - parent.push({ - type: 'block', - name: m[1], - }); - } - } - else if(line.indexOf('<category') >= 0) { - let m = line.match('name="([^"]*)"') - if(m.length == 2 && ( - (m[1] == 'Procedimientos primitivos' && this.primitiveProcedures && this.primitiveProcedures.length > 0) || - (m[1] == 'Funciones primitivas' && this.primitiveFunctions && this.primitiveFunctions.length > 0) || - (m[1] != 'Procedimientos primitivos' && m[1] != 'Funciones primitivas'))) { - parent.push({ - type: 'category', - name: m[1], - child: [], - xml: line - }); - stack.push(parent); - parent = parent[parent.length - 1].child; - if(m[1] == 'Procedimientos primitivos') - { - for(var i in this.primitiveProcedures) { - parent.push({ - type: 'block', - name: this.primitiveProcedures[i], - }); - } - } - if(m[1] == 'Funciones primitivas') - { - for(var i in this.primitiveFunctions) { - parent.push({ - type: 'block', - name: this.primitiveFunctions[i], - }); - } - } - } - else { - ignore_last = true; - } - } - else if(line.indexOf('</category') >= 0) { - if(ignore_last) { - ignore_last = false; - } - else { - parent = stack.pop(); - } - } - } - - return tree; - }, - - /** - * Sirve para manejar los alias de los ID que vengan por la interfaz. - * Recibe una lista que puede ser de IDs de bloques ó de categorías. - * Devuelve una lista similar, intercambiando los alias por los ids "oficiales". - * Mi idea acá también es que a futuro se haga la internacionalización de ids. - */ - _homogenizeIDs: function(ids){ - return ids ? ids.map(id => Blockly.GobstonesLanguage.aliasForBlockID(id)) : []; - }, - - /** - * Crea el toolbox a partir de la propiedad blocks, bloques visibles, y - * customblocks. - * - * Funciona podando el toolbox por defecto. - */ - _createToolbox: function() { - let tree = this._defaultToolboxTree(); - let toolbox = []; - - if(this.toolbox) { - let visibles = this._homogenizeIDs(this.toolbox.visible); - let disabled = this._homogenizeIDs(this.toolbox.disabled); - if(visibles.length > 0) { - visibles = visibles.concat(disabled); - if(this.primitiveProcedures) { - visibles = visibles.concat(this.primitiveProcedures); - } - if(this.primitiveFunctions) { - visibles = visibles.concat(this.primitiveFunctions); - } - } - - function filtrarToolboxTree(ttree) { - let tbox = []; - for(var i in ttree) { - if(visibles.length == 0 || visibles.indexOf(ttree[i].name) >= 0) { - tbox.push(ttree[i]); - } - else if(ttree[i].type == 'category') { - let tmp = filtrarToolboxTree(ttree[i].child); - if(tmp.length > 0) { - ttree[i].child = tmp; - tbox.push(ttree[i]); - } - } - } - return tbox; - } - - toolbox = filtrarToolboxTree(tree); - - // Marcar disabled - function marcarDisabled(tbox) { - for(var i in tbox) { - if(disabled.indexOf(tbox[i].name) >= 0) { - tbox[i].disabled = true; - } - if(tbox[i].child) { - marcarDisabled(tbox[i].child); - } - } - } - marcarDisabled(toolbox); - - } - else { - toolbox = tree; - } - - return this._toolboxTreeToXML(toolbox); - }, - - _toolboxBlockXML: function(blockDef, forceDisabled) { - let args = ""; - if(blockDef.disabled || forceDisabled) { - args = 'disabled="true"'; - } - let xml = `<block type="${blockDef.name}" ${args}></block>`; - return xml; - }, - - _toolboxTreeToXML : function(toolbox, noXmlTags, disabled) { - let xml = []; - if(!noXmlTags) { - xml.push('<xml>'); - } - for(var i in toolbox) { - if(toolbox[i].type == 'block') { - xml.push(this._toolboxBlockXML(toolbox[i], disabled)); - } - else if(toolbox[i].type == 'category') { - xml.push(toolbox[i].xml); - xml.push(this._toolboxTreeToXML(toolbox[i].child, true, toolbox[i].disabled || disabled)); - xml.push('</category>'); - } - } - if(!noXmlTags) { - xml.push('</xml>'); - } - return xml.join('\n'); - }, - - /** - * Define un bloque a partir de una definicion tipo Poner_En_ - */ - _definePrimitiveProcedure: function(def) { - let parts = def.replace(/([A-Z])/g, " $1").toLowerCase(); - parts = parts[1].toUpperCase() + parts.substring(2); - parts = parts.split("_"); - - // Bloque - Blockly.Blocks[def] = { - init: function () { - let argsIndex = 1; - this.setColour(CommandColor); - for(var i in parts) { - if(i == (parts.length - 1)) { - this.appendDummyInput().appendField(parts[i]); - } - else { - this.appendValueInput('arg' + argsIndex).appendField(parts[i]); - argsIndex++; - } - } - this.setPreviousStatement(true); - this.setNextStatement(true); - this.setInputsInline(true); - } - }; - let argsList = []; - for(var i=1;i<parts.length;i++) { - argsList.push('arg' + i); - } - // Generator - Blockly.GobstonesLanguage[def] = procBlockCodeGenerator(def, argsList); - }, - - _definePrimitiveProcedures: function () { - if(!this.primitiveProcedures) { - return; - } - - for(var i in this.primitiveProcedures) { - this._definePrimitiveProcedure(this.primitiveProcedures[i]); - } - }, - - - /** - * Define un bloque a partir de una definicion de funcion primitiva tipo hayFlores_en_ - */ - _definePrimitiveFunction: function(def) { - let parts = def.replace(/([A-Z])/g, " $1").toLowerCase(); - //let parts = def; - //parts = parts[1].toUpperCase() + parts.substring(2); - parts = parts.split("_"); - - // Bloque - Blockly.Blocks[def] = { - init: function () { - let argsIndex = 1; - this.setColour(CommandColor); - for(var i in parts) { - if(i == (parts.length - 1)) { - this.appendDummyInput().appendField(parts[i]); - } - else { - this.appendValueInput('arg' + argsIndex).appendField(parts[i]); - argsIndex++; - } - } - this.setPreviousStatement(false); - this.setNextStatement(false); - this.setInputsInline(true); - this.setOutput('var'); - } - }; - let argsList = []; - for(var i=1;i<parts.length;i++) { - argsList.push('arg' + i); - } - // Generator - Blockly.GobstonesLanguage[def] = functionBlockCodeGenerator(def, argsList); - }, - - _definePrimitiveFunctions: function () { - if(!this.primitiveFunctions) { - return; - } - - for(var i in this.primitiveFunctions) { - this._definePrimitiveFunction(this.primitiveFunctions[i]); - } - }, - - _onBlocklyWorkspaceUpdate: function () { - let xml = Blockly.Xml.workspaceToDom(this.workspace); - this._blocklyWorkspaceXML = Blockly.Xml.domToText(xml); - this.workspaceXml = this._blocklyWorkspaceXML; - this._keepOnlyAProgram(xml); - this._checkParameterBounds(xml); - }, - - _keepOnlyAProgram(xml) { - const findProgram = (programType) => { - const children = xml.children; - const items = []; - for (var i=0; i < children.length; i++) - if (children[i].getAttribute("type") === programType) - items.push(children[i]); - return items; - }; - - const block = (blockXml) => this.workspace.getBlockById(blockXml.getAttribute("id")); - - const programXmls = findProgram("Program"); - const interactiveProgramXmls = findProgram("InteractiveProgram"); - - // repeated blocks - if (programXmls.length > 1) { - // (en vez de .undo() se podría hacerle .dispose() al bloque) - - if (block(programXmls[0]).$timestamp > block(programXmls[1]).$timestamp) { - // delete programXmls[0] - this.workspace.undo(); - programXmls.shift(); - } else { - // delete programXmls[1] - this.workspace.undo(); - programXmls.pop(); - } - } - if (interactiveProgramXmls.length > 1) { - if (block(interactiveProgramXmls[0]).$timestamp > block(interactiveProgramXmls[1]).$timestamp) { - // delete interactiveProgramXmls[0] - this.workspace.undo(); - interactiveProgramXmls.shift(); - } else { - // delete interactiveProgramXmls[1] - this.workspace.undo(); - interactiveProgramXmls.pop(); - } - } - - const program = programXmls[0] && block(programXmls[0]); - const interactiveProgram = interactiveProgramXmls[0] && block(interactiveProgramXmls[0]); - - if (program && interactiveProgram) { - program.setDeletable(true); - interactiveProgram.setDeletable(true); - - const important = program.$timestamp > interactiveProgram.$timestamp ? program : interactiveProgram; - const unimportant = program.$timestamp > interactiveProgram.$timestamp ? interactiveProgram : program; - - const twoActivePrograms = !program.disabled && !interactiveProgram.disabled; - const twoDisabledPrograms = program.disabled && interactiveProgram.disabled; - - if (twoActivePrograms || twoDisabledPrograms) { - important.setDisabledAndUpdateTimestamp(false); - unimportant.setDisabledAndUpdateTimestamp(true); - } - } else if (program && !interactiveProgram) { - program.setDisabledAndUpdateTimestamp(false); - program.setDeletable(false); - } else if (interactiveProgram && !program) { - interactiveProgram.setDisabledAndUpdateTimestamp(false); - interactiveProgram.setDeletable(false); - } - }, - - _checkParameterBounds(xml) { - const blocks = this.workspace.getAllBlocks(); - for (block of blocks) { - if (block.type === "variables_get" && block.$parent !== null) { - const parentBlock = this.workspace.getBlockById(block.$parent); - const varField = block.getField("VAR"); - - if ( - parentBlock && - ( - !parentBlock.type.startsWith("procedures") || - parentBlock.arguments_.some(it => it === varField.getValue()) - ) - ) { - var parent = block; - while ((parent = parent.getSurroundParent()) !== null) { - if (parent.id === block.$parent) break; - } - - block.setDisabled(parent === null); - } else { - block.dispose(); - } - } - } - }, - - _onWorkspaceUpdate: function () { - if(this.workspaceXml != this._blocklyWorkspaceXML) { - this.workspace.clear(); - let dom = Blockly.Xml.textToDom(this.workspaceXml); - try { - Blockly.Xml.domToWorkspace(dom, this.workspace); - } catch (e) { - if(e.message.includes("nextConnection is null")){ - throw { - name: "BlockTypeError", - message: "There is at least one block type declared in the 'type' property of the block tag of the xml, which is incorrect or belongs to a block that is not in the Gobstones Language. Maybe you have forgotten any primitiveProcedure?", - toString: function(){ return this.name + ': ' + this.message; } - }; - } else { - throw e; - } - } - } - }, - - /** - * Reinicializa el workspace, dejando solo el bloque programa - */ - resetWorkspace: function() { - let xmlInicial = '<xml xmlns="http://www.w3.org/1999/xhtml"><block type="Program" x="30" y="30"></block></xml>'; - this.workspaceXml = xmlInicial; - }, - - /** - * Retorna una lista de objetos, uno por cada categoría y bloque. - * Representa la jerarquía del toolbox. - * - * Para los bloques se indica: - * - * * `blockXMLID`: El ID del bloque que estará en el XML exportado por el método - * `generateCode`. - * * `blockAliases`: La lista de aliases que refieren a ese bloque, y que pueden - * ser usados para definir el toolbox mediante la propiedad `toolbox`. - * - * Para las categorías se indica: - * - * * `categoryName`: El nombre de la categoría que es visible en el toolbox. - * * `categoryAliases`: La lista de aliases que refieren a esa categoría, y que pueden - * ser usados para definir el toolbox mediante la propiedad `toolbox`. - * * `categoryContents`: La lista de objetos categoría/bloque dentro de esta categoría. - * - * @return {Array} Los objetos. - */ - validToolboxIDs: function(){ - return this._validToolboxIDsFrom(this._defaultToolboxTree()); - }, - - /** - * Recursively defined. Gets a list of user-readable objects describing the - * toolboxTree hierarchy and aliases. - */ - _validToolboxIDsFrom: function(toolboxTree) { - var myThis = this; - return this._mapToolboxTree(toolboxTree, function(toolboxElement){ - if(toolboxElement.type === "category"){ - return { - categoryName: toolboxElement.name, - categoryAliases: Blockly.GobstonesLanguage.aliasesFor(toolboxElement.name), - categoryContents: myThis._validToolboxIDsFrom(toolboxElement.child) - }; - } else if(toolboxElement.type === "block") { - return { - blockXMLID: toolboxElement.name, - blockAliases: Blockly.GobstonesLanguage.aliasesFor(toolboxElement.name), - }; - } else { - /*Do nothing for other types*/ - } - }); - }, - - _mapToolboxTree: function(toolboxTree, f){ - var resultingList = []; - for (var i in toolboxTree){ - resultingList.push(f(toolboxTree[i])); - } - return resultingList; - }, - - /** - * Reinicializa el estado del componente, - */ - cleanup: function() { - this.primitiveProcedures = []; - this.primitiveFunctions = []; - this.toolbox = {}; - this.resetWorkspace(); - }, - - // Element Lifecycle - ready: function() { - }, - - attached: function() { - this._definePrimitiveProcedures(); - this._definePrimitiveFunctions(); - // create workspace - var blocklyDiv = this.$$('#blocklyDiv'); - this.blocklyDiv = blocklyDiv; - this._fixSize(); - this.workspace = Blockly.inject(blocklyDiv, { - toolbox: this._createToolbox(), - media: this.get("media"), - toolboxPosition: "start", - scrollbars: true, - horizontalLayout: false, - collapse: true, - css: true, - zoom: { - controls: true, - wheel: true - } - }); - var _this = this; - this.workspace.addChangeListener(function (a, b, c) { - Blockly.Events.disableOrphans(a, b, c); - _this._onBlocklyWorkspaceUpdate(); - }); - this.resetWorkspace() - this._onresize(); - if(window.jQuery !== undefined) { - $(window).resize(() => this._onresize()); - }; - - Blockly.ErrorInforming.addToWorkspace(this.workspace); - }, - - _onresize() { - Blockly.svgResize(this.workspace); - }, - - _fixSize() { - let style = ""; - if(this.width && this.height) { - style += "width:"+ this.width + "px;"; - style += "height:"+ this.height + "px;"; - this.blocklyDiv.style = style; - this._onresize(); - } - }, - - detached: function() { - // The analog to `attached`, `detached` fires when the element has been - // removed from a document. - // - // Use this to clean up anything you did in `attached`. - }, - - // Element Behavior - - /** - * Generate gobstones code from the blocks in the workspace. - * It removes all highlighting and errors if already present. - * - * There is an additional option which if provided adds pragma BEGIN/END REGION - * to the output. - * * `element.generateCode( {withRegions: true} );` - * @return {string} The code. - */ - generateCode: function(options = {}) { - this.workspace.highlightBlock(); // No parameters means reset highlighting - this.workspace.removeBlockErrors(); - Blockly.GobstonesLanguage.shouldAddRegionPragma = options.withRegions; - return Blockly.GobstonesLanguage.workspaceToCode(this.workspace); - }, - - /** - * Append blocks to workspace. Se agrega al contenido actual del workspace - * los bloques especificados en el xml (descartando el program si es que viene). - * - */ - appendBlocksToWorkspace: function(xml) { - }, - - /** - * Highlight a given block by its ID. Se hace highlight del bloque indicado. - * - */ - highlightBlock: function(blockId) { - this.workspace.highlightBlock(blockId) - }, - - /** - * Highlight a given block, telling the user it has an error - * - * `blockId` is the block ID where the error should appear - * - * `errorKind` is either a string with the description or an object with a kind of error. - * Next are examples with the existent error kinds. - * - * Examples: - * * `element.showBlockError('a1s2', 'Hey, here is an error')` - * * `element.showBlockError('a1s2', { kind: 'INCOMPLETE_ERROR'} )` - * * `element.showBlockError('a1s2', { kind: 'TYPE_ERROR', expectedType:'string', actualType: 'boolean' })` - * * `element.showBlockError('a1s2', { kind: 'PRECONDITION_ERROR', description: "Susan can't move right" })` - */ - showBlockError: function(blockId, errorKind) { - this.workspace.showBlockError(blockId, errorKind); - }, - }); - </script> - <script>// Do not edit this file; automatically generated by build.py. 'use strict'; var COMPILED=!0,goog=goog||{};goog.global=this;goog.isDef=function(a){return void 0!==a};goog.isString=function(a){return"string"==typeof a};goog.isBoolean=function(a){return"boolean"==typeof a};goog.isNumber=function(a){return"number"==typeof a};goog.exportPath_=function(a,b,c){a=a.split(".");c=c||goog.global;a[0]in c||!c.execScript||c.execScript("var "+a[0]);for(var d;a.length&&(d=a.shift());)!a.length&&goog.isDef(b)?c[d]=b:c=c[d]&&c[d]!==Object.prototype[d]?c[d]:c[d]={}}; goog.define=function(a,b){var c=b;COMPILED||(goog.global.CLOSURE_UNCOMPILED_DEFINES&&void 0===goog.global.CLOSURE_UNCOMPILED_DEFINES.nodeType&&Object.prototype.hasOwnProperty.call(goog.global.CLOSURE_UNCOMPILED_DEFINES,a)?c=goog.global.CLOSURE_UNCOMPILED_DEFINES[a]:goog.global.CLOSURE_DEFINES&&void 0===goog.global.CLOSURE_DEFINES.nodeType&&Object.prototype.hasOwnProperty.call(goog.global.CLOSURE_DEFINES,a)&&(c=goog.global.CLOSURE_DEFINES[a]));goog.exportPath_(a,c)};goog.DEBUG=!1;goog.LOCALE="en"; @@ -4003,47 +3306,267 @@ * To add a new function type add this to your code: * Blockly.Blocks['procedures_ifreturn'].FUNCTION_TYPES.push('custom_func'); */ FUNCTION_TYPES: ['procedures_defnoreturn', 'procedures_defreturn'] };</script> - <script>function initProcedsBlockly(customStatementType) { + <script>var PLUS = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAQAAAD2e2DtAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAHdElNRQfhDAUCCjFLV0NqAAAC60lEQVR42u3dQW7aQABA0Wl7MMjJICeDnIwuqm4qVQrYjMH/Pa/jsfFnTJDwjAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMBUP7Y+gClOY4zznX9zHmN8bn3gLHcYtwXbYevDZ5nLost/G7dx2foUeNzyyy+BN7Zs8ncjeHvrvP/NAW9qvff/rueAn1sfwNMcX3hvL2S/3wPcVt7fTl+p/c4AfIsA4gQQJ4A4AcQJIE4AcQKIE0CcAOIEECeAOAHECSBOAHECiBNAnADiBBAngDgBxAkgTgBxAogTQJwA4gQQJ4A4AcQJIE4AcQKIE0CcAOIEECeAOAHECSBOAHECiBNAnADiBBAngDgBxAkgTgBxAogTQJwA4mYHcBinVRdz+v+2tjlHfdrv8lRjHFZcyG3P22VmBPOWQrrsd+WtJ7iOjzkDzQrA5b/XpATmBHAY1ynj7MtxfD1/kDkBrP+RrGHC1ZnxX8Bpwhj7NOGV8z1A3IxbgBvA455+fcwAcQKIE0CcAOIEECeAOAHECSBOAHECiBNAnADiBBAngDgBxAkgTgBxAogTQJwA4gQQJ4A4AcT9mjLKcevTfFPn5/860AwQ58ehr2wnPw51C3jMccYgcwL48nyAu11nPB3AI2Je1bRHxMz7EPgxjuaBb7mO46zLP3MG+OMwjuM8ecx3cp419f81O4B51v7PY6evlO8B4gQQJ4A4AcQJIE4AcQKIE0CcAOIEECeAOAHECSBOAHECiBNAnADiBBAngDgBxAkgTgBxAogTQJwA4gQQJ4A4AcQJIE4AcQKIE0CcAOIEECeAOAHECSBOAHECiBNAnADiBBAngDgBxAkgTgBxAogTQJwA4gQQN2fp2G0cV9zXhEVct7HfGeD6wntjisu4rbRdtj4VHnFYLYDD1qfCY9aZA7z/39jyBFz+N7fsRrD7yX+n62H+4zTG3QvWnscYn1sfOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALvzG8Ijm7EmMQYoAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE3LTEyLTA1VDAyOjEwOjQ5LTA1OjAwJa2zowAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNy0xMi0wNVQwMjoxMDo0OS0wNTowMFTwCx8AAAAASUVORK5CYII="; +var MINUS = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAQAAAD2e2DtAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAHdElNRQfhDAUCCi+xWH4JAAABcUlEQVR42u3c7ZGCMBSG0etuYcTKls7AyrSEVWd4+bjnUECMeSbhD6kCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIBzu4XHm2rUvPekD2yutR57/4itTLXU0/Pvs9SUW5TcDrDUyE3r9Na6ZwZKBWD5PxVKIBPAVGtknGsZibeBTADPyCjXE1idn8A0/gJjXFPgn0sEwIEljgAHwPc2Xx87QHMCaE4AzQmgOQE0J4DmBNCcAJoTQHMCaE4AzQmgOQE0J4DmBNCcAJoTQHMCaE4AzQmgOQE0J4DmBNDcb2SUsfc0T2re/utAO0BzPg49sot8HOoI+M5IDJIJ4OF+gI+F7gpyRcwxxa6Iyb0E3mvYB96y1kgtv2vijubS18QBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAWXq7xrTQhKAi3AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE3LTEyLTA1VDAyOjEwOjQ3LTA1OjAwdZLI/gAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNy0xMi0wNVQwMjoxMDo0Ny0wNTowMATPcEIAAAAASUVORK5CYII="; +var WAND = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAB41BMVEUAAACrhwmsiArFmQTtxjftxjjOnwThrQrirQvOmQToxETpxkfUnwS2hQLYohPYoxW/igOXagHKjgPNkQXUnxzfsjngtDzVoR/NkgbLjwOkcwHIiwbov1PJjQnAggPiskDkulXBgwO8fATnuEjpwVq9fgeCUwC3eAW4eAOLWQCQVwDGjBLIiQKWWwC9fgHBhAFlOgBhNQDCggHHigFwPQBoNQDIhgHDqADBqQDOkAF4PgBjLwDIhwLFjAGUUgCJRACIQgCSTwDCiwHNkAJ2OABSJgCcWgqPQgCCOgB8OACPQgCcWgthLQD06Xj16nn47YT14Hv35YD14Xzx1G744Hv44Xvy1m/00nX31nj32Hb323b43Xb43nb43Hb32nb313f103bpwlz2zG/30W/31W/42W/422/43W/43G/42m/412/302/3z2/3zmj402j42Gj43Gj53mj53Wj42mj41mj30WjswEr401/52V/53V/54F/64V/531/522H4zErtsir4yD350j352Tn63TL63Cj61Bn4xQL3twD3tgD4xgD61QD73wD74QD72wD5zgD3vgD3uAD5ywD73AD86wD97wD85AD61AD4wgD2tQD5yQD01gDz3AD60wD4vwDsrgHqswH////vUbxMAAAATXRSTlMABAQHwMIIen0c8fIeCa6yCgI5e7Hp67WAPwOD/otJ/PxLev7+fAKSlQIG3OEIy9IBA+HpBAj1/v77DAr58ZMtKo3u/Q4BcGcTEGJzAcqMNfMAAAABYktHRKBe076gAAAAB3RJTUUH4QwFFBQHSAc27gAAANRJREFUGNNjYIAARiYGFMDMwsqGIsDu68eBzOfk8vfn5kHwefkCAgOD+AXAHEEhYRHR4JDQ0JAwMXEJSSkGaZnwiMio6JiY6Ni4+IREWQY5+aTklNS09PSMzKzsHAVFBgYl5dy8/ILCwqLiklIVVaAhaupl5RWVVdU1tXX1GpogY7W0Gxqbmlta29p1dCG26nV0dnX39Pb16xuA+YZGEyZOmjxl6rTpxiZgAVOzGTNnmVvMnjPX0gosYG0zz9bO3sHRab6zC1jA1c3dA0R7enn7MDAAABs9NIkRbjxSAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE3LTEyLTA1VDIwOjIwOjA3LTA1OjAw6QjR4wAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNy0xMi0wNVQyMDoyMDowNy0wNTowMJhVaV8AAAAASUVORK5CYII="; + +function initProcedsBlockly(customStatementType) { Blockly.Msg.PROCEDURES_DEFNORETURN_COMMENT = 'Describe el procedimiento...'; Blockly.Msg.PROCEDURES_DEFNORETURN_PROCEDURE = "Hacer algo"; Blockly.Msg.PROCEDURES_DEFNORETURN_TITLE = "Definir"; Blockly.Msg.PROCEDURES_DEFNORETURN_NOPARAMS = ""; Blockly.Msg.PROCEDURES_DEFRETURN_NOPARAMS = ""; Blockly.Msg.PROCEDURES_DEFRETURN_COMMENT = 'Describe la función...'; Blockly.Msg.PROCEDURES_DEFRETURN_PROCEDURE = "devolver algo"; Blockly.Msg.PROCEDURES_DEFRETURN_TITLE = "Definir"; - Blockly.Msg.PROCEDURES_BEFORE_PARAMS = "con:"; + Blockly.Msg.PROCEDURES_BEFORE_PARAMS = "con"; Blockly.Msg.PROCEDURES_DEFNORETURN_TOOLTIP = "Crea un procedimiento."; Blockly.Msg.PROCEDURES_DEFRETURN_TOOLTIP = "Crea una función."; Blockly.Msg.PROCEDURES_ADD_PARAMETER = "Agregar parámetro"; Blockly.Msg.PROCEDURES_ADD_PARAMETER_PROMPT = "Ingresa el nombre del parámetro"; Blockly.Msg.PROCEDURES_REMOVE_PARAMETER = "Quitar parámetro"; + Blockly.Msg.PROCEDURES_PARAMETER = "parámetro"; - // -------------------------------- - // [!] Adding defaultName parameter - // -------------------------------- + // ------------------------------------- + // [!] Fixing FieldImage's click handler + // ------------------------------------- - var makeProcedureInit = function(withReturn, withStatements = true, withParametersMutator = false, defaultName, title, comment, tooltip, helpUrl) { + Blockly.FieldImage.prototype.init = function() { + if (this.fieldGroup_) { + // Image has already been initialized once. + return; + } + // Build the DOM. + /** @type {SVGElement} */ + this.fieldGroup_ = Blockly.utils.createSvgElement('g', {}, null); + if (!this.visible_) { + this.fieldGroup_.style.display = 'none'; + } + /** @type {SVGElement} */ + this.imageElement_ = Blockly.utils.createSvgElement( + 'image', + { + 'height': this.height_ + 'px', + 'width': this.width_ + 'px' + }, + this.fieldGroup_); + this.setValue(this.src_); + this.sourceBlock_.getSvgRoot().appendChild(this.fieldGroup_); + + // Configure the field to be transparent with respect to tooltips. + this.setTooltip(this.sourceBlock_); + Blockly.Tooltip.bindMouseEvents(this.imageElement_); + + var self = this; + + if (this.clickHandler_) { // [!] + this.imageElement_.addEventListener("mousedown", function(event) { + event.stopPropagation(); + event.preventDefault(); + + if (event.button === 0) { + self.clickHandler_(); + } + }); + } + }; + + // [!] Modifying callbackFactory to return the created block + Blockly.ContextMenu.callbackFactory = function(block, xml) { return function() { - var nameField = new Blockly.FieldTextInput(defaultName, // [!] - Blockly.Procedures.rename); + Blockly.Events.disable(); + try { + var newBlock = Blockly.Xml.domToBlock(xml, block.workspace); + // Move the new block next to the old block. + var xy = block.getRelativeToSurfaceXY(); + if (block.RTL) { + xy.x -= Blockly.SNAP_RADIUS; + } else { + xy.x += Blockly.SNAP_RADIUS; + } + xy.y += Blockly.SNAP_RADIUS * 2; + newBlock.moveBy(xy.x, xy.y); + } finally { + Blockly.Events.enable(); + } + if (Blockly.Events.isEnabled() && !newBlock.isShadow()) { + Blockly.Events.fire(new Blockly.Events.BlockCreate(newBlock)); + } + newBlock.select(); + + return newBlock; // [!] + }; + }; + + // -------------------------------------- + // [!] Adding custom procedure parameters + // -------------------------------------- + + var createParameterCaller = function(procedureBlock, name) { + var xmlField = goog.dom.createDom('field', null, name); + xmlField.setAttribute('name', 'VAR'); + var xmlBlock = goog.dom.createDom('block', null, xmlField); + xmlBlock.setAttribute('type', 'variables_get'); + + var callback = Blockly.ContextMenu.callbackFactory(procedureBlock, xmlBlock); + + return function() { + var block = callback(); + block.$parent = procedureBlock.id; + }; + }; + + var getAvailableName = function(self, name) { + var result = name; + while (self.arguments_.some(it => it === result)) + result += "_"; + return result; + } + + var addParameter = function(self, index, name) { + var i = index === undefined ? self.arguments_.length : index; + var tmpName = name === undefined ? Blockly.Msg.PROCEDURES_PARAMETER + " " + (i + 1) : name; + var name = index === undefined ? getAvailableName(self, tmpName) : tmpName; + var id = "INPUTARG" + i; + + if (index === undefined) { + self.arguments_.push(name); + self.updateParams_(); + + var blocks = self.workspace.getAllBlocks(); + for (block of blocks) + if (block.type === self.callType_ && block.getProcedureCall() === self.getProcedureDef()[0]) { + block.arguments_.push(name); + block.updateShape_(); + } + } + + var createCallButton = new Blockly.FieldImage( + WAND, + 16, + 16, + Blockly.Msg.VARIABLES_SET_CREATE_GET.replace('%1', name), + function() { + createParameterCaller(self, self.arguments_[i])(); + } + ); + + var removeParameterButton = new Blockly.FieldImage( + MINUS, + 16, + 16, + Blockly.Msg.PROCEDURES_REMOVE_PARAMETER, + function() { + for (var j = 0; j < self.arguments_.length; j++) + self.removeInput("INPUTARG" + j); + self.arguments_.splice(i, 1); + self.arguments_.forEach(function(name, i) { + addParameter(this, i, name, true); + }.bind(self)); + + var blocks = self.workspace.getAllBlocks(); + for (block of blocks) + if (block.type === self.callType_ && block.getProcedureCall() === self.getProcedureDef()[0]) { + block.arguments_.splice(i, 1); + block.updateShape_(); + } + } + ); + + var nameField = new Blockly.FieldTextInput(name, function(newName) { + var oldName = self.arguments_[i]; + + if (oldName !== newName) + newName = getAvailableName(self, newName); + + self.arguments_[i] = newName; + + var blocks = self.workspace.getAllBlocks(); + for (block of blocks) { + if (block.type === self.callType_ && block.getProcedureCall() === self.getProcedureDef()[0]) { + block.arguments_ = block.arguments_.map(function(it) { + return it === oldName ? newName : it; + }); + block.updateShape_(); + } + + if (block.type === "variables_get" && block.$parent === self.id) { + var varField = block.getField("VAR"); + if (varField.getValue() === oldName) { + varField.setValue(newName); + } + } + } + + return newName; + }); + + self + .appendDummyInput(id) + .appendField(Blockly.Msg.PROCEDURES_BEFORE_PARAMS) + .appendField(nameField, 'ARG' + i) + .appendField(createCallButton) + .appendField(removeParameterButton); + + self.moveInputBefore(id, 'STACK'); + }; + + var makeProcedureDomToMutation = function() { + return function(xmlElement) { + this.arguments_ = []; + for (var i = 0, childNode; childNode = xmlElement.childNodes[i]; i++) { + if (childNode.nodeName.toLowerCase() == 'arg') { + this.arguments_.push(childNode.getAttribute('name')); + } + } + this.updateParams_(); + Blockly.Procedures.mutateCallers(this); + + // Show or hide the statement input. + this.setStatements_(xmlElement.getAttribute('statements') !== 'false'); + + this.arguments_.forEach(function(name, i) { // [!] + addParameter(this, i, name); + }.bind(this)); + }; + } + + var makeProcedureInit = function(withReturn, withStatements = true, withParameters = false, defaultName, title, comment, tooltip, helpUrl) { + return function() { + var defaultLegalName = Blockly.Procedures.findLegalName(defaultName, this); + var nameField = new Blockly.FieldTextInput(defaultLegalName, Blockly.Procedures.rename); nameField.setSpellcheck(false); - this.appendDummyInput() + + var self = this; + + // [!] + var addParameterButton = new Blockly.FieldImage( + PLUS, + 16, + 16, + Blockly.Msg.PROCEDURES_ADD_PARAMETER, + function() { addParameter(self); } + ); + + var input = this.appendDummyInput() .appendField(title) .appendField(nameField, 'NAME') .appendField('', 'PARAMS'); + if (withParameters) + input.appendField(addParameterButton); + if (withReturn) this.appendValueInput('RETURN') .setAlign(Blockly.ALIGN_RIGHT) .appendField(Blockly.Msg.PROCEDURES_DEFRETURN_RETURN); - if (withParametersMutator) - this.setMutator(new Blockly.Mutator(['procedures_mutatorarg'])); + // if (withParametersMutator) + // this.setMutator(new Blockly.Mutator(['procedures_mutatorarg'])); if ((this.workspace.options.comments || (this.workspace.options.parentWorkspace && this.workspace.options.parentWorkspace.options.comments)) && comment) { @@ -4055,60 +3578,57 @@ this.setHelpUrl(helpUrl); this.arguments_ = []; this.setStatements_(withStatements); this.statementConnection_ = null; - if (!withParametersMutator) this.updateParams_(); + // if (!withParametersMutator) this.updateParams_(); }; }; - // --------------------------------------------------- - // [!] Using .unshift instead of .push for new options - // --------------------------------------------------- + // ----------------------- + // [!] Custom context menu + // ----------------------- var makeProcedureCustomMenu = function(withParametersOptions = true) { return function(options) { // Add options to create getters for each parameter. if (!this.isCollapsed()) { for (var i = this.arguments_.length - 1; i >= 0; i--) { var option = {enabled: true}; var name = this.arguments_[i]; option.text = Blockly.Msg.VARIABLES_SET_CREATE_GET.replace('%1', name); - var xmlField = goog.dom.createDom('field', null, name); - xmlField.setAttribute('name', 'VAR'); - var xmlBlock = goog.dom.createDom('block', null, xmlField); - xmlBlock.setAttribute('type', 'variables_get'); - option.callback = Blockly.ContextMenu.callbackFactory(this, xmlBlock); + option.callback = createParameterCaller(this, name); + options.unshift(option); } } // [!] - if (withParametersOptions) { - options.unshift({ - enabled: this.arguments_.length > 0, - text: Blockly.Msg.PROCEDURES_REMOVE_PARAMETER, - callback: function() { - this.arguments_.pop(); - this.updateParams_(); - }.bind(this) - }); + // if (withParametersOptions) { + // options.unshift({ + // enabled: this.arguments_.length > 0, + // text: Blockly.Msg.PROCEDURES_REMOVE_PARAMETER, + // callback: function() { + // this.arguments_.pop(); + // this.updateParams_(); + // }.bind(this) + // }); - options.unshift({ - enabled: true, - text: Blockly.Msg.PROCEDURES_ADD_PARAMETER, - callback: function() { - var name = ""; - while (name === "") // Rompe encapsulamiento - name = Blockly.Blocks['procedures_mutatorarg'].validator_(prompt(Blockly.Msg.PROCEDURES_ADD_PARAMETER_PROMPT)); - if (name === null) return; + // options.unshift({ + // enabled: true, + // text: Blockly.Msg.PROCEDURES_ADD_PARAMETER, + // callback: function() { + // var name = ""; + // while (name === "") // Rompe encapsulamiento + // name = Blockly.Blocks['procedures_mutatorarg'].validator_(prompt(Blockly.Msg.PROCEDURES_ADD_PARAMETER_PROMPT)); + // if (name === null) return; - this.arguments_.push(name); - this.updateParams_(); - }.bind(this) - }); - } + // this.arguments_.push(name); + // this.updateParams_(); + // }.bind(this) + // }); + // } // Add option to create caller. var option = {enabled: true}; var name = this.getFieldValue('NAME'); option.text = Blockly.Msg.PROCEDURES_CREATE_DO.replace('%1', name); @@ -4142,50 +3662,39 @@ nextStatement: customStatementType }); } }; - // ----------------------------------------- - // [!] Using PROCEDURES_BEFORE_PARAMS always - // ----------------------------------------- + // ------------------------------- + // [!] Patching default procedures + // ------------------------------- - var makeUpdateParams = function() { - return function() { - var paramsString = this.arguments_.length > 0 - ? Blockly.Msg.PROCEDURES_BEFORE_PARAMS + ' ' + this.arguments_.join(", ") - : Blockly.Msg.PROCEDURES_BEFORE_PARAMS; // [!] + var makeUpdateParams = function() { return function() { }; }; - Blockly.Events.disable(); - try { - this.setFieldValue(paramsString, 'PARAMS'); - } finally { - Blockly.Events.enable(); - } - }; - }; - Blockly.Blocks['procedures_defnoreturn'].init = makeProcedureInit( - false, true, false, + false, true, true, Blockly.Msg.PROCEDURES_DEFNORETURN_PROCEDURE, Blockly.Msg.PROCEDURES_DEFNORETURN_TITLE, Blockly.Msg.PROCEDURES_DEFNORETURN_COMMENT, Blockly.Msg.PROCEDURES_DEFNORETURN_TOOLTIP, Blockly.Msg.PROCEDURES_DEFNORETURN_HELPURL ); Blockly.Blocks['procedures_defnoreturn'].customContextMenu = makeProcedureCustomMenu(); Blockly.Blocks['procedures_defnoreturn'].updateParams_ = makeUpdateParams(); + Blockly.Blocks['procedures_defnoreturn'].domToMutation = makeProcedureDomToMutation(); Blockly.Blocks['procedures_defreturn'].init = makeProcedureInit( - true, true, false, + true, true, true, Blockly.Msg.PROCEDURES_DEFRETURN_PROCEDURE, Blockly.Msg.PROCEDURES_DEFRETURN_TITLE, Blockly.Msg.PROCEDURES_DEFRETURN_COMMENT, Blockly.Msg.PROCEDURES_DEFRETURN_TOOLTIP, Blockly.Msg.PROCEDURES_DEFRETURN_HELPURL ); Blockly.Blocks['procedures_defreturn'].customContextMenu = makeProcedureCustomMenu(); Blockly.Blocks['procedures_defreturn'].updateParams_ = makeUpdateParams(); + Blockly.Blocks['procedures_defreturn'].domToMutation = makeProcedureDomToMutation(); // ------------------------------------------------- // [!] Adding a new type of procedure with no params // ------------------------------------------------- @@ -4197,20 +3706,13 @@ Blockly.Msg.PROCEDURES_DEFNORETURN_COMMENT, Blockly.Msg.PROCEDURES_DEFNORETURN_TOOLTIP, Blockly.Msg.PROCEDURES_DEFNORETURN_HELPURL ), setStatements_: Blockly.Blocks['procedures_defnoreturn'].setStatements_, - updateParams_: function() { - Blockly.Events.disable(); - try { - this.setFieldValue(Blockly.Msg.PROCEDURES_DEFNORETURN_NOPARAMS, 'PARAMS'); - } finally { - Blockly.Events.enable(); - } - }, + updateParams_: makeUpdateParams(), mutationToDom: Blockly.Blocks['procedures_defnoreturn'].mutationToDom, - domToMutation: Blockly.Blocks['procedures_defnoreturn'].domToMutation, + domToMutation: makeProcedureDomToMutation(), decompose: Blockly.Blocks['procedures_defnoreturn'].decompose, compose: Blockly.Blocks['procedures_defnoreturn'].compose, getProcedureDef: Blockly.Blocks['procedures_defnoreturn'].getProcedureDef, getVars: Blockly.Blocks['procedures_defnoreturn'].getVars, renameVar: Blockly.Blocks['procedures_defnoreturn'].renameVar, @@ -4236,21 +3738,21 @@ // [!] Adding a new type of function only with return value (and params) // --------------------------------------------------------------------- Blockly.Blocks['procedures_defreturnsimplewithparams'] = { init: makeProcedureInit( - true, false, false, + true, false, true, Blockly.Msg.PROCEDURES_DEFRETURN_PROCEDURE, Blockly.Msg.PROCEDURES_DEFRETURN_TITLE, Blockly.Msg.PROCEDURES_DEFRETURN_COMMENT, Blockly.Msg.PROCEDURES_DEFRETURN_TOOLTIP, Blockly.Msg.PROCEDURES_DEFRETURN_HELPURL ), setStatements_: Blockly.Blocks['procedures_defreturn'].setStatements_, - updateParams_: Blockly.Blocks['procedures_defreturn'].updateParams_, + updateParams_: makeUpdateParams(), mutationToDom: Blockly.Blocks['procedures_defreturn'].mutationToDom, - domToMutation: Blockly.Blocks['procedures_defreturn'].domToMutation, + domToMutation: makeProcedureDomToMutation(), decompose: Blockly.Blocks['procedures_defreturn'].decompose, compose: Blockly.Blocks['procedures_defreturn'].compose, getProcedureDef: Blockly.Blocks['procedures_defreturn'].getProcedureDef, getVars: Blockly.Blocks['procedures_defreturn'].getVars, renameVar: Blockly.Blocks['procedures_defreturn'].renameVar, @@ -4284,20 +3786,13 @@ Blockly.Msg.PROCEDURES_DEFRETURN_COMMENT, Blockly.Msg.PROCEDURES_DEFRETURN_TOOLTIP, Blockly.Msg.PROCEDURES_DEFRETURN_HELPURL ), setStatements_: Blockly.Blocks['procedures_defreturn'].setStatements_, - updateParams_: function() { - Blockly.Events.disable(); - try { - this.setFieldValue(Blockly.Msg.PROCEDURES_DEFRETURN_NOPARAMS, 'PARAMS'); - } finally { - Blockly.Events.enable(); - } - }, + updateParams_: makeUpdateParams(), mutationToDom: Blockly.Blocks['procedures_defreturn'].mutationToDom, - domToMutation: Blockly.Blocks['procedures_defreturn'].domToMutation, + domToMutation: makeProcedureDomToMutation(), decompose: Blockly.Blocks['procedures_defreturn'].decompose, compose: Blockly.Blocks['procedures_defreturn'].compose, getProcedureDef: Blockly.Blocks['procedures_defreturn'].getProcedureDef, getVars: Blockly.Blocks['procedures_defreturn'].getVars, renameVar: Blockly.Blocks['procedures_defreturn'].renameVar, @@ -4328,11 +3823,11 @@ function populateProcedures(procedureList) { // [!] for (var i = 0; i < procedureList.length; i++) { var name = procedureList[i][0]; var args = procedureList[i][1]; - const templateName = Blockly.Procedures.getDefinition(name, workspace).callType_; // [!] + var templateName = Blockly.Procedures.getDefinition(name, workspace).callType_; // [!] // <block type="procedures_callnoreturn" gap="16"> // <mutation name="do something"> // <arg name="x"></arg> // </mutation> // </block> @@ -4361,11 +3856,11 @@ function populateProcedures(procedureList) { // [!] for (var i = 0; i < procedureList.length; i++) { var name = procedureList[i][0]; var args = procedureList[i][1]; - const templateName = Blockly.Procedures.getDefinition(name, workspace).callType_; // [!] + var templateName = Blockly.Procedures.getDefinition(name, workspace).callType_; // [!] // <block type="procedures_callnoreturn" gap="16"> // <mutation name="do something"> // <arg name="x"></arg> // </mutation> // </block> @@ -4491,15 +3986,24 @@ <script>initProcedsBlockly("Statement");</script> <script>var MINUS = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAQAAAD2e2DtAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAHdElNRQfhDAUCCi+xWH4JAAABcUlEQVR42u3c7ZGCMBSG0etuYcTKls7AyrSEVWd4+bjnUECMeSbhD6kCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIBzu4XHm2rUvPekD2yutR57/4itTLXU0/Pvs9SUW5TcDrDUyE3r9Na6ZwZKBWD5PxVKIBPAVGtknGsZibeBTADPyCjXE1idn8A0/gJjXFPgn0sEwIEljgAHwPc2Xx87QHMCaE4AzQmgOQE0J4DmBNCcAJoTQHMCaE4AzQmgOQE0J4DmBNCcAJoTQHMCaE4AzQmgOQE0J4DmBNDcb2SUsfc0T2re/utAO0BzPg49sot8HOoI+M5IDJIJ4OF+gI+F7gpyRcwxxa6Iyb0E3mvYB96y1kgtv2vijubS18QBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAWXq7xrTQhKAi3AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE3LTEyLTA1VDAyOjEwOjQ3LTA1OjAwdZLI/gAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNy0xMi0wNVQwMjoxMDo0Ny0wNTowMATPcEIAAAAASUVORK5CYII="; /* global Blockly */ -var ControlColor = 60; -var CommandColor = 200; -var ExpressionColor = 180; -var BindingColor = 30; -var CompletarColor = 0; +Blockly.GOBSTONES_COLORS = { + globalHsvSaturation: Blockly.HSV_SATURATION, + globalHsvValue: Blockly.HSV_VALUE, + primitiveCommand: 80, + assignation: 230, + controlStructure: 60, + literalExpression: 180, + expression: 260, + program: 100, + interactiveProgram: 30, + interactiveBinding: 50, + procedure: Blockly.Blocks.procedures.HUE, + complete: 0, +}; /** * Create the svg representation of a block and render * @name {!string} name of the parameter. * @this Blockly.Block @@ -4530,11 +4034,11 @@ "name": "program", "check": ["Statement"] } ] }) - this.setColour(100); + this.setColour(Blockly.GOBSTONES_COLORS.program); this.setDeletable(true); this.setEditable(true); this.setMovable(true); }, @@ -4574,11 +4078,11 @@ "name": "interactiveprogram", "check": ["InteractiveBinding"] } ] }); - this.setColour(BindingColor); + this.setColour(Blockly.GOBSTONES_COLORS.interactiveProgram); this.setDeletable(true); this.setEditable(true); this.setMovable(true); }, @@ -4716,11 +4220,11 @@ type: "field_dropdown", name: "InteractiveBindingDropdownKey", options: keys.map(it => [it.name, it.code]), } ], - colour: BindingColor, + colour: Blockly.GOBSTONES_COLORS.interactiveBinding, tooltip: "Escoger una entrada", }); this.appendStatementInput('block').setCheck(["Statement"]); }, @@ -4817,11 +4321,11 @@ type: "Statement", previousStatement: "Statement", nextStatement: "Statement", }); - this.setColour(ControlColor); + this.setColour(Blockly.GOBSTONES_COLORS.controlStructure); this.appendValueInput('count') .appendField('repetir'); this.appendDummyInput() .appendField('veces'); this.appendStatementInput('block').setCheck(["Statement"]); @@ -4835,11 +4339,11 @@ type: "Statement", previousStatement: "Statement", nextStatement: "Statement", }); - this.setColour(ControlColor); + this.setColour(Blockly.GOBSTONES_COLORS.controlStructure); this.appendValueInput('condicion') .setCheck('Bool') .appendField('repetir hasta que'); this.appendStatementInput('block').setCheck(["Statement"]); this.setInputsInline(true); @@ -4852,11 +4356,11 @@ type: "Statement", previousStatement: "Statement", nextStatement: "Statement", }); - this.setColour(ControlColor); + this.setColour(Blockly.GOBSTONES_COLORS.controlStructure); this.appendValueInput('condicion') .appendField(Blockly.Msg["CONTROLS_IF_MSG_IF"]); this.appendStatementInput('block').setCheck(["Statement"]); this.setInputsInline(true); } @@ -4924,11 +4428,11 @@ "helpUrl": "%{BKY_CONTROLS_IF_HELPURL}", "mutator": "controls_if_mutator_without_ui", "extensions": ["controls_if_tooltip"] }); - this.setColour(ControlColor); + this.setColour(Blockly.GOBSTONES_COLORS.controlStructure); this.setInputsInline(true); this.elseCount_++; this.updateShape_(); }, @@ -4987,11 +4491,11 @@ { type: 'input_value', name: 'COLOR' } ], - colour: CommandColor, + colour: Blockly.GOBSTONES_COLORS.primitiveCommand, tooltip: 'Poner color en casillero.', inputsInline: true }); } }; @@ -5007,11 +4511,11 @@ { type: 'input_value', name: 'COLOR' } ], - colour: CommandColor, + colour: Blockly.GOBSTONES_COLORS.primitiveCommand, tooltip: 'Sacar color de casillero.', inputsInline: true }); } }; @@ -5027,11 +4531,11 @@ { type: 'input_value', name: 'DIRECCION' } ], - colour: CommandColor, + colour: Blockly.GOBSTONES_COLORS.primitiveCommand, tooltip: 'Mover en una dirección.', inputsInline: true }); } }; @@ -5047,11 +4551,11 @@ { type: 'input_value', name: 'DIRECCION' } ], - colour: CommandColor, + colour: Blockly.GOBSTONES_COLORS.primitiveCommand, tooltip: 'Ir al borde del tablero.', inputsInline: true }); } }; @@ -5061,11 +4565,11 @@ this.jsonInit({ type: "Statement", previousStatement: "Statement", nextStatement: "Statement", message0: 'Vaciar tablero', - colour: CommandColor, + colour: Blockly.GOBSTONES_COLORS.primitiveCommand, tooltip: 'Vaciar el tablero.', inputsInline: true }); } }; @@ -5087,11 +4591,11 @@ "name": "boomDescription", "text": "Ingresar motivo..." } ], "inputsInline": false, - "colour": CommandColor, + "colour": Blockly.GOBSTONES_COLORS.primitiveCommand, "tooltip": "Este comando hace que estalle todo." }); } }; @@ -5107,11 +4611,11 @@ "type": "Statement", "previousStatement": "Statement", "nextStatement": "Statement", "lastDummyAlign0": "RIGHT", "message0": "COMPLETAR", - "colour": CompletarColor, + "colour": Blockly.GOBSTONES_COLORS.complete, "tooltip": "Tenés que reemplazar este bloque por tu solución" }); }, onchange: Blockly.Blocks.makeShadowEventListener @@ -5123,11 +4627,11 @@ "type": "InteractiveBinding", "previousStatement": "InteractiveBinding", "nextStatement": "InteractiveBinding", "lastDummyAlign0": "RIGHT", "message0": "COMPLETAR", - "colour": CompletarColor, + "colour": Blockly.GOBSTONES_COLORS.complete, "tooltip": "Tenés que reemplazar este bloque por tu solución" }); }, onchange: Blockly.Blocks.makeShadowEventListener @@ -5141,11 +4645,11 @@ init: function () { this.jsonInit({ "type": "completar_expression", "message0": "COMPLETAR", "output": "any", - "colour": CompletarColor, + "colour": Blockly.GOBSTONES_COLORS.complete, "tooltip": "Tenés que reemplazar este bloque por tu solución" }); }, onchange: Blockly.Blocks.makeShadowEventListener @@ -5161,11 +4665,11 @@ type: "field_dropdown", name: type + "Dropdown", options: values.map(value => [value,value]), }], output: type, - colour: ExpressionColor, + colour: Blockly.GOBSTONES_COLORS.literalExpression, tooltip: "Escoger " + type, }); } }; } @@ -5183,11 +4687,11 @@ { type: 'input_value', name: 'VALUE' } ], - colour: ExpressionColor, + colour: Blockly.GOBSTONES_COLORS.expression, inputsInline: true, output: returnType }) } }; @@ -5221,11 +4725,11 @@ { type: 'input_value', name: 'arg2' } ], - colour: ExpressionColor, + colour: Blockly.GOBSTONES_COLORS.expression, inputsInline: false, output: 'Bool' }); } }; @@ -5250,11 +4754,11 @@ { type: 'input_value', name: 'arg2' } ], - colour: ExpressionColor, + colour: Blockly.GOBSTONES_COLORS.expression, inputsInline: false, output: 'Number' }); } }; @@ -5279,11 +4783,11 @@ { type: 'input_value', name: 'arg2' } ], - colour: ExpressionColor, + colour: Blockly.GOBSTONES_COLORS.expression, inputsInline: false, output: 'Bool' }); } }; @@ -5312,11 +4816,11 @@ } ], "inputsInline": true, "previousStatement": null, "nextStatement": null, - "colour": 230, + "colour": Blockly.GOBSTONES_COLORS.assignation, "tooltip": "", "helpUrl": "" }); this.getters = []; }, @@ -5328,24 +4832,11 @@ this.createVariableBlock(name); }}); }, createVariableBlock: function(name) { - var parent = this; - - while (parent.getSurroundParent() !== null) { - if (parent.type.startsWith("Interactive") && parent.type.endsWith("Binding")) break; - - const next = parent.getSurroundParent(); - if (next === null) break; - parent = next; - } - - var id = parent.id; - return Blockly.createBlockSvg(this.workspace, 'variables_get', b => { - b.$parent = id; b.setFieldValue(name, 'VAR'); b.moveBy(10,5); b.parentAssignmentBlock = this; this.getters.push(b); }); @@ -5479,11 +4970,11 @@ Blockly.GobstonesLanguage.addPragma = function(block, str){ if(!str || !Blockly.GobstonesLanguage.shouldAddRegionPragma) return str; // add newLine if block is a command or definition const newLine = str[str.length-1] === '\n' ? '\n' : ''; - return `/@BEGIN_REGION@${block.id}@/${newLine}${str}/@END_REGION@/${newLine}`; + return `/*@BEGIN_REGION@${block.id}@*/${newLine}${str}/*@END_REGION@*/${newLine}`; }; // Gobstones pragma BEGIN_REGION should avoid char 'at' ( @ ) Blockly.utils.genUid.soup_ = Blockly.utils.genUid.soup_.replace(/@/g,"a"); /** @@ -5745,12 +5236,12 @@ }; Blockly.GobstonesLanguage.ComandoCompletar = b => 'BOOM("El programa todavía no está completo")\n'; Blockly.GobstonesLanguage.ExpresionCompletar = b => ['boom("El programa todavía no está completo")',Blockly.GobstonesLanguage.ORDER_FUNCTION_CALL]; +Blockly.GobstonesLanguage.AsociacionDeTeclaCompletar = b => '' - Blockly.GobstonesLanguage.ColorSelector = literalSelectorBlockCodeGenerator('Color'); Blockly.GobstonesLanguage.DireccionSelector = literalSelectorBlockCodeGenerator('Direccion'); Blockly.GobstonesLanguage.BoolSelector = literalSelectorBlockCodeGenerator('Bool'); Blockly.GobstonesLanguage.OperadorDeComparacion = function (block) { @@ -6162,7 +5653,797 @@ '.blocklyPreconditionError>.blocklyPath {', 'stroke: #f88;', 'stroke-width: 3px;', '}', ];</script> + + <script> + Polymer({ + is: 'gs-element-blockly', + + properties: { + /* + * `primitiveProcedures` lista de definiciones de procedimientos primitivos. + * Ej: ["Poner_FloresAl_"] Se transormara en un bloque con dos inputs inline, cuyo + * codigo sera Poner_FloresAl_(arg1, arg2). + */ + primitiveProcedures: { + type: Array, + observer: '_onPrimitiveProceduresChange' + }, + + /* + * `primitiveFunctions` lista de definiciones de funciones primitivas. + * Ej: ["cantidadDeFlores_en_"] Se transormara en un bloque con dos inputs inline, cuyo + * codigo sera cantidadDeFlores_en_(arg1, arg2). + */ + primitiveFunctions: { + type: Array, + observer: '_onPrimitiveFunctionsChange' + }, + + /* + * `toolbox` Definicion de visibilidad y habilitación de los bloques en el toolbox. + * Pueden nombrarse bloques individuales o categorias. + * + * Ej: + * + * ```json + * { + * "visible": ["Comandos primitivos", "Literales", "Expresiones"], + * "disabled": ["Poner", "hayBolitas", "Literales"] + * } + * ``` + * + * Los IDs de los bloques a utilizar pueden consultarse [en la wiki](https://github.com/Program-AR/gs-element-blockly/wiki/Block-IDs-for-toolbox) + * + * Nota: Los bloques de primitiveProcedures siempre aparecen en el toolbox, aunque no figuren + * en esta propiedad. + */ + toolbox: { + type: Object, + observer: '_onUpdateToolbox' + }, + + /* + * `workspaceXml` Código XML de los bloques en el workspace. + */ + workspaceXml: { + type: String, + observer: '_onWorkspaceUpdate', + notify: true + }, + + /* + * `workspaceCanEdit` Indica si el usuario puede modificar el workspace. + */ + workspaceCanEdit: { + type: Boolean, + }, + + /* + * `width` Ancho del elemento. + */ + width: { + type: Number, + observer: '_fixSize' + }, + + /* + * `height` Alto del elemento. + */ + height: { + type: Number, + observer: '_fixSize' + }, + + /* + * `media` path a media de blockly. + */ + media: { + type: String, + value: "../bower_components/blockly-package/media/" + }, + }, + + _onPrimitiveProceduresChange: function() { + if(typeof this.primitiveProcedures == 'string') { + this.primitiveProcedures = JSON.parse(this.primitiveProcedures); + return; + } + this._definePrimitiveProcedures(); + this._onUpdateToolbox(); + }, + + _onPrimitiveFunctionsChange: function() { + if(typeof this.primitiveFunctions == 'string') { + this.primitiveFunctions = JSON.parse(this.primitiveFunctions); + return; + } + this._definePrimitiveFunctions(); + this._onUpdateToolbox(); + }, + + _onUpdateToolbox: function() { + if(typeof this.toolbox == 'string') { + if(this.toolbox == '') { + this.toolbox = undefined; + } + else { + this.toolbox = JSON.parse(this.toolbox); + } + return; + } + let toolbox = this._createToolbox(); + this.workspace.updateToolbox(toolbox); + }, + + /** + * Convierte primitiveProcedures y primitiveFunctions a array de strings para que sea retrocompatible con la versión original. + * La versión actual también acepta cosas como: + * { name: "unNombre", attributes: { block_icon: "base64..." } } + */ + _getPrimitiveNames: function(primitiveActions) { + return Array.isArray(primitiveActions) + ? primitiveActions.map(this._getPrimitiveName) + : primitiveActions; + }, + + _getPrimitiveName(primitive) { + return primitive.name || primitive; + }, + + /** + * Retorna el arbol por defecto de bloques y categorias + */ + _defaultToolboxTree : function() { + const primitiveProcedures = this._getPrimitiveNames(this.primitiveProcedures); + const primitiveFunctions = this._getPrimitiveNames(this.primitiveFunctions); + + let tree = [] + let toolboxDefault = this.$$('#toolbox'); + let toolboxDefaultLines = toolboxDefault.innerHTML.split("\n"); + let ignore_last = false; + let parent = tree; + let stack = []; + for(var i in toolboxDefaultLines) { + let line = toolboxDefaultLines[i]; + if(line.indexOf('<block') >= 0) { + let m = line.match('type="([^"]*)"') + if(m.length == 2) { + parent.push({ + type: 'block', + name: m[1], + }); + } + } + else if(line.indexOf('<category') >= 0) { + let m = line.match('name="([^"]*)"') + if(m.length == 2 && ( + (m[1] == 'Procedimientos primitivos' && primitiveProcedures && primitiveProcedures.length > 0) || + (m[1] == 'Funciones primitivas' && primitiveFunctions && primitiveFunctions.length > 0) || + (m[1] != 'Procedimientos primitivos' && m[1] != 'Funciones primitivas'))) { + parent.push({ + type: 'category', + name: m[1], + child: [], + xml: line + }); + stack.push(parent); + parent = parent[parent.length - 1].child; + if(m[1] == 'Procedimientos primitivos') + { + for(var i in primitiveProcedures) { + parent.push({ + type: 'block', + name: primitiveProcedures[i], + }); + } + } + if(m[1] == 'Funciones primitivas') + { + for(var i in primitiveFunctions) { + parent.push({ + type: 'block', + name: primitiveFunctions[i], + }); + } + } + } + else { + ignore_last = true; + } + } + else if(line.indexOf('</category') >= 0) { + if(ignore_last) { + ignore_last = false; + } + else { + parent = stack.pop(); + } + } + } + + return tree; + }, + + /** + * Sirve para manejar los alias de los ID que vengan por la interfaz. + * Recibe una lista que puede ser de IDs de bloques ó de categorías. + * Devuelve una lista similar, intercambiando los alias por los ids "oficiales". + * Mi idea acá también es que a futuro se haga la internacionalización de ids. + */ + _homogenizeIDs: function(ids){ + return ids ? ids.map(id => Blockly.GobstonesLanguage.aliasForBlockID(id)) : []; + }, + + /** + * Crea el toolbox a partir de la propiedad blocks, bloques visibles, y + * customblocks. + * + * Funciona podando el toolbox por defecto. + */ + _createToolbox: function() { + const primitiveProcedures = this._getPrimitiveNames(this.primitiveProcedures); + const primitiveFunctions = this._getPrimitiveNames(this.primitiveFunctions); + + let tree = this._defaultToolboxTree(); + let toolbox = []; + + if(this.toolbox) { + let visibles = this._homogenizeIDs(this.toolbox.visible); + let disabled = this._homogenizeIDs(this.toolbox.disabled); + if(visibles.length > 0) { + visibles = visibles.concat(disabled); + if(primitiveProcedures) { + visibles = visibles.concat(primitiveProcedures); + } + if(primitiveFunctions) { + visibles = visibles.concat(primitiveFunctions); + } + } + + function filtrarToolboxTree(ttree) { + let tbox = []; + for(var i in ttree) { + if(visibles.length == 0 || visibles.indexOf(ttree[i].name) >= 0) { + tbox.push(ttree[i]); + } + else if(ttree[i].type == 'category') { + let tmp = filtrarToolboxTree(ttree[i].child); + if(tmp.length > 0) { + ttree[i].child = tmp; + tbox.push(ttree[i]); + } + } + } + return tbox; + } + + toolbox = filtrarToolboxTree(tree); + + // Marcar disabled + function marcarDisabled(tbox) { + for(var i in tbox) { + if(disabled.indexOf(tbox[i].name) >= 0) { + tbox[i].disabled = true; + } + if(tbox[i].child) { + marcarDisabled(tbox[i].child); + } + } + } + marcarDisabled(toolbox); + + } + else { + toolbox = tree; + } + + return this._toolboxTreeToXML(toolbox); + }, + + _toolboxBlockXML: function(blockDef, forceDisabled) { + let args = ""; + if(blockDef.disabled || forceDisabled) { + args = 'disabled="true"'; + } + let xml = `<block type="${blockDef.name}" ${args}></block>`; + return xml; + }, + + _toolboxTreeToXML : function(toolbox, noXmlTags, disabled) { + let xml = []; + if(!noXmlTags) { + xml.push('<xml>'); + } + for(var i in toolbox) { + if(toolbox[i].type == 'block') { + xml.push(this._toolboxBlockXML(toolbox[i], disabled)); + } + else if(toolbox[i].type == 'category') { + xml.push(toolbox[i].xml); + xml.push(this._toolboxTreeToXML(toolbox[i].child, true, toolbox[i].disabled || disabled)); + xml.push('</category>'); + } + } + if(!noXmlTags) { + xml.push('</xml>'); + } + return xml.join('\n'); + }, + + _getPartsByConvention(name) { + let parts = name.replace(/([A-Z])/g, " $1").toLowerCase(); + parts = parts[1].toUpperCase() + parts.substring(2); + parts = this._getParts(parts); + + return parts; + }, + + _getParts(name) { + return name.split("_"); + }, + + /** + * Define un bloque a partir de una definicion tipo Poner_En_ + */ + _definePrimitiveProcedure: function(definition) { + const name = this._getPrimitiveName(definition); + const customName = definition.attributes && definition.attributes.block_name; + const icon = definition.attributes && definition.attributes.block_icon; + + const finalName = customName || name; + const parts = (customName ? this._getParts : this._getPartsByConvention) + .bind(this)(finalName); + + // Bloque + Blockly.Blocks[name] = { + init: function () { + let argsIndex = 1; + this.setColour(Blockly.GOBSTONES_COLORS.primitiveCommand); + + if (icon) { + this.appendDummyInput().appendField(new Blockly.FieldImage( + icon, + 16, + 16, + finalName + )); + } + + for(var i in parts) { + if(i == (parts.length - 1)) { + this.appendDummyInput().appendField(parts[i]); + } + else { + this.appendValueInput('arg' + argsIndex).appendField(parts[i]); + argsIndex++; + } + } + this.setPreviousStatement(true); + this.setNextStatement(true); + this.setInputsInline(true); + } + }; + let argsList = []; + for(var i=1;i<parts.length;i++) { + argsList.push('arg' + i); + } + // Generator + Blockly.GobstonesLanguage[name] = procBlockCodeGenerator(name, argsList); + }, + + _definePrimitiveProcedures: function () { + if(!this.primitiveProcedures) { + return; + } + + for(var i in this.primitiveProcedures) { + this._definePrimitiveProcedure(this.primitiveProcedures[i]); + } + }, + + + /** + * Define un bloque a partir de una definicion de funcion primitiva tipo hayFlores_en_ + */ + _definePrimitiveFunction: function(definition) { + const name = this._getPrimitiveName(definition); + const customName = definition.attributes && definition.attributes.block_name; + const icon = definition.attributes && definition.attributes.block_icon; + + const finalName = customName || name; + const parts = (customName ? this._getParts : this._getPartsByConvention) + .bind(this)(finalName); + + // Bloque + Blockly.Blocks[name] = { + init: function () { + let argsIndex = 1; + this.setColour(Blockly.GOBSTONES_COLORS.primitiveCommand); + + if (icon) { + this.appendDummyInput().appendField(new Blockly.FieldImage( + icon, + 16, + 16, + finalName + )); + } + + for(var i in parts) { + if(i == (parts.length - 1)) { + this.appendDummyInput().appendField(parts[i]); + } + else { + this.appendValueInput('arg' + argsIndex).appendField(parts[i]); + argsIndex++; + } + } + this.setPreviousStatement(false); + this.setNextStatement(false); + this.setInputsInline(true); + this.setOutput('var'); + } + }; + let argsList = []; + for(var i=1;i<parts.length;i++) { + argsList.push('arg' + i); + } + // Generator + Blockly.GobstonesLanguage[name] = functionBlockCodeGenerator(name, argsList); + }, + + _definePrimitiveFunctions: function () { + if(!this.primitiveFunctions) { + return; + } + + for(var i in this.primitiveFunctions) { + this._definePrimitiveFunction(this.primitiveFunctions[i]); + } + }, + + _onBlocklyWorkspaceUpdate: function () { + let xml = Blockly.Xml.workspaceToDom(this.workspace); + this._blocklyWorkspaceXML = Blockly.Xml.domToText(xml); + this.workspaceXml = this._blocklyWorkspaceXML; + this._keepOnlyAProgram(xml); + this._checkParameterBounds(xml); + }, + + _keepOnlyAProgram(xml) { + const findProgram = (programType) => { + const children = xml.children; + const items = []; + for (var i=0; i < children.length; i++) + if (children[i].getAttribute("type") === programType) + items.push(children[i]); + return items; + }; + + const block = (blockXml) => this.workspace.getBlockById(blockXml.getAttribute("id")); + + const programXmls = findProgram("Program"); + const interactiveProgramXmls = findProgram("InteractiveProgram"); + + // repeated blocks + if (programXmls.length > 1) { + // (en vez de .undo() se podría hacerle .dispose() al bloque) + + if (block(programXmls[0]).$timestamp > block(programXmls[1]).$timestamp) { + // delete programXmls[0] + this.workspace.undo(); + programXmls.shift(); + } else { + // delete programXmls[1] + this.workspace.undo(); + programXmls.pop(); + } + } + if (interactiveProgramXmls.length > 1) { + if (block(interactiveProgramXmls[0]).$timestamp > block(interactiveProgramXmls[1]).$timestamp) { + // delete interactiveProgramXmls[0] + this.workspace.undo(); + interactiveProgramXmls.shift(); + } else { + // delete interactiveProgramXmls[1] + this.workspace.undo(); + interactiveProgramXmls.pop(); + } + } + + const program = programXmls[0] && block(programXmls[0]); + const interactiveProgram = interactiveProgramXmls[0] && block(interactiveProgramXmls[0]); + + if (program && interactiveProgram) { + program.setDeletable(true); + interactiveProgram.setDeletable(true); + + const important = program.$timestamp > interactiveProgram.$timestamp ? program : interactiveProgram; + const unimportant = program.$timestamp > interactiveProgram.$timestamp ? interactiveProgram : program; + + const twoActivePrograms = !program.disabled && !interactiveProgram.disabled; + const twoDisabledPrograms = program.disabled && interactiveProgram.disabled; + + if (twoActivePrograms || twoDisabledPrograms) { + important.setDisabledAndUpdateTimestamp(false); + unimportant.setDisabledAndUpdateTimestamp(true); + } + } else if (program && !interactiveProgram) { + program.setDisabledAndUpdateTimestamp(false); + program.setDeletable(false); + } else if (interactiveProgram && !program) { + interactiveProgram.setDisabledAndUpdateTimestamp(false); + interactiveProgram.setDeletable(false); + } + }, + + _checkParameterBounds(xml) { + const blocks = this.workspace.getAllBlocks(); + for (block of blocks) { + if (block.type === "variables_get" && block.$parent) { + const parentBlock = this.workspace.getBlockById(block.$parent); + const varField = block.getField("VAR"); + + if ( + parentBlock && + ( + !parentBlock.type.startsWith("procedures") || + parentBlock.arguments_.some(it => it === varField.getValue()) + ) + ) { + var parent = block; + while ((parent = parent.getSurroundParent()) !== null) { + if (parent.id === block.$parent) break; + } + + block.setDisabled(parent === null); + } else { + block.dispose(); + } + } + } + }, + + _onWorkspaceUpdate: function () { + if(this.workspaceXml != this._blocklyWorkspaceXML) { + this.workspace.clear(); + let dom = Blockly.Xml.textToDom(this.workspaceXml); + try { + Blockly.Xml.domToWorkspace(dom, this.workspace); + } catch (e) { + if(e.message.includes("nextConnection is null")){ + throw { + name: "BlockTypeError", + message: "There is at least one block type declared in the 'type' property of the block tag of the xml, which is incorrect or belongs to a block that is not in the Gobstones Language. Maybe you have forgotten any primitiveProcedure?", + toString: function(){ return this.name + ': ' + this.message; } + }; + } else { + throw e; + } + } + } + }, + + /** + * Reinicializa el workspace, dejando solo el bloque programa + */ + resetWorkspace: function() { + let xmlInicial = '<xml xmlns="http://www.w3.org/1999/xhtml"><block type="Program" x="30" y="30"></block></xml>'; + this.workspaceXml = xmlInicial; + }, + + /** + * Retorna una lista de objetos, uno por cada categoría y bloque. + * Representa la jerarquía del toolbox. + * + * Para los bloques se indica: + * + * * `blockXMLID`: El ID del bloque que estará en el XML exportado por el método + * `generateCode`. + * * `blockAliases`: La lista de aliases que refieren a ese bloque, y que pueden + * ser usados para definir el toolbox mediante la propiedad `toolbox`. + * + * Para las categorías se indica: + * + * * `categoryName`: El nombre de la categoría que es visible en el toolbox. + * * `categoryAliases`: La lista de aliases que refieren a esa categoría, y que pueden + * ser usados para definir el toolbox mediante la propiedad `toolbox`. + * * `categoryContents`: La lista de objetos categoría/bloque dentro de esta categoría. + * + * @return {Array} Los objetos. + */ + validToolboxIDs: function(){ + return this._validToolboxIDsFrom(this._defaultToolboxTree()); + }, + + /** + * Recursively defined. Gets a list of user-readable objects describing the + * toolboxTree hierarchy and aliases. + */ + _validToolboxIDsFrom: function(toolboxTree) { + var myThis = this; + return this._mapToolboxTree(toolboxTree, function(toolboxElement){ + if(toolboxElement.type === "category"){ + return { + categoryName: toolboxElement.name, + categoryAliases: Blockly.GobstonesLanguage.aliasesFor(toolboxElement.name), + categoryContents: myThis._validToolboxIDsFrom(toolboxElement.child) + }; + } else if(toolboxElement.type === "block") { + return { + blockXMLID: toolboxElement.name, + blockAliases: Blockly.GobstonesLanguage.aliasesFor(toolboxElement.name), + }; + } else { + /*Do nothing for other types*/ + } + }); + }, + + _mapToolboxTree: function(toolboxTree, f){ + var resultingList = []; + for (var i in toolboxTree){ + resultingList.push(f(toolboxTree[i])); + } + return resultingList; + }, + + /** + * Reinicializa el estado del componente, + */ + cleanup: function() { + this.primitiveProcedures = []; + this.primitiveFunctions = []; + this.toolbox = {}; + this.resetWorkspace(); + }, + + // Element Lifecycle + ready: function() { + }, + + attached: function() { + this._definePrimitiveProcedures(); + this._definePrimitiveFunctions(); + // create workspace + var blocklyDiv = this.$$('#blocklyDiv'); + this.blocklyDiv = blocklyDiv; + this._fixSize(); + this._fixScroll(); + this.workspace = Blockly.inject(blocklyDiv, { + toolbox: this._createToolbox(), + media: this.get("media"), + toolboxPosition: "start", + scrollbars: true, + horizontalLayout: false, + collapse: true, + css: true, + zoom: { + controls: true, + wheel: true + } + }); + var _this = this; + this.workspace.addChangeListener(function (a, b, c) { + Blockly.Events.disableOrphans(a, b, c); + _this._onBlocklyWorkspaceUpdate(); + }); + this.resetWorkspace() + this._onresize(); + if(window.jQuery !== undefined) { + $(window).resize(() => this._onresize()); + }; + + Blockly.ErrorInforming.addToWorkspace(this.workspace); + }, + + _onresize() { + Blockly.svgResize(this.workspace); + }, + + _fixSize() { + let style = ""; + if(this.width && this.height) { + style += "width:"+ this.width + "px;"; + style += "height:"+ this.height + "px;"; + this.blocklyDiv.style = style; + this._onresize(); + } + }, + + _fixScroll() { + this.blocklyDiv.addEventListener("wheel", (event) => { + if (!event.ctrlKey) { + event.preventDefault(); + event.stopPropagation(); + + const { deltaX, deltaY } = event; + const metrics = this.workspace.getMetrics(); + const currentScrollX = (metrics.viewLeft - metrics.contentLeft); + const currentScrollY = (metrics.viewTop - metrics.contentTop); + const newScrollX = Math.min(currentScrollX + deltaX / 2, metrics.contentWidth - metrics.viewWidth); + const newScrollY = Math.min(currentScrollY + deltaY / 2, metrics.contentHeight - metrics.viewHeight); + + this.workspace.scrollbar.set(Math.max(newScrollX, 0), Math.max(newScrollY, 0)); + } + }, true); + }, + + detached: function() { + // The analog to `attached`, `detached` fires when the element has been + // removed from a document. + // + // Use this to clean up anything you did in `attached`. + }, + + // Element Behavior + + /** + * Generate gobstones code from the blocks in the workspace. + * It removes all highlighting and errors if already present. + * + * There is an additional option which if provided adds pragma BEGIN/END REGION + * to the output. + * * `element.generateCode( {withRegions: true, clearErrors: false} );` + * @return {string} The code. + */ + generateCode: function(options = {}) { + this.workspace.highlightBlock(); // No parameters means reset highlighting + if (this.workspace.removeBlockErrors && options.clearErrors !== false) + this.workspace.removeBlockErrors(); + Blockly.GobstonesLanguage.shouldAddRegionPragma = options.withRegions; + return Blockly.GobstonesLanguage.workspaceToCode(this.workspace); + }, + + /** + * Append blocks to workspace. Se agrega al contenido actual del workspace + * los bloques especificados en el xml (descartando el program si es que viene). + * + */ + appendBlocksToWorkspace: function(xml) { + }, + + /** + * Highlight a given block by its ID. Se hace highlight del bloque indicado. + * + */ + highlightBlock: function(blockId) { + this.workspace.highlightBlock(blockId) + }, + + /** + * Highlight a given block, telling the user it has an error + * + * `blockId` is the block ID where the error should appear + * + * `errorKind` is either a string with the description or an object with a kind of error. + * Next are examples with the existent error kinds. + * + * Examples: + * * `element.showBlockError('a1s2', 'Hey, here is an error')` + * * `element.showBlockError('a1s2', { kind: 'INCOMPLETE_ERROR'} )` + * * `element.showBlockError('a1s2', { kind: 'TYPE_ERROR', expectedType:'string', actualType: 'boolean' })` + * * `element.showBlockError('a1s2', { kind: 'PRECONDITION_ERROR', description: "Susan can't move right" })` + */ + showBlockError: function(blockId, errorKind) { + this.workspace.showBlockError(blockId, errorKind); + }, + + testColors(colors) { + Blockly.GOBSTONES_COLORS = colors; + Blockly.HSV_SATURATION = colors.globalHsvSaturation; + Blockly.HSV_VALUE = colors.globalHsvValue; + Blockly.Constants.Math.HUE = colors.literalExpression; + Blockly.Msg.MATH_HUE = colors.literalExpression.toString(); + Blockly.Blocks.procedures.HUE = colors.procedure; + + var xml = Blockly.Xml.workspaceToDom(this.workspace); + this.resetWorkspace(); + Blockly.Xml.domToWorkspace(xml, this.workspace); + } + }); + </script> </dom-module> </body></html> \ No newline at end of file