summaryrefslogtreecommitdiff
path: root/blockly/demos/blocklyfactory/workspacefactory/wfactory_model.js
diff options
context:
space:
mode:
authorDavid Barksdale <amatus@amatus.name>2016-09-10 17:58:46 -0500
committerDavid Barksdale <amatus@amatus.name>2016-09-10 18:32:35 -0500
commit475f9f3ac7688e58505690d420cafe6ae8bb8b5f (patch)
treeb1bf14b10751fe4e9e146ad7244ec86bb123893d /blockly/demos/blocklyfactory/workspacefactory/wfactory_model.js
parent923561056ddb63ce82fd1ec2a5e249bbdae267bf (diff)
Merge blockly sub-tree
Diffstat (limited to 'blockly/demos/blocklyfactory/workspacefactory/wfactory_model.js')
-rw-r--r--blockly/demos/blocklyfactory/workspacefactory/wfactory_model.js599
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;
+};