Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(2048)

Unified Diff: appengine/swarming/elements/build/elements.html

Issue 2227803002: Mirror filters and sort preferences to url-params (Closed) Base URL: https://chromium.googlesource.com/external/github.com/luci/luci-py@use-dimensions
Patch Set: Tweak docs Created 4 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « appengine/swarming/elements/Makefile ('k') | appengine/swarming/elements/build/js/common.js » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: appengine/swarming/elements/build/elements.html
diff --git a/appengine/swarming/elements/build/elements.html b/appengine/swarming/elements/build/elements.html
index f540c748c3a2093b69088909af11bd910742effc..553de914fc68e5c3da1c13716d8efdeca67b3554 100644
--- a/appengine/swarming/elements/build/elements.html
+++ b/appengine/swarming/elements/build/elements.html
@@ -15644,7 +15644,2691 @@ You can bind to `isAuthorized` property to monitor authorization state.
},
});
</script>
-</dom-module><script>
+</dom-module>
+
+<dom-module id="iron-a11y-announcer" assetpath="/res/imp/bower_components/iron-a11y-announcer/">
+ <template>
+ <style>
+ :host {
+ display: inline-block;
+ position: fixed;
+ clip: rect(0px,0px,0px,0px);
+ }
+ </style>
+ <div aria-live$="[[mode]]">[[_text]]</div>
+ </template>
+
+ <script>
+
+ (function() {
+ 'use strict';
+
+ Polymer.IronA11yAnnouncer = Polymer({
+ is: 'iron-a11y-announcer',
+
+ properties: {
+
+ /**
+ * The value of mode is used to set the `aria-live` attribute
+ * for the element that will be announced. Valid values are: `off`,
+ * `polite` and `assertive`.
+ */
+ mode: {
+ type: String,
+ value: 'polite'
+ },
+
+ _text: {
+ type: String,
+ value: ''
+ }
+ },
+
+ created: function() {
+ if (!Polymer.IronA11yAnnouncer.instance) {
+ Polymer.IronA11yAnnouncer.instance = this;
+ }
+
+ document.body.addEventListener('iron-announce', this._onIronAnnounce.bind(this));
+ },
+
+ /**
+ * Cause a text string to be announced by screen readers.
+ *
+ * @param {string} text The text that should be announced.
+ */
+ announce: function(text) {
+ this._text = '';
+ this.async(function() {
+ this._text = text;
+ }, 100);
+ },
+
+ _onIronAnnounce: function(event) {
+ if (event.detail && event.detail.text) {
+ this.announce(event.detail.text);
+ }
+ }
+ });
+
+ Polymer.IronA11yAnnouncer.instance = null;
+
+ Polymer.IronA11yAnnouncer.requestAvailability = function() {
+ if (!Polymer.IronA11yAnnouncer.instance) {
+ Polymer.IronA11yAnnouncer.instance = document.createElement('iron-a11y-announcer');
+ }
+
+ document.body.appendChild(Polymer.IronA11yAnnouncer.instance);
+ };
+ })();
+
+ </script>
+</dom-module>
+<script>
+/**
+`Polymer.IronFitBehavior` fits an element in another element using `max-height` and `max-width`, and
+optionally centers it in the window or another element.
+
+The element will only be sized and/or positioned if it has not already been sized and/or positioned
+by CSS.
+
+CSS properties | Action
+-----------------------------|-------------------------------------------
+`position` set | Element is not centered horizontally or vertically
+`top` or `bottom` set | Element is not vertically centered
+`left` or `right` set | Element is not horizontally centered
+`max-height` set | Element respects `max-height`
+`max-width` set | Element respects `max-width`
+
+`Polymer.IronFitBehavior` can position an element into another element using
+`verticalAlign` and `horizontalAlign`. This will override the element's css position.
+
+ <div class="container">
+ <iron-fit-impl vertical-align="top" horizontal-align="auto">
+ Positioned into the container
+ </iron-fit-impl>
+ </div>
+
+Use `noOverlap` to position the element around another element without overlapping it.
+
+ <div class="container">
+ <iron-fit-impl no-overlap vertical-align="auto" horizontal-align="auto">
+ Positioned around the container
+ </iron-fit-impl>
+ </div>
+
+@demo demo/index.html
+@polymerBehavior
+*/
+
+ Polymer.IronFitBehavior = {
+
+ properties: {
+
+ /**
+ * The element that will receive a `max-height`/`width`. By default it is the same as `this`,
+ * but it can be set to a child element. This is useful, for example, for implementing a
+ * scrolling region inside the element.
+ * @type {!Element}
+ */
+ sizingTarget: {
+ type: Object,
+ value: function() {
+ return this;
+ }
+ },
+
+ /**
+ * The element to fit `this` into.
+ */
+ fitInto: {
+ type: Object,
+ value: window
+ },
+
+ /**
+ * Will position the element around the positionTarget without overlapping it.
+ */
+ noOverlap: {
+ type: Boolean
+ },
+
+ /**
+ * The element that should be used to position the element. If not set, it will
+ * default to the parent node.
+ * @type {!Element}
+ */
+ positionTarget: {
+ type: Element
+ },
+
+ /**
+ * The orientation against which to align the element horizontally
+ * relative to the `positionTarget`. Possible values are "left", "right", "auto".
+ */
+ horizontalAlign: {
+ type: String
+ },
+
+ /**
+ * The orientation against which to align the element vertically
+ * relative to the `positionTarget`. Possible values are "top", "bottom", "auto".
+ */
+ verticalAlign: {
+ type: String
+ },
+
+ /**
+ * If true, it will use `horizontalAlign` and `verticalAlign` values as preferred alignment
+ * and if there's not enough space, it will pick the values which minimize the cropping.
+ */
+ dynamicAlign: {
+ type: Boolean
+ },
+
+ /**
+ * The same as setting margin-left and margin-right css properties.
+ * @deprecated
+ */
+ horizontalOffset: {
+ type: Number,
+ value: 0,
+ notify: true
+ },
+
+ /**
+ * The same as setting margin-top and margin-bottom css properties.
+ * @deprecated
+ */
+ verticalOffset: {
+ type: Number,
+ value: 0,
+ notify: true
+ },
+
+ /**
+ * Set to true to auto-fit on attach.
+ */
+ autoFitOnAttach: {
+ type: Boolean,
+ value: false
+ },
+
+ /** @type {?Object} */
+ _fitInfo: {
+ type: Object
+ }
+ },
+
+ get _fitWidth() {
+ var fitWidth;
+ if (this.fitInto === window) {
+ fitWidth = this.fitInto.innerWidth;
+ } else {
+ fitWidth = this.fitInto.getBoundingClientRect().width;
+ }
+ return fitWidth;
+ },
+
+ get _fitHeight() {
+ var fitHeight;
+ if (this.fitInto === window) {
+ fitHeight = this.fitInto.innerHeight;
+ } else {
+ fitHeight = this.fitInto.getBoundingClientRect().height;
+ }
+ return fitHeight;
+ },
+
+ get _fitLeft() {
+ var fitLeft;
+ if (this.fitInto === window) {
+ fitLeft = 0;
+ } else {
+ fitLeft = this.fitInto.getBoundingClientRect().left;
+ }
+ return fitLeft;
+ },
+
+ get _fitTop() {
+ var fitTop;
+ if (this.fitInto === window) {
+ fitTop = 0;
+ } else {
+ fitTop = this.fitInto.getBoundingClientRect().top;
+ }
+ return fitTop;
+ },
+
+ /**
+ * The element that should be used to position the element,
+ * if no position target is configured.
+ */
+ get _defaultPositionTarget() {
+ var parent = Polymer.dom(this).parentNode;
+
+ if (parent && parent.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
+ parent = parent.host;
+ }
+
+ return parent;
+ },
+
+ /**
+ * The horizontal align value, accounting for the RTL/LTR text direction.
+ */
+ get _localeHorizontalAlign() {
+ if (this._isRTL) {
+ // In RTL, "left" becomes "right".
+ if (this.horizontalAlign === 'right') {
+ return 'left';
+ }
+ if (this.horizontalAlign === 'left') {
+ return 'right';
+ }
+ }
+ return this.horizontalAlign;
+ },
+
+ attached: function() {
+ // Memoize this to avoid expensive calculations & relayouts.
+ this._isRTL = window.getComputedStyle(this).direction == 'rtl';
+ this.positionTarget = this.positionTarget || this._defaultPositionTarget;
+ if (this.autoFitOnAttach) {
+ if (window.getComputedStyle(this).display === 'none') {
+ setTimeout(function() {
+ this.fit();
+ }.bind(this));
+ } else {
+ this.fit();
+ }
+ }
+ },
+
+ /**
+ * Positions and fits the element into the `fitInto` element.
+ */
+ fit: function() {
+ this.position();
+ this.constrain();
+ this.center();
+ },
+
+ /**
+ * Memoize information needed to position and size the target element.
+ * @suppress {deprecated}
+ */
+ _discoverInfo: function() {
+ if (this._fitInfo) {
+ return;
+ }
+ var target = window.getComputedStyle(this);
+ var sizer = window.getComputedStyle(this.sizingTarget);
+
+ this._fitInfo = {
+ inlineStyle: {
+ top: this.style.top || '',
+ left: this.style.left || '',
+ position: this.style.position || ''
+ },
+ sizerInlineStyle: {
+ maxWidth: this.sizingTarget.style.maxWidth || '',
+ maxHeight: this.sizingTarget.style.maxHeight || '',
+ boxSizing: this.sizingTarget.style.boxSizing || ''
+ },
+ positionedBy: {
+ vertically: target.top !== 'auto' ? 'top' : (target.bottom !== 'auto' ?
+ 'bottom' : null),
+ horizontally: target.left !== 'auto' ? 'left' : (target.right !== 'auto' ?
+ 'right' : null)
+ },
+ sizedBy: {
+ height: sizer.maxHeight !== 'none',
+ width: sizer.maxWidth !== 'none',
+ minWidth: parseInt(sizer.minWidth, 10) || 0,
+ minHeight: parseInt(sizer.minHeight, 10) || 0
+ },
+ margin: {
+ top: parseInt(target.marginTop, 10) || 0,
+ right: parseInt(target.marginRight, 10) || 0,
+ bottom: parseInt(target.marginBottom, 10) || 0,
+ left: parseInt(target.marginLeft, 10) || 0
+ }
+ };
+
+ // Support these properties until they are removed.
+ if (this.verticalOffset) {
+ this._fitInfo.margin.top = this._fitInfo.margin.bottom = this.verticalOffset;
+ this._fitInfo.inlineStyle.marginTop = this.style.marginTop || '';
+ this._fitInfo.inlineStyle.marginBottom = this.style.marginBottom || '';
+ this.style.marginTop = this.style.marginBottom = this.verticalOffset + 'px';
+ }
+ if (this.horizontalOffset) {
+ this._fitInfo.margin.left = this._fitInfo.margin.right = this.horizontalOffset;
+ this._fitInfo.inlineStyle.marginLeft = this.style.marginLeft || '';
+ this._fitInfo.inlineStyle.marginRight = this.style.marginRight || '';
+ this.style.marginLeft = this.style.marginRight = this.horizontalOffset + 'px';
+ }
+ },
+
+ /**
+ * Resets the target element's position and size constraints, and clear
+ * the memoized data.
+ */
+ resetFit: function() {
+ var info = this._fitInfo || {};
+ for (var property in info.sizerInlineStyle) {
+ this.sizingTarget.style[property] = info.sizerInlineStyle[property];
+ }
+ for (var property in info.inlineStyle) {
+ this.style[property] = info.inlineStyle[property];
+ }
+
+ this._fitInfo = null;
+ },
+
+ /**
+ * Equivalent to calling `resetFit()` and `fit()`. Useful to call this after
+ * the element or the `fitInto` element has been resized, or if any of the
+ * positioning properties (e.g. `horizontalAlign, verticalAlign`) is updated.
+ * It preserves the scroll position of the sizingTarget.
+ */
+ refit: function() {
+ var scrollLeft = this.sizingTarget.scrollLeft;
+ var scrollTop = this.sizingTarget.scrollTop;
+ this.resetFit();
+ this.fit();
+ this.sizingTarget.scrollLeft = scrollLeft;
+ this.sizingTarget.scrollTop = scrollTop;
+ },
+
+ /**
+ * Positions the element according to `horizontalAlign, verticalAlign`.
+ */
+ position: function() {
+ if (!this.horizontalAlign && !this.verticalAlign) {
+ // needs to be centered, and it is done after constrain.
+ return;
+ }
+ this._discoverInfo();
+
+ this.style.position = 'fixed';
+ // Need border-box for margin/padding.
+ this.sizingTarget.style.boxSizing = 'border-box';
+ // Set to 0, 0 in order to discover any offset caused by parent stacking contexts.
+ this.style.left = '0px';
+ this.style.top = '0px';
+
+ var rect = this.getBoundingClientRect();
+ var positionRect = this.__getNormalizedRect(this.positionTarget);
+ var fitRect = this.__getNormalizedRect(this.fitInto);
+
+ var margin = this._fitInfo.margin;
+
+ // Consider the margin as part of the size for position calculations.
+ var size = {
+ width: rect.width + margin.left + margin.right,
+ height: rect.height + margin.top + margin.bottom
+ };
+
+ var position = this.__getPosition(this._localeHorizontalAlign, this.verticalAlign, size, positionRect, fitRect);
+
+ var left = position.left + margin.left;
+ var top = position.top + margin.top;
+
+ // Use original size (without margin).
+ var right = Math.min(fitRect.right - margin.right, left + rect.width);
+ var bottom = Math.min(fitRect.bottom - margin.bottom, top + rect.height);
+
+ var minWidth = this._fitInfo.sizedBy.minWidth;
+ var minHeight = this._fitInfo.sizedBy.minHeight;
+ if (left < margin.left) {
+ left = margin.left;
+ if (right - left < minWidth) {
+ left = right - minWidth;
+ }
+ }
+ if (top < margin.top) {
+ top = margin.top;
+ if (bottom - top < minHeight) {
+ top = bottom - minHeight;
+ }
+ }
+
+ this.sizingTarget.style.maxWidth = (right - left) + 'px';
+ this.sizingTarget.style.maxHeight = (bottom - top) + 'px';
+
+ // Remove the offset caused by any stacking context.
+ this.style.left = (left - rect.left) + 'px';
+ this.style.top = (top - rect.top) + 'px';
+ },
+
+ /**
+ * Constrains the size of the element to `fitInto` by setting `max-height`
+ * and/or `max-width`.
+ */
+ constrain: function() {
+ if (this.horizontalAlign || this.verticalAlign) {
+ return;
+ }
+ this._discoverInfo();
+
+ var info = this._fitInfo;
+ // position at (0px, 0px) if not already positioned, so we can measure the natural size.
+ if (!info.positionedBy.vertically) {
+ this.style.position = 'fixed';
+ this.style.top = '0px';
+ }
+ if (!info.positionedBy.horizontally) {
+ this.style.position = 'fixed';
+ this.style.left = '0px';
+ }
+
+ // need border-box for margin/padding
+ this.sizingTarget.style.boxSizing = 'border-box';
+ // constrain the width and height if not already set
+ var rect = this.getBoundingClientRect();
+ if (!info.sizedBy.height) {
+ this.__sizeDimension(rect, info.positionedBy.vertically, 'top', 'bottom', 'Height');
+ }
+ if (!info.sizedBy.width) {
+ this.__sizeDimension(rect, info.positionedBy.horizontally, 'left', 'right', 'Width');
+ }
+ },
+
+ /**
+ * @protected
+ * @deprecated
+ */
+ _sizeDimension: function(rect, positionedBy, start, end, extent) {
+ this.__sizeDimension(rect, positionedBy, start, end, extent);
+ },
+
+ /**
+ * @private
+ */
+ __sizeDimension: function(rect, positionedBy, start, end, extent) {
+ var info = this._fitInfo;
+ var fitRect = this.__getNormalizedRect(this.fitInto);
+ var max = extent === 'Width' ? fitRect.width : fitRect.height;
+ var flip = (positionedBy === end);
+ var offset = flip ? max - rect[end] : rect[start];
+ var margin = info.margin[flip ? start : end];
+ var offsetExtent = 'offset' + extent;
+ var sizingOffset = this[offsetExtent] - this.sizingTarget[offsetExtent];
+ this.sizingTarget.style['max' + extent] = (max - margin - offset - sizingOffset) + 'px';
+ },
+
+ /**
+ * Centers horizontally and vertically if not already positioned. This also sets
+ * `position:fixed`.
+ */
+ center: function() {
+ if (this.horizontalAlign || this.verticalAlign) {
+ return;
+ }
+ this._discoverInfo();
+
+ var positionedBy = this._fitInfo.positionedBy;
+ if (positionedBy.vertically && positionedBy.horizontally) {
+ // Already positioned.
+ return;
+ }
+ // Need position:fixed to center
+ this.style.position = 'fixed';
+ // Take into account the offset caused by parents that create stacking
+ // contexts (e.g. with transform: translate3d). Translate to 0,0 and
+ // measure the bounding rect.
+ if (!positionedBy.vertically) {
+ this.style.top = '0px';
+ }
+ if (!positionedBy.horizontally) {
+ this.style.left = '0px';
+ }
+ // It will take in consideration margins and transforms
+ var rect = this.getBoundingClientRect();
+ var fitRect = this.__getNormalizedRect(this.fitInto);
+ if (!positionedBy.vertically) {
+ var top = fitRect.top - rect.top + (fitRect.height - rect.height) / 2;
+ this.style.top = top + 'px';
+ }
+ if (!positionedBy.horizontally) {
+ var left = fitRect.left - rect.left + (fitRect.width - rect.width) / 2;
+ this.style.left = left + 'px';
+ }
+ },
+
+ __getNormalizedRect: function(target) {
+ if (target === document.documentElement || target === window) {
+ return {
+ top: 0,
+ left: 0,
+ width: window.innerWidth,
+ height: window.innerHeight,
+ right: window.innerWidth,
+ bottom: window.innerHeight
+ };
+ }
+ return target.getBoundingClientRect();
+ },
+
+ __getCroppedArea: function(position, size, fitRect) {
+ var verticalCrop = Math.min(0, position.top) + Math.min(0, fitRect.bottom - (position.top + size.height));
+ var horizontalCrop = Math.min(0, position.left) + Math.min(0, fitRect.right - (position.left + size.width));
+ return Math.abs(verticalCrop) * size.width + Math.abs(horizontalCrop) * size.height;
+ },
+
+
+ __getPosition: function(hAlign, vAlign, size, positionRect, fitRect) {
+ // All the possible configurations.
+ // Ordered as top-left, top-right, bottom-left, bottom-right.
+ var positions = [{
+ verticalAlign: 'top',
+ horizontalAlign: 'left',
+ top: positionRect.top,
+ left: positionRect.left
+ }, {
+ verticalAlign: 'top',
+ horizontalAlign: 'right',
+ top: positionRect.top,
+ left: positionRect.right - size.width
+ }, {
+ verticalAlign: 'bottom',
+ horizontalAlign: 'left',
+ top: positionRect.bottom - size.height,
+ left: positionRect.left
+ }, {
+ verticalAlign: 'bottom',
+ horizontalAlign: 'right',
+ top: positionRect.bottom - size.height,
+ left: positionRect.right - size.width
+ }];
+
+ if (this.noOverlap) {
+ // Duplicate.
+ for (var i = 0, l = positions.length; i < l; i++) {
+ var copy = {};
+ for (var key in positions[i]) {
+ copy[key] = positions[i][key];
+ }
+ positions.push(copy);
+ }
+ // Horizontal overlap only.
+ positions[0].top = positions[1].top += positionRect.height;
+ positions[2].top = positions[3].top -= positionRect.height;
+ // Vertical overlap only.
+ positions[4].left = positions[6].left += positionRect.width;
+ positions[5].left = positions[7].left -= positionRect.width;
+ }
+
+ // Consider auto as null for coding convenience.
+ vAlign = vAlign === 'auto' ? null : vAlign;
+ hAlign = hAlign === 'auto' ? null : hAlign;
+
+ var position;
+ for (var i = 0; i < positions.length; i++) {
+ var pos = positions[i];
+
+ // If both vAlign and hAlign are defined, return exact match.
+ // For dynamicAlign and noOverlap we'll have more than one candidate, so
+ // we'll have to check the croppedArea to make the best choice.
+ if (!this.dynamicAlign && !this.noOverlap &&
+ pos.verticalAlign === vAlign && pos.horizontalAlign === hAlign) {
+ position = pos;
+ break;
+ }
+
+ // Align is ok if alignment preferences are respected. If no preferences,
+ // it is considered ok.
+ var alignOk = (!vAlign || pos.verticalAlign === vAlign) &&
+ (!hAlign || pos.horizontalAlign === hAlign);
+
+ // Filter out elements that don't match the alignment (if defined).
+ // With dynamicAlign, we need to consider all the positions to find the
+ // one that minimizes the cropped area.
+ if (!this.dynamicAlign && !alignOk) {
+ continue;
+ }
+
+ position = position || pos;
+ pos.croppedArea = this.__getCroppedArea(pos, size, fitRect);
+ var diff = pos.croppedArea - position.croppedArea;
+ // Check which crops less. If it crops equally, check if align is ok.
+ if (diff < 0 || (diff === 0 && alignOk)) {
+ position = pos;
+ }
+ // If not cropped and respects the align requirements, keep it.
+ // This allows to prefer positions overlapping horizontally over the
+ // ones overlapping vertically.
+ if (position.croppedArea === 0 && alignOk) {
+ break;
+ }
+ }
+
+ return position;
+ }
+
+ };
+</script>
+<script>
+ (function() {
+ 'use strict';
+
+ /**
+ * Chrome uses an older version of DOM Level 3 Keyboard Events
+ *
+ * Most keys are labeled as text, but some are Unicode codepoints.
+ * Values taken from: http://www.w3.org/TR/2007/WD-DOM-Level-3-Events-20071221/keyset.html#KeySet-Set
+ */
+ var KEY_IDENTIFIER = {
+ 'U+0008': 'backspace',
+ 'U+0009': 'tab',
+ 'U+001B': 'esc',
+ 'U+0020': 'space',
+ 'U+007F': 'del'
+ };
+
+ /**
+ * Special table for KeyboardEvent.keyCode.
+ * KeyboardEvent.keyIdentifier is better, and KeyBoardEvent.key is even better
+ * than that.
+ *
+ * Values from: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent.keyCode#Value_of_keyCode
+ */
+ var KEY_CODE = {
+ 8: 'backspace',
+ 9: 'tab',
+ 13: 'enter',
+ 27: 'esc',
+ 33: 'pageup',
+ 34: 'pagedown',
+ 35: 'end',
+ 36: 'home',
+ 32: 'space',
+ 37: 'left',
+ 38: 'up',
+ 39: 'right',
+ 40: 'down',
+ 46: 'del',
+ 106: '*'
+ };
+
+ /**
+ * MODIFIER_KEYS maps the short name for modifier keys used in a key
+ * combo string to the property name that references those same keys
+ * in a KeyboardEvent instance.
+ */
+ var MODIFIER_KEYS = {
+ 'shift': 'shiftKey',
+ 'ctrl': 'ctrlKey',
+ 'alt': 'altKey',
+ 'meta': 'metaKey'
+ };
+
+ /**
+ * KeyboardEvent.key is mostly represented by printable character made by
+ * the keyboard, with unprintable keys labeled nicely.
+ *
+ * However, on OS X, Alt+char can make a Unicode character that follows an
+ * Apple-specific mapping. In this case, we fall back to .keyCode.
+ */
+ var KEY_CHAR = /[a-z0-9*]/;
+
+ /**
+ * Matches a keyIdentifier string.
+ */
+ var IDENT_CHAR = /U\+/;
+
+ /**
+ * Matches arrow keys in Gecko 27.0+
+ */
+ var ARROW_KEY = /^arrow/;
+
+ /**
+ * Matches space keys everywhere (notably including IE10's exceptional name
+ * `spacebar`).
+ */
+ var SPACE_KEY = /^space(bar)?/;
+
+ /**
+ * Matches ESC key.
+ *
+ * Value from: http://w3c.github.io/uievents-key/#key-Escape
+ */
+ var ESC_KEY = /^escape$/;
+
+ /**
+ * Transforms the key.
+ * @param {string} key The KeyBoardEvent.key
+ * @param {Boolean} [noSpecialChars] Limits the transformation to
+ * alpha-numeric characters.
+ */
+ function transformKey(key, noSpecialChars) {
+ var validKey = '';
+ if (key) {
+ var lKey = key.toLowerCase();
+ if (lKey === ' ' || SPACE_KEY.test(lKey)) {
+ validKey = 'space';
+ } else if (ESC_KEY.test(lKey)) {
+ validKey = 'esc';
+ } else if (lKey.length == 1) {
+ if (!noSpecialChars || KEY_CHAR.test(lKey)) {
+ validKey = lKey;
+ }
+ } else if (ARROW_KEY.test(lKey)) {
+ validKey = lKey.replace('arrow', '');
+ } else if (lKey == 'multiply') {
+ // numpad '*' can map to Multiply on IE/Windows
+ validKey = '*';
+ } else {
+ validKey = lKey;
+ }
+ }
+ return validKey;
+ }
+
+ function transformKeyIdentifier(keyIdent) {
+ var validKey = '';
+ if (keyIdent) {
+ if (keyIdent in KEY_IDENTIFIER) {
+ validKey = KEY_IDENTIFIER[keyIdent];
+ } else if (IDENT_CHAR.test(keyIdent)) {
+ keyIdent = parseInt(keyIdent.replace('U+', '0x'), 16);
+ validKey = String.fromCharCode(keyIdent).toLowerCase();
+ } else {
+ validKey = keyIdent.toLowerCase();
+ }
+ }
+ return validKey;
+ }
+
+ function transformKeyCode(keyCode) {
+ var validKey = '';
+ if (Number(keyCode)) {
+ if (keyCode >= 65 && keyCode <= 90) {
+ // ascii a-z
+ // lowercase is 32 offset from uppercase
+ validKey = String.fromCharCode(32 + keyCode);
+ } else if (keyCode >= 112 && keyCode <= 123) {
+ // function keys f1-f12
+ validKey = 'f' + (keyCode - 112);
+ } else if (keyCode >= 48 && keyCode <= 57) {
+ // top 0-9 keys
+ validKey = String(keyCode - 48);
+ } else if (keyCode >= 96 && keyCode <= 105) {
+ // num pad 0-9
+ validKey = String(keyCode - 96);
+ } else {
+ validKey = KEY_CODE[keyCode];
+ }
+ }
+ return validKey;
+ }
+
+ /**
+ * Calculates the normalized key for a KeyboardEvent.
+ * @param {KeyboardEvent} keyEvent
+ * @param {Boolean} [noSpecialChars] Set to true to limit keyEvent.key
+ * transformation to alpha-numeric chars. This is useful with key
+ * combinations like shift + 2, which on FF for MacOS produces
+ * keyEvent.key = @
+ * To get 2 returned, set noSpecialChars = true
+ * To get @ returned, set noSpecialChars = false
+ */
+ function normalizedKeyForEvent(keyEvent, noSpecialChars) {
+ // Fall back from .key, to .keyIdentifier, to .keyCode, and then to
+ // .detail.key to support artificial keyboard events.
+ return transformKey(keyEvent.key, noSpecialChars) ||
+ transformKeyIdentifier(keyEvent.keyIdentifier) ||
+ transformKeyCode(keyEvent.keyCode) ||
+ transformKey(keyEvent.detail ? keyEvent.detail.key : keyEvent.detail, noSpecialChars) || '';
+ }
+
+ function keyComboMatchesEvent(keyCombo, event) {
+ // For combos with modifiers we support only alpha-numeric keys
+ var keyEvent = normalizedKeyForEvent(event, keyCombo.hasModifiers);
+ return keyEvent === keyCombo.key &&
+ (!keyCombo.hasModifiers || (
+ !!event.shiftKey === !!keyCombo.shiftKey &&
+ !!event.ctrlKey === !!keyCombo.ctrlKey &&
+ !!event.altKey === !!keyCombo.altKey &&
+ !!event.metaKey === !!keyCombo.metaKey)
+ );
+ }
+
+ function parseKeyComboString(keyComboString) {
+ if (keyComboString.length === 1) {
+ return {
+ combo: keyComboString,
+ key: keyComboString,
+ event: 'keydown'
+ };
+ }
+ return keyComboString.split('+').reduce(function(parsedKeyCombo, keyComboPart) {
+ var eventParts = keyComboPart.split(':');
+ var keyName = eventParts[0];
+ var event = eventParts[1];
+
+ if (keyName in MODIFIER_KEYS) {
+ parsedKeyCombo[MODIFIER_KEYS[keyName]] = true;
+ parsedKeyCombo.hasModifiers = true;
+ } else {
+ parsedKeyCombo.key = keyName;
+ parsedKeyCombo.event = event || 'keydown';
+ }
+
+ return parsedKeyCombo;
+ }, {
+ combo: keyComboString.split(':').shift()
+ });
+ }
+
+ function parseEventString(eventString) {
+ return eventString.trim().split(' ').map(function(keyComboString) {
+ return parseKeyComboString(keyComboString);
+ });
+ }
+
+ /**
+ * `Polymer.IronA11yKeysBehavior` provides a normalized interface for processing
+ * keyboard commands that pertain to [WAI-ARIA best practices](http://www.w3.org/TR/wai-aria-practices/#kbd_general_binding).
+ * The element takes care of browser differences with respect to Keyboard events
+ * and uses an expressive syntax to filter key presses.
+ *
+ * Use the `keyBindings` prototype property to express what combination of keys
+ * will trigger the callback. A key binding has the format
+ * `"KEY+MODIFIER:EVENT": "callback"` (`"KEY": "callback"` or
+ * `"KEY:EVENT": "callback"` are valid as well). Some examples:
+ *
+ * keyBindings: {
+ * 'space': '_onKeydown', // same as 'space:keydown'
+ * 'shift+tab': '_onKeydown',
+ * 'enter:keypress': '_onKeypress',
+ * 'esc:keyup': '_onKeyup'
+ * }
+ *
+ * The callback will receive with an event containing the following information in `event.detail`:
+ *
+ * _onKeydown: function(event) {
+ * console.log(event.detail.combo); // KEY+MODIFIER, e.g. "shift+tab"
+ * console.log(event.detail.key); // KEY only, e.g. "tab"
+ * console.log(event.detail.event); // EVENT, e.g. "keydown"
+ * console.log(event.detail.keyboardEvent); // the original KeyboardEvent
+ * }
+ *
+ * Use the `keyEventTarget` attribute to set up event handlers on a specific
+ * node.
+ *
+ * See the [demo source code](https://github.com/PolymerElements/iron-a11y-keys-behavior/blob/master/demo/x-key-aware.html)
+ * for an example.
+ *
+ * @demo demo/index.html
+ * @polymerBehavior
+ */
+ Polymer.IronA11yKeysBehavior = {
+ properties: {
+ /**
+ * The EventTarget that will be firing relevant KeyboardEvents. Set it to
+ * `null` to disable the listeners.
+ * @type {?EventTarget}
+ */
+ keyEventTarget: {
+ type: Object,
+ value: function() {
+ return this;
+ }
+ },
+
+ /**
+ * If true, this property will cause the implementing element to
+ * automatically stop propagation on any handled KeyboardEvents.
+ */
+ stopKeyboardEventPropagation: {
+ type: Boolean,
+ value: false
+ },
+
+ _boundKeyHandlers: {
+ type: Array,
+ value: function() {
+ return [];
+ }
+ },
+
+ // We use this due to a limitation in IE10 where instances will have
+ // own properties of everything on the "prototype".
+ _imperativeKeyBindings: {
+ type: Object,
+ value: function() {
+ return {};
+ }
+ }
+ },
+
+ observers: [
+ '_resetKeyEventListeners(keyEventTarget, _boundKeyHandlers)'
+ ],
+
+
+ /**
+ * To be used to express what combination of keys will trigger the relative
+ * callback. e.g. `keyBindings: { 'esc': '_onEscPressed'}`
+ * @type {Object}
+ */
+ keyBindings: {},
+
+ registered: function() {
+ this._prepKeyBindings();
+ },
+
+ attached: function() {
+ this._listenKeyEventListeners();
+ },
+
+ detached: function() {
+ this._unlistenKeyEventListeners();
+ },
+
+ /**
+ * Can be used to imperatively add a key binding to the implementing
+ * element. This is the imperative equivalent of declaring a keybinding
+ * in the `keyBindings` prototype property.
+ */
+ addOwnKeyBinding: function(eventString, handlerName) {
+ this._imperativeKeyBindings[eventString] = handlerName;
+ this._prepKeyBindings();
+ this._resetKeyEventListeners();
+ },
+
+ /**
+ * When called, will remove all imperatively-added key bindings.
+ */
+ removeOwnKeyBindings: function() {
+ this._imperativeKeyBindings = {};
+ this._prepKeyBindings();
+ this._resetKeyEventListeners();
+ },
+
+ /**
+ * Returns true if a keyboard event matches `eventString`.
+ *
+ * @param {KeyboardEvent} event
+ * @param {string} eventString
+ * @return {boolean}
+ */
+ keyboardEventMatchesKeys: function(event, eventString) {
+ var keyCombos = parseEventString(eventString);
+ for (var i = 0; i < keyCombos.length; ++i) {
+ if (keyComboMatchesEvent(keyCombos[i], event)) {
+ return true;
+ }
+ }
+ return false;
+ },
+
+ _collectKeyBindings: function() {
+ var keyBindings = this.behaviors.map(function(behavior) {
+ return behavior.keyBindings;
+ });
+
+ if (keyBindings.indexOf(this.keyBindings) === -1) {
+ keyBindings.push(this.keyBindings);
+ }
+
+ return keyBindings;
+ },
+
+ _prepKeyBindings: function() {
+ this._keyBindings = {};
+
+ this._collectKeyBindings().forEach(function(keyBindings) {
+ for (var eventString in keyBindings) {
+ this._addKeyBinding(eventString, keyBindings[eventString]);
+ }
+ }, this);
+
+ for (var eventString in this._imperativeKeyBindings) {
+ this._addKeyBinding(eventString, this._imperativeKeyBindings[eventString]);
+ }
+
+ // Give precedence to combos with modifiers to be checked first.
+ for (var eventName in this._keyBindings) {
+ this._keyBindings[eventName].sort(function (kb1, kb2) {
+ var b1 = kb1[0].hasModifiers;
+ var b2 = kb2[0].hasModifiers;
+ return (b1 === b2) ? 0 : b1 ? -1 : 1;
+ })
+ }
+ },
+
+ _addKeyBinding: function(eventString, handlerName) {
+ parseEventString(eventString).forEach(function(keyCombo) {
+ this._keyBindings[keyCombo.event] =
+ this._keyBindings[keyCombo.event] || [];
+
+ this._keyBindings[keyCombo.event].push([
+ keyCombo,
+ handlerName
+ ]);
+ }, this);
+ },
+
+ _resetKeyEventListeners: function() {
+ this._unlistenKeyEventListeners();
+
+ if (this.isAttached) {
+ this._listenKeyEventListeners();
+ }
+ },
+
+ _listenKeyEventListeners: function() {
+ if (!this.keyEventTarget) {
+ return;
+ }
+ Object.keys(this._keyBindings).forEach(function(eventName) {
+ var keyBindings = this._keyBindings[eventName];
+ var boundKeyHandler = this._onKeyBindingEvent.bind(this, keyBindings);
+
+ this._boundKeyHandlers.push([this.keyEventTarget, eventName, boundKeyHandler]);
+
+ this.keyEventTarget.addEventListener(eventName, boundKeyHandler);
+ }, this);
+ },
+
+ _unlistenKeyEventListeners: function() {
+ var keyHandlerTuple;
+ var keyEventTarget;
+ var eventName;
+ var boundKeyHandler;
+
+ while (this._boundKeyHandlers.length) {
+ // My kingdom for block-scope binding and destructuring assignment..
+ keyHandlerTuple = this._boundKeyHandlers.pop();
+ keyEventTarget = keyHandlerTuple[0];
+ eventName = keyHandlerTuple[1];
+ boundKeyHandler = keyHandlerTuple[2];
+
+ keyEventTarget.removeEventListener(eventName, boundKeyHandler);
+ }
+ },
+
+ _onKeyBindingEvent: function(keyBindings, event) {
+ if (this.stopKeyboardEventPropagation) {
+ event.stopPropagation();
+ }
+
+ // if event has been already prevented, don't do anything
+ if (event.defaultPrevented) {
+ return;
+ }
+
+ for (var i = 0; i < keyBindings.length; i++) {
+ var keyCombo = keyBindings[i][0];
+ var handlerName = keyBindings[i][1];
+ if (keyComboMatchesEvent(keyCombo, event)) {
+ this._triggerKeyHandler(keyCombo, handlerName, event);
+ // exit the loop if eventDefault was prevented
+ if (event.defaultPrevented) {
+ return;
+ }
+ }
+ }
+ },
+
+ _triggerKeyHandler: function(keyCombo, handlerName, keyboardEvent) {
+ var detail = Object.create(keyCombo);
+ detail.keyboardEvent = keyboardEvent;
+ var event = new CustomEvent(keyCombo.event, {
+ detail: detail,
+ cancelable: true
+ });
+ this[handlerName].call(this, event);
+ if (event.defaultPrevented) {
+ keyboardEvent.preventDefault();
+ }
+ }
+ };
+ })();
+</script>
+
+
+<dom-module id="iron-overlay-backdrop" assetpath="/res/imp/bower_components/iron-overlay-behavior/">
+
+ <template>
+ <style>
+ :host {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-color: var(--iron-overlay-backdrop-background-color, #000);
+ opacity: 0;
+ transition: opacity 0.2s;
+ pointer-events: none;
+ @apply(--iron-overlay-backdrop);
+ }
+
+ :host(.opened) {
+ opacity: var(--iron-overlay-backdrop-opacity, 0.6);
+ pointer-events: auto;
+ @apply(--iron-overlay-backdrop-opened);
+ }
+ </style>
+
+ <content></content>
+ </template>
+
+</dom-module>
+
+<script>
+(function() {
+'use strict';
+
+ Polymer({
+
+ is: 'iron-overlay-backdrop',
+
+ properties: {
+
+ /**
+ * Returns true if the backdrop is opened.
+ */
+ opened: {
+ reflectToAttribute: true,
+ type: Boolean,
+ value: false,
+ observer: '_openedChanged'
+ }
+
+ },
+
+ listeners: {
+ 'transitionend': '_onTransitionend'
+ },
+
+ created: function() {
+ // Used to cancel previous requestAnimationFrame calls when opened changes.
+ this.__openedRaf = null;
+ },
+
+ attached: function() {
+ this.opened && this._openedChanged(this.opened);
+ },
+
+ /**
+ * Appends the backdrop to document body if needed.
+ */
+ prepare: function() {
+ if (this.opened && !this.parentNode) {
+ Polymer.dom(document.body).appendChild(this);
+ }
+ },
+
+ /**
+ * Shows the backdrop.
+ */
+ open: function() {
+ this.opened = true;
+ },
+
+ /**
+ * Hides the backdrop.
+ */
+ close: function() {
+ this.opened = false;
+ },
+
+ /**
+ * Removes the backdrop from document body if needed.
+ */
+ complete: function() {
+ if (!this.opened && this.parentNode === document.body) {
+ Polymer.dom(this.parentNode).removeChild(this);
+ }
+ },
+
+ _onTransitionend: function(event) {
+ if (event && event.target === this) {
+ this.complete();
+ }
+ },
+
+ /**
+ * @param {boolean} opened
+ * @private
+ */
+ _openedChanged: function(opened) {
+ if (opened) {
+ // Auto-attach.
+ this.prepare();
+ } else {
+ // Animation might be disabled via the mixin or opacity custom property.
+ // If it is disabled in other ways, it's up to the user to call complete.
+ var cs = window.getComputedStyle(this);
+ if (cs.transitionDuration === '0s' || cs.opacity == 0) {
+ this.complete();
+ }
+ }
+
+ if (!this.isAttached) {
+ return;
+ }
+
+ // Always cancel previous requestAnimationFrame.
+ if (this.__openedRaf) {
+ window.cancelAnimationFrame(this.__openedRaf);
+ this.__openedRaf = null;
+ }
+ // Force relayout to ensure proper transitions.
+ this.scrollTop = this.scrollTop;
+ this.__openedRaf = window.requestAnimationFrame(function() {
+ this.__openedRaf = null;
+ this.toggleClass('opened', this.opened);
+ }.bind(this));
+ }
+ });
+
+})();
+
+</script>
+<script>
+
+ /**
+ * @struct
+ * @constructor
+ * @private
+ */
+ Polymer.IronOverlayManagerClass = function() {
+ /**
+ * Used to keep track of the opened overlays.
+ * @private {Array<Element>}
+ */
+ this._overlays = [];
+
+ /**
+ * iframes have a default z-index of 100,
+ * so this default should be at least that.
+ * @private {number}
+ */
+ this._minimumZ = 101;
+
+ /**
+ * Memoized backdrop element.
+ * @private {Element|null}
+ */
+ this._backdropElement = null;
+
+ // Enable document-wide tap recognizer.
+ Polymer.Gestures.add(document, 'tap', null);
+ // Need to have useCapture=true, Polymer.Gestures doesn't offer that.
+ document.addEventListener('tap', this._onCaptureClick.bind(this), true);
+ document.addEventListener('focus', this._onCaptureFocus.bind(this), true);
+ document.addEventListener('keydown', this._onCaptureKeyDown.bind(this), true);
+ };
+
+ Polymer.IronOverlayManagerClass.prototype = {
+
+ constructor: Polymer.IronOverlayManagerClass,
+
+ /**
+ * The shared backdrop element.
+ * @type {!Element} backdropElement
+ */
+ get backdropElement() {
+ if (!this._backdropElement) {
+ this._backdropElement = document.createElement('iron-overlay-backdrop');
+ }
+ return this._backdropElement;
+ },
+
+ /**
+ * The deepest active element.
+ * @type {!Element} activeElement the active element
+ */
+ get deepActiveElement() {
+ // document.activeElement can be null
+ // https://developer.mozilla.org/en-US/docs/Web/API/Document/activeElement
+ // In case of null, default it to document.body.
+ var active = document.activeElement || document.body;
+ while (active.root && Polymer.dom(active.root).activeElement) {
+ active = Polymer.dom(active.root).activeElement;
+ }
+ return active;
+ },
+
+ /**
+ * Brings the overlay at the specified index to the front.
+ * @param {number} i
+ * @private
+ */
+ _bringOverlayAtIndexToFront: function(i) {
+ var overlay = this._overlays[i];
+ if (!overlay) {
+ return;
+ }
+ var lastI = this._overlays.length - 1;
+ var currentOverlay = this._overlays[lastI];
+ // Ensure always-on-top overlay stays on top.
+ if (currentOverlay && this._shouldBeBehindOverlay(overlay, currentOverlay)) {
+ lastI--;
+ }
+ // If already the top element, return.
+ if (i >= lastI) {
+ return;
+ }
+ // Update z-index to be on top.
+ var minimumZ = Math.max(this.currentOverlayZ(), this._minimumZ);
+ if (this._getZ(overlay) <= minimumZ) {
+ this._applyOverlayZ(overlay, minimumZ);
+ }
+
+ // Shift other overlays behind the new on top.
+ while (i < lastI) {
+ this._overlays[i] = this._overlays[i + 1];
+ i++;
+ }
+ this._overlays[lastI] = overlay;
+ },
+
+ /**
+ * Adds the overlay and updates its z-index if it's opened, or removes it if it's closed.
+ * Also updates the backdrop z-index.
+ * @param {!Element} overlay
+ */
+ addOrRemoveOverlay: function(overlay) {
+ if (overlay.opened) {
+ this.addOverlay(overlay);
+ } else {
+ this.removeOverlay(overlay);
+ }
+ },
+
+ /**
+ * Tracks overlays for z-index and focus management.
+ * Ensures the last added overlay with always-on-top remains on top.
+ * @param {!Element} overlay
+ */
+ addOverlay: function(overlay) {
+ var i = this._overlays.indexOf(overlay);
+ if (i >= 0) {
+ this._bringOverlayAtIndexToFront(i);
+ this.trackBackdrop();
+ return;
+ }
+ var insertionIndex = this._overlays.length;
+ var currentOverlay = this._overlays[insertionIndex - 1];
+ var minimumZ = Math.max(this._getZ(currentOverlay), this._minimumZ);
+ var newZ = this._getZ(overlay);
+
+ // Ensure always-on-top overlay stays on top.
+ if (currentOverlay && this._shouldBeBehindOverlay(overlay, currentOverlay)) {
+ // This bumps the z-index of +2.
+ this._applyOverlayZ(currentOverlay, minimumZ);
+ insertionIndex--;
+ // Update minimumZ to match previous overlay's z-index.
+ var previousOverlay = this._overlays[insertionIndex - 1];
+ minimumZ = Math.max(this._getZ(previousOverlay), this._minimumZ);
+ }
+
+ // Update z-index and insert overlay.
+ if (newZ <= minimumZ) {
+ this._applyOverlayZ(overlay, minimumZ);
+ }
+ this._overlays.splice(insertionIndex, 0, overlay);
+
+ // Get focused node.
+ var element = this.deepActiveElement;
+ overlay.restoreFocusNode = this._overlayParent(element) ? null : element;
+ this.trackBackdrop();
+ },
+
+ /**
+ * @param {!Element} overlay
+ */
+ removeOverlay: function(overlay) {
+ var i = this._overlays.indexOf(overlay);
+ if (i === -1) {
+ return;
+ }
+ this._overlays.splice(i, 1);
+
+ var node = overlay.restoreFocusOnClose ? overlay.restoreFocusNode : null;
+ overlay.restoreFocusNode = null;
+ // Focus back only if still contained in document.body
+ if (node && Polymer.dom(document.body).deepContains(node)) {
+ node.focus();
+ }
+ this.trackBackdrop();
+ },
+
+ /**
+ * Returns the current overlay.
+ * @return {Element|undefined}
+ */
+ currentOverlay: function() {
+ var i = this._overlays.length - 1;
+ return this._overlays[i];
+ },
+
+ /**
+ * Returns the current overlay z-index.
+ * @return {number}
+ */
+ currentOverlayZ: function() {
+ return this._getZ(this.currentOverlay());
+ },
+
+ /**
+ * Ensures that the minimum z-index of new overlays is at least `minimumZ`.
+ * This does not effect the z-index of any existing overlays.
+ * @param {number} minimumZ
+ */
+ ensureMinimumZ: function(minimumZ) {
+ this._minimumZ = Math.max(this._minimumZ, minimumZ);
+ },
+
+ focusOverlay: function() {
+ var current = /** @type {?} */ (this.currentOverlay());
+ // We have to be careful to focus the next overlay _after_ any current
+ // transitions are complete (due to the state being toggled prior to the
+ // transition). Otherwise, we risk infinite recursion when a transitioning
+ // (closed) overlay becomes the current overlay.
+ //
+ // NOTE: We make the assumption that any overlay that completes a transition
+ // will call into focusOverlay to kick the process back off. Currently:
+ // transitionend -> _applyFocus -> focusOverlay.
+ if (current && !current.transitioning) {
+ current._applyFocus();
+ }
+ },
+
+ /**
+ * Updates the backdrop z-index.
+ */
+ trackBackdrop: function() {
+ var overlay = this._overlayWithBackdrop();
+ // Avoid creating the backdrop if there is no overlay with backdrop.
+ if (!overlay && !this._backdropElement) {
+ return;
+ }
+ this.backdropElement.style.zIndex = this._getZ(overlay) - 1;
+ this.backdropElement.opened = !!overlay;
+ },
+
+ /**
+ * @return {Array<Element>}
+ */
+ getBackdrops: function() {
+ var backdrops = [];
+ for (var i = 0; i < this._overlays.length; i++) {
+ if (this._overlays[i].withBackdrop) {
+ backdrops.push(this._overlays[i]);
+ }
+ }
+ return backdrops;
+ },
+
+ /**
+ * Returns the z-index for the backdrop.
+ * @return {number}
+ */
+ backdropZ: function() {
+ return this._getZ(this._overlayWithBackdrop()) - 1;
+ },
+
+ /**
+ * Returns the first opened overlay that has a backdrop.
+ * @return {Element|undefined}
+ * @private
+ */
+ _overlayWithBackdrop: function() {
+ for (var i = 0; i < this._overlays.length; i++) {
+ if (this._overlays[i].withBackdrop) {
+ return this._overlays[i];
+ }
+ }
+ },
+
+ /**
+ * Calculates the minimum z-index for the overlay.
+ * @param {Element=} overlay
+ * @private
+ */
+ _getZ: function(overlay) {
+ var z = this._minimumZ;
+ if (overlay) {
+ var z1 = Number(overlay.style.zIndex || window.getComputedStyle(overlay).zIndex);
+ // Check if is a number
+ // Number.isNaN not supported in IE 10+
+ if (z1 === z1) {
+ z = z1;
+ }
+ }
+ return z;
+ },
+
+ /**
+ * @param {!Element} element
+ * @param {number|string} z
+ * @private
+ */
+ _setZ: function(element, z) {
+ element.style.zIndex = z;
+ },
+
+ /**
+ * @param {!Element} overlay
+ * @param {number} aboveZ
+ * @private
+ */
+ _applyOverlayZ: function(overlay, aboveZ) {
+ this._setZ(overlay, aboveZ + 2);
+ },
+
+ /**
+ * Returns the overlay containing the provided node. If the node is an overlay,
+ * it returns the node.
+ * @param {Element=} node
+ * @return {Element|undefined}
+ * @private
+ */
+ _overlayParent: function(node) {
+ while (node && node !== document.body) {
+ // Check if it is an overlay.
+ if (node._manager === this) {
+ return node;
+ }
+ // Use logical parentNode, or native ShadowRoot host.
+ node = Polymer.dom(node).parentNode || node.host;
+ }
+ },
+
+ /**
+ * Returns the deepest overlay in the path.
+ * @param {Array<Element>=} path
+ * @return {Element|undefined}
+ * @suppress {missingProperties}
+ * @private
+ */
+ _overlayInPath: function(path) {
+ path = path || [];
+ for (var i = 0; i < path.length; i++) {
+ if (path[i]._manager === this) {
+ return path[i];
+ }
+ }
+ },
+
+ /**
+ * Ensures the click event is delegated to the right overlay.
+ * @param {!Event} event
+ * @private
+ */
+ _onCaptureClick: function(event) {
+ var overlay = /** @type {?} */ (this.currentOverlay());
+ // Check if clicked outside of top overlay.
+ if (overlay && this._overlayInPath(Polymer.dom(event).path) !== overlay) {
+ overlay._onCaptureClick(event);
+ }
+ },
+
+ /**
+ * Ensures the focus event is delegated to the right overlay.
+ * @param {!Event} event
+ * @private
+ */
+ _onCaptureFocus: function(event) {
+ var overlay = /** @type {?} */ (this.currentOverlay());
+ if (overlay) {
+ overlay._onCaptureFocus(event);
+ }
+ },
+
+ /**
+ * Ensures TAB and ESC keyboard events are delegated to the right overlay.
+ * @param {!Event} event
+ * @private
+ */
+ _onCaptureKeyDown: function(event) {
+ var overlay = /** @type {?} */ (this.currentOverlay());
+ if (overlay) {
+ if (Polymer.IronA11yKeysBehavior.keyboardEventMatchesKeys(event, 'esc')) {
+ overlay._onCaptureEsc(event);
+ } else if (Polymer.IronA11yKeysBehavior.keyboardEventMatchesKeys(event, 'tab')) {
+ overlay._onCaptureTab(event);
+ }
+ }
+ },
+
+ /**
+ * Returns if the overlay1 should be behind overlay2.
+ * @param {!Element} overlay1
+ * @param {!Element} overlay2
+ * @return {boolean}
+ * @suppress {missingProperties}
+ * @private
+ */
+ _shouldBeBehindOverlay: function(overlay1, overlay2) {
+ return !overlay1.alwaysOnTop && overlay2.alwaysOnTop;
+ }
+ };
+
+ Polymer.IronOverlayManager = new Polymer.IronOverlayManagerClass();
+</script>
+<script>
+(function() {
+'use strict';
+
+/**
+Use `Polymer.IronOverlayBehavior` to implement an element that can be hidden or shown, and displays
+on top of other content. It includes an optional backdrop, and can be used to implement a variety
+of UI controls including dialogs and drop downs. Multiple overlays may be displayed at once.
+
+See the [demo source code](https://github.com/PolymerElements/iron-overlay-behavior/blob/master/demo/simple-overlay.html)
+for an example.
+
+### Closing and canceling
+
+An overlay may be hidden by closing or canceling. The difference between close and cancel is user
+intent. Closing generally implies that the user acknowledged the content on the overlay. By default,
+it will cancel whenever the user taps outside it or presses the escape key. This behavior is
+configurable with the `no-cancel-on-esc-key` and the `no-cancel-on-outside-click` properties.
+`close()` should be called explicitly by the implementer when the user interacts with a control
+in the overlay element. When the dialog is canceled, the overlay fires an 'iron-overlay-canceled'
+event. Call `preventDefault` on this event to prevent the overlay from closing.
+
+### Positioning
+
+By default the element is sized and positioned to fit and centered inside the window. You can
+position and size it manually using CSS. See `Polymer.IronFitBehavior`.
+
+### Backdrop
+
+Set the `with-backdrop` attribute to display a backdrop behind the overlay. The backdrop is
+appended to `<body>` and is of type `<iron-overlay-backdrop>`. See its doc page for styling
+options.
+
+In addition, `with-backdrop` will wrap the focus within the content in the light DOM.
+Override the [`_focusableNodes` getter](#Polymer.IronOverlayBehavior:property-_focusableNodes)
+to achieve a different behavior.
+
+### Limitations
+
+The element is styled to appear on top of other content by setting its `z-index` property. You
+must ensure no element has a stacking context with a higher `z-index` than its parent stacking
+context. You should place this element as a child of `<body>` whenever possible.
+
+@demo demo/index.html
+@polymerBehavior Polymer.IronOverlayBehavior
+*/
+
+ Polymer.IronOverlayBehaviorImpl = {
+
+ properties: {
+
+ /**
+ * True if the overlay is currently displayed.
+ */
+ opened: {
+ observer: '_openedChanged',
+ type: Boolean,
+ value: false,
+ notify: true
+ },
+
+ /**
+ * True if the overlay was canceled when it was last closed.
+ */
+ canceled: {
+ observer: '_canceledChanged',
+ readOnly: true,
+ type: Boolean,
+ value: false
+ },
+
+ /**
+ * Set to true to display a backdrop behind the overlay. It traps the focus
+ * within the light DOM of the overlay.
+ */
+ withBackdrop: {
+ observer: '_withBackdropChanged',
+ type: Boolean
+ },
+
+ /**
+ * Set to true to disable auto-focusing the overlay or child nodes with
+ * the `autofocus` attribute` when the overlay is opened.
+ */
+ noAutoFocus: {
+ type: Boolean,
+ value: false
+ },
+
+ /**
+ * Set to true to disable canceling the overlay with the ESC key.
+ */
+ noCancelOnEscKey: {
+ type: Boolean,
+ value: false
+ },
+
+ /**
+ * Set to true to disable canceling the overlay by clicking outside it.
+ */
+ noCancelOnOutsideClick: {
+ type: Boolean,
+ value: false
+ },
+
+ /**
+ * Contains the reason(s) this overlay was last closed (see `iron-overlay-closed`).
+ * `IronOverlayBehavior` provides the `canceled` reason; implementers of the
+ * behavior can provide other reasons in addition to `canceled`.
+ */
+ closingReason: {
+ // was a getter before, but needs to be a property so other
+ // behaviors can override this.
+ type: Object
+ },
+
+ /**
+ * Set to true to enable restoring of focus when overlay is closed.
+ */
+ restoreFocusOnClose: {
+ type: Boolean,
+ value: false
+ },
+
+ /**
+ * Set to true to keep overlay always on top.
+ */
+ alwaysOnTop: {
+ type: Boolean
+ },
+
+ /**
+ * Shortcut to access to the overlay manager.
+ * @private
+ * @type {Polymer.IronOverlayManagerClass}
+ */
+ _manager: {
+ type: Object,
+ value: Polymer.IronOverlayManager
+ },
+
+ /**
+ * The node being focused.
+ * @type {?Node}
+ */
+ _focusedChild: {
+ type: Object
+ }
+
+ },
+
+ listeners: {
+ 'iron-resize': '_onIronResize'
+ },
+
+ /**
+ * The backdrop element.
+ * @type {Element}
+ */
+ get backdropElement() {
+ return this._manager.backdropElement;
+ },
+
+ /**
+ * Returns the node to give focus to.
+ * @type {Node}
+ */
+ get _focusNode() {
+ return this._focusedChild || Polymer.dom(this).querySelector('[autofocus]') || this;
+ },
+
+ /**
+ * Array of nodes that can receive focus (overlay included), ordered by `tabindex`.
+ * This is used to retrieve which is the first and last focusable nodes in order
+ * to wrap the focus for overlays `with-backdrop`.
+ *
+ * If you know what is your content (specifically the first and last focusable children),
+ * you can override this method to return only `[firstFocusable, lastFocusable];`
+ * @type {Array<Node>}
+ * @protected
+ */
+ get _focusableNodes() {
+ // Elements that can be focused even if they have [disabled] attribute.
+ var FOCUSABLE_WITH_DISABLED = [
+ 'a[href]',
+ 'area[href]',
+ 'iframe',
+ '[tabindex]',
+ '[contentEditable=true]'
+ ];
+
+ // Elements that cannot be focused if they have [disabled] attribute.
+ var FOCUSABLE_WITHOUT_DISABLED = [
+ 'input',
+ 'select',
+ 'textarea',
+ 'button'
+ ];
+
+ // Discard elements with tabindex=-1 (makes them not focusable).
+ var selector = FOCUSABLE_WITH_DISABLED.join(':not([tabindex="-1"]),') +
+ ':not([tabindex="-1"]),' +
+ FOCUSABLE_WITHOUT_DISABLED.join(':not([disabled]):not([tabindex="-1"]),') +
+ ':not([disabled]):not([tabindex="-1"])';
+
+ var focusables = Polymer.dom(this).querySelectorAll(selector);
+ if (this.tabIndex >= 0) {
+ // Insert at the beginning because we might have all elements with tabIndex = 0,
+ // and the overlay should be the first of the list.
+ focusables.splice(0, 0, this);
+ }
+ // Sort by tabindex.
+ return focusables.sort(function (a, b) {
+ if (a.tabIndex === b.tabIndex) {
+ return 0;
+ }
+ if (a.tabIndex === 0 || a.tabIndex > b.tabIndex) {
+ return 1;
+ }
+ return -1;
+ });
+ },
+
+ ready: function() {
+ // Used to skip calls to notifyResize and refit while the overlay is animating.
+ this.__isAnimating = false;
+ // with-backdrop needs tabindex to be set in order to trap the focus.
+ // If it is not set, IronOverlayBehavior will set it, and remove it if with-backdrop = false.
+ this.__shouldRemoveTabIndex = false;
+ // Used for wrapping the focus on TAB / Shift+TAB.
+ this.__firstFocusableNode = this.__lastFocusableNode = null;
+ // Used for requestAnimationFrame when opened changes.
+ this.__openChangedAsync = null;
+ // Used for requestAnimationFrame when iron-resize is fired.
+ this.__onIronResizeAsync = null;
+ this._ensureSetup();
+ },
+
+ attached: function() {
+ // Call _openedChanged here so that position can be computed correctly.
+ if (this.opened) {
+ this._openedChanged();
+ }
+ this._observer = Polymer.dom(this).observeNodes(this._onNodesChange);
+ },
+
+ detached: function() {
+ Polymer.dom(this).unobserveNodes(this._observer);
+ this._observer = null;
+ this.opened = false;
+ },
+
+ /**
+ * Toggle the opened state of the overlay.
+ */
+ toggle: function() {
+ this._setCanceled(false);
+ this.opened = !this.opened;
+ },
+
+ /**
+ * Open the overlay.
+ */
+ open: function() {
+ this._setCanceled(false);
+ this.opened = true;
+ },
+
+ /**
+ * Close the overlay.
+ */
+ close: function() {
+ this._setCanceled(false);
+ this.opened = false;
+ },
+
+ /**
+ * Cancels the overlay.
+ * @param {Event=} event The original event
+ */
+ cancel: function(event) {
+ var cancelEvent = this.fire('iron-overlay-canceled', event, {cancelable: true});
+ if (cancelEvent.defaultPrevented) {
+ return;
+ }
+
+ this._setCanceled(true);
+ this.opened = false;
+ },
+
+ _ensureSetup: function() {
+ if (this._overlaySetup) {
+ return;
+ }
+ this._overlaySetup = true;
+ this.style.outline = 'none';
+ this.style.display = 'none';
+ },
+
+ _openedChanged: function() {
+ if (this.opened) {
+ this.removeAttribute('aria-hidden');
+ } else {
+ this.setAttribute('aria-hidden', 'true');
+ }
+
+ // wait to call after ready only if we're initially open
+ if (!this._overlaySetup) {
+ return;
+ }
+
+ if (this.__openChangedAsync) {
+ window.cancelAnimationFrame(this.__openChangedAsync);
+ }
+
+ // Synchronously remove the overlay.
+ // The adding is done asynchronously to go out of the scope of the event
+ // which might have generated the opening.
+ if (!this.opened) {
+ this._manager.removeOverlay(this);
+ }
+
+ // Defer any animation-related code on attached
+ // (_openedChanged gets called again on attached).
+ if (!this.isAttached) {
+ return;
+ }
+
+ this.__isAnimating = true;
+
+ // requestAnimationFrame for non-blocking rendering
+ this.__openChangedAsync = window.requestAnimationFrame(function() {
+ this.__openChangedAsync = null;
+ if (this.opened) {
+ this._manager.addOverlay(this);
+ this._prepareRenderOpened();
+ this._renderOpened();
+ } else {
+ // Move the focus before actually closing.
+ this._applyFocus();
+ this._renderClosed();
+ }
+ }.bind(this));
+ },
+
+ _canceledChanged: function() {
+ this.closingReason = this.closingReason || {};
+ this.closingReason.canceled = this.canceled;
+ },
+
+ _withBackdropChanged: function() {
+ // If tabindex is already set, no need to override it.
+ if (this.withBackdrop && !this.hasAttribute('tabindex')) {
+ this.setAttribute('tabindex', '-1');
+ this.__shouldRemoveTabIndex = true;
+ } else if (this.__shouldRemoveTabIndex) {
+ this.removeAttribute('tabindex');
+ this.__shouldRemoveTabIndex = false;
+ }
+ if (this.opened && this.isAttached) {
+ this._manager.trackBackdrop();
+ }
+ },
+
+ /**
+ * tasks which must occur before opening; e.g. making the element visible.
+ * @protected
+ */
+ _prepareRenderOpened: function() {
+
+ // Needed to calculate the size of the overlay so that transitions on its size
+ // will have the correct starting points.
+ this._preparePositioning();
+ this.refit();
+ this._finishPositioning();
+
+ // Move the focus to the child node with [autofocus].
+ this._applyFocus();
+
+ // Safari will apply the focus to the autofocus element when displayed for the first time,
+ // so we blur it. Later, _applyFocus will set the focus if necessary.
+ if (this.noAutoFocus && document.activeElement === this._focusNode) {
+ this._focusNode.blur();
+ }
+ },
+
+ /**
+ * Tasks which cause the overlay to actually open; typically play an animation.
+ * @protected
+ */
+ _renderOpened: function() {
+ this._finishRenderOpened();
+ },
+
+ /**
+ * Tasks which cause the overlay to actually close; typically play an animation.
+ * @protected
+ */
+ _renderClosed: function() {
+ this._finishRenderClosed();
+ },
+
+ /**
+ * Tasks to be performed at the end of open action. Will fire `iron-overlay-opened`.
+ * @protected
+ */
+ _finishRenderOpened: function() {
+
+ this.notifyResize();
+ this.__isAnimating = false;
+
+ // Store it so we don't query too much.
+ var focusableNodes = this._focusableNodes;
+ this.__firstFocusableNode = focusableNodes[0];
+ this.__lastFocusableNode = focusableNodes[focusableNodes.length - 1];
+
+ this.fire('iron-overlay-opened');
+ },
+
+ /**
+ * Tasks to be performed at the end of close action. Will fire `iron-overlay-closed`.
+ * @protected
+ */
+ _finishRenderClosed: function() {
+ // Hide the overlay and remove the backdrop.
+ this.style.display = 'none';
+ // Reset z-index only at the end of the animation.
+ this.style.zIndex = '';
+
+ this.notifyResize();
+ this.__isAnimating = false;
+ this.fire('iron-overlay-closed', this.closingReason);
+ },
+
+ _preparePositioning: function() {
+ this.style.transition = this.style.webkitTransition = 'none';
+ this.style.transform = this.style.webkitTransform = 'none';
+ this.style.display = '';
+ },
+
+ _finishPositioning: function() {
+ // First, make it invisible & reactivate animations.
+ this.style.display = 'none';
+ // Force reflow before re-enabling animations so that they don't start.
+ // Set scrollTop to itself so that Closure Compiler doesn't remove this.
+ this.scrollTop = this.scrollTop;
+ this.style.transition = this.style.webkitTransition = '';
+ this.style.transform = this.style.webkitTransform = '';
+ // Now that animations are enabled, make it visible again
+ this.style.display = '';
+ // Force reflow, so that following animations are properly started.
+ // Set scrollTop to itself so that Closure Compiler doesn't remove this.
+ this.scrollTop = this.scrollTop;
+ },
+
+ /**
+ * Applies focus according to the opened state.
+ * @protected
+ */
+ _applyFocus: function() {
+ if (this.opened) {
+ if (!this.noAutoFocus) {
+ this._focusNode.focus();
+ }
+ } else {
+ this._focusNode.blur();
+ this._focusedChild = null;
+ this._manager.focusOverlay();
+ }
+ },
+
+ /**
+ * Cancels (closes) the overlay. Call when click happens outside the overlay.
+ * @param {!Event} event
+ * @protected
+ */
+ _onCaptureClick: function(event) {
+ if (!this.noCancelOnOutsideClick) {
+ this.cancel(event);
+ }
+ },
+
+ /**
+ * Keeps track of the focused child. If withBackdrop, traps focus within overlay.
+ * @param {!Event} event
+ * @protected
+ */
+ _onCaptureFocus: function (event) {
+ if (!this.withBackdrop) {
+ return;
+ }
+ var path = Polymer.dom(event).path;
+ if (path.indexOf(this) === -1) {
+ event.stopPropagation();
+ this._applyFocus();
+ } else {
+ this._focusedChild = path[0];
+ }
+ },
+
+ /**
+ * Handles the ESC key event and cancels (closes) the overlay.
+ * @param {!Event} event
+ * @protected
+ */
+ _onCaptureEsc: function(event) {
+ if (!this.noCancelOnEscKey) {
+ this.cancel(event);
+ }
+ },
+
+ /**
+ * Handles TAB key events to track focus changes.
+ * Will wrap focus for overlays withBackdrop.
+ * @param {!Event} event
+ * @protected
+ */
+ _onCaptureTab: function(event) {
+ if (!this.withBackdrop) {
+ return;
+ }
+ // TAB wraps from last to first focusable.
+ // Shift + TAB wraps from first to last focusable.
+ var shift = event.shiftKey;
+ var nodeToCheck = shift ? this.__firstFocusableNode : this.__lastFocusableNode;
+ var nodeToSet = shift ? this.__lastFocusableNode : this.__firstFocusableNode;
+ var shouldWrap = false;
+ if (nodeToCheck === nodeToSet) {
+ // If nodeToCheck is the same as nodeToSet, it means we have an overlay
+ // with 0 or 1 focusables; in either case we still need to trap the
+ // focus within the overlay.
+ shouldWrap = true;
+ } else {
+ // In dom=shadow, the manager will receive focus changes on the main
+ // root but not the ones within other shadow roots, so we can't rely on
+ // _focusedChild, but we should check the deepest active element.
+ var focusedNode = this._manager.deepActiveElement;
+ // If the active element is not the nodeToCheck but the overlay itself,
+ // it means the focus is about to go outside the overlay, hence we
+ // should prevent that (e.g. user opens the overlay and hit Shift+TAB).
+ shouldWrap = (focusedNode === nodeToCheck || focusedNode === this);
+ }
+
+ if (shouldWrap) {
+ // When the overlay contains the last focusable element of the document
+ // and it's already focused, pressing TAB would move the focus outside
+ // the document (e.g. to the browser search bar). Similarly, when the
+ // overlay contains the first focusable element of the document and it's
+ // already focused, pressing Shift+TAB would move the focus outside the
+ // document (e.g. to the browser search bar).
+ // In both cases, we would not receive a focus event, but only a blur.
+ // In order to achieve focus wrapping, we prevent this TAB event and
+ // force the focus. This will also prevent the focus to temporarily move
+ // outside the overlay, which might cause scrolling.
+ event.preventDefault();
+ this._focusedChild = nodeToSet;
+ this._applyFocus();
+ }
+ },
+
+ /**
+ * Refits if the overlay is opened and not animating.
+ * @protected
+ */
+ _onIronResize: function() {
+ if (this.__onIronResizeAsync) {
+ window.cancelAnimationFrame(this.__onIronResizeAsync);
+ this.__onIronResizeAsync = null;
+ }
+ if (this.opened && !this.__isAnimating) {
+ this.__onIronResizeAsync = window.requestAnimationFrame(function() {
+ this.__onIronResizeAsync = null;
+ this.refit();
+ }.bind(this));
+ }
+ },
+
+ /**
+ * Will call notifyResize if overlay is opened.
+ * Can be overridden in order to avoid multiple observers on the same node.
+ * @protected
+ */
+ _onNodesChange: function() {
+ if (this.opened && !this.__isAnimating) {
+ this.notifyResize();
+ }
+ }
+ };
+
+ /** @polymerBehavior */
+ Polymer.IronOverlayBehavior = [Polymer.IronFitBehavior, Polymer.IronResizableBehavior, Polymer.IronOverlayBehaviorImpl];
+
+ /**
+ * Fired after the overlay opens.
+ * @event iron-overlay-opened
+ */
+
+ /**
+ * Fired when the overlay is canceled, but before it is closed.
+ * @event iron-overlay-canceled
+ * @param {Event} event The closing of the overlay can be prevented
+ * by calling `event.preventDefault()`. The `event.detail` is the original event that
+ * originated the canceling (e.g. ESC keyboard event or click event outside the overlay).
+ */
+
+ /**
+ * Fired after the overlay closes.
+ * @event iron-overlay-closed
+ * @param {Event} event The `event.detail` is the `closingReason` property
+ * (contains `canceled`, whether the overlay was canceled).
+ */
+
+})();
+</script>
+
+
+<dom-module id="paper-toast" assetpath="/res/imp/bower_components/paper-toast/">
+ <template>
+ <style>
+ :host {
+ display: block;
+ position: fixed;
+ background-color: var(--paper-toast-background-color, #323232);
+ color: var(--paper-toast-color, #f1f1f1);
+ min-height: 48px;
+ min-width: 288px;
+ padding: 16px 24px;
+ box-sizing: border-box;
+ box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.26);
+ border-radius: 2px;
+ margin: 12px;
+ font-size: 14px;
+ cursor: default;
+ -webkit-transition: -webkit-transform 0.3s, opacity 0.3s;
+ transition: transform 0.3s, opacity 0.3s;
+ opacity: 0;
+ -webkit-transform: translateY(100px);
+ transform: translateY(100px);
+ @apply(--paper-font-common-base);
+ }
+
+ :host(.capsule) {
+ border-radius: 24px;
+ }
+
+ :host(.fit-bottom) {
+ width: 100%;
+ min-width: 0;
+ border-radius: 0;
+ margin: 0;
+ }
+
+ :host(.paper-toast-open) {
+ opacity: 1;
+ -webkit-transform: translateY(0px);
+ transform: translateY(0px);
+ }
+ </style>
+
+ <span id="label">{{text}}</span>
+ <content></content>
+ </template>
+
+ <script>
+ (function() {
+ // Keeps track of the toast currently opened.
+ var currentToast = null;
+
+ Polymer({
+ is: 'paper-toast',
+
+ behaviors: [
+ Polymer.IronOverlayBehavior
+ ],
+
+ properties: {
+ /**
+ * The element to fit `this` into.
+ * Overridden from `Polymer.IronFitBehavior`.
+ */
+ fitInto: {
+ type: Object,
+ value: window,
+ observer: '_onFitIntoChanged'
+ },
+
+ /**
+ * The orientation against which to align the dropdown content
+ * horizontally relative to `positionTarget`.
+ * Overridden from `Polymer.IronFitBehavior`.
+ */
+ horizontalAlign: {
+ type: String,
+ value: 'left'
+ },
+
+ /**
+ * The orientation against which to align the dropdown content
+ * vertically relative to `positionTarget`.
+ * Overridden from `Polymer.IronFitBehavior`.
+ */
+ verticalAlign: {
+ type: String,
+ value: 'bottom'
+ },
+
+ /**
+ * The duration in milliseconds to show the toast.
+ * Set to `0`, a negative number, or `Infinity`, to disable the
+ * toast auto-closing.
+ */
+ duration: {
+ type: Number,
+ value: 3000
+ },
+
+ /**
+ * The text to display in the toast.
+ */
+ text: {
+ type: String,
+ value: ''
+ },
+
+ /**
+ * Overridden from `IronOverlayBehavior`.
+ * Set to false to enable closing of the toast by clicking outside it.
+ */
+ noCancelOnOutsideClick: {
+ type: Boolean,
+ value: true
+ },
+
+ /**
+ * Overridden from `IronOverlayBehavior`.
+ * Set to true to disable auto-focusing the toast or child nodes with
+ * the `autofocus` attribute` when the overlay is opened.
+ */
+ noAutoFocus: {
+ type: Boolean,
+ value: true
+ }
+ },
+
+ listeners: {
+ 'transitionend': '__onTransitionEnd'
+ },
+
+ /**
+ * Read-only. Deprecated. Use `opened` from `IronOverlayBehavior`.
+ * @property visible
+ * @deprecated
+ */
+ get visible() {
+ Polymer.Base._warn('`visible` is deprecated, use `opened` instead');
+ return this.opened;
+ },
+
+ /**
+ * Read-only. Can auto-close if duration is a positive finite number.
+ * @property _canAutoClose
+ */
+ get _canAutoClose() {
+ return this.duration > 0 && this.duration !== Infinity;
+ },
+
+ created: function() {
+ this._autoClose = null;
+ Polymer.IronA11yAnnouncer.requestAvailability();
+ },
+
+ /**
+ * Show the toast. Without arguments, this is the same as `open()` from `IronOverlayBehavior`.
+ * @param {(Object|string)=} properties Properties to be set before opening the toast.
+ * e.g. `toast.show('hello')` or `toast.show({text: 'hello', duration: 3000})`
+ */
+ show: function(properties) {
+ if (typeof properties == 'string') {
+ properties = { text: properties };
+ }
+ for (var property in properties) {
+ if (property.indexOf('_') === 0) {
+ Polymer.Base._warn('The property "' + property + '" is private and was not set.');
+ } else if (property in this) {
+ this[property] = properties[property];
+ } else {
+ Polymer.Base._warn('The property "' + property + '" is not valid.');
+ }
+ }
+ this.open();
+ },
+
+ /**
+ * Hide the toast. Same as `close()` from `IronOverlayBehavior`.
+ */
+ hide: function() {
+ this.close();
+ },
+
+ /**
+ * Called on transitions of the toast, indicating a finished animation
+ * @private
+ */
+ __onTransitionEnd: function(e) {
+ // there are different transitions that are happening when opening and
+ // closing the toast. The last one so far is for `opacity`.
+ // This marks the end of the transition, so we check for this to determine if this
+ // is the correct event.
+ if (e && e.target === this && e.propertyName === 'opacity') {
+ if (this.opened) {
+ this._finishRenderOpened();
+ } else {
+ this._finishRenderClosed();
+ }
+ }
+ },
+
+ /**
+ * Overridden from `IronOverlayBehavior`.
+ * Called when the value of `opened` changes.
+ */
+ _openedChanged: function() {
+ if (this._autoClose !== null) {
+ this.cancelAsync(this._autoClose);
+ this._autoClose = null;
+ }
+ if (this.opened) {
+ if (currentToast && currentToast !== this) {
+ currentToast.close();
+ }
+ currentToast = this;
+ this.fire('iron-announce', {
+ text: this.text
+ });
+ if (this._canAutoClose) {
+ this._autoClose = this.async(this.close, this.duration);
+ }
+ } else if (currentToast === this) {
+ currentToast = null;
+ }
+ Polymer.IronOverlayBehaviorImpl._openedChanged.apply(this, arguments);
+ },
+
+ /**
+ * Overridden from `IronOverlayBehavior`.
+ */
+ _renderOpened: function() {
+ this.classList.add('paper-toast-open');
+ },
+
+ /**
+ * Overridden from `IronOverlayBehavior`.
+ */
+ _renderClosed: function() {
+ this.classList.remove('paper-toast-open');
+ },
+
+ /**
+ * @private
+ */
+ _onFitIntoChanged: function(fitInto) {
+ this.positionTarget = fitInto;
+ }
+
+ /**
+ * Fired when `paper-toast` is opened.
+ *
+ * @event 'iron-announce'
+ * @param {{text: string}} detail Contains text that will be announced.
+ */
+ });
+ })();
+ </script>
+</dom-module>
+<dom-module id="url-param" assetpath="/res/imp/common/">
+ <template>
+ <paper-toast id="toast"></paper-toast>
+ </template>
+ <script>
+ (function(){
+ Polymer({
+ is: 'url-param',
+ properties: {
+ default_value: {
+ type: String,
+ },
+ default_values: {
+ type: Array,
+ },
+ multi: {
+ type: Boolean,
+ value: false,
+ },
+ name: {
+ type: String,
+ },
+ valid: {
+ type: Array,
+ },
+ value: {
+ type: String,
+ value: '',
+ notify: true,
+ observer: '_valueChanged',
+ },
+
+ _loaded: {
+ type: Boolean,
+ value: false,
+ }
+ },
+ // Listens to array changes for multi urls
+ observers: ["_valueChanged(value.splices)"],
+
+ ready: function () {
+ this._loaded = true;
+
+ // Read the URL parameters. If our variable is set, save its value.
+ // Otherwise, place our value in the URL.
+ var val = this._getURL();
+ if (val && this._isValid(val)) {
+ this.set('value', val);
+ } else if (this.default_value && this._isValid(this.default_value)) {
+ this.set('value', this.default_value);
+ }
+ else if (this.multi && this.default_values && this._isValid(this.default_values)) {
+ this.set('value', this.default_values);
+ }
+ else {
+ this._putURL();
+ }
+ },
+ // Retrieve the value for our variable from the URL.
+ _getURL: function () {
+ var vals = sk.query.toParamSet(window.location.search.substring(1))[this.name];
+ if (!vals) {
+ return null;
+ }
+ if (this.multi) {
+ return vals;
+ }
+ if (vals.length > 1) {
+ this._error('Multiple values provided for ' + this.name + ' but only one accepted: ' + vals);
+ return null;
+ }
+ return vals[0];
+ },
+ // Store the value for our variable in the URL.
+ _putURL: function () {
+ var params = sk.query.toParamSet(window.location.search.substring(1));
+ delete params[this.name];
+ if (!this.value || Array.isArray(this.value) && this.value.length == 0) {
+ } else
+ // Don't insert undefined/empty values.
+ {
+ if (this.multi) {
+ params[this.name] = this.value;
+ } else {
+ params[this.name] = [this.value];
+ }
+ }
+ var newUrl = window.location.href.split('?')[0] + '?' + sk.query.fromParamSet(params);
+ window.history.replaceState('', '', newUrl);
+ },
+ // Check to see whether the given value is valid.
+ _isValid: function (val) {
+ var checkValid = function (val) {
+ if (this.valid) {
+ for (var i = 0; i < this.valid.length; i++) {
+ if (val == this.valid[i]) {
+ return true;
+ }
+ }
+ this._error('Invalid value for ' + this.name + ': "' + val + '". Must be one of: ' + this.valid);
+ return false;
+ }
+ return true;
+ }.bind(this);
+ if (this.multi) {
+ // Verify that it's an array and that all elements are valid.
+ if (!Array.isArray(val)) {
+ this._error('url-param-sk: Value is not an array: ' + val);
+ return false;
+ }
+ for (var i = 0; i < val.length; i++) {
+ if (!checkValid(val[i])) {
+ return false;
+ }
+ }
+ } else {
+ if (Array.isArray(val)) {
+ this._error('Multiple values provided for ' + this.name + ' but only one accepted: ' + val);
+ }
+ return checkValid(val);
+ }
+ return true;
+ },
+ _valueChanged: function () {
+ if (this._loaded) {
+ // Save our value to the URL.
+ this._putURL();
+ }
+ },
+ _error: function (msg) {
+ console.log('[ERROR] '+msg);
+ this.set('$.toast.text', msg);
+ this.$.toast.show();
+ }
+ });
+ })()
+ </script>
+</dom-module>
+<script>
/**
* @param {!Function} selectCallback
@@ -16715,486 +19399,6 @@ You can bind to `isAuthorized` property to monitor authorization state.
</script>
<script>
- (function() {
- 'use strict';
-
- /**
- * Chrome uses an older version of DOM Level 3 Keyboard Events
- *
- * Most keys are labeled as text, but some are Unicode codepoints.
- * Values taken from: http://www.w3.org/TR/2007/WD-DOM-Level-3-Events-20071221/keyset.html#KeySet-Set
- */
- var KEY_IDENTIFIER = {
- 'U+0008': 'backspace',
- 'U+0009': 'tab',
- 'U+001B': 'esc',
- 'U+0020': 'space',
- 'U+007F': 'del'
- };
-
- /**
- * Special table for KeyboardEvent.keyCode.
- * KeyboardEvent.keyIdentifier is better, and KeyBoardEvent.key is even better
- * than that.
- *
- * Values from: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent.keyCode#Value_of_keyCode
- */
- var KEY_CODE = {
- 8: 'backspace',
- 9: 'tab',
- 13: 'enter',
- 27: 'esc',
- 33: 'pageup',
- 34: 'pagedown',
- 35: 'end',
- 36: 'home',
- 32: 'space',
- 37: 'left',
- 38: 'up',
- 39: 'right',
- 40: 'down',
- 46: 'del',
- 106: '*'
- };
-
- /**
- * MODIFIER_KEYS maps the short name for modifier keys used in a key
- * combo string to the property name that references those same keys
- * in a KeyboardEvent instance.
- */
- var MODIFIER_KEYS = {
- 'shift': 'shiftKey',
- 'ctrl': 'ctrlKey',
- 'alt': 'altKey',
- 'meta': 'metaKey'
- };
-
- /**
- * KeyboardEvent.key is mostly represented by printable character made by
- * the keyboard, with unprintable keys labeled nicely.
- *
- * However, on OS X, Alt+char can make a Unicode character that follows an
- * Apple-specific mapping. In this case, we fall back to .keyCode.
- */
- var KEY_CHAR = /[a-z0-9*]/;
-
- /**
- * Matches a keyIdentifier string.
- */
- var IDENT_CHAR = /U\+/;
-
- /**
- * Matches arrow keys in Gecko 27.0+
- */
- var ARROW_KEY = /^arrow/;
-
- /**
- * Matches space keys everywhere (notably including IE10's exceptional name
- * `spacebar`).
- */
- var SPACE_KEY = /^space(bar)?/;
-
- /**
- * Matches ESC key.
- *
- * Value from: http://w3c.github.io/uievents-key/#key-Escape
- */
- var ESC_KEY = /^escape$/;
-
- /**
- * Transforms the key.
- * @param {string} key The KeyBoardEvent.key
- * @param {Boolean} [noSpecialChars] Limits the transformation to
- * alpha-numeric characters.
- */
- function transformKey(key, noSpecialChars) {
- var validKey = '';
- if (key) {
- var lKey = key.toLowerCase();
- if (lKey === ' ' || SPACE_KEY.test(lKey)) {
- validKey = 'space';
- } else if (ESC_KEY.test(lKey)) {
- validKey = 'esc';
- } else if (lKey.length == 1) {
- if (!noSpecialChars || KEY_CHAR.test(lKey)) {
- validKey = lKey;
- }
- } else if (ARROW_KEY.test(lKey)) {
- validKey = lKey.replace('arrow', '');
- } else if (lKey == 'multiply') {
- // numpad '*' can map to Multiply on IE/Windows
- validKey = '*';
- } else {
- validKey = lKey;
- }
- }
- return validKey;
- }
-
- function transformKeyIdentifier(keyIdent) {
- var validKey = '';
- if (keyIdent) {
- if (keyIdent in KEY_IDENTIFIER) {
- validKey = KEY_IDENTIFIER[keyIdent];
- } else if (IDENT_CHAR.test(keyIdent)) {
- keyIdent = parseInt(keyIdent.replace('U+', '0x'), 16);
- validKey = String.fromCharCode(keyIdent).toLowerCase();
- } else {
- validKey = keyIdent.toLowerCase();
- }
- }
- return validKey;
- }
-
- function transformKeyCode(keyCode) {
- var validKey = '';
- if (Number(keyCode)) {
- if (keyCode >= 65 && keyCode <= 90) {
- // ascii a-z
- // lowercase is 32 offset from uppercase
- validKey = String.fromCharCode(32 + keyCode);
- } else if (keyCode >= 112 && keyCode <= 123) {
- // function keys f1-f12
- validKey = 'f' + (keyCode - 112);
- } else if (keyCode >= 48 && keyCode <= 57) {
- // top 0-9 keys
- validKey = String(keyCode - 48);
- } else if (keyCode >= 96 && keyCode <= 105) {
- // num pad 0-9
- validKey = String(keyCode - 96);
- } else {
- validKey = KEY_CODE[keyCode];
- }
- }
- return validKey;
- }
-
- /**
- * Calculates the normalized key for a KeyboardEvent.
- * @param {KeyboardEvent} keyEvent
- * @param {Boolean} [noSpecialChars] Set to true to limit keyEvent.key
- * transformation to alpha-numeric chars. This is useful with key
- * combinations like shift + 2, which on FF for MacOS produces
- * keyEvent.key = @
- * To get 2 returned, set noSpecialChars = true
- * To get @ returned, set noSpecialChars = false
- */
- function normalizedKeyForEvent(keyEvent, noSpecialChars) {
- // Fall back from .key, to .keyIdentifier, to .keyCode, and then to
- // .detail.key to support artificial keyboard events.
- return transformKey(keyEvent.key, noSpecialChars) ||
- transformKeyIdentifier(keyEvent.keyIdentifier) ||
- transformKeyCode(keyEvent.keyCode) ||
- transformKey(keyEvent.detail ? keyEvent.detail.key : keyEvent.detail, noSpecialChars) || '';
- }
-
- function keyComboMatchesEvent(keyCombo, event) {
- // For combos with modifiers we support only alpha-numeric keys
- var keyEvent = normalizedKeyForEvent(event, keyCombo.hasModifiers);
- return keyEvent === keyCombo.key &&
- (!keyCombo.hasModifiers || (
- !!event.shiftKey === !!keyCombo.shiftKey &&
- !!event.ctrlKey === !!keyCombo.ctrlKey &&
- !!event.altKey === !!keyCombo.altKey &&
- !!event.metaKey === !!keyCombo.metaKey)
- );
- }
-
- function parseKeyComboString(keyComboString) {
- if (keyComboString.length === 1) {
- return {
- combo: keyComboString,
- key: keyComboString,
- event: 'keydown'
- };
- }
- return keyComboString.split('+').reduce(function(parsedKeyCombo, keyComboPart) {
- var eventParts = keyComboPart.split(':');
- var keyName = eventParts[0];
- var event = eventParts[1];
-
- if (keyName in MODIFIER_KEYS) {
- parsedKeyCombo[MODIFIER_KEYS[keyName]] = true;
- parsedKeyCombo.hasModifiers = true;
- } else {
- parsedKeyCombo.key = keyName;
- parsedKeyCombo.event = event || 'keydown';
- }
-
- return parsedKeyCombo;
- }, {
- combo: keyComboString.split(':').shift()
- });
- }
-
- function parseEventString(eventString) {
- return eventString.trim().split(' ').map(function(keyComboString) {
- return parseKeyComboString(keyComboString);
- });
- }
-
- /**
- * `Polymer.IronA11yKeysBehavior` provides a normalized interface for processing
- * keyboard commands that pertain to [WAI-ARIA best practices](http://www.w3.org/TR/wai-aria-practices/#kbd_general_binding).
- * The element takes care of browser differences with respect to Keyboard events
- * and uses an expressive syntax to filter key presses.
- *
- * Use the `keyBindings` prototype property to express what combination of keys
- * will trigger the callback. A key binding has the format
- * `"KEY+MODIFIER:EVENT": "callback"` (`"KEY": "callback"` or
- * `"KEY:EVENT": "callback"` are valid as well). Some examples:
- *
- * keyBindings: {
- * 'space': '_onKeydown', // same as 'space:keydown'
- * 'shift+tab': '_onKeydown',
- * 'enter:keypress': '_onKeypress',
- * 'esc:keyup': '_onKeyup'
- * }
- *
- * The callback will receive with an event containing the following information in `event.detail`:
- *
- * _onKeydown: function(event) {
- * console.log(event.detail.combo); // KEY+MODIFIER, e.g. "shift+tab"
- * console.log(event.detail.key); // KEY only, e.g. "tab"
- * console.log(event.detail.event); // EVENT, e.g. "keydown"
- * console.log(event.detail.keyboardEvent); // the original KeyboardEvent
- * }
- *
- * Use the `keyEventTarget` attribute to set up event handlers on a specific
- * node.
- *
- * See the [demo source code](https://github.com/PolymerElements/iron-a11y-keys-behavior/blob/master/demo/x-key-aware.html)
- * for an example.
- *
- * @demo demo/index.html
- * @polymerBehavior
- */
- Polymer.IronA11yKeysBehavior = {
- properties: {
- /**
- * The EventTarget that will be firing relevant KeyboardEvents. Set it to
- * `null` to disable the listeners.
- * @type {?EventTarget}
- */
- keyEventTarget: {
- type: Object,
- value: function() {
- return this;
- }
- },
-
- /**
- * If true, this property will cause the implementing element to
- * automatically stop propagation on any handled KeyboardEvents.
- */
- stopKeyboardEventPropagation: {
- type: Boolean,
- value: false
- },
-
- _boundKeyHandlers: {
- type: Array,
- value: function() {
- return [];
- }
- },
-
- // We use this due to a limitation in IE10 where instances will have
- // own properties of everything on the "prototype".
- _imperativeKeyBindings: {
- type: Object,
- value: function() {
- return {};
- }
- }
- },
-
- observers: [
- '_resetKeyEventListeners(keyEventTarget, _boundKeyHandlers)'
- ],
-
-
- /**
- * To be used to express what combination of keys will trigger the relative
- * callback. e.g. `keyBindings: { 'esc': '_onEscPressed'}`
- * @type {Object}
- */
- keyBindings: {},
-
- registered: function() {
- this._prepKeyBindings();
- },
-
- attached: function() {
- this._listenKeyEventListeners();
- },
-
- detached: function() {
- this._unlistenKeyEventListeners();
- },
-
- /**
- * Can be used to imperatively add a key binding to the implementing
- * element. This is the imperative equivalent of declaring a keybinding
- * in the `keyBindings` prototype property.
- */
- addOwnKeyBinding: function(eventString, handlerName) {
- this._imperativeKeyBindings[eventString] = handlerName;
- this._prepKeyBindings();
- this._resetKeyEventListeners();
- },
-
- /**
- * When called, will remove all imperatively-added key bindings.
- */
- removeOwnKeyBindings: function() {
- this._imperativeKeyBindings = {};
- this._prepKeyBindings();
- this._resetKeyEventListeners();
- },
-
- /**
- * Returns true if a keyboard event matches `eventString`.
- *
- * @param {KeyboardEvent} event
- * @param {string} eventString
- * @return {boolean}
- */
- keyboardEventMatchesKeys: function(event, eventString) {
- var keyCombos = parseEventString(eventString);
- for (var i = 0; i < keyCombos.length; ++i) {
- if (keyComboMatchesEvent(keyCombos[i], event)) {
- return true;
- }
- }
- return false;
- },
-
- _collectKeyBindings: function() {
- var keyBindings = this.behaviors.map(function(behavior) {
- return behavior.keyBindings;
- });
-
- if (keyBindings.indexOf(this.keyBindings) === -1) {
- keyBindings.push(this.keyBindings);
- }
-
- return keyBindings;
- },
-
- _prepKeyBindings: function() {
- this._keyBindings = {};
-
- this._collectKeyBindings().forEach(function(keyBindings) {
- for (var eventString in keyBindings) {
- this._addKeyBinding(eventString, keyBindings[eventString]);
- }
- }, this);
-
- for (var eventString in this._imperativeKeyBindings) {
- this._addKeyBinding(eventString, this._imperativeKeyBindings[eventString]);
- }
-
- // Give precedence to combos with modifiers to be checked first.
- for (var eventName in this._keyBindings) {
- this._keyBindings[eventName].sort(function (kb1, kb2) {
- var b1 = kb1[0].hasModifiers;
- var b2 = kb2[0].hasModifiers;
- return (b1 === b2) ? 0 : b1 ? -1 : 1;
- })
- }
- },
-
- _addKeyBinding: function(eventString, handlerName) {
- parseEventString(eventString).forEach(function(keyCombo) {
- this._keyBindings[keyCombo.event] =
- this._keyBindings[keyCombo.event] || [];
-
- this._keyBindings[keyCombo.event].push([
- keyCombo,
- handlerName
- ]);
- }, this);
- },
-
- _resetKeyEventListeners: function() {
- this._unlistenKeyEventListeners();
-
- if (this.isAttached) {
- this._listenKeyEventListeners();
- }
- },
-
- _listenKeyEventListeners: function() {
- if (!this.keyEventTarget) {
- return;
- }
- Object.keys(this._keyBindings).forEach(function(eventName) {
- var keyBindings = this._keyBindings[eventName];
- var boundKeyHandler = this._onKeyBindingEvent.bind(this, keyBindings);
-
- this._boundKeyHandlers.push([this.keyEventTarget, eventName, boundKeyHandler]);
-
- this.keyEventTarget.addEventListener(eventName, boundKeyHandler);
- }, this);
- },
-
- _unlistenKeyEventListeners: function() {
- var keyHandlerTuple;
- var keyEventTarget;
- var eventName;
- var boundKeyHandler;
-
- while (this._boundKeyHandlers.length) {
- // My kingdom for block-scope binding and destructuring assignment..
- keyHandlerTuple = this._boundKeyHandlers.pop();
- keyEventTarget = keyHandlerTuple[0];
- eventName = keyHandlerTuple[1];
- boundKeyHandler = keyHandlerTuple[2];
-
- keyEventTarget.removeEventListener(eventName, boundKeyHandler);
- }
- },
-
- _onKeyBindingEvent: function(keyBindings, event) {
- if (this.stopKeyboardEventPropagation) {
- event.stopPropagation();
- }
-
- // if event has been already prevented, don't do anything
- if (event.defaultPrevented) {
- return;
- }
-
- for (var i = 0; i < keyBindings.length; i++) {
- var keyCombo = keyBindings[i][0];
- var handlerName = keyBindings[i][1];
- if (keyComboMatchesEvent(keyCombo, event)) {
- this._triggerKeyHandler(keyCombo, handlerName, event);
- // exit the loop if eventDefault was prevented
- if (event.defaultPrevented) {
- return;
- }
- }
- }
- },
-
- _triggerKeyHandler: function(keyCombo, handlerName, keyboardEvent) {
- var detail = Object.create(keyCombo);
- detail.keyboardEvent = keyboardEvent;
- var event = new CustomEvent(keyCombo.event, {
- detail: detail,
- cancelable: true
- });
- this[handlerName].call(this, event);
- if (event.defaultPrevented) {
- keyboardEvent.preventDefault();
- }
- }
- };
- })();
-</script>
-<script>
/**
* @demo demo/index.html
@@ -18726,86 +20930,6 @@ You can bind to `isAuthorized` property to monitor authorization state.
});
</script>
</dom-module>
-
-
-<dom-module id="iron-a11y-announcer" assetpath="/res/imp/bower_components/iron-a11y-announcer/">
- <template>
- <style>
- :host {
- display: inline-block;
- position: fixed;
- clip: rect(0px,0px,0px,0px);
- }
- </style>
- <div aria-live$="[[mode]]">[[_text]]</div>
- </template>
-
- <script>
-
- (function() {
- 'use strict';
-
- Polymer.IronA11yAnnouncer = Polymer({
- is: 'iron-a11y-announcer',
-
- properties: {
-
- /**
- * The value of mode is used to set the `aria-live` attribute
- * for the element that will be announced. Valid values are: `off`,
- * `polite` and `assertive`.
- */
- mode: {
- type: String,
- value: 'polite'
- },
-
- _text: {
- type: String,
- value: ''
- }
- },
-
- created: function() {
- if (!Polymer.IronA11yAnnouncer.instance) {
- Polymer.IronA11yAnnouncer.instance = this;
- }
-
- document.body.addEventListener('iron-announce', this._onIronAnnounce.bind(this));
- },
-
- /**
- * Cause a text string to be announced by screen readers.
- *
- * @param {string} text The text that should be announced.
- */
- announce: function(text) {
- this._text = '';
- this.async(function() {
- this._text = text;
- }, 100);
- },
-
- _onIronAnnounce: function(event) {
- if (event.detail && event.detail.text) {
- this.announce(event.detail.text);
- }
- }
- });
-
- Polymer.IronA11yAnnouncer.instance = null;
-
- Polymer.IronA11yAnnouncer.requestAvailability = function() {
- if (!Polymer.IronA11yAnnouncer.instance) {
- Polymer.IronA11yAnnouncer.instance = document.createElement('iron-a11y-announcer');
- }
-
- document.body.appendChild(Polymer.IronA11yAnnouncer.instance);
- };
- })();
-
- </script>
-</dom-module>
<script>
/*
@@ -20566,11 +22690,17 @@ is separate from validation, and `allowed-pattern` does not affect how the input
(function(){
var ANDROID_ALIASES = {
"bullhead": "Nexus 5X",
- "flo": "Nexus 7",
+ "flo": "Nexus 7 (2013)",
"flounder": "Nexus 9",
+ "foster": "NVIDIA Shield",
+ "fugu": "Nexus Player",
+ "grouper": "Nexus 7 (2012)",
"hammerhead": "Nexus 5",
+ "m0": "Galaxy S3",
"mako": "Nexus 4",
+ "manta": "Nexus 10",
"shamu": "Nexus 6",
+ "sprout": "Android One",
};
// Taken from http://developer.android.com/reference/android/os/BatteryManager.html
var BATTERY_HEALTH_UNKNOWN = 1;
@@ -20585,12 +22715,14 @@ is separate from validation, and `allowed-pattern` does not affect how the input
"1002": "AMD",
"1002:6779": "AMD Radeon HD 6450/7450/8450",
"1002:6821": "AMD Radeon HD 8870M",
+ "1002:683d": "AMD Radeon HD 7770/8760",
"1002:9830": "AMD Radeon HD 8400",
"102b": "Matrox",
"102b:0522": "Matrox MGA G200e",
"102b:0532": "Matrox MGA G200eW",
"102b:0534": "Matrox G200eR2",
"10de": "NVIDIA",
+ "10de:08a4": "NVIDIA GeForce 320M",
"10de:08aa": "NVIDIA GeForce 320M",
"10de:0fe9": "NVIDIA GeForce GT 750M Mac Edition",
"10de:104a": "NVIDIA GeForce GT 610",
@@ -20598,9 +22730,11 @@ is separate from validation, and `allowed-pattern` does not affect how the input
"10de:1244": "NVIDIA GeForce GTX 550 Ti",
"10de:1401": "NVIDIA GeForce GTX 960",
"8086": "Intel",
+ "8086:0412": "Intel Haswell Integrated",
"8086:041a": "Intel Xeon Integrated",
"8086:0a2e": "Intel Haswell Integrated",
"8086:0d26": "Intel Crystal Well Integrated",
+ "8086:22b1": "Intel Braswell Integrated",
}
// For consistency, all aliases are displayed like:
@@ -20612,15 +22746,6 @@ is separate from validation, and `allowed-pattern` does not affect how the input
SwarmingBehaviors.BotListBehavior = {
properties: {
- // TODO(kjlubick): Add more of these things from state, as they
- // needed/useful/requested.
- DIMENSIONS: {
- type: Array,
- value: function(){
- return ["android_devices", "cores", "cpu", "device_type",
- "device_os", "gpu", "id", "os", "pool"];
- },
- },
DIMENSIONS_WITH_ALIASES: {
type: Array,
value: function(){
@@ -20630,6 +22755,8 @@ is separate from validation, and `allowed-pattern` does not affect how the input
BOT_PROPERTIES: {
type: Array,
value: function() {
+ // TODO(kjlubick): Add more of these things from state, as they
+ // needed/useful/requested.
return ["disk_space", "task", "status"];
}
},
@@ -20660,6 +22787,9 @@ is separate from validation, and `allowed-pattern` does not affect how the input
var o = d[key];
o.serial = key;
o.okay = (o.state === AVAILABLE);
+ // It is easier to assume all devices on a bot are of the same type
+ // than to pick through the (incomplete) device state and find it.
+ o.device_type = this._attribute(bot, "device_type")[0];
devices.push(o);
}
return devices;
@@ -20667,12 +22797,7 @@ is separate from validation, and `allowed-pattern` does not affect how the input
// _deviceType returns the codename of a given Android device.
_deviceType: function(device) {
- if (!device || !device.build) {
- return UNKNOWN;
- }
- var t = device.build["build.product"] || device.build["product.board"] ||
- device.build["product.device"] || UNKNOWN;
- return t.toLowerCase();
+ return device.device_type.toLowerCase();
},
// _dimension returns the given dimension of a bot. If it is defined, it
@@ -20824,15 +22949,27 @@ is separate from validation, and `allowed-pattern` does not affect how the input
}
</style>
+ <url-param name="filters" value="{{_filters}}" default_values="[]" multi="">
+ </url-param>
+ <url-param name="columns" value="{{columns}}" default_values="[&quot;id&quot;,&quot;os&quot;,&quot;task&quot;,&quot;status&quot;]" multi="">
+ </url-param>
+ <url-param name="query" value="{{_query}}">
+ </url-param>
+ <url-param name="verbose" value="{{verbose}}">
+ </url-param>
+ <url-param name="limit" default_value="200" value="{{limit}}">
+ </url-param>
+
<div class="container horizontal layout">
<div class="narrow-down-selector">
<div>
- <paper-input id="filter" label="Search columns and filters" placeholder="gpu nvidia" value="{{_query}}"></paper-input>
+ <paper-input id="filter" label="Search columns and filters" placeholder="gpu nvidia" value="{{_query}}">
+ </paper-input>
</div>
- <div class="selector side-by-side">
+ <div class="selector side-by-side" title="This shows all bot dimension names and other interesting bot properties. Mark the check box to add as a column. Select the row to see filter options.">
<iron-selector attr-for-selected="label" selected="{{_primarySelected}}">
<template is="dom-repeat" items="[[_primaryItems]]" as="item">
<div class="selectable item horizontal layout" label="[[item]]">
@@ -20846,7 +22983,7 @@ is separate from validation, and `allowed-pattern` does not affect how the input
</iron-selector>
</div>
- <div class="selector side-by-side">
+ <div class="selector side-by-side" title="These are all options (if any) that the bot list can be filtered on.">
<template is="dom-repeat" id="secondaryList" items="[[_secondaryItems]]" as="item">
<div class="item horizontal layout" label="[[item]]">
@@ -20858,7 +22995,8 @@ is separate from validation, and `allowed-pattern` does not affect how the input
</template>
</div>
- <div class="selector side-by-side">
+ <div class="selector side-by-side" title="These filters are AND'd together and applied to all bots in
+the fleet.">
<template is="dom-repeat" items="[[_filters]]" as="fil">
<div class="item horizontal layout" label="[[fil]]">
<span>[[fil]]</span>
@@ -20869,7 +23007,11 @@ is separate from validation, and `allowed-pattern` does not affect how the input
</template>
</div>
+ <div class="side-by-side">
<paper-checkbox checked="{{verbose}}">Verbose Entries</paper-checkbox>
+ <paper-input id="limit" label="Limit Results" auto-validate="" min="0" max="1000" pattern="[0-9]+" value="{{limit}}">
+ </paper-input>
+ </div>
</div>
</div>
@@ -20881,20 +23023,13 @@ is separate from validation, and `allowed-pattern` does not affect how the input
// filterMap is a map of primary -> function. The function returns a
// boolean "does the bot (first arg) match the second argument". These
// functions will have "this" be the botlist, and will have access to all
- // functions defined in bot-list and bot-list-shared.
+ // functions defined in bot-list and bot-list-shared. If there is not
+ // one provided, a default will be used, see _makeFilter().
var filterMap = {
android_devices: function(bot, num) {
var o = this._attribute(bot, "android_devices", "0");
return o.indexOf(num) !== -1;
},
- cores: function(bot, cores) {
- var o = this._attribute(bot, "cores");
- return o.indexOf(cores) !== -1;
- },
- cpu: function(bot, cpu) {
- var o = this._attribute(bot, "cpu");
- return o.indexOf(cpu) !== -1;
- },
device_os: function(bot, os) {
var o = this._attribute(bot, "device_os", "none");
return o.indexOf(os) !== -1;
@@ -20913,21 +23048,13 @@ is separate from validation, and `allowed-pattern` does not affect how the input
id: function(bot, id) {
return true;
},
- os: function(bot, os) {
- var o = this._attribute(bot, "os");
- return o.indexOf(os) !== -1;
- },
- pool: function(bot, pool) {
- var o = this._attribute(bot, "pool");
- return o.indexOf(pool) !== -1;
- },
status: function(bot, status) {
if (status === "quarantined") {
return bot.quarantined;
} else if (status === "dead") {
return bot.is_dead;
} else {
- // Status must be "available".
+ // Status must be "alive".
return !bot.quarantined && !bot.is_dead;
}
},
@@ -20985,39 +23112,36 @@ is separate from validation, and `allowed-pattern` does not affect how the input
primary_arr: {
type: Array,
},
+ dimensions: {
+ type: Array,
+ },
// output
columns: {
type: Array,
- value: function() {
- // TODO(kjlubick) back these up to URL params and load them from
- // there on reload.
- return ["id","os","task","status"];
- },
- notify: true,
- },
- dimensions: {
- type: Array,
- computed: "_extractDimensions(DIMENSIONS.*,_filters.*)",
notify: true,
},
filter: {
- type: Object,
+ type: Function,
computed: "_makeFilter(_filters.*)",
notify: true,
},
+ query_params: {
+ type: Object,
+ computed: "_extractQueryParams(dimensions.*,_filters.*, limit)",
+ notify: true,
+ },
verbose: {
type: Boolean,
- value: false,
notify: true,
},
// private
_filters: {
type:Array,
- value: function() {
- return [];
- }
+ },
+ _limit: {
+ type: Number,
},
_primaryItems: {
type: Array,
@@ -21030,7 +23154,6 @@ is separate from validation, and `allowed-pattern` does not affect how the input
// query is treated as a space separated list.
_query: {
type:String,
- value: "",
},
_secondaryItems: {
type: Array,
@@ -21106,8 +23229,7 @@ is separate from validation, and `allowed-pattern` does not affect how the input
},
_makeFilter: function() {
- // The filters belonging to the same primary key will be or'd together.
- // Those groups will be and'd together.
+ // All filters will be AND'd together.
// filterGroups will be a map of primary (i.e. column) -> array of
// options that should be filtered to.
// e.g. "os" -> ["Windows", "Linux"]
@@ -21129,13 +23251,17 @@ is separate from validation, and `allowed-pattern` does not affect how the input
for (primary in filterGroups){
var params = filterGroups[primary];
var filter = filterMap[primary];
- var groupResult = false;
+ if (!filter) {
+ filter = function(bot, c) {
+ var o = this._attribute(bot, primary);
+ return o.indexOf(c) !== -1;
+ }.bind(this);
+ }
if (filter) {
params.forEach(function(param){
- groupResult = groupResult || filter.bind(this)(bot,param);
+ retVal = retVal && filter.bind(this)(bot,param);
}.bind(this));
}
- retVal = retVal && groupResult;
}
return retVal;
}
@@ -21226,17 +23352,40 @@ is separate from validation, and `allowed-pattern` does not affect how the input
return item.substring(match.idx + match.part.length);
},
- _extractDimensions: function() {
- var dims = []
+ _extractQueryParams: function() {
+ var params = {};
+ var dims = [];
this._filters.forEach(function(f) {
var split = f.split(FILTER_SEP, 1)
var col = split[0];
- if (this.DIMENSIONS.indexOf(col) !== -1) {
+ if (this.dimensions.indexOf(col) !== -1) {
var rest = f.substring(col.length + FILTER_SEP.length);
dims.push(col + FILTER_SEP + this._unalias(rest))
- };
+ } else if (col === "status") {
+ var rest = f.substring(col.length + FILTER_SEP.length);
+ if (rest === "alive") {
+ params["is_dead"] = "FALSE";
+ params["quarantined"] = "FALSE";
+ } else if (rest === "quarantined") {
+ params["quarantined"] = "TRUE";
+ } else if (rest === "dead") {
+ params["is_dead"] = "TRUE";
+ }
+ }
}.bind(this));
- return dims;
+ params["dimensions"] = dims;
+ var lim = Math.floor(this.limit)
+ if (Number.isInteger(lim)) {
+ // Clamp the limit
+ lim = Math.max(lim, 1);
+ lim = Math.min(1000, lim);
+ params["limit"] = lim;
+ // not !-- because limit could be "900"
+ if (this.limit != lim) {
+ this.set("limit", lim);
+ }
+ }
+ return params;
}
});
@@ -21244,7 +23393,7 @@ is separate from validation, and `allowed-pattern` does not affect how the input
</script>
</dom-module><dom-module id="bot-list-data" assetpath="/res/imp/botlist/">
<template>
- <iron-ajax id="botlist" url="/_ah/api/swarming/v1/bots/list" headers="[[auth_headers]]" params="[[_botlistParams(dimensions.*)]]" handle-as="json" last-response="{{_list}}" loading="{{_busy1}}">
+ <iron-ajax id="botlist" url="/_ah/api/swarming/v1/bots/list" headers="[[auth_headers]]" params="[[query_params]]" handle-as="json" last-response="{{_list}}" loading="{{_busy1}}">
</iron-ajax>
<iron-ajax id="dimensions" url="/_ah/api/swarming/v1/bots/dimensions" headers="[[auth_headers]]" handle-as="json" last-response="{{_dimensions}}" loading="{{_busy2}}">
@@ -21255,6 +23404,7 @@ is separate from validation, and `allowed-pattern` does not affect how the input
</template>
<script>
(function(){
+ var BLACKLIST_DIMENSIONS = ["quarantined", "error"];
Polymer({
is: 'bot-list-data',
@@ -21267,8 +23417,8 @@ is separate from validation, and `allowed-pattern` does not affect how the input
type: Object,
observer: "signIn",
},
- dimensions: {
- type: Array,
+ query_params: {
+ type: Object,
},
//outputs
@@ -21282,6 +23432,11 @@ is separate from validation, and `allowed-pattern` does not affect how the input
computed: "_or(_busy1,_busy2,_busy3)",
notify: true,
},
+ dimensions: {
+ type: Array,
+ computed: "_makeArray(_dimensions)",
+ notify: true,
+ },
fleet: {
type: Object,
computed: "_fleet(_count)",
@@ -21294,8 +23449,8 @@ is separate from validation, and `allowed-pattern` does not affect how the input
},
primary_arr: {
type: Array,
- // DIMENSIONS and BOT_PROPERTIES are inherited from BotListBehavior
- computed: "_primaryArr(DIMENSIONS, BOT_PROPERTIES)",
+ //BOT_PROPERTIES is inherited from BotListBehavior
+ computed: "_primaryArr(dimensions, BOT_PROPERTIES)",
notify: true,
},
@@ -21312,20 +23467,15 @@ is separate from validation, and `allowed-pattern` does not affect how the input
},
signIn: function(){
+ // Auto on iron-ajax means to automatically re-make the request if
+ // the url or the query params change. Auto does not trigger if the
+ // [auth] headers change, so we wait until the user is signed in
+ // before making any requests.
this.$.botlist.auto = true;
this.$.dimensions.auto = true;
this.$.fleet.auto = true;
},
- _botlistParams: function() {
- if (!this.dimensions) {
- return {};
- }
- return {
- dimensions: this.dimensions,
- };
- },
-
_bots: function(){
if (!this._list || !this._list.items) {
return [];
@@ -21360,23 +23510,37 @@ is separate from validation, and `allowed-pattern` does not affect how the input
return {};
}
return {
- alive: this._count.count || -1,
+ all: this._count.count || -1,
+ alive: (this._count.count - this._count.dead) || -1,
busy: this._count.busy || -1,
- idle: this._count.count && this._count.busy &&
- this._count.count - this._count.busy,
+ idle: (this._count.count - this._count.busy) || -1,
dead: this._count.dead || -1,
quarantined: this._count.quarantined || -1,
}
},
+ _makeArray: function(dimObj) {
+ if (!dimObj || !dimObj.bots_dimensions) {
+ return [];
+ }
+ var dims = [];
+ dimObj.bots_dimensions.forEach(function(d){
+ if (BLACKLIST_DIMENSIONS.indexOf(d.key) === -1) {
+ dims.push(d.key);
+ }
+ });
+ dims.sort();
+ return dims;
+ },
+
_primaryArr: function(dimensions, properties) {
return dimensions.concat(properties);
},
_primaryMap: function(dimensions){
- // map will keep track of dimensions that we have seen at least once.
- // This will then basically get turned into an array to be used for
- // filtering.
+ // pMap will have a list of columns to available values (primary key
+ // to secondary values). This includes bot dimensions, but also
+ // includes state like disk_space, quarantined, busy, etc.
dimensions = dimensions.bots_dimensions;
var pMap = {};
@@ -21421,7 +23585,7 @@ is separate from validation, and `allowed-pattern` does not affect how the input
// Create custom filter options
pMap["disk_space"] = [];
pMap["task"] = ["busy", "idle"];
- pMap["status"] = ["available", "dead", "quarantined"];
+ pMap["status"] = ["alive", "dead", "quarantined"];
// No need to sort any of this, bot-filters sorts secondary items
// automatically, especially when the user types a query.
@@ -21453,26 +23617,41 @@ is separate from validation, and `allowed-pattern` does not affect how the input
</style>
<div class="header">Fleet</div>
-
<table>
<tbody><tr>
- <td class="right"><a href="/newui/botlist?alive">Alive</a>:</td>
+ <td class="right">
+ <a href$="[[_makeURL('','',columns.*,filtered_bots.*,sort,verbose)]]">All</a>:
+ </td>
+ <td class="left">[[fleet.all]]</td>
+ </tr>
+ <tr>
+ <td class="right">
+ <a href$="[[_makeURL('alive','',columns.*,filtered_bots.*,sort,verbose)]]">Alive</a>:
+ </td>
<td class="left">[[fleet.alive]]</td>
</tr>
<tr>
- <td class="right"><a href="/newui/botlist?busy">Busy</a>:</td>
+ <td class="right">
+ <a href$="[[_makeURL('busy','',columns.*,filtered_bots.*,sort,verbose)]]">Busy</a>:
+ </td>
<td class="left">[[fleet.busy]]</td>
</tr>
<tr>
- <td class="right"><a href="/newui/botlist?idle">Idle</a>:</td>
+ <td class="right">
+ <a href$="[[_makeURL('idle','',columns.*,filtered_bots.*,sort,verbose)]]">Idle</a>:
+ </td>
<td class="left">[[fleet.idle]]</td>
</tr>
<tr>
- <td class="right"><a href="/newui/botlist?dead">Dead</a>:</td>
+ <td class="right">
+ <a href$="[[_makeURL('dead','',columns.*,filtered_bots.*,sort,verbose)]]">Dead</a>:
+ </td>
<td class="left">[[fleet.dead]]</td>
</tr>
<tr>
- <td class="right"><a href="/newui/botlist?quaren">Quarantined</a>:</td>
+ <td class="right">
+ <a href$="[[_makeURL('quarantined','',columns.*,filtered_bots.*,sort,verbose)]]">Quarantined</a>:
+ </td>
<td class="left">[[fleet.quarantined]]</td>
</tr>
</tbody></table>
@@ -21480,23 +23659,39 @@ is separate from validation, and `allowed-pattern` does not affect how the input
<div class="header">Displayed</div>
<table>
<tbody><tr>
- <td class="right"><a href="/newui/botlist?alive2">Alive</a>:</td>
+ <td class="right">
+ All:
+ </td>
+ <td class="left">[[_currently_showing.all]]</td>
+ </tr>
+ <tr>
+ <td class="right">
+ <a href$="[[_makeURL('alive','true',columns.*,filtered_bots.*,sort,verbose)]]">Alive</a>:
+ </td>
<td class="left">[[_currently_showing.alive]]</td>
</tr>
<tr>
- <td class="right"><a href="/newui/botlist?busy2">Busy</a>:</td>
+ <td class="right">
+ <a href$="[[_makeURL('busy','true',columns.*,filtered_bots.*,sort,verbose)]]">Busy</a>:
+ </td>
<td class="left">[[_currently_showing.busy]]</td>
</tr>
<tr>
- <td class="right"><a href="/newui/botlist?idle2">Idle</a>:</td>
+ <td class="right">
+ <a href$="[[_makeURL('idle','true',columns.*,filtered_bots.*,sort,verbose)]]">Idle</a>:
+ </td>
<td class="left">[[_currently_showing.idle]]</td>
</tr>
<tr>
- <td class="right"><a href="/newui/botlist?dead2">Dead</a>:</td>
+ <td class="right">
+ <a href$="[[_makeURL('dead','true',columns.*,filtered_bots.*,sort,verbose)]]">Dead</a>:
+ </td>
<td class="left">[[_currently_showing.dead]]</td>
</tr>
<tr>
- <td class="right"><a href="/newui/botlist?quaren2">Quarantined</a>:</td>
+ <td class="right">
+ <a href$="[[_makeURL('quarantined','true',columns.*,filtered_bots.*,sort,verbose)]]">Quarantined</a>:
+ </td>
<td class="left">[[_currently_showing.quarantined]]</td>
</tr>
</tbody></table>
@@ -21509,17 +23704,27 @@ is separate from validation, and `allowed-pattern` does not affect how the input
behaviors: [SwarmingBehaviors.BotListBehavior],
properties: {
+ columns: {
+ type: Array,
+ },
filtered_bots: {
type: Array,
},
fleet: {
type: Object,
},
+ sort: {
+ type: String,
+ },
+ verbose: {
+ type: Boolean,
+ },
_currently_showing: {
type: Object,
value: function() {
return {
+ all: -1,
alive: -1,
busy: -1,
idle: -1,
@@ -21534,8 +23739,42 @@ is separate from validation, and `allowed-pattern` does not affect how the input
// property observers
observers: ["_recount(filtered_bots.*)"],
+ _getFilterStr: function(filter) {
+ if (!filter) {
+ return "";
+ }
+ if (filter === "alive" || filter === "dead" ||
+ filter === "quarantined") {
+ return "status:" + filter;
+ } else {
+ return "task:" + filter;
+ }
+ },
+
+ _makeURL: function(filter, preserveOthers) {
+ if (preserveOthers) {
+ var fstr = encodeURIComponent(this._getFilterStr(filter));
+ if (window.location.href.indexOf(fstr) === -1) {
+ return window.location.href + "&filters=" + fstr;
+ }
+ // The filter is already on the list.
+ return undefined;
+ }
+ var params = {
+ sort: [this.sort],
+ columns: this.columns,
+ verbose: [this.verbose],
+ }
+ if (filter) {
+ params["filters"] = [this._getFilterStr(filter)];
+ }
+
+ return window.location.href.split('?')[0] + '?' + sk.query.fromParamSet(params);
+ },
+
_recount: function() {
var curr = {
+ all: 0,
alive: 0,
busy: 0,
idle: 0,
@@ -21559,6 +23798,7 @@ is separate from validation, and `allowed-pattern` does not affect how the input
} else {
curr.alive++;
}
+ curr.all++;
}.bind(this));
this.set("_currently_showing", curr);
}
@@ -21607,6 +23847,9 @@ is separate from validation, and `allowed-pattern` does not affect how the input
}
</style>
+ <url-param name="sort" value="{{_sortstr}}" default_value="id:asc">
+ </url-param>
+
<swarming-app client_id="[[client_id]]" auth_headers="{{_auth_headers}}" signed_in="{{_signed_in}}" busy="[[_busy]]" name="Swarming Bot List">
<h2 hidden$="[[_signed_in]]">You must sign in to see anything useful.</h2>
@@ -21615,15 +23858,15 @@ is separate from validation, and `allowed-pattern` does not affect how the input
<div class="horizontal layout">
- <bot-filters primary_map="[[_primary_map]]" primary_arr="[[_primary_arr]]" columns="{{_columns}}" dimensions="{{_dimensions}}" filter="{{_filter}}" verbose="{{_verbose}}">
+ <bot-filters dimensions="[[_dimensions]]" primary_map="[[_primary_map]]" primary_arr="[[_primary_arr]]" columns="{{_columns}}" query_params="{{_query_params}}" filter="{{_filter}}" verbose="{{_verbose}}">
</bot-filters>
- <bot-list-summary fleet="[[_fleet]]" filtered_bots="[[_filteredSortedBots]]">
+ <bot-list-summary columns="[[_columns]]" fleet="[[_fleet]]" filtered_bots="[[_filteredSortedBots]]" sort="[[_sortstr]]" verbose="[[_verbose]]">
</bot-list-summary>
</div>
- <bot-list-data auth_headers="[[_auth_headers]]" dimensions="[[_dimensions]]" bots="{{_bots}}" busy="{{_busy}}" fleet="{{_fleet}}" primary_map="{{_primary_map}}" primary_arr="{{_primary_arr}}">
+ <bot-list-data auth_headers="[[_auth_headers]]" query_params="[[_query_params]]" bots="{{_bots}}" busy="{{_busy}}" dimensions="{{_dimensions}}" fleet="{{_fleet}}" primary_map="{{_primary_map}}" primary_arr="{{_primary_arr}}">
</bot-list-data>
<table class="bot-list">
@@ -21700,6 +23943,7 @@ is separate from validation, and `allowed-pattern` does not affect how the input
"android_devices": "Android Devices",
"cores": "Cores",
"cpu": "CPU",
+ "device": "Non-android Device",
"device_os": "Device OS",
"device_type": "Device Type",
"disk_space": "Free Space (MB)",
@@ -21707,11 +23951,13 @@ is separate from validation, and `allowed-pattern` does not affect how the input
"os": "OS",
"pool": "Pool",
"status": "Status",
+ "xcode_version": "XCode Version",
};
// This maps column name to a function that will return the content for a
// given bot. These functions are bound to this element, and have access
- // to all functions defined here and in bot-list-shared.
+ // to all functions defined here and in bot-list-shared. If a column
+ // is not listed here, a sane default will be used (see _column()).
var columnMap = {
android_devices: function(bot) {
var devs = this._attribute(bot, "android_devices", "0");
@@ -21721,38 +23967,19 @@ is separate from validation, and `allowed-pattern` does not affect how the input
// max() works on strings as long as they can be coerced to Number.
return Math.max(...devs) + " devices available";
},
- cores: function(bot){
- var cores = this._attribute(bot, "cores");
- if (this._verbose){
- return cores.join(" | ");
- }
- return cores[0];
- },
- cpu: function(bot){
- var cpus = this._attribute(bot, "cpu");
- if (this._verbose){
- return cpus.join(" | ");
- }
- return cpus[0];
- },
- device_os: function(bot){
- var os = this._attribute(bot, "device_os", "none");
- if (this._verbose) {
- return os.join(" | ");
- }
- return os[0];
- },
- device_type: function(bot){
+ device_type: function(bot) {
var dt = this._attribute(bot, "device_type", "none");
- if (this._verbose) {
- return dt.join(" | ");
+ dt = dt[0];
+ var alias = this._androidAlias(dt);
+ if (alias === "unknown") {
+ return dt;
}
- return dt[0];
+ return this._applyAlias(dt, alias);
},
disk_space: function(bot) {
var aliased = [];
bot.disks.forEach(function(disk){
- var alias = swarming.humanBytes(disk.mb, swarming.MB);
+ var alias = sk.human.bytes(disk.mb, swarming.MB);
aliased.push(this._applyAlias(disk.mb, disk.id + " "+ alias));
}.bind(this));
if (this._verbose) {
@@ -21788,13 +24015,6 @@ is separate from validation, and `allowed-pattern` does not affect how the input
id: function(bot) {
return bot.bot_id;
},
- os: function(bot) {
- var os = this._attribute(bot, "os");
- if (this._verbose){
- return os.join(" | ");
- }
- return os[0];
- },
pool: function(bot) {
var pool = this._attribute(bot, "pool");
return pool.join(" | ");
@@ -21803,7 +24023,7 @@ is separate from validation, and `allowed-pattern` does not affect how the input
// If a bot is both dead and quarantined, show the deadness over the
// quarentinedness.
if (bot.is_dead) {
- return "Dead. Last seen " + swarming.diffDate(bot.last_seen_ts) +
+ return "Dead. Last seen " + sk.human.diffDate(bot.last_seen_ts) +
" ago";
}
if (bot.quarantined) {
@@ -21840,7 +24060,7 @@ is separate from validation, and `allowed-pattern` does not affect how the input
// specialSort defines any custom sorting rules. By default, a
// naturalCompare of the column content is done.
var specialSort = {
- device_type: function(dir, botA, botB) {
+ android_devices: function(dir, botA, botB) {
// We sort on the number of attached devices. Note that this
// may not be the same as android_devices, because _devices().length
// counts all devices plugged into the bot, whereas android_devices
@@ -21895,12 +24115,7 @@ is separate from validation, and `allowed-pattern` does not affect how the input
// _sort is an Object {name:String, direction:String}.
_sort: {
type: Object,
- value: function() {
- return {
- name: "id",
- direction: "asc",
- };
- }
+ computed: "_makeObject(_sortstr)",
},
_verbose: {
@@ -21925,7 +24140,17 @@ is separate from validation, and `allowed-pattern` does not affect how the input
_column: function(col, bot) {
- return columnMap[col].bind(this)(bot);
+ var f = columnMap[col];
+ if (!f) {
+ f = function(bot) {
+ var c = this._attribute(bot, col, "none");
+ if (this._verbose) {
+ return c.join(" | ");
+ }
+ return c[0];
+ }
+ }
+ return f.bind(this)(bot);
},
_androidAliasDevice: function(device) {
@@ -21964,13 +24189,28 @@ is separate from validation, and `allowed-pattern` does not affect how the input
},
_header: function(col){
- return headerMap[col];
+ return headerMap[col] || col;
},
_hide: function(col) {
return this._columns.indexOf(col) === -1;
},
+ _makeObject: function(sortstr){
+ if (!sortstr) {
+ return undefined;
+ }
+ var pieces = sortstr.split(":");
+ if (pieces.length != 2) {
+ // fail safe
+ return {name: "id", direction:"desc"};
+ }
+ return {
+ name: pieces[0],
+ direction: pieces[1],
+ }
+ },
+
_reRender: function(filter, sort) {
this.$.bot_table.render();
},
@@ -22000,8 +24240,8 @@ is separate from validation, and `allowed-pattern` does not affect how the input
if (!(e && e.detail && e.detail.name)) {
return;
}
- // should trigger __filterAndSort
- this.set("_sort", e.detail);
+ // should trigger the computation of _sort and __filterAndSort
+ this.set("_sortstr", e.detail.name +":"+e.detail.direction);
},
// _stripSpecial removes the special columns and sorts the remaining
« no previous file with comments | « appengine/swarming/elements/Makefile ('k') | appengine/swarming/elements/build/js/common.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698