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 { createFactory, createElement } = require("devtools/client/shared/vendor/react");
|
||||||
const { Provider } = require("devtools/client/shared/vendor/react-redux");
|
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 ChangesApp = createFactory(require("./components/ChangesApp"));
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -21,18 +23,28 @@ class ChangesView {
|
|||||||
this.document = window.document;
|
this.document = window.document;
|
||||||
this.inspector = inspector;
|
this.inspector = inspector;
|
||||||
this.store = this.inspector.store;
|
this.store = this.inspector.store;
|
||||||
this.toolbox = this.inspector.toolbox;
|
|
||||||
|
|
||||||
this.onAddChange = this.onAddChange.bind(this);
|
this.onAddChange = this.onAddChange.bind(this);
|
||||||
this.onClearChanges = this.onClearChanges.bind(this);
|
this.onClearChanges = this.onClearChanges.bind(this);
|
||||||
this.onChangesFront = this.onChangesFront.bind(this);
|
this.onChangesFront = this.onChangesFront.bind(this);
|
||||||
|
this.onContextMenu = this.onContextMenu.bind(this);
|
||||||
this.destroy = this.destroy.bind(this);
|
this.destroy = this.destroy.bind(this);
|
||||||
|
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get contextMenu() {
|
||||||
|
if (!this._contextMenu) {
|
||||||
|
this._contextMenu = new ChangesContextMenu(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._contextMenu;
|
||||||
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
const changesApp = ChangesApp({});
|
const changesApp = ChangesApp({
|
||||||
|
onContextMenu: this.onContextMenu,
|
||||||
|
});
|
||||||
|
|
||||||
// listen to the front for initialization, add listeners
|
// listen to the front for initialization, add listeners
|
||||||
// when it is ready
|
// when it is ready
|
||||||
@@ -88,6 +100,10 @@ class ChangesView {
|
|||||||
this.store.dispatch(resetChanges());
|
this.store.dispatch(resetChanges());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onContextMenu(e) {
|
||||||
|
this.contextMenu.show(e);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Destruction function called when the inspector is destroyed.
|
* Destruction function called when the inspector is destroyed.
|
||||||
*/
|
*/
|
||||||
@@ -102,7 +118,11 @@ class ChangesView {
|
|||||||
this.document = null;
|
this.document = null;
|
||||||
this.inspector = null;
|
this.inspector = null;
|
||||||
this.store = 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 {
|
return {
|
||||||
// Nested CSS rule tree structure of CSS changes grouped by source (stylesheet)
|
// Nested CSS rule tree structure of CSS changes grouped by source (stylesheet)
|
||||||
changesTree: PropTypes.object.isRequired,
|
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",
|
className: "theme-sidebar inspector-tabpanel",
|
||||||
id: "sidebar-panel-changes",
|
id: "sidebar-panel-changes",
|
||||||
|
onContextMenu: this.props.onContextMenu,
|
||||||
},
|
},
|
||||||
!hasChanges && this.renderEmptyState(),
|
!hasChanges && this.renderEmptyState(),
|
||||||
hasChanges && this.renderDiff(this.props.changesTree)
|
hasChanges && this.renderDiff(this.props.changesTree)
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ DIRS += [
|
|||||||
]
|
]
|
||||||
|
|
||||||
DevToolsModules(
|
DevToolsModules(
|
||||||
|
'ChangesContextMenu.js',
|
||||||
'ChangesView.js',
|
'ChangesView.js',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -24,3 +24,19 @@ changes.elementStyleLabel=Element
|
|||||||
# LOCALIZATION NOTE (changes.iframeLabel): This label appears next to URLs of stylesheets
|
# LOCALIZATION NOTE (changes.iframeLabel): This label appears next to URLs of stylesheets
|
||||||
# and element inline styles hosted by iframes. Lowercase intentional.
|
# and element inline styles hosted by iframes. Lowercase intentional.
|
||||||
changes.iframeLabel=iframe
|
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