merge
This commit is contained in:
Коммит
7c27f15c0b
11
README.md
11
README.md
|
@ -41,12 +41,19 @@ To start the main server and the notifier backend, run:
|
|||
npm start
|
||||
```
|
||||
|
||||
To start front end code watching, run
|
||||
To start front end code watching (sass, js lint check, babel, minify files), run
|
||||
|
||||
```bash
|
||||
npm run watch
|
||||
```
|
||||
|
||||
Run a fix-lint task (Lint for you on save):
|
||||
|
||||
```bash
|
||||
npm run lint
|
||||
```
|
||||
|
||||
|
||||
There are some developing information in developer-documentation.md.
|
||||
|
||||
##### FCM setup
|
||||
|
@ -74,8 +81,6 @@ Visit http://localhost:8080/admin/blink/populate_blink to see the list of Blink
|
|||
[`settings.py`](https://github.com/GoogleChrome/chromium-dashboard/blob/master/settings.py) contains a list
|
||||
of globals for debugging and running the site locally.
|
||||
|
||||
`VULCANIZE` - `False`, will run the site without vulcanizing the Polymer elements.
|
||||
|
||||
`SEND_EMAIL` - `False` will turn off email notifications to feature owners.
|
||||
|
||||
`SEND_PUSH_NOTIFICATIONS` - `False` will turn off sending push notifications for all users.
|
||||
|
|
151
README.txt
151
README.txt
|
@ -1,151 +0,0 @@
|
|||
** index.html
|
||||
*
|
||||
* The 'Most Popular' page.
|
||||
*
|
||||
* This file is to show 10 charts. They will be charts from
|
||||
* the top 10 Properties of the stack rank page.
|
||||
*
|
||||
* This implementation is not complete.
|
||||
* This depends upon stack rank.
|
||||
*
|
||||
**
|
||||
|
||||
** stackrank.html
|
||||
*
|
||||
* Lists all properties from highest popularity to least
|
||||
* User may reverse order
|
||||
*
|
||||
* Will function, once a date is specified in QueryStackRank of main.py
|
||||
*
|
||||
**
|
||||
|
||||
** main.py
|
||||
*
|
||||
* Handler File
|
||||
*
|
||||
* /data/querystableinstances performs a query to get all instances
|
||||
* from the database. This data is converted to JSON and written to
|
||||
* the page.
|
||||
*
|
||||
* /data/querystackrank performs a query to get most recent day's
|
||||
* popularity rank in default descending order. The data is converted to
|
||||
* JSON and written to the page.
|
||||
*
|
||||
**
|
||||
|
||||
** admin.py
|
||||
*
|
||||
* Handler File
|
||||
*
|
||||
* visiting /cron/metrics calls YesterdayHandler which retrieves yesterday's data from the UMA Cloud Storage.
|
||||
* This is how the cron job is updating - Daily grabbing the previous day's data
|
||||
* The data is parsed and stored as:
|
||||
* class StableInstance(DictModel):
|
||||
* property_name = db.StringProperty();
|
||||
* bucket_id = db.IntegerProperty();
|
||||
* date = db.DateProperty();
|
||||
* day_percentage = db.FloatProperty();
|
||||
* rolling_percentage = db.FloatProperty();
|
||||
*
|
||||
* visiting /cron/histograms calls HistogramsHandler which retrieves FeatureObserver and
|
||||
* FeatureObserver histograms from chromium.googlesource.com.
|
||||
*
|
||||
* ACTION REQUIRED: we will need to replace histogramID with the appropriate ID.
|
||||
* This can be obtained from uma.googleplex.com/data/histograms/ids-chrome-histograms.txt
|
||||
* Searching this file for CSS Properties, once our histogram has data should give us a hex
|
||||
* value which should be converted to an integer.
|
||||
*
|
||||
*
|
||||
|
||||
|
||||
** featurelevel.js
|
||||
*
|
||||
* Creates charts for the feature level page.
|
||||
*
|
||||
* drawVisualization()
|
||||
* This function takes in the name of the property for which the graph is being drawn.
|
||||
* (This should probably be changed to the propertyID/bucketID in the future.)
|
||||
* We iterate through parsed data, building up a data object which we can pass to chart.draw()
|
||||
* The desired form of data to pass to chart.draw() is:
|
||||
* [[Date, Name, Percentage]
|
||||
* [Mar 3, Color, 60]
|
||||
* [Mar 4, Width, 70]]
|
||||
* ** If we need to look into further optimization, building this data structure is probably the place it can happen.
|
||||
*
|
||||
* getNamesArray()
|
||||
* Takes the desired name to have displayed on the chart. Checks if there is a correspinding property (prefixed or unprefixed)
|
||||
* adds the given name plus the corresponding name to an array and returns the array.
|
||||
*
|
||||
**
|
||||
|
||||
|
||||
|
||||
appcfg.py upload_data --config_file=bulkloader.yaml --kind=Feature --url=https://chromestatus.googleplex.com/_ah/remote_api --filename=import.csv
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
OPTIMIZATIONS
|
||||
- show/hide less of panel when user clicks it
|
||||
- recalc style issue perf in chrome 28 (https://src.chromium.org/viewvc/blink?revision=150018&view=revision)
|
||||
-> turn on shadow dom polyfill for stable (e.g. Platform = {flags: {shadow: 'polyfill'}};)
|
||||
- insertBefore issue being fixed: https://code.google.com/p/chromium/issues/detail?id=255734.
|
||||
-> Remove inline <style> in elements.
|
||||
- Calculate a property in a *Changed handler rather than getters. Latter sets
|
||||
up timers if o.Observe() is not present. Former is only calculated once when
|
||||
the prop changes.
|
||||
|
||||
load features from server. could have also done ajax
|
||||
|
||||
elements talk to each other through events and passing chromemetadata in
|
||||
|
||||
color-status does its thing. repurposed in mulitple places
|
||||
|
||||
chrome-metadata that does auto ajax
|
||||
|
||||
render list of features on a filtered view, not entire list.
|
||||
|
||||
ajax-delete link element is pretty coo
|
||||
|
||||
sass workflow
|
||||
|
||||
added tabindex=0 to features so they can be a11yn
|
||||
|
||||
dont publish opened in chromedash-feature. Property reflection to attributes
|
||||
redistribute entire thing in SD polyfill. https://github.com/Polymer/polymer/issues/236
|
||||
|
||||
Way sass was written, selectors match from right to left. So *:hover matched
|
||||
all elements in SD, then filtered on :hover. Change was to be more specific and add tag:
|
||||
|
||||
.views {
|
||||
@include display-flex;
|
||||
@include flex-wrap(wrap);
|
||||
|
||||
& > span { // was & > * {
|
||||
@include display-flex;
|
||||
@include align-items(center);
|
||||
position: relative;
|
||||
...
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
the css in ericbidelman's app is the issue
|
||||
lots of very complex selectors
|
||||
ex. chromedash-feature section .views > :hover::before
|
||||
we match from right to left, which means that can "match" <html>
|
||||
or any descendants
|
||||
so now hovering anything on that page recalcs style the entire document
|
||||
same thing with tapping tapping
|
||||
chromedash-feature section .views > :active::before
|
||||
|
||||
the rest of the slow recalcs are [data-first-of-milestone]:first-of-type { ... } in the css for <chromedash-featurelist>
|
||||
tapping to expand a single row triggers the "invalidate style on all my siblings because of complex selectors" case in Element::recalcStyle
|
||||
|
||||
Solution was to remove one selector.
|
||||
|
||||
&:first-of-type {
|
||||
margin-top: $milestone-label-size + 2;
|
||||
}
|
|
@ -13,10 +13,170 @@ Front end codes exist in two parts: main site (including admin) and http2push.
|
|||
All the pages are rendered in a combination of Django template (`/templates`) and front-end components (`/static/elements`).
|
||||
|
||||
1. `/templates/base.html` and `/templates/base_embed.html` are the html skeleton.
|
||||
1. Templates in `/templates` (extend the `base.html` or `embed_base.html`) are the Django templates for each page.
|
||||
1. Templates in `/templates` (extend the `_base.html` or `_embed_base.html`) are the Django templates for each page.
|
||||
- The folder organization and template file names matches the router. (See `template_path=os.path.join(path + '.html')` in `server.py`)
|
||||
- lit-element components, css, js files are all imported/included in those templates.
|
||||
- We pass backend variables to js like this: `const variableInJs = {{variable_in_template|safe}}`.
|
||||
1. All lit-element components are in `/static/elements`.
|
||||
1. All JavaScript files are in `/static/js-src/` and processed by gulp, then output to '/static/js/' and get included in templates.
|
||||
1. All CSS files are in `/static/sass/` and processed by gulp, then output to `/static/css/` and get included in templates.
|
||||
1. A service worker is created by gulp too. Output in `/static/dist/service-worker.js`.
|
||||
|
||||
## Some nice-to-fix
|
||||
|
||||
- Wrap page in a component, so the data flow is more clear. Benefits: no need to assigns component properties from js files; no need to pass element reference to components to get value (e.g. we are passing searchEl to chromedash-featurelist.).
|
||||
- Some buttons are a / span / div.
|
||||
|
||||
## Content from the old developer doc (some are out-of-date)
|
||||
|
||||
** index.html
|
||||
*
|
||||
* The 'Most Popular' page.
|
||||
*
|
||||
* This file is to show 10 charts. They will be charts from
|
||||
* the top 10 Properties of the stack rank page.
|
||||
*
|
||||
* This implementation is not complete.
|
||||
* This depends upon stack rank.
|
||||
*
|
||||
**
|
||||
|
||||
** stackrank.html
|
||||
*
|
||||
* Lists all properties from highest popularity to least
|
||||
* User may reverse order
|
||||
*
|
||||
* Will function, once a date is specified in QueryStackRank of main.py
|
||||
*
|
||||
**
|
||||
|
||||
** main.py
|
||||
*
|
||||
* Handler File
|
||||
*
|
||||
* /data/querystableinstances performs a query to get all instances
|
||||
* from the database. This data is converted to JSON and written to
|
||||
* the page.
|
||||
*
|
||||
* /data/querystackrank performs a query to get most recent day's
|
||||
* popularity rank in default descending order. The data is converted to
|
||||
* JSON and written to the page.
|
||||
*
|
||||
**
|
||||
|
||||
** admin.py
|
||||
*
|
||||
* Handler File
|
||||
*
|
||||
* visiting /cron/metrics calls YesterdayHandler which retrieves yesterday's data from the UMA Cloud Storage.
|
||||
* This is how the cron job is updating - Daily grabbing the previous day's data
|
||||
* The data is parsed and stored as:
|
||||
* class StableInstance(DictModel):
|
||||
* property_name = db.StringProperty();
|
||||
* bucket_id = db.IntegerProperty();
|
||||
* date = db.DateProperty();
|
||||
* day_percentage = db.FloatProperty();
|
||||
* rolling_percentage = db.FloatProperty();
|
||||
*
|
||||
* visiting /cron/histograms calls HistogramsHandler which retrieves FeatureObserver and
|
||||
* FeatureObserver histograms from chromium.googlesource.com.
|
||||
*
|
||||
* ACTION REQUIRED: we will need to replace histogramID with the appropriate ID.
|
||||
* This can be obtained from uma.googleplex.com/data/histograms/ids-chrome-histograms.txt
|
||||
* Searching this file for CSS Properties, once our histogram has data should give us a hex
|
||||
* value which should be converted to an integer.
|
||||
*
|
||||
*
|
||||
|
||||
|
||||
** featurelevel.js
|
||||
*
|
||||
* Creates charts for the feature level page.
|
||||
*
|
||||
* drawVisualization()
|
||||
* This function takes in the name of the property for which the graph is being drawn.
|
||||
* (This should probably be changed to the propertyID/bucketID in the future.)
|
||||
* We iterate through parsed data, building up a data object which we can pass to chart.draw()
|
||||
* The desired form of data to pass to chart.draw() is:
|
||||
* [[Date, Name, Percentage]
|
||||
* [Mar 3, Color, 60]
|
||||
* [Mar 4, Width, 70]]
|
||||
* ** If we need to look into further optimization, building this data structure is probably the place it can happen.
|
||||
*
|
||||
* getNamesArray()
|
||||
* Takes the desired name to have displayed on the chart. Checks if there is a correspinding property (prefixed or unprefixed)
|
||||
* adds the given name plus the corresponding name to an array and returns the array.
|
||||
*
|
||||
**
|
||||
|
||||
|
||||
|
||||
appcfg.py upload_data --config_file=bulkloader.yaml --kind=Feature --url=https://chromestatus.googleplex.com/_ah/remote_api --filename=import.csv
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
OPTIMIZATIONS
|
||||
- show/hide less of panel when user clicks it
|
||||
- recalc style issue perf in chrome 28 (https://src.chromium.org/viewvc/blink?revision=150018&view=revision)
|
||||
-> turn on shadow dom polyfill for stable (e.g. Platform = {flags: {shadow: 'polyfill'}};)
|
||||
- insertBefore issue being fixed: https://code.google.com/p/chromium/issues/detail?id=255734.
|
||||
-> Remove inline <style> in elements.
|
||||
- Calculate a property in a *Changed handler rather than getters. Latter sets
|
||||
up timers if o.Observe() is not present. Former is only calculated once when
|
||||
the prop changes.
|
||||
|
||||
load features from server. could have also done ajax
|
||||
|
||||
elements talk to each other through events and passing chromemetadata in
|
||||
|
||||
color-status does its thing. repurposed in mulitple places
|
||||
|
||||
chrome-metadata that does auto ajax
|
||||
|
||||
render list of features on a filtered view, not entire list.
|
||||
|
||||
ajax-delete link element is pretty coo
|
||||
|
||||
sass workflow
|
||||
|
||||
added tabindex=0 to features so they can be a11yn
|
||||
|
||||
dont publish opened in chromedash-feature. Property reflection to attributes
|
||||
redistribute entire thing in SD polyfill. https://github.com/Polymer/polymer/issues/236
|
||||
|
||||
Way sass was written, selectors match from right to left. So *:hover matched
|
||||
all elements in SD, then filtered on :hover. Change was to be more specific and add tag:
|
||||
|
||||
.views {
|
||||
@include display-flex;
|
||||
@include flex-wrap(wrap);
|
||||
|
||||
& > span { // was & > * {
|
||||
@include display-flex;
|
||||
@include align-items(center);
|
||||
position: relative;
|
||||
...
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
the css in ericbidelman's app is the issue
|
||||
lots of very complex selectors
|
||||
ex. chromedash-feature section .views > :hover::before
|
||||
we match from right to left, which means that can "match" <html>
|
||||
or any descendants
|
||||
so now hovering anything on that page recalcs style the entire document
|
||||
same thing with tapping tapping
|
||||
chromedash-feature section .views > :active::before
|
||||
|
||||
the rest of the slow recalcs are [data-first-of-milestone]:first-of-type { ... } in the css for <chromedash-featurelist>
|
||||
tapping to expand a single row triggers the "invalidate style on all my siblings because of complex selectors" case in Element::recalcStyle
|
||||
|
||||
Solution was to remove one selector.
|
||||
|
||||
&:first-of-type {
|
||||
margin-top: $milestone-label-size + 2;
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ import swPrecache from 'sw-precache';
|
|||
import * as uglifyEs from 'gulp-uglify-es';
|
||||
const uglify = uglifyEs.default;
|
||||
import gulpLoadPlugins from 'gulp-load-plugins';
|
||||
|
||||
const autoFixTask = require('gulp-eslint-auto-fix')
|
||||
const $ = gulpLoadPlugins();
|
||||
|
||||
function minifyHtml() {
|
||||
|
@ -46,6 +46,11 @@ gulp.task('lint', () => {
|
|||
.pipe($.eslint.failAfterError());
|
||||
});
|
||||
|
||||
autoFixTask('fix-lint', [
|
||||
'static/js-src/*.js',
|
||||
'static/elements/*.js',
|
||||
])
|
||||
|
||||
// Compile and automatically prefix stylesheets
|
||||
gulp.task('styles', () => {
|
||||
const AUTOPREFIXER_BROWSERS = [
|
||||
|
@ -171,8 +176,8 @@ gulp.task('generate-service-worker', () => {
|
|||
gulp.task('watch', gulp.series(
|
||||
'clean',
|
||||
'styles',
|
||||
'lint',
|
||||
'js',
|
||||
'lint',
|
||||
'generate-service-worker',
|
||||
function watch() {
|
||||
gulp.watch(['static/sass/**/*.scss'], gulp.series('styles'));
|
||||
|
@ -184,7 +189,7 @@ gulp.task('watch', gulp.series(
|
|||
gulp.task('default', gulp.series(
|
||||
'clean',
|
||||
'styles',
|
||||
'lint',
|
||||
'js',
|
||||
'lint',
|
||||
'generate-service-worker',
|
||||
));
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
"scripts": {
|
||||
"postinstall": "npm run deps && npm run build",
|
||||
"deps": "pip install -t lib -r requirements.txt",
|
||||
"lint": "gulp lint",
|
||||
"build": "gulp",
|
||||
"lint": "gulp fix-lint",
|
||||
"watch": "gulp watch",
|
||||
"deploy": "./scripts/deploy_site.sh",
|
||||
"start": "./scripts/start_server.sh"
|
||||
|
@ -33,6 +33,7 @@
|
|||
"gulp-babel": "^7.0.1",
|
||||
"gulp-crisper": "^1.1.0",
|
||||
"gulp-eslint": "^5.0.0",
|
||||
"gulp-eslint-auto-fix": "^1.1.0",
|
||||
"gulp-if": "^2.0.1",
|
||||
"gulp-license": "^1.1.0",
|
||||
"gulp-load-plugins": "^1.2.4",
|
||||
|
|
|
@ -8,8 +8,7 @@ const MAX_WEBDEV_VIEW = 6;
|
|||
const MAX_RISK = MAX_VENDOR_VIEW + MAX_WEBDEV_VIEW + MAX_STANDARDS_VAL;
|
||||
|
||||
const IS_PUSH_NOTIFIER_ENABLED = window.PushNotifier.GRANTED_ACCESS;
|
||||
const IS_PUSH_NOTIFIER_SUPPORTED =
|
||||
window.PushNotifier && window.PushNotifier.SUPPORTS_NOTIFICATIONS;
|
||||
const IS_PUSH_NOTIFIER_SUPPORTED = window.PushNotifier.SUPPORTS_NOTIFICATIONS;
|
||||
|
||||
class ChromedashFeature extends LitElement {
|
||||
static get properties() {
|
||||
|
@ -172,9 +171,9 @@ class ChromedashFeature extends LitElement {
|
|||
this._receivePush = !this._receivePush;
|
||||
|
||||
if (this._receivePush) {
|
||||
PushNotifications.subscribeToFeature(featureId);
|
||||
window.PushNotifications.subscribeToFeature(featureId);
|
||||
} else {
|
||||
PushNotifications.unsubscribeFromFeature(featureId);
|
||||
window.PushNotifications.unsubscribeFromFeature(featureId);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -238,16 +237,18 @@ class ChromedashFeature extends LitElement {
|
|||
class="intervention" data-tooltip></iron-icon>
|
||||
</span>
|
||||
` : ''}
|
||||
<span class="tooltip no-push-notifications"
|
||||
title="Receive a push notification when there are updates">
|
||||
<a href="#" @click="${this.subscribeToFeature}" data-tooltip>
|
||||
<iron-icon icon="${this._receivePush ?
|
||||
'chromestatus:notifications' :
|
||||
'chromestatus:notifications-off'}"
|
||||
class="pushicon ${IS_PUSH_NOTIFIER_ENABLED ?
|
||||
'' : 'disabled'}"></iron-icon>
|
||||
</a>
|
||||
</span>
|
||||
${IS_PUSH_NOTIFIER_SUPPORTED ? html`
|
||||
<span class="tooltip"
|
||||
title="Receive a push notification when there are updates">
|
||||
<a href="#" @click="${this.subscribeToFeature}" data-tooltip>
|
||||
<iron-icon icon="${this._receivePush ?
|
||||
'chromestatus:notifications' :
|
||||
'chromestatus:notifications-off'}"
|
||||
class="pushicon ${IS_PUSH_NOTIFIER_ENABLED ?
|
||||
'' : 'disabled'}"></iron-icon>
|
||||
</a>
|
||||
</span>
|
||||
` : ''}
|
||||
<span class="tooltip" title="File a bug against this feature">
|
||||
<a href="${this._newBugUrl}" data-tooltip>
|
||||
<iron-icon icon="chromestatus:bug-report"></iron-icon>
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
import {LitElement, html} from 'https://unpkg.com/@polymer/lit-element@latest/lit-element.js?module';
|
||||
import '/static/elements/chromedash-color-status.js';
|
||||
|
||||
class ChromedashLegend extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
opened: {type: Boolean, reflect: true}, // Used to control visibility. See the css
|
||||
views: {type: Object}, // Assigned in features-page.js, value from Django
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.views = {};
|
||||
}
|
||||
|
||||
toggle() {
|
||||
this.opened = !this.opened;
|
||||
document.body.style.overflow = this.opened ? 'opened' : '';
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<link rel="stylesheet" href="/static/css/elements/chromedash-legend.css">
|
||||
|
||||
<div id="overlay">
|
||||
<h3>About the data</h3>
|
||||
<section class="content-wrapper">
|
||||
<p class="description">What you're looking at is a mostly
|
||||
comprehensive list of web platform features that have landed in
|
||||
Chromium, ordered chronologically by the milestone in which they were
|
||||
added. Features marked "No active development" are being considered or
|
||||
have yet to be started. Features marked "In development" are currently
|
||||
being worked on.</p>
|
||||
<button class="close buttons">
|
||||
<iron-icon icon="chromestatus:close" @click=${this.toggle}></iron-icon>
|
||||
</button>
|
||||
</section>
|
||||
<h3>Color legend</h3>
|
||||
<p>Colors indicate the "interoperability risk" for a given feature. The
|
||||
risk increases as
|
||||
<chromedash-color-status .value="1"
|
||||
.max="${this.views.vendors.length}"></chromedash-color-status> →
|
||||
<chromedash-color-status .value="${this.views.vendors.length}"
|
||||
.max="${this.views.vendors.length}"></chromedash-color-status>, and the
|
||||
color meaning differs for browser vendors, web developers, and the
|
||||
standards process.</p>
|
||||
<section class="views">
|
||||
<div>
|
||||
<label>Browser vendors</label>
|
||||
<ul>
|
||||
${this.views.vendors.map((vendor) => html`
|
||||
<li>
|
||||
<chromedash-color-status .value="${vendor.key}"
|
||||
.max="${this.views.vendors.length}">
|
||||
</chromedash-color-status>
|
||||
<span>${vendor.val}</span></li>
|
||||
`)}
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<label>Web developer</label>
|
||||
<ul>
|
||||
${this.views.webdevs.map((webdev) => html`
|
||||
<li>
|
||||
<chromedash-color-status .value="${webdev.key}"
|
||||
.max="${this.views.webdevs.length}">
|
||||
</chromedash-color-status>
|
||||
<span>${webdev.val}</span></li>
|
||||
`)}
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<label>Standards values</label>
|
||||
<ul>
|
||||
${this.views.standards.map((standard) => html`
|
||||
<li>
|
||||
<chromedash-color-status .value="${standard.key}"
|
||||
.max="${this.views.standards.length}">
|
||||
</chromedash-color-status>
|
||||
<span>${standard.val}</span></li>
|
||||
`)}
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
<h3>Search</h3>
|
||||
<section>
|
||||
<label>Example search queries</label>
|
||||
<ul class="queries">
|
||||
<li>
|
||||
<span>"browsers.chrome.desktop<30"</span>features that landed before 30
|
||||
</li>
|
||||
<li>
|
||||
<span>"browsers.chrome.desktop<=50"</span>features in desktop chrome 50
|
||||
</li>
|
||||
<li>
|
||||
<span>"browsers.chrome.android>40"</span>features that landed on Chrome Android after 40 (milestone types: browsers.chrome.android, browsers.chrome.ios, browsers.chrome.webview)
|
||||
</li>
|
||||
<li>
|
||||
<span>"browsers.chrome.status.val=4"</span>features behind a flag
|
||||
</li>
|
||||
<li>
|
||||
<span>"category:CSS"</span>features in the CSS category
|
||||
</li>
|
||||
<li>
|
||||
<span>"component:Blink>CSS"</span>features under the Blink component "Blink>CSS"
|
||||
</li>
|
||||
<li>
|
||||
<span>"browsers.chrome.owners:user@example.org"</span>features owned by user@example.org
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('chromedash-legend', ChromedashLegend);
|
|
@ -0,0 +1,155 @@
|
|||
import {LitElement, html} from 'https://unpkg.com/@polymer/lit-element@latest/lit-element.js?module';
|
||||
import 'https://unpkg.com/@polymer/iron-icon/iron-icon.js?module';
|
||||
import '/static/elements/chromedash-x-meter.js';
|
||||
|
||||
class ChromedashMetrics extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
type: {type: String}, // From attribute
|
||||
view: {type: String}, // From attribute
|
||||
prod: {type: Boolean}, // From attibute
|
||||
viewList: {type: Array},
|
||||
propertyNameSortIcon: {type: String},
|
||||
percentSortIcon: {type: String},
|
||||
maxPercentage: {type: Number},
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.viewList = [];
|
||||
this.type = '';
|
||||
this.maxPercentage = 100;
|
||||
this.sortOrders = {
|
||||
property_name: {reverse: false, activated: false},
|
||||
percentage: {reverse: true, activated: true},
|
||||
};
|
||||
}
|
||||
|
||||
_fireEvent(eventName, detail) {
|
||||
let event = new CustomEvent(eventName, {detail});
|
||||
this.dispatchEvent(event);
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
const endpoint = `${!this.prod ? 'https://www.chromestatus.com' : ''}/data/${this.type}${this.view}`;
|
||||
fetch(endpoint).then((res) => res.json()).then((response) => {
|
||||
this._updateAfterData(response);
|
||||
});
|
||||
}
|
||||
|
||||
_updateAfterData(items) {
|
||||
this._fireEvent('app-ready');
|
||||
|
||||
if (!items || !items.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let i = 0, item; item = items[i]; ++i) {
|
||||
item.percentage = (item.day_percentage * 100).toFixed(6);
|
||||
}
|
||||
|
||||
this.viewList = items.filter((item) => {
|
||||
return !['ERROR', 'PageVisits', 'PageDestruction'].includes(item.property_name);
|
||||
});
|
||||
|
||||
this.maxPercentage = this.viewList.reduce((accum, currVal) => {
|
||||
return Math.max(accum, currVal.percentage);
|
||||
}, 0);
|
||||
|
||||
if (location.hash) {
|
||||
const hash = decodeURIComponent(location.hash);
|
||||
if (hash) {
|
||||
const el = this.$['stack-rank-list'].querySelector(hash);
|
||||
el.scrollIntoView(true, {behavior: 'smooth'});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sort(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const order = e.target.dataset.order;
|
||||
sortBy_(order, this.viewList);
|
||||
switch (order) {
|
||||
case 'percentage':
|
||||
this.sortOrders.percentage.activated = true;
|
||||
this.sortOrders.percentage.reverse = !this.sortOrders.percentage.reverse;
|
||||
break;
|
||||
case 'property_name':
|
||||
this.sortOrders.property_name.activated = true;
|
||||
this.sortOrders.property_name.reverse = !this.sortOrders.property_name.reverse;
|
||||
break;
|
||||
}
|
||||
|
||||
this._updateSortIcons();
|
||||
if (this.sortOrders[order].reverse) {
|
||||
this.viewList.reverse();
|
||||
}
|
||||
|
||||
// TODO: remove when github.com/Polymer/polymer/issues/2175 is fixed.
|
||||
this.viewList = this.viewList.slice(0);
|
||||
}
|
||||
|
||||
showTimeline(e) {
|
||||
window.location.href = `/metrics/${this.type}/timeline/${this.view}/${e.model.prop.bucket_id}`;
|
||||
}
|
||||
|
||||
_updateSortIcons() {
|
||||
this.propertyNameSortIcon = this._getOrderIcon('property_name');
|
||||
this.percentSortIcon = this._getOrderIcon('percentage');
|
||||
}
|
||||
|
||||
_getOrderIcon(key) {
|
||||
if (!this.sortOrders[key].activated) {
|
||||
return '';
|
||||
}
|
||||
return this.sortOrders[key].reverse ?
|
||||
'chromestatus:arrow-drop-up' : 'chromestatus:arrow-drop-down';
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<link rel="stylesheet" href="/static/css/elements/chromedash-metrics.css">
|
||||
|
||||
<b>Showing <span>${this.viewList.length}</span> properties</b>
|
||||
<ol id="stack-rank-list">
|
||||
<li class="header">
|
||||
<label @click="${this.sort}" data-order="property_name">
|
||||
Property name <iron-icon icon="${this.propertyNameSortIcon}"></iron-icon>
|
||||
</label>
|
||||
<label @click="${this.sort}" data-order="percentage" class="percent_label">
|
||||
Percentage <iron-icon icon="${this.percentSortIcon}"></iron-icon>
|
||||
</label>
|
||||
</li>
|
||||
${this.viewList.map((item) => html`
|
||||
<li id="${item.property_name}"
|
||||
title="${item.property_name}. Click to deep link to this property." tabindex="0">
|
||||
<label>
|
||||
<a href="#${item.property_name}">${item.property_name}</a>
|
||||
</label>
|
||||
<chromedash-x-meter value="${item.percentage}" max="${this.maxPercentage}" @click="${this.showTimeline}"
|
||||
title="Click to see a timeline view of this property"></chromedash-x-meter>
|
||||
</li>
|
||||
`)}
|
||||
</ol>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('chromedash-metrics', ChromedashMetrics);
|
||||
|
||||
const sortBy_ = (propName, arr) => {
|
||||
const compareAsNumbers = propName === 'percentage' || false;
|
||||
arr.sort((a, b) => {
|
||||
const propA = compareAsNumbers ? Number(a[propName]) : a[propName];
|
||||
const propB = compareAsNumbers ? Number(b[propName]) : b[propName];
|
||||
if (propA > propB) {
|
||||
return 1;
|
||||
}
|
||||
if (propA < propB) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
};
|
|
@ -0,0 +1,178 @@
|
|||
import {LitElement, html} from 'https://unpkg.com/@polymer/lit-element@latest/lit-element.js?module';
|
||||
import 'https://unpkg.com/@polymer/iron-icon/iron-icon.js?module';
|
||||
|
||||
class ChromedashSamplePanel extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
categories: {type: Object}, // Edited in static/js/samples.js
|
||||
features: {type: Array}, // Edited in static/js/samples.js
|
||||
filtered: {type: Array}, // Edited in static/js/samples.js
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._loadData();
|
||||
}
|
||||
|
||||
_fireEvent(eventName, detail) {
|
||||
let event = new CustomEvent(eventName, {detail});
|
||||
this.dispatchEvent(event);
|
||||
}
|
||||
|
||||
async _loadData() {
|
||||
// Fire of samples.json XHR right away so data can populate faster.
|
||||
const url = location.hostname == 'localhost' ?
|
||||
'https://www.chromestatus.com/samples.json' : '/samples.json';
|
||||
|
||||
this.features = await (await fetch(url)).json();
|
||||
this.features.forEach((feature) => {
|
||||
feature.sample_links = feature.sample_links.map((link) => {
|
||||
return link.replace(/github.com\/GoogleChrome\/samples\/tree\/gh-pages\/(.*)/i, 'googlechrome.github.io/samples/$1');
|
||||
});
|
||||
});
|
||||
|
||||
this.filtered = this.features;
|
||||
this.filter(this.searchEl ? this.searchEl.value : '');
|
||||
this._fireEvent('update-length', {length: this.filtered.length});
|
||||
}
|
||||
|
||||
_computeIcon(el) {
|
||||
return 'chromestatus:' + (el ? el.dataset.category : '');
|
||||
}
|
||||
|
||||
_computeIconId(categoryStr) {
|
||||
return 'chromestatus:' + this.categories[categoryStr];
|
||||
}
|
||||
|
||||
_computeFeatureLink(id) {
|
||||
return '/features/' + id;
|
||||
}
|
||||
|
||||
_filterOnOperation(features, operator, version) {
|
||||
return features.filter((feature) => {
|
||||
const platformMilestones = [
|
||||
parseInt(feature.shipped_milestone),
|
||||
parseInt(feature.shipped_android_milestone),
|
||||
parseInt(feature.shipped_ios_milestone),
|
||||
parseInt(feature.shipped_webview_milestone),
|
||||
];
|
||||
for (let i = 0, milestone; milestone = platformMilestones[i]; ++i) {
|
||||
if (matchesMilestone_(milestone, operator, version)) {
|
||||
return true; // Only one of the platforms needs to match.
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: move filtering into a behavior. Mostly duped from chromedash-featurelist.html.
|
||||
filter(query, category) {
|
||||
let features = this.features;
|
||||
|
||||
if (!features) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!query && !category) {
|
||||
if (history && history.replaceState) {
|
||||
history.replaceState('', document.title, location.pathname + location.search);
|
||||
} else {
|
||||
location.hash = '';
|
||||
}
|
||||
this.filtered = features;
|
||||
this._fireEvent('update-length', {length: this.filtered.length});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (query) {
|
||||
if (history && history.replaceState) {
|
||||
// TODO debounce this 500ms
|
||||
history.replaceState({}, document.title, '/samples#' + query);
|
||||
}
|
||||
|
||||
// Returns operator and version query e.g. ["<=25", "<=", "25"].
|
||||
const operatorMatch = /^([<>=]=?)\s*([0-9]+)/.exec(query);
|
||||
if (operatorMatch) {
|
||||
features = this._filterOnOperation(features, operatorMatch[1], operatorMatch[2]);
|
||||
} else {
|
||||
const regex = new RegExp(query, 'i');
|
||||
features = features.filter((feature) => {
|
||||
return regex.test(feature.name) || regex.test(feature.summary);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Further refine list based on selected category in menu.
|
||||
if (category) {
|
||||
const regex = new RegExp(category, 'i');
|
||||
features = features.filter((feature) => {
|
||||
return regex.test(feature.category);
|
||||
});
|
||||
}
|
||||
|
||||
this.filtered = features;
|
||||
|
||||
this._fireEvent('update-length', {length: this.filtered.length});
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.filtered) {
|
||||
return html``;
|
||||
}
|
||||
return html`
|
||||
<link rel="stylesheet" href="/static/css/elements/chromedash-sample-panel.css">
|
||||
|
||||
<ul>
|
||||
${this.filtered.map((feature) => html`
|
||||
<li>
|
||||
<div class="card">
|
||||
<h3 class="feature-name">
|
||||
<a href="${this._computeFeatureLink(feature.id)}">${feature.name}</a>
|
||||
<span class="milestone" ?hidden="${!feature.shipped_milestone}">Chrome <span>${feature.shipped_milestone}</span></span>
|
||||
</h3>
|
||||
<div class="summary">${feature.summary}</div>
|
||||
<div class="sample_links">
|
||||
<div class="demo-links layout horizontal center">
|
||||
${feature.sample_links.map((link) => html`
|
||||
<a href="${link}" target="_blank" class="demo-link">
|
||||
<iron-icon icon="chromestatus:open-in-browser"></iron-icon> View Demo
|
||||
</a>
|
||||
`)}
|
||||
</div>
|
||||
<iron-icon icon="${this._computeIconId(feature.category)}"
|
||||
title="${feature.category}"></iron-icon>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
`)}
|
||||
</ul>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('chromedash-sample-panel', ChromedashSamplePanel);
|
||||
|
||||
// TODO: move this into a behavior. It's duplicated from chromedash-featurelist.html.
|
||||
function matchesMilestone_(milestone, operator, version) {
|
||||
switch (operator) {
|
||||
case '<':
|
||||
return milestone < version;
|
||||
break;
|
||||
case '<=':
|
||||
return milestone <= version;
|
||||
break;
|
||||
case '>':
|
||||
return milestone > version;
|
||||
break;
|
||||
case '>=':
|
||||
return milestone >= version;
|
||||
break;
|
||||
case '=': // Support both '=' and '=='.
|
||||
case '==':
|
||||
return milestone == version;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,177 @@
|
|||
import {LitElement, html} from 'https://unpkg.com/@polymer/lit-element@latest/lit-element.js?module';
|
||||
import 'https://unpkg.com/@polymer/iron-icon/iron-icon.js?module';
|
||||
|
||||
const TEMPLATE_CONTENT = {
|
||||
stable: {
|
||||
channelLabel: 'Stable',
|
||||
h1Class: '',
|
||||
downloadUrl: 'https://www.google.com/chrome/browser/desktop/index.html',
|
||||
downloadTitle: 'Download Chrome stable',
|
||||
dateText: 'was',
|
||||
featureHeader: 'Features in this release',
|
||||
},
|
||||
beta: {
|
||||
channelLabel: 'Next up',
|
||||
h1Class: 'chrome_version--beta',
|
||||
downloadUrl: 'https://www.google.com/chrome/browser/beta.html',
|
||||
downloadTitle: 'Download Chrome Beta',
|
||||
dateText: 'between',
|
||||
featureHeader: 'Features planned in this release',
|
||||
},
|
||||
dev: {
|
||||
channelLabel: 'Dev',
|
||||
h1Class: 'chrome_version--dev',
|
||||
downloadUrl: 'https://www.google.com/chrome/browser/canary.html',
|
||||
downloadTitle: 'Download Chrome Canary',
|
||||
dateText: 'coming',
|
||||
featureHeader: 'Features planned in this release',
|
||||
},
|
||||
};
|
||||
|
||||
const REMOVED_STATUS = ['Removed'];
|
||||
const DEPRECATED_STATUS = ['Deprecated', 'No longer pursuing'];
|
||||
const IS_PUSH_NOTIFIER_SUPPORTED = window.PushNotifier.SUPPORTS_NOTIFICATIONS;
|
||||
const IS_PUSH_NOTIFIER_ENABLED = window.PushNotifier.GRANTED_ACCESS;
|
||||
|
||||
class ChromedashSchedule extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
channels: {type: Object}, // Assigned in schedule.js, value from Django
|
||||
hideBlink: {type: Boolean}, // Edited in schedule.js
|
||||
};
|
||||
}
|
||||
|
||||
_objKeys(obj) {
|
||||
if (!obj) {
|
||||
return [];
|
||||
}
|
||||
return Object.keys(obj).sort();
|
||||
}
|
||||
|
||||
_computeDaysUntil(dateStr) {
|
||||
const today = new Date();
|
||||
const diff = this._dateDiffInDays(new Date(dateStr), today);
|
||||
const prefix = diff.future ? 'in' : '';
|
||||
const days = diff.days > 1 ? 's' : '';
|
||||
const ago = !diff.future ? 'ago' : '';
|
||||
return `${prefix} ${diff.days} day${days} ${ago}`;
|
||||
}
|
||||
|
||||
_computeDate(dateStr) {
|
||||
const opts = {/* weekday: 'short', */month: 'short', day: 'numeric'};
|
||||
const date = new Date(dateStr);
|
||||
return new Intl.DateTimeFormat('en-US', opts).format(date);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of days between a and b.
|
||||
* @param {!Date} a
|
||||
* @param {!Date} b
|
||||
* @return {!{days: number, future: boolean}}
|
||||
*/
|
||||
_dateDiffInDays(a, b) {
|
||||
// Discard time and time-zone information.
|
||||
const utc1 = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate());
|
||||
const utc2 = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate());
|
||||
|
||||
const MS_PER_DAY = 1000 * 60 * 60 * 24;
|
||||
const daysDiff = Math.floor((utc2 - utc1) / MS_PER_DAY);
|
||||
return {days: Math.abs(daysDiff), future: daysDiff < 1};
|
||||
}
|
||||
|
||||
_subscribeToFeature(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const iconEl = e.target;
|
||||
const featureId = iconEl.dataset.featureId;
|
||||
const receivePush = iconEl.icon !== 'chromestatus:notifications';
|
||||
iconEl.icon = receivePush ? 'chromestatus:notifications' : 'chromestatus:notifications-off';
|
||||
|
||||
if (receivePush) {
|
||||
window.PushNotifications.subscribeToFeature(featureId);
|
||||
} else {
|
||||
window.PushNotifications.unsubscribeFromFeature(featureId);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.channels) {
|
||||
return html``;
|
||||
}
|
||||
return html`
|
||||
<link rel="stylesheet" href="/static/css/elements/chromedash-schedule.css">
|
||||
|
||||
${['stable', 'beta', 'dev'].map((type) => html`
|
||||
<section class="release ${this.hideBlink ? 'no-components' : ''}">
|
||||
<div class="layout vertical center">
|
||||
<h1 class="channel_label">${TEMPLATE_CONTENT[type].channelLabel}</h1>
|
||||
<h1 class="chrome_version layout horizontal center ${TEMPLATE_CONTENT[type].h1Class}">
|
||||
<span class="chrome-logo"></span>
|
||||
<a href="${TEMPLATE_CONTENT[type].downloadUrl}" title="${TEMPLATE_CONTENT[type].downloadTitle}"
|
||||
target="_blank">Chrome ${this.channels[type].version}</a>
|
||||
</h1>
|
||||
</div>
|
||||
<div class="milestone_info layout horizontal center-center">
|
||||
<h3>
|
||||
<span class="channel_label">Beta</span> ${TEMPLATE_CONTENT[type].dateText}
|
||||
<span class="milestone_info-beta">${this._computeDate(this.channels[type].earliest_beta)} - ${this._computeDate(this.channels[type].latest_beta)}</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="milestone_info layout horizontal center-center">
|
||||
<h3>
|
||||
<span class="channel_label">Stable</span> ${this._computeDaysUntil(this.channels[type].stable_date)}
|
||||
<span class="release-stable">( ${this._computeDate(this.channels[type].stable_date)} )</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="features_list">
|
||||
<div class="features_header">${TEMPLATE_CONTENT[type].featureHeader}:</div>
|
||||
|
||||
${this._objKeys(this.channels[type].components).map((componentName) => html`
|
||||
<h3 class="feature_components">${componentName}</h3>
|
||||
<ul>
|
||||
${this.channels[type].components[componentName].map((f) => html`
|
||||
<li data-feature-id="${f.id}">
|
||||
<a href="/feature/${f.id}">${f.name}</a>
|
||||
<span class="icon_row">
|
||||
${f.browsers.chrome.origintrial ? html`
|
||||
<span class="tooltip" title="Origin Trial">
|
||||
<iron-icon icon="chromestatus:extension" class="experimental" data-tooltip></iron-icon>
|
||||
</span>
|
||||
` : ''}
|
||||
${f.browsers.chrome.intervention ? html`
|
||||
<span class="tooltip" title="Browser intervention">
|
||||
<iron-icon icon="chromestatus:pan-tool" class="intervention" data-tooltip></iron-icon>
|
||||
</span>
|
||||
` : ''}
|
||||
${REMOVED_STATUS.includes(f.browsers.chrome.status.text) ? html`
|
||||
<span class="tooltip" title="Removed">
|
||||
<iron-icon icon="chromestatus:cancel" class="remove" data-tooltip></iron-icon>
|
||||
</span>
|
||||
` : ''}
|
||||
${DEPRECATED_STATUS.includes(f.browsers.chrome.status.text) ? html`
|
||||
<span class="tooltip" title="Deprecated">
|
||||
<iron-icon icon="chromestatus:warning" class="deprecated" data-tooltip></iron-icon>
|
||||
</span>
|
||||
` : ''}
|
||||
${IS_PUSH_NOTIFIER_SUPPORTED ? html`
|
||||
<span class="tooltip" title="Subscribe to notification updates">
|
||||
<iron-icon icon="chromestatus:notifications-off"
|
||||
class="pushicon ${IS_PUSH_NOTIFIER_ENABLED ? '' : 'disabled'}"
|
||||
data-feature-id="${f.id}"
|
||||
@click="${this._subscribeToFeature}"></iron-icon>
|
||||
</span>
|
||||
</span>
|
||||
` : ''}
|
||||
</li>
|
||||
`)}
|
||||
</ul>
|
||||
`)}
|
||||
</div>
|
||||
</section>
|
||||
`)}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('chromedash-schedule', ChromedashSchedule);
|
|
@ -0,0 +1,217 @@
|
|||
import {LitElement, html} from 'https://unpkg.com/@polymer/lit-element@latest/lit-element.js?module';
|
||||
|
||||
class ChromedashTimeline extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
selectedBucketId: {type: Number},
|
||||
showAllHistoricalData: {type: Boolean},
|
||||
timeline: {type: Object}, // Not used?
|
||||
title: {type: String},
|
||||
type: {type: String},
|
||||
view: {type: String},
|
||||
props: {type: Array}, // Directly edited from metrics/css/timeline/popularity and metrics/feature/timeline/popularity
|
||||
useRemoteData: {type: Boolean}, // If true, fetches live data from chromestatus.com instead of localhost.
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.selectedBucketId = 1;
|
||||
this.showAllHistoricalData = false;
|
||||
this.title = '';
|
||||
this.type = '';
|
||||
this.view = '';
|
||||
this.props = [];
|
||||
this.useRemoteData = false;
|
||||
}
|
||||
|
||||
updated(changedProperties) {
|
||||
const TRACKING_PROPERTIES = [
|
||||
'selectedBucketId',
|
||||
'type',
|
||||
'view',
|
||||
'useRemoteData',
|
||||
'showAllHistoricalData',
|
||||
];
|
||||
if (TRACKING_PROPERTIES.some((property) => changedProperties.get(property))) {
|
||||
this._updateTimeline();
|
||||
}
|
||||
}
|
||||
|
||||
updateSelectedBucketId(e) {
|
||||
this.selectedBucketId = e.currentTarget.value;
|
||||
}
|
||||
|
||||
toggleShowAllHistoricalData() {
|
||||
this.showAllHistoricalData = !this.showAllHistoricalData;
|
||||
}
|
||||
|
||||
ready() {
|
||||
window.google.charts.load('current', {'packages': ['corechart']});
|
||||
window.google.charts.setOnLoadCallback(() => {
|
||||
// If there's an id in the URL, load the property it.
|
||||
const lastSlash = location.pathname.lastIndexOf('/');
|
||||
if (lastSlash > 0) {
|
||||
const id = parseInt(location.pathname.substring(lastSlash + 1));
|
||||
if (String(id) != 'NaN') {
|
||||
this.selectedBucketId = id;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
drawVisualization(data, bucketId, showAllHistoricalData) {
|
||||
const table = new google.visualization.DataTable();
|
||||
table.addColumn('date', 'Date');
|
||||
table.addColumn('number', 'Percentage');
|
||||
table.addColumn({type: 'string', role: 'annotation'});
|
||||
table.addColumn({type: 'string', role: 'annotationText'});
|
||||
|
||||
const rowArray = [];
|
||||
for (let i = 0, item; item = data[i]; ++i) {
|
||||
const dateStr = item.date.split('-');
|
||||
const date = new Date();
|
||||
date.setFullYear(parseInt(dateStr[0]), parseInt(dateStr[1]) - 1, parseInt(dateStr[2]));
|
||||
const row = [date, parseFloat((item.day_percentage * 100).toFixed(6))];
|
||||
// Add annotation where UMA data switched to new stuff.
|
||||
if (item.date === '2017-10-27') {
|
||||
row.push('A', 'Modernized metrics');
|
||||
} else {
|
||||
row.push(null, null);
|
||||
}
|
||||
rowArray.push(row);
|
||||
}
|
||||
|
||||
table.addRows(rowArray);
|
||||
|
||||
table = window.google.visualization.data.group(table,
|
||||
[{column: 0, modifier: new Date(date.getFullYear(), date.getMonth()), type: 'date'}],
|
||||
[{
|
||||
column: 1,
|
||||
aggregation: window.google.visualization.data.avg,
|
||||
type: 'number',
|
||||
label: 'Monthly Average',
|
||||
}],
|
||||
[{column: 2, type: 'string'}]
|
||||
);
|
||||
|
||||
const formatter = new window.google.visualization.NumberFormat({fractionDigits: 6});
|
||||
formatter.format(table, 1); // Apply formatter to percentage column.
|
||||
|
||||
let view = table;
|
||||
|
||||
if (!showAllHistoricalData) {
|
||||
const startYear = (new Date()).getFullYear() - 2; // Show only 2 years of data by default.
|
||||
view = new window.google.visualization.DataView(table);
|
||||
view.setRows(view.getFilteredRows([{column: 0, minValue: new Date(startYear, 0, 1)}]));
|
||||
}
|
||||
|
||||
const chart = new window.google.visualization.LineChart(this.$.chart);
|
||||
chart.draw(view, {
|
||||
// title: this.title,
|
||||
// subtitle: 'all channels and platforms',
|
||||
backgroundColor: 'white',
|
||||
legend: {position: 'none'},
|
||||
curveType: 'function',
|
||||
vAxis: {
|
||||
title: '% page loads',
|
||||
// maxValue: 100,
|
||||
minValue: 0,
|
||||
},
|
||||
hAxis: {
|
||||
title: 'Date',
|
||||
format: 'M/yy',
|
||||
},
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
pointSize: 4,
|
||||
series: {0: {color: '#4580c0'}},
|
||||
trendlines: {
|
||||
0: {
|
||||
type: 'linear',
|
||||
opacity: 0.5,
|
||||
color: '#bdd6ed',
|
||||
pointsVisible: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
_updateTimeline() {
|
||||
if (this.selectedBucketId === 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const prefix = this.useRemoteData ? 'https://www.chromestatus.com' : '';
|
||||
|
||||
const url = prefix + '/data/timeline/' + this.type + this.view +
|
||||
'?bucket_id=' + this.selectedBucketId;
|
||||
|
||||
this._renderHTTPArchiveData();
|
||||
|
||||
fetch(url).then((res) => res.json()).then((response) => {
|
||||
this.drawVisualization(response, this.selectedBucketId, this.showAllHistoricalData);
|
||||
});
|
||||
|
||||
if (history.pushState) {
|
||||
const url = '/metrics/' + this.type + '/timeline/' + this.view + '/' +
|
||||
this.selectedBucketId;
|
||||
history.pushState({id: this.selectedBucketId}, '', url);
|
||||
}
|
||||
}
|
||||
|
||||
_renderHTTPArchiveData() {
|
||||
if (!this.props.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const feature = this.props.find((el) => el[0] === parseInt(this.selectedBucketId));
|
||||
if (feature) {
|
||||
const featureName = feature[1];
|
||||
const REPORT_ID = '1M8kXOqPkwYNKjJhtag_nvDNJCpvmw_ri';
|
||||
const dsEmbedUrl = `https://datastudio.google.com/embed/reporting/${REPORT_ID}/page/tc5b?config=%7B"df3":"include%25EE%2580%25800%25EE%2580%2580IN%25EE%2580%2580${featureName}"%7D`;
|
||||
this.$.httparchivedata.src = dsEmbedUrl;
|
||||
|
||||
this.$.bigquery.textContent = `#standardSQL
|
||||
SELECT yyyymmdd, client, pct_urls, sample_urls
|
||||
FROM \`httparchive.blink_features.usage\`
|
||||
WHERE feature = '${featureName}'
|
||||
ORDER BY yyyymmdd DESC, client`;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<link rel="stylesheet" href="/static/css/elements/chromedash-timeline.css">
|
||||
|
||||
<select .value="${this.selectedBucketId}" @change="${this.updateSelectedBucketId}">
|
||||
<option disabled value="1">Select a property</option>
|
||||
${this.props.map((prop) => html`
|
||||
<option value="${prop[0]}">${prop[1]}</option>
|
||||
`)}
|
||||
</select>
|
||||
<label>Show all historical data: <input type="checkbox" ?checked="${this.showAllHistoricalData}" @change="${this.toggleShowAllHistoricalData}"></label>
|
||||
<h3 id="usage" class="header_title">Percentage of page loads that use this feature</h3>
|
||||
<p class="description">The chart below shows the percentage of page loads (in Chrome) that use
|
||||
this feature at least once. Data is across all channels and platforms.
|
||||
</p>
|
||||
<div id="chart"></div>
|
||||
<p class="callout">
|
||||
<b>Note</b>: on 2017-10-26 the underlying metrics were switched over to a newer collection system
|
||||
which is <a href="https://groups.google.com/a/chromium.org/forum/#!msg/blink-api-owners-discuss/IpIkbz0qtrM/HUCfSMv2AQAJ" target="_blank">more accurate</a>.
|
||||
This is also the reason for the abrupt spike around 2017-10-26.
|
||||
</p>
|
||||
<h3 id="httparchive" class="header_title">Adoption of the feature on top sites</h3>
|
||||
<p class="description">The chart below shows the adoption of the feature by the top URLs on the internet. Data from <a href="https://httparchive.org/" target="blank">HTTP Archive</a>.</p>
|
||||
<iframe id="httparchivedata"></iframe>
|
||||
<p class="callout">
|
||||
<b>Note</b>: The jump around July and December 2018 are because the corpus of URLs crawled by HTTP Archive increased. These jumps have no correlation with the jump in the top graph.
|
||||
See the <a href="https://discuss.httparchive.org/t/changes-to-the-http-archive-corpus/1539" target="_blank">announcement</a> for more details.
|
||||
</p>
|
||||
<p class="description">Copy and run this command in <a href="https://bigquery.cloud.google.com" target="_blank">BigQuery</a> to produce similar results:</p>
|
||||
<pre id="bigquery"></pre>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('chromedash-timeline', ChromedashTimeline);
|
|
@ -0,0 +1,86 @@
|
|||
import {LitElement, html} from 'https://unpkg.com/@polymer/lit-element@latest/lit-element.js?module';
|
||||
|
||||
// Keeps track of the toast currently opened.
|
||||
let _currentToast = null;
|
||||
|
||||
class ChromedashToast extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
msg: {type: String}, // The toast's message.
|
||||
actionLabel: {type: String}, // A label for the call to action of the toast.
|
||||
duration: {type: Number}, // The duration in milliseconds to show the toast. -1 or `Infinity`, to disable the toast auto-closing.
|
||||
open: {type: Boolean, reflect: true},
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.msg = '';
|
||||
this.actionLabel = '';
|
||||
this.duration = 7000;
|
||||
this.open = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a message in the toast.
|
||||
* @method showMessage
|
||||
* @param {string} msg Message to show.Notification (event) type.
|
||||
* @param {string} optAction Optional action link.
|
||||
* @param {function} optTapHandler Optional handler to execute when the
|
||||
* toast action is tapped.
|
||||
* @param {Number} optDuration Optional duration to show the toast for.
|
||||
*/
|
||||
showMessage(msg, optAction, optTapHandler, optDuration) {
|
||||
this.msg = msg;
|
||||
this.actionLabel = optAction;
|
||||
this.shadowRoot.querySelector('#action').addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
if (optTapHandler) {
|
||||
optTapHandler();
|
||||
}
|
||||
}, {once: true});
|
||||
|
||||
// Override duration just for this toast.
|
||||
const originalDuration = this.duration;
|
||||
if (typeof optDuration !== 'undefined') {
|
||||
this.duration = optDuration;
|
||||
}
|
||||
|
||||
this.open = true;
|
||||
|
||||
this.duration = originalDuration; // reset site-wide duration.
|
||||
}
|
||||
|
||||
close() {
|
||||
this.open = false;
|
||||
}
|
||||
|
||||
_openChanged() {
|
||||
clearTimeout(this._timerId);
|
||||
|
||||
if (this.open) {
|
||||
// Close existing toast if one is showing.
|
||||
if (_currentToast && _currentToast !== this) {
|
||||
_currentToast.close();
|
||||
}
|
||||
_currentToast = this;
|
||||
|
||||
if (this.duration >= 0) {
|
||||
this._timerId = setTimeout(() => this.close(), this.duration);
|
||||
}
|
||||
} else if (_currentToast === this) {
|
||||
_currentToast = null;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<link rel="stylesheet" href="/static/css/elements/chromedash-toast.css">
|
||||
|
||||
<span id="msg">${this.msg}</span>
|
||||
<a href="#" id="action">${this.actionLabel}</a>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('chromedash-toast', ChromedashToast);
|
|
@ -0,0 +1,33 @@
|
|||
import {LitElement, html} from 'https://unpkg.com/@polymer/lit-element@latest/lit-element.js?module';
|
||||
|
||||
class ChromedashXMeter extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
value: {type: Number},
|
||||
valueFormatted: {type: Number},
|
||||
max: {type: Number}, // Normalized, maximum width for the bar
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.value = 0;
|
||||
this.max = 100;
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
this.valueFormatted = this.value <= 0.000001 ? '<=0.000001%' : this.value + '%';
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<link rel="stylesheet" href="/static/css/elements/chromedash-x-meter.css">
|
||||
|
||||
<div style="width: ${(this.value / this.max * 100)}%">
|
||||
<span>${this.valueFormatted}</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('chromedash-x-meter', ChromedashXMeter);
|
|
@ -0,0 +1,71 @@
|
|||
// Event handler. Used in feature.html template.
|
||||
const subscribeToFeature = (featureId) => { // eslint-disable-line no-unused-vars
|
||||
const iconEl = document.querySelector('.pushicon');
|
||||
if (iconEl.icon === 'chromestatus:notifications') {
|
||||
iconEl.icon = 'chromestatus:notifications-off';
|
||||
PushNotifications.unsubscribeFromFeature(featureId);
|
||||
} else {
|
||||
iconEl.icon = 'chromestatus:notifications';
|
||||
PushNotifications.subscribeToFeature(featureId);
|
||||
}
|
||||
};
|
||||
|
||||
// Event handler. Used in feature.html template.
|
||||
const shareFeature = () => { // eslint-disable-line no-unused-vars
|
||||
if (navigator.share) {
|
||||
const url = '/feature/' + FEATURE_ID;
|
||||
navigator.share({
|
||||
title: FEATURE_NAME,
|
||||
text: FEATUER_SUMMARY,
|
||||
url: url,
|
||||
}).then(() => {
|
||||
ga('send', 'social',
|
||||
{
|
||||
'socialNetwork': 'web',
|
||||
'socialAction': 'share',
|
||||
'socialTarget': url,
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Remove loading spinner at page load.
|
||||
document.body.classList.remove('loading');
|
||||
|
||||
// Unhide "Web Share" feature if browser supports it.
|
||||
if (navigator.share) {
|
||||
Array.from(document.querySelectorAll('.no-web-share')).forEach((el) => {
|
||||
el.classList.remove('no-web-share');
|
||||
});
|
||||
}
|
||||
|
||||
// Unhide notification features if browser supports it.
|
||||
if (PushNotifier.SUPPORTS_NOTIFICATIONS) {
|
||||
document.querySelector('.push-notifications').removeAttribute('hidden');
|
||||
|
||||
// Lazy load Firebase messaging SDK.
|
||||
loadFirebaseSDKLibs().then(() => {
|
||||
PushNotifications.init(); // init Firebase messaging.
|
||||
|
||||
// If use already granted the notification permission, update state of the
|
||||
// push icon for each feature the user is subscribed to.
|
||||
if (PushNotifier.GRANTED_ACCESS) {
|
||||
PushNotifications.getAllSubscribedFeatures().then((subscribedFeatures) => {
|
||||
const iconEl = document.querySelector('.pushicon');
|
||||
if (subscribedFeatures.includes(FEATURE_ID)) {
|
||||
iconEl.icon = 'chromestatus:notifications';
|
||||
} else {
|
||||
iconEl.icon = 'chromestatus:notifications-off';
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (SHOW_TOAST) {
|
||||
setTimeout(() => {
|
||||
const toastEl = document.querySelector('chromedash-toast');
|
||||
toastEl.showMessage('Your feature was saved! It may take a few minutes to ' +
|
||||
'show up in the main list.', null, null, -1);
|
||||
}, 500);
|
||||
}
|
|
@ -1,8 +1,7 @@
|
|||
const featureListEl = document.querySelector('chromedash-featurelist');
|
||||
const chromeMetadataEl = document.querySelector('chromedash-metadata');
|
||||
const searchEl = document.querySelector('.search input');
|
||||
// const overlayEl = document.querySelector('chromedash-legend');
|
||||
// let overlayLoaded = false;
|
||||
const legendEl = document.querySelector('chromedash-legend');
|
||||
|
||||
// Set search box to URL deep link.
|
||||
if (location.hash) {
|
||||
|
@ -96,7 +95,10 @@ featureListEl.addEventListener('app-ready', () => {
|
|||
});
|
||||
|
||||
if (PushNotifier.SUPPORTS_NOTIFICATIONS) {
|
||||
document.querySelector('#features-subscribe-button').addEventListener('click', (e) => {
|
||||
const subscribeButtonEl = document.querySelector('#features-subscribe-button');
|
||||
subscribeButtonEl.removeAttribute('hidden');
|
||||
|
||||
subscribeButtonEl.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (window.Notification && Notification.permission === 'denied') {
|
||||
|
@ -117,39 +119,9 @@ if (PushNotifier.SUPPORTS_NOTIFICATIONS) {
|
|||
});
|
||||
}
|
||||
|
||||
// TODO(yangguang): Look into the following code.
|
||||
// // Handles lazy loading chromedash-legend and settings its properties only once.
|
||||
// const helpOverlayLoadPromise = (overlay) => {
|
||||
// return new Promise((resolve, reject) => {
|
||||
// if (overlayLoaded) {
|
||||
// return resolve();
|
||||
// }
|
||||
legendEl.views = VIEWS;
|
||||
|
||||
// const url = '/static/elements/chromedash-legend{% if VULCANIZE %}.vulcanize{% endif %}.html';
|
||||
|
||||
// Polymer.Base.importHref(url, () => {
|
||||
// overlayLoaded = true;
|
||||
// overlay.views = VIEWS; // set from server. Defined in features.html
|
||||
// overlay.hidden = false;
|
||||
// resolve();
|
||||
// }, (e) => {
|
||||
// reject(new Error('Error loading:' + url));
|
||||
// }, true);
|
||||
// });
|
||||
// }
|
||||
|
||||
|
||||
// document.querySelector('.legend').addEventListener('click', (e) => {
|
||||
// e.preventDefault();
|
||||
|
||||
// helpOverlayLoadPromise(overlayEl).then(() => {
|
||||
// overlayEl.toggle();
|
||||
// });
|
||||
// });
|
||||
|
||||
// Unhide notification features if browser supports it.
|
||||
// if (PushNotifier.SUPPORTS_NOTIFICATIONS) {
|
||||
// Array.from($All('.no-push-notifications')).forEach(el => {
|
||||
// el.classList.remove('no-push-notifications');
|
||||
// });
|
||||
// }
|
||||
document.querySelector('.legend-button').addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
legendEl.toggle();
|
||||
});
|
|
@ -0,0 +1,3 @@
|
|||
document.querySelector('chromedash-metrics').addEventListener('app-ready', function() {
|
||||
document.body.classList.remove('loading');
|
||||
});
|
|
@ -0,0 +1,10 @@
|
|||
const timelineEl = document.querySelector('chromedash-timeline');
|
||||
timelineEl.props = DATA;
|
||||
|
||||
document.body.classList.remove('loading');
|
||||
|
||||
window.addEventListener('popstate', function(e) {
|
||||
if (e.state) {
|
||||
timelineEl.selectedBucketId = e.state.id;
|
||||
}
|
||||
});
|
|
@ -0,0 +1,50 @@
|
|||
const KEY_CODE_ESC = 27;
|
||||
|
||||
const searchEl = document.querySelector('.search input');
|
||||
const numSamplesEl = document.querySelector('.num-features');
|
||||
const categoryMenuEl = document.querySelector('#papermenu');
|
||||
const samplePanelEl = document.querySelector('chromedash-sample-panel');
|
||||
let previousSelectedCategory = null;
|
||||
|
||||
samplePanelEl.categories = CATEGORIES;
|
||||
samplePanelEl.searchEl = searchEl;
|
||||
samplePanelEl.categoryMenuEl = categoryMenuEl;
|
||||
|
||||
// Set search box to URL deep link.
|
||||
if (location.hash) {
|
||||
searchEl.value = decodeURIComponent(location.hash.substr(1));
|
||||
}
|
||||
|
||||
samplePanelEl.addEventListener('update-length', (e) => {
|
||||
// chromedash-sample-panel fires update-length after init.
|
||||
document.body.classList.remove('loading');
|
||||
|
||||
numSamplesEl.textContent = e.detail.length;
|
||||
});
|
||||
|
||||
// Clear input when user clicks the 'x' button.
|
||||
searchEl.addEventListener('search', () => {
|
||||
samplePanelEl.filter(null, previousSelectedCategory);
|
||||
});
|
||||
|
||||
searchEl.addEventListener('keyup', (e) => {
|
||||
if (!e.target.value && e.keyCode != KEY_CODE_ESC) {
|
||||
samplePanelEl.filter(null, previousSelectedCategory);
|
||||
} else {
|
||||
samplePanelEl.filter(e.target.value, previousSelectedCategory);
|
||||
}
|
||||
});
|
||||
|
||||
categoryMenuEl.addEventListener('transitionend', (e) => {
|
||||
const selectedCategory = e.currentTarget.selectedItem.textContent.trim();
|
||||
|
||||
// Clicking selected item to de-select
|
||||
if (selectedCategory && selectedCategory === previousSelectedCategory) {
|
||||
previousSelectedCategory = null;
|
||||
categoryMenuEl.selected = null;
|
||||
samplePanelEl.filter(searchEl.value.trim());
|
||||
} else {
|
||||
samplePanelEl.filter(searchEl.value.trim(), selectedCategory);
|
||||
previousSelectedCategory = selectedCategory;
|
||||
}
|
||||
});
|
|
@ -1,58 +0,0 @@
|
|||
const KEY_CODE_ESC = 27;
|
||||
|
||||
const searchEl = document.querySelector('.search input');
|
||||
const numSamplesEl = document.querySelector('.num-features');
|
||||
const categoryMenuEl = document.querySelector('#papermenu');
|
||||
const samplePanelEl = document.querySelector('chromedash-sample-panel');
|
||||
|
||||
// Set search box to URL deep link.
|
||||
if (location.hash) {
|
||||
searchEl.value = decodeURIComponent(location.hash.substr(1));
|
||||
}
|
||||
|
||||
samplePanelEl.addEventListener('update-length', (e) => {
|
||||
numSamplesEl.textContent = e.detail.length;
|
||||
});
|
||||
|
||||
// Fire of samples.json XHR right away so data can populate faster.
|
||||
const url = location.hostname == 'localhost' ?
|
||||
'https://www.chromestatus.com/samples.json' : '/samples.json';
|
||||
fetch(url).then((res) => res.json()).then(function(features) {
|
||||
const re = /github.com\/GoogleChrome\/samples\/tree\/gh-pages\/(.*)/i;
|
||||
features.forEach((feature) => {
|
||||
feature.sample_links = feature.sample_links.map(function(link) {
|
||||
return link.replace(re, 'googlechrome.github.io/samples/$1');
|
||||
});
|
||||
});
|
||||
|
||||
samplePanelEl.features = features;
|
||||
samplePanelEl.filtered = features;
|
||||
|
||||
numSamplesEl.textContent = features.length;
|
||||
|
||||
document.body.classList.remove('loading');
|
||||
|
||||
samplePanelEl.filter(searchEl.value);
|
||||
}).catch((error) => {
|
||||
console.error(error);
|
||||
throw new Error('Samples XHR failed with status ' + e.message);
|
||||
});
|
||||
|
||||
// Clear input when user clicks the 'x' button.
|
||||
searchEl.addEventListener('search', () => {
|
||||
samplePanelEl.filter(null);
|
||||
});
|
||||
|
||||
searchEl.addEventListener('keyup', (e) => {
|
||||
if (!e.target.value && e.keyCode != KEY_CODE_ESC) {
|
||||
samplePanelEl.filter(null);
|
||||
} else {
|
||||
samplePanelEl.filter(e.target.value);
|
||||
}
|
||||
});
|
||||
|
||||
categoryMenuEl.addEventListener('transitionend', () => {
|
||||
// TODO(yangguang): Look into this. selectedItem doesn't exist
|
||||
// samplePanelEl.selectedCategory = this.selectedItem;
|
||||
samplePanelEl._onSelectCategory();
|
||||
});
|
|
@ -0,0 +1,95 @@
|
|||
// Start fetching right away.
|
||||
const url = location.hostname == 'localhost' ?
|
||||
'https://www.chromestatus.com/features.json' : '/features.json';
|
||||
const featuresPromise = fetch(url).then((res) => res.json());
|
||||
|
||||
document.querySelector('paper-toggle-button').addEventListener('change', e => {
|
||||
e.stopPropagation();
|
||||
document.querySelector('chromedash-schedule').hideBlink = e.target.checked;
|
||||
});
|
||||
|
||||
document.addEventListener('WebComponentsReady', function() {
|
||||
const header = document.querySelector('app-header-layout app-header');
|
||||
if (header) {
|
||||
header.fixed = false;
|
||||
}
|
||||
});
|
||||
|
||||
async function init() {
|
||||
document.body.classList.remove('loading');
|
||||
|
||||
// Prepare data for chromedash-schedule
|
||||
const features = await featuresPromise;
|
||||
['stable', 'beta', 'dev'].forEach((channel) => {
|
||||
CHANNELS[channel].components = mapFeaturesToComponents(features.filter(f =>
|
||||
f.browsers.chrome.status.milestone_str === CHANNELS[channel].version));
|
||||
});
|
||||
|
||||
const scheduleEl = document.querySelector('chromedash-schedule');
|
||||
scheduleEl.channels = CHANNELS;
|
||||
|
||||
// Show push notification icons if the browser supports the feature.
|
||||
if (window.PushNotifier && PushNotifier.SUPPORTS_NOTIFICATIONS) {
|
||||
initNotifications(features);
|
||||
}
|
||||
}
|
||||
|
||||
function mapFeaturesToComponents(features) {
|
||||
let set = new Set();
|
||||
features.forEach(f => set.add(...f.browsers.chrome.blink_components));
|
||||
|
||||
const featuresMappedToComponents = {};
|
||||
features.forEach(f => {
|
||||
const components = f.browsers.chrome.blink_components;
|
||||
components.forEach(component => {
|
||||
if (!featuresMappedToComponents[component]) {
|
||||
featuresMappedToComponents[component] = [];
|
||||
}
|
||||
featuresMappedToComponents[component].push(f);
|
||||
});
|
||||
});
|
||||
|
||||
for (let [, feautreList] of Object.entries(featuresMappedToComponents)) {
|
||||
sortFeaturesByName(feautreList);
|
||||
}
|
||||
|
||||
return featuresMappedToComponents;
|
||||
}
|
||||
|
||||
async function initNotifications(allFeatures) {
|
||||
await loadFirebaseSDKLibs(); // Lazy load Firebase messaging SDK.
|
||||
|
||||
PushNotifications.init(); // init Firebase messaging.
|
||||
|
||||
// If use already granted the notification permission, update state of the
|
||||
// push icon for each feature the user is subscribed to.
|
||||
const subscribedFeatures = await PushNotifications.getAllSubscribedFeatures();
|
||||
allFeatures.forEach((feature) => {
|
||||
if (subscribedFeatures.includes(String(feature.id))) {
|
||||
// f.receivePush = true;
|
||||
const iconEl = document.querySelector(`[data-feature-id="${feature.id}"] .pushicon`);
|
||||
if (iconEl) {
|
||||
iconEl.icon = 'chromestatus:notifications';
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!Array<!Object>} features
|
||||
*/
|
||||
function sortFeaturesByName(features) {
|
||||
features.sort((a, b) => {
|
||||
a = a.name.toLowerCase();
|
||||
b = b.name.toLowerCase();
|
||||
if (a < b) {
|
||||
return -1;
|
||||
}
|
||||
if (a > b) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
init();
|
|
@ -8,10 +8,4 @@ body.loading {
|
|||
chromedash-featurelist {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.main-toolbar {
|
||||
.dropdown-content {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -91,7 +91,9 @@ p {
|
|||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.content-wrapper ::content .close {
|
||||
.close {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
position: absolute;
|
||||
top: $content-padding / 2;
|
||||
right: $content-padding / 2;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
@import "../vars";
|
||||
@import "element";
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
|
|
|
@ -58,7 +58,7 @@
|
|||
}
|
||||
|
||||
chromedash-x-meter {
|
||||
padding-left: 7px;
|
||||
margin-left: 7px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
@import "element";
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
position: relative;
|
||||
max-width: 750px;
|
||||
|
||||
li {
|
||||
margin-bottom: $content-padding;
|
||||
line-height: 1.4;
|
||||
list-style: none;
|
||||
}
|
||||
.card {
|
||||
background: #fff;
|
||||
padding: $content-padding;
|
||||
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
iron-icon {
|
||||
color: $gray-2;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.feature-name {
|
||||
font-weight: 400;
|
||||
color: $chromium-color-dark;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
}
|
||||
|
||||
.sample_links {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.milestone {
|
||||
font-size: 16px;
|
||||
white-space: nowrap;
|
||||
color: $gray-3;
|
||||
}
|
||||
|
||||
.summary {
|
||||
margin: $content-padding 0;
|
||||
}
|
||||
|
||||
.demo-links {
|
||||
margin-right: $content-padding;
|
||||
font-weight: 400;
|
||||
color: $chromium-color-dark;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.demo-link {
|
||||
background-color: #F5F5F5;
|
||||
margin: 8px 8px 0 0;
|
||||
color: $gray-3;
|
||||
display: block;
|
||||
padding: 0.7em 0.57em;
|
||||
text-transform: uppercase;
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,204 @@
|
|||
@import "element";
|
||||
@import "../_layout";
|
||||
|
||||
:host {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding-bottom: $content-padding * 5;
|
||||
}
|
||||
|
||||
iron-icon {
|
||||
--iron-icon-height: 18px;
|
||||
--iron-icon-width: 18px;
|
||||
|
||||
color: $chromium-color-dark;
|
||||
|
||||
&:hover.android {
|
||||
color: #A4C739;
|
||||
}
|
||||
&:hover.remove {
|
||||
color: var(--paper-red-700);
|
||||
}
|
||||
&:hover.deprecated {
|
||||
color: var(--paper-orange-700);
|
||||
}
|
||||
&:hover.experimental {
|
||||
color: var(--paper-green-700);
|
||||
}
|
||||
&:hover.intervention {
|
||||
color: var(--paper-yellow-800);
|
||||
}
|
||||
&.pushicon {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.main-toolbar .toolbar-content {
|
||||
max-width: 100%; // override.
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chrome_version {
|
||||
font-size: 45px;
|
||||
margin: $content-padding / 2 0 $content-padding 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.channel_label {
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
font-size: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
.chrome_version .chrome-logo,
|
||||
.chrome_version--stable .chrome-logo {
|
||||
position: relative;
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
background: url(/static/img/chrome_logo.svg) no-repeat 50% 50%;
|
||||
background-size: contain;
|
||||
margin-right: $content-padding / 2;
|
||||
}
|
||||
|
||||
.chrome_version--beta .chrome-logo {
|
||||
background-image: url(/static/img/chrome_logo_beta.svg);
|
||||
}
|
||||
.chrome_version--dev .chrome-logo {
|
||||
background-image: url(/static/img/chrome_logo_dev.svg);
|
||||
}
|
||||
.chrome_version--canary .chrome-logo {
|
||||
background-image: url(/static/img/chrome_logo_canary.svg);
|
||||
background-size: 42px;
|
||||
}
|
||||
|
||||
.release {
|
||||
padding: $content-padding;
|
||||
background: #fff;
|
||||
padding: $content-padding;
|
||||
margin-bottom: $content-padding;
|
||||
border-radius: $default-border-radius;
|
||||
flex: 1 0 calc(33.33% - 16px);
|
||||
max-width: 33.33%;
|
||||
min-width: 300px;
|
||||
counter-reset: featurecount;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-right: $content-padding;
|
||||
}
|
||||
|
||||
&.no-components {
|
||||
.feature_components {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.feature_components + ul {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.milestone_info {
|
||||
margin-bottom: $content-padding / 2;
|
||||
|
||||
&:nth-of-type(3) {
|
||||
border-bottom: 1px solid $gray-1; //$bar-border-color;
|
||||
padding-bottom: $content-padding;
|
||||
}
|
||||
|
||||
.channel_label {
|
||||
font-size: inherit;
|
||||
font-weight: 500;
|
||||
text-transform: none;
|
||||
}
|
||||
}
|
||||
.features_list {
|
||||
margin-top: $content-padding * 2;
|
||||
|
||||
ul, ol {
|
||||
margin-top: $content-padding / 2;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
li {
|
||||
padding: $content-padding / 2 0;
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
&::before {
|
||||
counter-increment: featurecount;
|
||||
content: counter(featurecount) ".";
|
||||
position: absolute;
|
||||
color: $gray-2;
|
||||
}
|
||||
|
||||
.icon_row {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
> :first-child {
|
||||
margin-left: $content-padding + 4px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
.features_header {
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: $content-padding;
|
||||
}
|
||||
}
|
||||
|
||||
.feature_components {
|
||||
margin-top: $content-padding;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
// Replicated from _elements.scss
|
||||
@mixin tooltip {
|
||||
content: attr(title) ""; // force to be a string for FF/Safari.
|
||||
position: absolute;
|
||||
@include gradient-bar-bg();
|
||||
box-shadow: 2px 2px 4px $gray-2;
|
||||
border: 1px solid $gray-1;
|
||||
z-index: 100;
|
||||
text-align: center;
|
||||
color: $default-font-color;
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
position: relative;
|
||||
opacity: 0.5;
|
||||
font-weight: normal;
|
||||
will-change: transform, opacity, visibility;
|
||||
|
||||
&:before {
|
||||
transition: 200ms all cubic-bezier(0,0,0.2,1);
|
||||
transform: translateY(50%);
|
||||
@include tooltip;
|
||||
top: -45px;
|
||||
right: -40px;
|
||||
width: auto;
|
||||
white-space: nowrap;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&:hover, &:active {
|
||||
opacity: 1;
|
||||
&:before {
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 700px) {
|
||||
:host {
|
||||
.release {
|
||||
min-width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
:host {
|
||||
display: block;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
position: fixed;
|
||||
background-color: #323232;
|
||||
color: #f1f1f1;
|
||||
|
@ -20,20 +21,19 @@
|
|||
z-index: 3;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
:host([open]) {
|
||||
opacity: 1;
|
||||
transform: translateY(0px);
|
||||
}
|
||||
|
||||
#action {
|
||||
text-transform: uppercase;
|
||||
text-decoration: none;
|
||||
color: #FFEB3B;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#msg {
|
||||
margin-right: 16px;
|
||||
}
|
||||
#message_container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
|
@ -3,9 +3,6 @@
|
|||
:host {
|
||||
display: inline-block;
|
||||
contain: content;
|
||||
}
|
||||
|
||||
.meter {
|
||||
background: $chromium-color-light; //var(--paper-green-200);
|
||||
border-radius: $default-border-radius;
|
||||
overflow: hidden;
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
@import "../vars";
|
||||
|
||||
/* Fix a bug. Left align the right side list and the header.
|
||||
* TODO: Find out why it happens, and fix the root cause. */
|
||||
app-drawer-layout:not([narrow]) app-header-layout {
|
||||
margin-left: -60px;
|
||||
}
|
||||
|
||||
#content {
|
||||
// &.ready {
|
||||
// #spinner {
|
||||
|
@ -19,6 +25,15 @@ app-drawer {
|
|||
}
|
||||
}
|
||||
|
||||
button {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.legend-button {
|
||||
color: #4580c0;
|
||||
}
|
||||
|
||||
chromedash-legend {
|
||||
font-size: 90%;
|
||||
|
||||
|
|
|
@ -146,15 +146,25 @@ header {
|
|||
}
|
||||
}
|
||||
|
||||
paper-menu-button {
|
||||
margin: 0 !important; // override polyfill shimming
|
||||
padding: 0 !important; // override polyfill shimming
|
||||
line-height: 1;
|
||||
.nav-dropdown-container {
|
||||
position: relative;
|
||||
|
||||
.dropdown-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
contain: content;
|
||||
ul {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 80%;
|
||||
left: 0;
|
||||
list-style: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
a {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.nav-dropdown-trigger:hover + ul,
|
||||
ul:hover {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
@import "../vars";
|
||||
@import "vars";
|
||||
|
||||
.drawer-content-wrapper {
|
||||
max-width: var(--app-drawer-width);
|
||||
}
|
||||
|
||||
#content {
|
||||
h3 {
|
|
@ -1,5 +1,17 @@
|
|||
@import "vars";
|
||||
|
||||
/* Fix a bug. Left align the right side list and the header.
|
||||
* TODO: Find out why it happens, and fix the root cause. */
|
||||
app-drawer-layout:not([narrow]) app-header-layout {
|
||||
margin-left: -60px;
|
||||
}
|
||||
|
||||
paper-item {
|
||||
display: block;
|
||||
padding: 12px 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.demo-link, paper-item {
|
||||
iron-icon {
|
||||
margin-right: $content-padding / 2;
|
||||
|
@ -30,69 +42,3 @@ app-drawer {
|
|||
min-width: 170px;
|
||||
}
|
||||
}
|
||||
|
||||
#sample-panel {
|
||||
position: relative;
|
||||
max-width: 750px;
|
||||
}
|
||||
|
||||
#samplelist {
|
||||
li {
|
||||
margin-bottom: $content-padding;
|
||||
line-height: 1.4;
|
||||
list-style: none;
|
||||
}
|
||||
.card {
|
||||
background: #fff;
|
||||
padding: $content-padding;
|
||||
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
iron-icon {
|
||||
color: $gray-2;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.feature-name {
|
||||
font-weight: 400;
|
||||
color: $chromium-color-dark;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
}
|
||||
|
||||
.sample_links {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.milestone {
|
||||
font-size: 16px;
|
||||
white-space: nowrap;
|
||||
color: $gray-3;
|
||||
}
|
||||
|
||||
.summary {
|
||||
margin: $content-padding 0;
|
||||
}
|
||||
|
||||
.demo-links {
|
||||
margin-right: $content-padding;
|
||||
font-weight: 400;
|
||||
color: $chromium-color-dark;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.demo-link {
|
||||
background-color: #F5F5F5;
|
||||
margin: 8px 8px 0 0;
|
||||
color: $gray-3;
|
||||
display: block;
|
||||
padding: 0.7em 0.57em;
|
||||
text-transform: uppercase;
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,222 +2,25 @@
|
|||
@import "layout";
|
||||
|
||||
body[data-path*='features/schedule'] {
|
||||
--app-drawer-width: 0;
|
||||
|
||||
app-drawer {
|
||||
display: none;
|
||||
}
|
||||
// app-header {
|
||||
// padding-left: 0;
|
||||
// }
|
||||
|
||||
#spinner {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
iron-icon {
|
||||
--iron-icon-height: 18px;
|
||||
--iron-icon-width: 18px;
|
||||
|
||||
color: $chromium-color-dark;
|
||||
|
||||
&:hover.android {
|
||||
color: #A4C739;
|
||||
}
|
||||
&:hover.remove {
|
||||
color: var(--paper-red-700);
|
||||
}
|
||||
&:hover.deprecated {
|
||||
color: var(--paper-orange-700);
|
||||
}
|
||||
&:hover.experimental {
|
||||
color: var(--paper-green-700);
|
||||
}
|
||||
&:hover.intervention {
|
||||
color: var(--paper-yellow-800);
|
||||
}
|
||||
&.pushicon {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
#subheader {
|
||||
max-width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
.main-toolbar .toolbar-content {
|
||||
max-width: 100%; // override.
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.chrome_version {
|
||||
font-size: 45px;
|
||||
margin: $content-padding / 2 0 $content-padding 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.channel_label {
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
font-size: 24px;
|
||||
text-align: center;
|
||||
}
|
||||
.chrome_version .chrome-logo,
|
||||
.chrome_version--stable .chrome-logo {
|
||||
position: relative;
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
background: url(/static/img/chrome_logo.svg) no-repeat 50% 50%;
|
||||
background-size: contain;
|
||||
margin-right: $content-padding / 2;
|
||||
}
|
||||
|
||||
.chrome_version--beta .chrome-logo {
|
||||
background-image: url(/static/img/chrome_logo_beta.svg);
|
||||
}
|
||||
.chrome_version--dev .chrome-logo {
|
||||
background-image: url(/static/img/chrome_logo_dev.svg);
|
||||
}
|
||||
.chrome_version--canary .chrome-logo {
|
||||
background-image: url(/static/img/chrome_logo_canary.svg);
|
||||
background-size: 42px;
|
||||
}
|
||||
|
||||
.releases {
|
||||
padding-bottom: $content-padding * 5;
|
||||
|
||||
.release {
|
||||
padding: $content-padding;
|
||||
background: #fff;
|
||||
padding: $content-padding;
|
||||
margin-bottom: $content-padding;
|
||||
border-radius: $default-border-radius;
|
||||
flex: 1 0 calc(33.33% - 16px);
|
||||
max-width: 33.33%;
|
||||
min-width: 300px;
|
||||
counter-reset: featurecount;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-right: $content-padding;
|
||||
}
|
||||
|
||||
&.no-components {
|
||||
.feature_components {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.feature_components + ul {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.milestone_info {
|
||||
margin-bottom: $content-padding / 2;
|
||||
|
||||
&:nth-of-type(3) {
|
||||
border-bottom: 1px solid $gray-1; //$bar-border-color;
|
||||
padding-bottom: $content-padding;
|
||||
}
|
||||
|
||||
.channel_label {
|
||||
font-size: inherit;
|
||||
font-weight: 500;
|
||||
text-transform: none;
|
||||
}
|
||||
}
|
||||
.features_list {
|
||||
margin-top: $content-padding * 2;
|
||||
|
||||
ul, ol {
|
||||
margin-top: $content-padding / 2;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
li {
|
||||
padding: $content-padding / 2 0;
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
&::before {
|
||||
counter-increment: featurecount;
|
||||
content: counter(featurecount) ".";
|
||||
position: absolute;
|
||||
color: $gray-2;
|
||||
}
|
||||
|
||||
.icon_row {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
> :first-child {
|
||||
margin-left: $content-padding + 4px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
.features_header {
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: $content-padding;
|
||||
}
|
||||
}
|
||||
|
||||
.feature_components {
|
||||
margin-top: $content-padding;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
// Replicated from _elements.scss
|
||||
@mixin tooltip {
|
||||
content: attr(title) ""; // force to be a string for FF/Safari.
|
||||
position: absolute;
|
||||
@include gradient-bar-bg();
|
||||
box-shadow: 2px 2px 4px $gray-2;
|
||||
border: 1px solid $gray-1;
|
||||
z-index: 100;
|
||||
text-align: center;
|
||||
color: $default-font-color;
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
position: relative;
|
||||
opacity: 0.5;
|
||||
font-weight: normal;
|
||||
will-change: transform, opacity, visibility;
|
||||
|
||||
&:before {
|
||||
transition: 200ms all cubic-bezier(0,0,0.2,1);
|
||||
transform: translateY(50%);
|
||||
@include tooltip;
|
||||
top: -45px;
|
||||
right: -40px;
|
||||
width: auto;
|
||||
white-space: nowrap;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&:hover, &:active {
|
||||
opacity: 1;
|
||||
&:before {
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
paper-toggle-button {
|
||||
--paper-toggle-button-checked-bar-color: $chromium-color-medium;
|
||||
--paper-toggle-button-checked-button-color: $chromium-color-center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 700px) {
|
||||
.releases {
|
||||
.release {
|
||||
min-width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,7 +52,7 @@ button, .button {
|
|||
white-space: nowrap;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
text-shadow: 1px 1px #fff;
|
||||
// text-shadow: 1px 1px #fff;
|
||||
font-size: 10pt;
|
||||
}
|
||||
|
||||
|
@ -68,12 +68,6 @@ button:not(:disabled):active {
|
|||
content: ',\00a0';
|
||||
}
|
||||
|
||||
.no-push-notifications, .no-web-share {
|
||||
.no-web-share {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.supports-push-notifications {
|
||||
.no-push-notifications {
|
||||
display: initial;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ limitations under the License.
|
|||
<style>
|
||||
{% inline_file "/static/css/main.css" %}
|
||||
app-drawer {
|
||||
--app-drawer-width: 200px;
|
||||
--app-drawer-width: 200px; /* Used by the component. */
|
||||
}
|
||||
|
||||
/* Set the left sidebar the same background color as the page. */
|
||||
|
@ -56,18 +56,6 @@ limitations under the License.
|
|||
app-drawer-layout:not([narrow]) [drawer-toggle] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Fix a bug. Left align the right side list and the header.
|
||||
* TODO: Find out why it happens, and fix the root cause. */
|
||||
app-drawer-layout:not([narrow]) app-header-layout {
|
||||
margin-left: -60px;
|
||||
}
|
||||
|
||||
paper-item {
|
||||
--paper-item: {
|
||||
cursor: pointer;
|
||||
};
|
||||
}
|
||||
</style>
|
||||
|
||||
{% block css %}{% endblock %}
|
||||
|
@ -92,10 +80,6 @@ limitations under the License.
|
|||
|
||||
<body class="loading" data-path={{current_path}}>
|
||||
{% comment %}
|
||||
<!--<div id="site-banner">
|
||||
<a href="https://www.youtube.com/watch?v=Rd0plknSPYU" target="_blank">
|
||||
<iron-icon icon="chromestatus:ondemand-video"></iron-icon> How we built it</a>
|
||||
</div>-->
|
||||
{% endcomment %}
|
||||
|
||||
<app-drawer-layout fullbleed>
|
||||
|
@ -132,56 +116,16 @@ limitations under the License.
|
|||
<script src="https://unpkg.com/urlize.js/urlize.js"></script>
|
||||
|
||||
{# Metric is need by sw registration and page code. #}
|
||||
<script>{% inline_file "/static/js/metrics.min.js" %}</script>
|
||||
<script>{% inline_file "/static/js/metric.min.js" %}</script>
|
||||
|
||||
{% block js %}{% endblock %}
|
||||
|
||||
<script>
|
||||
// TODO (yangguang) Look into this code
|
||||
// // https://gist.github.com/ebidel/1d5ede1e35b6f426a2a7
|
||||
// function lazyLoadWCPolyfillsIfNecessary() {
|
||||
// function onload() {
|
||||
// // For native Imports, manually fire WCR so user code
|
||||
// // can use the same code path for native and polyfill'd imports.
|
||||
// if (!('HTMLImports' in window)) {
|
||||
// document.body.dispatchEvent(
|
||||
// new CustomEvent('WebComponentsReady', {bubbles: true}));
|
||||
// }
|
||||
// }
|
||||
// var webComponentsSupported = ('registerElement' in document
|
||||
// && 'import' in document.createElement('link')
|
||||
// && 'content' in document.createElement('template'));
|
||||
// if (!webComponentsSupported) {
|
||||
// var script = document.createElement('script');
|
||||
// script.async = true;
|
||||
// script.src = '/static/bower_components/webcomponentsjs/webcomponents-lite.min.js';
|
||||
// script.onload = onload;
|
||||
// document.head.appendChild(script);
|
||||
// } else {
|
||||
// onload();
|
||||
// }
|
||||
// }
|
||||
|
||||
// const button = document.querySelector('app-header paper-menu-button');
|
||||
// button.addEventListener('click', function lazyHandler(e) {
|
||||
// this.removeEventListener('click', lazyHandler);
|
||||
|
||||
// {% if VULCANIZE %}
|
||||
// var url = '/static/elements/paper-menu-button.vulcanize.html';
|
||||
// {% else %}
|
||||
// var url = '/static/bower_components/paper-menu-button/paper-menu-button.html';
|
||||
// {% endif %}
|
||||
|
||||
// Polymer.Base.importHref(url, function() {
|
||||
// button.contentElement.hidden = false;
|
||||
// button.open();
|
||||
// }, null, true);
|
||||
// });
|
||||
|
||||
// lazyLoadWCPolyfillsIfNecessary();
|
||||
|
||||
{% inline_file "/static/js/service-worker-registration.min.js" %}
|
||||
{% inline_file "/static/js/shared.min.js" %}
|
||||
(function() {
|
||||
'use strict';
|
||||
{% inline_file "/static/js/service-worker-registration.min.js" %}
|
||||
{% inline_file "/static/js/shared.min.js" %}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -84,13 +84,16 @@ limitations under the License.
|
|||
{% block content %}{% endblock %}
|
||||
|
||||
{# Metric is need by sw registration and page code. #}
|
||||
<script>{% inline_file "/static/js/metrics.min.js" %}</script>
|
||||
<script>{% inline_file "/static/js/metric.min.js" %}</script>
|
||||
|
||||
{% block js %}{% endblock %}
|
||||
|
||||
<script>
|
||||
{% inline_file "/static/js/service-worker-registration.min.js" %}
|
||||
{% inline_file "/static/js/shared.min.js" %}
|
||||
(function() {
|
||||
'use strict';
|
||||
{% inline_file "/static/js/service-worker-registration.min.js" %}
|
||||
{% inline_file "/static/js/shared.min.js" %}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "base.html" %}
|
||||
{% extends "_base.html" %}
|
||||
{% load verbatim %}
|
||||
{% load inline_file %}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "base.html" %}
|
||||
{% extends "_base.html" %}
|
||||
|
||||
{% block preload %}
|
||||
<script type="module" src="/static/elements/admin-imports.js"></script>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "base.html" %}
|
||||
{% extends "_base.html" %}
|
||||
{% load inline_file %}
|
||||
|
||||
{% block preload %}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "base.html" %}
|
||||
{% extends "_base.html" %}
|
||||
|
||||
{% block preload %}
|
||||
<script type="module" src="/static/elements/admin-imports.js"></script>
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
{% extends "base.html" %}
|
||||
{% extends "_base.html" %}
|
||||
|
||||
{% load inline_file %}
|
||||
|
||||
{% block css %}
|
||||
<style>
|
||||
.no-push-notifications {
|
||||
display: none;
|
||||
}
|
||||
body[data-path*='notifications/tester'] #spinner {
|
||||
display: none;
|
||||
}
|
||||
|
@ -33,7 +30,7 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<button onclick="sendNotification()" disabled class="no-push-notifications">Send test message to my token</button>
|
||||
<button onclick="sendNotification()" disabled class="push-notifications" hidden>Send test message to my token</button>
|
||||
<input id="feature_id" type="text" value="5827411627212800" placeholder="Feature id">
|
||||
|
||||
<section>
|
||||
|
@ -94,8 +91,8 @@
|
|||
|
||||
async function init() {
|
||||
$('#num_push_tokens').textContent = SUBSCRIPTIONS.length;
|
||||
document.querySelectorAll('.no-push-notifications').forEach(el => {
|
||||
el.classList.remove('no-push-notifications');
|
||||
document.querySelectorAll('.push-notifications').forEach(el => {
|
||||
el.removeAttribute('hidden');
|
||||
el.disabled = false;
|
||||
});
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "base.html" %}
|
||||
{% extends "_base.html" %}
|
||||
{% load verbatim %}
|
||||
{% load inline_file %}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "base.html" %}
|
||||
{% extends "_base.html" %}
|
||||
|
||||
{% block css %}
|
||||
<!-- <link rel="stylesheet" href="/static/css/users/users.css"> -->
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends embed|yesno:"base_embed.html,base.html" %}
|
||||
{% extends embed|yesno:"_base_embed.html,_base.html" %}
|
||||
{% load inline_file %}
|
||||
|
||||
{% block page_title %}{% if feature.name %}{{ feature.name }} - {% endif %}{% endblock %}
|
||||
|
@ -44,7 +44,7 @@
|
|||
<iron-icon icon="chromestatus:flag" class="experimental"></iron-icon>
|
||||
</span>
|
||||
{% endif %}
|
||||
<span class="tooltip no-push-notifications" title="Receive a push notification where there are updates">
|
||||
<span class="tooltip push-notifications" title="Receive a push notification where there are updates" hidden>
|
||||
<a href="#" data-tooltip>
|
||||
<iron-icon icon="chromestatus:notifications-off"
|
||||
onclick="subscribeToFeature({{ feature.id }})"
|
||||
|
@ -245,79 +245,17 @@
|
|||
|
||||
{% block js %}
|
||||
<script>
|
||||
{% inline_file "/static/js/notifications.min.js" %}
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
// Variables used in feature.js
|
||||
const FEATURE_ID = '{{ feature.id }}';
|
||||
const FEATURE_NAME = '{{ feature.name|escapejs }}';
|
||||
const FEATUER_SUMMARY = '{{ feature.summary|escapejs }}';
|
||||
const SHOW_TOAST = {% if was_updated %}true{% else %}false{% endif %};
|
||||
|
||||
const subscribeToFeature = (featureId) => {
|
||||
const iconEl = document.querySelector('.pushicon');
|
||||
if (iconEl.icon === 'chromestatus:notifications') {
|
||||
iconEl.icon = 'chromestatus:notifications-off';
|
||||
PushNotifications.unsubscribeFromFeature(featureId);
|
||||
} else {
|
||||
iconEl.icon = 'chromestatus:notifications';
|
||||
PushNotifications.subscribeToFeature(featureId);
|
||||
}
|
||||
}
|
||||
|
||||
const shareFeature = () => {
|
||||
if (navigator.share) {
|
||||
const url = '/feature/{{ feature.id }}';
|
||||
navigator.share({
|
||||
title: '{{ feature.name|escapejs }}',
|
||||
text: '{{ feature.summary|escapejs }}',
|
||||
url: url
|
||||
})
|
||||
.then(() => {
|
||||
ga('send', 'social', {
|
||||
'socialNetwork': 'web',
|
||||
'socialAction': 'share',
|
||||
'socialTarget': url
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Remove loading spinner at page load.
|
||||
document.body.classList.remove('loading');
|
||||
|
||||
// Unhide "Web Share" feature if browser supports it.
|
||||
if (navigator.share) {
|
||||
Array.from(document.querySelectorAll('.no-web-share')).forEach((el) => {
|
||||
el.classList.remove('no-web-share');
|
||||
});
|
||||
}
|
||||
|
||||
// Unhide notification features if browser supports it.
|
||||
if (PushNotifier.SUPPORTS_NOTIFICATIONS) {
|
||||
Array.from(document.querySelectorAll('.no-push-notifications')).forEach((el) => {
|
||||
el.classList.remove('no-push-notifications');
|
||||
});
|
||||
|
||||
// Lazy load Firebase messaging SDK.
|
||||
loadFirebaseSDKLibs().then(() => {
|
||||
PushNotifications.init(); // init Firebase messaging.
|
||||
|
||||
// If use already granted the notification permission, update state of the
|
||||
// push icon for each feature the user is subscribed to.
|
||||
if (PushNotifier.GRANTED_ACCESS) {
|
||||
PushNotifications.getAllSubscribedFeatures().then((subscribedFeatures) => {
|
||||
const iconEl = document.querySelector('.pushicon');
|
||||
if (subscribedFeatures.includes('{{feature.id}}')) {
|
||||
iconEl.icon = 'chromestatus:notifications';
|
||||
} else {
|
||||
iconEl.icon = 'chromestatus:notifications-off';
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const SHOW_TOAST = {% if was_updated %}true{% else %}false{% endif %};
|
||||
if (SHOW_TOAST) {
|
||||
setTimeout(() => {
|
||||
const toastEl = document.querySelector('chromedash-toast');
|
||||
toastEl.showMessage('Your feature was saved! It may take a few minutes to ' +
|
||||
'show up in the main list.', null, null, -1);
|
||||
}, 500);
|
||||
}
|
||||
{% inline_file "/static/js/notifications.min.js" %}
|
||||
{% inline_file "/static/js/feature-page.min.js" %}
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "base.html" %}
|
||||
{% extends "_base.html" %}
|
||||
|
||||
{% load inline_file %}
|
||||
{% load cache %}
|
||||
|
@ -14,7 +14,6 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block css %}
|
||||
<!-- <link rel="stylesheet" href="/static/css/features/features.css"> -->
|
||||
{% cache TEMPLATE_CACHE_TIME featurescss %}
|
||||
<style>{% inline_file "/static/css/features/features.css" %}</style>
|
||||
{% endcache %}
|
||||
|
@ -23,6 +22,7 @@
|
|||
{% block preload %}
|
||||
<script type="module">
|
||||
import '/static/elements/chromedash-featurelist.js';
|
||||
import '/static/elements/chromedash-legend.js';
|
||||
import '/static/elements/chromedash-metadata.js';
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
@ -34,7 +34,7 @@
|
|||
</div>
|
||||
<div class="search">
|
||||
<input type="search" placeholder="Filter" disabled>
|
||||
<a href="" class="legend"><iron-icon icon="chromestatus:help"></iron-icon></a>
|
||||
<button class="legend-button"><iron-icon icon="chromestatus:help"></iron-icon></button>
|
||||
</div>
|
||||
<div class="actionlinks">
|
||||
<a id="features-subscribe-button" class="blue-button no-push-notifications" title="Sends a push notification when new features are added."><iron-icon icon="chromestatus:notifications-off"></iron-icon><span>Subscribe</span></a>
|
||||
|
@ -54,7 +54,7 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block overlay %}
|
||||
<chromedash-legend hidden></chromedash-legend>
|
||||
<chromedash-legend></chromedash-legend>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
@ -64,15 +64,15 @@
|
|||
{% block js %}
|
||||
<script>
|
||||
(function() {
|
||||
'use strict';
|
||||
// Get values from server. used in /static/js/features.js
|
||||
'use strict';
|
||||
// Get values from server. used in /static/js/features-page.js
|
||||
const VIEWS = {
|
||||
vendors: {{VENDOR_VIEWS|safe}},
|
||||
webdevs: {{WEB_DEV_VIEWS|safe}},
|
||||
standards: {{STANDARDS_VALS|safe}}
|
||||
};
|
||||
{% inline_file "/static/js/notifications.min.js" %}
|
||||
{% inline_file "/static/js/features.min.js" %}
|
||||
{% inline_file "/static/js/features-page.min.js" %}
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script type="module">
|
||||
import 'https://unpkg.com/@polymer/paper-menu-button/paper-menu-button.js?module';
|
||||
import 'https://unpkg.com/@polymer/iron-icon/iron-icon.js?module';
|
||||
</script>
|
||||
<header>
|
||||
<aside>
|
||||
|
@ -13,13 +13,13 @@
|
|||
<a href="/features">All features</a>
|
||||
<a href="/features/schedule">Releases</a>
|
||||
<a href="/samples" class="features">Samples</a>
|
||||
<paper-menu-button vertical-align="top" horizontal-align="right">
|
||||
<a href="javascript:void(0)" class="dropdown-trigger">Stats</a>
|
||||
<div class="dropdown-content" hidden> <!-- hidden removed by lazy load code. -->
|
||||
<a href="/metrics/css/popularity" class="metrics">CSS</a>
|
||||
<a href="/metrics/feature/popularity" class="metrics">JS/HTML</a>
|
||||
</div>
|
||||
</paper-menu-button>
|
||||
<div class="nav-dropdown-container">
|
||||
<a class="nav-dropdown-trigger">Stats</a>
|
||||
<ul>
|
||||
<li><a href="/metrics/css/popularity">CSS</a></li>
|
||||
<li><a href="/metrics/feature/popularity">JS/HTML</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
<a href="#" id="a2hs-button" title="Add app to homescreen"><img src="/static/img/a2hs_icon_24px.svg"></a>
|
||||
</header>
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
{% extends "base.html" %}
|
||||
{% extends "_base.html" %}
|
||||
{% load inline_file %}
|
||||
|
||||
{% block css %}
|
||||
<!-- <link rel="stylesheet" href="/static/css/metrics/metrics.css"> -->
|
||||
<style>{% inline_file "/static/css/metrics/metrics.css" %}</style>
|
||||
<style>{% inline_file "/static/css/metrics.css" %}</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block preload %}
|
||||
|
@ -12,7 +12,7 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block drawer %}
|
||||
{% include "metrics/_nav.html" %}
|
||||
{% include "metrics/css/_css_metric_nav.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block subheader %}
|
||||
|
@ -23,20 +23,18 @@
|
|||
|
||||
{% block content %}
|
||||
<div class="data-panel">
|
||||
<div>
|
||||
<h3>About this data</h3>
|
||||
<p class="description">
|
||||
We've been using Chrome's <a href="https://cs.chromium.org/chromium/src/tools/metrics/histograms/enums.xml" target="_blank">anonymous usage statistics</a> to count the CSS properties which are animated.
|
||||
<b>Percentages on this page indicate the fraction of Chrome page loads that animates the corresponding CSS property (in a transition or animation) at least once</b>. Data is ~24 hrs old and reflects usage across all channels and platforms.</p>
|
||||
</div>
|
||||
<chromedash-metrics type="css" view="animated" {% if not prod %}use-remote-data{% endif %}></chromedash-metrics>
|
||||
<h3>About this data</h3>
|
||||
<p class="description">
|
||||
We've been using Chrome's <a href="https://cs.chromium.org/chromium/src/tools/metrics/histograms/enums.xml" target="_blank">anonymous usage statistics</a> to count the CSS properties which are animated. <b>Percentages on this page indicate the fraction of Chrome page loads that animates the corresponding CSS property (in a transition or animation) at least once</b>. Data is ~24 hrs old and reflects usage across all channels and platforms.</p>
|
||||
<chromedash-metrics type="css" view="animated" {% if prod %}prod{% endif %}></chromedash-metrics>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script>
|
||||
document.addEventListener('WebComponentsReady', function(e) {
|
||||
document.body.classList.remove('loading');
|
||||
});
|
||||
(function() {
|
||||
'use strict';
|
||||
{% inline_file "/static/js/metrics-page.min.js" %}
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,19 +1,18 @@
|
|||
{% extends "base.html" %}
|
||||
{% extends "_base.html" %}
|
||||
{% load inline_file %}
|
||||
|
||||
{% block css %}
|
||||
<!-- <link rel="stylesheet" href="/static/css/metrics/metrics.css"> -->
|
||||
<style>{% inline_file "/static/css/metrics/metrics.css" %}</style>
|
||||
<style>{% inline_file "/static/css/metrics.css" %}</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block preload %}
|
||||
<script type="module">
|
||||
import '/static/elements/chromedash-metric.js';
|
||||
import '/static/elements/chromedash-metrics.js';
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block drawer %}
|
||||
{% include "metrics/_nav.html" %}
|
||||
{% include "metrics/css/_css_metric_nav.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block subheader %}
|
||||
|
@ -24,18 +23,18 @@
|
|||
|
||||
{% block content %}
|
||||
<div class="data-panel">
|
||||
<div>
|
||||
<h3>About this data</h3>
|
||||
<p class="description">We've been using Chrome's <a href="https://cs.chromium.org/chromium/src/tools/metrics/histograms/enums.xml" target="_blank">anonymous usage statistics</a> to count the occurrences of certain CSS features in the wild. The numbers on this page indicate the <b>percentages of Chrome page loads (across all channels and platforms) that use the corresponding CSS property at least once</b>. Data is ~24 hrs old.</p>
|
||||
</div>
|
||||
<chromedash-metrics type="css" view="popularity" {% if not prod %}use-remote-data{% endif %}></chromedash-metrics>
|
||||
<h3>About this data</h3>
|
||||
<p class="description">
|
||||
We've been using Chrome's <a href="https://cs.chromium.org/chromium/src/tools/metrics/histograms/enums.xml" target="_blank">anonymous usage statistics</a> to count the occurrences of certain CSS features in the wild. The numbers on this page indicate the <b>percentages of Chrome page loads (across all channels and platforms) that use the corresponding CSS property at least once</b>. Data is ~24 hrs old.</p>
|
||||
<chromedash-metrics type="css" view="popularity" {% if prod %}prod{% endif %}></chromedash-metrics>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script>
|
||||
document.addEventListener('WebComponentsReady', function(e) {
|
||||
document.body.classList.remove('loading');
|
||||
});
|
||||
(function() {
|
||||
'use strict';
|
||||
{% inline_file "/static/js/metrics-page.min.js" %}
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
{% extends "base.html" %}
|
||||
{% extends "_base.html" %}
|
||||
{% load inline_file %}
|
||||
|
||||
{% block css %}
|
||||
<!-- <link rel="stylesheet" href="/static/css/metrics/metrics.css"> -->
|
||||
<style>{% inline_file "/static/css/metrics/metrics.css" %}</style>
|
||||
<style>{% inline_file "/static/css/metrics.css" %}</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block preload %}
|
||||
<script src="https://www.gstatic.com/charts/loader.js"></script>
|
||||
<script type="module">
|
||||
import '/static/elements/chromedash-timeline.js';
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block drawer %}
|
||||
{% include "metrics/_nav.html" %}
|
||||
{% include "metrics/css/_css_metric_nav.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block subheader %}
|
||||
|
@ -27,7 +27,7 @@
|
|||
<chromedash-timeline
|
||||
type="css" view="animated"
|
||||
title="The % of page loads in which the given CSS property was animated."
|
||||
{% if not prod %}use-remote-data{% endif %}></chromedash-timeline>
|
||||
{% if prod %}prod{% endif %}></chromedash-timeline>
|
||||
<p class="callout">
|
||||
<b>Note</b>: on 2017-10-26 the underlying metrics were switched over to a newer collection system
|
||||
which is <a href="https://groups.google.com/a/chromium.org/forum/#!msg/blink-api-owners-discuss/IpIkbz0qtrM/HUCfSMv2AQAJ" target="_blank">more accurate</a>.
|
||||
|
@ -38,17 +38,11 @@
|
|||
|
||||
{% block js %}
|
||||
<script>
|
||||
document.addEventListener('WebComponentsReady', function(e) {
|
||||
var timeline = document.querySelector('chromedash-timeline');
|
||||
timeline.props = {{CSS_PROPERTY_BUCKETS|safe}};
|
||||
|
||||
document.body.classList.remove('loading');
|
||||
|
||||
window.addEventListener('popstate', function(e) {
|
||||
if (e.state) {
|
||||
timeline.selectedBucketId = e.state.id;
|
||||
}
|
||||
});
|
||||
});
|
||||
(function() {
|
||||
'use strict';
|
||||
// Get values from server. used in /static/js/metrics-timeline-page.js
|
||||
const DATA = {{CSS_PROPERTY_BUCKETS|safe}};
|
||||
{% inline_file "/static/js/metrics-timeline-page.min.js" %}
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
{% extends "base.html" %}
|
||||
{% extends "_base.html" %}
|
||||
{% load inline_file %}
|
||||
|
||||
{% block css %}
|
||||
<!-- <link rel="stylesheet" href="/static/css/metrics/metrics.css"> -->
|
||||
<style>{% inline_file "/static/css/metrics/metrics.css" %}</style>
|
||||
<style>{% inline_file "/static/css/metrics.css" %}</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block preload %}
|
||||
<script src="https://www.gstatic.com/charts/loader.js"></script>
|
||||
<script type="module">
|
||||
import '/static/elements/chromedash-timeline.js';
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block drawer %}
|
||||
{% include "metrics/_nav.html" %}
|
||||
{% include "metrics/css/_css_metric_nav.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block subheader %}
|
||||
|
@ -28,7 +28,7 @@
|
|||
<chromedash-timeline
|
||||
type="css" view="popularity"
|
||||
title="Percentage of Chrome page loads that use this property at least once"
|
||||
{% if not prod %}use-remote-data{% endif %}></chromedash-timeline>
|
||||
{% if prod %}prod{% endif %}></chromedash-timeline>
|
||||
<p class="callout">
|
||||
<b>Note</b>: on 2017-10-26 the underlying metrics were switched over to a newer collection system
|
||||
which is <a href="https://groups.google.com/a/chromium.org/forum/#!msg/blink-api-owners-discuss/IpIkbz0qtrM/HUCfSMv2AQAJ" target="_blank">more accurate</a>.
|
||||
|
@ -39,17 +39,11 @@
|
|||
|
||||
{% block js %}
|
||||
<script>
|
||||
document.addEventListener('WebComponentsReady', function(e) {
|
||||
var timeline = document.querySelector('chromedash-timeline');
|
||||
timeline.props = {{CSS_PROPERTY_BUCKETS|safe}};
|
||||
|
||||
document.body.classList.remove('loading');
|
||||
|
||||
window.addEventListener('popstate', function(e) {
|
||||
if (e.state) {
|
||||
timeline.selectedBucketId = e.state.id;
|
||||
}
|
||||
});
|
||||
});
|
||||
(function() {
|
||||
'use strict';
|
||||
// Get values from server. used in /static/js/metrics-timeline-page.js
|
||||
const DATA = {{CSS_PROPERTY_BUCKETS|safe}};
|
||||
{% inline_file "/static/js/metrics-timeline-page.min.js" %}
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% extends "_base.html" %}
|
||||
{% load inline_file %}
|
||||
|
||||
{% block css %}
|
||||
<!-- <link rel="stylesheet" href="/static/css/metrics/metrics.css"> -->
|
||||
<style>{% inline_file "/static/css/metrics/metrics.css" %}</style>
|
||||
<style>{% inline_file "/static/css/metrics.css" %}</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block preload %}
|
||||
|
@ -13,11 +11,8 @@
|
|||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block html_imports %}
|
||||
{% endblock %}
|
||||
|
||||
{% block drawer %}
|
||||
{% include "metrics/feature/_nav.html" %}
|
||||
{% include "metrics/feature/_feature_metric_nav.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block subheader %}
|
||||
|
@ -28,18 +23,18 @@
|
|||
|
||||
{% block content %}
|
||||
<div class="data-panel">
|
||||
<div>
|
||||
<h3>About this data</h3>
|
||||
<p class="description">We've been using Chrome's <a href="https://cs.chromium.org/chromium/src/tools/metrics/histograms/enums.xml" target="_blank">anonymous usage statistics</a> to count the occurrences of certain HTML and JavaScript features in the wild. The numbers on this page indicate the <b>percentages of Chrome page loads (across all channels and platforms) that use the corresponding feature at least once</b>. Data is ~24 hrs old.</p>
|
||||
</div>
|
||||
<chromedash-metrics type="feature" view="popularity" {% if not prod %}use-remote-data{% endif %}></chromedash-metrics>
|
||||
<h3>About this data</h3>
|
||||
<p class="description">
|
||||
We've been using Chrome's <a href="https://cs.chromium.org/chromium/src/tools/metrics/histograms/enums.xml" target="_blank">anonymous usage statistics</a> to count the occurrences of certain HTML and JavaScript features in the wild. The numbers on this page indicate the <b>percentages of Chrome page loads (across all channels and platforms) that use the corresponding feature at least once</b>. Data is ~24 hrs old.</p>
|
||||
<chromedash-metrics type="feature" view="popularity" {% if prod %}prod{% endif %}></chromedash-metrics>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script>
|
||||
document.addEventListener('app-ready', function(e) {
|
||||
document.body.classList.remove('loading');
|
||||
});
|
||||
(function() {
|
||||
'use strict';
|
||||
{% inline_file "/static/js/metrics-page.min.js" %}
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,22 +1,19 @@
|
|||
{% extends "base.html" %}
|
||||
{% extends "_base.html" %}
|
||||
{% load inline_file %}
|
||||
|
||||
{% block css %}
|
||||
<!-- <link rel="stylesheet" href="/static/css/metrics/metrics.css"> -->
|
||||
<style>{% inline_file "/static/css/metrics/metrics.css" %}</style>
|
||||
<style>{% inline_file "/static/css/metrics.css" %}</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block preload %}
|
||||
<script src="https://www.gstatic.com/charts/loader.js"></script>
|
||||
<script type="module">
|
||||
import '/static/elements/chromedash-timeline.js';
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block html_imports %}
|
||||
{% endblock %}
|
||||
|
||||
{% block drawer %}
|
||||
{% include "metrics/feature/_nav.html" %}
|
||||
{% include "metrics/feature/_feature_metric_nav.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block subheader %}
|
||||
|
@ -31,22 +28,17 @@
|
|||
<chromedash-timeline
|
||||
type="feature" view="popularity"
|
||||
title="Percentage of page views that use this feature"
|
||||
{% if not prod %}use-remote-data{% endif %}></chromedash-timeline>
|
||||
{% if prod %}prod{% endif %}></chromedash-timeline>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script>
|
||||
document.addEventListener('WebComponentsReady', function(e) {
|
||||
var timeline = document.querySelector('chromedash-timeline');
|
||||
timeline.props = {{FEATUREOBSERVER_BUCKETS|safe}};
|
||||
document.body.classList.remove('loading');
|
||||
|
||||
window.addEventListener('popstate', function(e) {
|
||||
if (e.state) {
|
||||
timeline.selectedBucketId = e.state.id;
|
||||
}
|
||||
});
|
||||
});
|
||||
(function() {
|
||||
'use strict';
|
||||
// Get values from server. used in /static/js/metrics-timeline-page.js
|
||||
const DATA = {{FEATUREOBSERVER_BUCKETS|safe}};
|
||||
{% inline_file "/static/js/metrics-timeline-page.min.js" %}
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "base.html" %}
|
||||
{% extends "_base.html" %}
|
||||
{% load verbatim %}
|
||||
{% load inline_file %}
|
||||
|
||||
|
@ -12,9 +12,9 @@
|
|||
|
||||
{% block preload %}
|
||||
<script type="module">
|
||||
import 'https://unpkg.com/@polymer/paper-listbox/paper-listbox.js?moduel';
|
||||
import 'https://unpkg.com/@polymer/paper-ripple/paper-ripple.js?moduel';
|
||||
import 'https://unpkg.com/@polymer/paper-item/paper-item.js?moduel';
|
||||
import 'https://unpkg.com/@polymer/paper-listbox/paper-listbox.js?module';
|
||||
import 'https://unpkg.com/@polymer/paper-ripple/paper-ripple.js?module';
|
||||
import 'https://unpkg.com/@polymer/paper-item/paper-item.js?module';
|
||||
import 'https://unpkg.com/@polymer/iron-icon/iron-icon.js?module';
|
||||
import '/static/elements/chromedash-sample-panel.js';
|
||||
</script>
|
||||
|
@ -59,13 +59,16 @@
|
|||
behind a flag or still in the development.</p>
|
||||
</div>
|
||||
|
||||
<chromedash-sample-panel categories="{{categories|safe}}"></chromedash-sample-panel>
|
||||
<chromedash-sample-panel></chromedash-sample-panel>
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
<script>
|
||||
(function() {
|
||||
{% inline_file "/static/js/samples.min.js" %}
|
||||
'use strict';
|
||||
const CATEGORIES = {{categories|safe}};
|
||||
|
||||
{% inline_file "/static/js/samples-page.min.js" %}
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "base.html" %}
|
||||
{% extends "_base.html" %}
|
||||
{% load verbatim %}
|
||||
{% load inline_file %}
|
||||
|
||||
|
@ -33,191 +33,12 @@
|
|||
{% block js %}
|
||||
<script>
|
||||
(function() {
|
||||
'use strict';
|
||||
'use strict';
|
||||
// Variables used in schedule.js
|
||||
const CHANNELS = {{channels|safe}};
|
||||
|
||||
{% inline_file "/static/js/notifications.min.js" %}
|
||||
|
||||
async function initNotifications(allFeatures) {
|
||||
await loadFirebaseSDKLibs(); // Lazy load Firebase messaging SDK.
|
||||
|
||||
PushNotifications.init(); // init Firebase messaging.
|
||||
|
||||
// If use already granted the notification permission, update state of the
|
||||
// push icon for each feature the user is subscribed to.
|
||||
const subscribedFeatures = await PushNotifications.getAllSubscribedFeatures();
|
||||
allFeatures.forEach((feature, i) => {
|
||||
if (subscribedFeatures.includes(String(feature.id))) {
|
||||
// f.receivePush = true;
|
||||
const iconEl = $(`[data-feature-id="${feature.id}"] .pushicon`);
|
||||
if (iconEl) {
|
||||
iconEl.icon = 'chromestatus:notifications';
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// async function getMilestoneInfo(version) {
|
||||
// const resp = await fetch(`https://chromepmo.appspot.com/schedule/mstone/json?mstone=${version}`);
|
||||
// const json = await resp.json();
|
||||
// return json.mstones[0];
|
||||
// }
|
||||
|
||||
// function currentChromeVersions(versions) {
|
||||
// const channels = {};
|
||||
// const winVersions = versions.filter(platform => platform.os === 'win')[0].versions;
|
||||
// for (let i = 0, el; el = winVersions[i]; ++i) {
|
||||
// channels[el.channel] = parseInt(el.version);
|
||||
// }
|
||||
// return channels;
|
||||
// }
|
||||
|
||||
// const currentVersions = currentChromeVersions(versions);
|
||||
|
||||
// getMilestoneInfo(currentVersions.stable).then(milestone => {
|
||||
// t.set('channels.stable', Object.assign({
|
||||
// version: currentVersions.stable
|
||||
// }, milestone));
|
||||
|
||||
// document.body.classList.remove('loading');
|
||||
// });
|
||||
|
||||
// getMilestoneInfo(currentVersions.beta).then(milestone => {
|
||||
// t.set('channels.beta', Object.assign({
|
||||
// version: currentVersions.beta
|
||||
// }, milestone));
|
||||
// });
|
||||
|
||||
// getMilestoneInfo(currentVersions.dev).then(milestone => {
|
||||
// t.set('channels.dev', Object.assign({
|
||||
// version: currentVersions.dev
|
||||
// }, milestone));
|
||||
// });
|
||||
|
||||
const url = location.hostname == 'localhost' ? 'https://www.chromestatus.com/features.json' :
|
||||
'/features.json';
|
||||
const featuresPromise = fetch(url).then((res) => res.json());
|
||||
|
||||
/**
|
||||
* Returns the number of days between a and b.
|
||||
* @param {!Date} a
|
||||
* @param {!Date} b
|
||||
* @return {!{days: number, future: boolean}}
|
||||
*/
|
||||
function dateDiffInDays(a, b) {
|
||||
// Discard time and time-zone information.
|
||||
const utc1 = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate());
|
||||
const utc2 = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate());
|
||||
|
||||
const MS_PER_DAY = 1000 * 60 * 60 * 24;
|
||||
const daysDiff = Math.floor((utc2 - utc1) / MS_PER_DAY);
|
||||
return {days: Math.abs(daysDiff), future: daysDiff < 1};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!Array<!Object>} features
|
||||
*/
|
||||
function sortFeaturesByName(features) {
|
||||
features.sort((a, b) => {
|
||||
a = a.name.toLowerCase();
|
||||
b = b.name.toLowerCase();
|
||||
if (a < b) {
|
||||
return -1;
|
||||
}
|
||||
if (a > b) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {!Array<!Object>} features
|
||||
*/
|
||||
function mapFeaturesToComponents(features) {
|
||||
let set = new Set();
|
||||
features.forEach(f => set.add(...f.browsers.chrome.blink_components));
|
||||
const usedComponents = Array.from(set);
|
||||
|
||||
const featuresMappedToComponents = {};
|
||||
features.forEach(f => {
|
||||
const components = f.browsers.chrome.blink_components;
|
||||
components.forEach(component => {
|
||||
if (!featuresMappedToComponents[component]) {
|
||||
featuresMappedToComponents[component] = [];
|
||||
}
|
||||
featuresMappedToComponents[component].push(f);
|
||||
});
|
||||
});
|
||||
|
||||
for (let [component, feautreList] of Object.entries(featuresMappedToComponents)) {
|
||||
sortFeaturesByName(feautreList);
|
||||
}
|
||||
|
||||
return featuresMappedToComponents;
|
||||
}
|
||||
|
||||
t.channels = {{channels|safe}};
|
||||
|
||||
$('paper-toggle-button').addEventListener('change', e => {
|
||||
e.stopPropagation();
|
||||
document.querySelectorAll('.release').forEach(release => {
|
||||
release.classList.toggle('no-components', e.target.checked)
|
||||
});
|
||||
});
|
||||
|
||||
async function init() {
|
||||
document.body.classList.remove('loading');
|
||||
|
||||
// Wait for Polymer to be setup before setting features on template.
|
||||
// This prevents race conditions whereby when opening a new tab, Chrome
|
||||
// returns features from the cache and beats Polymer being ready.
|
||||
const target = $('#releases-section');
|
||||
const mutationObserverPromise = new Promise(resolve => {
|
||||
const observer = new MutationObserver(mutations => {
|
||||
mutations.forEach(mutation => {
|
||||
if (mutation.addedNodes.length && target.querySelector('.releases')) {
|
||||
observer.disconnect();
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
observer.observe(target, {childList: true});
|
||||
});
|
||||
|
||||
const timeoutPromise = new Promise(resolve => setTimeout(resolve, 5000));
|
||||
|
||||
// Race fetching features from network and timeout. If MO observer check fails
|
||||
// for some reason, manually set features after 5s.
|
||||
await Promise.race([mutationObserverPromise, timeoutPromise]);
|
||||
|
||||
const features = await featuresPromise;
|
||||
const stableFeatures = features.filter(f =>
|
||||
f.browsers.chrome.status.milestone_str === t.channels.stable.version);
|
||||
const betaFeatures = features.filter(f =>
|
||||
f.browsers.chrome.status.milestone_str === t.channels.beta.version);
|
||||
const devFeatures = features.filter(f =>
|
||||
f.browsers.chrome.status.milestone_str === t.channels.dev.version);
|
||||
|
||||
t.set('channels.stable.components', mapFeaturesToComponents(stableFeatures));
|
||||
t.set('channels.beta.components', mapFeaturesToComponents(betaFeatures));
|
||||
t.set('channels.dev.components', mapFeaturesToComponents(devFeatures));
|
||||
|
||||
// Show push notification icons if the browser supports the feature.
|
||||
if (window.PushNotifier && PushNotifier.SUPPORTS_NOTIFICATIONS) {
|
||||
$('.releases').classList.add('supports-push-notifications');
|
||||
initNotifications(features);
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('WebComponentsReady', function(e) {
|
||||
const header = document.querySelector('app-header-layout app-header');
|
||||
if (header) {
|
||||
header.fixed = false;
|
||||
}
|
||||
});
|
||||
|
||||
init();
|
||||
|
||||
})();
|
||||
{% inline_file "/static/js/notifications.min.js" %}
|
||||
{% inline_file "/static/js/schedule-page.min.js" %}
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
Загрузка…
Ссылка в новой задаче