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

chore: Use JSON objects for context menu callbackFactory #7382

Merged
merged 3 commits into from
Sep 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 5 additions & 7 deletions blocks/loops.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ import type {
} from '../core/contextmenu_registry.js';
import * as Events from '../core/events/events.js';
import * as Extensions from '../core/extensions.js';
import * as Variables from '../core/variables.js';
import * as xmlUtils from '../core/utils/xml.js';
import {Msg} from '../core/msg.js';
import {
createBlockDefinitionsFromJsonArray,
Expand Down Expand Up @@ -273,15 +271,15 @@ const CUSTOM_CONTEXT_MENU_CREATE_VARIABLES_GET_MIXIN = {
const variable = varField.getVariable()!;
const varName = variable.name;
if (!this.isCollapsed() && varName !== null) {
const xmlField = Variables.generateVariableFieldDom(variable);
const xmlBlock = xmlUtils.createElement('block');
xmlBlock.setAttribute('type', 'variables_get');
xmlBlock.appendChild(xmlField);
const getVarBlockState = {
type: 'variables_get',
fields: {VAR: varField.saveState(true)},
};

options.push({
enabled: true,
text: Msg['VARIABLES_SET_CREATE_GET'].replace('%1', varName),
callback: ContextMenu.callbackFactory(this, xmlBlock),
callback: ContextMenu.callbackFactory(this, getVarBlockState),
});
}
},
Expand Down
28 changes: 12 additions & 16 deletions blocks/procedures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -455,34 +455,30 @@ const PROCEDURE_DEF_COMMON = {
}
// Add option to create caller.
const name = this.getFieldValue('NAME');
const xmlMutation = xmlUtils.createElement('mutation');
xmlMutation.setAttribute('name', name);
for (let i = 0; i < this.arguments_.length; i++) {
const xmlArg = xmlUtils.createElement('arg');
xmlArg.setAttribute('name', this.arguments_[i]);
xmlMutation.appendChild(xmlArg);
}
const xmlBlock = xmlUtils.createElement('block');
xmlBlock.setAttribute('type', (this as AnyDuringMigration).callType_);
xmlBlock.appendChild(xmlMutation);
const callProcedureBlockState = {
type: (this as AnyDuringMigration).callType_,
extraState: {name: name, params: this.arguments_},
};
options.push({
enabled: true,
text: Msg['PROCEDURES_CREATE_DO'].replace('%1', name),
callback: ContextMenu.callbackFactory(this, xmlBlock),
callback: ContextMenu.callbackFactory(this, callProcedureBlockState),
});

// Add options to create getters for each parameter.
if (!this.isCollapsed()) {
for (let i = 0; i < this.argumentVarModels_.length; i++) {
const argVar = this.argumentVarModels_[i];
const argXmlField = Variables.generateVariableFieldDom(argVar);
const argXmlBlock = xmlUtils.createElement('block');
argXmlBlock.setAttribute('type', 'variables_get');
argXmlBlock.appendChild(argXmlField);
const getVarBlockState = {
type: 'variables_get',
fields: {
VAR: {name: argVar.name, id: argVar.getId(), type: argVar.type},
},
};
options.push({
enabled: true,
text: Msg['VARIABLES_SET_CREATE_GET'].replace('%1', argVar.name),
callback: ContextMenu.callbackFactory(this, argXmlBlock),
callback: ContextMenu.callbackFactory(this, getVarBlockState),
});
}
}
Expand Down
17 changes: 7 additions & 10 deletions blocks/variables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ goog.declareModuleId('Blockly.libraryBlocks.variables');
import * as ContextMenu from '../core/contextmenu.js';
import * as Extensions from '../core/extensions.js';
import * as Variables from '../core/variables.js';
import * as xmlUtils from '../core/utils/xml.js';
import type {Block} from '../core/block.js';
import type {
ContextMenuOption,
Expand Down Expand Up @@ -103,18 +102,16 @@ const CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN = {
contextMenuMsg = Msg['VARIABLES_SET_CREATE_GET'];
}

const name = this.getField('VAR')!.getText();
const xmlField = xmlUtils.createElement('field');
xmlField.setAttribute('name', 'VAR');
xmlField.appendChild(xmlUtils.createTextNode(name));
const xmlBlock = xmlUtils.createElement('block');
xmlBlock.setAttribute('type', oppositeType);
xmlBlock.appendChild(xmlField);
const varField = this.getField('VAR')!;
const newVarBlockState = {
type: oppositeType,
fields: {VAR: varField.saveState(true)},
};

options.push({
enabled: this.workspace.remainingCapacity() > 0,
text: contextMenuMsg.replace('%1', name),
callback: ContextMenu.callbackFactory(this, xmlBlock),
text: contextMenuMsg.replace('%1', varField.getText()),
callback: ContextMenu.callbackFactory(this, newVarBlockState),
});
// Getter blocks have the option to rename or delete that variable.
} else {
Expand Down
21 changes: 7 additions & 14 deletions blocks/variables_dynamic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ goog.declareModuleId('Blockly.libraryBlocks.variablesDynamic');
import * as ContextMenu from '../core/contextmenu.js';
import * as Extensions from '../core/extensions.js';
import * as Variables from '../core/variables.js';
import * as xml from '../core/utils/xml.js';
import {Abstract as AbstractEvent} from '../core/events/events_abstract.js';
import type {Block} from '../core/block.js';
import type {
Expand Down Expand Up @@ -96,9 +95,6 @@ const CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN = {
if (!this.isInFlyout) {
let oppositeType;
let contextMenuMsg;
const id = this.getFieldValue('VAR');
const variableModel = this.workspace.getVariableById(id);
const varType = variableModel!.type;
if (this.type === 'variables_get_dynamic') {
oppositeType = 'variables_set_dynamic';
contextMenuMsg = Msg['VARIABLES_GET_CREATE_SET'];
Expand All @@ -107,19 +103,16 @@ const CUSTOM_CONTEXT_MENU_VARIABLE_GETTER_SETTER_MIXIN = {
contextMenuMsg = Msg['VARIABLES_SET_CREATE_GET'];
}

const name = this.getField('VAR')!.getText();
const xmlField = xml.createElement('field');
xmlField.setAttribute('name', 'VAR');
xmlField.setAttribute('variabletype', varType);
xmlField.appendChild(xml.createTextNode(name));
const xmlBlock = xml.createElement('block');
xmlBlock.setAttribute('type', oppositeType);
xmlBlock.appendChild(xmlField);
const varField = this.getField('VAR')!;
const newVarBlockState = {
type: oppositeType,
fields: {VAR: varField.saveState(true)},
};

options.push({
enabled: this.workspace.remainingCapacity() > 0,
text: contextMenuMsg.replace('%1', name),
callback: ContextMenu.callbackFactory(this, xmlBlock),
text: contextMenuMsg.replace('%1', varField.getText()),
callback: ContextMenu.callbackFactory(this, newVarBlockState),
});
} else {
if (
Expand Down
22 changes: 17 additions & 5 deletions core/contextmenu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {Msg} from './msg.js';
import * as aria from './utils/aria.js';
import {Coordinate} from './utils/coordinate.js';
import {Rect} from './utils/rect.js';
import * as serializationBlocks from './serialization/blocks.js';
import * as svgMath from './utils/svg_math.js';
import * as WidgetDiv from './widgetdiv.js';
import {WorkspaceCommentSvg} from './workspace_comment_svg.js';
Expand Down Expand Up @@ -224,18 +225,28 @@ export function dispose() {

/**
* Create a callback function that creates and configures a block,
* then places the new block next to the original.
* then places the new block next to the original and returns it.
*
* @param block Original block.
* @param xml XML representation of new block.
* @param state XML or JSON object representation of the new block.
* @returns Function that creates a block.
*/
export function callbackFactory(block: Block, xml: Element): () => void {
export function callbackFactory(
block: Block,
state: Element | serializationBlocks.State,
): () => BlockSvg {
return () => {
eventUtils.disable();
let newBlock;
let newBlock: BlockSvg;
try {
newBlock = Xml.domToBlockInternal(xml, block.workspace!) as BlockSvg;
if (state instanceof Element) {
newBlock = Xml.domToBlockInternal(state, block.workspace!) as BlockSvg;
} else {
newBlock = serializationBlocks.appendInternal(
state,
block.workspace,
) as BlockSvg;
}
// Move the new block next to the old block.
const xy = block.getRelativeToSurfaceXY();
if (block.RTL) {
Expand All @@ -252,6 +263,7 @@ export function callbackFactory(block: Block, xml: Element): () => void {
eventUtils.fire(new (eventUtils.get(eventUtils.BLOCK_CREATE))(newBlock));
}
newBlock.select();
return newBlock;
};
}

Expand Down
70 changes: 70 additions & 0 deletions tests/mocha/contextmenu_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/**
* @license
* Copyright 2023 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import {
sharedTestSetup,
sharedTestTeardown,
} from './test_helpers/setup_teardown.js';

import {callbackFactory} from '../../build/src/core/contextmenu.js';
import * as xmlUtils from '../../build/src/core/utils/xml.js';
import * as Variables from '../../build/src/core/variables.js';

suite('Context Menu', function () {
setup(function () {
sharedTestSetup.call(this);

// Creates a WorkspaceSVG
const toolbox = document.getElementById('toolbox-categories');
this.workspace = Blockly.inject('blocklyDiv', {toolbox: toolbox});
});

teardown(function () {
sharedTestTeardown.call(this);
});

suite('Callback Factory', function () {
setup(function () {
this.forLoopBlock = this.workspace.newBlock('controls_for');
});

test('callback with xml state creates block', function () {
const xmlField = Variables.generateVariableFieldDom(
this.forLoopBlock.getField('VAR').getVariable(),
);
const xmlBlock = xmlUtils.createElement('block');
xmlBlock.setAttribute('type', 'variables_get');
xmlBlock.appendChild(xmlField);

const callback = callbackFactory(this.forLoopBlock, xmlBlock);
const getVarBlock = callback();

chai.assert.equal(getVarBlock.type, 'variables_get');
chai.assert.equal(getVarBlock.workspace, this.forLoopBlock.workspace);
chai.assert.equal(
getVarBlock.getField('VAR').getVariable().getId(),
this.forLoopBlock.getField('VAR').getVariable().getId(),
);
});

test('callback with json state creates block', function () {
const jsonState = {
type: 'variables_get',
fields: {VAR: this.forLoopBlock.getField('VAR').saveState(true)},
};

const callback = callbackFactory(this.forLoopBlock, jsonState);
const getVarBlock = callback();

chai.assert.equal(getVarBlock.type, 'variables_get');
chai.assert.equal(getVarBlock.workspace, this.forLoopBlock.workspace);
chai.assert.equal(
getVarBlock.getField('VAR').getVariable().getId(),
this.forLoopBlock.getField('VAR').getVariable().getId(),
);
});
});
});
1 change: 1 addition & 0 deletions tests/mocha/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
import './connection_db_test.js';
import './connection_test.js';
import './contextmenu_items_test.js';
import './contextmenu_test.js';
import './cursor_test.js';
import './dropdowndiv_test.js';
import './event_test.js';
Expand Down