mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-10-09 21:28:43 +08:00
Enable image preview in styles panel
This change adds image preview functionality to the styles panel including the computed styles view. The preview functionality within ElementsPanel is removed because it seems unused (equivalent code in ElementsTreeOutline handles the previews). Screenshot: https://i.imgur.com/TIU3Rg3.png Fixed: 1040019 Change-Id: I6e387826de908df74ea6e5d7a89e2293291e692b Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/2156347 Commit-Queue: Alex Rudenko <alexrudenko@chromium.org> Reviewed-by: Mathias Bynens <mathias@chromium.org> Reviewed-by: Peter Marshall <petermarshall@chromium.org>
This commit is contained in:
@@ -1167,6 +1167,7 @@ all_devtools_modules = [
|
||||
"elements/ElementsTreeElementHighlighter.js",
|
||||
"elements/ElementsTreeOutline.js",
|
||||
"elements/EventListenersWidget.js",
|
||||
"elements/ImagePreviewPopover.js",
|
||||
"elements/InspectElementModeController.js",
|
||||
"elements/MarkerDecorator.js",
|
||||
"elements/MetricsSidebarPane.js",
|
||||
|
||||
@@ -34,6 +34,7 @@ import * as SDK from '../sdk/sdk.js';
|
||||
import * as UI from '../ui/ui.js';
|
||||
|
||||
import {ComputedStyle, ComputedStyleModel, Events} from './ComputedStyleModel.js'; // eslint-disable-line no-unused-vars
|
||||
import {ImagePreviewPopover} from './ImagePreviewPopover.js';
|
||||
import {PlatformFontsWidget} from './PlatformFontsWidget.js';
|
||||
import {StylePropertiesSection, StylesSidebarPane, StylesSidebarPropertyRenderer} from './StylesSidebarPane.js';
|
||||
|
||||
@@ -78,6 +79,14 @@ export class ComputedStyleWidget extends UI.ThrottledWidget.ThrottledWidget {
|
||||
|
||||
this._linkifier = new Components.Linkifier.Linkifier(_maxLinkLength);
|
||||
|
||||
this._imagePreviewPopover = new ImagePreviewPopover(this.contentElement, event => {
|
||||
const link = event.composedPath()[0];
|
||||
if (link instanceof Element) {
|
||||
return link;
|
||||
}
|
||||
return null;
|
||||
}, () => this._computedStyleModel.node());
|
||||
|
||||
/**
|
||||
* @param {?RegExp} regex
|
||||
* @this {ComputedStyleWidget}
|
||||
@@ -164,6 +173,7 @@ export class ComputedStyleWidget extends UI.ThrottledWidget.ThrottledWidget {
|
||||
expandedProperties.add(propertyName);
|
||||
}
|
||||
const hadFocus = this._propertiesOutline.element.hasFocus();
|
||||
this._imagePreviewPopover.hide();
|
||||
this._propertiesOutline.removeChildren();
|
||||
this._linkifier.reset();
|
||||
const cssModel = this._computedStyleModel.cssModel();
|
||||
|
||||
@@ -37,7 +37,7 @@ import * as UI from '../ui/ui.js';
|
||||
|
||||
import {ComputedStyleWidget} from './ComputedStyleWidget.js';
|
||||
import {ElementsBreadcrumbs, Events} from './ElementsBreadcrumbs.js';
|
||||
import {ElementsTreeElement, HrefSymbol} from './ElementsTreeElement.js'; // eslint-disable-line no-unused-vars
|
||||
import {ElementsTreeElement} from './ElementsTreeElement.js'; // eslint-disable-line no-unused-vars
|
||||
import {ElementsTreeElementHighlighter} from './ElementsTreeElementHighlighter.js';
|
||||
import {ElementsTreeOutline} from './ElementsTreeOutline.js';
|
||||
import {MarkerDecorator} from './MarkerDecorator.js'; // eslint-disable-line no-unused-vars
|
||||
@@ -306,9 +306,6 @@ export class ElementsPanel extends UI.Panel.Panel {
|
||||
this._contentElement.removeChild(header);
|
||||
}
|
||||
}
|
||||
if (this._popoverHelper) {
|
||||
this._popoverHelper.hidePopover();
|
||||
}
|
||||
super.willHide();
|
||||
self.UI.context.setFlavor(ElementsPanel, null);
|
||||
}
|
||||
@@ -523,36 +520,6 @@ export class ElementsPanel extends UI.Panel.Panel {
|
||||
UI.ViewManager.ViewManager.instance().showView('elements').then(() => this.selectDOMNode(node, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!Event} event
|
||||
* @return {?UI.PopoverRequest}
|
||||
*/
|
||||
_getPopoverRequest(event) {
|
||||
let link = event.target;
|
||||
while (link && !link[HrefSymbol]) {
|
||||
link = link.parentElementOrShadowHost();
|
||||
}
|
||||
if (!link) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
box: link.boxInWindow(),
|
||||
show: async popover => {
|
||||
const node = this.selectedDOMNode();
|
||||
if (!node) {
|
||||
return false;
|
||||
}
|
||||
const preview =
|
||||
await Components.ImagePreview.ImagePreview.build(node.domModel().target(), link[HrefSymbol], true);
|
||||
if (preview) {
|
||||
popover.contentElement.appendChild(preview);
|
||||
}
|
||||
return !!preview;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
_jumpToSearchResult(index) {
|
||||
if (!this._searchResults) {
|
||||
return;
|
||||
@@ -880,12 +847,6 @@ export class ElementsPanel extends UI.Panel.Panel {
|
||||
this.sidebarPaneView = UI.ViewManager.ViewManager.instance().createTabbedLocation(
|
||||
() => UI.ViewManager.ViewManager.instance().showView('elements'));
|
||||
const tabbedPane = this.sidebarPaneView.tabbedPane();
|
||||
if (this._popoverHelper) {
|
||||
this._popoverHelper.hidePopover();
|
||||
}
|
||||
this._popoverHelper = new UI.PopoverHelper.PopoverHelper(tabbedPane.element, this._getPopoverRequest.bind(this));
|
||||
this._popoverHelper.setHasPadding(true);
|
||||
this._popoverHelper.setTimeout(0);
|
||||
|
||||
if (this._splitMode !== _splitMode.Vertical) {
|
||||
this._splitWidget.installResizer(tabbedPane.headerElement());
|
||||
|
||||
@@ -39,6 +39,7 @@ import * as UI from '../ui/ui.js';
|
||||
|
||||
import {canGetJSPath, cssPath, jsPath, xPath} from './DOMPath.js';
|
||||
import {MappedCharToEntity, UpdateRecord} from './ElementsTreeOutline.js'; // eslint-disable-line no-unused-vars
|
||||
import {ImagePreviewPopover} from './ImagePreviewPopover.js';
|
||||
import {MarkerDecorator} from './MarkerDecorator.js';
|
||||
|
||||
/**
|
||||
@@ -1410,8 +1411,7 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
|
||||
const link = node.nodeName().toLowerCase() === 'a' ?
|
||||
UI.XLink.XLink.create(rewrittenHref, value, '', true /* preventClick */) :
|
||||
Components.Linkifier.Linkifier.linkifyURL(rewrittenHref, {text: value, preventClick: true});
|
||||
link[HrefSymbol] = rewrittenHref;
|
||||
return link;
|
||||
return ImagePreviewPopover.setImageUrl(link, rewrittenHref);
|
||||
}
|
||||
|
||||
const nodeName = node ? node.nodeName().toLowerCase() : '';
|
||||
@@ -1838,7 +1838,6 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
|
||||
}
|
||||
}
|
||||
|
||||
export const HrefSymbol = Symbol('ElementsTreeElement.Href');
|
||||
export const InitialChildrenLimit = 500;
|
||||
|
||||
// A union of HTML4 and HTML5-Draft elements that explicitly
|
||||
|
||||
@@ -29,13 +29,13 @@
|
||||
*/
|
||||
|
||||
import * as Common from '../common/common.js';
|
||||
import * as Components from '../components/components.js';
|
||||
import * as ProtocolClient from '../protocol_client/protocol_client.js'; // eslint-disable-line no-unused-vars
|
||||
import * as SDK from '../sdk/sdk.js';
|
||||
import * as UI from '../ui/ui.js';
|
||||
|
||||
import {linkifyDeferredNodeReference} from './DOMLinkifier.js';
|
||||
import {ElementsTreeElement, HrefSymbol, InitialChildrenLimit} from './ElementsTreeElement.js';
|
||||
import {ElementsTreeElement, InitialChildrenLimit} from './ElementsTreeElement.js';
|
||||
import {HrefSymbol, ImagePreviewPopover} from './ImagePreviewPopover.js';
|
||||
|
||||
/**
|
||||
* @unrestricted
|
||||
@@ -88,9 +88,22 @@ export class ElementsTreeOutline extends UI.TreeOutline.TreeOutline {
|
||||
|
||||
this._visible = false;
|
||||
|
||||
this._popoverHelper = new UI.PopoverHelper.PopoverHelper(this._element, this._getPopoverRequest.bind(this));
|
||||
this._popoverHelper.setHasPadding(true);
|
||||
this._popoverHelper.setTimeout(0, 100);
|
||||
this._imagePreviewPopover = new ImagePreviewPopover(
|
||||
this.contentElement,
|
||||
event => {
|
||||
let link = event.target;
|
||||
while (link && !link[HrefSymbol]) {
|
||||
link = link.parentElementOrShadowHost();
|
||||
}
|
||||
return link;
|
||||
},
|
||||
link => {
|
||||
const listItem = link.enclosingNodeOrSelfWithNodeName('li');
|
||||
if (!listItem) {
|
||||
return null;
|
||||
}
|
||||
return /** @type {!ElementsTreeElement} */ (listItem.treeElement).node();
|
||||
});
|
||||
|
||||
/** @type {!Map<!SDK.DOMModel.DOMNode, !UpdateRecord>} */
|
||||
this._updateRecords = new Map();
|
||||
@@ -324,7 +337,7 @@ export class ElementsTreeOutline extends UI.TreeOutline.TreeOutline {
|
||||
}
|
||||
this._visible = visible;
|
||||
if (!this._visible) {
|
||||
this._popoverHelper.hidePopover();
|
||||
this._imagePreviewPopover.hide();
|
||||
if (this._multilineEditing) {
|
||||
this._multilineEditing.cancel();
|
||||
}
|
||||
@@ -574,38 +587,6 @@ export class ElementsTreeOutline extends UI.TreeOutline.TreeOutline {
|
||||
return element;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!Event} event
|
||||
* @return {?UI.PopoverRequest}
|
||||
*/
|
||||
_getPopoverRequest(event) {
|
||||
let link = event.target;
|
||||
while (link && !link[HrefSymbol]) {
|
||||
link = link.parentElementOrShadowHost();
|
||||
}
|
||||
if (!link) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
box: link.boxInWindow(),
|
||||
show: async popover => {
|
||||
const listItem = link.enclosingNodeOrSelfWithNodeName('li');
|
||||
if (!listItem) {
|
||||
return false;
|
||||
}
|
||||
const node = /** @type {!ElementsTreeElement} */ (listItem.treeElement).node();
|
||||
const precomputedFeatures = await Components.ImagePreview.ImagePreview.loadDimensionsForNode(node);
|
||||
const preview = await Components.ImagePreview.ImagePreview.build(
|
||||
node.domModel().target(), link[HrefSymbol], true, {precomputedFeatures});
|
||||
if (preview) {
|
||||
popover.contentElement.appendChild(preview);
|
||||
}
|
||||
return !!preview;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!Event} event
|
||||
*/
|
||||
@@ -1047,7 +1028,7 @@ export class ElementsTreeOutline extends UI.TreeOutline.TreeOutline {
|
||||
_reset() {
|
||||
this.rootDOMNode = null;
|
||||
this.selectDOMNode(null, false);
|
||||
this._popoverHelper.hidePopover();
|
||||
this._imagePreviewPopover.hide();
|
||||
delete this._clipboardNodeData;
|
||||
SDK.OverlayModel.OverlayModel.hideDOMNodeHighlight();
|
||||
this._updateRecords.clear();
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
// Copyright 2020 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import * as Components from '../components/components.js';
|
||||
import * as SDK from '../sdk/sdk.js'; // eslint-disable-line no-unused-vars
|
||||
import * as UI from '../ui/ui.js';
|
||||
|
||||
/**
|
||||
* ImagePreviewPopover sets listeners on the container element to display
|
||||
* an image preview if needed. The image URL comes from the event (mouseover) target
|
||||
* in a propery identified by HrefSymbol. To enable preview for any child element
|
||||
* set the property HrefSymbol.
|
||||
*/
|
||||
export class ImagePreviewPopover {
|
||||
/**
|
||||
* @param {!Element} container
|
||||
* @param {function(!Event):?Element} getLinkElement
|
||||
* @param {function(!Element):?SDK.DOMModel.DOMNode} getDOMNode
|
||||
*/
|
||||
constructor(container, getLinkElement, getDOMNode) {
|
||||
this._getLinkElement = getLinkElement;
|
||||
this._getDOMNode = getDOMNode;
|
||||
this._popover = new UI.PopoverHelper.PopoverHelper(container, this._handleRequest.bind(this));
|
||||
this._popover.setHasPadding(true);
|
||||
this._popover.setTimeout(0, 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!Event} event
|
||||
* @return {?UI.PopoverHelper.PopoverRequest}
|
||||
*/
|
||||
_handleRequest(event) {
|
||||
const link = this._getLinkElement(event);
|
||||
if (!link || !link[HrefSymbol]) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
box: link.boxInWindow(),
|
||||
hide: undefined,
|
||||
show: async popover => {
|
||||
const node = this._getDOMNode(/** @type {!Element} */ (link));
|
||||
if (!node) {
|
||||
return false;
|
||||
}
|
||||
const precomputedFeatures = await Components.ImagePreview.ImagePreview.loadDimensionsForNode(node);
|
||||
const preview = await Components.ImagePreview.ImagePreview.build(
|
||||
node.domModel().target(), link[HrefSymbol], true, {precomputedFeatures});
|
||||
if (preview) {
|
||||
popover.contentElement.appendChild(preview);
|
||||
}
|
||||
return !!preview;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
hide() {
|
||||
this._popover.hidePopover();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!Element} element
|
||||
* @param {string} url
|
||||
*/
|
||||
static setImageUrl(element, url) {
|
||||
element[HrefSymbol] = url;
|
||||
return element;
|
||||
}
|
||||
}
|
||||
|
||||
export const HrefSymbol = Symbol('ImagePreviewPopover.Href');
|
||||
@@ -39,6 +39,7 @@ import * as UI from '../ui/ui.js';
|
||||
import {ColorSwatchPopoverIcon, ShadowSwatchPopoverHelper} from './ColorSwatchPopoverIcon.js';
|
||||
import {linkifyDeferredNodeReference} from './DOMLinkifier.js';
|
||||
import {ElementsSidebarPane} from './ElementsSidebarPane.js';
|
||||
import {ImagePreviewPopover} from './ImagePreviewPopover.js';
|
||||
import {StylePropertyHighlighter} from './StylePropertyHighlighter.js';
|
||||
import {StylePropertyTreeElement} from './StylePropertyTreeElement.js';
|
||||
import {Context} from './StylePropertyTreeElement.js'; // eslint-disable-line no-unused-vars
|
||||
@@ -90,6 +91,14 @@ export class StylesSidebarPane extends ElementsSidebarPane {
|
||||
self.UI.context.addFlavorChangeListener(SDK.DOMModel.DOMNode, this.forceUpdate, this);
|
||||
this.contentElement.addEventListener('copy', this._clipboardCopy.bind(this));
|
||||
this._resizeThrottler = new Common.Throttler.Throttler(100);
|
||||
|
||||
this._imagePreviewPopover = new ImagePreviewPopover(this.contentElement, event => {
|
||||
const link = event.composedPath()[0];
|
||||
if (link instanceof Element) {
|
||||
return link;
|
||||
}
|
||||
return null;
|
||||
}, () => this.node());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -707,6 +716,7 @@ export class StylesSidebarPane extends ElementsSidebarPane {
|
||||
*/
|
||||
willHide() {
|
||||
this._swatchPopoverHelper.hide();
|
||||
this._imagePreviewPopover.hide();
|
||||
super.willHide();
|
||||
}
|
||||
|
||||
@@ -2704,15 +2714,18 @@ export class StylesSidebarPropertyRenderer {
|
||||
} else if (this._node) {
|
||||
hrefUrl = this._node.resolveURL(url);
|
||||
}
|
||||
container.appendChild(Components.Linkifier.Linkifier.linkifyURL(hrefUrl || url, {
|
||||
text: url,
|
||||
preventClick: true,
|
||||
// crbug.com/1027168
|
||||
// We rely on CSS text-overflow: ellipsis to hide long URLs in the Style panel,
|
||||
// so that we don't have to keep two versions (original vs. trimmed) of URL
|
||||
// at the same time, which complicates both StylesSidebarPane and StylePropertyTreeElement.
|
||||
bypassURLTrimming: true,
|
||||
}));
|
||||
const link = ImagePreviewPopover.setImageUrl(
|
||||
Components.Linkifier.Linkifier.linkifyURL(hrefUrl || url, {
|
||||
text: url,
|
||||
preventClick: true,
|
||||
// crbug.com/1027168
|
||||
// We rely on CSS text-overflow: ellipsis to hide long URLs in the Style panel,
|
||||
// so that we don't have to keep two versions (original vs. trimmed) of URL
|
||||
// at the same time, which complicates both StylesSidebarPane and StylePropertyTreeElement.
|
||||
bypassURLTrimming: true,
|
||||
}),
|
||||
url);
|
||||
container.appendChild(link);
|
||||
container.createTextChild(')');
|
||||
return container;
|
||||
}
|
||||
|
||||
@@ -323,7 +323,8 @@
|
||||
"ElementsPanel.js",
|
||||
"ClassesPaneWidget.js",
|
||||
"ElementStatePaneWidget.js",
|
||||
"ElementsTreeElementHighlighter.js"
|
||||
"ElementsTreeElementHighlighter.js",
|
||||
"ImagePreviewPopover.js"
|
||||
],
|
||||
"resources": [
|
||||
"breadcrumbs.css",
|
||||
|
||||
Reference in New Issue
Block a user