Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add dictionary variables and related blocks #22

Merged
merged 13 commits into from
May 11, 2019
139 changes: 139 additions & 0 deletions blocks_vertical/data.js
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,85 @@ Blockly.Blocks['data_hidelist'] = {
}
};

Blockly.Blocks['data_dictcontents'] = {
/**
* Dictionary reporter.
* @this Blockly.Block
*/
init: function() {
this.jsonInit({
"message0": "%1",
"args0": [
{
"type": "field_variable_getter",
"text": "",
"name": "DICT",
"variableType": Blockly.DICT_VARIABLE_TYPE
}
],
"category": Blockly.Categories.dataDicts,
"extensions": ["contextMenu_getDictBlock", "colours_data_dicts", "output_string"],
"checkboxInFlyout": true
});
}
};

Blockly.Blocks['data_addtodict'] = {
/**
* Block to add item to dictionary.
* @this Blockly.Block
*/
init: function() {
this.jsonInit({
"message0": Blockly.Msg.DATA_ADDTODICT,
"args0": [
{
"type": "input_value",
"name": "ITEM"
},
{
"type": "input_value",
"name": "KEY"
},
{
"type": "field_variable",
"name": "DICT",
"variableTypes": [Blockly.DICT_VARIABLE_TYPE]
}
],
"category": Blockly.Categories.dataDicts,
"extensions": ["colours_data_dicts", "shape_statement"]
});
}
};

Blockly.Blocks['data_itemofdict'] = {
/**
* Block for reporting item of dictionary.
* @this Blockly.Block
*/
init: function() {
this.jsonInit({
"message0": Blockly.Msg.DATA_ITEMOFDICT,
"args0": [
{
"type": "input_value",
"name": "KEY"
},
{
"type": "field_variable",
"name": "DICT",
"variableTypes": [Blockly.DICT_VARIABLE_TYPE]
}
],
"output": null,
"category": Blockly.Categories.dataDicts,
"extensions": ["colours_data_dicts"],
"outputShape": Blockly.OUTPUT_SHAPE_ROUND
});
}
};

/**
* Mixin to add a context menu for a data_variable block. It adds one item for
* each variable defined on the workspace.
Expand Down Expand Up @@ -612,6 +691,66 @@ Blockly.Constants.Data.CUSTOM_CONTEXT_MENU_GET_LIST_MIXIN = {
Blockly.Extensions.registerMixin('contextMenu_getListBlock',
Blockly.Constants.Data.CUSTOM_CONTEXT_MENU_GET_LIST_MIXIN);

/**
* XXX: This is copied from the mixin for the list context menu.
* I've modified it without paying much attention so beware.
* TODO: Inspect this in detail.
tansly marked this conversation as resolved.
Show resolved Hide resolved
* Make sure the stuff about variable renaming, deletion etc. work properly.
*
* Mixin to add a context menu for a data_dictcontents block. It adds one item for
* each dictionary defined on the workspace.
* @mixin
* @augments Blockly.Block
* @package
* @readonly
*/
Blockly.Constants.Data.CUSTOM_CONTEXT_MENU_GET_DICT_MIXIN = {
/**
* Add context menu option to change the selected dictionary.
* @param {!Array} options List of menu options to add to.
* @this Blockly.Block
*/
customContextMenu: function(options) {
var fieldName = 'DICT';
if (this.isCollapsed()) {
return;
}
var currentVarName = this.getField(fieldName).text_;
if (!this.isInFlyout) {
var variablesList = this.workspace.getVariablesOfType('dict');
for (var i = 0; i < variablesList.length; i++) {
var varName = variablesList[i].name;
if (varName == currentVarName) continue;

var option = {enabled: true};
option.text = varName;

option.callback =
Blockly.Constants.Data.VARIABLE_OPTION_CALLBACK_FACTORY(this,
variablesList[i].getId(), fieldName);
options.push(option);
}
} else {
var renameOption = {
text: Blockly.Msg.RENAME_DICT,
enabled: true,
callback: Blockly.Constants.Data.RENAME_OPTION_CALLBACK_FACTORY(this,
fieldName)
};
var deleteOption = {
text: Blockly.Msg.DELETE_DICT.replace('%1', currentVarName),
enabled: true,
callback: Blockly.Constants.Data.DELETE_OPTION_CALLBACK_FACTORY(this,
fieldName)
};
options.push(renameOption);
options.push(deleteOption);
}
}
};
Blockly.Extensions.registerMixin('contextMenu_getDictBlock',
Blockly.Constants.Data.CUSTOM_CONTEXT_MENU_GET_DICT_MIXIN);

