summaryrefslogtreecommitdiff
path: root/blockly/demos/blocklyfactory/workspacefactory/wfactory_view.js
diff options
context:
space:
mode:
Diffstat (limited to 'blockly/demos/blocklyfactory/workspacefactory/wfactory_view.js')
-rw-r--r--blockly/demos/blocklyfactory/workspacefactory/wfactory_view.js452
1 files changed, 452 insertions, 0 deletions
diff --git a/blockly/demos/blocklyfactory/workspacefactory/wfactory_view.js b/blockly/demos/blocklyfactory/workspacefactory/wfactory_view.js
new file mode 100644
index 0000000..6bfb8f9
--- /dev/null
+++ b/blockly/demos/blocklyfactory/workspacefactory/wfactory_view.js
@@ -0,0 +1,452 @@
+/**
+ * @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.
+ */
+
+/**
+ * Controls the UI elements for workspace factory, mainly the category tabs.
+ * Also includes downloading files because that interacts directly with the DOM.
+ * Depends on WorkspaceFactoryController (for adding mouse listeners). Tabs for
+ * each category are stored in tab map, which associates a unique ID for a
+ * category with a particular tab.
+ *
+ * @author Emma Dauterman (edauterman)
+ */
+
+ goog.require('FactoryUtils');
+
+ /**
+ * Class for a WorkspaceFactoryView
+ * @constructor
+ */
+
+WorkspaceFactoryView = function() {
+ // For each tab, maps ID of a ListElement to the td DOM element.
+ this.tabMap = Object.create(null);
+};
+
+/**
+ * Adds a category tab to the UI, and updates tabMap accordingly.
+ *
+ * @param {!string} name The name of the category being created
+ * @param {!string} id ID of category being created
+ * @return {!Element} DOM element created for tab
+ */
+WorkspaceFactoryView.prototype.addCategoryRow = function(name, id) {
+ var table = document.getElementById('categoryTable');
+ var count = table.rows.length;
+
+ // Delete help label and enable category buttons if it's the first category.
+ if (count == 0) {
+ document.getElementById('categoryHeader').textContent = 'Your Categories:';
+ }
+
+ // Create tab.
+ var row = table.insertRow(count);
+ var nextEntry = row.insertCell(0);
+ // Configure tab.
+ nextEntry.id = this.createCategoryIdName(name);
+ nextEntry.textContent = name;
+ // Store tab.
+ this.tabMap[id] = table.rows[count].cells[0];
+ // Return tab.
+ return nextEntry;
+};
+
+/**
+ * Deletes a category tab from the UI and updates tabMap accordingly.
+ *
+ * @param {!string} id ID of category to be deleted.
+ * @param {!string} name The name of the category to be deleted.
+ */
+WorkspaceFactoryView.prototype.deleteElementRow = function(id, index) {
+ // Delete tab entry.
+ delete this.tabMap[id];
+ // Delete tab row.
+ var table = document.getElementById('categoryTable');
+ var count = table.rows.length;
+ table.deleteRow(index);
+
+ // If last category removed, add category help text and disable category
+ // buttons.
+ this.addEmptyCategoryMessage();
+};
+
+/**
+ * If there are no toolbox elements created, adds a help message to show
+ * where categories will appear. Should be called when deleting list elements
+ * in case the last element is deleted.
+ */
+WorkspaceFactoryView.prototype.addEmptyCategoryMessage = function() {
+ var table = document.getElementById('categoryTable');
+ if (table.rows.length == 0) {
+ document.getElementById('categoryHeader').textContent =
+ 'Your categories will appear here';
+ }
+}
+
+/**
+ * Given the index of the currently selected element, updates the state of
+ * the buttons that allow the user to edit the list elements. Updates the edit
+ * and arrow buttons. Should be called when adding or removing elements
+ * or when changing to a new element or when swapping to a different element.
+ *
+ * TODO(evd2014): Switch to using CSS to add/remove styles.
+ *
+ * @param {int} selectedIndex The index of the currently selected category,
+ * -1 if no categories created.
+ * @param {ListElement} selected The selected ListElement.
+ */
+WorkspaceFactoryView.prototype.updateState = function(selectedIndex, selected) {
+ // Disable/enable editing buttons as necessary.
+ document.getElementById('button_editCategory').disabled = selectedIndex < 0 ||
+ selected.type != ListElement.TYPE_CATEGORY;
+ document.getElementById('button_remove').disabled = selectedIndex < 0;
+ document.getElementById('button_up').disabled =
+ selectedIndex <= 0 ? true : false;
+ var table = document.getElementById('categoryTable');
+ document.getElementById('button_down').disabled = selectedIndex >=
+ table.rows.length - 1 || selectedIndex < 0 ? true : false;
+ // Disable/enable the workspace as necessary.
+ this.disableWorkspace(this.shouldDisableWorkspace(selected));
+};
+
+/**
+ * Determines the DOM id for a category given its name.
+ *
+ * @param {!string} name Name of category
+ * @return {!string} ID of category tab
+ */
+WorkspaceFactoryView.prototype.createCategoryIdName = function(name) {
+ return 'tab_' + name;
+};
+
+/**
+ * Switches a tab on or off.
+ *
+ * @param {!string} id ID of the tab to switch on or off.
+ * @param {boolean} selected True if tab should be on, false if tab should be
+ * off.
+ */
+WorkspaceFactoryView.prototype.setCategoryTabSelection =
+ function(id, selected) {
+ if (!this.tabMap[id]) {
+ return; // Exit if tab does not exist.
+ }
+ this.tabMap[id].className = selected ? 'tabon' : 'taboff';
+};
+
+/**
+ * Used to bind a click to a certain DOM element (used for category tabs).
+ * Taken directly from code.js
+ *
+ * @param {string|!Element} e1 tab element or corresponding id string
+ * @param {!Function} func Function to be executed on click
+ */
+WorkspaceFactoryView.prototype.bindClick = function(el, func) {
+ if (typeof el == 'string') {
+ el = document.getElementById(el);
+ }
+ el.addEventListener('click', func, true);
+ el.addEventListener('touchend', func, true);
+};
+
+/**
+ * Creates a file and downloads it. In some browsers downloads, and in other
+ * browsers, opens new tab with contents.
+ *
+ * @param {!string} filename Name of file
+ * @param {!Blob} data Blob containing contents to download
+ */
+WorkspaceFactoryView.prototype.createAndDownloadFile =
+ function(filename, data) {
+ var clickEvent = new MouseEvent("click", {
+ "view": window,
+ "bubbles": true,
+ "cancelable": false
+ });
+ var a = document.createElement('a');
+ a.href = window.URL.createObjectURL(data);
+ a.download = filename;
+ a.textContent = 'Download file!';
+ a.dispatchEvent(clickEvent);
+ };
+
+/**
+ * Given the ID of a certain category, updates the corresponding tab in
+ * the DOM to show a new name.
+ *
+ * @param {!string} newName Name of string to be displayed on tab
+ * @param {!string} id ID of category to be updated
+ *
+ */
+WorkspaceFactoryView.prototype.updateCategoryName = function(newName, id) {
+ this.tabMap[id].textContent = newName;
+ this.tabMap[id].id = this.createCategoryIdName(newName);
+};
+
+/**
+ * Moves a tab from one index to another. Adjusts index inserting before
+ * based on if inserting before or after. Checks that the indexes are in
+ * bounds, throws error if not.
+ *
+ * @param {!string} id The ID of the category to move.
+ * @param {int} newIndex The index to move the category to.
+ * @param {int} oldIndex The index the category is currently at.
+ */
+WorkspaceFactoryView.prototype.moveTabToIndex =
+ function(id, newIndex, oldIndex) {
+ var table = document.getElementById('categoryTable');
+ // Check that indexes are in bounds
+ if (newIndex < 0 || newIndex >= table.rows.length || oldIndex < 0 ||
+ oldIndex >= table.rows.length) {
+ throw new Error('Index out of bounds when moving tab in the view.');
+ }
+
+ if (newIndex < oldIndex) {
+ // Inserting before.
+ var row = table.insertRow(newIndex);
+ row.appendChild(this.tabMap[id]);
+ table.deleteRow(oldIndex + 1);
+ } else {
+ // Inserting after.
+ var row = table.insertRow(newIndex + 1);
+ row.appendChild(this.tabMap[id]);
+ table.deleteRow(oldIndex);
+ }
+};
+
+/**
+ * Given a category ID and color, use that color to color the left border of the
+ * tab for that category.
+ *
+ * @param {!string} id The ID of the category to color.
+ * @param {!string} color The color for to be used for the border of the tab.
+ * Must be a valid CSS string.
+ */
+WorkspaceFactoryView.prototype.setBorderColor = function(id, color) {
+ var tab = this.tabMap[id];
+ tab.style.borderLeftWidth = "8px";
+ tab.style.borderLeftStyle = "solid";
+ tab.style.borderColor = color;
+};
+
+/**
+ * Given a separator ID, creates a corresponding tab in the view, updates
+ * tab map, and returns the tab.
+ *
+ * @param {!string} id The ID of the separator.
+ * @param {!Element} The td DOM element representing the separator.
+ */
+WorkspaceFactoryView.prototype.addSeparatorTab = function(id) {
+ var table = document.getElementById('categoryTable');
+ var count = table.rows.length;
+
+ if (count == 0) {
+ document.getElementById('categoryHeader').textContent = 'Your Categories:';
+ }
+ // Create separator.
+ var row = table.insertRow(count);
+ var nextEntry = row.insertCell(0);
+ // Configure separator.
+ nextEntry.style.height = '10px';
+ // Store and return separator.
+ this.tabMap[id] = table.rows[count].cells[0];
+ return nextEntry;
+};
+
+/**
+ * Disables or enables the workspace by putting a div over or under the
+ * toolbox workspace, depending on the value of disable. Used when switching
+ * to/from separators where the user shouldn't be able to drag blocks into
+ * the workspace.
+ *
+ * @param {boolean} disable True if the workspace should be disabled, false
+ * if it should be enabled.
+ */
+WorkspaceFactoryView.prototype.disableWorkspace = function(disable) {
+ if (disable) {
+ document.getElementById('toolbox_section').className = 'disabled';
+ document.getElementById('toolbox_blocks').style.pointerEvents = 'none';
+ } else {
+ document.getElementById('toolbox_section').className = '';
+ document.getElementById('toolbox_blocks').style.pointerEvents = 'auto';
+ }
+
+};
+
+/**
+ * Determines if the workspace should be disabled. The workspace should be
+ * disabled if category is a separator or has VARIABLE or PROCEDURE tags.
+ *
+ * @return {boolean} True if the workspace should be disabled, false otherwise.
+ */
+WorkspaceFactoryView.prototype.shouldDisableWorkspace = function(category) {
+ return category != null && category.type != ListElement.TYPE_FLYOUT &&
+ (category.type == ListElement.TYPE_SEPARATOR ||
+ category.custom == 'VARIABLE' || category.custom == 'PROCEDURE');
+};
+
+/*
+ * Removes all categories and separators in the view. Clears the tabMap to
+ * reflect this.
+ */
+WorkspaceFactoryView.prototype.clearToolboxTabs = function() {
+ this.tabMap = [];
+ var oldCategoryTable = document.getElementById('categoryTable');
+ var newCategoryTable = document.createElement('table');
+ newCategoryTable.id = 'categoryTable';
+ newCategoryTable.style.width = 'auto';
+ oldCategoryTable.parentElement.replaceChild(newCategoryTable,
+ oldCategoryTable);
+};
+
+/**
+ * Given a set of blocks currently loaded user-generated shadow blocks, visually
+ * marks them without making them actual shadow blocks (allowing them to still
+ * be editable and movable).
+ *
+ * @param {!<Blockly.Block>} blocks Array of user-generated shadow blocks
+ * currently loaded.
+ */
+WorkspaceFactoryView.prototype.markShadowBlocks = function(blocks) {
+ for (var i = 0; i < blocks.length; i++) {
+ this.markShadowBlock(blocks[i]);
+ }
+};
+
+/**
+ * Visually marks a user-generated shadow block as a shadow block in the
+ * workspace without making the block an actual shadow block (allowing it
+ * to be moved and edited).
+ *
+ * @param {!Blockly.Block} block The block that should be marked as a shadow
+ * block (must be rendered).
+ */
+WorkspaceFactoryView.prototype.markShadowBlock = function(block) {
+ // Add Blockly CSS for user-generated shadow blocks.
+ Blockly.addClass_(block.svgGroup_, 'shadowBlock');
+ // If not a valid shadow block, add a warning message.
+ if (!block.getSurroundParent()) {
+ block.setWarningText('Shadow blocks must be nested inside' +
+ ' other blocks to be displayed.');
+ }
+
+ if (FactoryUtils.hasVariableField(block)) {
+ block.setWarningText('Cannot make variable blocks shadow blocks.');
+ }
+};
+
+/**
+ * Removes visual marking for a shadow block given a rendered block.
+ *
+ * @param {!Blockly.Block} block The block that should be unmarked as a shadow
+ * block (must be rendered).
+ */
+WorkspaceFactoryView.prototype.unmarkShadowBlock = function(block) {
+ // Remove Blockly CSS for user-generated shadow blocks.
+ if (Blockly.hasClass_(block.svgGroup_, 'shadowBlock')) {
+ Blockly.removeClass_(block.svgGroup_, 'shadowBlock');
+ }
+};
+
+/**
+ * Sets the tabs for modes according to which mode the user is currenly
+ * editing in.
+ *
+ * @param {!string} mode The mode being switched to
+ * (WorkspaceFactoryController.MODE_TOOLBOX or WorkspaceFactoryController.MODE_PRELOAD).
+ */
+WorkspaceFactoryView.prototype.setModeSelection = function(mode) {
+ document.getElementById('tab_preload').className = mode ==
+ WorkspaceFactoryController.MODE_PRELOAD ? 'tabon' : 'taboff';
+ document.getElementById('preload_div').style.display = mode ==
+ WorkspaceFactoryController.MODE_PRELOAD ? 'block' : 'none';
+ document.getElementById('tab_toolbox').className = mode ==
+ WorkspaceFactoryController.MODE_TOOLBOX ? 'tabon' : 'taboff';
+ document.getElementById('toolbox_div').style.display = mode ==
+ WorkspaceFactoryController.MODE_TOOLBOX ? 'block' : 'none';
+};
+
+/**
+ * Updates the help text above the workspace depending on the selected mode.
+ *
+ * @param {!string} mode The selected mode (WorkspaceFactoryController.MODE_TOOLBOX or
+ * WorkspaceFactoryController.MODE_PRELOAD).
+ */
+WorkspaceFactoryView.prototype.updateHelpText = function(mode) {
+ if (mode == WorkspaceFactoryController.MODE_TOOLBOX) {
+ var helpText = 'Drag blocks into the workspace to configure the toolbox '
+ + 'in your custom workspace.';
+ } else {
+ var helpText = 'Drag blocks into the workspace to pre-load them in your '
+ + 'custom workspace.'
+ }
+ document.getElementById('editHelpText').textContent = helpText;
+};
+
+/**
+ * Sets the basic options that are not dependent on if there are categories
+ * or a single flyout of blocks. Updates checkboxes and text fields.
+ */
+WorkspaceFactoryView.prototype.setBaseOptions = function() {
+ // Set basic options.
+ document.getElementById('option_css_checkbox').checked = true;
+ document.getElementById('option_infiniteBlocks_checkbox').checked = true;
+ document.getElementById('option_maxBlocks_number').value = 100;
+ document.getElementById('option_media_text').value =
+ 'https://blockly-demo.appspot.com/static/media/';
+ document.getElementById('option_readOnly_checkbox').checked = false;
+ document.getElementById('option_rtl_checkbox').checked = false;
+ document.getElementById('option_sounds_checkbox').checked = true;
+
+ // Uncheck grid and zoom options and hide suboptions.
+ document.getElementById('option_grid_checkbox').checked = false;
+ document.getElementById('grid_options').style.display = 'none';
+ document.getElementById('option_zoom_checkbox').checked = false;
+ document.getElementById('zoom_options').style.display = 'none';
+
+ // Set grid options.
+ document.getElementById('gridOption_spacing_number').value = 0;
+ document.getElementById('gridOption_length_number').value = 1;
+ document.getElementById('gridOption_colour_text').value = '#888';
+ document.getElementById('gridOption_snap_checkbox').checked = false;
+
+ // Set zoom options.
+ document.getElementById('zoomOption_controls_checkbox').checked = true;
+ document.getElementById('zoomOption_wheel_checkbox').checked = true;
+ document.getElementById('zoomOption_startScale_number').value = 1.0;
+ document.getElementById('zoomOption_maxScale_number').value = 3;
+ document.getElementById('zoomOption_minScale_number').value = 0.3;
+ document.getElementById('zoomOption_scaleSpeed_number').value = 1.2;
+};
+
+/**
+ * Updates category specific options depending on if there are categories
+ * currently present. Updates checkboxes and text fields in the view.
+ *
+ * @param {boolean} hasCategories True if categories are present, false if all
+ * blocks are displayed in a single flyout.
+ */
+WorkspaceFactoryView.prototype.setCategoryOptions = function(hasCategories) {
+ document.getElementById('option_collapse_checkbox').checked = hasCategories;
+ document.getElementById('option_comments_checkbox').checked = hasCategories;
+ document.getElementById('option_disable_checkbox').checked = hasCategories;
+ document.getElementById('option_scrollbars_checkbox').checked = hasCategories;
+ document.getElementById('option_trashcan_checkbox').checked = hasCategories;
+}