diff --git a/demos/blocklyfactory/app_controller.js b/demos/blockfactory/app_controller.js similarity index 100% rename from demos/blocklyfactory/app_controller.js rename to demos/blockfactory/app_controller.js diff --git a/demos/blocklyfactory/block_exporter_controller.js b/demos/blockfactory/block_exporter_controller.js similarity index 100% rename from demos/blocklyfactory/block_exporter_controller.js rename to demos/blockfactory/block_exporter_controller.js diff --git a/demos/blocklyfactory/block_exporter_tools.js b/demos/blockfactory/block_exporter_tools.js similarity index 100% rename from demos/blocklyfactory/block_exporter_tools.js rename to demos/blockfactory/block_exporter_tools.js diff --git a/demos/blocklyfactory/block_exporter_view.js b/demos/blockfactory/block_exporter_view.js similarity index 100% rename from demos/blocklyfactory/block_exporter_view.js rename to demos/blockfactory/block_exporter_view.js diff --git a/demos/blocklyfactory/block_library_controller.js b/demos/blockfactory/block_library_controller.js similarity index 100% rename from demos/blocklyfactory/block_library_controller.js rename to demos/blockfactory/block_library_controller.js diff --git a/demos/blocklyfactory/block_library_storage.js b/demos/blockfactory/block_library_storage.js similarity index 100% rename from demos/blocklyfactory/block_library_storage.js rename to demos/blockfactory/block_library_storage.js diff --git a/demos/blocklyfactory/block_library_view.js b/demos/blockfactory/block_library_view.js similarity index 100% rename from demos/blocklyfactory/block_library_view.js rename to demos/blockfactory/block_library_view.js diff --git a/demos/blocklyfactory/block_option.js b/demos/blockfactory/block_option.js similarity index 100% rename from demos/blocklyfactory/block_option.js rename to demos/blockfactory/block_option.js diff --git a/demos/blocklyfactory/factory.css b/demos/blockfactory/factory.css similarity index 100% rename from demos/blocklyfactory/factory.css rename to demos/blockfactory/factory.css diff --git a/demos/blockfactory/factory.js b/demos/blockfactory/factory.js index 4af5ff28a..873c007b2 100644 --- a/demos/blockfactory/factory.js +++ b/demos/blockfactory/factory.js @@ -1,11 +1,12 @@ /** - * Blockly Demos: Block Factory + * @license + * Visual Blocks Editor * - * Copyright 2012 Google Inc. + * Copyright 2016 Google Inc. * https://developers.google.com/blockly/ * * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. + * You may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 @@ -18,32 +19,58 @@ */ /** - * @fileoverview JavaScript for Blockly's Block Factory application. - * @author fraser@google.com (Neil Fraser) + * @fileoverview JavaScript for Blockly's Block Factory application through + * which users can build blocks using a visual interface and dynamically + * generate a preview block and starter code for the block (block definition and + * generator stub. Uses the Block Factory namespace. Depends on the FactoryUtils + * for its code generation functions. + * + * @author fraser@google.com (Neil Fraser), quachtina96 (Tina Quach) */ 'use strict'; +/** + * Namespace for Block Factory. + */ +goog.provide('BlockFactory'); + +goog.require('FactoryUtils'); +goog.require('StandardCategories'); + + /** * Workspace for user to build block. * @type {Blockly.Workspace} */ -var mainWorkspace = null; +BlockFactory.mainWorkspace = null; /** * Workspace for preview of block. * @type {Blockly.Workspace} */ -var previewWorkspace = null; +BlockFactory.previewWorkspace = null; /** * Name of block if not named. */ -var UNNAMED = 'unnamed'; +BlockFactory.UNNAMED = 'unnamed'; + +/** + * Existing direction ('ltr' vs 'rtl') of preview. + */ +BlockFactory.oldDir = null; + +/* + * The starting XML for the Block Factory main workspace. Contains the + * unmovable, undeletable factory_base block. + */ +BlockFactory.STARTER_BLOCK_XML_TEXT = ''; /** * Change the language code format. */ -function formatChange() { +BlockFactory.formatChange = function() { var mask = document.getElementById('blocklyMask'); var languagePre = document.getElementById('languagePre'); var languageTA = document.getElementById('languageTA'); @@ -55,619 +82,63 @@ function formatChange() { var code = languagePre.textContent.trim(); languageTA.value = code; languageTA.focus(); - updatePreview(); + BlockFactory.updatePreview(); } else { mask.style.display = 'none'; languageTA.style.display = 'none'; languagePre.style.display = 'block'; - updateLanguage(); + BlockFactory.updateLanguage(); } - disableEnableLink(); -} + BlockFactory.disableEnableLink(); +}; /** * Update the language code based on constructs made in Blockly. */ -function updateLanguage() { - var rootBlock = getRootBlock(); +BlockFactory.updateLanguage = function() { + var rootBlock = FactoryUtils.getRootBlock(BlockFactory.mainWorkspace); if (!rootBlock) { return; } var blockType = rootBlock.getFieldValue('NAME').trim().toLowerCase(); if (!blockType) { - blockType = UNNAMED; + blockType = BlockFactory.UNNAMED; } - blockType = blockType.replace(/\W/g, '_').replace(/^(\d)/, '_\\1'); - switch (document.getElementById('format').value) { - case 'JSON': - var code = formatJson_(blockType, rootBlock); - break; - case 'JavaScript': - var code = formatJavaScript_(blockType, rootBlock); - break; - } - injectCode(code, 'languagePre'); - updatePreview(); -} - -/** - * Update the language code as JSON. - * @param {string} blockType Name of block. - * @param {!Blockly.Block} rootBlock Factory_base block. - * @return {string} Generanted language code. - * @private - */ -function formatJson_(blockType, rootBlock) { - var JS = {}; - // Type is not used by Blockly, but may be used by a loader. - JS.type = blockType; - // Generate inputs. - var message = []; - var args = []; - var contentsBlock = rootBlock.getInputTargetBlock('INPUTS'); - var lastInput = null; - while (contentsBlock) { - if (!contentsBlock.disabled && !contentsBlock.getInheritedDisabled()) { - var fields = getFieldsJson_(contentsBlock.getInputTargetBlock('FIELDS')); - for (var i = 0; i < fields.length; i++) { - if (typeof fields[i] == 'string') { - message.push(fields[i].replace(/%/g, '%%')); - } else { - args.push(fields[i]); - message.push('%' + args.length); - } - } - - var input = {type: contentsBlock.type}; - // Dummy inputs don't have names. Other inputs do. - if (contentsBlock.type != 'input_dummy') { - input.name = contentsBlock.getFieldValue('INPUTNAME'); - } - var check = JSON.parse(getOptTypesFrom(contentsBlock, 'TYPE') || 'null'); - if (check) { - input.check = check; - } - var align = contentsBlock.getFieldValue('ALIGN'); - if (align != 'LEFT') { - input.align = align; - } - args.push(input); - message.push('%' + args.length); - lastInput = contentsBlock; - } - contentsBlock = contentsBlock.nextConnection && - contentsBlock.nextConnection.targetBlock(); - } - // Remove last input if dummy and not empty. - if (lastInput && lastInput.type == 'input_dummy') { - var fields = lastInput.getInputTargetBlock('FIELDS'); - if (fields && getFieldsJson_(fields).join('').trim() != '') { - var align = lastInput.getFieldValue('ALIGN'); - if (align != 'LEFT') { - JS.lastDummyAlign0 = align; - } - args.pop(); - message.pop(); - } - } - JS.message0 = message.join(' '); - if (args.length) { - JS.args0 = args; - } - // Generate inline/external switch. - if (rootBlock.getFieldValue('INLINE') == 'EXT') { - JS.inputsInline = false; - } else if (rootBlock.getFieldValue('INLINE') == 'INT') { - JS.inputsInline = true; - } - // Generate output, or next/previous connections. - switch (rootBlock.getFieldValue('CONNECTIONS')) { - case 'LEFT': - JS.output = - JSON.parse(getOptTypesFrom(rootBlock, 'OUTPUTTYPE') || 'null'); - break; - case 'BOTH': - JS.previousStatement = - JSON.parse(getOptTypesFrom(rootBlock, 'TOPTYPE') || 'null'); - JS.nextStatement = - JSON.parse(getOptTypesFrom(rootBlock, 'BOTTOMTYPE') || 'null'); - break; - case 'TOP': - JS.previousStatement = - JSON.parse(getOptTypesFrom(rootBlock, 'TOPTYPE') || 'null'); - break; - case 'BOTTOM': - JS.nextStatement = - JSON.parse(getOptTypesFrom(rootBlock, 'BOTTOMTYPE') || 'null'); - break; - } - // Generate colour. - var colourBlock = rootBlock.getInputTargetBlock('COLOUR'); - if (colourBlock && !colourBlock.disabled) { - var hue = parseInt(colourBlock.getFieldValue('HUE'), 10); - JS.colour = hue; - } - JS.tooltip = ''; - JS.helpUrl = 'http://www.example.com/'; - return JSON.stringify(JS, null, ' '); -} - -/** - * Update the language code as JavaScript. - * @param {string} blockType Name of block. - * @param {!Blockly.Block} rootBlock Factory_base block. - * @return {string} Generanted language code. - * @private - */ -function formatJavaScript_(blockType, rootBlock) { - var code = []; - code.push("Blockly.Blocks['" + blockType + "'] = {"); - code.push(" init: function() {"); - // Generate inputs. - var TYPES = {'input_value': 'appendValueInput', - 'input_statement': 'appendStatementInput', - 'input_dummy': 'appendDummyInput'}; - var contentsBlock = rootBlock.getInputTargetBlock('INPUTS'); - while (contentsBlock) { - if (!contentsBlock.disabled && !contentsBlock.getInheritedDisabled()) { - var name = ''; - // Dummy inputs don't have names. Other inputs do. - if (contentsBlock.type != 'input_dummy') { - name = escapeString(contentsBlock.getFieldValue('INPUTNAME')); - } - code.push(' this.' + TYPES[contentsBlock.type] + '(' + name + ')'); - var check = getOptTypesFrom(contentsBlock, 'TYPE'); - if (check) { - code.push(' .setCheck(' + check + ')'); - } - var align = contentsBlock.getFieldValue('ALIGN'); - if (align != 'LEFT') { - code.push(' .setAlign(Blockly.ALIGN_' + align + ')'); - } - var fields = getFieldsJs_(contentsBlock.getInputTargetBlock('FIELDS')); - for (var i = 0; i < fields.length; i++) { - code.push(' .appendField(' + fields[i] + ')'); - } - // Add semicolon to last line to finish the statement. - code[code.length - 1] += ';'; - } - contentsBlock = contentsBlock.nextConnection && - contentsBlock.nextConnection.targetBlock(); - } - // Generate inline/external switch. - if (rootBlock.getFieldValue('INLINE') == 'EXT') { - code.push(' this.setInputsInline(false);'); - } else if (rootBlock.getFieldValue('INLINE') == 'INT') { - code.push(' this.setInputsInline(true);'); - } - // Generate output, or next/previous connections. - switch (rootBlock.getFieldValue('CONNECTIONS')) { - case 'LEFT': - code.push(connectionLineJs_('setOutput', 'OUTPUTTYPE')); - break; - case 'BOTH': - code.push(connectionLineJs_('setPreviousStatement', 'TOPTYPE')); - code.push(connectionLineJs_('setNextStatement', 'BOTTOMTYPE')); - break; - case 'TOP': - code.push(connectionLineJs_('setPreviousStatement', 'TOPTYPE')); - break; - case 'BOTTOM': - code.push(connectionLineJs_('setNextStatement', 'BOTTOMTYPE')); - break; - } - // Generate colour. - var colourBlock = rootBlock.getInputTargetBlock('COLOUR'); - if (colourBlock && !colourBlock.disabled) { - var hue = parseInt(colourBlock.getFieldValue('HUE'), 10); - if (!isNaN(hue)) { - code.push(' this.setColour(' + hue + ');'); - } - } - code.push(" this.setTooltip('');"); - code.push(" this.setHelpUrl('http://www.example.com/');"); - code.push(' }'); - code.push('};'); - return code.join('\n'); -} - -/** - * Create JS code required to create a top, bottom, or value connection. - * @param {string} functionName JavaScript function name. - * @param {string} typeName Name of type input. - * @return {string} Line of JavaScript code to create connection. - * @private - */ -function connectionLineJs_(functionName, typeName) { - var type = getOptTypesFrom(getRootBlock(), typeName); - if (type) { - type = ', ' + type; - } else { - type = ''; - } - return ' this.' + functionName + '(true' + type + ');'; -} - -/** - * Returns field strings and any config. - * @param {!Blockly.Block} block Input block. - * @return {!Array.} Field strings. - * @private - */ -function getFieldsJs_(block) { - var fields = []; - while (block) { - 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_number': - // Result: new Blockly.FieldNumber(10, 0, 100, 1), 'NUMBER' - var args = [ - Number(block.getFieldValue('VALUE')), - Number(block.getFieldValue('MIN')), - Number(block.getFieldValue('MAX')), - Number(block.getFieldValue('PRECISION')) - ]; - // Remove any trailing arguments that aren't needed. - if (args[3] == 0) { - args.pop(); - if (args[2] == Infinity) { - args.pop(); - if (args[1] == -Infinity) { - args.pop(); - } - } - } - fields.push('new Blockly.FieldNumber(' + args.join(', ') + '), ' + - escapeString(block.getFieldValue('FIELDNAME'))); - break; - case 'field_angle': - // Result: new Blockly.FieldAngle(90), 'ANGLE' - fields.push('new Blockly.FieldAngle(' + - Number(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_date': - // Result: new Blockly.FieldDate('2015-02-04'), 'DATE' - fields.push('new Blockly.FieldDate(' + - escapeString(block.getFieldValue('DATE')) + '), ' + - escapeString(block.getFieldValue('FIELDNAME'))); - break; - case 'field_variable': - // Result: new Blockly.FieldVariable('item'), 'VAR' - var varname = escapeString(block.getFieldValue('TEXT') || 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 i = 0; i < block.optionCount_; i++) { - options[i] = '[' + escapeString(block.getFieldValue('USER' + i)) + - ', ' + escapeString(block.getFieldValue('CPU' + i)) + ']'; - } - 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(); - } - return fields; -} - -/** - * Returns field strings and any config. - * @param {!Blockly.Block} block Input block. - * @return {!Array.} Array of static text and field configs. - * @private - */ -function getFieldsJson_(block) { - var fields = []; - while (block) { - if (!block.disabled && !block.getInheritedDisabled()) { - switch (block.type) { - case 'field_static': - // Result: 'hello' - fields.push(block.getFieldValue('TEXT')); - break; - case 'field_input': - fields.push({ - type: block.type, - name: block.getFieldValue('FIELDNAME'), - text: block.getFieldValue('TEXT') - }); - break; - case 'field_number': - var obj = { - type: block.type, - name: block.getFieldValue('FIELDNAME'), - value: parseFloat(block.getFieldValue('VALUE')) - }; - var min = parseFloat(block.getFieldValue('MIN')); - if (min > -Infinity) { - obj.min = min; - } - var max = parseFloat(block.getFieldValue('MAX')); - if (max < Infinity) { - obj.max = max; - } - var precision = parseFloat(block.getFieldValue('PRECISION')); - if (precision) { - obj.precision = precision; - } - fields.push(obj); - break; - case 'field_angle': - fields.push({ - type: block.type, - name: block.getFieldValue('FIELDNAME'), - angle: Number(block.getFieldValue('ANGLE')) - }); - break; - case 'field_checkbox': - fields.push({ - type: block.type, - name: block.getFieldValue('FIELDNAME'), - checked: block.getFieldValue('CHECKED') == 'TRUE' - }); - break; - case 'field_colour': - fields.push({ - type: block.type, - name: block.getFieldValue('FIELDNAME'), - colour: block.getFieldValue('COLOUR') - }); - break; - case 'field_date': - fields.push({ - type: block.type, - name: block.getFieldValue('FIELDNAME'), - date: block.getFieldValue('DATE') - }); - break; - case 'field_variable': - fields.push({ - type: block.type, - name: block.getFieldValue('FIELDNAME'), - variable: block.getFieldValue('TEXT') || null - }); - break; - case 'field_dropdown': - var options = []; - for (var i = 0; i < block.optionCount_; i++) { - options[i] = [block.getFieldValue('USER' + i), - block.getFieldValue('CPU' + i)]; - } - if (options.length) { - fields.push({ - type: block.type, - name: block.getFieldValue('FIELDNAME'), - options: options - }); - } - break; - case 'field_image': - fields.push({ - type: block.type, - src: block.getFieldValue('SRC'), - width: Number(block.getFieldValue('WIDTH')), - height: Number(block.getFieldValue('HEIGHT')), - alt: block.getFieldValue('ALT') - }); - break; - } - } - block = block.nextConnection && block.nextConnection.targetBlock(); - } - return fields; -} - -/** - * Escape a string. - * @param {string} string String to escape. - * @return {string} Escaped string surrouned by quotes. - */ -function escapeString(string) { - return JSON.stringify(string); -} - -/** - * Fetch the type(s) defined in the given input. - * Format as a string for appending to the generated code. - * @param {!Blockly.Block} block Block with input. - * @param {string} name Name of the input. - * @return {?string} String defining the types. - */ -function getOptTypesFrom(block, name) { - var types = getTypesFrom_(block, name); - if (types.length == 0) { - return undefined; - } else if (types.indexOf('null') != -1) { - return 'null'; - } else if (types.length == 1) { - return types[0]; - } else { - return '[' + types.join(', ') + ']'; - } -} - -/** - * Fetch the type(s) defined in the given input. - * @param {!Blockly.Block} block Block with input. - * @param {string} name Name of the input. - * @return {!Array.} List of types. - * @private - */ -function getTypesFrom_(block, name) { - var typeBlock = block.getInputTargetBlock(name); - var types; - if (!typeBlock || typeBlock.disabled) { - types = []; - } else if (typeBlock.type == 'type_other') { - types = [escapeString(typeBlock.getFieldValue('TYPE'))]; - } else if (typeBlock.type == 'type_group') { - types = []; - for (var i = 0; i < typeBlock.typeCount_; i++) { - types = types.concat(getTypesFrom_(typeBlock, 'TYPE' + i)); - } - // Remove duplicates. - var hash = Object.create(null); - for (var n = types.length - 1; n >= 0; n--) { - if (hash[types[n]]) { - types.splice(n, 1); - } - hash[types[n]] = true; - } - } else { - types = [escapeString(typeBlock.valueType)]; - } - return types; -} + var format = document.getElementById('format').value; + var code = FactoryUtils.getBlockDefinition(blockType, rootBlock, format, + BlockFactory.mainWorkspace); + FactoryUtils.injectCode(code, 'languagePre'); + BlockFactory.updatePreview(); +}; /** * Update the generator code. * @param {!Blockly.Block} block Rendered block in preview workspace. */ -function updateGenerator(block) { - function makeVar(root, name) { - name = name.toLowerCase().replace(/\W/g, '_'); - return ' var ' + root + '_' + name; - } +BlockFactory.updateGenerator = function(block) { var language = document.getElementById('language').value; - var code = []; - code.push("Blockly." + language + "['" + block.type + - "'] = function(block) {"); - - // Generate getters for any fields or inputs. - for (var i = 0, input; input = block.inputList[i]; i++) { - for (var j = 0, field; field = input.fieldRow[j]; j++) { - var name = field.name; - if (!name) { - continue; - } - if (field instanceof Blockly.FieldVariable) { - // Subclass of Blockly.FieldDropdown, must test first. - code.push(makeVar('variable', name) + - " = Blockly." + language + - ".variableDB_.getName(block.getFieldValue('" + name + - "'), Blockly.Variables.NAME_TYPE);"); - } else if (field instanceof Blockly.FieldAngle) { - // Subclass of Blockly.FieldTextInput, must test first. - code.push(makeVar('angle', name) + - " = block.getFieldValue('" + name + "');"); - } else if (Blockly.FieldDate && field instanceof Blockly.FieldDate) { - // Blockly.FieldDate may not be compiled into Blockly. - code.push(makeVar('date', name) + - " = block.getFieldValue('" + name + "');"); - } else if (field instanceof Blockly.FieldColour) { - code.push(makeVar('colour', name) + - " = block.getFieldValue('" + name + "');"); - } else if (field instanceof Blockly.FieldCheckbox) { - code.push(makeVar('checkbox', name) + - " = block.getFieldValue('" + name + "') == 'TRUE';"); - } else if (field instanceof Blockly.FieldDropdown) { - code.push(makeVar('dropdown', name) + - " = block.getFieldValue('" + name + "');"); - } else if (field instanceof Blockly.FieldNumber) { - code.push(makeVar('number', name) + - " = block.getFieldValue('" + name + "');"); - } else if (field instanceof Blockly.FieldTextInput) { - code.push(makeVar('text', name) + - " = block.getFieldValue('" + name + "');"); - } - } - var name = input.name; - if (name) { - if (input.type == Blockly.INPUT_VALUE) { - code.push(makeVar('value', name) + - " = Blockly." + language + ".valueToCode(block, '" + name + - "', Blockly." + language + ".ORDER_ATOMIC);"); - } else if (input.type == Blockly.NEXT_STATEMENT) { - code.push(makeVar('statements', name) + - " = Blockly." + language + ".statementToCode(block, '" + - name + "');"); - } - } - } - // Most languages end lines with a semicolon. Python does not. - var lineEnd = { - 'JavaScript': ';', - 'Python': '', - 'PHP': ';', - 'Dart': ';' - }; - code.push(" // TODO: Assemble " + language + " into code variable."); - if (block.outputConnection) { - code.push(" var code = '...';"); - code.push(" // TODO: Change ORDER_NONE to the correct strength."); - code.push(" return [code, Blockly." + language + ".ORDER_NONE];"); - } else { - code.push(" var code = '..." + (lineEnd[language] || '') + "\\n';"); - code.push(" return code;"); - } - code.push("};"); - - injectCode(code.join('\n'), 'generatorPre'); -} - -/** - * Existing direction ('ltr' vs 'rtl') of preview. - */ -var oldDir = null; + var generatorStub = FactoryUtils.getGeneratorStub(block, language); + FactoryUtils.injectCode(generatorStub, 'generatorPre'); +}; /** * Update the preview display. */ -function updatePreview() { +BlockFactory.updatePreview = function() { // Toggle between LTR/RTL if needed (also used in first display). var newDir = document.getElementById('direction').value; - if (oldDir != newDir) { - if (previewWorkspace) { - previewWorkspace.dispose(); + if (BlockFactory.oldDir != newDir) { + if (BlockFactory.previewWorkspace) { + BlockFactory.previewWorkspace.dispose(); } var rtl = newDir == 'rtl'; - previewWorkspace = Blockly.inject('preview', + BlockFactory.previewWorkspace = Blockly.inject('preview', {rtl: rtl, media: '../../media/', scrollbars: true}); - oldDir = newDir; + BlockFactory.oldDir = newDir; } - previewWorkspace.clear(); + BlockFactory.previewWorkspace.clear(); // Fetch the code and determine its format (JSON or JavaScript). var format = document.getElementById('format').value; @@ -693,14 +164,14 @@ function updatePreview() { var backupBlocks = Blockly.Blocks; try { // Make a shallow copy. - Blockly.Blocks = {}; + Blockly.Blocks = Object.create(null); for (var prop in backupBlocks) { Blockly.Blocks[prop] = backupBlocks[prop]; } if (format == 'JSON') { var json = JSON.parse(code); - Blockly.Blocks[json.type || UNNAMED] = { + Blockly.Blocks[json.type || BlockFactory.UNNAMED] = { init: function() { this.jsonInit(json); } @@ -725,126 +196,70 @@ function updatePreview() { } // Create the preview block. - var previewBlock = previewWorkspace.newBlock(blockType); + var previewBlock = BlockFactory.previewWorkspace.newBlock(blockType); previewBlock.initSvg(); previewBlock.render(); previewBlock.setMovable(false); previewBlock.setDeletable(false); previewBlock.moveBy(15, 10); - previewWorkspace.clearUndo(); + BlockFactory.previewWorkspace.clearUndo(); + BlockFactory.updateGenerator(previewBlock); + + // Warn user only if their block type is already exists in Blockly's + // standard library. + var rootBlock = FactoryUtils.getRootBlock(BlockFactory.mainWorkspace); + if (StandardCategories.coreBlockTypes.indexOf(blockType) != -1) { + rootBlock.setWarningText('A core Blockly block already exists ' + + 'under this name.'); + + } else if (blockType == 'block_type') { + // Warn user to let them know they can't save a block under the default + // name 'block_type' + rootBlock.setWarningText('You cannot save a block with the default ' + + 'name, "block_type"'); + + } else { + rootBlock.setWarningText(null); + } - updateGenerator(previewBlock); } finally { Blockly.Blocks = backupBlocks; } -} +}; /** - * Inject code into a pre tag, with syntax highlighting. - * Safe from HTML/script injection. - * @param {string} code Lines of code. - * @param {string} id ID of
 element to inject into.
+ * Disable link and save buttons if the format is 'Manual', enable otherwise.
  */
-function injectCode(code, id) {
-  var pre = document.getElementById(id);
-  pre.textContent = code;
-  code = pre.innerHTML;
-  code = prettyPrintOne(code, 'js');
-  pre.innerHTML = code;
-}
-
-/**
- * Return the uneditable container block that everything else attaches to.
- * @return {Blockly.Block}
- */
-function getRootBlock() {
-  var blocks = mainWorkspace.getTopBlocks(false);
-  for (var i = 0, block; block = blocks[i]; i++) {
-    if (block.type == 'factory_base') {
-      return block;
-    }
-  }
-  return null;
-}
-
-/**
- * Disable the link button if the format is 'Manual', enable otherwise.
- */
-function disableEnableLink() {
+BlockFactory.disableEnableLink = function() {
   var linkButton = document.getElementById('linkButton');
-  linkButton.disabled = document.getElementById('format').value == 'Manual';
-}
+  var saveBlockButton = document.getElementById('localSaveButton');
+  var saveToLibButton = document.getElementById('saveToBlockLibraryButton');
+  var disabled = document.getElementById('format').value == 'Manual';
+  linkButton.disabled = disabled;
+  saveBlockButton.disabled = disabled;
+  saveToLibButton.disabled = disabled;
+};
 
 /**
- * Initialize Blockly and layout.  Called on page load.
+ * Render starter block (factory_base).
  */
-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',
-        function() {BlocklyStorage.link(mainWorkspace);});
-    disableEnableLink();
-  }
+BlockFactory.showStarterBlock = function() {
+  BlockFactory.mainWorkspace.clear();
+  var xml = Blockly.Xml.textToDom(BlockFactory.STARTER_BLOCK_XML_TEXT);
+  Blockly.Xml.domToWorkspace(xml, BlockFactory.mainWorkspace);
+};
 
-  document.getElementById('helpButton').addEventListener('click',
-    function() {
-      open('https://developers.google.com/blockly/guides/create-custom-blocks/block-factory',
-           'BlockFactoryHelp');
-    });
-
-  var expandList = [
-    document.getElementById('blockly'),
-    document.getElementById('blocklyMask'),
-    document.getElementById('preview'),
-    document.getElementById('languagePre'),
-    document.getElementById('languageTA'),
-    document.getElementById('generatorPre')
-  ];
-  var onresize = function(e) {
-    for (var i = 0, expand; expand = expandList[i]; i++) {
-      expand.style.width = (expand.parentNode.offsetWidth - 2) + 'px';
-      expand.style.height = (expand.parentNode.offsetHeight - 2) + 'px';
-    }
-  };
-  onresize();
-  window.addEventListener('resize', onresize);
-
-  var toolbox = document.getElementById('toolbox');
-  mainWorkspace = Blockly.inject('blockly',
-      {collapse: false,
-       toolbox: toolbox,
-       media: '../../media/'});
-
-  // Create the root block.
-  if ('BlocklyStorage' in window && window.location.hash.length > 1) {
-    BlocklyStorage.retrieveXml(window.location.hash.substring(1),
-                               mainWorkspace);
-  } else {
-    var xml = '';
-    Blockly.Xml.domToWorkspace(Blockly.Xml.textToDom(xml), mainWorkspace);
-  }
-  mainWorkspace.clearUndo();
-
-  mainWorkspace.addChangeListener(Blockly.Events.disableOrphans);
-  mainWorkspace.addChangeListener(updateLanguage);
-  document.getElementById('direction')
-      .addEventListener('change', updatePreview);
-  document.getElementById('languageTA')
-      .addEventListener('change', updatePreview);
-  document.getElementById('languageTA')
-      .addEventListener('keyup', updatePreview);
-  document.getElementById('format')
-      .addEventListener('change', formatChange);
-  document.getElementById('language')
-      .addEventListener('change', updatePreview);
-}
-window.addEventListener('load', init);
+/**
+ * Returns whether or not the current block open is the starter block.
+ */
+BlockFactory.isStarterBlock = function() {
+  var rootBlock = FactoryUtils.getRootBlock(BlockFactory.mainWorkspace);
+  // The starter block does not have blocks nested into the factory_base block.
+  return !(rootBlock.getChildren().length > 0 ||
+      // The starter block's name is the default, 'block_type'.
+      rootBlock.getFieldValue('NAME').trim().toLowerCase() != 'block_type' ||
+      // The starter block has no connections.
+      rootBlock.getFieldValue('CONNECTIONS') != 'NONE' ||
+      // The starter block has automatic inputs.
+      rootBlock.getFieldValue('INLINE') != 'AUTO');
+};
diff --git a/demos/blocklyfactory/factory_utils.js b/demos/blockfactory/factory_utils.js
similarity index 100%
rename from demos/blocklyfactory/factory_utils.js
rename to demos/blockfactory/factory_utils.js
diff --git a/demos/blockfactory/index.html b/demos/blockfactory/index.html
index 449b9a5e8..72ddce573 100644
--- a/demos/blockfactory/index.html
+++ b/demos/blockfactory/index.html
@@ -1,107 +1,311 @@
+
+
 
 
 
   
   
-  Blockly Demo: Block Factory
-  
-  
+  Blockly Demo: Blockly Factory
   
+  
   
+  
+  
+  
+  
+  
+  
+  
+  
+  
+  
+  
+  
+  
+  
+  
+  
+  
+  
   
-  
+  
+  
   
   
+  
 
-
-  
-    
-      
+
+

Blockly > + Demos > Blockly Factory + +

+
+
Block Factory
+
Block Exporter
+
Workspace Factory
+
+ + +
+
+

First, select blocks from your block library by clicking on them. Then, use the Export Settings form to download starter code for selected blocks. +

+
+
+

Block Selector

+ + +
+
+ + +
+
+

Export Settings

+
+ +
+

Currently Selected:

+

+
+ Block Definition(s)
+
+ Format: + +
+ File Name: +
+ +
+
+ Generator Stub(s)
+
+ Language: + +
+ File Name: +
+
+
+
+ + +
+
+
+

Export Preview

+
+

Block Definitions:

+

+        
+
+

Generator Stubs:

+

+        
+
+
+ + + +
+
+

+

+ + + + + + + +

+
+ +
+
+

Edit

+

Drag blocks into the workspace to configure the toolbox in your custom workspace.

+
+
-

Blockly > - Demos > Block Factory

-
+ + +
ToolboxWorkspace
+
+
+
+ + + + + + + + + + + + + + + + + - - + -
+ + + + +
+ + + + + + + +
+
+ - -
+

Preview:

+ - -
+
- + - +
+
@@ -138,7 +347,7 @@
-

Language code: +

Block Definition:

-