From 6e88d5c0355fb1d4db19d0ea3b4ed07af36b4591 Mon Sep 17 00:00:00 2001 From: Tina Quach Date: Fri, 26 Aug 2016 11:24:56 -0700 Subject: [PATCH] alert when creating new block with unsaved changes (#594) working warnings on tab switches and create new block --- demos/blocklyfactory/app_controller.js | 54 +++++++++++++++++-- .../block_library_controller.js | 23 +++++--- demos/blocklyfactory/factory.js | 37 +++++++------ demos/blocklyfactory/factory_utils.js | 27 ++++++++++ 4 files changed, 112 insertions(+), 29 deletions(-) diff --git a/demos/blocklyfactory/app_controller.js b/demos/blocklyfactory/app_controller.js index 082987d03..2a6301cbf 100644 --- a/demos/blocklyfactory/app_controller.js +++ b/demos/blocklyfactory/app_controller.js @@ -64,6 +64,8 @@ AppController = function() { this.tabMap[AppController.EXPORTER] = goog.dom.getElement('blocklibraryExporter_tab'); + // Last selected tab. + this.lastSelectedTab = null; // Selected tab. this.selectedTab = AppController.BLOCK_FACTORY; }; @@ -268,6 +270,7 @@ AppController.prototype.addTabHandlers = function(tabMap) { * AppController.WORKSPACE_FACTORY, or AppController.EXPORTER */ AppController.prototype.setSelected_ = function(tabName) { + this.lastSelectedTab = this.selectedTab; this.selectedTab = tabName; }; @@ -297,14 +300,28 @@ AppController.prototype.onTab = function() { var exporterTab = this.tabMap[AppController.EXPORTER]; var workspaceFactoryTab = this.tabMap[AppController.WORKSPACE_FACTORY]; - // Turn selected tab on and other tabs off. - this.styleTabs_(); + // 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; + } + } + } // Only enable key events in workspace factory if workspace factory tab is // selected. this.workspaceFactoryController.keyEventsEnabled = this.selectedTab == AppController.WORKSPACE_FACTORY; + // Turn selected tab on and other tabs off. + this.styleTabs_(); + if (this.selectedTab == AppController.EXPORTER) { // Hide other tabs. FactoryUtils.hide('workspaceFactoryContent'); @@ -462,6 +479,24 @@ 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. */ @@ -524,9 +559,18 @@ AppController.prototype.assignBlockFactoryClickHandlers = function() { document.getElementById('createNewBlockButton') .addEventListener('click', function() { - BlockFactory.showStarterBlock(); - BlockLibraryView.selectDefaultOption('blockLibraryDropdown'); - }); + // 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; + } + } + BlockFactory.showStarterBlock(); + BlockLibraryView.selectDefaultOption('blockLibraryDropdown'); + }); }; /** diff --git a/demos/blocklyfactory/block_library_controller.js b/demos/blocklyfactory/block_library_controller.js index 69239f87b..201a9e705 100644 --- a/demos/blocklyfactory/block_library_controller.js +++ b/demos/blocklyfactory/block_library_controller.js @@ -59,7 +59,7 @@ BlockLibraryController = function(blockLibraryName, opt_blockLibraryStorage) { * * @return {string} The current block's type. */ -BlockLibraryController.prototype.getCurrentBlockType_ = function() { +BlockLibraryController.prototype.getCurrentBlockType = function() { var rootBlock = FactoryUtils.getRootBlock(BlockFactory.mainWorkspace); var blockType = rootBlock.getFieldValue('NAME').trim().toLowerCase(); // Replace white space with underscores @@ -72,7 +72,7 @@ BlockLibraryController.prototype.getCurrentBlockType_ = function() { * @param {string} blockType - Type of block. */ BlockLibraryController.prototype.removeFromBlockLibrary = function() { - var blockType = this.getCurrentBlockType_(); + var blockType = this.getCurrentBlockType(); this.storage.removeBlock(blockType); this.storage.saveToLocalStorage(); this.populateBlockLibrary(); @@ -131,14 +131,13 @@ BlockLibraryController.prototype.clearBlockLibrary = function() { * Saves current block to local storage and updates dropdown. */ BlockLibraryController.prototype.saveToBlockLibrary = function() { - var blockType = this.getCurrentBlockType_(); - + var blockType = this.getCurrentBlockType(); // If block under that name already exists, confirm that user wants to replace // saved block. - if (this.isInBlockLibrary(blockType)) { + if (this.has(blockType)) { var replace = confirm('You already have a block called "' + blockType + '" in your library. Replace this block?'); - if ( !replace) { + if (!replace) { // Do not save if user doesn't want to replace the saved block. return; } @@ -171,7 +170,7 @@ BlockLibraryController.prototype.saveToBlockLibrary = function() { * @param {string} blockType - Type of block. * @return {boolean} Boolean indicating whether or not block is in the library. */ -BlockLibraryController.prototype.isInBlockLibrary = function(blockType) { +BlockLibraryController.prototype.has = function(blockType) { var blockLibrary = this.storage.blocks; return (blockType in blockLibrary && blockLibrary[blockType] != null); }; @@ -204,6 +203,16 @@ BlockLibraryController.prototype.getBlockLibrary = function() { return this.storage.getBlockXmlTextMap(); }; +/** + * Return stored xml of a given block type. + * + * @param {!string} blockType - The type of block. + * @return {!Element} Xml element of a given block type or null. + */ +BlockLibraryController.prototype.getBlockXml = function(blockType) { + return this.storage.getBlockXml(blockType); +}; + /** * Set the block library storage object from which exporter exports. * diff --git a/demos/blocklyfactory/factory.js b/demos/blocklyfactory/factory.js index 343abf55b..6ec2cfd00 100644 --- a/demos/blocklyfactory/factory.js +++ b/demos/blocklyfactory/factory.js @@ -60,19 +60,12 @@ BlockFactory.UNNAMED = 'unnamed'; */ BlockFactory.oldDir = null; -/** - * 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.
+/*
+ * The starting xml for the Block Factory main workspace. Contains the
+ * unmovable, undeletable factory_base block.
  */
-FactoryUtils.injectCode = function(code, id) {
-  var pre = document.getElementById(id);
-  pre.textContent = code;
-  code = pre.innerHTML;
-  code = prettyPrintOne(code, 'js');
-  pre.innerHTML = code;
-};
+BlockFactory.STARTER_BLOCK_XML_TEXT = '';
 
 /**
  * Change the language code format.
@@ -243,9 +236,19 @@ BlockFactory.disableEnableLink = function() {
  * Render starter block (factory_base).
  */
 BlockFactory.showStarterBlock = function() {
-    BlockFactory.mainWorkspace.clear();
-    var xml = '';
-    Blockly.Xml.domToWorkspace(
-        Blockly.Xml.textToDom(xml), BlockFactory.mainWorkspace);
+  BlockFactory.mainWorkspace.clear();
+  var xml = Blockly.Xml.textToDom(BlockFactory.STARTER_BLOCK_XML_TEXT);
+  Blockly.Xml.domToWorkspace(xml, BlockFactory.mainWorkspace);
+};
+
+/**
+ * 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 ||
+    rootBlock.getFieldValue('NAME').trim().toLowerCase() != 'block_type' ||
+    rootBlock.getFieldValue('CONNECTIONS') != 'NONE' ||
+    rootBlock.getFieldValue('INLINE') != 'AUTO');
 };
diff --git a/demos/blocklyfactory/factory_utils.js b/demos/blocklyfactory/factory_utils.js
index 1dabc018a..f8ce4c861 100644
--- a/demos/blocklyfactory/factory_utils.js
+++ b/demos/blocklyfactory/factory_utils.js
@@ -886,3 +886,30 @@ FactoryUtils.injectCode = function(code, id) {
   code = prettyPrintOne(code, 'js');
   pre.innerHTML = code;
 };
+
+/**
+ * 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
+ * Block Factory's main workspace.
+ *
+ * @param {!Element} blockXml1 - An xml element with a single child node that
+ *    is a factory_base block.
+ * @param {!Element} blockXml2 - An xml element with a single child node that
+ *    is a factory_base block.
+ * @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]);
+
+  // Strip white space.
+  blockXmlText1 = blockXmlText1.replace(/\s+/g, '');
+  blockXmlText2 = blockXmlText2.replace(/\s+/g, '');
+
+  // Return whether or not changes have been saved.
+  return blockXmlText1 == blockXmlText2;
+};
+