Fix(boards): 修复所有板卡在使用函数块进行复制时一定概率出现块渲染异常

This commit is contained in:
王立帮
2025-02-25 12:04:24 +08:00
parent 08a2e18af9
commit 0033cff33a
42 changed files with 480 additions and 215 deletions

View File

@@ -164,6 +164,58 @@ export const procedures_defnoreturn = {
// Show or hide the statement input.
this.setStatements_(xmlElement.getAttribute('statements') !== 'false');
},
/**
* Returns the state of this block as a JSON serializable object.
*
* @returns The state of this block, eg the parameters and statements.
*/
saveExtraState: function () {
if (!this.argumentVarModels_.length && this.hasStatements_) {
return null;
}
const state = Object.create(null);
if (this.argumentVarModels_.length) {
state['params'] = [];
for (let i = 0; i < this.argumentVarModels_.length; i++) {
state['params'].push({
// We don't need to serialize the name, but just in case we decide
// to separate params from variables.
'name': this.argumentVarModels_[i].name,
'id': this.argumentVarModels_[i].getId(),
});
}
}
if (!this.hasStatements_) {
state['hasStatements'] = false;
}
return state;
},
/**
* Applies the given state to this block.
*
* @param state The state to apply to this block, eg the parameters
* and statements.
*/
loadExtraState: function (state) {
this.arguments_ = [];
this.argumentVarModels_ = [];
if (state['params']) {
for (let i = 0; i < state['params'].length; i++) {
const param = state['params'][i];
const variable = Variables.getOrCreateVariablePackage(
this.workspace,
param['id'],
param['name'],
'',
);
this.arguments_.push(variable.name);
this.argumentVarModels_.push(variable);
}
}
this.updateParams_();
Procedures.mutateCallers(this);
this.setStatements_(state['hasStatements'] !== false);
},
/**
* Populate the mutator's dialog with this block's components.
* @param {!Blockly.Workspace} workspace Mutator's workspace.
@@ -635,7 +687,7 @@ export const procedures_callnoreturn = {
/**
* Returns the name of the procedure this block calls.
* @return {string} Procedure name.
* @this Blockly.Block
* @this {Blockly.Block}
*/
getProcedureCall: function () {
// The NAME field is guaranteed to exist, null will never be returned.
@@ -646,82 +698,76 @@ export const procedures_callnoreturn = {
* If the name matches this block's procedure, rename it.
* @param {string} oldName Previous name of procedure.
* @param {string} newName Renamed procedure.
* @this Blockly.Block
* @this {Blockly.Block}
*/
renameProcedure: function (oldName, newName) {
if (Blockly.Names.equals(oldName, this.getProcedureCall())) {
this.setFieldValue(newName, 'NAME');
this.setTooltip(
(this.outputConnection ? Blockly.Msg.PROCEDURES_CALLRETURN_TOOLTIP :
Blockly.Msg.PROCEDURES_CALLNORETURN_TOOLTIP)
.replace('%1', newName));
var baseMsg = this.outputConnection ?
Blockly.Msg['PROCEDURES_CALLRETURN_TOOLTIP'] :
Blockly.Msg['PROCEDURES_CALLNORETURN_TOOLTIP'];
this.setTooltip(baseMsg.replace('%1', newName));
}
},
/**
* Notification that the procedure's parameters have changed.
* @param {!Array.<string>} paramNames New param names, e.g. ['x', 'y', 'z'].
* @param {!Array.<string>} paramIds IDs of params (consistent for each
* @param {!Array<string>} paramNames New param names, e.g. ['x', 'y', 'z'].
* @param {!Array<string>} paramIds IDs of params (consistent for each
* parameter through the life of a mutator, regardless of param renaming),
* e.g. ['piua', 'f8b_', 'oi.o'].
* @private
* @this Blockly.Block
* @this {Blockly.Block}
*/
setProcedureParameters_: function (paramNames, paramIds) {
// Data structures:
// this.arguments = ['x', 'y']
// Existing param names.
// this.quarkConnections_ {piua: null, f8b_: Blockly.Connection}
// this.quarkConnections_ {piua: null, f8b_: Connection}
// Look-up of paramIds to connections plugged into the call block.
// this.quarkIds_ = ['piua', 'f8b_']
// Existing param IDs.
// Note that quarkConnections_ may include IDs that no longer exist, but
// which might reappear if a param is reattached in the mutator.
var defBlock = Procedures.getDefinition(this.getProcedureCall(),
this.workspace);
const defBlock = Procedures.getDefinition(
this.getProcedureCall(),
this.workspace,
);
const mutatorIcon = defBlock && defBlock.getIcon(Blockly.icons.MutatorIcon.TYPE);
const mutatorOpen =
mutatorIcon && mutatorIcon.bubbleIsVisible();
const mutatorOpen = mutatorIcon && mutatorIcon.bubbleIsVisible();
if (!mutatorOpen) {
this.quarkConnections_ = {};
this.quarkIds_ = null;
} else {
// fix #6091 - this call could cause an error when outside if-else
// expanding block while mutating prevents another error (ancient fix)
this.setCollapsed(false);
}
if (!paramIds) {
// Reset the quarks (a mutator is about to open)
this.quarkIds_ = paramIds;
return;
}
if (paramNames.join('\n') == this.arguments_.join('\n')) {
// Test arguments (arrays of strings) for changes. '\n' is not a valid
// argument name character, so it is a valid delimiter here.
if (paramNames.join('\n') === this.arguments_.join('\n')) {
// No change.
this.quarkIds_ = paramIds;
return;
}
if (paramIds.length != paramNames.length) {
throw 'Error: paramNames and paramIds must be the same length.';
if (paramIds.length !== paramNames.length) {
throw Error('paramNames and paramIds must be the same length.');
}
this.setCollapsed(false);
if (!this.quarkIds_) {
// Initialize tracking for this block.
this.quarkConnections_ = {};
if (paramNames.join('\n') == this.arguments_.join('\n')) {
// No change to the parameters, allow quarkConnections_ to be
// populated with the existing connections.
this.quarkIds_ = paramIds;
} else {
this.quarkIds_ = [];
}
this.quarkIds_ = [];
}
// Switch off rendering while the block is rebuilt.
var savedRendered = this.rendered;
this.rendered = false;
// Update the quarkConnections_ with existing connections.
for (var i = 0; i < this.arguments_.length; i++) {
var input = this.getInput('ARG' + i);
for (let i = 0; i < this.arguments_.length; i++) {
const input = this.getInput('ARG' + i);
if (input) {
var connection = input.connection.targetConnection;
const connection = input?.connection?.targetConnection;
this.quarkConnections_[this.quarkIds_[i]] = connection;
if (mutatorOpen && connection &&
paramIds.indexOf(this.quarkIds_[i]) == -1) {
if (
mutatorOpen &&
connection &&
!paramIds.includes(this.quarkIds_[i])
) {
// This connection should no longer be attached to this block.
connection.disconnect();
connection.getSourceBlock().bumpNeighbours();
@@ -730,31 +776,35 @@ export const procedures_callnoreturn = {
}
// Rebuild the block's arguments.
this.arguments_ = [].concat(paramNames);
// And rebuild the argument model list.
this.argumentVarModels_ = [];
for (var i = 0; i < this.arguments_.length; i++) {
var variable = Blockly.Variables.getOrCreateVariablePackage(
this.workspace, null, this.arguments_[i], '');
this.argumentVarModels_.push(variable);
}
this.updateShape_();
this.quarkIds_ = paramIds;
// Reconnect any child blocks.
if (this.quarkIds_) {
for (var i = 0; i < this.arguments_.length; i++) {
var quarkId = this.quarkIds_[i];
for (let i = 0; i < this.arguments_.length; i++) {
const quarkId = this.quarkIds_[i]; // TODO(#6920)
if (quarkId in this.quarkConnections_) {
var connection = this.quarkConnections_[quarkId];
if (connection && !connection.reconnect(this, 'ARG' + i)) {
// TODO(#6920): investigate claimed circular initialisers.
const connection = this.quarkConnections_[quarkId];
if (!connection?.reconnect(this, 'ARG' + i)) {
// Block no longer exists or has been attached elsewhere.
delete this.quarkConnections_[quarkId];
}
}
}
}
// Restore rendering and show the changes.
this.rendered = savedRendered;
if (this.rendered) {
this.render();
}
},
/**
* Modify this block to have the correct number of arguments.
* @private
* @this Blockly.Block
* @this {Blockly.Block}
*/
updateShape_: function () {
for (var i = 0; i < this.arguments_.length; i++) {
@@ -764,8 +814,11 @@ export const procedures_callnoreturn = {
// The argument name field is deterministic based on the mutation,
// no need to fire a change event.
Blockly.Events.disable();
field.setValue(this.arguments_[i]);
Blockly.Events.enable();
try {
field.setValue(this.arguments_[i]);
} finally {
Blockly.Events.enable();
}
} else {
// Add new input.
field = new Blockly.FieldLabel(this.arguments_[i]);
@@ -785,7 +838,7 @@ export const procedures_callnoreturn = {
if (topRow) {
if (this.arguments_.length) {
if (!this.getField('WITH')) {
topRow.appendField(Blockly.Msg.PROCEDURES_CALL_BEFORE_PARAMS, 'WITH');
topRow.appendField(Blockly.Msg['PROCEDURES_CALL_BEFORE_PARAMS'], 'WITH');
topRow.init();
}
} else {
@@ -798,13 +851,13 @@ export const procedures_callnoreturn = {
/**
* Create XML to represent the (non-editable) name and arguments.
* @return {!Element} XML storage element.
* @this Blockly.Block
* @this {Blockly.Block}
*/
mutationToDom: function () {
var container = document.createElement('mutation');
var container = Blockly.utils.xml.createElement('mutation');
container.setAttribute('name', this.getProcedureCall());
for (var i = 0; i < this.arguments_.length; i++) {
var parameter = document.createElement('arg');
var parameter = Blockly.utils.xml.createElement('arg');
parameter.setAttribute('name', this.arguments_[i]);
container.appendChild(parameter);
}
@@ -813,15 +866,14 @@ export const procedures_callnoreturn = {
/**
* Parse XML to restore the (non-editable) name and parameters.
* @param {!Element} xmlElement XML storage element.
* @this Blockly.Block
* @this {Blockly.Block}
*/
domToMutation: function (xmlElement) {
var name = xmlElement.getAttribute('name');
this.renameProcedure(this.getProcedureCall(), name);
var args = [];
var paramIds = [];
for (var i = 0; xmlElement.childNodes[i]; i++) {
let childNode = xmlElement.childNodes[i];
for (var i = 0, childNode; (childNode = xmlElement.childNodes[i]); i++) {
if (childNode.nodeName.toLowerCase() == 'arg') {
args.push(childNode.getAttribute('name'));
paramIds.push(childNode.getAttribute('paramId'));
@@ -830,10 +882,39 @@ export const procedures_callnoreturn = {
this.setProcedureParameters_(args, paramIds);
},
/**
* Return all variables referenced by this block.
* @return {!Array<string>} List of variable names.
* @this {Blockly.Block}
*/
* Returns the state of this block as a JSON serializable object.
*
* @returns The state of this block, ie the params and procedure name.
*/
saveExtraState: function () {
const state = Object.create(null);
state['name'] = this.getProcedureCall();
if (this.arguments_.length) {
state['params'] = this.arguments_;
}
return state;
},
/**
* Applies the given state to this block.
*
* @param state The state to apply to this block, ie the params and
* procedure name.
*/
loadExtraState: function (state) {
this.renameProcedure(this.getProcedureCall(), state['name']);
const params = state['params'];
if (params) {
const ids = [];
ids.length = params.length;
ids.fill(null); // TODO(#6920)
this.setProcedureParameters_(params, ids);
}
},
/**
* Return all variables referenced by this block.
* @return {!Array<string>} List of variable names.
* @this {Blockly.Block}
*/
getVars: function () {
return this.arguments_;
},
@@ -846,11 +927,11 @@ export const procedures_callnoreturn = {
return this.argumentVarModels_;
},
/**
* Procedure calls cannot exist without the corresponding procedure
* definition. Enforce this link whenever an event is fired.
* @param {!Blockly.Events.Abstract} event Change event.
* @this {Blockly.Block}
*/
* Procedure calls cannot exist without the corresponding procedure
* definition. Enforce this link whenever an event is fired.
* @param {!Blockly.Events.Abstract} event Change event.
* @this {Blockly.Block}
*/
onchange: function (event) {
if (!this.workspace || this.workspace.isFlyout) {
// Block is deleted or is in a flyout.
@@ -960,35 +1041,28 @@ export const procedures_callnoreturn = {
}
}
},
/**
* Notification that a variable is renaming.
* If the name matches one of this block's variables, rename it.
* @param {string} oldName Previous name of variable.
* @param {string} newName Renamed variable.
* @this Blockly.Block
*/
renameVar: function (oldName, newName) {
for (var i = 0; i < this.arguments_.length; i++) {
if (Blockly.Names.equals(oldName, this.arguments_[i])) {
this.arguments_[i] = newName;
this.getField('ARGNAME' + i)
.setValue(newName);
}
}
},
/**
* Add menu option to find the definition block for this call.
* @param {!Array} options List of menu options to add to.
* @this Blockly.Block
* @this {Blockly.Block}
*/
customContextMenu: function (options) {
if (!this.workspace.isMovable()) {
// If we center on the block and the workspace isn't movable we could
// loose blocks at the edges of the workspace.
return;
}
var option = { enabled: true };
option.text = Blockly.Msg.PROCEDURES_HIGHLIGHT_DEF;
option.text = Blockly.Msg['PROCEDURES_HIGHLIGHT_DEF'];
var name = this.getProcedureCall();
var workspace = this.workspace;
option.callback = function () {
var def = Procedures.getDefinition(name, workspace);
def && def.select();
if (def) {
workspace.centerOnBlock(def.id);
def.select();
}
};
options.push(option);
},