File "wfactory_view.js"
Full path: /usr/home/mndrn/domains/mndrn.ru/public_html/block-hill/blockly/demos/blockfactory/workspacefactory/wfactory_view.js
File size: 15.98 KiB (16362 bytes)
MIME-type: text/plain
Charset: utf-8
/**
* @license
* Copyright 2016 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
/**
* 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)
*/
/**
* 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) {
document.getElementById('categoryHeader').textContent =
'You currently have no categories.';
}
};
/**
* 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 {number} 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;
var table = document.getElementById('categoryTable');
document.getElementById('button_down').disabled = selectedIndex >=
table.rows.length - 1 || selectedIndex < 0;
// 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 {number} newIndex The index to move the category to.
* @param {number} 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 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} colour The colour for to be used for the border of the tab,
* or null if none. Must be a valid CSS string.
*/
WorkspaceFactoryView.prototype.setBorderColor = function(id, colour) {
var style = this.tabMap[id].style;
if (colour) {
style.borderLeftWidth = '8px';
style.borderLeftStyle = 'solid';
style.borderColor = colour;
} else {
style.borderLeftWidth = '';
style.borderLeftStyle = '';
style.borderColor = '';
}
};
/**
* 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 {!Array.<!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.utils.dom.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.
Blockly.utils.dom.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() {
// Readonly mode.
document.getElementById('option_readOnly_checkbox').checked = false;
blocklyFactory.ifCheckedEnable(true, ['readonly1', 'readonly2']);
// Set basic options.
document.getElementById('option_css_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_rtl_checkbox').checked = false;
document.getElementById('option_sounds_checkbox').checked = true;
document.getElementById('option_oneBasedIndex_checkbox').checked = true;
document.getElementById('option_horizontalLayout_checkbox').checked = false;
document.getElementById('option_toolboxPosition_checkbox').checked = false;
// Check infinite blocks and hide suboption.
document.getElementById('option_infiniteBlocks_checkbox').checked = true;
document.getElementById('maxBlockNumber_option').style.display =
'none';
// 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 = 20;
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;
};