Initial commit of 001code-html Scratch frontend project.
Includes scratch-gui, scratch-vm, scratch-blocks, scratch-render, scratch-l10n, and deployment config. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
385
scratch-blocks/core/field_variable.js
Normal file
385
scratch-blocks/core/field_variable.js
Normal file
@@ -0,0 +1,385 @@
|
||||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2012 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 Variable input field.
|
||||
* @author fraser@google.com (Neil Fraser)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.FieldVariable');
|
||||
|
||||
goog.require('Blockly.FieldDropdown');
|
||||
goog.require('Blockly.Msg');
|
||||
goog.require('Blockly.VariableModel');
|
||||
goog.require('Blockly.Variables');
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.string');
|
||||
|
||||
|
||||
/**
|
||||
* Class for a variable's dropdown field.
|
||||
* @param {?string} varname The default name for the variable. If null,
|
||||
* a unique variable name will be generated.
|
||||
* @param {Function=} opt_validator A function that is executed when a new
|
||||
* option is selected. Its sole argument is the new option value.
|
||||
* @param {Array.<string>} opt_variableTypes A list of the types of variables to
|
||||
* include in the dropdown.
|
||||
* @extends {Blockly.FieldDropdown}
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.FieldVariable = function(varname, opt_validator, opt_variableTypes) {
|
||||
// The FieldDropdown constructor would call setValue, which might create a
|
||||
// spurious variable. Just do the relevant parts of the constructor.
|
||||
this.menuGenerator_ = Blockly.FieldVariable.dropdownCreate;
|
||||
this.size_ = new goog.math.Size(Blockly.BlockSvg.FIELD_WIDTH,
|
||||
Blockly.BlockSvg.FIELD_HEIGHT);
|
||||
this.setValidator(opt_validator);
|
||||
// TODO (blockly #1499): Add opt_default_type to match default value.
|
||||
// If not set, ''.
|
||||
this.defaultVariableName = (varname || '');
|
||||
var hasSingleVarType = opt_variableTypes && (opt_variableTypes.length == 1);
|
||||
this.defaultType_ = hasSingleVarType ? opt_variableTypes[0] : '';
|
||||
this.variableTypes = opt_variableTypes;
|
||||
this.addArgType('variable');
|
||||
|
||||
this.value_ = null;
|
||||
};
|
||||
goog.inherits(Blockly.FieldVariable, Blockly.FieldDropdown);
|
||||
|
||||
/**
|
||||
* Construct a FieldVariable from a JSON arg object,
|
||||
* dereferencing any string table references.
|
||||
* @param {!Object} options A JSON object with options (variable,
|
||||
* variableTypes, and defaultType).
|
||||
* @returns {!Blockly.FieldVariable} The new field instance.
|
||||
* @package
|
||||
* @nocollapse
|
||||
*/
|
||||
Blockly.FieldVariable.fromJson = function(options) {
|
||||
var varname = Blockly.utils.replaceMessageReferences(options['variable']);
|
||||
var variableTypes = options['variableTypes'];
|
||||
return new Blockly.FieldVariable(varname, null, variableTypes);
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize everything needed to render this field. This includes making sure
|
||||
* that the field's value is valid.
|
||||
* @public
|
||||
*/
|
||||
Blockly.FieldVariable.prototype.init = function() {
|
||||
if (this.fieldGroup_) {
|
||||
// Dropdown has already been initialized once.
|
||||
return;
|
||||
}
|
||||
Blockly.FieldVariable.superClass_.init.call(this);
|
||||
|
||||
// TODO (blockly #1010): Change from init/initModel to initView/initModel
|
||||
this.initModel();
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize the model for this field if it has not already been initialized.
|
||||
* If the value has not been set to a variable by the first render, we make up a
|
||||
* variable rather than let the value be invalid.
|
||||
* @package
|
||||
*/
|
||||
Blockly.FieldVariable.prototype.initModel = function() {
|
||||
if (this.variable_) {
|
||||
return; // Initialization already happened.
|
||||
}
|
||||
this.workspace_ = this.sourceBlock_.workspace;
|
||||
// Initialize this field if it's in a broadcast block in the flyout
|
||||
var variable = this.initFlyoutBroadcast_(this.workspace_);
|
||||
if (!variable) {
|
||||
var variable = Blockly.Variables.getOrCreateVariablePackage(
|
||||
this.workspace_, null, this.defaultVariableName, this.defaultType_);
|
||||
}
|
||||
// Don't fire a change event for this setValue. It would have null as the
|
||||
// old value, which is not valid.
|
||||
Blockly.Events.disable();
|
||||
try {
|
||||
this.setValue(variable.getId());
|
||||
} finally {
|
||||
Blockly.Events.enable();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize broadcast blocks in the flyout.
|
||||
* Implicit deletion of broadcast messages from the scratch vm may cause
|
||||
* broadcast blocks in the flyout to change which variable they display as the
|
||||
* selected option when the workspace is refreshed.
|
||||
* Re-sort the broadcast messages by name, and set the field value to the id
|
||||
* of the variable that comes first in sorted order.
|
||||
* @param {!Blockly.Workspace} workspace The flyout workspace containing the
|
||||
* broadcast block.
|
||||
* @return {string} The variable of type 'broadcast_msg' that comes
|
||||
* first in sorted order.
|
||||
*/
|
||||
Blockly.FieldVariable.prototype.initFlyoutBroadcast_ = function(workspace) {
|
||||
// Using shorter name for this constant
|
||||
var broadcastMsgType = Blockly.BROADCAST_MESSAGE_VARIABLE_TYPE;
|
||||
var broadcastVars = workspace.getVariablesOfType(broadcastMsgType);
|
||||
if(workspace.isFlyout && this.defaultType_ == broadcastMsgType &&
|
||||
broadcastVars.length != 0) {
|
||||
broadcastVars.sort(Blockly.VariableModel.compareByName);
|
||||
return broadcastVars[0];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Dispose of this field.
|
||||
* @public
|
||||
*/
|
||||
Blockly.FieldVariable.dispose = function() {
|
||||
Blockly.FieldVariable.superClass_.dispose.call(this);
|
||||
this.workspace_ = null;
|
||||
this.variableMap_ = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Attach this field to a block.
|
||||
* @param {!Blockly.Block} block The block containing this field.
|
||||
*/
|
||||
Blockly.FieldVariable.prototype.setSourceBlock = function(block) {
|
||||
goog.asserts.assert(!block.isShadow(),
|
||||
'Variable fields are not allowed to exist on shadow blocks.');
|
||||
Blockly.FieldVariable.superClass_.setSourceBlock.call(this, block);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the variable's ID.
|
||||
* @return {string} Current variable's ID.
|
||||
*/
|
||||
Blockly.FieldVariable.prototype.getValue = function() {
|
||||
return this.variable_ ? this.variable_.getId() : null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the text from this field, which is the selected variable's name.
|
||||
* @return {string} The selected variable's name, or the empty string if no
|
||||
* variable is selected.
|
||||
*/
|
||||
Blockly.FieldVariable.prototype.getText = function() {
|
||||
return this.variable_ ? this.variable_.name : '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the variable model for the selected variable.
|
||||
* Not guaranteed to be in the variable map on the workspace (e.g. if accessed
|
||||
* after the variable has been deleted).
|
||||
* @return {?Blockly.VariableModel} the selected variable, or null if none was
|
||||
* selected.
|
||||
* @package
|
||||
*/
|
||||
Blockly.FieldVariable.prototype.getVariable = function() {
|
||||
return this.variable_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the variable ID.
|
||||
* @param {string} id New variable ID, which must reference an existing
|
||||
* variable.
|
||||
*/
|
||||
Blockly.FieldVariable.prototype.setValue = function(id) {
|
||||
var workspace = this.sourceBlock_.workspace;
|
||||
var variable = Blockly.Variables.getVariable(workspace, id);
|
||||
|
||||
if (!variable) {
|
||||
throw new Error('Variable id doesn\'t point to a real variable! ID was ' +
|
||||
id);
|
||||
}
|
||||
// Type checks!
|
||||
var type = variable.type;
|
||||
if (!this.typeIsAllowed_(type)) {
|
||||
throw new Error('Variable type doesn\'t match this field! Type was ' +
|
||||
type);
|
||||
}
|
||||
if (this.sourceBlock_ && Blockly.Events.isEnabled()) {
|
||||
var oldValue = this.variable_ ? this.variable_.getId() : null;
|
||||
Blockly.Events.fire(new Blockly.Events.BlockChange(
|
||||
this.sourceBlock_, 'field', this.name, oldValue, id));
|
||||
}
|
||||
this.variable_ = variable;
|
||||
this.value_ = id;
|
||||
this.setText(variable.name);
|
||||
};
|
||||
|
||||
/**
|
||||
* Check whether the given variable type is allowed on this field.
|
||||
* @param {string} type The type to check.
|
||||
* @return {boolean} True if the type is in the list of allowed types.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldVariable.prototype.typeIsAllowed_ = function(type) {
|
||||
var typeList = this.getVariableTypes_();
|
||||
if (!typeList) {
|
||||
return true; // If it's null, all types are valid.
|
||||
}
|
||||
for (var i = 0; i < typeList.length; i++) {
|
||||
if (type == typeList[i]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return a list of variable types to include in the dropdown.
|
||||
* @return {!Array.<string>} Array of variable types.
|
||||
* @throws {Error} if variableTypes is an empty array.
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldVariable.prototype.getVariableTypes_ = function() {
|
||||
// TODO (#1513): Try to avoid calling this every time the field is edited.
|
||||
var variableTypes = this.variableTypes;
|
||||
if (variableTypes === null) {
|
||||
// If variableTypes is null, return all variable types.
|
||||
if (this.sourceBlock_) {
|
||||
var workspace = this.sourceBlock_.workspace;
|
||||
return workspace.getVariableTypes();
|
||||
}
|
||||
}
|
||||
variableTypes = variableTypes || [''];
|
||||
if (variableTypes.length == 0) {
|
||||
// Throw an error if variableTypes is an empty list.
|
||||
var name = this.getText();
|
||||
throw new Error('\'variableTypes\' of field variable ' +
|
||||
name + ' was an empty list');
|
||||
}
|
||||
return variableTypes;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return a sorted list of variable names for variable dropdown menus.
|
||||
* Include a special option at the end for creating a new variable name.
|
||||
* @return {!Array.<string>} Array of variable names.
|
||||
* @this {Blockly.FieldVariable}
|
||||
*/
|
||||
Blockly.FieldVariable.dropdownCreate = function() {
|
||||
if (!this.variable_) {
|
||||
throw new Error('Tried to call dropdownCreate on a variable field with no' +
|
||||
' variable selected.');
|
||||
}
|
||||
var variableModelList = [];
|
||||
var name = this.getText();
|
||||
var workspace = null;
|
||||
if (this.sourceBlock_) {
|
||||
workspace = this.sourceBlock_.workspace;
|
||||
}
|
||||
if (workspace) {
|
||||
var variableTypes = this.getVariableTypes_();
|
||||
var variableModelList = [];
|
||||
// Get a copy of the list, so that adding rename and new variable options
|
||||
// doesn't modify the workspace's list.
|
||||
for (var i = 0; i < variableTypes.length; i++) {
|
||||
var variableType = variableTypes[i];
|
||||
var variables = workspace.getVariablesOfType(variableType);
|
||||
variableModelList = variableModelList.concat(variables);
|
||||
|
||||
var potentialVarMap = workspace.getPotentialVariableMap();
|
||||
if (potentialVarMap) {
|
||||
var potentialVars = potentialVarMap.getVariablesOfType(variableType);
|
||||
variableModelList = variableModelList.concat(potentialVars);
|
||||
}
|
||||
}
|
||||
}
|
||||
variableModelList.sort(Blockly.VariableModel.compareByName);
|
||||
|
||||
var options = [];
|
||||
for (var i = 0; i < variableModelList.length; i++) {
|
||||
// Set the uuid as the internal representation of the variable.
|
||||
options[i] = [variableModelList[i].name, variableModelList[i].getId()];
|
||||
}
|
||||
if (this.defaultType_ == Blockly.BROADCAST_MESSAGE_VARIABLE_TYPE) {
|
||||
options.unshift(
|
||||
[Blockly.Msg.NEW_BROADCAST_MESSAGE, Blockly.NEW_BROADCAST_MESSAGE_ID]);
|
||||
} else {
|
||||
// Scalar variables and lists have the same backing action, but the option
|
||||
// text is different.
|
||||
if (this.defaultType_ == Blockly.LIST_VARIABLE_TYPE) {
|
||||
var renameText = Blockly.Msg.RENAME_LIST;
|
||||
var deleteText = Blockly.Msg.DELETE_LIST;
|
||||
} else {
|
||||
var renameText = Blockly.Msg.RENAME_VARIABLE;
|
||||
var deleteText = Blockly.Msg.DELETE_VARIABLE;
|
||||
}
|
||||
options.push([renameText, Blockly.RENAME_VARIABLE_ID]);
|
||||
if (deleteText) {
|
||||
options.push(
|
||||
[
|
||||
deleteText.replace('%1', name),
|
||||
Blockly.DELETE_VARIABLE_ID
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return options;
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle the selection of an item in the variable dropdown menu.
|
||||
* Special case the 'Rename variable...', 'Delete variable...',
|
||||
* and 'New message...' options.
|
||||
* In the rename case, prompt the user for a new name.
|
||||
* @param {!goog.ui.Menu} menu The Menu component clicked.
|
||||
* @param {!goog.ui.MenuItem} menuItem The MenuItem selected within menu.
|
||||
*/
|
||||
Blockly.FieldVariable.prototype.onItemSelected = function(menu, menuItem) {
|
||||
var id = menuItem.getValue();
|
||||
if (this.sourceBlock_ && this.sourceBlock_.workspace) {
|
||||
var workspace = this.sourceBlock_.workspace;
|
||||
if (id == Blockly.RENAME_VARIABLE_ID) {
|
||||
// Rename variable.
|
||||
Blockly.Variables.renameVariable(workspace, this.variable_);
|
||||
return;
|
||||
} else if (id == Blockly.DELETE_VARIABLE_ID) {
|
||||
// Delete variable.
|
||||
workspace.deleteVariableById(this.variable_.getId());
|
||||
return;
|
||||
} else if (id == Blockly.NEW_BROADCAST_MESSAGE_ID) {
|
||||
var thisField = this;
|
||||
var updateField = function(varId) {
|
||||
if (varId) {
|
||||
thisField.setValue(varId);
|
||||
}
|
||||
};
|
||||
Blockly.Variables.createVariable(workspace, updateField,
|
||||
Blockly.BROADCAST_MESSAGE_VARIABLE_TYPE);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO (blockly #1529): Call any validation function, and allow it to override.
|
||||
}
|
||||
this.setValue(id);
|
||||
};
|
||||
|
||||
/**
|
||||
* Overrides referencesVariables(), indicating this field refers to a variable.
|
||||
* @return {boolean} True.
|
||||
* @package
|
||||
* @override
|
||||
*/
|
||||
Blockly.FieldVariable.prototype.referencesVariables = function() {
|
||||
return true;
|
||||
};
|
||||
|
||||
Blockly.Field.register('field_variable', Blockly.FieldVariable);
|
||||
Reference in New Issue
Block a user