Initial port to polymer 1.0
This commit is contained in:
Родитель
518b2d0031
Коммит
e3de37b102
|
@ -10,9 +10,10 @@ module.exports = function(grunt) {
|
|||
// "polymer.html$"
|
||||
// ]
|
||||
// },
|
||||
strip: true,
|
||||
csp: true,
|
||||
inline: true
|
||||
stripComments: true,
|
||||
inlineScripts: true,
|
||||
inlineCss: true
|
||||
// csp: true,
|
||||
},
|
||||
build: {
|
||||
files: {
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"devDependencies": {
|
||||
"grunt": "*",
|
||||
"grunt-appengine": "^0.1.5",
|
||||
"grunt-vulcanize": "0.6.4",
|
||||
"grunt-vulcanize": "^1.0.0",
|
||||
"load-grunt-tasks": "*"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,4 +29,4 @@ MEMCACHE_KEY_PREFIX = APP_VERSION # For memcache busting on new version
|
|||
|
||||
RSS_FEED_LIMIT = 15
|
||||
|
||||
VULCANIZE = True #PROD
|
||||
VULCANIZE = False #PROD
|
||||
|
|
|
@ -19,15 +19,13 @@
|
|||
"test",
|
||||
"tests"
|
||||
],
|
||||
"dependencies": {
|
||||
"polymer": "Polymer/polymer#^0.5.6",
|
||||
"core-ajax": "Polymer/core-ajax#^0.5.6",
|
||||
"google-apis": "GoogleWebComponents/google-apis#^0.4.4",
|
||||
"paper-dialog": "Polymer/paper-dialog#^0.4.2",
|
||||
"webcomponentsjs": "webcomponents/webcomponentsjs#^0.6.1"
|
||||
"devDependencies": {
|
||||
"iron-component-page": "PolymerElements/iron-component-page#^1.0.5"
|
||||
},
|
||||
"resolutions": {
|
||||
"polymer": "0.5.6",
|
||||
"core-component-page": "0.5.6"
|
||||
"dependencies": {
|
||||
"polymer": "Polymer/polymer#^1.0.8",
|
||||
"iron-ajax": "PolymerElements/iron-ajax#^1.0.3",
|
||||
"google-apis": "GoogleWebComponents/google-apis#^1.0.2",
|
||||
"paper-dialog": "PolymerElements/paper-dialog#^1.0.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,53 @@
|
|||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
|
||||
<polymer-element name="chromedash-color-status" attributes="value max"><!-- width height corner">-->
|
||||
<dom-module id="chromedash-color-status">
|
||||
<template>
|
||||
<link rel="stylesheet" href="../css/elements/chromedash-color-status.css">
|
||||
<!--<style>
|
||||
span {
|
||||
width: {{width}}px;
|
||||
height: {{height}}px;
|
||||
<span id="status"></span>
|
||||
</template>
|
||||
</dom-module>
|
||||
<script>
|
||||
Polymer({
|
||||
is: 'chromedash-color-status',
|
||||
properties: {
|
||||
height: {
|
||||
type: Number,
|
||||
value: 10
|
||||
},
|
||||
max: {
|
||||
type: Number,
|
||||
value: 7,
|
||||
notify: true,
|
||||
observer: 'maxChanged'
|
||||
},
|
||||
value: {
|
||||
notify: true,
|
||||
observer: 'valueChanged'
|
||||
},
|
||||
width: {
|
||||
type: Number,
|
||||
value: 10
|
||||
}
|
||||
</style>-->
|
||||
},
|
||||
updateColor: function () {
|
||||
var h = Math.round(120 - this.value * 120 / this.max);
|
||||
this.$.status.style.backgroundColor = 'hsl(' + h + ', 100%, 50%)';
|
||||
},
|
||||
valueChanged: function () {
|
||||
this.updateColor();
|
||||
},
|
||||
maxChanged: function () {
|
||||
this.updateColor();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<!-- <link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
|
||||
<polymer-element name="chromedash-color-status" attributes="value max">
|
||||
<template>
|
||||
<link rel="stylesheet" href="../css/elements/chromedash-color-status.css">
|
||||
<span id="status"></span>
|
||||
</template>
|
||||
<script>
|
||||
|
@ -28,4 +67,4 @@
|
|||
}
|
||||
});
|
||||
</script>
|
||||
</polymer-element>
|
||||
</polymer-element> -->
|
||||
|
|
|
@ -4,6 +4,312 @@
|
|||
<!-- https://github.com/ljosa/urlize.js -->
|
||||
<script src="urlize.js"></script>
|
||||
|
||||
<dom-module id="html-echo">
|
||||
</dom-module>
|
||||
<script>
|
||||
Polymer({
|
||||
is: 'html-echo',
|
||||
properties: {
|
||||
html: {
|
||||
notify: true,
|
||||
observer: 'htmlChanged'
|
||||
}
|
||||
},
|
||||
htmlChanged: function () {
|
||||
this.innerHTML = this.html;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<dom-module id="multi-links">
|
||||
<template>
|
||||
<link rel="stylesheet" href="../css/shared.css">
|
||||
<template is="dom-repeat" items="{{links}}" as="link" index-as="i">
|
||||
<a href="{{link}}" target="_blank">{{computeExpression1(link, prettyURLFilter)}}</a><template is="dom-if" if="{{computeIf(i, links)}}">,</template>
|
||||
</template>
|
||||
</template>
|
||||
</dom-module>
|
||||
<script>
|
||||
Polymer({
|
||||
is: 'multi-links',
|
||||
properties: { links: { notify: true } },
|
||||
prettyURLFilter: function (val) {
|
||||
return val.replace(/https?:\/\//, '');
|
||||
},
|
||||
computeIf: function (i, links) {
|
||||
return links.length > 1 && i < links.length - 1;
|
||||
},
|
||||
computeExpression1: function (link, prettyURLFilter) {
|
||||
return link | prettyURLFilter;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<dom-module id="chromedash-feature">
|
||||
<template>
|
||||
<link rel="stylesheet" href="../css/elements/chromedash-feature.css">
|
||||
<div on-tap="{{toggle}}">
|
||||
<div class="topcorner pull-right">
|
||||
<span hidden$="{{computeWarningHidden(deprecated, removed)}}" class="tooltip" title="{{computeTitle(removed)}}">
|
||||
<i class="icon-warning-sign" data-tooltip></i>
|
||||
</span>
|
||||
<span hidden$="{{!needsflag}}" class="tooltip" title="Experimental feature behind a flag.">
|
||||
<i class="icon-beaker" data-tooltip></i>
|
||||
</span>
|
||||
<a href="{{computeStandaloneHref(feature)}}" target="_blank" class="tooltip" title="View on standalone page.">
|
||||
<i class="icon-external-link-sign" data-tooltip></i>
|
||||
</a>
|
||||
<content select=".edit"></content>
|
||||
</div>
|
||||
<hgroup>
|
||||
<chromedash-color-status class="tooltip corner" title="Compatibility risk: perceived interest from browser vendors and web developers" value="{{compatRisk}}" max="{{MAX_RISK}}"></chromedash-color-status>
|
||||
<h2>{{feature.name}}</h2>
|
||||
<label class="category" on-tap="{{categoryFilter}}">{{feature.category}}</label>
|
||||
</hgroup>
|
||||
<section class="desc"><summary class="{{computeSummaryClass(open)}}">{{feature.summary}}</summary></section>
|
||||
<template is="dom-if" if="{{open}}">
|
||||
<section>
|
||||
<h3>Implementation Status</h3>
|
||||
<div class="impl_status">
|
||||
<template is="dom-if" if="{{feature.shipped_milestone}}">
|
||||
<span class="chrome_desktop tooltip" title="Chrome desktop"><label>{{feature.impl_status_chrome}}</label>{{feature.shipped_milestone}}</span>
|
||||
</template>
|
||||
<template is="dom-if" if="{{!feature.shipped_milestone}}">
|
||||
<span class="chrome_desktop"><label>{{feature.impl_status_chrome}}</label></span>
|
||||
</template>
|
||||
<template is="dom-if" if="{{feature.shipped_android_milestone}}">
|
||||
<span class="chrome_android tooltip" title="Chrome for Android"><label><i class="icon-android"></i></label>{{feature.shipped_android_milestone}}</span>
|
||||
</template>
|
||||
<template is="dom-if" if="{{feature.shipped_webview_milestone}}">
|
||||
<span class="chrome_webview tooltip" title="Chrome Webview"><label>Webview</label>{{feature.shipped_webview_milestone}}</span>
|
||||
</template>
|
||||
<span hidden$="{{!feature.prefixed}}"><label>Prefixed</label>Yes</span>
|
||||
<span hidden$="{{!feature.bug_url}}"><a href="{{feature.bug_url}}" target="_blank">Launch bug</a></span>
|
||||
<span class="owner" hidden$="{{!feature.owner.length}}">
|
||||
<label>Owner(s)</label><template is="dom-repeat" items="{{feature.owner}}" as="owner" index-as="i">
|
||||
<span on-tap="{{ownerFilter}}">{{owner}}</span><template is="dom-if" if="{{computeShowComma(feature, i)}}">,</template>
|
||||
</template>
|
||||
</span>
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<h3>Consensus & Standardization</h3>
|
||||
<div class="views">
|
||||
<template is="dom-if" if="{{feature.shipped_opera_milestone}}">
|
||||
<span title="{{feature.impl_status_chrome}}">
|
||||
Opera <span>{{feature.shipped_opera_milestone}}</span>
|
||||
</span>
|
||||
</template>
|
||||
<template is="dom-if" if="{{feature.shipped_opera_android_milestone}}">
|
||||
<span title="{{feature.impl_status_chrome}}">
|
||||
Opera for Android <span>{{feature.shipped_opera_android_milestone}}</span>
|
||||
</span>
|
||||
</template>
|
||||
<span title="{{feature.ff_views.text}}">
|
||||
<chromedash-color-status value="{{feature.ff_views.value}}" max="{{MAX_VENDOR_VIEW}}"></chromedash-color-status>
|
||||
<template is="dom-if" if="{{feature.ff_views_link}}">
|
||||
<a href="{{feature.ff_views_link}}" target="_blank">Firefox</a>
|
||||
</template>
|
||||
<label hidden$="{{feature.ff_views_link}}">Firefox</label>
|
||||
</span>
|
||||
<span title="{{feature.ie_views.text}}">
|
||||
<chromedash-color-status value="{{feature.ie_views.value}}" max="{{MAX_VENDOR_VIEW}}"></chromedash-color-status>
|
||||
<template is="dom-if" if="{{feature.ie_views_link}}">
|
||||
<a href="{{feature.ie_views_link}}" target="_blank">IE</a>
|
||||
</template>
|
||||
<label hidden$="{{feature.ie_views_link}}">IE</label>
|
||||
</span>
|
||||
<span title="{{feature.safari_views.text}}">
|
||||
<chromedash-color-status value="{{feature.safari_views.value}}" max="{{MAX_VENDOR_VIEW}}"></chromedash-color-status>
|
||||
<template is="dom-if" if="{{feature.safari_views_link}}">
|
||||
<a href="{{feature.safari_views_link}}" target="_blank">Safari</a>
|
||||
</template>
|
||||
<label hidden$="{{feature.safari_views_link}}">Safari</label>
|
||||
</span>
|
||||
<span title="{{feature.web_dev_views.text}}">
|
||||
<chromedash-color-status value="{{feature.web_dev_views.value}}" max="{{MAX_WEBDEV_VIEW}}"></chromedash-color-status>
|
||||
<label>Web developers</label>
|
||||
</span>
|
||||
</div>
|
||||
<div class="standardization">
|
||||
<span>
|
||||
<chromedash-color-status value="{{feature.standardization.value}}" max="{{MAX_STANDARDS_VAL}}"></chromedash-color-status>
|
||||
<template is="dom-if" if="{{feature.spec_link}}">
|
||||
<a href="{{feature.spec_link}}" target="_blank">{{feature.standardization.text}}</a>
|
||||
</template>
|
||||
<span hidden$="{{feature.spec_link}}">{{feature.standardization.text}}</span>
|
||||
</span>
|
||||
</div>
|
||||
</section>
|
||||
<template is="dom-if" if="{{computeShowResources(feature)}}">
|
||||
<section>
|
||||
<h3>Developer resources</h3>
|
||||
<div>
|
||||
<template is="dom-if" if="{{computeShowDocLinks(feature)}}">
|
||||
<span class="doc_links" hidden$="{{!feature.doc_links.length}}">
|
||||
<label>Documentation</label>
|
||||
<multi-links links="[[feature.doc_links]]"></multi-links>
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
<div>
|
||||
<template is="dom-if" if="{{computeShowDocLinks(feature)}}">
|
||||
<span class="sample_links" hidden$="{{!feature.sample_links.length}}">
|
||||
<label>Samples</label>
|
||||
<multi-links links="[[feature.sample_links]]"></multi-links>
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
<template is="dom-if" if="{{feature.comments}}">
|
||||
<section>
|
||||
<h3>Comments</h3>
|
||||
<summary class="comments"><html-echo html="{{computeHtml(feature)}}"></html-echo></summary>
|
||||
</section>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</dom-module>
|
||||
<script>
|
||||
Polymer({
|
||||
is: 'chromedash-feature',
|
||||
extends: 'li',
|
||||
|
||||
hostAttributes: {
|
||||
tabindex: '0'
|
||||
},
|
||||
|
||||
properties: {
|
||||
MAX_RISK: {
|
||||
type: Number,
|
||||
computed: 'computeMaxRisk(MAX_VENDOR_VIEW, MAX_WEBDEV_VIEW, MAX_STANDARDS_VAL)'
|
||||
},
|
||||
MAX_STANDARDS_VAL: {
|
||||
type: Number,
|
||||
value: 6,
|
||||
readOnly: true
|
||||
},
|
||||
MAX_VENDOR_VIEW: {
|
||||
type: Number,
|
||||
value: 7,
|
||||
readOnly: true
|
||||
},
|
||||
MAX_WEBDEV_VIEW: {
|
||||
type: Number,
|
||||
value: 6,
|
||||
readOnly: true
|
||||
},
|
||||
compatRisk: {
|
||||
type: Number,
|
||||
value: 0
|
||||
},
|
||||
deprecated: {
|
||||
type: Boolean,
|
||||
value: false
|
||||
},
|
||||
feature: {
|
||||
value: null,
|
||||
notify: true,
|
||||
observer: 'featureChanged'
|
||||
},
|
||||
needsflag: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
notify: true
|
||||
},
|
||||
open: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
observer: 'openChanged'
|
||||
}
|
||||
},
|
||||
|
||||
computeMaxRisk: function(vendorViewVal, webdevViewVal, standardsVal) {
|
||||
return vendorViewVal + webdevViewVal + standardsVal;
|
||||
},
|
||||
|
||||
openChanged: function() {
|
||||
this.open ? this.classList.add('open') : this.classList.remove('open');
|
||||
this.fire('feature-toggled', {
|
||||
feature: this.feature,
|
||||
open: this.open
|
||||
});
|
||||
},
|
||||
|
||||
toggle: function(e, details, sender) {
|
||||
// Don't toggle panel if tooltip or link is being clicked.
|
||||
if (e.target.classList.contains('tooltip') || 'tooltip' in e.target.dataset || e.target.tagName == 'A' || e.target.tagName == 'MULTI-LINKS') {
|
||||
return;
|
||||
}
|
||||
this.open = !this.open;
|
||||
},
|
||||
|
||||
calculateCompatRisk: function() {
|
||||
var vendors = (this.feature.ff_views.value + this.feature.ie_views.value + this.feature.safari_views.value) / 3;
|
||||
var webdevs = this.feature.web_dev_views.value;
|
||||
var standards = this.feature.standardization.value;
|
||||
this.compatRisk = vendors + webdevs + standards;
|
||||
},
|
||||
|
||||
featureChanged: function() {
|
||||
this.calculateCompatRisk(); // TODO: these values are brittle if the strings change.
|
||||
// TODO: these values are brittle if the strings change.
|
||||
this.removed = this.feature.impl_status_chrome == 'Removed';
|
||||
this.deprecated = this.feature.impl_status_chrome == 'Deprecated' || this.feature.impl_status_chrome == 'No longer pursuing';
|
||||
},
|
||||
|
||||
categoryFilter: function(e, details, sender) {
|
||||
e.stopPropagation();
|
||||
this.fire('filter-category', {val: sender.textContent});
|
||||
},
|
||||
|
||||
ownerFilter: function(e, details, sender) {
|
||||
e.stopPropagation();
|
||||
this.fire('filter-owner', {val: sender.textContent});
|
||||
},
|
||||
|
||||
_urlizeFilter: function(val) {
|
||||
return urlize(val, {target: '_blank', trim: 'www', autoescape: true});
|
||||
},
|
||||
|
||||
computeDeprecatedHidden: function(deprecated, removed) {
|
||||
return !removed && !deprecated;
|
||||
},
|
||||
computeTitle: function(removed) {
|
||||
return removed ? 'Removed feature' : 'Deprecated feature';
|
||||
},
|
||||
computeStandaloneHref: function(feature) {
|
||||
return '/feature/' + feature.id;
|
||||
},
|
||||
computeSummaryClass: function(open) {
|
||||
return open ? 'open' : '';
|
||||
},
|
||||
computeShowResources: function(feature) {
|
||||
return feature.doc_links.length || feature.sample_links.length;
|
||||
},
|
||||
computeShowComma: function(feature, i) {
|
||||
return feature.owner.length > 1 && i < feature.owner.length - 1;
|
||||
},
|
||||
computeShowDocLinks: function(feature) {
|
||||
return feature.doc_links && feature.doc_links.length > 0;
|
||||
},
|
||||
computeSampleLinks: function(feature) {
|
||||
return feature.sample_links && feature.sample_links.length > 0;
|
||||
},
|
||||
computeHtml: function(feature) {
|
||||
return this._urlizeFilter(feature.comments);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<!-- <link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
<link rel="import" href="chromedash-color-status.html">
|
||||
|
||||
<script src="urlize.js"></script>
|
||||
|
||||
|
||||
<polymer-element name="html-echo" attributes="html">
|
||||
<script>
|
||||
|
@ -217,3 +523,4 @@
|
|||
</script>
|
||||
</polymer-element>
|
||||
|
||||
-->
|
||||
|
|
|
@ -1,6 +1,280 @@
|
|||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
<link rel="import" href="chromedash-feature.html">
|
||||
|
||||
<dom-module id="chromedash-featurelist">
|
||||
<template>
|
||||
<link rel="stylesheet" href="../css/elements/chromedash-featurelist.css">
|
||||
<ul>
|
||||
<template is="dom-repeat" items="{{filtered}}" as="feature">
|
||||
<div hidden$="{{computeMilestoneHidden(feature, features, filtered)}}" class="milestone-marker">{{feature.meta.milestone_str}}</div>
|
||||
<li id="{{feature.id}}" is="chromedash-feature" feature="{{feature}}" needsflag="{{feature.meta.needsflag}}">
|
||||
<a href="{{computeHref(feature)}}" class="edit" hidden$="{{computeEditLinkHidden(whitelisted)}}">edit</a>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</template>
|
||||
</dom-module>
|
||||
<script>
|
||||
(function () {
|
||||
function onMetadataChanged_(e) {
|
||||
this.scrollToMilestone(e.detail.version);
|
||||
}
|
||||
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 '=='.
|
||||
// Support both '=' and '=='.
|
||||
case '==':
|
||||
return milestone == version;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
var featureLiList_ = [];
|
||||
Polymer({
|
||||
is: 'chromedash-featurelist',
|
||||
properties: {
|
||||
features: {
|
||||
notify: true,
|
||||
observer: 'featuresChanged'
|
||||
},
|
||||
filtered: { observer: 'filteredChanged' },
|
||||
metadata: {
|
||||
value: null,
|
||||
observer: 'metadataChanged'
|
||||
},
|
||||
search: { value: null },
|
||||
whitelisted: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
notify: true
|
||||
}
|
||||
},
|
||||
created: function () {
|
||||
this.features = this.features || [];
|
||||
this.filtered = this.filtered || [];
|
||||
},
|
||||
onKeyUp: function (e, detail, sender) {
|
||||
var focusedEl = this.shadowRoot.querySelector(':focus');
|
||||
if (focusedEl) {
|
||||
switch (e.keyCode) {
|
||||
case 13:
|
||||
// enter
|
||||
//focusedEl.toggle();
|
||||
focusedEl.open = !focusedEl.open;
|
||||
break;
|
||||
default: // noop
|
||||
}
|
||||
}
|
||||
},
|
||||
// noop
|
||||
onScrollList: function (e, detail, sender) {
|
||||
var feature = this.featureInView(sender.scrollTop);
|
||||
this.metadata.selectMilestone(feature);
|
||||
},
|
||||
onFeatureToggled: function (e, detail, sender) {
|
||||
var feature = detail.feature;
|
||||
var open = detail.open;
|
||||
if (history && history.replaceState) {
|
||||
if (open) {
|
||||
history.pushState({ id: feature.id }, feature.name, '/features/' + feature.id);
|
||||
} else {
|
||||
var hash = this.search.value ? '#' + this.search.value : '';
|
||||
history.replaceState({ id: null }, feature.name, '/features' + hash);
|
||||
}
|
||||
}
|
||||
},
|
||||
metadataChanged: function (_, oldVal) {
|
||||
// TODO: probably need to remove the listener if metadata element changes.
|
||||
//this.metadata.removeEventListener('milestoneselect', onMetadataChanged_.bind(this));
|
||||
this.metadata.addEventListener('milestoneselect', onMetadataChanged_.bind(this));
|
||||
},
|
||||
featuresChanged: function () {
|
||||
if (!this.features || !this.features.length) {
|
||||
return;
|
||||
}
|
||||
this.filter(location.hash.substr(1));
|
||||
document.body.classList.add('ready'); // unmask app.
|
||||
// unmask app.
|
||||
this.async(function () {
|
||||
// If there's an id in the URL, highlight and scroll to the feature.
|
||||
// Otherwise, go to the first "in development" feature.
|
||||
// TODO: really want this in ready(), but featureLiList and metadata may
|
||||
// not be set yet due to timing issues.
|
||||
var lastSlash = location.pathname.lastIndexOf('/');
|
||||
if (lastSlash > 0) {
|
||||
var id = parseInt(location.pathname.substring(lastSlash + 1));
|
||||
this.scrollToFeature(id);
|
||||
} else {
|
||||
this.scrollToMilestone(this.metadata.implStatuses[this.metadata.STATUS.IN_DEVELOPMENT - 1].val);
|
||||
}
|
||||
});
|
||||
},
|
||||
filterOnCategory: function (val) {
|
||||
var regex = new RegExp(val, 'i');
|
||||
this.filtered = this.features.filter(function (feature, idx, array) {
|
||||
return regex.test(feature.category);
|
||||
});
|
||||
return this.filtered.length;
|
||||
},
|
||||
filterOnOwner: function (val) {
|
||||
var regex = new RegExp(val, 'i');
|
||||
this.filtered = this.features.filter(function (feature, idx, array) {
|
||||
return regex.test(feature.owner.toString());
|
||||
});
|
||||
return this.filtered.length;
|
||||
},
|
||||
filteredChanged: function () {
|
||||
// Wait one rAF do model has updated the DOM.
|
||||
this.async(function () {
|
||||
featureLiList_ = this.shadowRoot.querySelectorAll('li');
|
||||
});
|
||||
},
|
||||
filter: function (val) {
|
||||
// Clear filter if there's no search or if called directly.
|
||||
if (!val) {
|
||||
if (history && history.replaceState) {
|
||||
history.replaceState('', document.title, location.pathname + location.search);
|
||||
} else {
|
||||
location.hash = '';
|
||||
}
|
||||
this.filtered = this.features;
|
||||
} else {
|
||||
val = val.trim();
|
||||
if (history && history.replaceState) {
|
||||
history.replaceState({ id: null }, document.title, '/features#' + val);
|
||||
} // owner: user@chromium.org
|
||||
// owner: user@chromium.org
|
||||
var match = val.match(/^owner:\s*(.*)/);
|
||||
if (match) {
|
||||
return this.filterOnOwner(match[1]);
|
||||
} // category: Multimedia
|
||||
// category: Multimedia
|
||||
var match = val.match(/^category:\s*(.*)/);
|
||||
if (match) {
|
||||
return this.filterOnCategory(match[1]);
|
||||
} // Returns operator and version query e.g. ["<=25", "<=", "25"].
|
||||
// Returns operator and version query e.g. ["<=25", "<=", "25"].
|
||||
var result = /^([<>=]=?)\s*?([0-9]+)/.exec(val);
|
||||
if (result) {
|
||||
var operator = result[1];
|
||||
var version = parseInt(result[2]);
|
||||
this.filtered = this.features.filter(function (feature, idx, array) {
|
||||
var platformMilestones = [
|
||||
parseInt(feature.shipped_milestone),
|
||||
parseInt(feature.shipped_android_milestone),
|
||||
parseInt(feature.shipped_ios_milestone),
|
||||
parseInt(feature.shipped_webview_milestone)
|
||||
];
|
||||
for (var i = 0, milestone; milestone = platformMilestones[i]; ++i) {
|
||||
if (matchesMilestone_(milestone, operator, version)) {
|
||||
return true; // Only one of the platforms needs to match.
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Only one of the platforms needs to match.
|
||||
// Do a general search over misc fields.
|
||||
// Remove "=" prefix so "in development"/"proposed" version queries
|
||||
// return results.
|
||||
if (val.indexOf('=') == 0) {
|
||||
val = val.substring(1);
|
||||
}
|
||||
var regex = new RegExp(val, 'i');
|
||||
this.filtered = this.features.filter(function (feature, idx, array) {
|
||||
return regex.test(feature.name) || regex.test(feature.category) || regex.test(feature.summary) || regex.test(feature.search_tags) || /*regex.test(feature.shipped_milestone) ||*/
|
||||
regex.test(feature.impl_status_chrome);
|
||||
});
|
||||
}
|
||||
}
|
||||
this.filteredChanged();
|
||||
return this.filtered.length;
|
||||
},
|
||||
// Returns the closest feature <li> to the scroll top position passed in.
|
||||
featureInView: function (containerScrollTop) {
|
||||
var closest = null;
|
||||
for (var i = 0, li; li = featureLiList_[i]; ++i) {
|
||||
var dist = li.offsetTop - containerScrollTop;
|
||||
if (dist < 0) {
|
||||
dist = -dist * 1.5;
|
||||
}
|
||||
if (closest == null || dist < closest.dist) {
|
||||
//closest = this.features[i];
|
||||
closest = this.filtered[i];
|
||||
closest.dist = dist;
|
||||
}
|
||||
}
|
||||
return closest;
|
||||
},
|
||||
// Returns the index of the first feature of a given milestone string.
|
||||
firstOfMilestone: function (milestone, opt_startFrom) {
|
||||
var start = opt_startFrom != undefined ? opt_startFrom : 0;
|
||||
for (var i = start, feature; feature = this.filtered[i]; ++i) {
|
||||
if (feature.first_of_milestone && [
|
||||
String(feature.shipped_milestone),
|
||||
feature.impl_status_chrome
|
||||
].indexOf(String(milestone)) != -1) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
},
|
||||
scrollToMilestone: function (milestone) {
|
||||
var idx = this.firstOfMilestone(milestone);
|
||||
if (idx != -1) {
|
||||
var el = featureLiList_[idx].previousElementSibling || featureLiList_[idx];
|
||||
// TODO(ericbidelman): Implement smooth scrolling when
|
||||
// https://twitter.com/ebidel/status/364825118171602944 lands.
|
||||
el.scrollIntoView(true); // el.scrollIntoView(true, {behavior: 'smooth'});
|
||||
}
|
||||
},
|
||||
// el.scrollIntoView(true, {behavior: 'smooth'});
|
||||
scrollToFeature: function (id) {
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
for (var i = 0, f; f = this.filtered[i]; ++i) {
|
||||
if (f.id == id) {
|
||||
featureLiList_[i].scrollIntoView(true);
|
||||
featureLiList_[i].open = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
listeners: {
|
||||
'scroll': 'onScrollList',
|
||||
'keyup': 'onKeyUp',
|
||||
'feature-toggled': 'onFeatureToggled'
|
||||
},
|
||||
computeMilestoneHidden: function (feature, features, filtered) {
|
||||
return filtered.length != features.length || !feature.first_of_milestone;
|
||||
},
|
||||
computeEditLinkHidden: function (feature) {
|
||||
return '/admin/features/edit/' + feature.id;
|
||||
},
|
||||
computeHidden: function (whitelisted) {
|
||||
return !whitelisted;
|
||||
}
|
||||
});
|
||||
}());
|
||||
</script>
|
||||
|
||||
|
||||
<!-- <link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
<link rel="import" href="chromedash-feature.html">
|
||||
|
||||
<polymer-element name="chromedash-featurelist" attributes="whitelisted features" on-scroll="{{onScrollList}}" on-keyup="{{onKeyUp}}" on-feature-toggled="{{onFeatureToggled}}">
|
||||
<template>
|
||||
<link rel="stylesheet" href="../css/elements/chromedash-featurelist.css">
|
||||
|
@ -261,3 +535,4 @@
|
|||
})();
|
||||
</script>
|
||||
</polymer-element>
|
||||
-->
|
||||
|
|
|
@ -1,5 +1,84 @@
|
|||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
<link rel="import" href="../bower_components/paper-dialog/paper-dialog.html">
|
||||
<link rel="import" href="../bower_components/neon-animation/animations/scale-up-animation.html">
|
||||
<link rel="import" href="../bower_components/neon-animation/animations/fade-out-animation.html">
|
||||
|
||||
<dom-module id="chromedash-legend">
|
||||
<template>
|
||||
<link rel="stylesheet" href="../css/elements/chromedash-legend.css">
|
||||
<paper-dialog id="overlay" opened="{{opened}}" entry-animation="scale-up-animation" exit-animation="fade-out-animation" with-backdrop>
|
||||
<div class="dialog">
|
||||
<h3>About the data</h3>
|
||||
<section>
|
||||
<content class="description"></content>
|
||||
</section>
|
||||
<h3>Color legend</h3>
|
||||
<p>Colors indicate the "compatibility risk" for a given feature. The risk
|
||||
increases as <chromedash-color-status value="1" max="{{views.vendors.length}}"></chromedash-color-status> → <chromedash-color-status value="{{views.vendors.length}}" max="{{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>
|
||||
<template is="dom-repeat" items="{{views.vendors}}" as="o">
|
||||
<li><chromedash-color-status value="{{o.key}}" max="{{views.vendors.length}}"></chromedash-color-status>{{o.val}}</li>
|
||||
</template>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<label>Web developer</label>
|
||||
<ul>
|
||||
<template is="dom-repeat" items="{{views.webdevs}}" as="o">
|
||||
<li><chromedash-color-status value="{{o.key}}" max="{{views.webdevs.length}}"></chromedash-color-status>{{o.val}}</li>
|
||||
</template>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<label>Standards values</label>
|
||||
<ul>
|
||||
<template is="dom-repeat" items="{{views.standards}}" as="o">
|
||||
<li><chromedash-color-status value="{{o.key}}" max="{{views.standards.length}}"></chromedash-color-status>{{o.val}}</li>
|
||||
</template>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
<h3>Search</h3>
|
||||
<section>
|
||||
<label>Example search queries</label>
|
||||
<ul class="queries">
|
||||
<li><span>"<30"</span>features that landed before 30</li>
|
||||
<li><span>"<=30"</span>features in 30</li>
|
||||
<li><span>"=30"</span>features that landed in 30</li>
|
||||
<li><span>">28"</span>features since 28
|
||||
</li><li><span>"behind a flag"</span>all experimental features
|
||||
<!-- <li><span>"category: CSS"</span>features in the category CSS</li> -->
|
||||
</li><li><span>"owner: user@example.org"</span>features owned by user@example.org</li>
|
||||
</ul>
|
||||
</section>
|
||||
<content></content>
|
||||
</div>
|
||||
</paper-dialog>
|
||||
</template>
|
||||
</dom-module>
|
||||
<script>
|
||||
Polymer({
|
||||
is: 'chromedash-legend',
|
||||
properties: {
|
||||
opened: {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
notify: true
|
||||
}
|
||||
},
|
||||
toggle: function () {
|
||||
this.$.overlay.toggle();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<!-- <link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
<link rel="import" href="../bower_components/paper-dialog/paper-dialog.html">
|
||||
<link rel="import" href="../bower_components/paper-dialog/paper-dialog-transition.html">
|
||||
|
||||
<polymer-element name="chromedash-legend" attributes="opened">
|
||||
|
@ -50,7 +129,6 @@
|
|||
<li><span>"=30"</span>features that landed in 30</li>
|
||||
<li><span>">28"</span>features since 28
|
||||
<li><span>"behind a flag"</span>all experimental features
|
||||
<!-- <li><span>"category: CSS"</span>features in the category CSS</li> -->
|
||||
<li><span>"owner: user@example.org"</span>features owned by user@example.org</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
@ -67,3 +145,4 @@
|
|||
});
|
||||
</script>
|
||||
</polymer-element>
|
||||
-->
|
||||
|
|
|
@ -1,4 +1,103 @@
|
|||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
<link rel="import" href="../bower_components/iron-ajax/iron-ajax.html">
|
||||
|
||||
<dom-module id="chromedash-metadata">
|
||||
<template>
|
||||
<link rel="stylesheet" href="../css/elements/chromedash-metadata.css">
|
||||
<iron-ajax auto="" url="https://omahaproxy.appspot.com/all.json" handle-as="json" on-core-response="{{onResponse}}"></iron-ajax>
|
||||
<ul class="{{computeClass(betaIsDev, canaryIsDev, tokenList)}}">
|
||||
<template is="dom-repeat" items="{{versions}}" as="v">
|
||||
<li data-version="{{v}}" on-tap="{{selectMilestone}}" selected$="{{computeSelected(selected, v)}}">{{v}}</li>
|
||||
</template>
|
||||
</ul>
|
||||
<div hidden$="{{computeHidden(fetchError)}}" class="error">Error fetching Chrome version information.</div>
|
||||
</template>
|
||||
</dom-module>
|
||||
<script>
|
||||
Polymer({
|
||||
is: 'chromedash-metadata',
|
||||
properties: {
|
||||
betaIsDev: {
|
||||
type: Boolean,
|
||||
value: false
|
||||
},
|
||||
canaryIsDev: {
|
||||
type: Boolean,
|
||||
value: false
|
||||
},
|
||||
fetchError: {
|
||||
type: Boolean,
|
||||
value: false
|
||||
},
|
||||
selected: { value: null }
|
||||
},
|
||||
created: function () {
|
||||
this.implStatuses = this.implStatuses || {};
|
||||
this.STATUS = this.STATUS || {};
|
||||
this.channels = this.channels || {};
|
||||
this.versions = this.versions || [];
|
||||
},
|
||||
onResponse: function (e, detail, sender) {
|
||||
if (detail.response.type == 'error') {
|
||||
this.fetchError = true;
|
||||
return;
|
||||
}
|
||||
// TODO(ericbidelman): Share this data across instances.
|
||||
var windowsVersions = detail.response[0];
|
||||
for (var i = 0, el; el = windowsVersions.versions[i]; ++i) {
|
||||
// Include previous version if current is foobar'd.
|
||||
this.channels[el.channel] = parseInt(el.version) || parseInt(el.prev_version);
|
||||
}
|
||||
// Dev channel explicitly left out. Treat same as canary.
|
||||
this.versions = [
|
||||
this.implStatuses[this.STATUS.NO_ACTIVE_DEV - 1].val,
|
||||
this.implStatuses[this.STATUS.PROPOSED - 1].val,
|
||||
this.implStatuses[this.STATUS.IN_DEVELOPMENT - 1].val,
|
||||
this.channels.canary,
|
||||
this.channels.dev,
|
||||
this.channels.beta,
|
||||
this.channels.stable
|
||||
];
|
||||
// Consolidate channels if they're the same.
|
||||
if (this.channels.canary == this.channels.dev) {
|
||||
this.versions.splice(4, 1);
|
||||
this.canaryIsDev = true;
|
||||
} else if (this.channels.dev == this.channels.beta) {
|
||||
this.versions.splice(5, 1);
|
||||
this.betaIsDev = true;
|
||||
}
|
||||
for (var i = this.channels.stable - 1; i >= 1; i--) {
|
||||
this.versions.push(i);
|
||||
}
|
||||
this.versions.push(this.implStatuses[this.implStatuses.length - 1].val);
|
||||
},
|
||||
selectMilestone: function (e, details, sender) {
|
||||
if (details) {
|
||||
// Came from an internal click.
|
||||
this.selected = sender.dataset.version;
|
||||
this.fire('milestoneselect', { version: this.selected });
|
||||
} else {
|
||||
// Called directly (from outside). e is a feature.
|
||||
this.selected = e.meta.milestone_str;
|
||||
}
|
||||
},
|
||||
computeClass: function (betaIsDev, canaryIsDev, tokenList) {
|
||||
return {
|
||||
canaryisdev: canaryIsDev,
|
||||
betaisdev: betaIsDev
|
||||
} | tokenList;
|
||||
},
|
||||
computeHidden: function (fetchError) {
|
||||
return !fetchError;
|
||||
},
|
||||
computeSelected: function (selected, v) {
|
||||
return v == selected;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<!-- <link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
<link rel="import" href="../bower_components/core-ajax/core-ajax.html">
|
||||
|
||||
<polymer-element name="chromedash-metadata">
|
||||
|
@ -76,3 +175,4 @@
|
|||
});
|
||||
</script>
|
||||
</polymer-element>
|
||||
-->
|
||||
|
|
|
@ -1,6 +1,133 @@
|
|||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
<link rel="import" href="../bower_components/iron-ajax/iron-ajax.html">
|
||||
<link rel="import" href="x-meter.html">
|
||||
|
||||
<dom-module id="chromedash-metrics">
|
||||
<template>
|
||||
<link rel="stylesheet" href="../css/elements/chromedash-metrics.css">
|
||||
<iron-ajax auto="" url="{{endpoint}}" last-response="{{props}}" handle-as="json"></iron-ajax>
|
||||
<b>Showing <span>{{props.length}}</span> properties</b>
|
||||
<ol id="stack-rank-list">
|
||||
<li class="header">
|
||||
<label on-tap="{{sort}}" data-order="property_name">Name <i class="{{computePropertyNameClass(sortOrders, tokenList)}}"></i></label>
|
||||
<label on-tap="{{sort}}" data-order="percentage" class="flex2">Percentage <i class="{{computePercentClass(sortOrders, tokenList)}}"></i></label>
|
||||
</li>
|
||||
<template is="dom-if" if="{{computeIf(props)}}">
|
||||
<li class="loading">Loading...</li>
|
||||
</template>
|
||||
<template is="dom-repeat" items="{{viewList}}" as="prop">
|
||||
<li id="{{prop.property_name}}" hidden$="{{computeHidden(prop)}}" tabindex="0">
|
||||
<label><a href="{{computeHref(prop)}}" title="Deep link to this property" alt="Deep link to this property">{{prop.property_name}}</a></label>
|
||||
<x-meter value="{{prop.percentage}}" on-tap="{{showTimeline}}" title="Click to see a timeline view of this property"></x-meter>
|
||||
</li>
|
||||
</template>
|
||||
</ol>
|
||||
</template>
|
||||
</dom-module>
|
||||
<script>
|
||||
(function () {
|
||||
function sortBy_(prop, arr, opt_compareAsNumbers) {
|
||||
var compareAsNumbers = opt_compareAsNumbers || false;
|
||||
arr.sort(function (a, b) {
|
||||
var propA = compareAsNumbers ? Number(a[prop]) : a[prop];
|
||||
var propB = compareAsNumbers ? Number(b[prop]) : b[prop];
|
||||
if (propA > propB) {
|
||||
return 1;
|
||||
}
|
||||
if (propA < propB) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
Polymer({
|
||||
is: 'chromedash-metrics',
|
||||
properties: {
|
||||
props: { observer: 'propsChanged' },
|
||||
sortOrders: {
|
||||
type: Object,
|
||||
value: function () {
|
||||
return {
|
||||
property_name: {
|
||||
reverse: false,
|
||||
activated: false
|
||||
},
|
||||
percentage: {
|
||||
reverse: true,
|
||||
activated: true
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
value: '',
|
||||
notify: true
|
||||
},
|
||||
view: { notify: true }
|
||||
},
|
||||
created: function () {
|
||||
this.props = [];
|
||||
this.viewList = [];
|
||||
},
|
||||
propsChanged: function () {
|
||||
if (!this.props || !this.props.length) {
|
||||
return;
|
||||
}
|
||||
for (var i = 0, prop; prop = this.props[i]; ++i) {
|
||||
prop.percentage = (prop.day_percentage * 100).toFixed(4);
|
||||
}
|
||||
this.viewList = this.props;
|
||||
if (location.hash) {
|
||||
this.async(function () {
|
||||
this.scrollToProperty(location.hash.split('#')[1]);
|
||||
});
|
||||
}
|
||||
},
|
||||
get endpoint() {
|
||||
return '/data/' + this.type + this.view;
|
||||
},
|
||||
sort: function (e, detail, sender) {
|
||||
e.preventDefault();
|
||||
var order = sender.dataset.order;
|
||||
sortBy_(order, this.viewList, order == 'percentage');
|
||||
this.sortOrders[order].activated = true;
|
||||
this.sortOrders[order].reverse = !this.sortOrders[order].reverse;
|
||||
if (this.sortOrders[order].reverse) {
|
||||
this.viewList.reverse();
|
||||
}
|
||||
},
|
||||
showTimeline: function (e, detail, sender) {
|
||||
window.location.href = '/metrics/' + this.type + '/timeline/' + this.view + '/' + e.target.templateInstance.model.prop.bucket_id;
|
||||
},
|
||||
scrollToProperty: function (prop) {
|
||||
if (prop) {
|
||||
var el = this.$['stack-rank-list'].querySelector('#' + prop);
|
||||
el.scrollIntoView(true); //el.scrollIntoView(true, {behavior: 'smooth'});
|
||||
}
|
||||
},
|
||||
computePropertyNameClass: function (sortOrders, tokenList) {
|
||||
return 'icon-long-arrow-' + (sortOrders.property_name.reverse ? 'up' : 'down') + ' ' + ({ activated: sortOrders.property_name.activated } | tokenList);
|
||||
},
|
||||
computePercentClass: function (sortOrders, tokenList) {
|
||||
return 'icon-long-arrow-' + (sortOrders.percentage.reverse ? 'down' : 'up') + ' ' + ({ activated: sortOrders.percentage.activated } | tokenList);
|
||||
},
|
||||
computeIf: function (props) {
|
||||
return !props.length;
|
||||
},
|
||||
computeHidden: function (prop) {
|
||||
return prop.property_name == 'ERROR';
|
||||
},
|
||||
computeHref: function (prop) {
|
||||
return '#' + prop.property_name;
|
||||
}
|
||||
});
|
||||
}());
|
||||
</script>
|
||||
|
||||
|
||||
<!-- <link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
<link rel="import" href="../bower_components/core-ajax/core-ajax.html">
|
||||
<!-- <link rel="import" href="chromedash-color-status.html"> -->
|
||||
<link rel="import" href="x-meter.html">
|
||||
|
||||
<polymer-element name="chromedash-metrics" attributes="type view">
|
||||
|
@ -103,3 +230,4 @@
|
|||
})();
|
||||
</script>
|
||||
</polymer-element>
|
||||
-->
|
||||
|
|
|
@ -1,4 +1,132 @@
|
|||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
<link rel="import" href="../bower_components/iron-ajax/iron-ajax.html">
|
||||
<link rel="import" href="../bower_components/google-apis/google-js-api.html">
|
||||
|
||||
<dom-module id="chromedash-feature-timeline">
|
||||
<template>
|
||||
<link rel="stylesheet" href="../css/elements/chromedash-timeline.css">
|
||||
<google-js-api on-api-load="{{chartAPILoaded}}"></google-js-api>
|
||||
<select value="{{selectedBucketId::input}}">
|
||||
<option disabled value="1">Select a property</option>
|
||||
<option template repeat="{{computeRepeat(bucket, props)}}" value="{{bucket[0]}}">{{bucket[1]}}</option>
|
||||
</select>
|
||||
<div id="chart"></div>
|
||||
</template>
|
||||
</dom-module>
|
||||
<script>
|
||||
Polymer({
|
||||
is: 'chromedash-feature-timeline',
|
||||
|
||||
properties: {
|
||||
selectedBucketId: {
|
||||
type: Number,
|
||||
value: 1,
|
||||
observer: 'selectedBucketIdChanged'
|
||||
},
|
||||
timeline: {
|
||||
notify: true
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
value: ''
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
value: '',
|
||||
notify: true
|
||||
},
|
||||
view: {
|
||||
type: String,
|
||||
value: '',
|
||||
notify: true
|
||||
},
|
||||
props: {
|
||||
type: Array,
|
||||
value: function() { return []; }
|
||||
}
|
||||
},
|
||||
|
||||
selectedBucketIdChanged: function () {
|
||||
var ajax = document.createElement('iron-ajax');
|
||||
ajax.url = '/data/timeline/' + this.type + this.view;
|
||||
ajax.params = {bucket_id: this.selectedBucketId};
|
||||
ajax.handleAs = 'json';
|
||||
|
||||
ajax.addEventListener('response', function(e) {
|
||||
this.drawVisualization(e.detail.response, this.selectedBucketId);
|
||||
}.bind(this));
|
||||
|
||||
ajax.generateRequest();
|
||||
|
||||
if (history.pushState) {
|
||||
history.pushState({ id: this.selectedBucketId }, '', '/metrics/' + this.type + '/timeline/' + this.view + '/' + this.selectedBucketId);
|
||||
}
|
||||
},
|
||||
chartAPILoaded: function (e, detail, sender) {
|
||||
google.load('visualization', '1.0', {
|
||||
packages: ['corechart'],
|
||||
callback: function () {
|
||||
// If there's an id in the URL, load the property it.
|
||||
var lastSlash = location.pathname.lastIndexOf('/');
|
||||
if (lastSlash > 0) {
|
||||
var id = parseInt(location.pathname.substring(lastSlash + 1));
|
||||
if (String(id) != 'NaN') {
|
||||
this.selectedBucketId = id;
|
||||
}
|
||||
}
|
||||
}.bind(this)
|
||||
});
|
||||
},
|
||||
drawVisualization: function (data, bucketId) {
|
||||
var table = new google.visualization.DataTable();
|
||||
table.addColumn('date', 'Date');
|
||||
table.addColumn('number', 'Percentage');
|
||||
var rowArray = [];
|
||||
for (var i = 0, item; item = data[i]; ++i) {
|
||||
var dateStr = item.date.split('-');
|
||||
var date = new Date();
|
||||
date.setFullYear(parseInt(dateStr[0]), parseInt(dateStr[1]) - 1, parseInt(dateStr[2]));
|
||||
rowArray.push([
|
||||
date,
|
||||
parseFloat((item.day_percentage * 100).toFixed(4))
|
||||
]);
|
||||
}
|
||||
table.addRows(rowArray);
|
||||
var options = {
|
||||
title: this.title,
|
||||
legend: { position: 'none' },
|
||||
vAxis: {
|
||||
title: 'Percentage',
|
||||
// maxValue: 100,
|
||||
minValue: 0
|
||||
},
|
||||
hAxis: {
|
||||
title: 'Date',
|
||||
format: 'yyyy-MM-dd'
|
||||
},
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
// chartArea: {width: '75%'},
|
||||
pointSize: 5
|
||||
}; // var dataView = new google.visualization.DataView(table);
|
||||
// dataView.setColumns([{calc: function(data, row) {
|
||||
// return data.getFormattedValue(row, 0);
|
||||
// }, type:'string'}, 1]);
|
||||
// var dataView = new google.visualization.DataView(table);
|
||||
// dataView.setColumns([{calc: function(data, row) {
|
||||
// return data.getFormattedValue(row, 0);
|
||||
// }, type:'string'}, 1]);
|
||||
var chart = new google.visualization.LineChart(this.$.chart);
|
||||
chart.draw(table, options);
|
||||
},
|
||||
computeRepeat: function (bucket, props) {
|
||||
return bucket in props;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<!-- <link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
<link rel="import" href="../bower_components/core-ajax/core-ajax.html">
|
||||
<link rel="import" href="../bower_components/google-apis/google-jsapi.html">
|
||||
|
||||
|
@ -10,9 +138,7 @@
|
|||
<option disabled value="1">Select a property</option>
|
||||
<option template repeat="{{bucket in props}}" value="{{bucket[0]}}">{{bucket[1]}}</option>
|
||||
</select>
|
||||
<!-- <div id="chart-container" style="width:900px;margin: 0 auto;"> -->
|
||||
<div id="chart"></div>
|
||||
<!-- </div> -->
|
||||
<div id="chart"></div>
|
||||
</template>
|
||||
<script>
|
||||
Polymer({
|
||||
|
@ -104,4 +230,4 @@
|
|||
}
|
||||
});
|
||||
</script>
|
||||
</polymer-element>
|
||||
</polymer-element> -->
|
||||
|
|
|
@ -1,5 +1,103 @@
|
|||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
<link rel="import" href="../bower_components/core-ajax/core-xhr.html">
|
||||
<link rel="import" href="../bower_components/iron-ajax/iron-request.html">
|
||||
|
||||
<dom-module id="chromedash-userlist">
|
||||
<template>
|
||||
<link rel="stylesheet" href="../css/elements/chromedash-userlist.css">
|
||||
<form id="form" name="user_form" method="post" action="{{action}}" onsubmit="return false;">
|
||||
<table>
|
||||
<tr>
|
||||
<td><input type="email" placeholder="Email address" name="email" id="id_email" required></td>
|
||||
<td><input type="submit" on-tap="{{ajaxSubmit}}"></td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
<hr>
|
||||
<ul id="user-list">
|
||||
<template is="dom-repeat" items="{{users}}" as="user">
|
||||
<li>
|
||||
<a href="{{computeDeleteHref(action, user)}}" on-tap="{{ajaxDelete}}">delete</a><span>{{user.email}}</span>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</template>
|
||||
</dom-module>
|
||||
<script>
|
||||
Polymer({
|
||||
is: 'chromedash-userlist',
|
||||
properties: {
|
||||
action: {
|
||||
type: String
|
||||
},
|
||||
users: {
|
||||
type: Array,
|
||||
value: function () { return []; },
|
||||
notify: true
|
||||
}
|
||||
},
|
||||
computeDeleteHref: function(action, user) {
|
||||
return action + '/' + user.id;
|
||||
},
|
||||
addUser: function (user) {
|
||||
this.users.splice(0, 0, user);
|
||||
},
|
||||
removeUser: function (idx) {
|
||||
return this.users.splice(idx, 1);
|
||||
},
|
||||
ajaxSubmit: function (e, details, sender) {
|
||||
e.preventDefault();
|
||||
if (this.$.form.checkValidity()) {
|
||||
// TODO(ericbidelman): move back to this.$.form.email.value when SD
|
||||
// polyfill merges the commit that wraps element.form.
|
||||
var email = this.$.form.querySelector('input[name="email"]').value;
|
||||
var formData = new FormData();
|
||||
formData.append('email', email);
|
||||
var xhr = document.createElement('iron-request');
|
||||
xhr.send({
|
||||
url: this.action,
|
||||
method: 'POST',
|
||||
body: formData
|
||||
}).then(function (request) {
|
||||
if (request.status == 201) {
|
||||
this.addUser(JSON.parse(response));
|
||||
this.$.form.reset();
|
||||
} else if (request.status == 200) {
|
||||
alert('Thanks. But that user already exists');
|
||||
}
|
||||
}.bind(this));
|
||||
}
|
||||
},
|
||||
ajaxDelete: function (e, details, sender) {
|
||||
e.preventDefault();
|
||||
if (!confirm('Remove user?')) {
|
||||
return;
|
||||
} // Get index of user model instance that was clicked from template.
|
||||
// Get index of user model instance that was clicked from template.
|
||||
var user = e.target.templateInstance.model.user;
|
||||
var idx = this.users.indexOf(user);
|
||||
var xhr = document.createElement('iron-request');
|
||||
xhr.request({
|
||||
url: sender.href,
|
||||
method: 'POST'
|
||||
}).then(function (request) {
|
||||
e.target.parentElement.classList.add('faded');
|
||||
if ('ontransitionend' in window) {
|
||||
var li = sender.parentElement;
|
||||
li.addEventListener('transitionend', function (e) {
|
||||
this.removeUser(idx);
|
||||
}.bind(this));
|
||||
li.classList.add('faded');
|
||||
} else {
|
||||
this.removeUser(idx);
|
||||
}
|
||||
}.bind(this));
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<!-- <link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
<link rel="import" href="../bower_components/iron-ajax/iron-request.html">
|
||||
|
||||
<polymer-element name="chromedash-userlist" attributes="users action">
|
||||
<template>
|
||||
|
@ -7,25 +105,32 @@
|
|||
<form id="form" name="user_form" method="post" action="{{action}}" onsubmit="return false;">
|
||||
<table>
|
||||
<tr>
|
||||
<td><input type="email" placeholder="Email address" name="email" id="id_email" required /></td>
|
||||
<td><input type="email" placeholder="Email address" name="email" id="id_email" required></td>
|
||||
<td><input type="submit" on-tap="{{ajaxSubmit}}"></td>
|
||||
</tr>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
<hr>
|
||||
<ul id="user-list">
|
||||
<template repeat="{{user in users}}">
|
||||
<li><a href="{{action}}/{{user.id}}" on-tap="{{ajaxDelete}}">delete</a>{{user.email}}</li>
|
||||
<li>
|
||||
<a href="{{action}}/{{user.id}}" on-tap="{{ajaxDelete}}">delete</a><span>{{user.email}}</span>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
</template>
|
||||
<script>
|
||||
Polymer({
|
||||
created: function() {
|
||||
this.users = this.users || [];
|
||||
},
|
||||
test: function(e, details, sender) {
|
||||
e.preventDefault();
|
||||
is: 'chromedash-userlist',
|
||||
properties: {
|
||||
action: {
|
||||
type: String
|
||||
},
|
||||
users: {
|
||||
type: Array,
|
||||
value: function() { return []; },
|
||||
notify: true
|
||||
}
|
||||
},
|
||||
addUser: function(user) {
|
||||
this.users.splice(0, 0, user);
|
||||
|
@ -44,17 +149,15 @@ Polymer({
|
|||
var formData = new FormData();
|
||||
formData.append('email', email);
|
||||
|
||||
var xhr = document.createElement('core-xhr');
|
||||
xhr.request({url: this.action, method: 'POST', body: formData, callback:
|
||||
function(response, xhr) {
|
||||
if (xhr.status == 201) {
|
||||
this.addUser(JSON.parse(response));
|
||||
this.$.form.reset();
|
||||
} else if (xhr.status == 200) {
|
||||
alert('Thanks. But that user already exists');
|
||||
}
|
||||
}.bind(this)
|
||||
});
|
||||
var xhr = document.createElement('iron-request');
|
||||
xhr.send({url: this.action, method: 'POST', body: formData}).then(function(request) {
|
||||
if (request.status == 201) {
|
||||
this.addUser(JSON.parse(response));
|
||||
this.$.form.reset();
|
||||
} else if (request.status == 200) {
|
||||
alert('Thanks. But that user already exists');
|
||||
}
|
||||
}.bind(this));
|
||||
}
|
||||
},
|
||||
ajaxDelete: function(e, details, sender) {
|
||||
|
@ -68,9 +171,9 @@ Polymer({
|
|||
var user = e.target.templateInstance.model.user;
|
||||
var idx = this.users.indexOf(user);
|
||||
|
||||
var xhr = document.createElement('core-xhr');
|
||||
xhr.request({url: sender.href, method: 'POST', callback: function(response, xhr) {
|
||||
sender.parentElement.classList.add('faded');
|
||||
var xhr = document.createElement('iron-request');
|
||||
xhr.send({url: sender.href, method: 'POST'}).then(function(request) {
|
||||
e.target.parentElement.classList.add('faded');
|
||||
|
||||
if ('ontransitionend' in window) {
|
||||
var li = sender.parentElement;
|
||||
|
@ -81,27 +184,8 @@ Polymer({
|
|||
} else {
|
||||
this.removeUser(idx);
|
||||
}
|
||||
}.bind(this)});
|
||||
|
||||
// var xhr = new XMLHttpRequest();
|
||||
// xhr.open('POST', sender.href);
|
||||
// xhr.onloadend = function(e) {
|
||||
// if (e.target.status == 200) {
|
||||
// sender.parentElement.classList.add('faded');
|
||||
|
||||
// var li = sender.parentElement;
|
||||
// if ('ontransitionend' in window) {
|
||||
// li.addEventListener('transitionend', function(e) {
|
||||
// this.users.splice(idx, 1);
|
||||
// }.bind(this));
|
||||
// li.classList.add('faded');
|
||||
// } else {
|
||||
// this.users.splice(idx, 1);
|
||||
// }
|
||||
// }
|
||||
// }.bind(this);
|
||||
// xhr.send();
|
||||
}.bind(this));
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</polymer-element>
|
||||
</polymer-element> -->
|
||||
|
|
|
@ -1,4 +1,38 @@
|
|||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
<link rel="import" href="../bower_components/iron-ajax/iron-request.html">
|
||||
|
||||
<dom-module id="delete-link">
|
||||
<template>
|
||||
<content></content>
|
||||
</template>
|
||||
</dom-module>
|
||||
<script>
|
||||
Polymer({
|
||||
is: 'delete-link',
|
||||
|
||||
extends: 'a',
|
||||
|
||||
listeners: {
|
||||
'click': 'onClick'
|
||||
},
|
||||
|
||||
onClick: function (e, details, sender) {
|
||||
e.preventDefault();
|
||||
|
||||
var msg = this.getAttribute('message') || 'Remove?';
|
||||
if (!confirm(msg)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var xhr = document.createElement('iron-request');
|
||||
xhr.send({url: e.target.href, method: 'POST'}).then(function(request) {
|
||||
this.fire('ajaxdeleted', {response: request.response, xhr: request.xhr});
|
||||
}.bind(this));
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- <link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
<link rel="import" href="../bower_components/core-ajax/core-xhr.html">
|
||||
|
||||
<polymer-element name="delete-link" extends="a" on-click="{{onClick}}">
|
||||
|
@ -14,7 +48,7 @@
|
|||
if (!confirm(msg)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var xhr = document.createElement('core-xhr');
|
||||
xhr.request({url: sender.href, method: 'POST', callback: function(response, xhr) {
|
||||
this.fire('ajaxdeleted', {response: response, xhr: xhr});
|
||||
|
@ -23,3 +57,4 @@
|
|||
});
|
||||
</script>
|
||||
</polymer-element>
|
||||
-->
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
<!-- <link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
|
||||
<polymer-element name="x-meter" attributes="value">
|
||||
<template>
|
||||
|
@ -30,3 +30,49 @@
|
|||
});
|
||||
</script>
|
||||
</polymer-element>
|
||||
-->
|
||||
<link rel="import" href="../bower_components/polymer/polymer.html">
|
||||
|
||||
<dom-module id="x-meter">
|
||||
<template>
|
||||
<link rel="stylesheet" href="../css/elements/x-meter.css">
|
||||
<div class="meter" id="meter" title="{{helpText}}">
|
||||
<div style="{{computeStyle(value)}}"><span>{{valueFormmatted}}</span>%</div>
|
||||
</div>
|
||||
</template>
|
||||
</dom-module>
|
||||
<script>
|
||||
Polymer({
|
||||
is: 'x-meter',
|
||||
properties: {
|
||||
helpText: {
|
||||
type: String,
|
||||
value: ''
|
||||
},
|
||||
value: {
|
||||
type: Number,
|
||||
value: 0,
|
||||
notify: true,
|
||||
observer: 'valueChanged'
|
||||
}
|
||||
},
|
||||
valueChanged: function () {
|
||||
var MIN_VAL = 0.0001;
|
||||
if (this.value <= MIN_VAL) {
|
||||
this.valueFormmatted = '<=0.0001';
|
||||
} else {
|
||||
this.valueFormmatted = Number(this.value).toFixed(4);
|
||||
}
|
||||
this.value = Number(this.value).toFixed(4);
|
||||
var MIN_FOR_AT_RISK = 0.03;
|
||||
if (this.value <= MIN_FOR_AT_RISK) {
|
||||
this.$.meter.classList.add('atrisk');
|
||||
this.helpText = 'This feature may be at risk for removal. Usage is <= ' + MIN_FOR_AT_RISK + '%.';
|
||||
}
|
||||
},
|
||||
computeStyle: function (value) {
|
||||
return 'width:' + value + '%';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@
|
|||
<link rel="apple-touch-icon" href="/static/img/chromium-128.png">
|
||||
<link rel="apple-touch-icon-precomposed" href="/static/img/chromium-128.png">
|
||||
|
||||
<script src="/static/bower_components/webcomponentsjs/webcomponents.min.js"></script>
|
||||
<script src="/static/bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
|
||||
<!-- <script src="/static/js/smoothscroll/dist/smoothscroll.js"></script> -->
|
||||
<!-- <script src="/static/js/bower_components/smoothscroll/smoothscroll.js"></script> -->
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче