mirror of
https://github.com/google/blockly.git
synced 2026-01-11 19:07:08 +01:00
@@ -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 =
|
||||
!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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -398,16 +383,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");
|
||||
});
|
||||
@@ -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,40 @@ 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 =
|
||||
self.blockLibraryController.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')
|
||||
@@ -636,6 +620,22 @@ 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)) ||
|
||||
this.workspaceFactoryController.hasUnsavedChanges()) {
|
||||
// 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.
|
||||
*/
|
||||
@@ -651,7 +651,7 @@ AppController.prototype.init = function() {
|
||||
this.assignBlockFactoryClickHandlers();
|
||||
|
||||
this.onresize();
|
||||
self = this;
|
||||
var self = this;
|
||||
window.addEventListener('resize', function() {
|
||||
self.onresize();
|
||||
});
|
||||
|
||||
@@ -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,8 @@ 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);
|
||||
// Add select handler to the new option.
|
||||
this.addOptionSelectHandler(blockType);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -179,19 +180,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 +246,92 @@ 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);
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
BlockLibraryController.prototype.warnIfUnsavedChanges = function() {
|
||||
if (!FactoryUtils.savedBlockChanges(this)) {
|
||||
return confirm('You have unsaved changes. By proceeding without saving ' +
|
||||
' your block first, you will lose these changes.');
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.addOptionSelectHandler = function(blockType) {
|
||||
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 = self.warnIfUnsavedChanges();
|
||||
if (!proceedWithUnsavedChanges) {
|
||||
return;
|
||||
}
|
||||
setSelectedAndOpen_(blockOption);
|
||||
};
|
||||
};
|
||||
|
||||
// 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) {
|
||||
this.addOptionSelectHandler(blockType);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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);
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -302,6 +302,28 @@ button, .buttonStyle {
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
/* Block Library */
|
||||
|
||||
#dropdownDiv_blockLib {
|
||||
max-height: 65%;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
#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 +553,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 +571,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 */
|
||||
|
||||
@@ -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');
|
||||
};
|
||||
|
||||
@@ -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,29 @@ FactoryUtils.isProcedureBlock = function(block) {
|
||||
block.type == 'procedures_callreturn' ||
|
||||
block.type == 'procedures_ifreturn');
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns whether or not a modified 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) {
|
||||
if (BlockFactory.isStarterBlock()) {
|
||||
return true;
|
||||
}
|
||||
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;
|
||||
};
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
window.addEventListener('load', init);
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<body onbeforeunload="return blocklyFactory.confirmLeavePage()">
|
||||
<h1><a href="https://developers.google.com/blockly/">Blockly</a> >
|
||||
<a href="../index.html">Demos</a> > Blockly Factory
|
||||
<button id="helpButton" title="View documentation in new window.">
|
||||
@@ -50,26 +50,27 @@
|
||||
</h1>
|
||||
<div id="tabContainer">
|
||||
<div id="blockFactory_tab" class="tab tabon"> Block Factory</div>
|
||||
<div id="workspaceFactory_tab" class="tab taboff"> Workspace Factory</div>
|
||||
<div id="blocklibraryExporter_tab" class="tab taboff"> Block Exporter</div>
|
||||
<div id="workspaceFactory_tab" class="tab taboff"> Workspace Factory</div>
|
||||
</div>
|
||||
|
||||
<!-- Exporter tab -->
|
||||
<div id="blockLibraryExporter">
|
||||
<br>
|
||||
<p id="helperText"> 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.
|
||||
<p id="helperText"> First, select blocks from your block library by clicking on them. Then, use the Export Settings form to download starter code for selected blocks.
|
||||
</p>
|
||||
<div id="exportSelector">
|
||||
<br>
|
||||
<h3> Block Selector </h3>
|
||||
<div class='dropdown'>
|
||||
<button id="button_setBlocks">Select From Library</button>
|
||||
<button id="button_setBlocks">Select</button>
|
||||
<div id="dropdownDiv_setBlocks" class="dropdown-content">
|
||||
<a id='dropdown_addAllFromLib' title="Select all block library blocks.">All Stored</a>
|
||||
<a id='dropdown_addAllUsed' title="Select all block library blocks used in workspace factory.">All Used</a>
|
||||
<a id='dropdown_clearSelected' title="Clear selected blocks.">Clear</a>
|
||||
<a id='dropdown_addAllFromLib' title="Select all block library blocks.">All Stored in Block Library</a>
|
||||
<a id='dropdown_addAllUsed' title="Select all block library blocks used in workspace factory.">All Used in Workspace Factory</a>
|
||||
</div>
|
||||
<button id='clearSelectedButton' title="Clear selected blocks.">Clear Selected</a>
|
||||
</div>
|
||||
|
||||
<div id="blockSelector"></div>
|
||||
</div>
|
||||
|
||||
@@ -157,9 +158,9 @@
|
||||
<div class="dropdown">
|
||||
<button id="button_export">Export</button>
|
||||
<div id="dropdownDiv_export" class="dropdown-content">
|
||||
<a id='dropdown_exportOptions'>Starter Code</a>
|
||||
<a id='dropdown_exportToolbox'>Toolbox</a>
|
||||
<a id='dropdown_exportPreload'>Workspace Blocks</a>
|
||||
<a id='dropdown_exportOptions'>Inject Options</a>
|
||||
<a id='dropdown_exportAll'>All</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -193,6 +194,7 @@
|
||||
<a id='dropdown_newCategory'>New Category</a>
|
||||
<a id='dropdown_loadCategory'>Standard Category</a>
|
||||
<a id='dropdown_separator'>Separator</a>
|
||||
<a id='dropdown_loadStandardToolbox'>Standard Toolbox</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -219,7 +221,7 @@
|
||||
<div id="preloadHelp">
|
||||
<p>Configure the options for your Blockly inject call.</p>
|
||||
<button id="button_optionsHelp">Help</button>
|
||||
<button class="small" id="button_standardOptions">Restore</button>
|
||||
<button class="small" id="button_standardOptions">Reset to Default</button>
|
||||
</div>
|
||||
<div id="workspace_options">
|
||||
<input type="checkbox" id="option_readOnly_checkbox" class="optionsInput">Read Only<br>
|
||||
@@ -279,20 +281,22 @@
|
||||
<tr id="blockLibrary">
|
||||
<td id="blockLibraryContainer">
|
||||
<span>
|
||||
<h3>Block Library:</h3>
|
||||
<select id="blockLibraryDropdown">
|
||||
<div class='dropdown'>
|
||||
<button id="button_blockLib">Block Library</button>
|
||||
<div id="dropdownDiv_blockLib" class="dropdown-content">
|
||||
<a id='createNewBlockButton'>Create New Block</a>
|
||||
</div>
|
||||
</div>
|
||||
<select id="blockLibraryDropdown" style="display:none">
|
||||
</select>
|
||||
</span>
|
||||
</td>
|
||||
<td id="blockLibraryControls">
|
||||
<button id="saveToBlockLibraryButton" title="Save block to Block Library.">
|
||||
<span>Save Block</span>
|
||||
Save "block_type"
|
||||
</button>
|
||||
<button id="removeBlockFromLibraryButton" title="Remove block from Block Library.">
|
||||
<span>Delete Block</span>
|
||||
</button>
|
||||
<button id="createNewBlockButton" title="Create a new block.">
|
||||
<span> Create Block</span>
|
||||
Delete "block_type"
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -84,6 +84,10 @@ WorkspaceFactoryController = function(toolboxName, toolboxDiv, previewDiv) {
|
||||
this.selectedMode = WorkspaceFactoryController.MODE_TOOLBOX;
|
||||
// True if key events are enabled, false otherwise.
|
||||
this.keyEventsEnabled = true;
|
||||
// True if there are unsaved changes in the toolbox, false otherwise.
|
||||
this.hasUnsavedToolboxChanges = false;
|
||||
// True if there are unsaved changes in the preloaded blocks, false otherwise.
|
||||
this.hasUnsavedPreloadChanges = false;
|
||||
};
|
||||
|
||||
// Toolbox editing mode. Changes the user makes to the workspace updates the
|
||||
@@ -98,37 +102,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 +163,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();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -359,29 +322,32 @@ WorkspaceFactoryController.prototype.clearAndLoadElement = function(id) {
|
||||
* configuration)
|
||||
*/
|
||||
WorkspaceFactoryController.prototype.exportXmlFile = function(exportMode) {
|
||||
// Generate XML.
|
||||
if (exportMode == WorkspaceFactoryController.MODE_TOOLBOX) {
|
||||
// Export the toolbox XML.
|
||||
|
||||
var configXml = Blockly.Xml.domToPrettyText
|
||||
(this.generator.generateToolboxXml());
|
||||
} else if (exportMode == WorkspaceFactoryController.MODE_PRELOAD) {
|
||||
// Export the pre-loaded block XML.
|
||||
|
||||
var configXml = Blockly.Xml.domToPrettyText
|
||||
(this.generator.generateWorkspaceXml());
|
||||
} else {
|
||||
// Unknown mode. Throw error.
|
||||
throw new Error ("Unknown export mode: " + exportMode);
|
||||
}
|
||||
|
||||
// Get file name.
|
||||
// Get file name.
|
||||
var fileName = prompt('File Name for ' + (exportMode ==
|
||||
WorkspaceFactoryController.MODE_TOOLBOX ? 'toolbox XML: ' :
|
||||
'pre-loaded workspace XML: '));
|
||||
if (!fileName) { // If cancelled
|
||||
return;
|
||||
}
|
||||
|
||||
// Generate XML.
|
||||
if (exportMode == WorkspaceFactoryController.MODE_TOOLBOX) {
|
||||
// Export the toolbox XML.
|
||||
|
||||
var configXml = Blockly.Xml.domToPrettyText
|
||||
(this.generator.generateToolboxXml());
|
||||
this.hasUnsavedToolboxChanges = false;
|
||||
} else if (exportMode == WorkspaceFactoryController.MODE_PRELOAD) {
|
||||
// Export the pre-loaded block XML.
|
||||
|
||||
var configXml = Blockly.Xml.domToPrettyText
|
||||
(this.generator.generateWorkspaceXml());
|
||||
this.hasUnsavedPreloadChanges = false;
|
||||
} else {
|
||||
// Unknown mode. Throw error.
|
||||
throw new Error ("Unknown export mode: " + exportMode);
|
||||
}
|
||||
|
||||
// Download file.
|
||||
var data = new Blob([configXml], {type: 'text/xml'});
|
||||
this.view.createAndDownloadFile(fileName, data);
|
||||
@@ -391,15 +357,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);
|
||||
};
|
||||
@@ -474,9 +440,22 @@ WorkspaceFactoryController.prototype.updatePreview = function() {
|
||||
WorkspaceFactoryController.prototype.saveStateFromWorkspace = function() {
|
||||
if (this.selectedMode == WorkspaceFactoryController.MODE_TOOLBOX) {
|
||||
// If currently editing the toolbox.
|
||||
// Update flags if toolbox has been changed.
|
||||
if (this.model.getSelectedXml() !=
|
||||
Blockly.Xml.workspaceToDom(this.toolboxWorkspace)) {
|
||||
this.hasUnsavedToolboxChanges = true;
|
||||
}
|
||||
|
||||
this.model.getSelected().saveFromWorkspace(this.toolboxWorkspace);
|
||||
|
||||
} else if (this.selectedMode == WorkspaceFactoryController.MODE_PRELOAD) {
|
||||
// If currently editing the pre-loaded workspace.
|
||||
// Update flags if preloaded blocks have been changed.
|
||||
if (this.model.getPreloadXml() !=
|
||||
Blockly.Xml.workspaceToDom(this.toolboxWorkspace)) {
|
||||
this.hasUnsavedPreloadChanges = true;
|
||||
}
|
||||
|
||||
this.model.savePreloadXml
|
||||
(Blockly.Xml.workspaceToDom(this.toolboxWorkspace));
|
||||
}
|
||||
@@ -632,8 +611,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 +645,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 +684,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 +730,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 +899,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();
|
||||
@@ -892,6 +912,8 @@ WorkspaceFactoryController.prototype.clearAll = function() {
|
||||
this.toolboxWorkspace.clear();
|
||||
this.toolboxWorkspace.clearUndo();
|
||||
this.saveStateFromWorkspace();
|
||||
this.hasUnsavedToolboxChanges = false;
|
||||
this.hasUnsavedPreloadChanges = false;
|
||||
this.view.setCategoryOptions(this.model.hasElements());
|
||||
this.generateNewOptions();
|
||||
this.updatePreview();
|
||||
@@ -1185,7 +1207,7 @@ WorkspaceFactoryController.prototype.readOptions_ = function() {
|
||||
WorkspaceFactoryController.prototype.importBlocks =
|
||||
function(file, format) {
|
||||
// Generate category name from file name.
|
||||
var categoryName = file.name + ' blocks';
|
||||
var categoryName = file.name;
|
||||
|
||||
var controller = this;
|
||||
var reader = new FileReader();
|
||||
@@ -1196,8 +1218,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 +1238,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);
|
||||
@@ -1238,6 +1270,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.
|
||||
@@ -1300,3 +1333,12 @@ WorkspaceFactoryController.prototype.hasVariablesCategory = function() {
|
||||
WorkspaceFactoryController.prototype.hasProceduresCategory = function() {
|
||||
return this.model.hasProcedures();
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines if there are any unsaved changes in workspace factory.
|
||||
*
|
||||
* @return {boolean} True if there are unsaved changes, false otherwise.
|
||||
*/
|
||||
WorkspaceFactoryController.prototype.hasUnsavedChanges = function() {
|
||||
return this.hasUnsavedToolboxChanges || this.hasUnsavedPreloadChanges;
|
||||
};
|
||||
|
||||
@@ -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,32 @@ 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, \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 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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
|
||||
@@ -521,13 +528,16 @@ WorkspaceFactoryInit.addWorkspaceFactoryEventListeners_ = function(controller) {
|
||||
if (confirm('Your new block has a variables field. To use this block '
|
||||
+ 'fully, you will need a Variables category. Do you want to add '
|
||||
+ 'a Variables category to your custom toolbox?')) {
|
||||
controller.setMode(WorkspaceFactoryController.MODE_TOOLBOX);
|
||||
controller.loadCategoryByName('variables');
|
||||
}
|
||||
}
|
||||
|
||||
} else if (procedureCreated && !controller.hasProceduresCategory()) {
|
||||
if (procedureCreated && !controller.hasProceduresCategory()) {
|
||||
if (confirm('Your new block is a function block. To use this block '
|
||||
+ 'fully, you will need a Functions category. Do you want to add '
|
||||
+ 'a Functions category to your custom toolbox?')) {
|
||||
controller.setMode(WorkspaceFactoryController.MODE_TOOLBOX);
|
||||
controller.loadCategoryByName('functions');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ WorkspaceFactoryModel.prototype.hasVariables = function() {
|
||||
* false otherwise.
|
||||
*/
|
||||
WorkspaceFactoryModel.prototype.hasProcedures = function() {
|
||||
return this.hasFunctionCategory;
|
||||
return this.hasProcedureCategory;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -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<!string>} 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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user