devtools: enable sticky header, top bar, and report ui features (#9023)

This commit is contained in:
cjamcl 2019-05-30 00:42:08 -07:00 коммит произвёл GitHub
Родитель ffaea60094
Коммит a11dec42ce
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
8 изменённых файлов: 140 добавлений и 83 удалений

Просмотреть файл

@ -91,17 +91,6 @@ class ReportRenderer {
return el;
}
/**
* @return {Element}
*/
_renderReportShortHeader() {
const shortHeaderContainer = this._dom.createElement('div', 'lh-header-container');
const wrapper = this._dom.cloneTemplate('#tmpl-lh-scores-wrapper', this._templateContext);
shortHeaderContainer.appendChild(wrapper);
return shortHeaderContainer;
}
/**
* @param {LH.ReportResult} report
* @return {DocumentFragment}
@ -195,16 +184,8 @@ class ReportRenderer {
* @return {DocumentFragment}
*/
_renderReport(report) {
let header;
const headerContainer = this._dom.createElement('div');
if (this._dom.isDevTools()) {
headerContainer.classList.add('lh-header-plain');
header = this._renderReportShortHeader();
} else {
headerContainer.classList.add('lh-header-sticky');
header = this._renderReportHeader();
}
headerContainer.appendChild(header);
headerContainer.appendChild(this._renderReportHeader());
const container = this._dom.createElement('div', 'lh-container');
const reportSection = container.appendChild(this._dom.createElement('div', 'lh-report'));
@ -242,38 +223,27 @@ class ReportRenderer {
wrapper.appendChild(renderer.render(category, report.categoryGroups));
}
const reportFragment = this._dom.createFragment();
const topbarDocumentFragment = this._renderReportTopbar(report);
reportFragment.appendChild(topbarDocumentFragment);
if (scoreHeader) {
const scoreGauges =
this._renderScoreGauges(report, categoryRenderer, specificCategoryRenderers);
scoreHeader.append(...scoreGauges);
const scoreScale = this._dom.cloneTemplate('#tmpl-lh-scorescale', this._templateContext);
const scoresContainer = this._dom.find('.lh-scores-container', headerContainer);
scoreHeader.append(
...this._renderScoreGauges(report, categoryRenderer, specificCategoryRenderers));
scoresContainer.appendChild(scoreHeader);
scoresContainer.appendChild(scoreScale);
}
reportSection.appendChild(this._renderReportFooter(report));
const reportFragment = this._dom.createFragment();
if (!this._dom.isDevTools()) {
const topbarDocumentFragment = this._renderReportTopbar(report);
reportFragment.appendChild(topbarDocumentFragment);
}
if (scoreHeader && !this._dom.isDevTools()) {
const stickyHeader = this._dom.createElement('div', 'lh-sticky-header');
this._dom.createChildOf(stickyHeader, 'div', 'lh-highlighter');
const scoreGauges =
this._renderScoreGauges(report, categoryRenderer, specificCategoryRenderers);
stickyHeader.append(...scoreGauges);
stickyHeader.append(
...this._renderScoreGauges(report, categoryRenderer, specificCategoryRenderers));
reportFragment.appendChild(stickyHeader);
}
reportFragment.appendChild(headerContainer);
reportFragment.appendChild(container);
reportSection.appendChild(this._renderReportFooter(report));
return reportFragment;
}

Просмотреть файл

@ -44,6 +44,8 @@ class ReportUIFeatures {
this._dom = dom;
/** @type {Document} */
this._document = this._dom.document();
/** @type {ParentNode} */
this._templateContext = this._dom.document();
/** @type {boolean} */
this._copyAttempt = false;
/** @type {HTMLElement} */
@ -76,9 +78,8 @@ class ReportUIFeatures {
* @param {LH.Result} report
*/
initFeatures(report) {
if (this._dom.isDevTools()) return;
this.json = report;
this._setupMediaQueryListeners();
this._setupExportButton();
this._setupThirdPartyFilter();
@ -86,6 +87,7 @@ class ReportUIFeatures {
this._resetUIState();
this._document.addEventListener('keyup', this.onKeyUp);
this._document.addEventListener('copy', this.onCopy);
const topbarLogo = this._dom.find('.lh-topbar__logo', this._document);
topbarLogo.addEventListener('click', () => this._toggleDarkTheme());
@ -98,13 +100,9 @@ class ReportUIFeatures {
const scoresAll100 = Object.values(report.categories).every(cat => cat.score === 1);
const hasAllCoreCategories =
Object.keys(report.categories).filter(id => !Util.isPluginCategory(id)).length >= 5;
if (!this._dom.isDevTools() && scoresAll100 && hasAllCoreCategories) {
if (scoresAll100 && hasAllCoreCategories) {
turnOffTheLights = true;
const scoresContainer = this._dom.find('.lh-scores-container', this._document);
scoresContainer.classList.add('score100');
scoresContainer.addEventListener('click', _ => {
scoresContainer.classList.toggle('fireworks-paused');
});
this._enableFireworks();
}
if (turnOffTheLights) {
@ -114,8 +112,21 @@ class ReportUIFeatures {
// There is only a sticky header when at least 2 categories are present.
if (Object.keys(this.json.categories).length >= 2) {
this._setupStickyHeaderElements();
this._document.addEventListener('scroll', this._updateStickyHeaderOnScroll);
window.addEventListener('resize', this._updateStickyHeaderOnScroll);
const containerEl = this._dom.find('.lh-container', this._document);
const elToAddScrollListener = this._getScrollParent(containerEl);
elToAddScrollListener.addEventListener('scroll', this._updateStickyHeaderOnScroll);
// Use ResizeObserver where available.
// TODO: there is an issue with incorrect position numbers and, as a result, performance
// issues due to layout thrashing.
// See https://github.com/GoogleChrome/lighthouse/pull/9023/files#r288822287 for details.
// For now, limit to DevTools.
if (this._dom.isDevTools()) {
const resizeObserver = new window.ResizeObserver(this._updateStickyHeaderOnScroll);
resizeObserver.observe(containerEl);
} else {
window.addEventListener('resize', this._updateStickyHeaderOnScroll);
}
}
// Show the metric descriptions by default when there is an error.
@ -128,6 +139,43 @@ class ReportUIFeatures {
}
}
/**
* Define a custom element for <templates> to be extracted from. For example:
* this.setTemplateContext(new DOMParser().parseFromString(htmlStr, 'text/html'))
* @param {ParentNode} context
*/
setTemplateContext(context) {
this._templateContext = context;
}
/**
* Finds the first scrollable ancestor of `element`. Falls back to the document.
* @param {HTMLElement} element
* @return {Node}
*/
_getScrollParent(element) {
const {overflowY} = window.getComputedStyle(element);
const isScrollable = overflowY !== 'visible' && overflowY !== 'hidden';
if (isScrollable) {
return element;
}
if (element.parentElement) {
return this._getScrollParent(element.parentElement);
}
return document;
}
_enableFireworks() {
const scoresContainer = this._dom.find('.lh-scores-container', this._document);
scoresContainer.classList.add('score100');
scoresContainer.addEventListener('click', _ => {
scoresContainer.classList.toggle('fireworks-paused');
});
}
/**
* Fires a custom DOM event on target.
* @param {string} name Name of the event.
@ -188,13 +236,13 @@ class ReportUIFeatures {
if (thirdPartyRows.size === urlItems.length || !thirdPartyRows.size) return;
// create input box
const filterTemplate = this._dom.cloneTemplate('#tmpl-lh-3p-filter', this._document);
const filterTemplate = this._dom.cloneTemplate('#tmpl-lh-3p-filter', this._templateContext);
const filterInput = this._dom.find('input', filterTemplate);
const id = `lh-3p-filter-label--${index}`;
filterInput.id = id;
filterInput.addEventListener('change', e => {
// Remove rows from the dom and keep track of them to readd on uncheck.
// Remove rows from the dom and keep track of them to re-add on uncheck.
// Why removing instead of hiding? To keep nth-child(even) background-colors working.
if (e.target instanceof HTMLInputElement && !e.target.checked) {
for (const row of thirdPartyRows.values()) {
@ -263,7 +311,10 @@ class ReportUIFeatures {
this.topbarEl = this._dom.find('.lh-topbar', this._document);
this.scoreScaleEl = this._dom.find('.lh-scorescale', this._document);
this.stickyHeaderEl = this._dom.find('.lh-sticky-header', this._document);
this.highlightEl = this._dom.find('.lh-highlighter', this._document);
// Position highlighter at first gauge; will be transformed on scroll.
const firstGauge = this._dom.find('.lh-gauge__wrapper', this.stickyHeaderEl);
this.highlightEl = this._dom.createChildOf(firstGauge, 'div', 'lh-highlighter');
}
/**
@ -554,7 +605,15 @@ class ReportUIFeatures {
* @param {boolean} [force]
*/
_toggleDarkTheme(force) {
this._document.body.classList.toggle('dark', force);
const el = this._dom.find('.lh-vars', this._document);
// This seems unnecessary, but in DevTools, passing "undefined" as the second
// parameter acts like passing "false".
// https://github.com/ChromeDevTools/devtools-frontend/blob/dd6a6d4153647c2a4203c327c595692c5e0a4256/front_end/dom_extension/DOMExtension.js#L809-L819
if (typeof force === 'undefined') {
el.classList.toggle('dark');
} else {
el.classList.toggle('dark', force);
}
}
_updateStickyHeaderOnScroll() {
@ -574,11 +633,12 @@ class ReportUIFeatures {
// Category order matches gauge order in sticky header.
const gaugeWrapperEls = this.stickyHeaderEl.querySelectorAll('.lh-gauge__wrapper');
const gaugeToHighlight = gaugeWrapperEls[highlightIndex];
const offset = gaugeToHighlight.getBoundingClientRect().left + 'px';
const origin = gaugeWrapperEls[0].getBoundingClientRect().left;
const offset = gaugeToHighlight.getBoundingClientRect().left - origin;
// Mutate at end to avoid layout thrashing.
this.highlightEl.style.transform = `translate(${offset}px)`;
this.stickyHeaderEl.classList.toggle('lh-sticky-header--visible', showStickyHeader);
this.highlightEl.style.left = offset;
}
}

Просмотреть файл

@ -261,6 +261,24 @@
--audit-indent: 16px;
--expandable-indent: 16px;
--gauge-circle-size-big: 72px;
--gauge-circle-size: 64px;
--audits-margin-bottom: 20px;
--env-name-min-width: 120px;
--header-padding: 16px 0 16px 0;
--plugin-icon-size: 75%;
--pwa-icon-margin: 0 7px 0 -3px;
--score-container-width: 92px;
--score-number-font-size-big: 34px;
--score-number-font-size: 26px;
--score-shape-margin-left: 2px;
--score-shape-size: 10px;
--score-title-font-size-big: 22px;
--score-title-font-size: 14px;
--score-title-line-height-big: 26px;
--score-title-line-height: 20px;
--lh-audit-vpadding: 4px;
--lh-audit-hgap: 12px;
--lh-audit-group-vpadding: 12px;
@ -555,6 +573,9 @@
.lh-column:first-of-type {
margin-right: 0px;
}
.lh-column:first-of-type .lh-metric:last-of-type {
border-bottom: 0;
}
}
@ -952,22 +973,6 @@
}
/* Report */
.lh-header-sticky {
/** TODO: Redesigned report has a small sticky header.
For now, disable the current sticky behavior. */
/* position: -webkit-sticky;
position: sticky; */
top: 0;
width: 100%;
min-width: var(--report-min-width);
z-index: 2;
will-change: transform;
}
.lh-header-plain {
margin-top: var(--section-padding);
}
.lh-list > div:not(:last-child) {
padding-bottom: 20px;
}

Просмотреть файл

@ -159,7 +159,6 @@ limitations under the License.
.lh-scores-container {
display: flex;
flex-direction: column;
margin-top: var(--topbar-height);
padding: var(--header-padding);
position: relative;
width: 100%;
@ -171,12 +170,12 @@ limitations under the License.
--plugin-icon-size: 75%;
--score-container-width: 60px;
--score-number-font-size: 13px;
position: fixed;
position: sticky;
left: 0;
right: 0;
top: var(--topbar-height);
font-weight: 700;
display: flex;
display: none;
justify-content: center;
background-color: var(--color-sticky-header-bg);
border-bottom: 1px solid var(--color-black-200);
@ -184,12 +183,17 @@ limitations under the License.
padding-bottom: 4px;
z-index: 1;
pointer-events: none;
opacity: 0;
}
.lh-sticky-header--visible {
display: flex;
pointer-events: auto;
opacity: 1;
}
/* Disable the gauge arc animation for the sticky header, so toggling display: none
does not play the animation. */
.lh-sticky-header .lh-gauge-arc {
animation: none;
}
.lh-sticky-header .lh-gauge__label {
@ -201,8 +205,11 @@ limitations under the License.
height: 1px;
background: var(--color-highlighter-bg);
position: absolute;
bottom: -1px;
left: 0;
bottom: -5px;
}
.lh-gauge__wrapper:first-of-type {
contain: none;
}
</style>
<div class="lh-scores-wrapper">
@ -219,7 +226,7 @@ limitations under the License.
<template id="tmpl-lh-topbar">
<style>
.lh-topbar {
position: fixed;
position: sticky;
top: 0;
left: 0;
right: 0;
@ -256,6 +263,15 @@ limitations under the License.
cursor: pointer;
margin-right: 5px;
}
/*
Some features in the top right drop down menu don't work in the DevTools
client. They could with some tweaks, but currently they don't. For example:
Saving as HTML/JSON - does not bring up a file dialog, as one would expect in DevTools.
also, it saves the AuditsPanel HTML, which is funky.
*/
.lh-devtools .lh-export__button {
display: none;
}
.lh-export__button svg {
fill: var(--lh-export-icon-color);
}

Просмотреть файл

@ -77,7 +77,7 @@ describe('ReportRenderer', () => {
it('should render a report', () => {
const container = renderer._dom._document.body;
const output = renderer.renderReport(sampleResults, container);
assert.ok(output.querySelector('.lh-header-sticky'), 'has a header');
assert.ok(output.querySelector('.lh-header-container'), 'has a header');
assert.ok(output.querySelector('.lh-report'), 'has report body');
// 3 sets of gauges - one in sticky header, one in scores header, and one in each section.
assert.equal(output.querySelectorAll('.lh-gauge__wrapper, .lh-gauge--pwa__wrapper').length,

Просмотреть файл

@ -2,7 +2,7 @@
"extends": "../tsconfig",
"compilerOptions": {
"typeRoots": [
"@types",
"../node_modules/@types",
"../types",
"./types",
],

Просмотреть файл

@ -87,6 +87,7 @@
"@types/node": "*",
"@types/opn": "^3.0.28",
"@types/raven": "^2.5.1",
"@types/resize-observer-browser": "^0.1.1",
"@types/rimraf": "^2.0.2",
"@types/semver": "^5.5.0",
"@types/uglify-es": "^3.0.0",
@ -183,7 +184,7 @@
},
{
"path": "./dist/viewer/src/viewer.js",
"threshold": "70 Kb"
"threshold": "75 Kb"
}
],
"nyc": {

Просмотреть файл

@ -585,6 +585,11 @@
"@types/events" "*"
"@types/node" "*"
"@types/resize-observer-browser@^0.1.1":
version "0.1.1"
resolved "https://registry.yarnpkg.com/@types/resize-observer-browser/-/resize-observer-browser-0.1.1.tgz#9b7cdae9cdc8b1a7020ca7588018dac64c770866"
integrity sha512-5/bJS/uGB5kmpRrrAWXQnmyKlv+4TlPn4f+A2NBa93p+mt6Ht+YcNGkQKf8HMx28a9hox49ZXShtbGqZkk41Sw==
"@types/rimraf@^2.0.2":
version "2.0.2"
resolved "https://registry.yarnpkg.com/@types/rimraf/-/rimraf-2.0.2.tgz#7f0fc3cf0ff0ad2a99bb723ae1764f30acaf8b6e"