mirror of
https://github.com/google/blockly.git
synced 2026-01-07 00:50:27 +01:00
Context menu registry, initial version. (#3930)
* initial version of registry for context menu options * Registers workspace-level context menu options
This commit is contained in:
committed by
GitHub
parent
02d5f6e4e9
commit
5e55d89d3b
@@ -40,6 +40,8 @@ goog.addDependency('../../core/connection.js', ['Blockly.Connection'], ['Blockly
|
||||
goog.addDependency('../../core/connection_db.js', ['Blockly.ConnectionDB'], ['Blockly.RenderedConnection'], {});
|
||||
goog.addDependency('../../core/constants.js', ['Blockly.constants'], [], {});
|
||||
goog.addDependency('../../core/contextmenu.js', ['Blockly.ContextMenu'], ['Blockly.Events', 'Blockly.Events.BlockCreate', 'Blockly.Menu', 'Blockly.MenuItem', 'Blockly.Msg', 'Blockly.Xml', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.Rect', 'Blockly.utils.dom', 'Blockly.utils.userAgent'], {});
|
||||
goog.addDependency('../../core/contextmenu_items.js', ['Blockly.ContextMenuItems'], [], {});
|
||||
goog.addDependency('../../core/contextmenu_registry.js', ['Blockly.ContextMenuRegistry'], ['Blockly.ContextMenuItems'], {'lang': 'es5'});
|
||||
goog.addDependency('../../core/css.js', ['Blockly.Css'], [], {'lang': 'es5'});
|
||||
goog.addDependency('../../core/dropdowndiv.js', ['Blockly.DropDownDiv'], ['Blockly.utils.dom', 'Blockly.utils.math', 'Blockly.utils.style'], {});
|
||||
goog.addDependency('../../core/events.js', ['Blockly.Events'], ['Blockly.utils'], {});
|
||||
@@ -188,7 +190,7 @@ goog.addDependency('../../core/workspace_comment_svg.js', ['Blockly.WorkspaceCom
|
||||
goog.addDependency('../../core/workspace_drag_surface_svg.js', ['Blockly.WorkspaceDragSurfaceSvg'], ['Blockly.utils', 'Blockly.utils.dom'], {});
|
||||
goog.addDependency('../../core/workspace_dragger.js', ['Blockly.WorkspaceDragger'], ['Blockly.utils.Coordinate'], {});
|
||||
goog.addDependency('../../core/workspace_events.js', ['Blockly.Events.FinishedLoading'], ['Blockly.Events', 'Blockly.Events.Ui', 'Blockly.utils.object'], {'lang': 'es5'});
|
||||
goog.addDependency('../../core/workspace_svg.js', ['Blockly.WorkspaceSvg'], ['Blockly.BlockSvg', 'Blockly.ConnectionDB', 'Blockly.Events', 'Blockly.Events.BlockCreate', 'Blockly.Gesture', 'Blockly.Grid', 'Blockly.MarkerManager', 'Blockly.Msg', 'Blockly.Options', 'Blockly.ThemeManager', 'Blockly.Themes.Classic', 'Blockly.TouchGesture', 'Blockly.Workspace', 'Blockly.WorkspaceAudio', 'Blockly.WorkspaceDragSurfaceSvg', 'Blockly.Xml', 'Blockly.blockRendering', 'Blockly.constants', 'Blockly.navigation', 'Blockly.registry', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.Metrics', 'Blockly.utils.Rect', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.toolbox'], {});
|
||||
goog.addDependency('../../core/workspace_svg.js', ['Blockly.WorkspaceSvg'], ['Blockly.BlockSvg', 'Blockly.ConnectionDB', 'Blockly.ContextMenuRegistry', 'Blockly.Events', 'Blockly.Events.BlockCreate', 'Blockly.Gesture', 'Blockly.Grid', 'Blockly.MarkerManager', 'Blockly.Msg', 'Blockly.Options', 'Blockly.ThemeManager', 'Blockly.Themes.Classic', 'Blockly.TouchGesture', 'Blockly.Workspace', 'Blockly.WorkspaceAudio', 'Blockly.WorkspaceDragSurfaceSvg', 'Blockly.Xml', 'Blockly.blockRendering', 'Blockly.constants', 'Blockly.navigation', 'Blockly.registry', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.Metrics', 'Blockly.utils.Rect', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.toolbox'], {});
|
||||
goog.addDependency('../../core/ws_comment_events.js', ['Blockly.Events.CommentBase', 'Blockly.Events.CommentChange', 'Blockly.Events.CommentCreate', 'Blockly.Events.CommentDelete', 'Blockly.Events.CommentMove'], ['Blockly.Events', 'Blockly.Events.Abstract', 'Blockly.utils.Coordinate', 'Blockly.utils.object', 'Blockly.utils.xml'], {});
|
||||
goog.addDependency('../../core/xml.js', ['Blockly.Xml'], ['Blockly.Events', 'Blockly.Events.BlockCreate', 'Blockly.Events.FinishedLoading', 'Blockly.Events.VarCreate', 'Blockly.utils', 'Blockly.utils.dom', 'Blockly.utils.global', 'Blockly.utils.xml'], {});
|
||||
goog.addDependency('../../core/zoom_controls.js', ['Blockly.ZoomControls'], ['Blockly.Css', 'Blockly.Scrollbar', 'Blockly.Touch', 'Blockly.utils.dom'], {'lang': 'es5'});
|
||||
|
||||
@@ -89,7 +89,7 @@ Blockly.ContextMenu.populate_ = function(options, rtl) {
|
||||
var actionHandler = function(_menuItem) {
|
||||
var option = this;
|
||||
Blockly.ContextMenu.hide();
|
||||
option.callback();
|
||||
option.callback(option.scope);
|
||||
};
|
||||
menuItem.onAction(actionHandler, option);
|
||||
}
|
||||
|
||||
281
core/contextmenu_items.js
Normal file
281
core/contextmenu_items.js
Normal file
@@ -0,0 +1,281 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2020 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Registers default context menu items.
|
||||
* @author maribethb@google.com (Maribeth Bottorff)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @name Blockly.ContextMenuItems
|
||||
* @namespace
|
||||
*/
|
||||
goog.provide('Blockly.ContextMenuItems');
|
||||
|
||||
goog.requireType('Blockly.BlockSvg');
|
||||
|
||||
/** Option to undo previous action. */
|
||||
Blockly.ContextMenuItems.registerUndo = function() {
|
||||
var undoOption = {};
|
||||
undoOption.displayText = function() {
|
||||
return Blockly.Msg['UNDO'];
|
||||
};
|
||||
undoOption.preconditionFn = function(scope) {
|
||||
if (scope.workspace.undoStack_.length > 0) {
|
||||
return 'enabled';
|
||||
}
|
||||
return 'disabled';
|
||||
};
|
||||
undoOption.callback = function(scope) {
|
||||
scope.workspace.undo(false);
|
||||
};
|
||||
undoOption.scopeType = Blockly.ContextMenuRegistry.ScopeType.WORKSPACE;
|
||||
undoOption.id = 'undoWorkspace';
|
||||
undoOption.weight = 0;
|
||||
Blockly.ContextMenuRegistry.registry.register(undoOption);
|
||||
};
|
||||
|
||||
/** Option to redo previous action. */
|
||||
Blockly.ContextMenuItems.registerRedo = function() {
|
||||
var redoOption = {};
|
||||
redoOption.displayText = function() { return Blockly.Msg['REDO']; };
|
||||
redoOption.preconditionFn = function(scope) {
|
||||
if (scope.workspace.redoStack_.length > 0) {
|
||||
return 'enabled';
|
||||
}
|
||||
return 'disabled';
|
||||
};
|
||||
redoOption.callback = function(scope) {
|
||||
scope.workspace.undo(true);
|
||||
};
|
||||
redoOption.scopeType = Blockly.ContextMenuRegistry.ScopeType.WORKSPACE;
|
||||
redoOption.id = 'redoWorkspace';
|
||||
redoOption.weight = 0;
|
||||
Blockly.ContextMenuRegistry.registry.register(redoOption);
|
||||
};
|
||||
|
||||
/** Option to clean up blocks. */
|
||||
Blockly.ContextMenuItems.registerCleanup = function() {
|
||||
var cleanOption = {};
|
||||
cleanOption.displayText = function() {
|
||||
return Blockly.Msg['CLEAN_UP'];
|
||||
};
|
||||
cleanOption.preconditionFn = function(scope) {
|
||||
if (scope.workspace.isMovable()) {
|
||||
if (scope.workspace.getTopBlocks(false).length > 1) {
|
||||
return 'enabled';
|
||||
}
|
||||
return 'disabled';
|
||||
}
|
||||
return 'hidden';
|
||||
};
|
||||
cleanOption.callback = function(scope) {
|
||||
scope.workspace.cleanUp();
|
||||
};
|
||||
cleanOption.scopeType = Blockly.ContextMenuRegistry.ScopeType.WORKSPACE;
|
||||
cleanOption.id = 'cleanWorkspace';
|
||||
cleanOption.weight = 0;
|
||||
Blockly.ContextMenuRegistry.registry.register(cleanOption);
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a callback to collapse or expand top blocks.
|
||||
* @param {boolean} shouldCollapse Whether a block should collapse.
|
||||
* @param {!Array<Blockly.BlockSvg>} topBlocks Top blocks in the workspace.
|
||||
* @private
|
||||
*/
|
||||
Blockly.ContextMenuItems.toggleOption_ = function(shouldCollapse, topBlocks) {
|
||||
var DELAY = 10;
|
||||
var ms = 0;
|
||||
for (var i = 0; i < topBlocks.length; i++) {
|
||||
var block = topBlocks[i];
|
||||
while (block) {
|
||||
setTimeout(block.setCollapsed.bind(block, shouldCollapse), ms);
|
||||
block = block.getNextBlock();
|
||||
ms += DELAY;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** Option to collapse all blocks. */
|
||||
Blockly.ContextMenuItems.registerCollapse = function() {
|
||||
var collapseOption = {};
|
||||
collapseOption.displayText = function() {
|
||||
return Blockly.Msg['COLLAPSE_ALL'];
|
||||
};
|
||||
collapseOption.preconditionFn = function(scope) {
|
||||
if (scope.workspace.options.collapse) {
|
||||
var topBlocks = scope.workspace.getTopBlocks(false);
|
||||
for (var i = 0; i < topBlocks.length; i++) {
|
||||
var block = topBlocks[i];
|
||||
while (block) {
|
||||
if (!block.isCollapsed()) {
|
||||
return 'enabled';
|
||||
}
|
||||
block = block.getNextBlock();
|
||||
}
|
||||
}
|
||||
return 'disabled';
|
||||
}
|
||||
return 'hidden';
|
||||
};
|
||||
collapseOption.callback = function(scope) {
|
||||
Blockly.ContextMenuItems.toggleOption_(true, scope.workspace.getTopBlocks(true));
|
||||
};
|
||||
collapseOption.scopeType = Blockly.ContextMenuRegistry.ScopeType.WORKSPACE;
|
||||
collapseOption.id = 'collapseWorkspace';
|
||||
collapseOption.weight = 0;
|
||||
Blockly.ContextMenuRegistry.registry.register(collapseOption);
|
||||
};
|
||||
|
||||
/** Option to expand all blocks. */
|
||||
Blockly.ContextMenuItems.registerExpand = function() {
|
||||
var expandOption = {};
|
||||
expandOption.displayText = function() {
|
||||
return Blockly.Msg['EXPAND_ALL'];
|
||||
};
|
||||
expandOption.preconditionFn = function(scope) {
|
||||
if (scope.workspace.options.collapse) {
|
||||
var topBlocks = scope.workspace.getTopBlocks(false);
|
||||
for (var i = 0; i < topBlocks.length; i++) {
|
||||
var block = topBlocks[i];
|
||||
while (block) {
|
||||
if (block.isCollapsed()) {
|
||||
return 'enabled';
|
||||
}
|
||||
block = block.getNextBlock();
|
||||
}
|
||||
}
|
||||
return 'disabled';
|
||||
}
|
||||
return 'hidden';
|
||||
};
|
||||
expandOption.callback = function(scope) {
|
||||
Blockly.ContextMenuItems.toggleOption_(false, scope.workspace.getTopBlocks(true));
|
||||
};
|
||||
expandOption.scopeType = Blockly.ContextMenuRegistry.ScopeType.WORKSPACE;
|
||||
expandOption.id = 'toggleWorkspace';
|
||||
expandOption.weight = 0;
|
||||
Blockly.ContextMenuRegistry.registry.register(expandOption);
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a block and its children to a list of deletable blocks.
|
||||
* @param {!Blockly.BlockSvg} block to delete.
|
||||
* @param {!Array.<!Blockly.BlockSvg>} deleteList list of blocks that can be deleted. This will be
|
||||
* modifed in place with the given block and its descendants.
|
||||
* @private
|
||||
*/
|
||||
Blockly.ContextMenuItems.addDeletableBlocks_ = function(block, deleteList) {
|
||||
if (block.isDeletable()) {
|
||||
Array.prototype.push.apply(deleteList, block.getDescendants(false));
|
||||
} else {
|
||||
var children = /** @type !Array.<!Blockly.BlockSvg> */ (block.getChildren(false));
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
Blockly.ContextMenuItems.addDeletableBlocks_(children[i], deleteList);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructs a list of blocks that can be deleted in the given workspace.
|
||||
* @param {!Blockly.WorkspaceSvg} workspace to delete all blocks from.
|
||||
* @return {!Array.<!Blockly.BlockSvg>} list of blocks to delete.
|
||||
* @private
|
||||
*/
|
||||
Blockly.ContextMenuItems.getDeletableBlocks_ = function(workspace) {
|
||||
var deleteList = [];
|
||||
var topBlocks = workspace.getTopBlocks(true);
|
||||
for (var i = 0; i < topBlocks.length; i++) {
|
||||
Blockly.ContextMenuItems.addDeletableBlocks_(topBlocks[i], deleteList);
|
||||
}
|
||||
return deleteList;
|
||||
};
|
||||
|
||||
/** Deletes the given blocks. Used to delete all blocks in the workspace.
|
||||
* @param {!Array.<!Blockly.BlockSvg>} deleteList list of blocks to delete.
|
||||
* @param {string} eventGroup event group id with which all delete events should be associated.
|
||||
* @private
|
||||
*/
|
||||
Blockly.ContextMenuItems.deleteNext_ = function(deleteList, eventGroup) {
|
||||
var DELAY = 10;
|
||||
Blockly.Events.setGroup(eventGroup);
|
||||
var block = deleteList.shift();
|
||||
if (block) {
|
||||
if (block.workspace) {
|
||||
block.dispose(false, true);
|
||||
setTimeout(Blockly.ContextMenuItems.deleteNext_, DELAY, deleteList, eventGroup);
|
||||
} else {
|
||||
Blockly.ContextMenuItems.deleteNext_(deleteList, eventGroup);
|
||||
}
|
||||
}
|
||||
Blockly.Events.setGroup(false);
|
||||
};
|
||||
|
||||
/** Option to delete all blocks. */
|
||||
Blockly.ContextMenuItems.registerDeleteAll = function() {
|
||||
var deleteOption = {};
|
||||
deleteOption.displayText = function(scope) {
|
||||
var deletableBlocksLength =
|
||||
Blockly.ContextMenuItems.getDeletableBlocks_(scope.workspace).length;
|
||||
if (deletableBlocksLength == 1) {
|
||||
return Blockly.Msg['DELETE_BLOCK'];
|
||||
} else {
|
||||
return Blockly.Msg['DELETE_X_BLOCKS'].replace('%1', String(deletableBlocksLength));
|
||||
}
|
||||
};
|
||||
deleteOption.preconditionFn = function(scope) {
|
||||
var deletableBlocksLength =
|
||||
Blockly.ContextMenuItems.getDeletableBlocks_(scope.workspace).length;
|
||||
return deletableBlocksLength > 0 ? 'enabled' : 'disabled';
|
||||
};
|
||||
deleteOption.callback = function(scope) {
|
||||
if (scope.workspace.currentGesture_) {
|
||||
scope.workspace.currentGesture_.cancel();
|
||||
}
|
||||
var deletableBlocks = Blockly.ContextMenuItems.getDeletableBlocks_(scope.workspace);
|
||||
var eventGroup = Blockly.utils.genUid();
|
||||
if (deletableBlocks.length < 2) {
|
||||
Blockly.ContextMenuItems.deleteNext_(deletableBlocks, eventGroup);
|
||||
} else {
|
||||
Blockly.confirm(
|
||||
Blockly.Msg['DELETE_ALL_BLOCKS'].replace('%1', deletableBlocks.length),
|
||||
function(ok) {
|
||||
if (ok) {
|
||||
Blockly.ContextMenuItems.deleteNext_(deletableBlocks, eventGroup);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
deleteOption.scopeType = Blockly.ContextMenuRegistry.ScopeType.WORKSPACE;
|
||||
deleteOption.id = 'workspaceDelete';
|
||||
deleteOption.weight = 0;
|
||||
Blockly.ContextMenuRegistry.registry.register(deleteOption);
|
||||
};
|
||||
|
||||
/**
|
||||
* Registers all workspace-scoped context menu items.
|
||||
* @private
|
||||
*/
|
||||
Blockly.ContextMenuItems.registerWorkspaceOptions_ = function() {
|
||||
Blockly.ContextMenuItems.registerUndo();
|
||||
Blockly.ContextMenuItems.registerRedo();
|
||||
Blockly.ContextMenuItems.registerCleanup();
|
||||
Blockly.ContextMenuItems.registerCollapse();
|
||||
Blockly.ContextMenuItems.registerExpand();
|
||||
Blockly.ContextMenuItems.registerDeleteAll();
|
||||
};
|
||||
|
||||
/**
|
||||
* Registers all default context menu items. This should be called once per instance of
|
||||
* ContextMenuRegistry.
|
||||
* @package
|
||||
*/
|
||||
Blockly.ContextMenuItems.registerDefaultOptions = function() {
|
||||
Blockly.ContextMenuItems.registerWorkspaceOptions_();
|
||||
};
|
||||
|
||||
165
core/contextmenu_registry.js
Normal file
165
core/contextmenu_registry.js
Normal file
@@ -0,0 +1,165 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2020 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Registry for context menu option items.
|
||||
* @author maribethb@google.com (Maribeth Bottorff)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @name Blockly.ContextMenuRegistry
|
||||
* @namespace
|
||||
*/
|
||||
goog.provide('Blockly.ContextMenuRegistry');
|
||||
|
||||
goog.require('Blockly.ContextMenuItems');
|
||||
|
||||
/**
|
||||
* Class for the registry of context menu items. This is intended to be a singleton. You should
|
||||
* not create a new instance, and only access this class from Blockly.ContextMenuRegistry.registry.
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.ContextMenuRegistry = function() {
|
||||
|
||||
// Singleton instance should be registered once.
|
||||
Blockly.ContextMenuRegistry.registry = this;
|
||||
|
||||
/**
|
||||
* Registry of all registered RegistryItems, keyed by id.
|
||||
* @type {!Object<string, Blockly.ContextMenuRegistry.RegistryItem>}
|
||||
* @private
|
||||
*/
|
||||
this.registry_ = {};
|
||||
Blockly.ContextMenuItems.registerDefaultOptions();
|
||||
};
|
||||
|
||||
/**
|
||||
* Where this menu item should be rendered. If the menu item should be rendered in multiple
|
||||
* scopes, e.g. on both a block and a workspace, it should be registered for each scope.
|
||||
* @enum {string}
|
||||
*/
|
||||
Blockly.ContextMenuRegistry.ScopeType = {
|
||||
BLOCK: 'block',
|
||||
WORKSPACE: 'workspace',
|
||||
};
|
||||
|
||||
/**
|
||||
* The actual workspace/block where the menu is being rendered. This is passed to callback and
|
||||
* displayText functions that depend on this information.
|
||||
* @typedef {{
|
||||
* block: (Blockly.BlockSvg|undefined),
|
||||
* workspace: (Blockly.WorkspaceSvg|undefined),
|
||||
* }}
|
||||
*/
|
||||
Blockly.ContextMenuRegistry.Scope;
|
||||
|
||||
/**
|
||||
* A menu item as entered in the registry.
|
||||
* @typedef {{
|
||||
* callback: function(!Blockly.ContextMenuRegistry.Scope),
|
||||
* scopeType: !Blockly.ContextMenuRegistry.ScopeType,
|
||||
* displayText: ((function(!Blockly.ContextMenuRegistry.Scope):string)|string),
|
||||
* preconditionFn: function(!Blockly.ContextMenuRegistry.Scope):string,
|
||||
* weight: number,
|
||||
* id: string,
|
||||
* }}
|
||||
*/
|
||||
Blockly.ContextMenuRegistry.RegistryItem;
|
||||
|
||||
/**
|
||||
* A menu item as presented to contextmenu.js.
|
||||
* @typedef {{
|
||||
* text: string,
|
||||
* enabled: boolean,
|
||||
* callback: function(!Blockly.ContextMenuRegistry.Scope),
|
||||
* scope: !Blockly.ContextMenuRegistry.Scope,
|
||||
* weight: number,
|
||||
* }}
|
||||
*/
|
||||
Blockly.ContextMenuRegistry.ContextMenuOption;
|
||||
|
||||
/**
|
||||
* Singleton instance of this class. All interactions with this class should be done on this object.
|
||||
* @type {?Blockly.ContextMenuRegistry}
|
||||
*/
|
||||
Blockly.ContextMenuRegistry.registry = null;
|
||||
|
||||
/**
|
||||
* Registers a RegistryItem.
|
||||
* @param {!Blockly.ContextMenuRegistry.RegistryItem} item Context menu item to register.
|
||||
* @throws {Error} if an item with the given id already exists.
|
||||
*/
|
||||
Blockly.ContextMenuRegistry.prototype.register = function(item) {
|
||||
if (this.registry_[item.id]) {
|
||||
throw Error('Menu item with id "' + item.id + '" is already registered.');
|
||||
}
|
||||
this.registry_[item.id] = item;
|
||||
};
|
||||
|
||||
/**
|
||||
* Unregisters a RegistryItem with the given id.
|
||||
* @param {string} id The id of the RegistryItem to remove.
|
||||
* @throws {Error} if an item with the given id does not exist.
|
||||
*/
|
||||
Blockly.ContextMenuRegistry.prototype.unregister = function(id) {
|
||||
if (this.registry_[id]) {
|
||||
delete this.registry_[id];
|
||||
} else {
|
||||
throw new Error('Menu item with id "' + id + '" not found.');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} id The id of the RegistryItem to get.
|
||||
* @returns {?Blockly.ContextMenuRegistry.RegistryItem} RegistryItem or null if not found
|
||||
*/
|
||||
Blockly.ContextMenuRegistry.prototype.getItem = function(id) {
|
||||
if (this.registry_[id]) {
|
||||
return this.registry_[id];
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the valid context menu options for the given scope type (e.g. block or workspace) and scope.
|
||||
* Blocks are only shown if the preconditionFn shows they should not be hidden.
|
||||
* @param {!Blockly.ContextMenuRegistry.ScopeType} scopeType Type of scope where menu should be
|
||||
* shown (e.g. on a block or on a workspace)
|
||||
* @param {!Blockly.ContextMenuRegistry.Scope} scope Current scope of context menu
|
||||
* (i.e., the exact workspace or block being clicked on)
|
||||
* @returns {!Array.<!Blockly.ContextMenuRegistry.ContextMenuOption>} the list of ContextMenuOptions
|
||||
*/
|
||||
Blockly.ContextMenuRegistry.prototype.getContextMenuOptions = function(scopeType, scope) {
|
||||
var menuOptions = [];
|
||||
var registry = this.registry_;
|
||||
Object.keys(registry).forEach(function(id) {
|
||||
var item = registry[id];
|
||||
if (scopeType == item.scopeType) {
|
||||
var precondition = item.preconditionFn(scope);
|
||||
if (precondition != 'hidden') {
|
||||
var displayText = typeof item.displayText == 'function' ?
|
||||
item.displayText(scope) : item.displayText;
|
||||
/** @type {!Blockly.ContextMenuRegistry.ContextMenuOption} */
|
||||
var menuOption = {
|
||||
text: displayText,
|
||||
enabled: (precondition == 'enabled'),
|
||||
callback: item.callback,
|
||||
scope: scope,
|
||||
weight: item.weight,
|
||||
};
|
||||
menuOptions.push(menuOption);
|
||||
}
|
||||
}
|
||||
});
|
||||
menuOptions.sort(function(a, b) {
|
||||
return a.weight - b.weight;
|
||||
});
|
||||
return menuOptions;
|
||||
};
|
||||
|
||||
// Creates and assigns the singleton instance.
|
||||
new Blockly.ContextMenuRegistry();
|
||||
@@ -16,6 +16,7 @@ goog.require('Blockly.BlockSvg');
|
||||
goog.require('Blockly.blockRendering');
|
||||
goog.require('Blockly.ConnectionDB');
|
||||
goog.require('Blockly.constants');
|
||||
goog.require('Blockly.ContextMenuRegistry');
|
||||
goog.require('Blockly.Events');
|
||||
goog.require('Blockly.Events.BlockCreate');
|
||||
goog.require('Blockly.Gesture');
|
||||
@@ -1689,136 +1690,8 @@ Blockly.WorkspaceSvg.prototype.showContextMenu = function(e) {
|
||||
if (this.options.readOnly || this.isFlyout) {
|
||||
return;
|
||||
}
|
||||
var menuOptions = [];
|
||||
var topBlocks = this.getTopBlocks(true);
|
||||
var eventGroup = Blockly.utils.genUid();
|
||||
var ws = this;
|
||||
|
||||
// Options to undo/redo previous action.
|
||||
var undoOption = {};
|
||||
undoOption.text = Blockly.Msg['UNDO'];
|
||||
undoOption.enabled = this.undoStack_.length > 0;
|
||||
undoOption.callback = this.undo.bind(this, false);
|
||||
menuOptions.push(undoOption);
|
||||
var redoOption = {};
|
||||
redoOption.text = Blockly.Msg['REDO'];
|
||||
redoOption.enabled = this.redoStack_.length > 0;
|
||||
redoOption.callback = this.undo.bind(this, true);
|
||||
menuOptions.push(redoOption);
|
||||
|
||||
// Option to clean up blocks.
|
||||
if (this.isMovable()) {
|
||||
var cleanOption = {};
|
||||
cleanOption.text = Blockly.Msg['CLEAN_UP'];
|
||||
cleanOption.enabled = topBlocks.length > 1;
|
||||
cleanOption.callback = this.cleanUp.bind(this);
|
||||
menuOptions.push(cleanOption);
|
||||
}
|
||||
|
||||
// Add a little animation to collapsing and expanding.
|
||||
var DELAY = 10;
|
||||
if (this.options.collapse) {
|
||||
var hasCollapsedBlocks = false;
|
||||
var hasExpandedBlocks = false;
|
||||
for (var i = 0; i < topBlocks.length; i++) {
|
||||
var block = topBlocks[i];
|
||||
while (block) {
|
||||
if (block.isCollapsed()) {
|
||||
hasCollapsedBlocks = true;
|
||||
} else {
|
||||
hasExpandedBlocks = true;
|
||||
}
|
||||
block = block.getNextBlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Option to collapse or expand top blocks.
|
||||
* @param {boolean} shouldCollapse Whether a block should collapse.
|
||||
* @private
|
||||
*/
|
||||
var toggleOption = function(shouldCollapse) {
|
||||
var ms = 0;
|
||||
for (var i = 0; i < topBlocks.length; i++) {
|
||||
var block = topBlocks[i];
|
||||
while (block) {
|
||||
setTimeout(block.setCollapsed.bind(block, shouldCollapse), ms);
|
||||
block = block.getNextBlock();
|
||||
ms += DELAY;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Option to collapse top blocks.
|
||||
var collapseOption = {enabled: hasExpandedBlocks};
|
||||
collapseOption.text = Blockly.Msg['COLLAPSE_ALL'];
|
||||
collapseOption.callback = function() {
|
||||
toggleOption(true);
|
||||
};
|
||||
menuOptions.push(collapseOption);
|
||||
|
||||
// Option to expand top blocks.
|
||||
var expandOption = {enabled: hasCollapsedBlocks};
|
||||
expandOption.text = Blockly.Msg['EXPAND_ALL'];
|
||||
expandOption.callback = function() {
|
||||
toggleOption(false);
|
||||
};
|
||||
menuOptions.push(expandOption);
|
||||
}
|
||||
|
||||
// Option to delete all blocks.
|
||||
// Count the number of blocks that are deletable.
|
||||
var deleteList = [];
|
||||
function addDeletableBlocks(block) {
|
||||
if (block.isDeletable()) {
|
||||
deleteList = deleteList.concat(block.getDescendants(false));
|
||||
} else {
|
||||
var children = block.getChildren(false);
|
||||
for (var i = 0; i < children.length; i++) {
|
||||
addDeletableBlocks(children[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (var i = 0; i < topBlocks.length; i++) {
|
||||
addDeletableBlocks(topBlocks[i]);
|
||||
}
|
||||
|
||||
function deleteNext() {
|
||||
Blockly.Events.setGroup(eventGroup);
|
||||
var block = deleteList.shift();
|
||||
if (block) {
|
||||
if (block.workspace) {
|
||||
block.dispose(false, true);
|
||||
setTimeout(deleteNext, DELAY);
|
||||
} else {
|
||||
deleteNext();
|
||||
}
|
||||
}
|
||||
Blockly.Events.setGroup(false);
|
||||
}
|
||||
|
||||
var deleteOption = {
|
||||
text: deleteList.length == 1 ? Blockly.Msg['DELETE_BLOCK'] :
|
||||
Blockly.Msg['DELETE_X_BLOCKS'].replace('%1', String(deleteList.length)),
|
||||
enabled: deleteList.length > 0,
|
||||
callback: function() {
|
||||
if (ws.currentGesture_) {
|
||||
ws.currentGesture_.cancel();
|
||||
}
|
||||
if (deleteList.length < 2 ) {
|
||||
deleteNext();
|
||||
} else {
|
||||
Blockly.confirm(
|
||||
Blockly.Msg['DELETE_ALL_BLOCKS'].replace('%1', deleteList.length),
|
||||
function(ok) {
|
||||
if (ok) {
|
||||
deleteNext();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
menuOptions.push(deleteOption);
|
||||
var menuOptions = Blockly.ContextMenuRegistry.registry.getContextMenuOptions(
|
||||
Blockly.ContextMenuRegistry.ScopeType.WORKSPACE, {workspace: this});
|
||||
|
||||
// Allow the developer to add or modify menuOptions.
|
||||
if (this.configureContextMenu) {
|
||||
|
||||
Reference in New Issue
Block a user