Bug 1518512 - (Part 1) Add basic context menu to Changes panel. r=gl
Adds context menu with options to select all and copy text content from the Changes panel. Differential Revision: https://phabricator.services.mozilla.com/D17255
This commit is contained in:
99
devtools/client/inspector/changes/ChangesContextMenu.js
Normal file
99
devtools/client/inspector/changes/ChangesContextMenu.js
Normal file
@@ -0,0 +1,99 @@
|
||||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
loader.lazyRequireGetter(this, "Menu", "devtools/client/framework/menu");
|
||||
loader.lazyRequireGetter(this, "MenuItem", "devtools/client/framework/menu-item");
|
||||
loader.lazyRequireGetter(this, "clipboardHelper", "devtools/shared/platform/clipboard");
|
||||
|
||||
const { getStr } = require("./utils/l10n");
|
||||
|
||||
/**
|
||||
* Context menu for the Changes panel with options to select, copy and export CSS changes.
|
||||
*/
|
||||
class ChangesContextMenu {
|
||||
/**
|
||||
* @param {ChangesView} view
|
||||
*/
|
||||
constructor(view) {
|
||||
this.view = view;
|
||||
this.inspector = this.view.inspector;
|
||||
// Document object to which the Changes panel belongs to.
|
||||
this.document = this.view.document;
|
||||
// DOM element container for the Changes panel content.
|
||||
this.panel = this.document.getElementById("sidebar-panel-changes");
|
||||
// Window object to which the Changes panel belongs to.
|
||||
this.window = this.document.defaultView;
|
||||
|
||||
this._onCopy = this._onCopy.bind(this);
|
||||
this._onSelectAll = this._onSelectAll.bind(this);
|
||||
}
|
||||
|
||||
show(event) {
|
||||
this._openMenu({
|
||||
target: event.explicitOriginalTarget,
|
||||
screenX: event.screenX,
|
||||
screenY: event.screenY,
|
||||
});
|
||||
}
|
||||
|
||||
_openMenu({ target, screenX = 0, screenY = 0 } = {}) {
|
||||
this.window.focus();
|
||||
|
||||
const menu = new Menu();
|
||||
|
||||
// Copy option
|
||||
const menuitemCopy = new MenuItem({
|
||||
label: getStr("changes.contextmenu.copy"),
|
||||
accesskey: getStr("changes.contextmenu.copy.accessKey"),
|
||||
click: this._onCopy,
|
||||
disabled: !this._hasTextSelected(),
|
||||
});
|
||||
menu.append(menuitemCopy);
|
||||
|
||||
// Select All option
|
||||
const menuitemSelectAll = new MenuItem({
|
||||
label: getStr("changes.contextmenu.selectAll"),
|
||||
accesskey: getStr("changes.contextmenu.selectAll.accessKey"),
|
||||
click: this._onSelectAll,
|
||||
});
|
||||
menu.append(menuitemSelectAll);
|
||||
|
||||
menu.popup(screenX, screenY, this.inspector.toolbox);
|
||||
return menu;
|
||||
}
|
||||
|
||||
_hasTextSelected() {
|
||||
const selection = this.window.getSelection();
|
||||
return selection.toString() && !selection.isCollapsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Select all text.
|
||||
*/
|
||||
_onSelectAll() {
|
||||
const selection = this.window.getSelection();
|
||||
selection.selectAllChildren(this.panel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy the selected text to clipboard.
|
||||
*/
|
||||
_onCopy() {
|
||||
const text = this.window.getSelection().toString();
|
||||
clipboardHelper.copyString(text);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.inspector = null;
|
||||
this.panel = null;
|
||||
this.view = null;
|
||||
this.window = null;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ChangesContextMenu;
|
||||
@@ -9,6 +9,8 @@
|
||||
const { createFactory, createElement } = require("devtools/client/shared/vendor/react");
|
||||
const { Provider } = require("devtools/client/shared/vendor/react-redux");
|
||||
|
||||
loader.lazyRequireGetter(this, "ChangesContextMenu", "devtools/client/inspector/changes/ChangesContextMenu");
|
||||
|
||||
const ChangesApp = createFactory(require("./components/ChangesApp"));
|
||||
|
||||
const {
|
||||
@@ -21,18 +23,28 @@ class ChangesView {
|
||||
this.document = window.document;
|
||||
this.inspector = inspector;
|
||||
this.store = this.inspector.store;
|
||||
this.toolbox = this.inspector.toolbox;
|
||||
|
||||
this.onAddChange = this.onAddChange.bind(this);
|
||||
this.onClearChanges = this.onClearChanges.bind(this);
|
||||
this.onChangesFront = this.onChangesFront.bind(this);
|
||||
this.onContextMenu = this.onContextMenu.bind(this);
|
||||
this.destroy = this.destroy.bind(this);
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
get contextMenu() {
|
||||
if (!this._contextMenu) {
|
||||
this._contextMenu = new ChangesContextMenu(this);
|
||||
}
|
||||
|
||||
return this._contextMenu;
|
||||
}
|
||||
|
||||
init() {
|
||||
const changesApp = ChangesApp({});
|
||||
const changesApp = ChangesApp({
|
||||
onContextMenu: this.onContextMenu,
|
||||
});
|
||||
|
||||
// listen to the front for initialization, add listeners
|
||||
// when it is ready
|
||||
@@ -88,6 +100,10 @@ class ChangesView {
|
||||
this.store.dispatch(resetChanges());
|
||||
}
|
||||
|
||||
onContextMenu(e) {
|
||||
this.contextMenu.show(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destruction function called when the inspector is destroyed.
|
||||
*/
|
||||
@@ -102,7 +118,11 @@ class ChangesView {
|
||||
this.document = null;
|
||||
this.inspector = null;
|
||||
this.store = null;
|
||||
this.toolbox = null;
|
||||
|
||||
if (this._contextMenu) {
|
||||
this._contextMenu.destroy();
|
||||
this._contextMenu = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@ class ChangesApp extends PureComponent {
|
||||
return {
|
||||
// Nested CSS rule tree structure of CSS changes grouped by source (stylesheet)
|
||||
changesTree: PropTypes.object.isRequired,
|
||||
// Event handler for "contextmenu" event
|
||||
onContextMenu: PropTypes.func.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -140,6 +142,7 @@ class ChangesApp extends PureComponent {
|
||||
{
|
||||
className: "theme-sidebar inspector-tabpanel",
|
||||
id: "sidebar-panel-changes",
|
||||
onContextMenu: this.props.onContextMenu,
|
||||
},
|
||||
!hasChanges && this.renderEmptyState(),
|
||||
hasChanges && this.renderDiff(this.props.changesTree)
|
||||
|
||||
@@ -13,6 +13,7 @@ DIRS += [
|
||||
]
|
||||
|
||||
DevToolsModules(
|
||||
'ChangesContextMenu.js',
|
||||
'ChangesView.js',
|
||||
)
|
||||
|
||||
|
||||
@@ -24,3 +24,19 @@ changes.elementStyleLabel=Element
|
||||
# LOCALIZATION NOTE (changes.iframeLabel): This label appears next to URLs of stylesheets
|
||||
# and element inline styles hosted by iframes. Lowercase intentional.
|
||||
changes.iframeLabel=iframe
|
||||
|
||||
# LOCALIZATION NOTE (changes.contextmenu.copy): Label for "Copy" option in Changes panel
|
||||
# context menu
|
||||
changes.contextmenu.copy=Copy
|
||||
|
||||
# LOCALIZATION NOTE (changes.contextmenu.copy.accessKey): Access key for "Copy"
|
||||
# option in the Changes panel.
|
||||
changes.contextmenu.copy.accessKey=C
|
||||
|
||||
# LOCALIZATION NOTE (changes.contextmenu.selectAll): Label for "Select All" option in the
|
||||
# Changes panel context menu to select all text content.
|
||||
changes.contextmenu.selectAll=Select All
|
||||
|
||||
# LOCALIZATION NOTE (changes.contextmenu.selectAll.accessKey): Access key for "Select All"
|
||||
# option in the Changes panel.
|
||||
changes.contextmenu.selectAll.accessKey=A
|
||||
|
||||
Reference in New Issue
Block a user