alert when creating new block with unsaved changes (#594)

working warnings on tab switches and create new block
This commit is contained in:
Tina Quach
2016-08-26 11:24:56 -07:00
committed by picklesrus
parent 0cf5d12374
commit 6e88d5c035
4 changed files with 112 additions and 29 deletions

View File

@@ -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');
});
};
/**

View File

@@ -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.
*

View File

@@ -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 <pre> 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 = '<xml><block type="factory_base" ' +
'deletable="false" movable="false"></block></xml>';
/**
* 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 = '<xml><block type="factory_base" deletable="false" ' +
'movable="false"></block></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');
};

View File

@@ -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;
};