diff --git a/appengine/app.yaml b/appengine/app.yaml index 38b041c68..32fe41181 100644 --- a/appengine/app.yaml +++ b/appengine/app.yaml @@ -110,3 +110,18 @@ handlers: upload: robots\.txt secure: always + +skip_files: +# App Engine default patterns. +- ^(.*/)?#.*#$ +- ^(.*/)?.*~$ +- ^(.*/)?.*\.py[co]$ +- ^(.*/)?.*/RCS/.*$ +- ^(.*/)?\..*$ +# Custom skip patterns. +- ^static/appengine/.*$ +- ^static/apps/json/.*$ +- ^static/apps/_soy/.+\.jar$ +- ^static/i18n/.*$ +- ^static/msg/json/.*$ +- ^.+\.soy$ diff --git a/appengine/apple-touch-icon.png b/appengine/apple-touch-icon.png index ae6287491..455abac2d 100644 Binary files a/appengine/apple-touch-icon.png and b/appengine/apple-touch-icon.png differ diff --git a/apps/blockfactory/blocks.js b/apps/blockfactory/blocks.js index 6b993fdb5..40b97c03f 100644 --- a/apps/blockfactory/blocks.js +++ b/apps/blockfactory/blocks.js @@ -40,10 +40,10 @@ Blockly.Blocks['factory_base'] = { .appendField(dropdown, 'INLINE'); dropdown = new Blockly.FieldDropdown([ ['no connections', 'NONE'], - ['left output', 'LEFT'], - ['top+bottom connections', 'BOTH'], - ['top connection', 'TOP'], - ['bottom connection', 'BOTTOM']], + ['← left output', 'LEFT'], + ['↕ top+bottom connections', 'BOTH'], + ['↑ top connection', 'TOP'], + ['↓ bottom connection', 'BOTTOM']], function(option) { var block = this.sourceBlock_; var outputExists = block.getInput('OUTPUTTYPE'); @@ -276,12 +276,13 @@ Blockly.Blocks['field_dropdown'] = { } }, decompose: function(workspace) { - var containerBlock = new Blockly.Block(workspace, - 'field_dropdown_container'); + var containerBlock = + Blockly.Block.obtain(workspace, 'field_dropdown_container'); containerBlock.initSvg(); var connection = containerBlock.getInput('STACK').connection; for (var x = 0; x < this.optionCount_; x++) { - var optionBlock = new Blockly.Block(workspace, 'field_dropdown_option'); + var optionBlock = + Blockly.Block.obtain(workspace, 'field_dropdown_option'); optionBlock.initSvg(); connection.connect(optionBlock.previousConnection); connection = optionBlock.nextConnection; @@ -483,12 +484,12 @@ Blockly.Blocks['type_group'] = { } }, decompose: function(workspace) { - var containerBlock = new Blockly.Block(workspace, - 'type_group_container'); + var containerBlock = + Blockly.Block.obtain(workspace, 'type_group_container'); containerBlock.initSvg(); var connection = containerBlock.getInput('STACK').connection; for (var x = 0; x < this.typeCount_; x++) { - var typeBlock = new Blockly.Block(workspace, 'type_group_item'); + var typeBlock = Blockly.Block.obtain(workspace, 'type_group_item'); typeBlock.initSvg(); connection.connect(typeBlock.previousConnection); connection = typeBlock.nextConnection; @@ -655,10 +656,9 @@ function fieldNameCheck(referenceBlock) { var blocks = referenceBlock.workspace.getAllBlocks(); for (var x = 0, block; block = blocks[x]; x++) { var otherName = block.getFieldValue('FIELDNAME'); - if (otherName) { - if (otherName.toLowerCase() == name) { - count++; - } + if (!block.disabled && !block.getInheritedDisabled() && + otherName && otherName.toLowerCase() == name) { + count++; } } var msg = (count > 1) ? @@ -677,10 +677,9 @@ function inputNameCheck(referenceBlock) { var blocks = referenceBlock.workspace.getAllBlocks(); for (var x = 0, block; block = blocks[x]; x++) { var otherName = block.getFieldValue('INPUTNAME'); - if (otherName) { - if (otherName.toLowerCase() == name) { - count++; - } + if (!block.disabled && !block.getInheritedDisabled() && + otherName && otherName.toLowerCase() == name) { + count++; } } var msg = (count > 1) ? diff --git a/apps/blockfactory/factory.js b/apps/blockfactory/factory.js index 5f8fc8d9f..5a0e133f4 100644 --- a/apps/blockfactory/factory.js +++ b/apps/blockfactory/factory.js @@ -23,12 +23,6 @@ */ 'use strict'; -/** - * The uneditable container block that everything else attaches to. - * @type {Blockly.Block} - */ -var rootBlock = null; - /** * The type of the generated block. */ @@ -47,7 +41,11 @@ function initPreview(updateFunc) { * When the workspace changes, update the three other displays. */ function onchange() { - var name = rootBlock.getFieldValue('NAME'); + var name = ''; + var rootBlock = getRootBlock(); + if (rootBlock) { + name = rootBlock.getFieldValue('NAME'); + } blockType = name.replace(/\W/g, '_').replace(/^(\d)/, '_\\1').toLowerCase(); if (!blockType) { blockType = 'unnamed'; @@ -64,67 +62,72 @@ function updateLanguage() { // Generate name. var code = []; code.push("Blockly.Blocks['" + blockType + "'] = {"); - code.push(" init: function() {"); - code.push(" this.setHelpUrl('http://www.example.com/');"); - // Generate colour. - var colourBlock = rootBlock.getInputTargetBlock('COLOUR'); - if (colourBlock) { - var hue = parseInt(colourBlock.getFieldValue('HUE'), 10); - code.push(' this.setColour(' + hue + ');'); - } - // Generate inputs. - var TYPES = {'input_value': 'appendValueInput', - 'input_statement': 'appendStatementInput', - 'input_dummy': 'appendDummyInput'}; - var inputVarDefined = false; - var contentsBlock = rootBlock.getInputTargetBlock('INPUTS'); - while (contentsBlock) { - var align = contentsBlock.getFieldValue('ALIGN'); - var fields = getFields(contentsBlock.getInputTargetBlock('FIELDS')); - var name = ''; - // Dummy inputs don't have names. Other inputs do. - if (contentsBlock.type != 'input_dummy') { - name = escapeString(contentsBlock.getFieldValue('INPUTNAME')); + var rootBlock = getRootBlock(); + if (rootBlock) { + code.push(" init: function() {"); + code.push(" this.setHelpUrl('http://www.example.com/');"); + // Generate colour. + var colourBlock = rootBlock.getInputTargetBlock('COLOUR'); + if (colourBlock && !colourBlock.disabled) { + var hue = parseInt(colourBlock.getFieldValue('HUE'), 10); + code.push(' this.setColour(' + hue + ');'); } - var check = getOptTypesFrom(contentsBlock, 'TYPE'); - code.push(' this.' + TYPES[contentsBlock.type] + - '(' + name + ')'); - if (check && check != 'null') { - code.push(' .setCheck(' + check + ')'); + // Generate inputs. + var TYPES = {'input_value': 'appendValueInput', + 'input_statement': 'appendStatementInput', + 'input_dummy': 'appendDummyInput'}; + var inputVarDefined = false; + var contentsBlock = rootBlock.getInputTargetBlock('INPUTS'); + while (contentsBlock) { + if (!contentsBlock.disabled && !contentsBlock.getInheritedDisabled()) { + var align = contentsBlock.getFieldValue('ALIGN'); + var fields = getFields(contentsBlock.getInputTargetBlock('FIELDS')); + var name = ''; + // Dummy inputs don't have names. Other inputs do. + if (contentsBlock.type != 'input_dummy') { + name = escapeString(contentsBlock.getFieldValue('INPUTNAME')); + } + var check = getOptTypesFrom(contentsBlock, 'TYPE'); + code.push(' this.' + TYPES[contentsBlock.type] + + '(' + name + ')'); + if (check && check != 'null') { + code.push(' .setCheck(' + check + ')'); + } + if (align != 'LEFT') { + code.push(' .setAlign(Blockly.ALIGN_' + align + ')'); + } + for (var x = 0; x < fields.length; x++) { + code.push(' .appendField(' + fields[x] + ')'); + } + // Add semicolon to last line to finish the statement. + code[code.length - 1] += ';'; + } + contentsBlock = contentsBlock.nextConnection && + contentsBlock.nextConnection.targetBlock(); } - if (align != 'LEFT') { - code.push(' .setAlign(Blockly.ALIGN_' + align + ')'); + // Generate inline/external switch. + if (rootBlock.getFieldValue('INLINE') == 'INT') { + code.push(' this.setInputsInline(true);'); } - for (var x = 0; x < fields.length; x++) { - code.push(' .appendField(' + fields[x] + ')'); + // Generate output, or next/previous connections. + switch (rootBlock.getFieldValue('CONNECTIONS')) { + case 'LEFT': + code.push(connectionLine_('setOutput', 'OUTPUTTYPE')); + break; + case 'BOTH': + code.push(connectionLine_('setPreviousStatement', 'TOPTYPE')); + code.push(connectionLine_('setNextStatement', 'BOTTOMTYPE')); + break; + case 'TOP': + code.push(connectionLine_('setPreviousStatement', 'TOPTYPE')); + break; + case 'BOTTOM': + code.push(connectionLine_('setNextStatement', 'BOTTOMTYPE')); + break; } - // Add semicolon to last line to finish the statement. - code[code.length - 1] += ';'; - contentsBlock = contentsBlock.nextConnection && - contentsBlock.nextConnection.targetBlock(); + code.push(" this.setTooltip('');"); + code.push(" }"); } - // Generate inline/external switch. - if (rootBlock.getFieldValue('INLINE') == 'INT') { - code.push(' this.setInputsInline(true);'); - } - // Generate output, or next/previous connections. - switch (rootBlock.getFieldValue('CONNECTIONS')) { - case 'LEFT': - code.push(connectionLine_('setOutput', 'OUTPUTTYPE')); - break; - case 'BOTH': - code.push(connectionLine_('setPreviousStatement', 'TOPTYPE')); - code.push(connectionLine_('setNextStatement', 'BOTTOMTYPE')); - break; - case 'TOP': - code.push(connectionLine_('setPreviousStatement', 'TOPTYPE')); - break; - case 'BOTTOM': - code.push(connectionLine_('setNextStatement', 'BOTTOMTYPE')); - break; - } - code.push(" this.setTooltip('');"); - code.push(" }"); code.push("};"); injectCode(code, 'languagePre'); @@ -138,7 +141,7 @@ function updateLanguage() { * @private */ function connectionLine_(functionName, typeName) { - var type = getOptTypesFrom(rootBlock, typeName); + var type = getOptTypesFrom(getRootBlock(), typeName); if (type) { type = ', ' + type; } @@ -153,66 +156,68 @@ function connectionLine_(functionName, typeName) { function getFields(block) { var fields = []; while (block) { - switch (block.type) { - case 'field_static': - // Result: 'hello' - fields.push(escapeString(block.getFieldValue('TEXT'))); - break; - case 'field_input': - // Result: new Blockly.FieldTextInput('Hello'), 'GREET' - fields.push('new Blockly.FieldTextInput(' + - escapeString(block.getFieldValue('TEXT')) + '), ' + - escapeString(block.getFieldValue('FIELDNAME'))); - break; - case 'field_angle': - // Result: new Blockly.FieldAngle(90), 'ANGLE' - fields.push('new Blockly.FieldAngle(' + - escapeString(block.getFieldValue('ANGLE')) + '), ' + - escapeString(block.getFieldValue('FIELDNAME'))); - break; - case 'field_checkbox': - // Result: new Blockly.FieldCheckbox('TRUE'), 'CHECK' - fields.push('new Blockly.FieldCheckbox(' + - escapeString(block.getFieldValue('CHECKED')) + '), ' + - escapeString(block.getFieldValue('FIELDNAME'))); - break; - case 'field_colour': - // Result: new Blockly.FieldColour('#ff0000'), 'COLOUR' - fields.push('new Blockly.FieldColour(' + - escapeString(block.getFieldValue('COLOUR')) + '), ' + - escapeString(block.getFieldValue('FIELDNAME'))); - break; - case 'field_variable': - // Result: - // new Blockly.FieldVariable('item'), 'VAR' - var varname = block.getFieldValue('TEXT'); - varname = varname ? escapeString(varname) : 'null'; - fields.push('new Blockly.FieldVariable(' + varname + '), ' + - escapeString(block.getFieldValue('FIELDNAME'))); - break; - case 'field_dropdown': - // Result: - // new Blockly.FieldDropdown([['yes', '1'], ['no', '0']]), 'TOGGLE' - var options = []; - for (var x = 0; x < block.optionCount_; x++) { - options[x] = '[' + escapeString(block.getFieldValue('USER' + x)) + - ', ' + escapeString(block.getFieldValue('CPU' + x)) + ']'; - } - if (options.length) { - fields.push('new Blockly.FieldDropdown([' + - options.join(', ') + ']), ' + + if (!block.disabled && !block.getInheritedDisabled()) { + switch (block.type) { + case 'field_static': + // Result: 'hello' + fields.push(escapeString(block.getFieldValue('TEXT'))); + break; + case 'field_input': + // Result: new Blockly.FieldTextInput('Hello'), 'GREET' + fields.push('new Blockly.FieldTextInput(' + + escapeString(block.getFieldValue('TEXT')) + '), ' + escapeString(block.getFieldValue('FIELDNAME'))); - } - break; - case 'field_image': - // Result: new Blockly.FieldImage('http://...', 80, 60) - var src = escapeString(block.getFieldValue('SRC')); - var width = Number(block.getFieldValue('WIDTH')); - var height = Number(block.getFieldValue('HEIGHT')); - var alt = escapeString(block.getFieldValue('ALT')); - fields.push('new Blockly.FieldImage(' + - src + ', ' + width + ', ' + height + ', ' + alt + ')'); - break; + break; + case 'field_angle': + // Result: new Blockly.FieldAngle(90), 'ANGLE' + fields.push('new Blockly.FieldAngle(' + + escapeString(block.getFieldValue('ANGLE')) + '), ' + + escapeString(block.getFieldValue('FIELDNAME'))); + break; + case 'field_checkbox': + // Result: new Blockly.FieldCheckbox('TRUE'), 'CHECK' + fields.push('new Blockly.FieldCheckbox(' + + escapeString(block.getFieldValue('CHECKED')) + '), ' + + escapeString(block.getFieldValue('FIELDNAME'))); + break; + case 'field_colour': + // Result: new Blockly.FieldColour('#ff0000'), 'COLOUR' + fields.push('new Blockly.FieldColour(' + + escapeString(block.getFieldValue('COLOUR')) + '), ' + + escapeString(block.getFieldValue('FIELDNAME'))); + break; + case 'field_variable': + // Result: + // new Blockly.FieldVariable('item'), 'VAR' + var varname = block.getFieldValue('TEXT'); + varname = varname ? escapeString(varname) : 'null'; + fields.push('new Blockly.FieldVariable(' + varname + '), ' + + escapeString(block.getFieldValue('FIELDNAME'))); + break; + case 'field_dropdown': + // Result: + // new Blockly.FieldDropdown([['yes', '1'], ['no', '0']]), 'TOGGLE' + var options = []; + for (var x = 0; x < block.optionCount_; x++) { + options[x] = '[' + escapeString(block.getFieldValue('USER' + x)) + + ', ' + escapeString(block.getFieldValue('CPU' + x)) + ']'; + } + if (options.length) { + fields.push('new Blockly.FieldDropdown([' + + options.join(', ') + ']), ' + + escapeString(block.getFieldValue('FIELDNAME'))); + } + break; + case 'field_image': + // Result: new Blockly.FieldImage('http://...', 80, 60) + var src = escapeString(block.getFieldValue('SRC')); + var width = Number(block.getFieldValue('WIDTH')); + var height = Number(block.getFieldValue('HEIGHT')); + var alt = escapeString(block.getFieldValue('ALT')); + fields.push('new Blockly.FieldImage(' + + src + ', ' + width + ', ' + height + ', ' + alt + ')'); + break; + } } block = block.nextConnection && block.nextConnection.targetBlock(); } @@ -262,7 +267,7 @@ function getOptTypesFrom(block, name) { function getTypesFrom_(block, name) { var typeBlock = block.getInputTargetBlock(name); var types; - if (!typeBlock) { + if (!typeBlock || typeBlock.disabled) { types = []; } else if (typeBlock.type == 'type_other') { types = [escapeString(typeBlock.getFieldValue('TYPE'))]; @@ -295,64 +300,71 @@ function updateGenerator() { } var language = document.getElementById('language').value; var code = []; - code.push("Blockly." + language + "['" + blockType + "'] = function(block) {"); - // Loop through every block, and generate getters for any fields or inputs. - var blocks = rootBlock.getDescendants(); - for (var x = 0, block; block = blocks[x]; x++) { - switch (block.type) { - case 'field_input': - var name = block.getFieldValue('FIELDNAME'); - code.push(makeVar('text', name) + - " = block.getFieldValue('" + name + "');"); - break; - case 'field_angle': - var name = block.getFieldValue('FIELDNAME'); - code.push(makeVar('angle', name) + - " = block.getFieldValue('" + name + "');"); - break; - case 'field_dropdown': - var name = block.getFieldValue('FIELDNAME'); - code.push(makeVar('dropdown', name) + - " = block.getFieldValue('" + name + "');"); - break; - case 'field_checkbox': - var name = block.getFieldValue('FIELDNAME'); - code.push(makeVar('checkbox', name) + - " = block.getFieldValue('" + name + "') == 'TRUE';"); - break; - case 'field_colour': - var name = block.getFieldValue('FIELDNAME'); - code.push(makeVar('colour', name) + - " = block.getFieldValue('" + name + "');"); - break; - case 'field_variable': - var name = block.getFieldValue('FIELDNAME'); - code.push(makeVar('variable', name) + - " = Blockly." + language + - ".variableDB_.getName(block.getFieldValue('" + name + - "'), Blockly.Variables.NAME_TYPE);"); - break; - case 'input_value': - var name = block.getFieldValue('INPUTNAME'); - code.push(makeVar('value', name) + - " = Blockly." + language + ".valueToCode(block, '" + name + - "', Blockly." + language + ".ORDER_ATOMIC);"); - break; - case 'input_statement': - var name = block.getFieldValue('INPUTNAME'); - code.push(makeVar('statements', name) + - " = Blockly.' + language + '.statementToCode(block, '" + - name + "');"); - break; + code.push("Blockly." + language + "['" + blockType + + "'] = function(block) {"); + var rootBlock = getRootBlock(); + if (rootBlock) { + // Loop through every block, and generate getters for any fields or inputs. + var blocks = rootBlock.getDescendants(); + for (var x = 0, block; block = blocks[x]; x++) { + if (block.disabled || block.getInheritedDisabled()) { + continue; + } + switch (block.type) { + case 'field_input': + var name = block.getFieldValue('FIELDNAME'); + code.push(makeVar('text', name) + + " = block.getFieldValue('" + name + "');"); + break; + case 'field_angle': + var name = block.getFieldValue('FIELDNAME'); + code.push(makeVar('angle', name) + + " = block.getFieldValue('" + name + "');"); + break; + case 'field_dropdown': + var name = block.getFieldValue('FIELDNAME'); + code.push(makeVar('dropdown', name) + + " = block.getFieldValue('" + name + "');"); + break; + case 'field_checkbox': + var name = block.getFieldValue('FIELDNAME'); + code.push(makeVar('checkbox', name) + + " = block.getFieldValue('" + name + "') == 'TRUE';"); + break; + case 'field_colour': + var name = block.getFieldValue('FIELDNAME'); + code.push(makeVar('colour', name) + + " = block.getFieldValue('" + name + "');"); + break; + case 'field_variable': + var name = block.getFieldValue('FIELDNAME'); + code.push(makeVar('variable', name) + + " = Blockly." + language + + ".variableDB_.getName(block.getFieldValue('" + name + + "'), Blockly.Variables.NAME_TYPE);"); + break; + case 'input_value': + var name = block.getFieldValue('INPUTNAME'); + code.push(makeVar('value', name) + + " = Blockly." + language + ".valueToCode(block, '" + name + + "', Blockly." + language + ".ORDER_ATOMIC);"); + break; + case 'input_statement': + var name = block.getFieldValue('INPUTNAME'); + code.push(makeVar('statements', name) + + " = Blockly." + language + ".statementToCode(block, '" + + name + "');"); + break; + } + } + code.push(" // TODO: Assemble " + language + " into code variable."); + code.push(" var code = \'...\';"); + if (rootBlock.getFieldValue('CONNECTIONS') == 'LEFT') { + code.push(" // TODO: Change ORDER_NONE to the correct strength."); + code.push(" return [code, Blockly." + language + ".ORDER_NONE];"); + } else { + code.push(" return code;"); } - } - code.push(" // TODO: Assemble " + language + " into code variable."); - code.push(" var code = \'...\';"); - if (rootBlock.getFieldValue('CONNECTIONS') == 'LEFT') { - code.push(" // TODO: Change ORDER_NONE to the correct strength."); - code.push(" return [code, Blockly." + language + ".ORDER_NONE];"); - } else { - code.push(" return code;"); } code.push("};"); @@ -389,10 +401,38 @@ function injectCode(code, id) { pre.innerHTML = code; } +/** + * Return the uneditable container block that everything else attaches to. + * @return {Blockly.Block} + */ +function getRootBlock() { + var blocks = Blockly.mainWorkspace.getTopBlocks(false); + for (var i = 0, block; block = blocks[i]; i++) { + if (block.type == 'factory_base') { + return block; + } + } + return null; +} + /** * Initialize Blockly and layout. Called on page load. */ function init() { + if ('BlocklyStorage' in window) { + BlocklyStorage.HTTPREQUEST_ERROR = + 'There was a problem with the request.\n'; + BlocklyStorage.LINK_ALERT = + 'Share your blocks with this link:\n\n%1'; + BlocklyStorage.HASH_ERROR = + 'Sorry, "%1" doesn\'t correspond with any saved Blockly file.'; + BlocklyStorage.XML_ERROR = 'Could not load your saved file.\n'+ + 'Perhaps it was created with a different version of Blockly?'; + var linkButton = document.getElementById('linkButton'); + linkButton.style.display = 'inline-block'; + linkButton.addEventListener('click', BlocklyStorage.link); + } + var expandList = [ document.getElementById('blockly'), document.getElementById('previewFrame'), @@ -413,11 +453,15 @@ function init() { {path: '../../', toolbox: toolbox}); // Create the root block. - rootBlock = new Blockly.Block(Blockly.mainWorkspace, 'factory_base'); - rootBlock.initSvg(); - rootBlock.render(); - rootBlock.setMovable(false); - rootBlock.setDeletable(false); + if ('BlocklyStorage' in window && window.location.hash.length > 1) { + BlocklyStorage.retrieveXml(window.location.hash.substring(1)); + } else { + var rootBlock = Blockly.Block.obtain(Blockly.mainWorkspace, 'factory_base'); + rootBlock.initSvg(); + rootBlock.render(); + rootBlock.setMovable(false); + rootBlock.setDeletable(false); + } Blockly.addChangeListener(onchange); document.getElementById('direction') diff --git a/apps/blockfactory/index.html b/apps/blockfactory/index.html index a69551f73..e172bf723 100644 --- a/apps/blockfactory/index.html +++ b/apps/blockfactory/index.html @@ -4,6 +4,7 @@
+ Preview: + ++ |
+ + + | +