diff --git a/.github/lighthouse/lighthouse-config-dev.json b/.github/lighthouse/lighthouse-config-dev.json index 9222decc..90c9b7d1 100644 --- a/.github/lighthouse/lighthouse-config-dev.json +++ b/.github/lighthouse/lighthouse-config-dev.json @@ -27,6 +27,7 @@ "service-worker": "off", "speed-index": "off", "splash-screen": "off", + "target-size": "off", "themed-omnibox": "off", "third-party-facades": "off", "total-byte-weight": "off", diff --git a/.github/lighthouse/lighthouse-config-prod.json b/.github/lighthouse/lighthouse-config-prod.json index 09e21b20..3af526be 100644 --- a/.github/lighthouse/lighthouse-config-prod.json +++ b/.github/lighthouse/lighthouse-config-prod.json @@ -27,6 +27,7 @@ "service-worker": "off", "speed-index": "off", "splash-screen": "off", + "target-size": "off", "themed-omnibox": "off", "third-party-facades": "off", "total-byte-weight": "off", diff --git a/config/techreport.json b/config/techreport.json index b5e087b2..80e1ecc5 100644 --- a/config/techreport.json +++ b/config/techreport.json @@ -47,7 +47,24 @@ }, "config": { "default": { - "app": ["WordPress", "Squarespace", "Drupal"] + "app": ["WordPress", "Squarespace", "Drupal"], + "series": { + "breakdown": "client", + "breakdown_values": [ + { + "name": "desktop", + "color": "#669E8E", + "color_dark": "#fff000", + "suffix": "%" + }, + { + "name": "mobile", + "color": "#BD6EBE", + "color_dark": "#ff00f0", + "suffix": "%" + } + ] + } }, "popular_tech": { "id": "popular_tech", @@ -76,11 +93,13 @@ { "name": "desktop", "color": "#669E8E", + "color_dark": "#fff000", "suffix": "%" }, { "name": "mobile", "color": "#BD6EBE", + "color_dark": "#ff00f0", "suffix": "%" } ] @@ -158,11 +177,13 @@ "values": [ { "name": "desktop", - "color": "#669E8E" + "color": "#669E8E", + "color_dark": "#fff000" }, { "name": "mobile", - "color": "#BD6EBE" + "color": "#BD6EBE", + "color_dark": "#ff00f0" } ], "defaults": [ @@ -339,11 +360,13 @@ { "name": "desktop", "color": "#669E8E", + "color_dark": "#fff000", "suffix": "%" }, { "name": "mobile", "color": "#BD6EBE", + "color_dark": "#ff00f0", "suffix": "%" } ], @@ -477,11 +500,13 @@ "values": [ { "name": "desktop", - "color": "#669E8E" + "color": "#669E8E", + "color_dark": "#fff000" }, { "name": "mobile", - "color": "#BD6EBE" + "color": "#BD6EBE", + "color_dark": "#ff00f0" } ], "defaults": [ @@ -607,12 +632,14 @@ { "name": "desktop", "color": "#669E8E", - "suffix": " bytes" + "suffix": " bytes", + "color_dark": "#fff000" }, { "name": "mobile", "color": "#BD6EBE", - "suffix": " bytes" + "suffix": " bytes", + "color_dark": "#ff00f0" } ], "defaults": [ @@ -669,8 +696,20 @@ "#8E3496", "#CB377C" ], + "app_dark": [ + "#5980EB", + "#D55316", + "#A8A9CA", + "#68A16D", + "#B979A7", + "#AC8D1C", + "#947786", + "#449DB1", + "#C861D2", + "#E24070" + ], "overrides": { - "WordPress": "#ff0000" + "WordPress": "#fff000" } }, "default": { diff --git a/src/js/components/drilldownHeader.js b/src/js/components/drilldownHeader.js index c31b9c8c..99e784dd 100644 --- a/src/js/components/drilldownHeader.js +++ b/src/js/components/drilldownHeader.js @@ -1,3 +1,5 @@ +import { DataUtils } from "../techreport/utils/data"; + function setTitle(title) { const mainTitle = document.querySelector('h2 span.main-title'); mainTitle.textContent = title; @@ -33,7 +35,8 @@ function update(data, filters) { const app = filters.app[0]; if(app) { - setTitle(app); + const formattedApp = DataUtils.formatAppName(app); + setTitle(formattedApp); } if(data[app]) { diff --git a/src/js/components/filters.js b/src/js/components/filters.js index 5ea226bc..260788d8 100644 --- a/src/js/components/filters.js +++ b/src/js/components/filters.js @@ -99,7 +99,7 @@ class Filters { const optionTmpl = document.getElementById('filter-option').content.cloneNode(true); const option = optionTmpl.querySelector('option'); const formattedTech = technology.technology; - option.textContent = technology.technology; + option.textContent = DataUtils.formatAppName(technology.technology); option.value = formattedTech; if(formattedTech === techSelector.getAttribute('data-selected')) { option.selected = true; diff --git a/src/js/techreport/index.js b/src/js/techreport/index.js index 38e715da..4c3a9717 100644 --- a/src/js/techreport/index.js +++ b/src/js/techreport/index.js @@ -1,4 +1,5 @@ import Filters from '../components/filters'; +import { Constants } from './utils/constants'; const { DrilldownHeader } = require("../components/drilldownHeader"); const { DataUtils } = require("./utils/data"); const { UIUtils } = require("./utils/ui"); @@ -25,10 +26,13 @@ class TechReport { this.initializePage(); this.getAllMetricData(); this.bindSettingsListeners(); + this.initializeAccessibility(); } // Initialize the sections for the different pages initializePage() { + this.updateStyling(); + switch(this.pageId) { case 'landing': this.initializeLanding(); @@ -36,6 +40,7 @@ class TechReport { case 'drilldown': this.initializeReport(); + this.getTechInfo(); break; case 'comparison': @@ -44,18 +49,14 @@ class TechReport { } } - // TODO - initializeLanding() { - } - - // TODO - initializeReport() { - // TODO: Move to function + // Load accessibility/themeing info + initializeAccessibility() { + // Show indicators? const showIndicators = localStorage.getItem('showIndicators'); document.querySelector('main').dataset.showIndicators = showIndicators; document.querySelector('#indicators-check').checked = showIndicators === 'true'; - // TODO: Move to function + // Dark or light mode? const theme = localStorage.getItem('haTheme'); document.querySelector('html').dataset.theme = theme; const btn = document.querySelector('.theme-switcher'); @@ -64,7 +65,13 @@ class TechReport { } else if(theme === 'light') { btn.innerHTML = '🌚 Switch to dark theme'; } + } + initializeLanding() { + } + + // TODO + initializeReport() { const sections = document.querySelectorAll('[data-type="section"]'); // TODO: add general config too sections.forEach(section => { @@ -79,7 +86,6 @@ class TechReport { }); this.bindClientListener(); - this.updateStyling(); } // Watch for changes in the client dropdown @@ -174,8 +180,6 @@ class TechReport { }, ]; - const base = 'https://prod-gw-2vzgiib6.ue.gateway.dev/v1'; - const technology = technologies.join('%2C') .replaceAll(" ", "%20"); @@ -186,18 +190,21 @@ class TechReport { technologies.forEach(tech => allResults[tech] = []); Promise.all(apis.map(api => { - const url = `${base}/${api.endpoint}?technology=${technology}&geo=${geo}&rank=${rank}`; + const url = `${Constants.apiBase}/${api.endpoint}?technology=${technology}&geo=${geo}&rank=${rank}`; return fetch(url) .then(result => result.json()) .then(result => { + // Loop through all the rows of the API result result.forEach(row => { const parsedRow = { ...row, } + // Parse the data and add it to the results if(api.parse) { - parsedRow[api.metric] = api.parse(parsedRow[api.metric], parsedRow?.date); + const metric = parsedRow[api.metric] || parsedRow; + parsedRow[api.metric] = api.parse(metric, parsedRow?.date); } const resIndex = allResults[row.technology].findIndex(res => res.date === row.date); @@ -217,6 +224,37 @@ class TechReport { }); } + // Get the information about the selected technology + getTechInfo() { + const technologies = this.filters.app; + const technology = technologies.join('%2C') + .replaceAll(" ", "%20"); + + const url = `${Constants.apiBase}/technologies?technology=${technology}`; + + fetch(url) + .then(result => result.json()) + .then(result => { + const techInfo = result[0]; + + const categoryListEl = document.getElementsByClassName('category-list')[0]; + categoryListEl.innerHTML = ''; + + const categories = techInfo && techInfo.category ? techInfo.category.split(', ') : []; + categories.forEach(category => { + const categoryItemEl = document.createElement('li'); + categoryItemEl.className = 'cell'; + categoryItemEl.textContent = category; + categoryListEl.append(categoryItemEl); + }); + + const descriptionEl = document.createElement('p'); + descriptionEl.className = 'tech-description'; + descriptionEl.textContent = techInfo?.description; + categoryListEl.after(descriptionEl); + }); + } + // Update components and sections that are relevant to the current page updateComponents(data) { switch(this.pageId) { @@ -239,12 +277,11 @@ class TechReport { // Fetch the data for the filter dropdowns getFilterInfo() { const filterData = {}; - const base = 'https://prod-gw-2vzgiib6.ue.gateway.dev/v1'; const filterApis = ['categories', 'technologies', 'ranks', 'geos']; Promise.all(filterApis.map(api => { - const url = `${base}/${api}`; + const url = `${Constants.apiBase}/${api}`; return fetch(url) .then(result => result.json()) diff --git a/src/js/techreport/tableLinked.js b/src/js/techreport/tableLinked.js index cfbd1d2f..a9bb02f5 100644 --- a/src/js/techreport/tableLinked.js +++ b/src/js/techreport/tableLinked.js @@ -1,3 +1,5 @@ +import { DataUtils } from "./utils/data"; + class TableLinked { constructor(id, config, filters, data) { this.id = id; @@ -40,7 +42,8 @@ class TableLinked { cell = document.createElement('th'); const link = document.createElement('a'); link.setAttribute('href', `?tech=${app}`); - link.innerHTML = app; + const formattedApp = DataUtils.formatAppName(app); + link.textContent = formattedApp; cell.append(link); } else if(column.key === 'client') { cell = document.createElement('td'); diff --git a/src/js/techreport/timeseries.js b/src/js/techreport/timeseries.js index 45e3c6ce..07684a00 100644 --- a/src/js/techreport/timeseries.js +++ b/src/js/techreport/timeseries.js @@ -1,4 +1,5 @@ import { Table } from "./table"; +import { DataUtils } from "./utils/data"; import { UIUtils } from "./utils/ui"; class Timeseries { // Create the component @@ -136,7 +137,11 @@ class Timeseries { /* Create a wrapper */ const itemWrapper = document.createElement('div'); itemWrapper.classList.add('breakdown-item'); - itemWrapper.style.setProperty('--breakdown-color', breakdown.color); + + /* Set the breakdown color depending on chosen theme */ + const theme = document.querySelector('html').dataset.theme; + const themeColor = theme === 'dark' ? breakdown.color_dark : breakdown.color; + itemWrapper.style.setProperty('--breakdown-color', themeColor); /* Add a text label to the wrapper */ const breakdownLabel = document.createElement('p'); @@ -153,7 +158,7 @@ class Timeseries { } else { /* Add the value to the wrapper */ const valueLabel = document.createElement('p'); - valueLabel.textContent = `${latestValue}${breakdown.suffix || ''}`; + valueLabel.textContent = `${latestValue.toLocaleString()}${breakdown.suffix || ''}`; valueLabel.classList.add('breakdown-value'); itemWrapper.appendChild(valueLabel); } @@ -193,8 +198,8 @@ class Timeseries { const latestEndpoint = latest[endpoint]; const latestSubcategory = latestEndpoint?.find(row => row.name === subcategory); const latestClient = latestSubcategory?.[client]; - const latestValue = latestClient?.[metric]; - const summaryValue = latestClient?.[summary]; + const latestValue = latestClient?.[metric]?.toLocaleString(); + const summaryValue = latestClient?.[summary]?.toLocaleString(); /* Select the container to which we'll add elements. */ const card = container.querySelector(`[data-app="${app}"]`); @@ -203,7 +208,8 @@ class Timeseries { const value = card.getElementsByClassName('breakdown-value')[0]; /* Update text */ - label.textContent = latest.technology; + const formattedApp = DataUtils.formatAppName(latest.technology); + label.textContent = formattedApp; if(latestValue) { if(summary) { value.textContent = `${summaryValue}`; @@ -343,7 +349,7 @@ class Timeseries { document.getElementsByTagName('main')[0].append(pointSvg); pointSeries.innerHTML = point.series.name; - pointItem.innerHTML = `${pointSvg.outerHTML} ${pointSeries.outerHTML}: ${point.y}`; + pointItem.innerHTML = `${pointSvg.outerHTML} ${pointSeries.outerHTML}: ${point.y.toLocaleString()}`; pointList.appendChild(pointItem); }); @@ -434,6 +440,9 @@ class Timeseries { const category = this.getCategory(config); + // Get color scheme + const theme = document.querySelector('html').dataset.theme; + // Breakdown data by categories defined in config config?.series?.values?.forEach((value, index) => { // Filter by selected client & sort @@ -454,14 +463,18 @@ class Timeseries { const sortedData = formattedData.sort((a, b) => new Date(a.x) - new Date(b.x)); + // Pick color from settings depending on theme const colors = this.defaults(config)?.chart?.colors; + const colorDark = value.color_dark; + const colorLight = value.color; + const seriesColor = theme === "dark" ? colorDark : colorLight; // Push the configurations and formatted data to the series array series.push( { name: value.name, data: sortedData, - color: value.color || colors[index], + color: seriesColor || colors?.[index], lineWidth: 2, } ) diff --git a/src/js/techreport/utils/constants.js b/src/js/techreport/utils/constants.js new file mode 100644 index 00000000..3e1dadcd --- /dev/null +++ b/src/js/techreport/utils/constants.js @@ -0,0 +1,5 @@ +const apiBase = 'https://prod-gw-2vzgiib6.ue.gateway.dev/v1'; + +export const Constants = { + apiBase, +}; diff --git a/src/js/techreport/utils/data.js b/src/js/techreport/utils/data.js index e455b481..46598312 100644 --- a/src/js/techreport/utils/data.js +++ b/src/js/techreport/utils/data.js @@ -60,6 +60,10 @@ const formatBytes = (value) => { return value > 1048576 ? `${Math.round(value / 1048576)} MB` : value > 1024 ? `${Math.round(value / 1024)} KB` : `${submetric.desktop.median_bytes} bytes`; }; +const formatAppName = (app) => { + return app === 'ALL' ? 'All technologies' : app; +} + const parsePageWeightData = (metric, date) => { return metric.map(submetric => { return { @@ -103,4 +107,5 @@ export const DataUtils = { parsePageWeightData, filterDuplicates, getLighthouseScoreCategories, + formatAppName, }; diff --git a/src/js/techreport/utils/ui.js b/src/js/techreport/utils/ui.js index 77d1b938..c7438bb9 100644 --- a/src/js/techreport/utils/ui.js +++ b/src/js/techreport/utils/ui.js @@ -5,6 +5,9 @@ const getAppColor = (tech, technologies, colors) => { const sortedTechs = [...technologies].sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase())); const techIndex = sortedTechs.indexOf(tech); + // Get color scheme + const theme = document.querySelector('html').dataset.theme; + // Return custom colors if configured if(colors.overrides && colors.overrides[tech]) { return colors.overrides[tech]; @@ -12,7 +15,8 @@ const getAppColor = (tech, technologies, colors) => { // Otherwise reutrn based on alphabetic position if(techIndex < colors.app.length) { - return colors.app[techIndex]; + const appColors = theme === "dark" ? colors.app_dark : colors.app; + return appColors[techIndex]; } } diff --git a/static/css/techreport/techreport.css b/static/css/techreport/techreport.css index 4f3e26cc..2cd1d553 100644 --- a/static/css/techreport/techreport.css +++ b/static/css/techreport/techreport.css @@ -29,6 +29,7 @@ --color-page-border: var(--color-teal-medium); --color-tooltip-background: var(--color-card-background); --color-tooltip-border: var(--color-card-border); + --color-nav: #667a7d; /* Font sizes */ --font-size-medium: 1.75rem; @@ -39,6 +40,8 @@ --card-shadow: 0 2px 7px 0px rgba(143, 149, 150, 0.05); --card-radius: 0.25rem; --table-row-hover: var(--color-blue-100); + --color-panel-text: #203b40; + --color-panel-background: #bfe1e7; /* Graph colors */ --graph-color-primary: var(--color-teal-dark); @@ -66,6 +69,9 @@ --color-bg-gradient: #111; --color-page-border: #000; --color-tooltip-border: var(--color-page-background); + --color-panel-text: #bfe1e7; + --color-panel-background: #203b40; + --color-nav: #bfe1e7; /* Graph colors */ --graph-color-labels: #a6bbbe; @@ -86,6 +92,15 @@ main :is(a, p a) { color: var(--color-link); } +main :is(a, p a):is(:hover, :focus) { + color: var(--color-text); + text-decoration-thickness: 2px; +} + +nav { + color: var(--color-nav); +} + /* CTA */ .cta-link { background-color: var(--color-teal-dark); @@ -299,6 +314,10 @@ main :is(a, p a) { outline-offset: 2px; } +.dropdown-content { + z-index: 2; +} + .select-label::before { content: ""; display: block; @@ -330,11 +349,11 @@ main :is(a, p a) { } .select-label:has(select:is(:focus-visible, :hover))::after { - border-color: #f2f9ff; + border-color: var(--color-page-background); } .select-label:has(select:focus-visible) { - background-color: #f2f9ff; + background-color: var(--color-page-background); } .select-label:has(select:focus-visible) label { @@ -370,7 +389,7 @@ main :is(a, p a) { } .tech-selector-group:has(.remove-tech:is(:focus, :hover)) { - background-color: #f2f9ff; + background-color: var(--color-page-background); } .tech-selector-group { @@ -391,7 +410,7 @@ main :is(a, p a) { position: absolute; right: 0; border-radius: 0 4px 4px 0; - z-index: 0; + z-index: 2; } .tech-input-wrapper::after { @@ -407,7 +426,7 @@ main :is(a, p a) { border-left: 1.5px solid var(--color-text-darker); border-radius: 1px; transform: rotate(-45deg); - z-index: 0; + z-index: 3; } .tech-input-wrapper select { @@ -421,11 +440,11 @@ main :is(a, p a) { } .tech-input-wrapper:has(select:is(:focus-visible, :hover))::after { - border-color: #f2f9ff; + border-color: var(--color-page-background); } .tech-input-wrapper:has(select:focus-visible) { - background-color: #f2f9ff; + background-color: var(--color-page-background); color: var(--color-text); } @@ -436,6 +455,7 @@ main :is(a, p a) { [data-theme="dark"] select option, [data-theme="dark"] .tech-input-wrapper:has(select:focus-visible) select { color: var(--color-page-background) !important; + background-color: var(--color-text-darker) !important; } .tech-selector-group .content { @@ -653,11 +673,11 @@ select { } .subcategory-selector-wrapper:has(select:is(:focus-visible, :hover))::after { - border-color: #f2f9ff; + border-color: var(--color-page-background); } .subcategory-selector-wrapper:has(select:focus-visible) { - background-color: #f2f9ff; + background-color: var(--color-page-background); } .subcategory-selector-wrapper:has(select:focus-visible) label { @@ -686,8 +706,8 @@ select { /* Info message */ .info-panel { - background: #bfe1e7; - color: #203b40; + background: var(--color-panel-background); + color: var(--color-panel-text); font-weight: 400; padding: 0.5rem; } @@ -1143,6 +1163,10 @@ path.highcharts-tick { stroke-width: 1px; } +.tooltip-wrapper { + color: var(--color-text); +} + .tooltip-wrapper ul { margin-top: 0.5rem; } diff --git a/templates/techreport/comparison.html b/templates/techreport/comparison.html index f2a2d488..536d18f0 100644 --- a/templates/techreport/comparison.html +++ b/templates/techreport/comparison.html @@ -18,7 +18,7 @@

- {% set filter_tech_title = technologies_str or tech_report_page.filters.app[0] or 'ALL' %} + {% set filter_tech_title = technologies_str or tech_report_page.filters.app[0] or 'All Technologies' %} {% include "techreport/components/filter_info_header.html" %}
diff --git a/templates/techreport/components/filter_info_header.html b/templates/techreport/components/filter_info_header.html index 4de53cd8..0dbdd9cf 100644 --- a/templates/techreport/components/filter_info_header.html +++ b/templates/techreport/components/filter_info_header.html @@ -3,7 +3,7 @@

- {{ filter_tech_title }} + {{ filter_tech_title.replace('ALL', 'All technologies') }}

diff --git a/templates/techreport/components/table.html b/templates/techreport/components/table.html index d0c51eb1..893ce400 100644 --- a/templates/techreport/components/table.html +++ b/templates/techreport/components/table.html @@ -23,7 +23,12 @@ data-metric="{{column.metric}}" class="{{ column.className }}" > - {{ app }} + {% if app == 'ALL' %} + All technologies + {% else %} + {{ app }} + {% endif %} + {% if column.hiddenSuffix %}
{% include "techreport/templates/filters.html" %}
- {% set filter_tech_title = tech_report_page.filters.app[0] or 'ALL' %} + {% set filter_tech_title = tech_report_page.filters.app[0] or 'All technologies' %} {% include "techreport/components/filter_info_header.html" %}
@@ -28,7 +25,7 @@

ALL

    -
  • Placeholder
  • +
  • Uncategorized
diff --git a/templates/techreport/landing.html b/templates/techreport/landing.html index b6be6c66..d57c1518 100644 --- a/templates/techreport/landing.html +++ b/templates/techreport/landing.html @@ -13,7 +13,7 @@

Tech Report

- Placeholder text: A short description about what it is that we display in the Core Web Vitals report, and how it can be used. There should probably be links here to the individual technology pages and a few interesting comparison pages, as well as to the methodology page and the discussions etc like the reports have today. + Track and compare web technology performance and adoption. Real-world Core Web Vitals performance data is provided by the Chrome UX Report at the origin level, and technologies are identified by HTTP Archive on the home page and one interior page.

diff --git a/templates/techreport/techreport.html b/templates/techreport/techreport.html index 4ce5d4f2..6ffef20b 100644 --- a/templates/techreport/techreport.html +++ b/templates/techreport/techreport.html @@ -11,13 +11,6 @@ {% endblock %} {% block report_navigation %} -
-
-

Beta version

-

This dashboard is still under development.

-
-
- + +
+
+

Beta version

+

This dashboard is still under development.

+
+
{% endblock %} {% block main %} diff --git a/templates/techreport/templates/filters.html b/templates/techreport/templates/filters.html index bfbdd0a1..cb00a5e7 100644 --- a/templates/techreport/templates/filters.html +++ b/templates/techreport/templates/filters.html @@ -11,6 +11,10 @@ {% for technology in technologies %} {% set key = loop.index %} {% set technology = technology %} + {% set technologyName = technology %} + {% if technology == "ALL" %} + {% set technologyName = "All technologies" %} + {% endif %} {% set name = "tech-" + key|string %} {% include "techreport/templates/selector_tech.html" %} {% endfor %} diff --git a/templates/techreport/templates/selector_tech.html b/templates/techreport/templates/selector_tech.html index 4608857c..9abd970f 100644 --- a/templates/techreport/templates/selector_tech.html +++ b/templates/techreport/templates/selector_tech.html @@ -14,7 +14,7 @@