From 9bb02abb2c604221dfb52fab73f2b0af22f4c1fa Mon Sep 17 00:00:00 2001 From: Emma Dauterman Date: Fri, 26 Aug 2016 12:03:15 -0700 Subject: [PATCH] Blockly Factory: Workspace Factory Options (#600) * Some options stuff changed * Finished changes to options, including moving readonly to toop, fixing zoom errors, indenting suboptions, generating an options string, getting category options automatically, and making max blocks clearer, and having number inputs * Added null check and nit line length --- demos/blocklyfactory/factory.css | 4 + demos/blocklyfactory/index.html | 28 +-- .../workspacefactory/wfactory_controller.js | 167 +++++++++++------- .../workspacefactory/wfactory_generator.js | 34 ++++ .../workspacefactory/wfactory_init.js | 19 ++ .../workspacefactory/wfactory_model.js | 10 -- .../workspacefactory/wfactory_view.js | 19 +- 7 files changed, 187 insertions(+), 94 deletions(-) diff --git a/demos/blocklyfactory/factory.css b/demos/blocklyfactory/factory.css index 6118816f0..704b8de4d 100644 --- a/demos/blocklyfactory/factory.css +++ b/demos/blocklyfactory/factory.css @@ -478,6 +478,10 @@ td { z-index: -1; /* Start behind workspace */ } +#grid_options, #zoom_options, #maxBlockNumber_option { + padding-left: 15px; +} + /* Rules for Closure popup color picker */ .goog-palette { outline: none; diff --git a/demos/blocklyfactory/index.html b/demos/blocklyfactory/index.html index 6c5aadf5e..b002b4061 100644 --- a/demos/blocklyfactory/index.html +++ b/demos/blocklyfactory/index.html @@ -217,36 +217,42 @@ diff --git a/demos/blocklyfactory/workspacefactory/wfactory_controller.js b/demos/blocklyfactory/workspacefactory/wfactory_controller.js index 9e8a37655..af2f9b695 100644 --- a/demos/blocklyfactory/workspacefactory/wfactory_controller.js +++ b/demos/blocklyfactory/workspacefactory/wfactory_controller.js @@ -57,7 +57,7 @@ WorkspaceFactoryController = function(toolboxName, toolboxDiv, previewDiv) { colour: '#ccc', snap: true}, media: '../../media/', - toolbox: this.toolbox, + toolbox: this.toolbox }); // Workspace for user to preview their changes. @@ -99,6 +99,36 @@ WorkspaceFactoryController.MODE_PRELOAD = 'preload'; */ WorkspaceFactoryController.prototype.addCategory = function() { this.allowToTransferFlyoutBlocksToCategory(); + // Check if it's the first category added. + var isFirstCategory = !this.model.hasElements(); + // Give the option to save blocks if their workspace is not empty and they + // are creating their first category. + if (isFirstCategory && this.toolboxWorkspace.getAllBlocks().length > 0) { + var confirmCreate = confirm('Do you want to save your work in another ' + + 'category? If you don\'t, the blocks in your workspace will be ' + + 'deleted.'); + + // Create a new category for current blocks. + if (confirmCreate) { + var name = prompt('Enter the name of the category for your ' + + 'current blocks: '); + if (!name) { // Exit if cancelled. + return; + } + + // Create the new category. + this.createCategory(name, true); + // Set the new category as selected. + var id = this.model.getCategoryIdByName(name); + this.model.setSelectedById(id); + this.view.setCategoryTabSelection(id, true); + // Set default options if switching from single flyout to categories. + this.view.setCategoryOptions(this.model.hasElements()); + this.generateNewOptions(); + // Update preview here in case exit early. + this.updatePreview(); + } + } // After possibly creating a category, check again if it's the first category. var isFirstCategory = !this.model.hasElements(); @@ -112,10 +142,11 @@ WorkspaceFactoryController.prototype.addCategory = function() { // Switch to category. this.switchElement(this.model.getCategoryIdByName(name)); - // Allow the user to use the default options for injecting the workspace + // Sets the default options for injecting the workspace // when there are categories if adding the first category. if (isFirstCategory) { - this.allowToSetDefaultOptions(); + this.view.setCategoryOptions(this.model.hasElements()); + this.generateNewOptions(); } // Update preview. this.updatePreview(); @@ -238,9 +269,6 @@ WorkspaceFactoryController.prototype.removeElement = function() { this.toolboxWorkspace.clear(); this.toolboxWorkspace.clearUndo(); this.model.createDefaultSelectedIfEmpty(); - // Allow the user to use the default options for injecting the workspace - // when there are no categories. - this.allowToSetDefaultOptions(); } // Update preview. this.updatePreview(); @@ -371,9 +399,8 @@ WorkspaceFactoryController.prototype.exportOptionsFile = function() { // Generate new options to remove toolbox XML from options object (if // necessary). this.generateNewOptions(); - // TODO(evd2014): Use Regex to prettify JSON generated. - var data = new Blob([JSON.stringify(this.model.options)], - {type: 'text/javascript'}); + var printableOptions = this.generator.generateOptionsString() + var data = new Blob([printableOptions], {type: 'text/javascript'}); this.view.createAndDownloadFile(fileName, data); }; @@ -465,8 +492,9 @@ WorkspaceFactoryController.prototype.saveStateFromWorkspace = function() { */ WorkspaceFactoryController.prototype.reinjectPreview = function(tree) { this.previewWorkspace.dispose(); - this.model.setOptionsAttribute('toolbox', Blockly.Xml.domToPrettyText(tree)); - this.previewWorkspace = Blockly.inject('preview_blocks', this.model.options); + var injectOptions = this.readOptions_(); + injectOptions['toolbox'] = Blockly.Xml.domToPrettyText(tree); + this.previewWorkspace = Blockly.inject('preview_blocks', injectOptions); Blockly.Xml.domToWorkspace(this.generator.generateWorkspaceXml(), this.previewWorkspace); }; @@ -630,7 +658,8 @@ WorkspaceFactoryController.prototype.loadCategoryByName = function(name) { if (isFirstCategory) { // Allow the user to use the default options for injecting the workspace // when there are categories. - this.allowToSetDefaultOptions(); + this.view.setCategoryOptions(this.model.hasElements()); + this.generateNewOptions(); } // Update preview. this.updatePreview(); @@ -795,9 +824,11 @@ WorkspaceFactoryController.prototype.importToolboxFromTree_ = function(tree) { (this.model.getSelectedId()), this.model.getSelected()); this.saveStateFromWorkspace(); - // Allow the user to set default configuration options for a single flyout - // or multiple categories. - this.allowToSetDefaultOptions(); + + // Set default configuration options for a single flyout or multiple + // categories. + this.view.setCategoryOptions(this.model.hasElements()); + this.generateNewOptions(); this.updatePreview(); }; @@ -861,9 +892,8 @@ WorkspaceFactoryController.prototype.clearAll = function() { this.toolboxWorkspace.clear(); this.toolboxWorkspace.clearUndo(); this.saveStateFromWorkspace(); - if (hasCategories) { - this.allowToSetDefaultOptions(); - } + this.view.setCategoryOptions(this.model.hasElements()); + this.generateNewOptions(); this.updatePreview(); }; @@ -1047,35 +1077,27 @@ WorkspaceFactoryController.prototype.setStandardOptionsAndUpdate = function() { this.generateNewOptions(); }; -/** - * Asks the user if they want to use default configuration options specific - * to categories or a single flyout of blocks. If desired, makes the necessary - * changes to the options object depending on if there are categories and then - * updates the preview workspace. Only updates category/flyout specific - * options, not the base default options that are set regardless of if - * categories or a single flyout are used. - */ -WorkspaceFactoryController.prototype.allowToSetDefaultOptions = function() { - if (!this.model.hasElements() && !confirm('Do you want to use the default ' + - 'workspace configuration options for injecting a workspace without ' + - 'categories?')) { - return; - } else if (this.model.hasElements() && !confirm('Do you want to use the ' + - 'default workspace configuration options for injecting a workspace ' + - 'with categories?')) { - return; - } - this.view.setCategoryOptions(this.model.hasElements()); - this.generateNewOptions(); -}; - /** * Generates a new options object for injecting a Blockly workspace based * on user input. Should be called every time a change has been made to * an input field. Updates the model and reinjects the preview workspace. */ WorkspaceFactoryController.prototype.generateNewOptions = function() { - var optionsObj = new Object(null); + this.model.setOptions(this.readOptions_()); + + this.reinjectPreview(Blockly.Options.parseToolboxTree + (this.generator.generateToolboxXml())); +}; + +/** + * Generates a new options object for injecting a Blockly workspace based on + * user input. + * @private + * + * @return {!Object} Blockly injection options object. + */ +WorkspaceFactoryController.prototype.readOptions_ = function() { + var optionsObj = Object.create(null); // Add all standard options to the options object. // Use parse int to get numbers from value inputs. @@ -1086,8 +1108,14 @@ WorkspaceFactoryController.prototype.generateNewOptions = function() { optionsObj['css'] = document.getElementById('option_css_checkbox').checked; optionsObj['disable'] = document.getElementById('option_disable_checkbox').checked; - optionsObj['maxBlocks'] = - parseInt(document.getElementById('option_maxBlocks_text').value); + if (document.getElementById('option_infiniteBlocks_checkbox').checked) { + optionsObj['maxBlocks'] = Infinity; + } else { + var maxBlocksValue = + document.getElementById('option_maxBlocks_number').value; + optionsObj['maxBlocks'] = typeof maxBlocksValue == 'string' ? + parseInt(maxBlocksValue) : maxBlocksValue; + } optionsObj['media'] = document.getElementById('option_media_text').value; optionsObj['readOnly'] = document.getElementById('option_readOnly_checkbox').checked; @@ -1096,16 +1124,21 @@ WorkspaceFactoryController.prototype.generateNewOptions = function() { document.getElementById('option_scrollbars_checkbox').checked; optionsObj['sounds'] = document.getElementById('option_sounds_checkbox').checked; - optionsObj['trashcan'] = - document.getElementById('option_trashcan_checkbox').checked; + if (!optionsObj['readOnly']) { + optionsObj['trashcan'] = + document.getElementById('option_trashcan_checkbox').checked; + } // If using a grid, add all grid options. if (document.getElementById('option_grid_checkbox').checked) { - var grid = new Object(null); - grid['spacing'] = - parseInt(document.getElementById('gridOption_spacing_text').value); - grid['length'] = - parseInt(document.getElementById('gridOption_length_text').value); + var grid = Object.create(null); + var spacingValue = + document.getElementById('gridOption_spacing_number').value; + grid['spacing'] = typeof spacingValue == 'string' ? + parseInt(spacingValue) : spacingValue; + var lengthValue = document.getElementById('gridOption_length_number').value; + grid['length'] = typeof lengthValue == 'string' ? + parseInt(lengthValue) : lengthValue; grid['colour'] = document.getElementById('gridOption_colour_text').value; grid['snap'] = document.getElementById('gridOption_snap_checkbox').checked; optionsObj['grid'] = grid; @@ -1113,26 +1146,31 @@ WorkspaceFactoryController.prototype.generateNewOptions = function() { // If using zoom, add all zoom options. if (document.getElementById('option_zoom_checkbox').checked) { - var zoom = new Object(null); + var zoom = Object.create(null); zoom['controls'] = document.getElementById('zoomOption_controls_checkbox').checked; zoom['wheel'] = document.getElementById('zoomOption_wheel_checkbox').checked; - zoom['startScale'] = - parseInt(document.getElementById('zoomOption_startScale_text').value); - zoom['maxScale'] = - parseInt(document.getElementById('zoomOption_maxScale_text').value); - zoom['minScale'] = - parseInt(document.getElementById('zoomOption_minScale_text').value); - zoom['scaleSpeed'] = - parseInt(document.getElementById('zoomOption_scaleSpeed_text').value); + var startScaleValue = + document.getElementById('zoomOption_startScale_number').value; + zoom['startScale'] = typeof startScaleValue == 'string' ? + parseFloat(startScaleValue) : startScaleValue; + var maxScaleValue = + document.getElementById('zoomOption_maxScale_number').value; + zoom['maxcale'] = typeof maxScaleValue == 'string' ? + parseFloat(maxScaleValue) : maxScaleValue; + var minScaleValue = + document.getElementById('zoomOption_minScale_number').value; + zoom['minScale'] = typeof minScaleValue == 'string' ? + parseFloat(minScaleValue) : minScaleValue; + var scaleSpeedValue = + document.getElementById('zoomOption_scaleSpeed_number').value; + zoom['startScale'] = typeof startScaleValue == 'string' ? + parseFloat(scaleSpeedValue) : scaleSpeedValue; optionsObj['zoom'] = zoom; } - this.model.setOptions(optionsObj); - - this.reinjectPreview(Blockly.Options.parseToolboxTree - (this.generator.generateToolboxXml())); + return optionsObj; }; /** @@ -1156,7 +1194,8 @@ WorkspaceFactoryController.prototype.importBlocks = reader.onload = function() { try { // Define blocks using block types from file. - var blockTypes = FactoryUtils.defineAndGetBlockTypes(reader.result, format); + var blockTypes = FactoryUtils.defineAndGetBlockTypes(reader.result, + format); var blocks = controller.generator.getDefinedBlocks(blockTypes); // Generate category XML and append to toolbox. diff --git a/demos/blocklyfactory/workspacefactory/wfactory_generator.js b/demos/blocklyfactory/workspacefactory/wfactory_generator.js index 37725b0c2..8c2fa91d7 100644 --- a/demos/blocklyfactory/workspacefactory/wfactory_generator.js +++ b/demos/blocklyfactory/workspacefactory/wfactory_generator.js @@ -118,6 +118,8 @@ WorkspaceFactoryGenerator.prototype.generateToolboxXml = function() { * it includes XY and ID attributes). Uses a workspace and converts user * generated shadow blocks to actual shadow blocks. * + * @return {!Element} XML element representing toolbox or flyout corresponding + * to toolbox workspace. */ WorkspaceFactoryGenerator.prototype.generateWorkspaceXml = function() { // Load workspace XML to hidden workspace with user-generated shadow blocks @@ -133,6 +135,38 @@ WorkspaceFactoryGenerator.prototype.generateWorkspaceXml = function() { return generatedXml; }; +/** + * Generates a string representation of the options object for injecting the + * workspace. + * + * @return {!string} String representation of options object. + */ +WorkspaceFactoryGenerator.prototype.generateOptionsString = function() { + + var addAttributes = function(obj, tabChar) { + if (!obj) { + return '{}\n'; + } + var str = ''; + for (var key in obj) { + if (key == 'grid' || key == 'zoom') { + var temp = tabChar + key + ' : {\n' + addAttributes(obj[key], + tabChar + '\t') + tabChar + '}, \n'; + } else if (typeof obj[key] == 'string') { + var temp = tabChar + key + ' : \'' + obj[key] + '\', \n'; + } else { + var temp = tabChar + key + ' : ' + obj[key] + ', \n'; + } + str = str.concat(temp); + } + var lastCommaIndex = str.lastIndexOf(','); + str = str.slice(0, lastCommaIndex) + '\n'; + return str; + }; + + return 'var options = { \n' + addAttributes(this.model.options, '\t') + '};'; +} + /** * Loads the given XML to the hidden workspace and sets any user-generated * shadow blocks to be actual shadow blocks. diff --git a/demos/blocklyfactory/workspacefactory/wfactory_init.js b/demos/blocklyfactory/workspacefactory/wfactory_init.js index db10c2163..212c3e04f 100644 --- a/demos/blocklyfactory/workspacefactory/wfactory_init.js +++ b/demos/blocklyfactory/workspacefactory/wfactory_init.js @@ -351,6 +351,11 @@ document.getElementById('button_importBlocks').addEventListener ('click', function() { controller.setStandardOptionsAndUpdate(); }); + + document.getElementById('button_optionsHelp').addEventListener + ('click', function() { + open('https://developers.google.com/blockly/guides/get-started/web'); + }); }; /** @@ -578,6 +583,20 @@ WorkspaceFactoryInit.addWorkspaceFactoryOptionsListeners_ = 'block' : 'none'; }); + document.getElementById('option_readOnly_checkbox').addEventListener('change', + function(e) { + document.getElementById('trashcan_option').style.display = + document.getElementById('option_readOnly_checkbox').checked ? + 'none' : 'block'; + }); + + document.getElementById('option_infiniteBlocks_checkbox').addEventListener('change', + function(e) { + document.getElementById('maxBlockNumber_option').style.display = + document.getElementById('option_infiniteBlocks_checkbox').checked ? + 'none' : 'block'; + }); + // Generate new options every time an options input is updated. var optionsElements = document.getElementsByClassName('optionsInput'); for (var i = 0; i < optionsElements.length; i++) { diff --git a/demos/blocklyfactory/workspacefactory/wfactory_model.js b/demos/blocklyfactory/workspacefactory/wfactory_model.js index 8e552d838..ec059354c 100644 --- a/demos/blocklyfactory/workspacefactory/wfactory_model.js +++ b/demos/blocklyfactory/workspacefactory/wfactory_model.js @@ -420,16 +420,6 @@ WorkspaceFactoryModel.prototype.setOptions = function(options) { this.options = options; }; -/** - * Sets an attribute of the options object. - * - * @param {!string} name Name of the attribute to add. - * @param {Object} value The value of the attribute to add. - */ -WorkspaceFactoryModel.prototype.setOptionsAttribute = function(name, value) { - this.options[name] = value; -}; - /* * Returns an array of all the block types currently being used in the toolbox * and the pre-loaded blocks. No duplicates. diff --git a/demos/blocklyfactory/workspacefactory/wfactory_view.js b/demos/blocklyfactory/workspacefactory/wfactory_view.js index ae8d79dc5..6bfb8f9e8 100644 --- a/demos/blocklyfactory/workspacefactory/wfactory_view.js +++ b/demos/blocklyfactory/workspacefactory/wfactory_view.js @@ -407,7 +407,8 @@ WorkspaceFactoryView.prototype.updateHelpText = function(mode) { WorkspaceFactoryView.prototype.setBaseOptions = function() { // Set basic options. document.getElementById('option_css_checkbox').checked = true; - document.getElementById('option_maxBlocks_text').value = Infinity; + document.getElementById('option_infiniteBlocks_checkbox').checked = true; + document.getElementById('option_maxBlocks_number').value = 100; document.getElementById('option_media_text').value = 'https://blockly-demo.appspot.com/static/media/'; document.getElementById('option_readOnly_checkbox').checked = false; @@ -421,18 +422,18 @@ WorkspaceFactoryView.prototype.setBaseOptions = function() { document.getElementById('zoom_options').style.display = 'none'; // Set grid options. - document.getElementById('gridOption_spacing_text').value = 0; - document.getElementById('gridOption_length_text').value = 1; + document.getElementById('gridOption_spacing_number').value = 0; + document.getElementById('gridOption_length_number').value = 1; document.getElementById('gridOption_colour_text').value = '#888'; document.getElementById('gridOption_snap_checkbox').checked = false; // Set zoom options. - document.getElementById('zoomOption_controls_checkbox').checked = false; - document.getElementById('zoomOption_wheel_checkbox').checked = false; - document.getElementById('zoomOption_startScale_text').value = 1.0; - document.getElementById('zoomOption_maxScale_text').value = 3; - document.getElementById('zoomOption_minScale_text').value = 0.3; - document.getElementById('zoomOption_scaleSpeed_text').value = 1.2; + document.getElementById('zoomOption_controls_checkbox').checked = true; + document.getElementById('zoomOption_wheel_checkbox').checked = true; + document.getElementById('zoomOption_startScale_number').value = 1.0; + document.getElementById('zoomOption_maxScale_number').value = 3; + document.getElementById('zoomOption_minScale_number').value = 0.3; + document.getElementById('zoomOption_scaleSpeed_number').value = 1.2; }; /**