From 7bbd125bd0eb56fd53824af187e5dc8036f3d499 Mon Sep 17 00:00:00 2001 From: Tina Quach Date: Mon, 29 Aug 2016 10:22:27 -0700 Subject: [PATCH 01/10] changed tab ordering and export selector buttons (#605) --- demos/blocklyfactory/app_controller.js | 8 ++++---- demos/blocklyfactory/index.html | 11 ++++++----- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/demos/blocklyfactory/app_controller.js b/demos/blocklyfactory/app_controller.js index 2a6301cbf..31f1a8f39 100644 --- a/demos/blocklyfactory/app_controller.js +++ b/demos/blocklyfactory/app_controller.js @@ -398,16 +398,16 @@ AppController.prototype.assignExporterClickHandlers = function() { document.getElementById('dropdownDiv_setBlocks').classList.remove("show"); }); - document.getElementById('dropdown_clearSelected').addEventListener('click', + document.getElementById('dropdown_addAllFromLib').addEventListener('click', function() { - self.exporter.clearSelectedBlocks(); + self.exporter.selectAllBlocks(); self.exporter.updatePreview(); document.getElementById('dropdownDiv_setBlocks').classList.remove("show"); }); - document.getElementById('dropdown_addAllFromLib').addEventListener('click', + document.getElementById('clearSelectedButton').addEventListener('click', function() { - self.exporter.selectAllBlocks(); + self.exporter.clearSelectedBlocks(); self.exporter.updatePreview(); document.getElementById('dropdownDiv_setBlocks').classList.remove("show"); }); diff --git a/demos/blocklyfactory/index.html b/demos/blocklyfactory/index.html index b002b4061..2a0139e2b 100644 --- a/demos/blocklyfactory/index.html +++ b/demos/blocklyfactory/index.html @@ -50,8 +50,8 @@
Block Factory
-
Workspace Factory
Block Exporter
+
Workspace Factory
@@ -63,13 +63,14 @@

Block Selector

+
From 6e493be3c47f491740ed483ddf6c9858e6d7449f Mon Sep 17 00:00:00 2001 From: Emma Dauterman Date: Mon, 29 Aug 2016 15:31:03 -0700 Subject: [PATCH 02/10] Blockly Factory: Confirm Changes with User, Reduce Alerts, Generate Starter Code (#606) * Automatically generates flyout on switching to category * Fixed import blocks bugs * Made last set of UI changes and changes to starter code exporting * Combined if statements * Only add toolbox if not read only * Removed extra curly braces --- demos/blocklyfactory/index.html | 5 +- .../workspacefactory/wfactory_controller.js | 152 ++++++++++-------- .../workspacefactory/wfactory_generator.js | 26 ++- .../workspacefactory/wfactory_init.js | 11 +- .../workspacefactory/wfactory_model.js | 16 ++ 5 files changed, 129 insertions(+), 81 deletions(-) diff --git a/demos/blocklyfactory/index.html b/demos/blocklyfactory/index.html index 2a0139e2b..0f5af9e71 100644 --- a/demos/blocklyfactory/index.html +++ b/demos/blocklyfactory/index.html @@ -158,9 +158,9 @@ @@ -194,6 +194,7 @@ New Category Standard Category Separator + Standard Toolbox @@ -220,7 +221,7 @@

Configure the options for your Blockly inject call.

- +
Read Only
diff --git a/demos/blocklyfactory/workspacefactory/wfactory_controller.js b/demos/blocklyfactory/workspacefactory/wfactory_controller.js index af2f9b695..5f9d45e70 100644 --- a/demos/blocklyfactory/workspacefactory/wfactory_controller.js +++ b/demos/blocklyfactory/workspacefactory/wfactory_controller.js @@ -98,37 +98,8 @@ WorkspaceFactoryController.MODE_PRELOAD = 'preload'; * before), and then creates a tab and switches to it. */ 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(); - } - } + // Transfers the user's blocks to a flyout if it's the first category created. + this.transferFlyoutBlocksToCategory(); // After possibly creating a category, check again if it's the first category. var isFirstCategory = !this.model.hasElements(); @@ -188,40 +159,28 @@ WorkspaceFactoryController.prototype.addClickToSwitch = function(tab, id) { }; /** - * Allows the user to transfer blocks in their flyout to a new category if + * Transfers the blocks in the user's flyout to a new category if * the user is creating their first category and their workspace is not * empty. Should be called whenever it is possible to switch from single flyout * to categories (not including importing). */ -WorkspaceFactoryController.prototype.allowToTransferFlyoutBlocksToCategory = +WorkspaceFactoryController.prototype.transferFlyoutBlocksToCategory = function() { - // Give the option to save blocks if their workspace is not empty and they - // are creating their first category. + // Saves the user's blocks from the flyout in a category if there is no + // toolbox and the user has dragged in blocks. if (!this.model.hasElements() && 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); - // Allow user to use the default options for injecting with categories. - this.allowToSetDefaultOptions(); - // Update preview here in case exit early. - this.updatePreview(); - } + // Create the new category. + this.createCategory('Category 1', true); + // Set the new category as selected. + var id = this.model.getCategoryIdByName('Category 1'); + this.model.setSelectedById(id); + this.view.setCategoryTabSelection(id, true); + // Allow user to use the default options for injecting with categories. + this.view.setCategoryOptions(this.model.hasElements()); + this.generateNewOptions(); + // Update preview here in case exit early. + this.updatePreview(); } }; @@ -391,15 +350,15 @@ WorkspaceFactoryController.prototype.exportXmlFile = function(exportMode) { * Export the options object to be used for the Blockly inject call. Gets a * file name from the user and downloads the options object to that file. */ -WorkspaceFactoryController.prototype.exportOptionsFile = function() { - var fileName = prompt('File Name for options object for injecting: '); +WorkspaceFactoryController.prototype.exportInjectFile = function() { + var fileName = prompt('File Name for starter Blockly workspace code: '); if (!fileName) { // If cancelled. return; } // Generate new options to remove toolbox XML from options object (if // necessary). this.generateNewOptions(); - var printableOptions = this.generator.generateOptionsString() + var printableOptions = this.generator.generateInjectString() var data = new Blob([printableOptions], {type: 'text/javascript'}); this.view.createAndDownloadFile(fileName, data); }; @@ -632,8 +591,9 @@ WorkspaceFactoryController.prototype.loadCategoryByName = function(name) { + '. Rename your category and try again.'); return; } - // Allow user to transfer current flyout blocks to a category. - this.allowToTransferFlyoutBlocksToCategory(); + // Transfers current flyout blocks to a category if it's the first category + // created. + this.transferFlyoutBlocksToCategory(); var isFirstCategory = !this.model.hasElements(); // Copy the standard category in the model. @@ -665,6 +625,22 @@ WorkspaceFactoryController.prototype.loadCategoryByName = function(name) { this.updatePreview(); }; +/** + * Loads the standard Blockly toolbox into the editing space. Should only + * be called when the mode is set to toolbox. + */ +WorkspaceFactoryController.prototype.loadStandardToolbox = function() { + this.loadCategoryByName('Logic'); + this.loadCategoryByName('Loops'); + this.loadCategoryByName('Math'); + this.loadCategoryByName('Text'); + this.loadCategoryByName('Lists'); + this.loadCategoryByName('Colour'); + this.addSeparator(); + this.loadCategoryByName('Variables'); + this.loadCategoryByName('Functions'); +} + /** * Given the name of a category, determines if it's the name of a standard * category (case insensitive). @@ -688,9 +664,9 @@ WorkspaceFactoryController.prototype.isStandardCategoryName = function(name) { * the separator, and updates the preview. */ WorkspaceFactoryController.prototype.addSeparator = function() { - // If adding the first element in the toolbox, allow the user to transfer - // their flyout blocks to a category. - this.allowToTransferFlyoutBlocksToCategory(); + // If adding the first element in the toolbox, transfers the user's blocks + // in a flyout to a category. + this.transferFlyoutBlocksToCategory(); // Create the separator in the model. var separator = new ListElement(ListElement.TYPE_SEPARATOR); this.model.addElementToList(separator); @@ -734,12 +710,32 @@ WorkspaceFactoryController.prototype.importFile = function(file, importMode) { try { var tree = Blockly.Xml.textToDom(reader.result); if (importMode == WorkspaceFactoryController.MODE_TOOLBOX) { - // Switch mode and import toolbox XML. + // Switch mode. controller.setMode(WorkspaceFactoryController.MODE_TOOLBOX); + + // Confirm that the user wants to override their current toolbox. + var hasToolboxElements = controller.model.hasElements() || + controller.getAllBlocks().length > 0; + if (hasToolboxElements && + !confirm('Are you sure you want to import? You will lose your ' + + 'current toolbox. ')) { + return; + } + // Import toolbox XML. controller.importToolboxFromTree_(tree); + } else if (importMode == WorkspaceFactoryController.MODE_PRELOAD) { - // Switch mode and import pre-loaded workspace XML. + // Switch mode. controller.setMode(WorkspaceFactoryController.MODE_PRELOAD); + + // Confirm that the user wants to override their current blocks. + if (controller.toolboxWorkspace.getAllBlocks().length > 0 && + !confirm('Are you sure you want to import? You will lose your ' + + 'current workspace blocks. ')) { + return; + } + + // Import pre-loaded workspace XML. controller.importPreloadFromTree_(tree); } else { // Throw error if invalid mode. @@ -883,6 +879,10 @@ WorkspaceFactoryController.prototype.importPreloadFromTree_ = function(tree) { * "Clear" button. */ WorkspaceFactoryController.prototype.clearAll = function() { + if (!confirm('Are you sure you want to clear all of your work in Workspace' + + ' Factory?')) { + return; + } var hasCategories = this.model.hasElements(); this.model.clearToolboxList(); this.view.clearToolboxTabs(); @@ -1196,8 +1196,17 @@ WorkspaceFactoryController.prototype.importBlocks = // Define blocks using block types from file. var blockTypes = FactoryUtils.defineAndGetBlockTypes(reader.result, format); - var blocks = controller.generator.getDefinedBlocks(blockTypes); + // If an imported block type is already defined, check if the user wants + // to override the current block definition. + if (controller.model.hasDefinedBlockTypes(blockTypes) && + !confirm('An imported block uses the same name as a block ' + + 'already in your toolbox. Are you sure you want to override the ' + + 'currently defined block?')) { + return; + } + + var blocks = controller.generator.getDefinedBlocks(blockTypes); // Generate category XML and append to toolbox. var categoryXml = FactoryUtils.generateCategoryXml(blocks, categoryName); // Get random color for category between 0 and 360. Gives each imported @@ -1207,9 +1216,10 @@ WorkspaceFactoryController.prototype.importBlocks = controller.toolbox.appendChild(categoryXml); controller.toolboxWorkspace.updateToolbox(controller.toolbox); // Update imported block types. - this.model.addImportedBlocks(blockTypes); + controller.model.addImportedBlockTypes(blockTypes); // Reload current category to possibly reflect any newly defined blocks. - this.clearAndLoadXml_(Blockly.Xml.workspaceToDom(this.toolboxWorkspace)); + controller.clearAndLoadXml_ + (Blockly.Xml.workspaceToDom(controller.toolboxWorkspace)); } catch (e) { alert('Cannot read blocks from file.'); window.console.log(e); diff --git a/demos/blocklyfactory/workspacefactory/wfactory_generator.js b/demos/blocklyfactory/workspacefactory/wfactory_generator.js index 8c2fa91d7..01d578f78 100644 --- a/demos/blocklyfactory/workspacefactory/wfactory_generator.js +++ b/demos/blocklyfactory/workspacefactory/wfactory_generator.js @@ -130,18 +130,18 @@ WorkspaceFactoryGenerator.prototype.generateWorkspaceXml = function() { // Generate XML and set attributes. var generatedXml = Blockly.Xml.workspaceToDom(this.hiddenWorkspace); - generatedXml.setAttribute('id', 'preload_blocks'); + generatedXml.setAttribute('id', 'workspaceBlocks'); generatedXml.setAttribute('style', 'display:none'); return generatedXml; }; /** * Generates a string representation of the options object for injecting the - * workspace. + * workspace and starter code. * - * @return {!string} String representation of options object. + * @return {!string} String representation of starter code for injecting. */ -WorkspaceFactoryGenerator.prototype.generateOptionsString = function() { +WorkspaceFactoryGenerator.prototype.generateInjectString = function() { var addAttributes = function(obj, tabChar) { if (!obj) { @@ -157,14 +157,28 @@ WorkspaceFactoryGenerator.prototype.generateOptionsString = function() { } else { var temp = tabChar + key + ' : ' + obj[key] + ', \n'; } - str = str.concat(temp); + str += temp; } var lastCommaIndex = str.lastIndexOf(','); str = str.slice(0, lastCommaIndex) + '\n'; return str; }; - return 'var options = { \n' + addAttributes(this.model.options, '\t') + '};'; + var attributes = addAttributes(this.model.options, '\t'); + if (!this.model.options['readOnly']) { + attributes = '\ttoolbox : toolbox, /* TODO: Change toolbox XML ID if ' + + 'necessary. Can export toolbox XML from Workspace Factory. */\n' + + attributes; + } + var finalStr = 'var options = { \n' + attributes + '};'; + finalStr += '\n\n/* Inject your workspace */ \nvar workspace = Blockly.' + + 'inject(/* TODO: Add ID of div to inject Blockly into */, options);'; + finalStr += '\n\n/* Load Workspace Blocks from XML to workspace. ' + + 'Remove if no blocks to load */' + + '\nBlockly.Xml.domToWorkspace(workspace, workspaceBlocks/*' + + ' TODO: Change workspace blocks XML ID if necessary. Can export' + + ' workspace blocks XML from Workspace Factory. */);'; + return finalStr; } /** diff --git a/demos/blocklyfactory/workspacefactory/wfactory_init.js b/demos/blocklyfactory/workspacefactory/wfactory_init.js index 212c3e04f..3a37eae38 100644 --- a/demos/blocklyfactory/workspacefactory/wfactory_init.js +++ b/demos/blocklyfactory/workspacefactory/wfactory_init.js @@ -193,6 +193,13 @@ WorkspaceFactoryInit.assignWorkspaceFactoryClickHandlers_ = document.getElementById('dropdownDiv_add').classList.remove("show"); }); + document.getElementById('dropdown_loadStandardToolbox').addEventListener + ('click', + function() { + controller.loadStandardToolbox(); + document.getElementById('dropdownDiv_add').classList.remove("show"); + }); + document.getElementById('button_remove').addEventListener ('click', function() { @@ -216,16 +223,16 @@ WorkspaceFactoryInit.assignWorkspaceFactoryClickHandlers_ = document.getElementById('dropdown_exportOptions').addEventListener ('click', function() { - controller.exportOptionsFile(); + controller.exportInjectFile(); document.getElementById('dropdownDiv_export').classList.remove("show"); }); document.getElementById('dropdown_exportAll').addEventListener ('click', function() { + controller.exportInjectFile(); controller.exportXmlFile(WorkspaceFactoryController.MODE_TOOLBOX); controller.exportXmlFile(WorkspaceFactoryController.MODE_PRELOAD); - controller.exportOptionsFile(); document.getElementById('dropdownDiv_export').classList.remove("show"); }); diff --git a/demos/blocklyfactory/workspacefactory/wfactory_model.js b/demos/blocklyfactory/workspacefactory/wfactory_model.js index ec059354c..a03fb86ea 100644 --- a/demos/blocklyfactory/workspacefactory/wfactory_model.js +++ b/demos/blocklyfactory/workspacefactory/wfactory_model.js @@ -495,6 +495,22 @@ WorkspaceFactoryModel.prototype.isDefinedBlockType = function(blockType) { var isLibBlock = this.libBlockTypes.indexOf(blockType) != -1; var isImportedBlock = this.importedBlockTypes.indexOf(blockType) != -1; return (isStandardBlock || isLibBlock || isImportedBlock); +}; + +/** + * Checks if any of the block types are already defined. + * + * @param {!Array} blockTypes Array of block types. + * @return {boolean} True if a block type in the array is already defined, + * false if none of the blocks are already defined. + */ +WorkspaceFactoryModel.prototype.hasDefinedBlockTypes = function(blockTypes) { + for (var i = 0, blockType; blockType = blockTypes[i]; i++) { + if (this.isDefinedBlockType(blockType)) { + return true; + } + } + return false; } /** From a1ead192a61571186e1e2519e0581a82e2f0681d Mon Sep 17 00:00:00 2001 From: Tina Quach Date: Tue, 30 Aug 2016 09:19:05 -0700 Subject: [PATCH 03/10] Blockly Factory: Enhanced Block Library UI (#603) * block library now dynamically updates buttons based on the current block open in block factory--whether or not it is saved, whether or not changes have been made, whether or not it's the starter block. update buttons rather than switching to new block on delete remove warning about updating block alert when creating new block with unsaved changes working warnings on tab switches and create new block green buttons indicate user can save or update block, red save button indicates that user cannot save the block ( but encourages user to click button) consistent button name on default added alert upon switching between blocks without saving nit comments, condensing if statements, removing extra code throw errors in sameBlockXml function of factory utils created wrapper function for warning user about unsaved changes and checking if they'd like to proceed cleaned up code for setSelected block * nit change to warning message on block type being one of core block types * nit helper text for exporter * nit comments and spacing --- demos/blocklyfactory/app_controller.js | 99 ++++---- .../block_library_controller.js | 136 +++++++---- demos/blocklyfactory/block_library_view.js | 213 +++++++++++++----- demos/blocklyfactory/factory.css | 28 ++- demos/blocklyfactory/factory.js | 13 +- demos/blocklyfactory/factory_utils.js | 74 ++++-- demos/blocklyfactory/index.html | 18 +- 7 files changed, 406 insertions(+), 175 deletions(-) diff --git a/demos/blocklyfactory/app_controller.js b/demos/blocklyfactory/app_controller.js index 31f1a8f39..b2be5b05a 100644 --- a/demos/blocklyfactory/app_controller.js +++ b/demos/blocklyfactory/app_controller.js @@ -230,23 +230,6 @@ AppController.prototype.getBlockTypeFromXml_ = function(xmlText) { } }; -/** - * Updates the Block Factory tab to show selected block when user selects a - * different block in the block library dropdown. Tied to block library dropdown - * in index.html. - * - * @param {!Element} blockLibraryDropdown - HTML select element from which the - * user selects a block to work on. - */ -AppController.prototype.onSelectedBlockChanged - = function(blockLibraryDropdown) { - // Get selected block type. - var blockType = this.blockLibraryController.getSelectedBlockType( - blockLibraryDropdown); - // Update Block Factory page by showing the selected block. - this.blockLibraryController.openBlock(blockType); -}; - /** * Add click handlers to each tab to allow switching between the Block Factory, * Workspace Factory, and Block Exporter tab. @@ -303,14 +286,16 @@ AppController.prototype.onTab = function() { // Warn user if they have unsaved changes when leaving Block Factory. if (this.lastSelectedTab == AppController.BLOCK_FACTORY && this.selectedTab != AppController.BLOCK_FACTORY) { - if (!BlockFactory.isStarterBlock() && !this.savedBlockChanges()) { - if (!confirm('You have unsaved changes in Block Factory.')) { - // If the user doesn't want to switch tabs with unsaved changes, - // stay on Block Factory Tab. - this.setSelected_(AppController.BLOCK_FACTORY); - this.lastSelectedTab == AppController.BLOCK_FACTORY; - return; - } + + var hasUnsavedChanges = !BlockFactory.isStarterBlock() && + !FactoryUtils.savedBlockChanges(this.blockLibraryController); + if (hasUnsavedChanges && + !confirm('You have unsaved changes in Block Factory.')) { + // If the user doesn't want to switch tabs with unsaved changes, + // stay on Block Factory Tab. + this.setSelected_(AppController.BLOCK_FACTORY); + this.lastSelectedTab = AppController.BLOCK_FACTORY; + return; } } @@ -479,50 +464,35 @@ AppController.prototype.ifCheckedDisplay = function(checkbox, elementArray) { } }; -/** - * Returns whether or not a block's changes has been saved to the Block Library. - * - * @return {boolean} True if all changes made to the block have been saved to - * the Block Library. - */ -AppController.prototype.savedBlockChanges = function() { - var blockType = this.blockLibraryController.getCurrentBlockType(); - var currentXml = Blockly.Xml.workspaceToDom(BlockFactory.mainWorkspace); - - if (this.blockLibraryController.has(blockType)) { - // Block is saved in block library. - var savedXml = this.blockLibraryController.getBlockXml(blockType); - return FactoryUtils.sameBlockXml(savedXml, currentXml); - } - return false; -}; - /** * Assign button click handlers for the block library. */ AppController.prototype.assignLibraryClickHandlers = function() { var self = this; - // Assign button click handlers for Block Library. + + // Button for saving block to library. document.getElementById('saveToBlockLibraryButton').addEventListener('click', function() { self.blockLibraryController.saveToBlockLibrary(); }); + // Button for removing selected block from library. document.getElementById('removeBlockFromLibraryButton').addEventListener( 'click', function() { self.blockLibraryController.removeFromBlockLibrary(); }); + // Button for clearing the block library. document.getElementById('clearBlockLibraryButton').addEventListener('click', function() { self.blockLibraryController.clearBlockLibrary(); }); - var dropdown = document.getElementById('blockLibraryDropdown'); - dropdown.addEventListener('change', + // Hide and show the block library dropdown. + document.getElementById('button_blockLib').addEventListener('click', function() { - self.onSelectedBlockChanged(dropdown); + document.getElementById('dropdownDiv_blockLib').classList.toggle("show"); }); }; @@ -559,26 +529,39 @@ AppController.prototype.assignBlockFactoryClickHandlers = function() { document.getElementById('createNewBlockButton') .addEventListener('click', function() { - // If there are unsaved changes to the block in open in Block Factory, - // warn user that proceeding to create a new block will cause them to lose - // their changes if they don't save. - if (!self.savedBlockChanges()) { - if(!confirm('You have unsaved changes. By proceeding without saving ' + - ' your block first, you will lose these changes.')) { - return; - } + // If there are unsaved changes warn user, check if they'd like to + // proceed with unsaved changes, and act accordingly. + var proceedWithUnsavedChanges = FactoryUtils.warnIfUnsavedChanges(); + if (!proceedWithUnsavedChanges) { + return; } - BlockFactory.showStarterBlock(); - BlockLibraryView.selectDefaultOption('blockLibraryDropdown'); - }); + + BlockFactory.showStarterBlock(); + self.blockLibraryController.setNoneSelected(); + + // Close the Block Library Dropdown. + goog.dom.getElement('dropdownDiv_blockLib').classList.remove("show"); + }); }; /** * Add event listeners for the block factory. */ AppController.prototype.addBlockFactoryEventListeners = function() { + // Update code on changes to block being edited. BlockFactory.mainWorkspace.addChangeListener(BlockFactory.updateLanguage); + + // Disable blocks not attached to the factory_base block. BlockFactory.mainWorkspace.addChangeListener(Blockly.Events.disableOrphans); + + // Update the buttons on the screen based on whether + // changes have been saved. + var self = this; + BlockFactory.mainWorkspace.addChangeListener(function() { + self.blockLibraryController.updateButtons(FactoryUtils.savedBlockChanges( + self.blockLibraryController)); + }); + document.getElementById('direction') .addEventListener('change', BlockFactory.updatePreview); document.getElementById('languageTA') diff --git a/demos/blocklyfactory/block_library_controller.js b/demos/blocklyfactory/block_library_controller.js index 201a9e705..7f7a19ed1 100644 --- a/demos/blocklyfactory/block_library_controller.js +++ b/demos/blocklyfactory/block_library_controller.js @@ -51,6 +51,9 @@ BlockLibraryController = function(blockLibraryName, opt_blockLibraryStorage) { this.name = blockLibraryName; // Create a new, empty Block Library Storage object, or load existing one. this.storage = opt_blockLibraryStorage || new BlockLibraryStorage(this.name); + // The BlockLibraryView object handles the proper updating and formatting of + // the block library dropdown. + this.view = new BlockLibraryView(); }; /** @@ -67,7 +70,8 @@ BlockLibraryController.prototype.getCurrentBlockType = function() { }; /** - * Removes current block from Block Library + * Removes current block from Block Library and updates the save and delete + * buttons so that user may save block to library and but not delete. * * @param {string} blockType - Type of block. */ @@ -76,8 +80,7 @@ BlockLibraryController.prototype.removeFromBlockLibrary = function() { this.storage.removeBlock(blockType); this.storage.saveToLocalStorage(); this.populateBlockLibrary(); - // Show default block. - BlockFactory.showStarterBlock(); + this.view.updateButtons(blockType, false, false); }; /** @@ -86,25 +89,24 @@ BlockLibraryController.prototype.removeFromBlockLibrary = function() { * @param {string} blockType - Block to edit on block factory. */ BlockLibraryController.prototype.openBlock = function(blockType) { - if (blockType =='BLOCK_LIBRARY_DEFAULT_BLANK') { - BlockFactory.showStarterBlock(); - } else { + if (blockType) { var xml = this.storage.getBlockXml(blockType); BlockFactory.mainWorkspace.clear(); Blockly.Xml.domToWorkspace(xml, BlockFactory.mainWorkspace); BlockFactory.mainWorkspace.clearUndo(); + } else { + BlockFactory.showStarterBlock(); + this.view.setSelectedBlockType(null); } }; /** * Returns type of block selected from library. * - * @param {Element} blockLibraryDropdown - The block library dropdown. * @return {string} Type of block selected. */ -BlockLibraryController.prototype.getSelectedBlockType = - function(blockLibraryDropdown) { - return BlockLibraryView.getSelected(blockLibraryDropdown); +BlockLibraryController.prototype.getSelectedBlockType = function() { + return this.view.getSelectedBlockType(); }; /** @@ -118,12 +120,12 @@ BlockLibraryController.prototype.clearBlockLibrary = function() { this.storage.clear(); this.storage.saveToLocalStorage(); // Update dropdown. - BlockLibraryView.clearOptions('blockLibraryDropdown'); - // Add a default, blank option to dropdown for when no block from library is - // selected. - BlockLibraryView.addDefaultOption('blockLibraryDropdown'); + this.view.clearOptions(); // Show default block. BlockFactory.showStarterBlock(); + // User may not save the starter block, but will get explicit instructions + // upon clicking the red save button. + this.view.updateButtons(null); } }; @@ -132,15 +134,13 @@ BlockLibraryController.prototype.clearBlockLibrary = function() { */ BlockLibraryController.prototype.saveToBlockLibrary = function() { var blockType = this.getCurrentBlockType(); - // If block under that name already exists, confirm that user wants to replace - // saved block. - if (this.has(blockType)) { - var replace = confirm('You already have a block called "' + blockType + - '" in your library. Replace this block?'); - if (!replace) { - // Do not save if user doesn't want to replace the saved block. - return; - } + // If user has not changed the name of the starter block. + if (blockType == 'block_type') { + // Do not save block if it has the default type, 'block_type'. + alert('You cannot save a block under the name "block_type". Try changing ' + + 'the name before saving. Then, click on the "Block Library" button ' + + 'to view your saved blocks.'); + return; } // Create block xml. @@ -148,6 +148,11 @@ BlockLibraryController.prototype.saveToBlockLibrary = function() { var block = FactoryUtils.getRootBlock(BlockFactory.mainWorkspace); xmlElement.appendChild(Blockly.Xml.blockToDomWithXY(block)); + // Do not add option again if block type is already in library. + if (!this.has(blockType)) { + this.view.addOption(blockType, true, true); + } + // Save block. this.storage.addBlock(blockType, xmlElement); this.storage.saveToLocalStorage(); @@ -156,12 +161,7 @@ BlockLibraryController.prototype.saveToBlockLibrary = function() { // main workspace. this.openBlock(blockType); - // Do not add another option to dropdown if replacing. - if (replace) { - return; - } - BlockLibraryView.addOption( - blockType, blockType, 'blockLibraryDropdown', true, true); + this.addOptionSelectHandlers(); }; /** @@ -179,19 +179,13 @@ BlockLibraryController.prototype.has = function(blockType) { * Populates the dropdown menu. */ BlockLibraryController.prototype.populateBlockLibrary = function() { - BlockLibraryView.clearOptions('blockLibraryDropdown'); - // Add a default, blank option to dropdown for when no block from library is - // selected. - BlockLibraryView.addDefaultOption('blockLibraryDropdown'); - // Add option for each saved block. + this.view.clearOptions(); + // Add an unselected option for each saved block. var blockLibrary = this.storage.blocks; - for (var block in blockLibrary) { - // Make sure the block wasn't deleted. - if (blockLibrary[block] != null) { - BlockLibraryView.addOption( - block, block, 'blockLibraryDropdown', false, true); - } + for (var blockType in blockLibrary) { + this.view.addOption(blockType, false); } + this.addOptionSelectHandlers(); }; /** @@ -251,3 +245,65 @@ BlockLibraryController.prototype.hasEmptyBlockLibrary = function() { BlockLibraryController.prototype.getStoredBlockTypes = function() { return this.storage.getBlockTypes(); }; + +/** + * Sets the currently selected block option to none. + */ +BlockLibraryController.prototype.setNoneSelected = function() { + this.view.setSelectedBlockType(null); +}; + +/** + * Add select handlers to each option to update the view and the selected + * blocks accordingly. + */ +BlockLibraryController.prototype.addOptionSelectHandlers = function() { + var self = this; + + // Click handler for a block option. Sets the block option as the selected + // option and opens the block for edit in Block Factory. + var setSelectedAndOpen_ = function(blockOption) { + var blockType = blockOption.textContent; + self.view.setSelectedBlockType(blockType); + self.openBlock(blockType); + // The block is saved in the block library and all changes have been saved + // when the user opens a block from the block library dropdown. + // Thus, the buttons show up as a disabled update button and an enabled + // delete. + self.view.updateButtons(blockType, true, true); + self.view.hide(); + }; + + // Returns a block option select handler. + var makeOptionSelectHandler_ = function(blockOption) { + return function() { + // If there are unsaved changes warn user, check if they'd like to + // proceed with unsaved changes, and act accordingly. + var proceedWithUnsavedChanges = FactoryUtils.warnIfUnsavedChanges(); + if (!proceedWithUnsavedChanges) { + return; + } + setSelectedAndOpen_(blockOption); + }; + }; + + // Assign a click handler to each block option. + for (var blockType in this.view.optionMap) { + var blockOption = this.view.optionMap[blockType]; + // Use an additional closure to correctly assign the tab callback. + blockOption.addEventListener( + 'click', makeOptionSelectHandler_(blockOption)); + } +}; + +/** + * Update the save and delete buttons based on the current block type of the + * block the user is currently editing. + * + * @param {boolean} Whether changes to the block have been saved. + */ +BlockLibraryController.prototype.updateButtons = function(savedChanges) { + var blockType = this.getCurrentBlockType(); + var isInLibrary = this.has(blockType); + this.view.updateButtons(blockType, isInLibrary, savedChanges); +}; diff --git a/demos/blocklyfactory/block_library_view.js b/demos/blocklyfactory/block_library_view.js index cc5800646..00c676dfa 100644 --- a/demos/blocklyfactory/block_library_view.js +++ b/demos/blocklyfactory/block_library_view.js @@ -19,8 +19,8 @@ */ /** - * @fileoverview Javascript for Block Library's UI for pulling blocks from the - * Block Library's storage to edit in Block Factory. + * @fileoverview Javascript for BlockLibraryView class. It manages the display + * of the Block Library dropdown, save, and delete buttons. * * @author quachtina96 (Tina Quach) */ @@ -29,89 +29,198 @@ goog.provide('BlockLibraryView'); +goog.require('goog.dom'); +goog.require('goog.dom.classlist'); + +/** + * BlockLibraryView Class + * @constructor + */ +var BlockLibraryView = function() { + // Div element to contain the block types to choose from. + // Id of the div that holds the block library view. + this.blockLibraryViewDivID = 'dropdownDiv_blockLib'; + this.dropdown = goog.dom.getElement('dropdownDiv_blockLib'); + // Map of block type to corresponding 'a' element that is the option in the + // dropdown. Used to quickly and easily get a specific option. + this.optionMap = Object.create(null); + // Save and delete buttons. + this.saveButton = goog.dom.getElement('saveToBlockLibraryButton'); + this.deleteButton = goog.dom.getElement('removeBlockFromLibraryButton'); + // Initially, user should not be able to delete a block. They must save a + // block or select a stored block first. + this.deleteButton.disabled = true; +}; + +/** + * Open the Block Library dropdown. + */ +BlockLibraryView.prototype.show = function() { + this.dropdown.classList.add("show"); +}; + +/** + * Close the Block Library dropdown. + */ +BlockLibraryView.prototype.hide = function() { + this.dropdown.classList.remove("show"); +}; + /** * Creates a node of a given element type and appends to the node with given id. * - * @param {string} optionIdentifier - String used to identify option. - * @param {string} optionText - Text to display in the dropdown for the option. - * @param {string} dropdownID - ID for HTML select element. + * @param {!string} blockType - Type of block. * @param {boolean} selected - Whether or not the option should be selected on * the dropdown. - * @param {boolean} enabled - Whether or not the option should be enabled. */ -BlockLibraryView.addOption - = function(optionIdentifier, optionText, dropdownID, selected, enabled) { - var dropdown = document.getElementById(dropdownID); - var option = document.createElement('option'); - // The value attribute of a dropdown's option is not visible in the UI, but is - // useful for identifying different options that may have the same text. - option.value = optionIdentifier; - // The text attribute is what the user sees in the dropdown for the option. - option.text = optionText; - option.selected = selected; - option.disabled = !enabled; - dropdown.add(option); +BlockLibraryView.prototype.addOption = function(blockType, selected) { + // Create option. + var option = goog.dom.createDom('a', { + 'id': 'dropdown_' + blockType, + 'class': 'blockLibOpt' + }, blockType); + + // Add option to dropdown. + this.dropdown.appendChild(option); + this.optionMap[blockType] = option; + + // Select the block. + if (selected) { + this.setSelectedBlockType(blockType); + } }; /** - * Adds a default, blank option to dropdown for when no block from library is - * selected. + * Sets a given block type to selected and all other blocks to deselected. + * If null, deselects all blocks. * - * @param {string} dropdownID - ID of HTML select element + * @param {string} blockTypeToSelect - Type of block to select or null. */ -BlockLibraryView.addDefaultOption = function(dropdownID) { - BlockLibraryView.addOption( - 'BLOCK_LIBRARY_DEFAULT_BLANK', '', dropdownID, true, true); +BlockLibraryView.prototype.setSelectedBlockType = function(blockTypeToSelect) { + // Select given block type and deselect all others. Will deselect all blocks + // if null or invalid block type selected. + for (var blockType in this.optionMap) { + var option = this.optionMap[blockType]; + if (blockType == blockTypeToSelect) { + this.selectOption_(option); + } else { + this.deselectOption_(option); + } + } }; /** - * Selects the default, blank option in dropdown identified by given ID. + * Selects a given option. + * @private * - * @param {string} dropdownID - ID of HTML select element + * @param {!Element} option - HTML 'a' element in the dropdown that represents + * a particular block type. */ -BlockLibraryView.selectDefaultOption = function(dropdownID) { - var dropdown = document.getElementById(dropdownID); - // Deselect currently selected option. - var index = dropdown.selectedIndex; - dropdown.options[index].selected = false; - // Select default option, always the first in the dropdown. - var defaultOption = dropdown.options[0]; - defaultOption.selected = true; +BlockLibraryView.prototype.selectOption_ = function(option) { + goog.dom.classlist.add(option, 'dropdown-content-selected'); +}; + +/** + * Deselects a given option. + * @private + * + * @param {!Element} option - HTML 'a' element in the dropdown that represents + * a particular block type. + */ +BlockLibraryView.prototype.deselectOption_ = function(option) { + goog.dom.classlist.remove(option, 'dropdown-content-selected'); +}; + +/** + * Updates the save and delete buttons to represent how the current block will + * be saved by including the block type in the button text as well as indicating + * whether the block is being saved or updated. + * + * @param {!string} blockType - The type of block being edited. + * @param {boolean} isInLibrary - Whether the block type is in the library. + * @param {boolean} savedChanges - Whether changes to block have been saved. + */ +BlockLibraryView.prototype.updateButtons = + function(blockType, isInLibrary, savedChanges) { + if (blockType) { + // User is editing a block. + + if (!isInLibrary) { + // Block type has not been saved to library yet. Disable the delete button + // and allow user to save. + this.saveButton.textContent = 'Save "' + blockType + '"'; + this.saveButton.disabled = false; + this.deleteButton.disabled = true; + } else { + // Block type has already been saved. Disable the save button unless the + // there are unsaved changes (checked below). + this.saveButton.textContent = 'Update "' + blockType + '"'; + this.saveButton.disabled = true; + this.deleteButton.disabled = false; + } + this.deleteButton.textContent = 'Delete "' + blockType + '"'; + + // If changes to block have been made and are not saved, make button + // green to encourage user to save the block. + if (!savedChanges) { + var buttonFormatClass = 'button_warn'; + + // If block type is the default, 'block_type', make button red to alert + // user. + if (blockType == 'block_type') { + buttonFormatClass = 'button_alert'; + } + goog.dom.classlist.add(this.saveButton, buttonFormatClass); + this.saveButton.disabled = false; + + } else { + // No changes to save. + var classesToRemove = ['button_alert', 'button_warn']; + goog.dom.classlist.removeAll(this.saveButton, classesToRemove); + this.saveButton.disabled = true; + } + + } +}; + +/** + * Removes option currently selected in dropdown from dropdown menu. + */ +BlockLibraryView.prototype.removeSelectedOption = function() { + var selectedOption = this.getSelectedOption(); + this.dropdown.removeNode(selectedOption); }; /** * Returns block type of selected block. * - * @param {Element} dropdown - HTML select element. * @return {string} Type of block selected. */ -BlockLibraryView.getSelected = function(dropdown) { - var index = dropdown.selectedIndex; - return dropdown.options[index].value; +BlockLibraryView.prototype.getSelectedBlockType = function() { + var selectedOption = this.getSelectedOption(); + var blockType = selectedOption.textContent; + return blockType; }; /** - * Removes option currently selected in dropdown from dropdown menu. + * Returns selected option. * - * @param {string} dropdownID - ID of HTML select element within which to find - * the selected option. + * @return {!Element} HTML 'a' element that is the option for a block type. */ -BlockLibraryView.removeSelectedOption = function(dropdownID) { - var dropdown = document.getElementById(dropdownID); - if (dropdown) { - dropdown.remove(dropdown.selectedIndex); - } +BlockLibraryView.prototype.getSelectedOption = function() { + return goog.dom.getElementByClass('dropdown-content-selected', this.dropdown); }; /** * Removes all options from dropdown. - * - * @param {string} dropdownID - ID of HTML select element to clear options of. */ -BlockLibraryView.clearOptions = function(dropdownID) { - var dropdown = document.getElementById(dropdownID); - while (dropdown.length > 0) { - dropdown.remove(dropdown.length - 1); +BlockLibraryView.prototype.clearOptions = function() { + var blockOpts = goog.dom.getElementsByClass('blockLibOpt', this.dropdown); + if (blockOpts) { + for (var i = 0, option; option = blockOpts[i]; i++) { + goog.dom.removeNode(option); + } } }; + diff --git a/demos/blocklyfactory/factory.css b/demos/blocklyfactory/factory.css index 704b8de4d..46a1d3d30 100644 --- a/demos/blocklyfactory/factory.css +++ b/demos/blocklyfactory/factory.css @@ -302,6 +302,23 @@ button, .buttonStyle { width: 90%; } +/* Block Library */ + +#button_blockLib { + border-color: darkgrey; + font-size: large; +} + +.button_alert { + background-color: #fcc; + border-color: #f99; +} + +.button_warn { + background-color: #aea; + border-color: #5d5; +} + /* Tabs */ .tab { @@ -531,7 +548,7 @@ td { /* Dropdown Content (Hidden by Default) */ .dropdown-content { - background-color: #f9f9f9; + background-color: #FFF; box-shadow: 0px 8px 16px 0px rgba(0,0,0,.2); display: none; min-width: 170px; @@ -549,9 +566,14 @@ td { text-decoration: none; } -/* Change color of dropdown links on hover. */ +/* Change color of dropdown links on hover. */ .dropdown-content a:hover, .dropdown-content label:hover { - background-color: #f1f1f1 + background-color: #EEE; +} + +/* Change color of dropdown links on selected. */ +.dropdown-content-selected { + background-color: #DDD; } /* Show the dropdown menu */ diff --git a/demos/blocklyfactory/factory.js b/demos/blocklyfactory/factory.js index 6ec2cfd00..837d04bc7 100644 --- a/demos/blocklyfactory/factory.js +++ b/demos/blocklyfactory/factory.js @@ -209,11 +209,19 @@ BlockFactory.updatePreview = function() { // standard library. var rootBlock = FactoryUtils.getRootBlock(BlockFactory.mainWorkspace); if (StandardCategories.coreBlockTypes.indexOf(blockType) != -1) { - rootBlock.setWarningText('A standard Blockly.Block already exists ' + + 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); } + } finally { Blockly.Blocks = backupBlocks; } @@ -248,7 +256,10 @@ 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/blocklyfactory/factory_utils.js index 89e421663..1a16488de 100644 --- a/demos/blocklyfactory/factory_utils.js +++ b/demos/blocklyfactory/factory_utils.js @@ -33,8 +33,6 @@ */ goog.provide('FactoryUtils'); -goog.require('goog.dom.classes'); - /** * Get block definition code for the current block. * @@ -889,7 +887,7 @@ FactoryUtils.injectCode = function(code, id) { /** * Returns whether or not two blocks are the same based on their xml. Expects - * xml with a single child node that is a factory_base block. The xml found on + * xml with a single child node that is a factory_base block, the xml found on * Block Factory's main workspace. * * @param {!Element} blockXml1 - An xml element with a single child node that @@ -899,18 +897,31 @@ FactoryUtils.injectCode = function(code, id) { * @return {boolean} Whether or not two blocks are the same based on their xml. */ FactoryUtils.sameBlockXml = function(blockXml1, blockXml2) { - // Each block xml has only one child. - var blockXmlText1 = Blockly.Xml.domToText( - blockXml1.getElementsByTagName('block')[0]); - var blockXmlText2 = Blockly.Xml.domToText( - blockXml2.getElementsByTagName('block')[0]); + // Each xml element should contain a single child element with a 'block' tag + if (goog.string.caseInsensitiveCompare(blockXml1.tagName, 'xml') || + goog.string.caseInsensitiveCompare(blockXml2.tagName, 'xml')) { + throw new Error('Expected two xml elements, recieved elements with tag ' + + 'names: ' + blockXml1.tagName + ' and ' + blockXml2.tagName + '.'); + } - // Strip white space. - blockXmlText1 = blockXmlText1.replace(/\s+/g, ''); - blockXmlText2 = blockXmlText2.replace(/\s+/g, ''); + // Compare the block elements directly. The xml tags may include other meta + // information we want to igrore. + var blockElement1 = blockXml1.getElementsByTagName('block')[0]; + var blockElement2 = blockXml2.getElementsByTagName('block')[0]; - // Return whether or not changes have been saved. - return blockXmlText1 == blockXmlText2; + if (!(blockElement1 && blockElement2)) { + throw new Error('Could not get find block element in xml.'); + } + + var blockXmlText1 = Blockly.Xml.domToText(blockElement1); + var blockXmlText2 = Blockly.Xml.domToText(blockElement2); + + // Strip white space. + blockXmlText1 = blockXmlText1.replace(/\s+/g, ''); + blockXmlText2 = blockXmlText2.replace(/\s+/g, ''); + + // Return whether or not changes have been saved. + return blockXmlText1 == blockXmlText2; }; /* @@ -950,3 +961,40 @@ FactoryUtils.isProcedureBlock = function(block) { block.type == 'procedures_callreturn' || block.type == 'procedures_ifreturn'); }; + +/** + * Returns whether or not a block's changes has been saved to the Block Library. + * TODO(quachtina96): move into the Block Factory Controller once made. + * + * @param {!BlockLibraryController} blockLibraryController - Block Library + * Controller storing custom blocks. + * @return {boolean} True if all changes made to the block have been saved to + * the given Block Library. + */ +FactoryUtils.savedBlockChanges = function(blockLibraryController) { + var blockType = blockLibraryController.getCurrentBlockType(); + var currentXml = Blockly.Xml.workspaceToDom(BlockFactory.mainWorkspace); + + if (blockLibraryController.has(blockType)) { + // Block is saved in block library. + var savedXml = blockLibraryController.getBlockXml(blockType); + return FactoryUtils.sameBlockXml(savedXml, currentXml); + } + return false; +}; + +/** + * If there are unsaved changes to the block in open in Block Factory + * and the block is not the starter block, check if user wants to proceed, + * knowing that it will cause them to lose their changes. + * + * @return {boolean} Whether or not to proceed. + */ +FactoryUtils.warnIfUnsavedChanges = function() { + if (!BlockFactory.isStarterBlock() && + !FactoryUtils.savedBlockChanges(self.blockLibraryController)) { + return confirm('You have unsaved changes. By proceeding without saving ' + + ' your block first, you will lose these changes.'); + } + return true; +}; diff --git a/demos/blocklyfactory/index.html b/demos/blocklyfactory/index.html index 0f5af9e71..63996cb02 100644 --- a/demos/blocklyfactory/index.html +++ b/demos/blocklyfactory/index.html @@ -57,7 +57,7 @@

