diff options
author | David Barksdale <amatus@amatus.name> | 2016-09-10 17:58:46 -0500 |
---|---|---|
committer | David Barksdale <amatus@amatus.name> | 2016-09-10 18:32:35 -0500 |
commit | 475f9f3ac7688e58505690d420cafe6ae8bb8b5f (patch) | |
tree | b1bf14b10751fe4e9e146ad7244ec86bb123893d /blockly/demos/blocklyfactory/workspacefactory/wfactory_model.js | |
parent | 923561056ddb63ce82fd1ec2a5e249bbdae267bf (diff) |
Merge blockly sub-tree
Diffstat (limited to 'blockly/demos/blocklyfactory/workspacefactory/wfactory_model.js')
-rw-r--r-- | blockly/demos/blocklyfactory/workspacefactory/wfactory_model.js | 599 |
1 files changed, 599 insertions, 0 deletions
diff --git a/blockly/demos/blocklyfactory/workspacefactory/wfactory_model.js b/blockly/demos/blocklyfactory/workspacefactory/wfactory_model.js new file mode 100644 index 0000000..eabaafb --- /dev/null +++ b/blockly/demos/blocklyfactory/workspacefactory/wfactory_model.js @@ -0,0 +1,599 @@ +/** + * @license + * Visual Blocks Editor + * + * Copyright 2016 Google Inc. + * https://developers.google.com/blockly/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview Stores and updates information about state and categories + * in workspace factory. Each list element is either a separator or a category, + * and each category stores its name, XML to load that category, color, + * custom tags, and a unique ID making it possible to change category names and + * move categories easily. Keeps track of the currently selected list + * element. Also keeps track of all the user-created shadow blocks and + * manipulates them as necessary. + * + * @author Emma Dauterman (evd2014) + */ + +/** + * Class for a WorkspaceFactoryModel + * @constructor + */ +WorkspaceFactoryModel = function() { + // Ordered list of ListElement objects. Empty if there is a single flyout. + this.toolboxList = []; + // ListElement for blocks in a single flyout. Null if a toolbox exists. + this.flyout = new ListElement(ListElement.TYPE_FLYOUT); + // Array of block IDs for all user created shadow blocks. + this.shadowBlocks = []; + // Reference to currently selected ListElement. Stored in this.toolboxList if + // there are categories, or in this.flyout if blocks are displayed in a single + // flyout. + this.selected = this.flyout; + // Boolean for if a Variable category has been added. + this.hasVariableCategory = false; + // Boolean for if a Procedure category has been added. + this.hasProcedureCategory = false; + // XML to be pre-loaded to workspace. Empty on default; + this.preloadXml = Blockly.Xml.textToDom('<xml></xml>'); + // Options object to be configured for Blockly inject call. + this.options = new Object(null); + // Block Library block types. + this.libBlockTypes = []; + // Imported block types. + this.importedBlockTypes = []; + // +}; + +/** + * Given a name, determines if it is the name of a category already present. + * Used when getting a valid category name from the user. + * + * @param {string} name String name to be compared against. + * @return {boolean} True if string is a used category name, false otherwise. + */ +WorkspaceFactoryModel.prototype.hasCategoryByName = function(name) { + for (var i = 0; i < this.toolboxList.length; i++) { + if (this.toolboxList[i].type == ListElement.TYPE_CATEGORY && + this.toolboxList[i].name == name) { + return true; + } + } + return false; +}; + +/** + * Determines if a category with the 'VARIABLE' tag exists. + * + * @return {boolean} True if there exists a category with the Variables tag, + * false otherwise. + */ +WorkspaceFactoryModel.prototype.hasVariables = function() { + return this.hasVariableCategory; +}; + +/** + * Determines if a category with the 'PROCEDURE' tag exists. + * + * @return {boolean} True if there exists a category with the Procedures tag, + * false otherwise. + */ +WorkspaceFactoryModel.prototype.hasProcedures = function() { + return this.hasProcedureCategory; +}; + +/** + * Determines if the user has any elements in the toolbox. Uses the length of + * toolboxList. + * + * @return {boolean} True if elements exist, false otherwise. + */ +WorkspaceFactoryModel.prototype.hasElements = function() { + return this.toolboxList.length > 0; +}; + +/** + * Given a ListElement, adds it to the toolbox list. + * + * @param {!ListElement} element The element to be added to the list. + */ +WorkspaceFactoryModel.prototype.addElementToList = function(element) { + // Update state if the copied category has a custom tag. + this.hasVariableCategory = element.custom == 'VARIABLE' ? true : + this.hasVariableCategory; + this.hasProcedureCategory = element.custom == 'PROCEDURE' ? true : + this.hasProcedureCategory; + // Add element to toolboxList. + this.toolboxList.push(element); + // Empty single flyout. + this.flyout = null; +}; + +/** + * Given an index, deletes a list element and all associated data. + * + * @param {int} index The index of the list element to delete. + */ +WorkspaceFactoryModel.prototype.deleteElementFromList = function(index) { + // Check if index is out of bounds. + if (index < 0 || index >= this.toolboxList.length) { + return; // No entry to delete. + } + // Check if need to update flags. + this.hasVariableCategory = this.toolboxList[index].custom == 'VARIABLE' ? + false : this.hasVariableCategory; + this.hasProcedureCategory = this.toolboxList[index].custom == 'PROCEDURE' ? + false : this.hasProcedureCategory; + // Remove element. + this.toolboxList.splice(index, 1); +}; + +/** + * Sets selected to be an empty category not in toolbox list if toolbox list + * is empty. Should be called when removing the last element from toolbox list. + * If the toolbox list is empty, selected stores the XML for the single flyout + * of blocks displayed. + * + */ +WorkspaceFactoryModel.prototype.createDefaultSelectedIfEmpty = function() { + if (this.toolboxList.length == 0) { + this.flyout = new ListElement(ListElement.TYPE_FLYOUT); + this.selected = this.flyout; + } +} + +/** + * Moves a list element to a certain position in toolboxList by removing it + * and then inserting it at the correct index. Checks that indices are in + * bounds (throws error if not), but assumes that oldIndex is the correct index + * for list element. + * + * @param {!ListElement} element The element to move in toolboxList. + * @param {int} newIndex The index to insert the element at. + * @param {int} oldIndex The index the element is currently at. + */ +WorkspaceFactoryModel.prototype.moveElementToIndex = function(element, newIndex, + oldIndex) { + // Check that indexes are in bounds. + if (newIndex < 0 || newIndex >= this.toolboxList.length || oldIndex < 0 || + oldIndex >= this.toolboxList.length) { + throw new Error('Index out of bounds when moving element in the model.'); + } + this.deleteElementFromList(oldIndex); + this.toolboxList.splice(newIndex, 0, element); +} + +/** + * Returns the ID of the currently selected element. Returns null if there are + * no categories (if selected == null). + * + * @return {string} The ID of the element currently selected. + */ +WorkspaceFactoryModel.prototype.getSelectedId = function() { + return this.selected ? this.selected.id : null; +}; + +/** + * Returns the name of the currently selected category. Returns null if there + * are no categories (if selected == null) or the selected element is not + * a category (in which case its name is null). + * + * @return {string} The name of the category currently selected. + */ +WorkspaceFactoryModel.prototype.getSelectedName = function() { + return this.selected ? this.selected.name : null; +}; + +/** + * Returns the currently selected list element object. + * + * @return {ListElement} The currently selected ListElement + */ +WorkspaceFactoryModel.prototype.getSelected = function() { + return this.selected; +}; + +/** + * Sets list element currently selected by id. + * + * @param {string} id ID of list element that should now be selected. + */ +WorkspaceFactoryModel.prototype.setSelectedById = function(id) { + this.selected = this.getElementById(id); +}; + +/** + * Given an ID of a list element, returns the index of that list element in + * toolboxList. Returns -1 if ID is not present. + * + * @param {!string} id The ID of list element to search for. + * @return {int} The index of the list element in toolboxList, or -1 if it + * doesn't exist. + */ + +WorkspaceFactoryModel.prototype.getIndexByElementId = function(id) { + for (var i = 0; i < this.toolboxList.length; i++) { + if (this.toolboxList[i].id == id) { + return i; + } + } + return -1; // ID not present in toolboxList. +}; + +/** + * Given the ID of a list element, returns that ListElement object. + * + * @param {!string} id The ID of element to search for. + * @return {ListElement} Corresponding ListElement object in toolboxList, or + * null if that element does not exist. + */ +WorkspaceFactoryModel.prototype.getElementById = function(id) { + for (var i = 0; i < this.toolboxList.length; i++) { + if (this.toolboxList[i].id == id) { + return this.toolboxList[i]; + } + } + return null; // ID not present in toolboxList. +}; + +/** + * Given the index of a list element in toolboxList, returns that ListElement + * object. + * + * @param {int} index The index of the element to return. + * @return {ListElement} The corresponding ListElement object in toolboxList. + */ +WorkspaceFactoryModel.prototype.getElementByIndex = function(index) { + if (index < 0 || index >= this.toolboxList.length) { + return null; + } + return this.toolboxList[index]; +}; + +/** + * Returns the xml to load the selected element. + * + * @return {!Element} The XML of the selected element, or null if there is + * no selected element. + */ +WorkspaceFactoryModel.prototype.getSelectedXml = function() { + return this.selected ? this.selected.xml : null; +}; + +/** + * Return ordered list of ListElement objects. + * + * @return {!Array<!ListElement>} ordered list of ListElement objects + */ +WorkspaceFactoryModel.prototype.getToolboxList = function() { + return this.toolboxList; +}; + +/** + * Gets the ID of a category given its name. + * + * @param {string} name Name of category. + * @return {int} ID of category + */ +WorkspaceFactoryModel.prototype.getCategoryIdByName = function(name) { + for (var i = 0; i < this.toolboxList.length; i++) { + if (this.toolboxList[i].name == name) { + return this.toolboxList[i].id; + } + } + return null; // Name not present in toolboxList. +}; + +/** + * Clears the toolbox list, deleting all ListElements. + */ +WorkspaceFactoryModel.prototype.clearToolboxList = function() { + this.toolboxList = []; + this.hasVariableCategory = false; + this.hasProcedureCategory = false; + this.shadowBlocks = []; + this.selected.xml = Blockly.Xml.textToDom('<xml></xml>'); +}; + +/** + * Class for a ListElement + * Adds a shadow block to the list of shadow blocks. + * + * @param {!string} blockId The unique ID of block to be added. + */ +WorkspaceFactoryModel.prototype.addShadowBlock = function(blockId) { + this.shadowBlocks.push(blockId); +}; + +/** + * Removes a shadow block ID from the list of shadow block IDs if that ID is + * in the list. + * + * @param {!string} blockId The unique ID of block to be removed. + */ +WorkspaceFactoryModel.prototype.removeShadowBlock = function(blockId) { + for (var i = 0; i < this.shadowBlocks.length; i++) { + if (this.shadowBlocks[i] == blockId) { + this.shadowBlocks.splice(i, 1); + return; + } + } +}; + +/** + * Determines if a block is a shadow block given a unique block ID. + * + * @param {!string} blockId The unique ID of the block to examine. + * @return {boolean} True if the block is a user-generated shadow block, false + * otherwise. + */ +WorkspaceFactoryModel.prototype.isShadowBlock = function(blockId) { + for (var i = 0; i < this.shadowBlocks.length; i++) { + if (this.shadowBlocks[i] == blockId) { + return true; + } + } + return false; +}; + +/** + * Given a set of blocks currently loaded, returns all blocks in the workspace + * that are user generated shadow blocks. + * + * @param {!<Blockly.Block>} blocks Array of blocks currently loaded. + * @return {!<Blockly.Block>} Array of user-generated shadow blocks currently + * loaded. + */ +WorkspaceFactoryModel.prototype.getShadowBlocksInWorkspace = + function(workspaceBlocks) { + var shadowsInWorkspace = []; + for (var i = 0; i < workspaceBlocks.length; i++) { + if (this.isShadowBlock(workspaceBlocks[i].id)) { + shadowsInWorkspace.push(workspaceBlocks[i]); + } + } + return shadowsInWorkspace; +}; + +/** + * Adds a custom tag to a category, updating state variables accordingly. + * Only accepts 'VARIABLE' and 'PROCEDURE' tags. + * + * @param {!ListElement} category The category to add the tag to. + * @param {!string} tag The custom tag to add to the category. + */ +WorkspaceFactoryModel.prototype.addCustomTag = function(category, tag) { + // Only update list elements that are categories. + if (category.type != ListElement.TYPE_CATEGORY) { + return; + } + // Only update the tag to be 'VARIABLE' or 'PROCEDURE'. + if (tag == 'VARIABLE') { + this.hasVariableCategory = true; + category.custom = 'VARIABLE'; + } else if (tag == 'PROCEDURE') { + this.hasProcedureCategory = true; + category.custom = 'PROCEDURE'; + } +}; + +/** + * Have basic pre-loaded workspace working + * Saves XML as XML to be pre-loaded into the workspace. + * + * @param {!Element} xml The XML to be saved. + */ +WorkspaceFactoryModel.prototype.savePreloadXml = function(xml) { + this.preloadXml = xml +}; + +/** + * Gets the XML to be pre-loaded into the workspace. + * + * @return {!Element} The XML for the workspace. + */ +WorkspaceFactoryModel.prototype.getPreloadXml = function() { + return this.preloadXml; +}; + +/** + * Sets a new options object for injecting a Blockly workspace. + * + * @param {Object} options Options object for injecting a Blockly workspace. + */ +WorkspaceFactoryModel.prototype.setOptions = function(options) { + this.options = options; +}; + +/* + * Returns an array of all the block types currently being used in the toolbox + * and the pre-loaded blocks. No duplicates. + * TODO(evd2014): Move pushBlockTypesToList to FactoryUtils. + * + * @return {!Array<!string>} Array of block types currently being used. + */ +WorkspaceFactoryModel.prototype.getAllUsedBlockTypes = function() { + var blockTypeList = []; + + // Given XML for the workspace, adds all block types included in the XML + // to the list, not including duplicates. + var pushBlockTypesToList = function(xml, list) { + // Get all block XML nodes. + var blocks = xml.getElementsByTagName('block'); + + // Add block types if not already in list. + for (var i = 0; i < blocks.length; i++) { + var type = blocks[i].getAttribute('type'); + if (list.indexOf(type) == -1) { + list.push(type); + } + } + }; + + if (this.flyout) { + // If has a single flyout, add block types for the single flyout. + pushBlockTypesToList(this.getSelectedXml(), blockTypeList); + } else { + // If has categories, add block types for each category. + + for (var i = 0, category; category = this.toolboxList[i]; i++) { + if (category.type == ListElement.TYPE_CATEGORY) { + pushBlockTypesToList(category.xml, blockTypeList); + } + } + } + + // Add the block types from any pre-loaded blocks. + pushBlockTypesToList(this.getPreloadXml(), blockTypeList); + + return blockTypeList; +}; + +/** + * Adds new imported block types to the list of current imported block types. + * + * @param {!Array<!string>} blockTypes Array of block types imported. + */ +WorkspaceFactoryModel.prototype.addImportedBlockTypes = function(blockTypes) { + this.importedBlockTypes = this.importedBlockTypes.concat(blockTypes); +}; + +/** + * Updates block types in block library. + * + * @param {!Array<!string>} blockTypes Array of block types in block library. + */ +WorkspaceFactoryModel.prototype.updateLibBlockTypes = function(blockTypes) { + this.libBlockTypes = blockTypes; +}; + +/** + * Determines if a block type is defined as a standard block, in the block + * library, or as an imported block. + * + * @param {!string} blockType Block type to check. + * @return {boolean} True if blockType is defined, false otherwise. + */ +WorkspaceFactoryModel.prototype.isDefinedBlockType = function(blockType) { + var isStandardBlock = StandardCategories.coreBlockTypes.indexOf(blockType) + != -1; + 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; +} + +/** + * Class for a ListElement. + * @constructor + */ +ListElement = function(type, opt_name) { + this.type = type; + // XML DOM element to load the element. + this.xml = Blockly.Xml.textToDom('<xml></xml>'); + // Name of category. Can be changed by user. Null if separator. + this.name = opt_name ? opt_name : null; + // Unique ID of element. Does not change. + this.id = Blockly.genUid(); + // Color of category. Default is no color. Null if separator. + this.color = null; + // Stores a custom tag, if necessary. Null if no custom tag or separator. + this.custom = null; +}; + +// List element types. +ListElement.TYPE_CATEGORY = 'category'; +ListElement.TYPE_SEPARATOR = 'separator'; +ListElement.TYPE_FLYOUT = 'flyout'; + +/** + * Saves a category by updating its XML (does not save XML for + * elements that are not categories). + * + * @param {!Blockly.workspace} workspace The workspace to save category entry + * from. + */ +ListElement.prototype.saveFromWorkspace = function(workspace) { + // Only save XML for categories and flyouts. + if (this.type == ListElement.TYPE_FLYOUT || + this.type == ListElement.TYPE_CATEGORY) { + this.xml = Blockly.Xml.workspaceToDom(workspace); + } +}; + + +/** + * Changes the name of a category object given a new name. Returns if + * not a category. + * + * @param {string} name New name of category. + */ +ListElement.prototype.changeName = function (name) { + // Only update list elements that are categories. + if (this.type != ListElement.TYPE_CATEGORY) { + return; + } + this.name = name; +}; + +/** + * Sets the color of a category. If tries to set the color of something other + * than a category, returns. + * + * @param {!string} color The color that should be used for that category. + */ +ListElement.prototype.changeColor = function (color) { + if (this.type != ListElement.TYPE_CATEGORY) { + return; + } + this.color = color; +}; + +/** + * Makes a copy of the original element and returns it. Everything about the + * copy is identical except for its ID. + * + * @return {!ListElement} The copy of the ListElement. + */ +ListElement.prototype.copy = function() { + copy = new ListElement(this.type); + // Generate a unique ID for the element. + copy.id = Blockly.genUid(); + // Copy all attributes except ID. + copy.name = this.name; + copy.xml = this.xml; + copy.color = this.color; + copy.custom = this.custom; + // Return copy. + return copy; +}; |