/**
* Callback factory for dropdown menu options associated with a variable getter
* block. Each variable on the workspace gets its own item in the dropdown
Expand Down
2 changes: 1 addition & 1 deletion blocks_vertical/vertical_extensions.js
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ Blockly.ScratchBlocks.VerticalExtensions.SCRATCH_EXTENSION = function() {
*/
Blockly.ScratchBlocks.VerticalExtensions.registerAll = function() {
var categoryNames =
['control', 'data', 'data_lists', 'sounds', 'motion', 'looks', 'event',
['control', 'data', 'data_lists', 'data_dicts', 'sounds', 'motion', 'looks', 'event',
'sensing', 'pen', 'operators', 'more'];
// Register functions for all category colours.
for (var i = 0; i < categoryNames.length; i++) {
Expand Down
6 changes: 6 additions & 0 deletions core/colours.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,12 @@ Blockly.Colours = {
"secondary": "#FF5500",
"tertiary": "#E64D00"
},
"data_dicts": {
// TODO: May reconsider these colours.
"primary": "#0FFF0F",
"secondary": "#00C614",
"tertiary": "#006005"
},
"more": {
"primary": "#FF6680",
"secondary": "#FF4D6A",
Expand Down
9 changes: 9 additions & 0 deletions core/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@ Blockly.Categories = {
"pen": "pen",
"data": "data",
"dataLists": "data-lists",
"dataDicts": "data-dicts",
"event": "events",
"control": "control",
"sensing": "sensing",
Expand Down Expand Up @@ -366,6 +367,14 @@ Blockly.CLONE_NAME_VARIABLE_TYPE = 'clone_name';
*/
Blockly.LIST_VARIABLE_TYPE = 'list';

/**
* String representing the variable type of dictionary blocks.
* This string, for use in differentiating between types of variables,
* indicates that the current variable is a dictionary.
* @const {string}
*/
Blockly.DICT_VARIABLE_TYPE = 'dict';

// TODO (#1251) Replace '' below with 'scalar', and start using this constant
// everywhere.
/**
Expand Down
90 changes: 90 additions & 0 deletions core/data_category.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,33 @@ Blockly.DataCategory = function(workspace) {
Blockly.DataCategory.addHideList(xmlList, firstVariable);
}

Blockly.DataCategory.addCreateButton(xmlList, workspace, 'DICT');
variableModelList = workspace.getVariablesOfType(Blockly.DICT_VARIABLE_TYPE);
variableModelList.sort(Blockly.VariableModel.compareByName);
for (var i = 0; i < variableModelList.length; i++) {
Blockly.DataCategory.addDataDict(xmlList, variableModelList[i]);
}

if (variableModelList.length > 0) {
xmlList[xmlList.length - 1].setAttribute('gap', 24);
var firstVariable = variableModelList[0];

Blockly.DataCategory.addAddToDict(xmlList, firstVariable);
Blockly.DataCategory.addSep(xmlList);
// Blockly.DataCategory.addDeleteOfList(xmlList, firstVariable);
// Blockly.DataCategory.addDeleteAllOfList(xmlList, firstVariable);
// Blockly.DataCategory.addInsertAtList(xmlList, firstVariable);
// Blockly.DataCategory.addReplaceItemOfList(xmlList, firstVariable);
tansly marked this conversation as resolved.
Show resolved Hide resolved
Blockly.DataCategory.addSep(xmlList);
Blockly.DataCategory.addItemOfDict(xmlList, firstVariable);
// Blockly.DataCategory.addItemNumberOfList(xmlList, firstVariable);
// Blockly.DataCategory.addLengthOfList(xmlList, firstVariable);
// Blockly.DataCategory.addListContainsItem(xmlList, firstVariable);
// Blockly.DataCategory.addSep(xmlList);
// Blockly.DataCategory.addShowList(xmlList, firstVariable);
// Blockly.DataCategory.addHideList(xmlList, firstVariable);
}

return xmlList;
};

Expand Down Expand Up @@ -374,6 +401,61 @@ Blockly.DataCategory.addHideList = function(xmlList, variable) {
Blockly.DataCategory.addBlock(xmlList, variable, 'data_hidelist', 'LIST');
};

/**
* Construct and add a data_dictcontents block to xmlList.
* @param {!Array.<!Element>} xmlList Array of XML block elements.
* @param {?Blockly.VariableModel} variable Variable to select in the field.
*/
Blockly.DataCategory.addDataDict = function(xmlList, variable) {
// <block id="variableId" type="data_dictcontents">
// <field name="DICT">variablename</field>
// </block>
Blockly.DataCategory.addBlock(xmlList, variable, 'data_dictcontents', 'DICT');
// In the flyout, this ID must match variable ID for monitor syncing reasons
xmlList[xmlList.length - 1].setAttribute('id', variable.getId());
};

/**
* Construct and add a data_addtodict block to xmlList.
* @param {!Array.<!Element>} xmlList Array of XML block elements.
* @param {?Blockly.VariableModel} variable Variable to select in the field.
*/
Blockly.DataCategory.addAddToDict = function(xmlList, variable) {
// <block type="data_addtodict">
// <field name="DICT" variabletype="dict" id="">variablename</field>
// <value name="ITEM">
// <shadow type="text">
// <field name="TEXT">thing</field>
// </shadow>
// </value>
// <value name="KEY">
// <shadow type="text">
// <field name="TEXT">key</field>
// </shadow>
// </value>
// </block>
Blockly.DataCategory.addBlock(xmlList, variable, 'data_addtodict', 'DICT',
['ITEM', 'text', Blockly.Msg.DEFAULT_DICT_ITEM], ['KEY', 'text', Blockly.Msg.DEFAULT_DICT_KEY]);
tansly marked this conversation as resolved.
Show resolved Hide resolved
};

/**
* Construct and add a data_itemofdict block to xmlList.
* @param {!Array.<!Element>} xmlList Array of XML block elements.
* @param {?Blockly.VariableModel} variable Variable to select in the field.
*/
Blockly.DataCategory.addItemOfDict = function(xmlList, variable) {
// <block type="data_itemofdict">
// <field name="DICT" variabletype="dict" id="">variablename</field>
// <value name="KEY">
// <shadow type="text">
// <field name="TEXT">key</field>
// </shadow>
// </value>
// </block>
Blockly.DataCategory.addBlock(xmlList, variable, 'data_itemofdict', 'DICT',
['KEY', 'text', Blockly.Msg.DEFAULT_DICT_KEY]);
};

/**
* Construct a create variable button and push it to the xmlList.
* @param {!Array.<!Element>} xmlList Array of XML block elements.
Expand All @@ -395,6 +477,12 @@ Blockly.DataCategory.addCreateButton = function(xmlList, workspace, type) {
callback = function(button) {
Blockly.Variables.createVariable(button.getTargetWorkspace(), null,
Blockly.LIST_VARIABLE_TYPE);};
} else if (type === 'DICT') {
msg = Blockly.Msg.NEW_DICT;
callbackKey = 'CREATE_DICT';
callback = function(button) {
Blockly.Variables.createVariable(button.getTargetWorkspace(), null,
Blockly.DICT_VARIABLE_TYPE);};
}
button.setAttribute('text', msg);
button.setAttribute('callbackKey', callbackKey);
Expand Down Expand Up @@ -454,6 +542,8 @@ Blockly.DataCategory.createValue = function(valueName, type, value) {
var fieldName;
switch (valueName) {
case 'ITEM':
// Fall through.
case 'KEY':
fieldName = 'TEXT';
break;
case 'INDEX':
Expand Down
19 changes: 13 additions & 6 deletions core/variables.js
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,9 @@ Blockly.Variables.createVariable = function(workspace, opt_callback, opt_type) {
} else if (opt_type == Blockly.LIST_VARIABLE_TYPE) {
newMsg = Blockly.Msg.NEW_LIST_TITLE;
modalTitle = Blockly.Msg.LIST_MODAL_TITLE;
} else if (opt_type == Blockly.DICT_VARIABLE_TYPE) {
newMsg = Blockly.Msg.NEW_DICT_TITLE;
modalTitle = Blockly.Msg.DICT_MODAL_TITLE;
} else if (opt_type == Blockly.CLONE_NAME_VARIABLE_TYPE) {
newMsg = Blockly.Msg.NEW_CLONE_TITLE;
modalTitle = Blockly.Msg.NEW_CLONE_MODAL_TITLE;
Expand Down Expand Up @@ -377,19 +380,22 @@ Blockly.Variables.nameValidator_ = function(type, text, workspace, additionalVar
// For broadcast messages, if a broadcast message of the provided name already exists,
// the validator needs to call a function that updates the selected
// field option of the dropdown menu of the block that was used to create the new message.
// For scalar variables and lists, the validator has the same validation behavior, but needs
// For scalar variables, lists and dictionaries, the validator has the same validation behavior, but needs
// to know which type of variable to check for and needs a type-specific error message
// that is displayed when a variable of the given name and type already exists.

if (type == Blockly.BROADCAST_MESSAGE_VARIABLE_TYPE) {
return Blockly.Variables.validateBroadcastMessageName_(text, workspace, opt_callback);
} else if (type == Blockly.LIST_VARIABLE_TYPE) {
return Blockly.Variables.validateScalarVarOrListName_(text, workspace, additionalVars, false, type,
return Blockly.Variables.validateScalarVarOrListOrDictName_(text, workspace, additionalVars, false, type,
Blockly.Msg.LIST_ALREADY_EXISTS);
} else if (type == Blockly.DICT_VARIABLE_TYPE) {
return Blockly.Variables.validateScalarVarOrListOrDictName_(text, workspace, additionalVars, false, type,
Blockly.Msg.DICT_ALREADY_EXISTS);
} else if (type == Blockly.CLONE_NAME_VARIABLE_TYPE) {
return Blockly.Variables.validateCloneName_(text, workspace);
} else {
return Blockly.Variables.validateScalarVarOrListName_(text, workspace, additionalVars, isCloud, type,
return Blockly.Variables.validateScalarVarOrListOrDictName_(text, workspace, additionalVars, isCloud, type,
Blockly.Msg.VARIABLE_ALREADY_EXISTS);
}
};
Expand Down Expand Up @@ -453,7 +459,7 @@ Blockly.Variables.validateBroadcastMessageName_ = function(name, workspace, opt_
};

/**
* Validate the given name as a scalar variable or list type.
* Validate the given name as a scalar variable or list type (or dictionary type in BBGE).
* This function is also responsible for any user facing error-handling.
* @param {string} name The name to validate
* @param {!Blockly.Workspace} workspace The workspace the name should be validated
Expand All @@ -462,13 +468,14 @@ Blockly.Variables.validateBroadcastMessageName_ = function(name, workspace, opt_
* for conflicts against.
* @param {boolean} isCloud Whether the variable is a cloud variable.
* @param {string} type The type to validate the variable as. This should be one of
* Blockly.SCALAR_VARIABLE_TYPE or Blockly.LIST_VARIABLE_TYPE.
* Blockly.SCALAR_VARIABLE_TYPE or Blockly.LIST_VARIABLE_TYPE
* (or Blockly.DICT_VARIABLE_TYPE in BBGE).
* @param {string} errorMsg The type-specific error message the user should see
* if a variable of the validated, given name and type already exists.
* @return {string} The validated name, or null if invalid.
* @private
*/
Blockly.Variables.validateScalarVarOrListName_ = function(name, workspace, additionalVars,
Blockly.Variables.validateScalarVarOrListOrDictName_ = function(name, workspace, additionalVars,
isCloud, type, errorMsg) {
// For scalar variables, we don't want leading or trailing white space
name = Blockly.Variables.trimName_(name);
Expand Down
Loading