-

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

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


@@ -281,20 +281,22 @@ -

Block Library:

-
- From 64d34b3cdcb3b0a5b976dad8b08ed009df8903b3 Mon Sep 17 00:00:00 2001 From: Emma Dauterman Date: Tue, 30 Aug 2016 10:59:17 -0700 Subject: [PATCH 04/10] Close toolbox workspace toolbox when update block library (#610) --- demos/blocklyfactory/workspacefactory/wfactory_controller.js | 1 + 1 file changed, 1 insertion(+) diff --git a/demos/blocklyfactory/workspacefactory/wfactory_controller.js b/demos/blocklyfactory/workspacefactory/wfactory_controller.js index 5f9d45e70..97ca7627c 100644 --- a/demos/blocklyfactory/workspacefactory/wfactory_controller.js +++ b/demos/blocklyfactory/workspacefactory/wfactory_controller.js @@ -1248,6 +1248,7 @@ WorkspaceFactoryController.prototype.setBlockLibCategory = // Update the toolbox and toolboxWorkspace. this.toolbox.replaceChild(categoryXml, blockLibCategory); + this.toolboxWorkspace.toolbox_.clearSelection(); this.toolboxWorkspace.updateToolbox(this.toolbox); // Update the block library types. From d6e71ffe38bfbe674d226851bccc020af3558559 Mon Sep 17 00:00:00 2001 From: Tina Quach Date: Tue, 30 Aug 2016 13:59:07 -0700 Subject: [PATCH 05/10] scrollable block lib, fix bug for repeated alert about unsaved changes (#612) --- .../block_library_controller.js | 29 ++++++++++++++----- demos/blocklyfactory/factory.css | 5 ++++ demos/blocklyfactory/factory_utils.js | 2 +- 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/demos/blocklyfactory/block_library_controller.js b/demos/blocklyfactory/block_library_controller.js index 7f7a19ed1..e7548488d 100644 --- a/demos/blocklyfactory/block_library_controller.js +++ b/demos/blocklyfactory/block_library_controller.js @@ -161,7 +161,8 @@ BlockLibraryController.prototype.saveToBlockLibrary = function() { // main workspace. this.openBlock(blockType); - this.addOptionSelectHandlers(); + // Add select handler to the new option. + this.addOptionSelectHandler(blockType); }; /** @@ -254,10 +255,13 @@ BlockLibraryController.prototype.setNoneSelected = function() { }; /** - * Add select handlers to each option to update the view and the selected - * blocks accordingly. + * Add select handler for an option of a given block type. The handler will to + * update the view and the selected block accordingly. + * + * @param {!string} blockType - The type of block represented by the option is + * for. */ -BlockLibraryController.prototype.addOptionSelectHandlers = function() { +BlockLibraryController.prototype.addOptionSelectHandler = function(blockType) { var self = this; // Click handler for a block option. Sets the block option as the selected @@ -287,12 +291,21 @@ BlockLibraryController.prototype.addOptionSelectHandlers = function() { }; }; + // Assign a click handler to the block option. + var blockOption = this.view.optionMap[blockType]; + // Use an additional closure to correctly assign the tab callback. + blockOption.addEventListener( + 'click', makeOptionSelectHandler_(blockOption)); +}; + +/** + * Add select handlers to each option to update the view and the selected + * blocks accordingly. + */ +BlockLibraryController.prototype.addOptionSelectHandlers = function() { // Assign a click handler to each block option. for (var blockType in this.view.optionMap) { - var blockOption = this.view.optionMap[blockType]; - // Use an additional closure to correctly assign the tab callback. - blockOption.addEventListener( - 'click', makeOptionSelectHandler_(blockOption)); + this.addOptionSelectHandler(blockType); } }; diff --git a/demos/blocklyfactory/factory.css b/demos/blocklyfactory/factory.css index 46a1d3d30..f212b1d49 100644 --- a/demos/blocklyfactory/factory.css +++ b/demos/blocklyfactory/factory.css @@ -304,6 +304,11 @@ button, .buttonStyle { /* Block Library */ +#dropdownDiv_blockLib { + max-height: 65%; + overflow-y: scroll; +} + #button_blockLib { border-color: darkgrey; font-size: large; diff --git a/demos/blocklyfactory/factory_utils.js b/demos/blocklyfactory/factory_utils.js index 1a16488de..416015362 100644 --- a/demos/blocklyfactory/factory_utils.js +++ b/demos/blocklyfactory/factory_utils.js @@ -994,7 +994,7 @@ FactoryUtils.warnIfUnsavedChanges = function() { if (!BlockFactory.isStarterBlock() && !FactoryUtils.savedBlockChanges(self.blockLibraryController)) { return confirm('You have unsaved changes. By proceeding without saving ' + - ' your block first, you will lose these changes.'); + ' your block first, you will lose these changes.'); } return true; }; From 8d89e62bc85d6d97e94e61c86267280a3045a11d Mon Sep 17 00:00:00 2001 From: Emma Dauterman Date: Wed, 31 Aug 2016 10:50:01 -0700 Subject: [PATCH 06/10] Fixed bug on updating flags for custom categories, adjusted starter code (#615) --- .../workspacefactory/wfactory_generator.js | 18 +++++++++++------- .../workspacefactory/wfactory_model.js | 2 +- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/demos/blocklyfactory/workspacefactory/wfactory_generator.js b/demos/blocklyfactory/workspacefactory/wfactory_generator.js index 01d578f78..c3183ab6d 100644 --- a/demos/blocklyfactory/workspacefactory/wfactory_generator.js +++ b/demos/blocklyfactory/workspacefactory/wfactory_generator.js @@ -166,18 +166,22 @@ WorkspaceFactoryGenerator.prototype.generateInjectString = function() { var attributes = addAttributes(this.model.options, '\t'); if (!this.model.options['readOnly']) { - attributes = '\ttoolbox : toolbox, /* TODO: Change toolbox XML ID if ' + - 'necessary. Can export toolbox XML from Workspace Factory. */\n' + + attributes = '\ttoolbox : toolbox, \n' + attributes; } - var finalStr = 'var options = { \n' + attributes + '};'; + var finalStr = '/* TODO: Change toolbox XML ID if necessary. Can export ' + + 'toolbox XML from Workspace Factory. */\n' + + 'var toolbox = document.getElementById("toolbox");\n\n'; + finalStr += 'var options = { \n' + attributes + '};'; finalStr += '\n\n/* Inject your workspace */ \nvar workspace = Blockly.' + 'inject(/* TODO: Add ID of div to inject Blockly into */, options);'; finalStr += '\n\n/* Load Workspace Blocks from XML to workspace. ' + - 'Remove if no blocks to load */' + - '\nBlockly.Xml.domToWorkspace(workspace, workspaceBlocks/*' + - ' TODO: Change workspace blocks XML ID if necessary. Can export' + - ' workspace blocks XML from Workspace Factory. */);'; + 'Remove all code below if no blocks to load */\n\n' + + '/* TODO: Change workspace blocks XML ID if necessary. Can export' + + ' workspace blocks XML from Workspace Factory. */\n' + + 'var workspaceBlocks = document.getElementById("workspaceBlocks"); \n\n' + + '/* Load blocks to workspace. */\n' + + 'Blockly.Xml.domToWorkspace(workspace, workspaceBlocks);'; return finalStr; } diff --git a/demos/blocklyfactory/workspacefactory/wfactory_model.js b/demos/blocklyfactory/workspacefactory/wfactory_model.js index a03fb86ea..eabaafbbc 100644 --- a/demos/blocklyfactory/workspacefactory/wfactory_model.js +++ b/demos/blocklyfactory/workspacefactory/wfactory_model.js @@ -94,7 +94,7 @@ WorkspaceFactoryModel.prototype.hasVariables = function() { * false otherwise. */ WorkspaceFactoryModel.prototype.hasProcedures = function() { - return this.hasFunctionCategory; + return this.hasProcedureCategory; }; /** From cbdf8a597efc9659e50c299baf085ddd8f6dc2d8 Mon Sep 17 00:00:00 2001 From: Tina Quach Date: Wed, 31 Aug 2016 13:39:47 -0700 Subject: [PATCH 07/10] Blockly Factory: Warn user when leaving/refreshing page (#607) * warn user upon refreshing page and leaving page (by going to prev page or by exiting the window or tab) * alert upon leaving page ONLY when unsaved changes * nit remove log * no alert when on starter block, added clarifying comment --- demos/blocklyfactory/app_controller.js | 15 +++++++++++++++ demos/blocklyfactory/index.html | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/demos/blocklyfactory/app_controller.js b/demos/blocklyfactory/app_controller.js index b2be5b05a..c8d5acd47 100644 --- a/demos/blocklyfactory/app_controller.js +++ b/demos/blocklyfactory/app_controller.js @@ -619,6 +619,21 @@ AppController.prototype.onresize = function(event) { } }; +/** + * Handler for the window's 'onbeforeunload' event. When a user has unsaved + * changes and refreshes or leaves the page, confirm that they want to do so + * before actually refreshing. + */ +AppController.prototype.confirmLeavePage = function() { + if (!BlockFactory.isStarterBlock() && + !FactoryUtils.savedBlockChanges(this.blockLibraryController)) { + // When a string is assigned to the returnValue Event property, a dialog box + // appears, asking the users for confirmation to leave the page. + return 'You will lose any unsaved changes. Are you sure you want ' + + 'to exit this page?'; + } +}; + /** * Initialize Blockly and layout. Called on page load. */ diff --git a/demos/blocklyfactory/index.html b/demos/blocklyfactory/index.html index 63996cb02..69d18a09b 100644 --- a/demos/blocklyfactory/index.html +++ b/demos/blocklyfactory/index.html @@ -41,7 +41,7 @@ window.addEventListener('load', init); - +

Blockly > Demos > Blockly Factory