This commit is contained in:
Yangguang Li 2019-07-01 10:53:51 -07:00
Родитель ccf52decd9 e7508cf9f7
Коммит 7c27f15c0b
57 изменённых файлов: 1867 добавлений и 1026 удалений

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

@ -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.

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

@ -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&lt;30"</span>features that landed before 30
</li>
<li>
<span>"browsers.chrome.desktop&lt;=50"</span>features in desktop chrome 50
</li>
<li>
<span>"browsers.chrome.android&gt;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 %}