mirror of
https://github.com/scratchfoundation/scratch-blocks.git
synced 2025-08-28 22:10:31 -04:00
Blockly Factory: Prompt User to Add Variables/Functions Category (#589)
* Fixed marking shadow blocks so keeps warnings when switching between categories * Done with variable and procedure block checks * Used setShadowDom instead of shadowDom_, and nit changes in wfactory init * Fixed bug of disable div covering whole screen
This commit is contained in:
parent
6e88d5c035
commit
4192ca6b52
6 changed files with 229 additions and 113 deletions
|
@ -430,8 +430,9 @@ td {
|
|||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.test {
|
||||
border: 1px solid black;
|
||||
.disabled {
|
||||
background-color: white;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
#toolbox_div {
|
||||
|
@ -466,17 +467,6 @@ td {
|
|||
margin-top: 2%;
|
||||
}
|
||||
|
||||
#disable_div {
|
||||
background-color: white;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
opacity: .5;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
z-index: -1; /* Start behind workspace */
|
||||
}
|
||||
|
||||
/* Rules for Closure popup color picker */
|
||||
.goog-palette {
|
||||
outline: none;
|
||||
|
|
|
@ -913,3 +913,40 @@ FactoryUtils.sameBlockXml = function(blockXml1, blockXml2) {
|
|||
return blockXmlText1 == blockXmlText2;
|
||||
};
|
||||
|
||||
/*
|
||||
* Checks if a block has a variable field. Blocks with variable fields cannot
|
||||
* be shadow blocks.
|
||||
*
|
||||
* @param {Blockly.Block} block The block to check if a variable field exists.
|
||||
* @return {boolean} True if the block has a variable field, false otherwise.
|
||||
*/
|
||||
FactoryUtils.hasVariableField = function(block) {
|
||||
if (!block) {
|
||||
return false;
|
||||
}
|
||||
for (var i = 0, input; input = block.inputList[i]; i++) {
|
||||
for (var j = 0, field; field = input.fieldRow[j]; j++) {
|
||||
if (field.name == 'VAR') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if a block is a procedures block. If procedures block names are
|
||||
* ever updated or expanded, this function should be updated as well (no
|
||||
* other known markers for procedure blocks beyond name).
|
||||
*
|
||||
* @param {Blockly.Block} block The block to check.
|
||||
* @return {boolean} True if hte block is a procedure block, false otherwise.
|
||||
*/
|
||||
FactoryUtils.isProcedureBlock = function(block) {
|
||||
return block &&
|
||||
(block.type == 'procedures_defnoreturn' ||
|
||||
block.type == 'procedures_defreturn' ||
|
||||
block.type == 'procedures_callnoreturn' ||
|
||||
block.type == 'procedures_callreturn' ||
|
||||
block.type == 'procedures_ifreturn');
|
||||
};
|
||||
|
|
|
@ -180,7 +180,6 @@
|
|||
</table>
|
||||
<section id="toolbox_section">
|
||||
<div id="toolbox_blocks"></div>
|
||||
<div id='disable_div'></div>
|
||||
</section>
|
||||
<aside id="toolbox_div">
|
||||
<p id="categoryHeader">Your categories will appear here</p>
|
||||
|
|
|
@ -98,11 +98,76 @@ WorkspaceFactoryController.MODE_PRELOAD = 'preload';
|
|||
* before), and then creates a tab and switches to it.
|
||||
*/
|
||||
WorkspaceFactoryController.prototype.addCategory = function() {
|
||||
// Check if it's the first category added.
|
||||
this.allowToTransferFlyoutBlocksToCategory();
|
||||
|
||||
// After possibly creating a category, check again if it's the first category.
|
||||
var isFirstCategory = !this.model.hasElements();
|
||||
// Get name from user.
|
||||
name = this.promptForNewCategoryName('Enter the name of your new category: ');
|
||||
if (!name) { //Exit if cancelled.
|
||||
return;
|
||||
}
|
||||
// Create category.
|
||||
this.createCategory(name);
|
||||
// Switch to category.
|
||||
this.switchElement(this.model.getCategoryIdByName(name));
|
||||
|
||||
// Allow the user to use the default options for injecting the workspace
|
||||
// when there are categories if adding the first category.
|
||||
if (isFirstCategory) {
|
||||
this.allowToSetDefaultOptions();
|
||||
}
|
||||
// Update preview.
|
||||
this.updatePreview();
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper method for addCategory. Adds a category to the view given a name, ID,
|
||||
* and a boolean for if it's the first category created. Assumes the category
|
||||
* has already been created in the model. Does not switch to category.
|
||||
*
|
||||
* @param {!string} name Name of category being added.
|
||||
* @param {!string} id The ID of the category being added.
|
||||
*/
|
||||
WorkspaceFactoryController.prototype.createCategory = function(name) {
|
||||
// Create empty category
|
||||
var category = new ListElement(ListElement.TYPE_CATEGORY, name);
|
||||
this.model.addElementToList(category);
|
||||
// Create new category.
|
||||
var tab = this.view.addCategoryRow(name, category.id);
|
||||
this.addClickToSwitch(tab, category.id);
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a tab and a ID to be associated to that tab, adds a listener to
|
||||
* that tab so that when the user clicks on the tab, it switches to the
|
||||
* element associated with that ID.
|
||||
*
|
||||
* @param {!Element} tab The DOM element to add the listener to.
|
||||
* @param {!string} id The ID of the element to switch to when tab is clicked.
|
||||
*/
|
||||
WorkspaceFactoryController.prototype.addClickToSwitch = function(tab, id) {
|
||||
var self = this;
|
||||
var clickFunction = function(id) { // Keep this in scope for switchElement
|
||||
return function() {
|
||||
self.switchElement(id);
|
||||
};
|
||||
};
|
||||
this.view.bindClick(tab, clickFunction(id));
|
||||
};
|
||||
|
||||
/**
|
||||
* Allows the user to transfer blocks in their 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 =
|
||||
function() {
|
||||
// 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) {
|
||||
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.');
|
||||
|
@ -127,64 +192,6 @@ WorkspaceFactoryController.prototype.addCategory = function() {
|
|||
this.updatePreview();
|
||||
}
|
||||
}
|
||||
|
||||
// After possibly creating a category, check again if it's the first category.
|
||||
isFirstCategory = !this.model.hasElements();
|
||||
// Get name from user.
|
||||
name = this.promptForNewCategoryName('Enter the name of your new category: ');
|
||||
if (!name) { //Exit if cancelled.
|
||||
return;
|
||||
}
|
||||
// Create category.
|
||||
this.createCategory(name, isFirstCategory);
|
||||
// Switch to category.
|
||||
this.switchElement(this.model.getCategoryIdByName(name));
|
||||
|
||||
// Allow the user to use the default options for injecting the workspace
|
||||
// when there are categories if adding the first category.
|
||||
if (isFirstCategory) {
|
||||
this.allowToSetDefaultOptions();
|
||||
}
|
||||
// Update preview.
|
||||
this.updatePreview();
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper method for addCategory. Adds a category to the view given a name, ID,
|
||||
* and a boolean for if it's the first category created. Assumes the category
|
||||
* has already been created in the model. Does not switch to category.
|
||||
*
|
||||
* @param {!string} name Name of category being added.
|
||||
* @param {!string} id The ID of the category being added.
|
||||
* @param {boolean} isFirstCategory True if it's the first category created,
|
||||
* false otherwise.
|
||||
*/
|
||||
WorkspaceFactoryController.prototype.createCategory = function(name,
|
||||
isFirstCategory) {
|
||||
// Create empty category
|
||||
var category = new ListElement(ListElement.TYPE_CATEGORY, name);
|
||||
this.model.addElementToList(category);
|
||||
// Create new category.
|
||||
var tab = this.view.addCategoryRow(name, category.id, isFirstCategory);
|
||||
this.addClickToSwitch(tab, category.id);
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a tab and a ID to be associated to that tab, adds a listener to
|
||||
* that tab so that when the user clicks on the tab, it switches to the
|
||||
* element associated with that ID.
|
||||
*
|
||||
* @param {!Element} tab The DOM element to add the listener to.
|
||||
* @param {!string} id The ID of the element to switch to when tab is clicked.
|
||||
*/
|
||||
WorkspaceFactoryController.prototype.addClickToSwitch = function(tab, id) {
|
||||
var self = this;
|
||||
var clickFunction = function(id) { // Keep this in scope for switchElement
|
||||
return function() {
|
||||
self.switchElement(id);
|
||||
};
|
||||
};
|
||||
this.view.bindClick(tab, clickFunction(id));
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -562,7 +569,21 @@ WorkspaceFactoryController.prototype.loadCategory = function() {
|
|||
}
|
||||
} while (!this.isStandardCategoryName(name));
|
||||
|
||||
// Check if the user can create that standard category.
|
||||
// Load category.
|
||||
this.loadCategoryByName(name);
|
||||
};
|
||||
|
||||
/**
|
||||
* Loads a Standard Category by name and switches to it. Leverages
|
||||
* StandardCategories. Returns if cannot load standard category.
|
||||
*
|
||||
* @param {string} name Name of the standard category to load.
|
||||
*/
|
||||
WorkspaceFactoryController.prototype.loadCategoryByName = function(name) {
|
||||
// Check if the user can load that standard category.
|
||||
if (!this.isStandardCategoryName(name)) {
|
||||
return;
|
||||
}
|
||||
if (this.model.hasVariables() && name.toLowerCase() == 'variables') {
|
||||
alert('A Variables category already exists. You cannot create multiple' +
|
||||
' variables categories.');
|
||||
|
@ -580,6 +601,8 @@ WorkspaceFactoryController.prototype.loadCategory = function() {
|
|||
+ '. Rename your category and try again.');
|
||||
return;
|
||||
}
|
||||
// Allow user to transfer current flyout blocks to a category.
|
||||
this.allowToTransferFlyoutBlocksToCategory();
|
||||
|
||||
var isFirstCategory = !this.model.hasElements();
|
||||
// Copy the standard category in the model.
|
||||
|
@ -589,7 +612,7 @@ WorkspaceFactoryController.prototype.loadCategory = function() {
|
|||
this.model.addElementToList(copy);
|
||||
|
||||
// Update the copy in the view.
|
||||
var tab = this.view.addCategoryRow(copy.name, copy.id, isFirstCategory);
|
||||
var tab = this.view.addCategoryRow(copy.name, copy.id);
|
||||
this.addClickToSwitch(tab, copy.id);
|
||||
// Color the category tab in the view.
|
||||
if (copy.color) {
|
||||
|
@ -633,11 +656,9 @@ WorkspaceFactoryController.prototype.isStandardCategoryName = function(name) {
|
|||
* the separator, and updates the preview.
|
||||
*/
|
||||
WorkspaceFactoryController.prototype.addSeparator = function() {
|
||||
// Don't allow the user to add a separator if a category has not been created.
|
||||
if (!this.model.hasElements()) {
|
||||
alert('Add a category before adding a separator.');
|
||||
return;
|
||||
}
|
||||
// If adding the first element in the toolbox, allow the user to transfer
|
||||
// their flyout blocks to a category.
|
||||
this.allowToTransferFlyoutBlocksToCategory();
|
||||
// Create the separator in the model.
|
||||
var separator = new ListElement(ListElement.TYPE_SEPARATOR);
|
||||
this.model.addElementToList(separator);
|
||||
|
@ -881,7 +902,7 @@ WorkspaceFactoryController.prototype.addShadowForBlockAndChildren_ =
|
|||
this.view.markShadowBlock(block);
|
||||
this.model.addShadowBlock(block.id);
|
||||
|
||||
if (this.hasVariableField(block)) {
|
||||
if (FactoryUtils.hasVariableField(block)) {
|
||||
block.setWarningText('Cannot make variable blocks shadow blocks.');
|
||||
}
|
||||
|
||||
|
@ -892,26 +913,6 @@ WorkspaceFactoryController.prototype.addShadowForBlockAndChildren_ =
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if a block has a variable field. Blocks with variable fields cannot
|
||||
* be shadow blocks.
|
||||
*
|
||||
* @param {Blockly.Block} block The block to check if a variable field exists.
|
||||
* @return {boolean} True if the block has a variable field, false otherwise.
|
||||
*/
|
||||
WorkspaceFactoryController.prototype.hasVariableField = function(block) {
|
||||
if (!block) {
|
||||
return false;
|
||||
}
|
||||
for (var i = 0; i < block.inputList.length; i++) {
|
||||
if (block.inputList[i].fieldRow.length > 0 &&
|
||||
block.inputList[i].fieldRow[0].name == 'VAR') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* If the currently selected block is a user-generated shadow block, this
|
||||
* function makes it a normal block again, removing it from the list of
|
||||
|
@ -956,6 +957,14 @@ WorkspaceFactoryController.prototype.convertShadowBlocks = function() {
|
|||
for (var i = 0, block; block = blocks[i]; i++) {
|
||||
if (block.isShadow()) {
|
||||
block.setShadow(false);
|
||||
// Delete the shadow DOM attached to the block so that the shadow block
|
||||
// does not respawn. Dependent on implementation details.
|
||||
var parentConnection = block.outputConnection ?
|
||||
block.outputConnection.targetConnection :
|
||||
block.previousConnection.targetConnection;
|
||||
if (parentConnection) {
|
||||
parentConnection.setShadowDom(null);
|
||||
}
|
||||
this.model.addShadowBlock(block.id);
|
||||
this.view.markShadowBlock(block);
|
||||
}
|
||||
|
@ -1231,3 +1240,21 @@ WorkspaceFactoryController.prototype.warnForUndefinedBlocks_ = function() {
|
|||
}
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Determines if a standard variable category is in the custom toolbox.
|
||||
*
|
||||
* @return {boolean} True if a variables category is in use, false otherwise.
|
||||
*/
|
||||
WorkspaceFactoryController.prototype.hasVariablesCategory = function() {
|
||||
return this.model.hasVariables();
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines if a standard procedures category is in the custom toolbox.
|
||||
*
|
||||
* @return {boolean} True if a procedures category is in use, false otherwise.
|
||||
*/
|
||||
WorkspaceFactoryController.prototype.hasProceduresCategory = function() {
|
||||
return this.model.hasProcedures();
|
||||
};
|
||||
|
|
|
@ -27,6 +27,8 @@
|
|||
* @author Emma Dauterman (evd2014)
|
||||
*/
|
||||
|
||||
goog.require('FactoryUtils');
|
||||
|
||||
/**
|
||||
* Namespace for workspace factory initialization methods.
|
||||
* @namespace
|
||||
|
@ -443,7 +445,7 @@ WorkspaceFactoryInit.addWorkspaceFactoryEventListeners_ = function(controller) {
|
|||
// Enable block editing and remove warnings if the block is not a
|
||||
// variable user-generated shadow block.
|
||||
document.getElementById('button_editShadow').disabled = false;
|
||||
if (!controller.hasVariableField(selected) &&
|
||||
if (!FactoryUtils.hasVariableField(selected) &&
|
||||
controller.isDefinedBlock(selected)) {
|
||||
selected.setWarningText(null);
|
||||
}
|
||||
|
@ -459,7 +461,7 @@ WorkspaceFactoryInit.addWorkspaceFactoryEventListeners_ = function(controller) {
|
|||
// Warn if a non-shadow block is nested inside a shadow block.
|
||||
selected.setWarningText('Only shadow blocks can be nested inside '
|
||||
+ 'other shadow blocks.');
|
||||
} else if (!controller.hasVariableField(selected)) {
|
||||
} else if (!FactoryUtils.hasVariableField(selected)) {
|
||||
// Warn if a shadow block is invalid only if not replacing
|
||||
// warning for variables.
|
||||
selected.setWarningText('Shadow blocks must be nested inside other'
|
||||
|
@ -475,7 +477,7 @@ WorkspaceFactoryInit.addWorkspaceFactoryEventListeners_ = function(controller) {
|
|||
|
||||
// Remove possible 'invalid shadow block placement' warning.
|
||||
if (selected != null && controller.isDefinedBlock(selected) &&
|
||||
(!controller.hasVariableField(selected) ||
|
||||
(!FactoryUtils.hasVariableField(selected) ||
|
||||
!controller.isUserGenShadowBlock(selected.id))) {
|
||||
selected.setWarningText(null);
|
||||
}
|
||||
|
@ -495,6 +497,51 @@ WorkspaceFactoryInit.addWorkspaceFactoryEventListeners_ = function(controller) {
|
|||
// shadow blocks.
|
||||
if (e.type == Blockly.Events.CREATE) {
|
||||
controller.convertShadowBlocks();
|
||||
|
||||
// Let the user create a Variables or Functions category if they use
|
||||
// blocks from either category.
|
||||
|
||||
// Get all children of a block and add them to childList.
|
||||
var getAllChildren = function(block, childList) {
|
||||
childList.push(block);
|
||||
var children = block.getChildren();
|
||||
for (var i = 0, child; child = children[i]; i++) {
|
||||
getAllChildren(child, childList);
|
||||
}
|
||||
};
|
||||
|
||||
var newBaseBlock = controller.toolboxWorkspace.getBlockById(e.blockId);
|
||||
var allNewBlocks = [];
|
||||
getAllChildren(newBaseBlock, allNewBlocks);
|
||||
var variableCreated = false;
|
||||
var procedureCreated = false;
|
||||
|
||||
// Check if the newly created block or any of its children are variable
|
||||
// or procedure blocks.
|
||||
for (var i = 0, block; block = allNewBlocks[i]; i++) {
|
||||
if (FactoryUtils.hasVariableField(block)) {
|
||||
variableCreated = true;
|
||||
} else if (FactoryUtils.isProcedureBlock(block)) {
|
||||
procedureCreated = true;
|
||||
}
|
||||
}
|
||||
|
||||
// If any of the newly created blocks are variable or procedure blocks,
|
||||
// prompt the user to create the corresponding standard category.
|
||||
if (variableCreated && !controller.hasVariablesCategory()) {
|
||||
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.loadCategoryByName('variables');
|
||||
}
|
||||
|
||||
} else 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.loadCategoryByName('functions');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
@ -28,6 +28,8 @@
|
|||
* @author Emma Dauterman (edauterman)
|
||||
*/
|
||||
|
||||
goog.require('FactoryUtils');
|
||||
|
||||
/**
|
||||
* Class for a WorkspaceFactoryView
|
||||
* @constructor
|
||||
|
@ -43,19 +45,18 @@ WorkspaceFactoryView = function() {
|
|||
*
|
||||
* @param {!string} name The name of the category being created
|
||||
* @param {!string} id ID of category being created
|
||||
* @param {boolean} firstCategory true if it's the first category, false
|
||||
* otherwise
|
||||
* @return {!Element} DOM element created for tab
|
||||
*/
|
||||
WorkspaceFactoryView.prototype.addCategoryRow =
|
||||
function(name, id, firstCategory) {
|
||||
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 (firstCategory) {
|
||||
if (count == 0) {
|
||||
document.getElementById('categoryHeader').textContent = 'Your Categories:';
|
||||
}
|
||||
|
||||
// Create tab.
|
||||
var count = table.rows.length;
|
||||
var row = table.insertRow(count);
|
||||
var nextEntry = row.insertCell(0);
|
||||
// Configure tab.
|
||||
|
@ -253,9 +254,13 @@ WorkspaceFactoryView.prototype.setBorderColor = function(id, color) {
|
|||
* @param {!Element} The td DOM element representing the separator.
|
||||
*/
|
||||
WorkspaceFactoryView.prototype.addSeparatorTab = function(id) {
|
||||
// Create separator.
|
||||
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.
|
||||
|
@ -275,7 +280,14 @@ WorkspaceFactoryView.prototype.addSeparatorTab = function(id) {
|
|||
* if it should be enabled.
|
||||
*/
|
||||
WorkspaceFactoryView.prototype.disableWorkspace = function(disable) {
|
||||
document.getElementById('disable_div').style.zIndex = disable ? 1 : -1;
|
||||
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';
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -334,6 +346,10 @@ WorkspaceFactoryView.prototype.markShadowBlock = function(block) {
|
|||
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.');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue