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

Side by Side 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: Make limit a visible option 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 unified diff | Download patch
OLDNEW
1 <!DOCTYPE html><html><head><!-- 1 <!DOCTYPE html><html><head><!--
2 @license 2 @license
3 Copyright (c) 2016 The Polymer Project Authors. All rights reserved. 3 Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
4 This code may only be used under the BSD style license found at http://polymer.g ithub.io/LICENSE.txt 4 This code may only be used under the BSD style license found at http://polymer.g ithub.io/LICENSE.txt
5 The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 5 The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
6 The complete set of contributors may be found at http://polymer.github.io/CONTRI BUTORS.txt 6 The complete set of contributors may be found at http://polymer.github.io/CONTRI BUTORS.txt
7 Code distributed by Google as part of the polymer project is also 7 Code distributed by Google as part of the polymer project is also
8 subject to an additional IP rights grant found at http://polymer.github.io/PATEN TS.txt 8 subject to an additional IP rights grant found at http://polymer.github.io/PATEN TS.txt
9 --><!-- 9 --><!--
10 @license 10 @license
(...skipping 15620 matching lines...) Expand 10 before | Expand all | Expand 10 after
15631 // added before "os", for example. Additionally, this makes sure that 15631 // added before "os", for example. Additionally, this makes sure that
15632 // only one sort-toggle is active at a given time. 15632 // only one sort-toggle is active at a given time.
15633 if (this.current && this.current.name === this.name) { 15633 if (this.current && this.current.name === this.name) {
15634 this.set("direction", this.current.direction); 15634 this.set("direction", this.current.direction);
15635 } else { 15635 } else {
15636 this.set("direction", ""); 15636 this.set("direction", "");
15637 } 15637 }
15638 }, 15638 },
15639 }); 15639 });
15640 </script> 15640 </script>
15641 </dom-module><script> 15641 </dom-module>
15642
15643 <dom-module id="iron-a11y-announcer" assetpath="/res/imp/bower_components/iron-a 11y-announcer/">
15644 <template>
15645 <style>
15646 :host {
15647 display: inline-block;
15648 position: fixed;
15649 clip: rect(0px,0px,0px,0px);
15650 }
15651 </style>
15652 <div aria-live$="[[mode]]">[[_text]]</div>
15653 </template>
15654
15655 <script>
15656
15657 (function() {
15658 'use strict';
15659
15660 Polymer.IronA11yAnnouncer = Polymer({
15661 is: 'iron-a11y-announcer',
15662
15663 properties: {
15664
15665 /**
15666 * The value of mode is used to set the `aria-live` attribute
15667 * for the element that will be announced. Valid values are: `off`,
15668 * `polite` and `assertive`.
15669 */
15670 mode: {
15671 type: String,
15672 value: 'polite'
15673 },
15674
15675 _text: {
15676 type: String,
15677 value: ''
15678 }
15679 },
15680
15681 created: function() {
15682 if (!Polymer.IronA11yAnnouncer.instance) {
15683 Polymer.IronA11yAnnouncer.instance = this;
15684 }
15685
15686 document.body.addEventListener('iron-announce', this._onIronAnnounce.b ind(this));
15687 },
15688
15689 /**
15690 * Cause a text string to be announced by screen readers.
15691 *
15692 * @param {string} text The text that should be announced.
15693 */
15694 announce: function(text) {
15695 this._text = '';
15696 this.async(function() {
15697 this._text = text;
15698 }, 100);
15699 },
15700
15701 _onIronAnnounce: function(event) {
15702 if (event.detail && event.detail.text) {
15703 this.announce(event.detail.text);
15704 }
15705 }
15706 });
15707
15708 Polymer.IronA11yAnnouncer.instance = null;
15709
15710 Polymer.IronA11yAnnouncer.requestAvailability = function() {
15711 if (!Polymer.IronA11yAnnouncer.instance) {
15712 Polymer.IronA11yAnnouncer.instance = document.createElement('iron-a11y -announcer');
15713 }
15714
15715 document.body.appendChild(Polymer.IronA11yAnnouncer.instance);
15716 };
15717 })();
15718
15719 </script>
15720 </dom-module>
15721 <script>
15722 /**
15723 `Polymer.IronFitBehavior` fits an element in another element using `max-height` and `max-width`, and
15724 optionally centers it in the window or another element.
15725
15726 The element will only be sized and/or positioned if it has not already been size d and/or positioned
15727 by CSS.
15728
15729 CSS properties | Action
15730 -----------------------------|-------------------------------------------
15731 `position` set | Element is not centered horizontally or verticall y
15732 `top` or `bottom` set | Element is not vertically centered
15733 `left` or `right` set | Element is not horizontally centered
15734 `max-height` set | Element respects `max-height`
15735 `max-width` set | Element respects `max-width`
15736
15737 `Polymer.IronFitBehavior` can position an element into another element using
15738 `verticalAlign` and `horizontalAlign`. This will override the element's css posi tion.
15739
15740 <div class="container">
15741 <iron-fit-impl vertical-align="top" horizontal-align="auto">
15742 Positioned into the container
15743 </iron-fit-impl>
15744 </div>
15745
15746 Use `noOverlap` to position the element around another element without overlappi ng it.
15747
15748 <div class="container">
15749 <iron-fit-impl no-overlap vertical-align="auto" horizontal-align="auto">
15750 Positioned around the container
15751 </iron-fit-impl>
15752 </div>
15753
15754 @demo demo/index.html
15755 @polymerBehavior
15756 */
15757
15758 Polymer.IronFitBehavior = {
15759
15760 properties: {
15761
15762 /**
15763 * The element that will receive a `max-height`/`width`. By default it is the same as `this`,
15764 * but it can be set to a child element. This is useful, for example, for implementing a
15765 * scrolling region inside the element.
15766 * @type {!Element}
15767 */
15768 sizingTarget: {
15769 type: Object,
15770 value: function() {
15771 return this;
15772 }
15773 },
15774
15775 /**
15776 * The element to fit `this` into.
15777 */
15778 fitInto: {
15779 type: Object,
15780 value: window
15781 },
15782
15783 /**
15784 * Will position the element around the positionTarget without overlapping it.
15785 */
15786 noOverlap: {
15787 type: Boolean
15788 },
15789
15790 /**
15791 * The element that should be used to position the element. If not set, it will
15792 * default to the parent node.
15793 * @type {!Element}
15794 */
15795 positionTarget: {
15796 type: Element
15797 },
15798
15799 /**
15800 * The orientation against which to align the element horizontally
15801 * relative to the `positionTarget`. Possible values are "left", "right", "auto".
15802 */
15803 horizontalAlign: {
15804 type: String
15805 },
15806
15807 /**
15808 * The orientation against which to align the element vertically
15809 * relative to the `positionTarget`. Possible values are "top", "bottom", "auto".
15810 */
15811 verticalAlign: {
15812 type: String
15813 },
15814
15815 /**
15816 * If true, it will use `horizontalAlign` and `verticalAlign` values as pr eferred alignment
15817 * and if there's not enough space, it will pick the values which minimize the cropping.
15818 */
15819 dynamicAlign: {
15820 type: Boolean
15821 },
15822
15823 /**
15824 * The same as setting margin-left and margin-right css properties.
15825 * @deprecated
15826 */
15827 horizontalOffset: {
15828 type: Number,
15829 value: 0,
15830 notify: true
15831 },
15832
15833 /**
15834 * The same as setting margin-top and margin-bottom css properties.
15835 * @deprecated
15836 */
15837 verticalOffset: {
15838 type: Number,
15839 value: 0,
15840 notify: true
15841 },
15842
15843 /**
15844 * Set to true to auto-fit on attach.
15845 */
15846 autoFitOnAttach: {
15847 type: Boolean,
15848 value: false
15849 },
15850
15851 /** @type {?Object} */
15852 _fitInfo: {
15853 type: Object
15854 }
15855 },
15856
15857 get _fitWidth() {
15858 var fitWidth;
15859 if (this.fitInto === window) {
15860 fitWidth = this.fitInto.innerWidth;
15861 } else {
15862 fitWidth = this.fitInto.getBoundingClientRect().width;
15863 }
15864 return fitWidth;
15865 },
15866
15867 get _fitHeight() {
15868 var fitHeight;
15869 if (this.fitInto === window) {
15870 fitHeight = this.fitInto.innerHeight;
15871 } else {
15872 fitHeight = this.fitInto.getBoundingClientRect().height;
15873 }
15874 return fitHeight;
15875 },
15876
15877 get _fitLeft() {
15878 var fitLeft;
15879 if (this.fitInto === window) {
15880 fitLeft = 0;
15881 } else {
15882 fitLeft = this.fitInto.getBoundingClientRect().left;
15883 }
15884 return fitLeft;
15885 },
15886
15887 get _fitTop() {
15888 var fitTop;
15889 if (this.fitInto === window) {
15890 fitTop = 0;
15891 } else {
15892 fitTop = this.fitInto.getBoundingClientRect().top;
15893 }
15894 return fitTop;
15895 },
15896
15897 /**
15898 * The element that should be used to position the element,
15899 * if no position target is configured.
15900 */
15901 get _defaultPositionTarget() {
15902 var parent = Polymer.dom(this).parentNode;
15903
15904 if (parent && parent.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
15905 parent = parent.host;
15906 }
15907
15908 return parent;
15909 },
15910
15911 /**
15912 * The horizontal align value, accounting for the RTL/LTR text direction.
15913 */
15914 get _localeHorizontalAlign() {
15915 if (this._isRTL) {
15916 // In RTL, "left" becomes "right".
15917 if (this.horizontalAlign === 'right') {
15918 return 'left';
15919 }
15920 if (this.horizontalAlign === 'left') {
15921 return 'right';
15922 }
15923 }
15924 return this.horizontalAlign;
15925 },
15926
15927 attached: function() {
15928 // Memoize this to avoid expensive calculations & relayouts.
15929 this._isRTL = window.getComputedStyle(this).direction == 'rtl';
15930 this.positionTarget = this.positionTarget || this._defaultPositionTarget;
15931 if (this.autoFitOnAttach) {
15932 if (window.getComputedStyle(this).display === 'none') {
15933 setTimeout(function() {
15934 this.fit();
15935 }.bind(this));
15936 } else {
15937 this.fit();
15938 }
15939 }
15940 },
15941
15942 /**
15943 * Positions and fits the element into the `fitInto` element.
15944 */
15945 fit: function() {
15946 this.position();
15947 this.constrain();
15948 this.center();
15949 },
15950
15951 /**
15952 * Memoize information needed to position and size the target element.
15953 * @suppress {deprecated}
15954 */
15955 _discoverInfo: function() {
15956 if (this._fitInfo) {
15957 return;
15958 }
15959 var target = window.getComputedStyle(this);
15960 var sizer = window.getComputedStyle(this.sizingTarget);
15961
15962 this._fitInfo = {
15963 inlineStyle: {
15964 top: this.style.top || '',
15965 left: this.style.left || '',
15966 position: this.style.position || ''
15967 },
15968 sizerInlineStyle: {
15969 maxWidth: this.sizingTarget.style.maxWidth || '',
15970 maxHeight: this.sizingTarget.style.maxHeight || '',
15971 boxSizing: this.sizingTarget.style.boxSizing || ''
15972 },
15973 positionedBy: {
15974 vertically: target.top !== 'auto' ? 'top' : (target.bottom !== 'auto' ?
15975 'bottom' : null),
15976 horizontally: target.left !== 'auto' ? 'left' : (target.right !== 'aut o' ?
15977 'right' : null)
15978 },
15979 sizedBy: {
15980 height: sizer.maxHeight !== 'none',
15981 width: sizer.maxWidth !== 'none',
15982 minWidth: parseInt(sizer.minWidth, 10) || 0,
15983 minHeight: parseInt(sizer.minHeight, 10) || 0
15984 },
15985 margin: {
15986 top: parseInt(target.marginTop, 10) || 0,
15987 right: parseInt(target.marginRight, 10) || 0,
15988 bottom: parseInt(target.marginBottom, 10) || 0,
15989 left: parseInt(target.marginLeft, 10) || 0
15990 }
15991 };
15992
15993 // Support these properties until they are removed.
15994 if (this.verticalOffset) {
15995 this._fitInfo.margin.top = this._fitInfo.margin.bottom = this.verticalOf fset;
15996 this._fitInfo.inlineStyle.marginTop = this.style.marginTop || '';
15997 this._fitInfo.inlineStyle.marginBottom = this.style.marginBottom || '';
15998 this.style.marginTop = this.style.marginBottom = this.verticalOffset + ' px';
15999 }
16000 if (this.horizontalOffset) {
16001 this._fitInfo.margin.left = this._fitInfo.margin.right = this.horizontal Offset;
16002 this._fitInfo.inlineStyle.marginLeft = this.style.marginLeft || '';
16003 this._fitInfo.inlineStyle.marginRight = this.style.marginRight || '';
16004 this.style.marginLeft = this.style.marginRight = this.horizontalOffset + 'px';
16005 }
16006 },
16007
16008 /**
16009 * Resets the target element's position and size constraints, and clear
16010 * the memoized data.
16011 */
16012 resetFit: function() {
16013 var info = this._fitInfo || {};
16014 for (var property in info.sizerInlineStyle) {
16015 this.sizingTarget.style[property] = info.sizerInlineStyle[property];
16016 }
16017 for (var property in info.inlineStyle) {
16018 this.style[property] = info.inlineStyle[property];
16019 }
16020
16021 this._fitInfo = null;
16022 },
16023
16024 /**
16025 * Equivalent to calling `resetFit()` and `fit()`. Useful to call this after
16026 * the element or the `fitInto` element has been resized, or if any of the
16027 * positioning properties (e.g. `horizontalAlign, verticalAlign`) is updated .
16028 * It preserves the scroll position of the sizingTarget.
16029 */
16030 refit: function() {
16031 var scrollLeft = this.sizingTarget.scrollLeft;
16032 var scrollTop = this.sizingTarget.scrollTop;
16033 this.resetFit();
16034 this.fit();
16035 this.sizingTarget.scrollLeft = scrollLeft;
16036 this.sizingTarget.scrollTop = scrollTop;
16037 },
16038
16039 /**
16040 * Positions the element according to `horizontalAlign, verticalAlign`.
16041 */
16042 position: function() {
16043 if (!this.horizontalAlign && !this.verticalAlign) {
16044 // needs to be centered, and it is done after constrain.
16045 return;
16046 }
16047 this._discoverInfo();
16048
16049 this.style.position = 'fixed';
16050 // Need border-box for margin/padding.
16051 this.sizingTarget.style.boxSizing = 'border-box';
16052 // Set to 0, 0 in order to discover any offset caused by parent stacking c ontexts.
16053 this.style.left = '0px';
16054 this.style.top = '0px';
16055
16056 var rect = this.getBoundingClientRect();
16057 var positionRect = this.__getNormalizedRect(this.positionTarget);
16058 var fitRect = this.__getNormalizedRect(this.fitInto);
16059
16060 var margin = this._fitInfo.margin;
16061
16062 // Consider the margin as part of the size for position calculations.
16063 var size = {
16064 width: rect.width + margin.left + margin.right,
16065 height: rect.height + margin.top + margin.bottom
16066 };
16067
16068 var position = this.__getPosition(this._localeHorizontalAlign, this.vertic alAlign, size, positionRect, fitRect);
16069
16070 var left = position.left + margin.left;
16071 var top = position.top + margin.top;
16072
16073 // Use original size (without margin).
16074 var right = Math.min(fitRect.right - margin.right, left + rect.width);
16075 var bottom = Math.min(fitRect.bottom - margin.bottom, top + rect.height);
16076
16077 var minWidth = this._fitInfo.sizedBy.minWidth;
16078 var minHeight = this._fitInfo.sizedBy.minHeight;
16079 if (left < margin.left) {
16080 left = margin.left;
16081 if (right - left < minWidth) {
16082 left = right - minWidth;
16083 }
16084 }
16085 if (top < margin.top) {
16086 top = margin.top;
16087 if (bottom - top < minHeight) {
16088 top = bottom - minHeight;
16089 }
16090 }
16091
16092 this.sizingTarget.style.maxWidth = (right - left) + 'px';
16093 this.sizingTarget.style.maxHeight = (bottom - top) + 'px';
16094
16095 // Remove the offset caused by any stacking context.
16096 this.style.left = (left - rect.left) + 'px';
16097 this.style.top = (top - rect.top) + 'px';
16098 },
16099
16100 /**
16101 * Constrains the size of the element to `fitInto` by setting `max-height`
16102 * and/or `max-width`.
16103 */
16104 constrain: function() {
16105 if (this.horizontalAlign || this.verticalAlign) {
16106 return;
16107 }
16108 this._discoverInfo();
16109
16110 var info = this._fitInfo;
16111 // position at (0px, 0px) if not already positioned, so we can measure the natural size.
16112 if (!info.positionedBy.vertically) {
16113 this.style.position = 'fixed';
16114 this.style.top = '0px';
16115 }
16116 if (!info.positionedBy.horizontally) {
16117 this.style.position = 'fixed';
16118 this.style.left = '0px';
16119 }
16120
16121 // need border-box for margin/padding
16122 this.sizingTarget.style.boxSizing = 'border-box';
16123 // constrain the width and height if not already set
16124 var rect = this.getBoundingClientRect();
16125 if (!info.sizedBy.height) {
16126 this.__sizeDimension(rect, info.positionedBy.vertically, 'top', 'bottom' , 'Height');
16127 }
16128 if (!info.sizedBy.width) {
16129 this.__sizeDimension(rect, info.positionedBy.horizontally, 'left', 'righ t', 'Width');
16130 }
16131 },
16132
16133 /**
16134 * @protected
16135 * @deprecated
16136 */
16137 _sizeDimension: function(rect, positionedBy, start, end, extent) {
16138 this.__sizeDimension(rect, positionedBy, start, end, extent);
16139 },
16140
16141 /**
16142 * @private
16143 */
16144 __sizeDimension: function(rect, positionedBy, start, end, extent) {
16145 var info = this._fitInfo;
16146 var fitRect = this.__getNormalizedRect(this.fitInto);
16147 var max = extent === 'Width' ? fitRect.width : fitRect.height;
16148 var flip = (positionedBy === end);
16149 var offset = flip ? max - rect[end] : rect[start];
16150 var margin = info.margin[flip ? start : end];
16151 var offsetExtent = 'offset' + extent;
16152 var sizingOffset = this[offsetExtent] - this.sizingTarget[offsetExtent];
16153 this.sizingTarget.style['max' + extent] = (max - margin - offset - sizingO ffset) + 'px';
16154 },
16155
16156 /**
16157 * Centers horizontally and vertically if not already positioned. This also sets
16158 * `position:fixed`.
16159 */
16160 center: function() {
16161 if (this.horizontalAlign || this.verticalAlign) {
16162 return;
16163 }
16164 this._discoverInfo();
16165
16166 var positionedBy = this._fitInfo.positionedBy;
16167 if (positionedBy.vertically && positionedBy.horizontally) {
16168 // Already positioned.
16169 return;
16170 }
16171 // Need position:fixed to center
16172 this.style.position = 'fixed';
16173 // Take into account the offset caused by parents that create stacking
16174 // contexts (e.g. with transform: translate3d). Translate to 0,0 and
16175 // measure the bounding rect.
16176 if (!positionedBy.vertically) {
16177 this.style.top = '0px';
16178 }
16179 if (!positionedBy.horizontally) {
16180 this.style.left = '0px';
16181 }
16182 // It will take in consideration margins and transforms
16183 var rect = this.getBoundingClientRect();
16184 var fitRect = this.__getNormalizedRect(this.fitInto);
16185 if (!positionedBy.vertically) {
16186 var top = fitRect.top - rect.top + (fitRect.height - rect.height) / 2;
16187 this.style.top = top + 'px';
16188 }
16189 if (!positionedBy.horizontally) {
16190 var left = fitRect.left - rect.left + (fitRect.width - rect.width) / 2;
16191 this.style.left = left + 'px';
16192 }
16193 },
16194
16195 __getNormalizedRect: function(target) {
16196 if (target === document.documentElement || target === window) {
16197 return {
16198 top: 0,
16199 left: 0,
16200 width: window.innerWidth,
16201 height: window.innerHeight,
16202 right: window.innerWidth,
16203 bottom: window.innerHeight
16204 };
16205 }
16206 return target.getBoundingClientRect();
16207 },
16208
16209 __getCroppedArea: function(position, size, fitRect) {
16210 var verticalCrop = Math.min(0, position.top) + Math.min(0, fitRect.bottom - (position.top + size.height));
16211 var horizontalCrop = Math.min(0, position.left) + Math.min(0, fitRect.righ t - (position.left + size.width));
16212 return Math.abs(verticalCrop) * size.width + Math.abs(horizontalCrop) * si ze.height;
16213 },
16214
16215
16216 __getPosition: function(hAlign, vAlign, size, positionRect, fitRect) {
16217 // All the possible configurations.
16218 // Ordered as top-left, top-right, bottom-left, bottom-right.
16219 var positions = [{
16220 verticalAlign: 'top',
16221 horizontalAlign: 'left',
16222 top: positionRect.top,
16223 left: positionRect.left
16224 }, {
16225 verticalAlign: 'top',
16226 horizontalAlign: 'right',
16227 top: positionRect.top,
16228 left: positionRect.right - size.width
16229 }, {
16230 verticalAlign: 'bottom',
16231 horizontalAlign: 'left',
16232 top: positionRect.bottom - size.height,
16233 left: positionRect.left
16234 }, {
16235 verticalAlign: 'bottom',
16236 horizontalAlign: 'right',
16237 top: positionRect.bottom - size.height,
16238 left: positionRect.right - size.width
16239 }];
16240
16241 if (this.noOverlap) {
16242 // Duplicate.
16243 for (var i = 0, l = positions.length; i < l; i++) {
16244 var copy = {};
16245 for (var key in positions[i]) {
16246 copy[key] = positions[i][key];
16247 }
16248 positions.push(copy);
16249 }
16250 // Horizontal overlap only.
16251 positions[0].top = positions[1].top += positionRect.height;
16252 positions[2].top = positions[3].top -= positionRect.height;
16253 // Vertical overlap only.
16254 positions[4].left = positions[6].left += positionRect.width;
16255 positions[5].left = positions[7].left -= positionRect.width;
16256 }
16257
16258 // Consider auto as null for coding convenience.
16259 vAlign = vAlign === 'auto' ? null : vAlign;
16260 hAlign = hAlign === 'auto' ? null : hAlign;
16261
16262 var position;
16263 for (var i = 0; i < positions.length; i++) {
16264 var pos = positions[i];
16265
16266 // If both vAlign and hAlign are defined, return exact match.
16267 // For dynamicAlign and noOverlap we'll have more than one candidate, so
16268 // we'll have to check the croppedArea to make the best choice.
16269 if (!this.dynamicAlign && !this.noOverlap &&
16270 pos.verticalAlign === vAlign && pos.horizontalAlign === hAlign) {
16271 position = pos;
16272 break;
16273 }
16274
16275 // Align is ok if alignment preferences are respected. If no preferences ,
16276 // it is considered ok.
16277 var alignOk = (!vAlign || pos.verticalAlign === vAlign) &&
16278 (!hAlign || pos.horizontalAlign === hAlign);
16279
16280 // Filter out elements that don't match the alignment (if defined).
16281 // With dynamicAlign, we need to consider all the positions to find the
16282 // one that minimizes the cropped area.
16283 if (!this.dynamicAlign && !alignOk) {
16284 continue;
16285 }
16286
16287 position = position || pos;
16288 pos.croppedArea = this.__getCroppedArea(pos, size, fitRect);
16289 var diff = pos.croppedArea - position.croppedArea;
16290 // Check which crops less. If it crops equally, check if align is ok.
16291 if (diff < 0 || (diff === 0 && alignOk)) {
16292 position = pos;
16293 }
16294 // If not cropped and respects the align requirements, keep it.
16295 // This allows to prefer positions overlapping horizontally over the
16296 // ones overlapping vertically.
16297 if (position.croppedArea === 0 && alignOk) {
16298 break;
16299 }
16300 }
16301
16302 return position;
16303 }
16304
16305 };
16306 </script>
16307 <script>
16308 (function() {
16309 'use strict';
16310
16311 /**
16312 * Chrome uses an older version of DOM Level 3 Keyboard Events
16313 *
16314 * Most keys are labeled as text, but some are Unicode codepoints.
16315 * Values taken from: http://www.w3.org/TR/2007/WD-DOM-Level-3-Events-200712 21/keyset.html#KeySet-Set
16316 */
16317 var KEY_IDENTIFIER = {
16318 'U+0008': 'backspace',
16319 'U+0009': 'tab',
16320 'U+001B': 'esc',
16321 'U+0020': 'space',
16322 'U+007F': 'del'
16323 };
16324
16325 /**
16326 * Special table for KeyboardEvent.keyCode.
16327 * KeyboardEvent.keyIdentifier is better, and KeyBoardEvent.key is even bett er
16328 * than that.
16329 *
16330 * Values from: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEve nt.keyCode#Value_of_keyCode
16331 */
16332 var KEY_CODE = {
16333 8: 'backspace',
16334 9: 'tab',
16335 13: 'enter',
16336 27: 'esc',
16337 33: 'pageup',
16338 34: 'pagedown',
16339 35: 'end',
16340 36: 'home',
16341 32: 'space',
16342 37: 'left',
16343 38: 'up',
16344 39: 'right',
16345 40: 'down',
16346 46: 'del',
16347 106: '*'
16348 };
16349
16350 /**
16351 * MODIFIER_KEYS maps the short name for modifier keys used in a key
16352 * combo string to the property name that references those same keys
16353 * in a KeyboardEvent instance.
16354 */
16355 var MODIFIER_KEYS = {
16356 'shift': 'shiftKey',
16357 'ctrl': 'ctrlKey',
16358 'alt': 'altKey',
16359 'meta': 'metaKey'
16360 };
16361
16362 /**
16363 * KeyboardEvent.key is mostly represented by printable character made by
16364 * the keyboard, with unprintable keys labeled nicely.
16365 *
16366 * However, on OS X, Alt+char can make a Unicode character that follows an
16367 * Apple-specific mapping. In this case, we fall back to .keyCode.
16368 */
16369 var KEY_CHAR = /[a-z0-9*]/;
16370
16371 /**
16372 * Matches a keyIdentifier string.
16373 */
16374 var IDENT_CHAR = /U\+/;
16375
16376 /**
16377 * Matches arrow keys in Gecko 27.0+
16378 */
16379 var ARROW_KEY = /^arrow/;
16380
16381 /**
16382 * Matches space keys everywhere (notably including IE10's exceptional name
16383 * `spacebar`).
16384 */
16385 var SPACE_KEY = /^space(bar)?/;
16386
16387 /**
16388 * Matches ESC key.
16389 *
16390 * Value from: http://w3c.github.io/uievents-key/#key-Escape
16391 */
16392 var ESC_KEY = /^escape$/;
16393
16394 /**
16395 * Transforms the key.
16396 * @param {string} key The KeyBoardEvent.key
16397 * @param {Boolean} [noSpecialChars] Limits the transformation to
16398 * alpha-numeric characters.
16399 */
16400 function transformKey(key, noSpecialChars) {
16401 var validKey = '';
16402 if (key) {
16403 var lKey = key.toLowerCase();
16404 if (lKey === ' ' || SPACE_KEY.test(lKey)) {
16405 validKey = 'space';
16406 } else if (ESC_KEY.test(lKey)) {
16407 validKey = 'esc';
16408 } else if (lKey.length == 1) {
16409 if (!noSpecialChars || KEY_CHAR.test(lKey)) {
16410 validKey = lKey;
16411 }
16412 } else if (ARROW_KEY.test(lKey)) {
16413 validKey = lKey.replace('arrow', '');
16414 } else if (lKey == 'multiply') {
16415 // numpad '*' can map to Multiply on IE/Windows
16416 validKey = '*';
16417 } else {
16418 validKey = lKey;
16419 }
16420 }
16421 return validKey;
16422 }
16423
16424 function transformKeyIdentifier(keyIdent) {
16425 var validKey = '';
16426 if (keyIdent) {
16427 if (keyIdent in KEY_IDENTIFIER) {
16428 validKey = KEY_IDENTIFIER[keyIdent];
16429 } else if (IDENT_CHAR.test(keyIdent)) {
16430 keyIdent = parseInt(keyIdent.replace('U+', '0x'), 16);
16431 validKey = String.fromCharCode(keyIdent).toLowerCase();
16432 } else {
16433 validKey = keyIdent.toLowerCase();
16434 }
16435 }
16436 return validKey;
16437 }
16438
16439 function transformKeyCode(keyCode) {
16440 var validKey = '';
16441 if (Number(keyCode)) {
16442 if (keyCode >= 65 && keyCode <= 90) {
16443 // ascii a-z
16444 // lowercase is 32 offset from uppercase
16445 validKey = String.fromCharCode(32 + keyCode);
16446 } else if (keyCode >= 112 && keyCode <= 123) {
16447 // function keys f1-f12
16448 validKey = 'f' + (keyCode - 112);
16449 } else if (keyCode >= 48 && keyCode <= 57) {
16450 // top 0-9 keys
16451 validKey = String(keyCode - 48);
16452 } else if (keyCode >= 96 && keyCode <= 105) {
16453 // num pad 0-9
16454 validKey = String(keyCode - 96);
16455 } else {
16456 validKey = KEY_CODE[keyCode];
16457 }
16458 }
16459 return validKey;
16460 }
16461
16462 /**
16463 * Calculates the normalized key for a KeyboardEvent.
16464 * @param {KeyboardEvent} keyEvent
16465 * @param {Boolean} [noSpecialChars] Set to true to limit keyEvent.key
16466 * transformation to alpha-numeric chars. This is useful with key
16467 * combinations like shift + 2, which on FF for MacOS produces
16468 * keyEvent.key = @
16469 * To get 2 returned, set noSpecialChars = true
16470 * To get @ returned, set noSpecialChars = false
16471 */
16472 function normalizedKeyForEvent(keyEvent, noSpecialChars) {
16473 // Fall back from .key, to .keyIdentifier, to .keyCode, and then to
16474 // .detail.key to support artificial keyboard events.
16475 return transformKey(keyEvent.key, noSpecialChars) ||
16476 transformKeyIdentifier(keyEvent.keyIdentifier) ||
16477 transformKeyCode(keyEvent.keyCode) ||
16478 transformKey(keyEvent.detail ? keyEvent.detail.key : keyEvent.detail, no SpecialChars) || '';
16479 }
16480
16481 function keyComboMatchesEvent(keyCombo, event) {
16482 // For combos with modifiers we support only alpha-numeric keys
16483 var keyEvent = normalizedKeyForEvent(event, keyCombo.hasModifiers);
16484 return keyEvent === keyCombo.key &&
16485 (!keyCombo.hasModifiers || (
16486 !!event.shiftKey === !!keyCombo.shiftKey &&
16487 !!event.ctrlKey === !!keyCombo.ctrlKey &&
16488 !!event.altKey === !!keyCombo.altKey &&
16489 !!event.metaKey === !!keyCombo.metaKey)
16490 );
16491 }
16492
16493 function parseKeyComboString(keyComboString) {
16494 if (keyComboString.length === 1) {
16495 return {
16496 combo: keyComboString,
16497 key: keyComboString,
16498 event: 'keydown'
16499 };
16500 }
16501 return keyComboString.split('+').reduce(function(parsedKeyCombo, keyComboP art) {
16502 var eventParts = keyComboPart.split(':');
16503 var keyName = eventParts[0];
16504 var event = eventParts[1];
16505
16506 if (keyName in MODIFIER_KEYS) {
16507 parsedKeyCombo[MODIFIER_KEYS[keyName]] = true;
16508 parsedKeyCombo.hasModifiers = true;
16509 } else {
16510 parsedKeyCombo.key = keyName;
16511 parsedKeyCombo.event = event || 'keydown';
16512 }
16513
16514 return parsedKeyCombo;
16515 }, {
16516 combo: keyComboString.split(':').shift()
16517 });
16518 }
16519
16520 function parseEventString(eventString) {
16521 return eventString.trim().split(' ').map(function(keyComboString) {
16522 return parseKeyComboString(keyComboString);
16523 });
16524 }
16525
16526 /**
16527 * `Polymer.IronA11yKeysBehavior` provides a normalized interface for proces sing
16528 * keyboard commands that pertain to [WAI-ARIA best practices](http://www.w3 .org/TR/wai-aria-practices/#kbd_general_binding).
16529 * The element takes care of browser differences with respect to Keyboard ev ents
16530 * and uses an expressive syntax to filter key presses.
16531 *
16532 * Use the `keyBindings` prototype property to express what combination of k eys
16533 * will trigger the callback. A key binding has the format
16534 * `"KEY+MODIFIER:EVENT": "callback"` (`"KEY": "callback"` or
16535 * `"KEY:EVENT": "callback"` are valid as well). Some examples:
16536 *
16537 * keyBindings: {
16538 * 'space': '_onKeydown', // same as 'space:keydown'
16539 * 'shift+tab': '_onKeydown',
16540 * 'enter:keypress': '_onKeypress',
16541 * 'esc:keyup': '_onKeyup'
16542 * }
16543 *
16544 * The callback will receive with an event containing the following informat ion in `event.detail`:
16545 *
16546 * _onKeydown: function(event) {
16547 * console.log(event.detail.combo); // KEY+MODIFIER, e.g. "shift+tab"
16548 * console.log(event.detail.key); // KEY only, e.g. "tab"
16549 * console.log(event.detail.event); // EVENT, e.g. "keydown"
16550 * console.log(event.detail.keyboardEvent); // the original KeyboardE vent
16551 * }
16552 *
16553 * Use the `keyEventTarget` attribute to set up event handlers on a specific
16554 * node.
16555 *
16556 * See the [demo source code](https://github.com/PolymerElements/iron-a11y-k eys-behavior/blob/master/demo/x-key-aware.html)
16557 * for an example.
16558 *
16559 * @demo demo/index.html
16560 * @polymerBehavior
16561 */
16562 Polymer.IronA11yKeysBehavior = {
16563 properties: {
16564 /**
16565 * The EventTarget that will be firing relevant KeyboardEvents. Set it t o
16566 * `null` to disable the listeners.
16567 * @type {?EventTarget}
16568 */
16569 keyEventTarget: {
16570 type: Object,
16571 value: function() {
16572 return this;
16573 }
16574 },
16575
16576 /**
16577 * If true, this property will cause the implementing element to
16578 * automatically stop propagation on any handled KeyboardEvents.
16579 */
16580 stopKeyboardEventPropagation: {
16581 type: Boolean,
16582 value: false
16583 },
16584
16585 _boundKeyHandlers: {
16586 type: Array,
16587 value: function() {
16588 return [];
16589 }
16590 },
16591
16592 // We use this due to a limitation in IE10 where instances will have
16593 // own properties of everything on the "prototype".
16594 _imperativeKeyBindings: {
16595 type: Object,
16596 value: function() {
16597 return {};
16598 }
16599 }
16600 },
16601
16602 observers: [
16603 '_resetKeyEventListeners(keyEventTarget, _boundKeyHandlers)'
16604 ],
16605
16606
16607 /**
16608 * To be used to express what combination of keys will trigger the relati ve
16609 * callback. e.g. `keyBindings: { 'esc': '_onEscPressed'}`
16610 * @type {Object}
16611 */
16612 keyBindings: {},
16613
16614 registered: function() {
16615 this._prepKeyBindings();
16616 },
16617
16618 attached: function() {
16619 this._listenKeyEventListeners();
16620 },
16621
16622 detached: function() {
16623 this._unlistenKeyEventListeners();
16624 },
16625
16626 /**
16627 * Can be used to imperatively add a key binding to the implementing
16628 * element. This is the imperative equivalent of declaring a keybinding
16629 * in the `keyBindings` prototype property.
16630 */
16631 addOwnKeyBinding: function(eventString, handlerName) {
16632 this._imperativeKeyBindings[eventString] = handlerName;
16633 this._prepKeyBindings();
16634 this._resetKeyEventListeners();
16635 },
16636
16637 /**
16638 * When called, will remove all imperatively-added key bindings.
16639 */
16640 removeOwnKeyBindings: function() {
16641 this._imperativeKeyBindings = {};
16642 this._prepKeyBindings();
16643 this._resetKeyEventListeners();
16644 },
16645
16646 /**
16647 * Returns true if a keyboard event matches `eventString`.
16648 *
16649 * @param {KeyboardEvent} event
16650 * @param {string} eventString
16651 * @return {boolean}
16652 */
16653 keyboardEventMatchesKeys: function(event, eventString) {
16654 var keyCombos = parseEventString(eventString);
16655 for (var i = 0; i < keyCombos.length; ++i) {
16656 if (keyComboMatchesEvent(keyCombos[i], event)) {
16657 return true;
16658 }
16659 }
16660 return false;
16661 },
16662
16663 _collectKeyBindings: function() {
16664 var keyBindings = this.behaviors.map(function(behavior) {
16665 return behavior.keyBindings;
16666 });
16667
16668 if (keyBindings.indexOf(this.keyBindings) === -1) {
16669 keyBindings.push(this.keyBindings);
16670 }
16671
16672 return keyBindings;
16673 },
16674
16675 _prepKeyBindings: function() {
16676 this._keyBindings = {};
16677
16678 this._collectKeyBindings().forEach(function(keyBindings) {
16679 for (var eventString in keyBindings) {
16680 this._addKeyBinding(eventString, keyBindings[eventString]);
16681 }
16682 }, this);
16683
16684 for (var eventString in this._imperativeKeyBindings) {
16685 this._addKeyBinding(eventString, this._imperativeKeyBindings[eventStri ng]);
16686 }
16687
16688 // Give precedence to combos with modifiers to be checked first.
16689 for (var eventName in this._keyBindings) {
16690 this._keyBindings[eventName].sort(function (kb1, kb2) {
16691 var b1 = kb1[0].hasModifiers;
16692 var b2 = kb2[0].hasModifiers;
16693 return (b1 === b2) ? 0 : b1 ? -1 : 1;
16694 })
16695 }
16696 },
16697
16698 _addKeyBinding: function(eventString, handlerName) {
16699 parseEventString(eventString).forEach(function(keyCombo) {
16700 this._keyBindings[keyCombo.event] =
16701 this._keyBindings[keyCombo.event] || [];
16702
16703 this._keyBindings[keyCombo.event].push([
16704 keyCombo,
16705 handlerName
16706 ]);
16707 }, this);
16708 },
16709
16710 _resetKeyEventListeners: function() {
16711 this._unlistenKeyEventListeners();
16712
16713 if (this.isAttached) {
16714 this._listenKeyEventListeners();
16715 }
16716 },
16717
16718 _listenKeyEventListeners: function() {
16719 if (!this.keyEventTarget) {
16720 return;
16721 }
16722 Object.keys(this._keyBindings).forEach(function(eventName) {
16723 var keyBindings = this._keyBindings[eventName];
16724 var boundKeyHandler = this._onKeyBindingEvent.bind(this, keyBindings);
16725
16726 this._boundKeyHandlers.push([this.keyEventTarget, eventName, boundKeyH andler]);
16727
16728 this.keyEventTarget.addEventListener(eventName, boundKeyHandler);
16729 }, this);
16730 },
16731
16732 _unlistenKeyEventListeners: function() {
16733 var keyHandlerTuple;
16734 var keyEventTarget;
16735 var eventName;
16736 var boundKeyHandler;
16737
16738 while (this._boundKeyHandlers.length) {
16739 // My kingdom for block-scope binding and destructuring assignment..
16740 keyHandlerTuple = this._boundKeyHandlers.pop();
16741 keyEventTarget = keyHandlerTuple[0];
16742 eventName = keyHandlerTuple[1];
16743 boundKeyHandler = keyHandlerTuple[2];
16744
16745 keyEventTarget.removeEventListener(eventName, boundKeyHandler);
16746 }
16747 },
16748
16749 _onKeyBindingEvent: function(keyBindings, event) {
16750 if (this.stopKeyboardEventPropagation) {
16751 event.stopPropagation();
16752 }
16753
16754 // if event has been already prevented, don't do anything
16755 if (event.defaultPrevented) {
16756 return;
16757 }
16758
16759 for (var i = 0; i < keyBindings.length; i++) {
16760 var keyCombo = keyBindings[i][0];
16761 var handlerName = keyBindings[i][1];
16762 if (keyComboMatchesEvent(keyCombo, event)) {
16763 this._triggerKeyHandler(keyCombo, handlerName, event);
16764 // exit the loop if eventDefault was prevented
16765 if (event.defaultPrevented) {
16766 return;
16767 }
16768 }
16769 }
16770 },
16771
16772 _triggerKeyHandler: function(keyCombo, handlerName, keyboardEvent) {
16773 var detail = Object.create(keyCombo);
16774 detail.keyboardEvent = keyboardEvent;
16775 var event = new CustomEvent(keyCombo.event, {
16776 detail: detail,
16777 cancelable: true
16778 });
16779 this[handlerName].call(this, event);
16780 if (event.defaultPrevented) {
16781 keyboardEvent.preventDefault();
16782 }
16783 }
16784 };
16785 })();
16786 </script>
16787
16788
16789 <dom-module id="iron-overlay-backdrop" assetpath="/res/imp/bower_components/iron -overlay-behavior/">
16790
16791 <template>
16792 <style>
16793 :host {
16794 position: fixed;
16795 top: 0;
16796 left: 0;
16797 width: 100%;
16798 height: 100%;
16799 background-color: var(--iron-overlay-backdrop-background-color, #000);
16800 opacity: 0;
16801 transition: opacity 0.2s;
16802 pointer-events: none;
16803 @apply(--iron-overlay-backdrop);
16804 }
16805
16806 :host(.opened) {
16807 opacity: var(--iron-overlay-backdrop-opacity, 0.6);
16808 pointer-events: auto;
16809 @apply(--iron-overlay-backdrop-opened);
16810 }
16811 </style>
16812
16813 <content></content>
16814 </template>
16815
16816 </dom-module>
16817
16818 <script>
16819 (function() {
16820 'use strict';
16821
16822 Polymer({
16823
16824 is: 'iron-overlay-backdrop',
16825
16826 properties: {
16827
16828 /**
16829 * Returns true if the backdrop is opened.
16830 */
16831 opened: {
16832 reflectToAttribute: true,
16833 type: Boolean,
16834 value: false,
16835 observer: '_openedChanged'
16836 }
16837
16838 },
16839
16840 listeners: {
16841 'transitionend': '_onTransitionend'
16842 },
16843
16844 created: function() {
16845 // Used to cancel previous requestAnimationFrame calls when opened changes .
16846 this.__openedRaf = null;
16847 },
16848
16849 attached: function() {
16850 this.opened && this._openedChanged(this.opened);
16851 },
16852
16853 /**
16854 * Appends the backdrop to document body if needed.
16855 */
16856 prepare: function() {
16857 if (this.opened && !this.parentNode) {
16858 Polymer.dom(document.body).appendChild(this);
16859 }
16860 },
16861
16862 /**
16863 * Shows the backdrop.
16864 */
16865 open: function() {
16866 this.opened = true;
16867 },
16868
16869 /**
16870 * Hides the backdrop.
16871 */
16872 close: function() {
16873 this.opened = false;
16874 },
16875
16876 /**
16877 * Removes the backdrop from document body if needed.
16878 */
16879 complete: function() {
16880 if (!this.opened && this.parentNode === document.body) {
16881 Polymer.dom(this.parentNode).removeChild(this);
16882 }
16883 },
16884
16885 _onTransitionend: function(event) {
16886 if (event && event.target === this) {
16887 this.complete();
16888 }
16889 },
16890
16891 /**
16892 * @param {boolean} opened
16893 * @private
16894 */
16895 _openedChanged: function(opened) {
16896 if (opened) {
16897 // Auto-attach.
16898 this.prepare();
16899 } else {
16900 // Animation might be disabled via the mixin or opacity custom property.
16901 // If it is disabled in other ways, it's up to the user to call complete .
16902 var cs = window.getComputedStyle(this);
16903 if (cs.transitionDuration === '0s' || cs.opacity == 0) {
16904 this.complete();
16905 }
16906 }
16907
16908 if (!this.isAttached) {
16909 return;
16910 }
16911
16912 // Always cancel previous requestAnimationFrame.
16913 if (this.__openedRaf) {
16914 window.cancelAnimationFrame(this.__openedRaf);
16915 this.__openedRaf = null;
16916 }
16917 // Force relayout to ensure proper transitions.
16918 this.scrollTop = this.scrollTop;
16919 this.__openedRaf = window.requestAnimationFrame(function() {
16920 this.__openedRaf = null;
16921 this.toggleClass('opened', this.opened);
16922 }.bind(this));
16923 }
16924 });
16925
16926 })();
16927
16928 </script>
16929 <script>
16930
16931 /**
16932 * @struct
16933 * @constructor
16934 * @private
16935 */
16936 Polymer.IronOverlayManagerClass = function() {
16937 /**
16938 * Used to keep track of the opened overlays.
16939 * @private {Array<Element>}
16940 */
16941 this._overlays = [];
16942
16943 /**
16944 * iframes have a default z-index of 100,
16945 * so this default should be at least that.
16946 * @private {number}
16947 */
16948 this._minimumZ = 101;
16949
16950 /**
16951 * Memoized backdrop element.
16952 * @private {Element|null}
16953 */
16954 this._backdropElement = null;
16955
16956 // Enable document-wide tap recognizer.
16957 Polymer.Gestures.add(document, 'tap', null);
16958 // Need to have useCapture=true, Polymer.Gestures doesn't offer that.
16959 document.addEventListener('tap', this._onCaptureClick.bind(this), true);
16960 document.addEventListener('focus', this._onCaptureFocus.bind(this), true);
16961 document.addEventListener('keydown', this._onCaptureKeyDown.bind(this), true );
16962 };
16963
16964 Polymer.IronOverlayManagerClass.prototype = {
16965
16966 constructor: Polymer.IronOverlayManagerClass,
16967
16968 /**
16969 * The shared backdrop element.
16970 * @type {!Element} backdropElement
16971 */
16972 get backdropElement() {
16973 if (!this._backdropElement) {
16974 this._backdropElement = document.createElement('iron-overlay-backdrop');
16975 }
16976 return this._backdropElement;
16977 },
16978
16979 /**
16980 * The deepest active element.
16981 * @type {!Element} activeElement the active element
16982 */
16983 get deepActiveElement() {
16984 // document.activeElement can be null
16985 // https://developer.mozilla.org/en-US/docs/Web/API/Document/activeElement
16986 // In case of null, default it to document.body.
16987 var active = document.activeElement || document.body;
16988 while (active.root && Polymer.dom(active.root).activeElement) {
16989 active = Polymer.dom(active.root).activeElement;
16990 }
16991 return active;
16992 },
16993
16994 /**
16995 * Brings the overlay at the specified index to the front.
16996 * @param {number} i
16997 * @private
16998 */
16999 _bringOverlayAtIndexToFront: function(i) {
17000 var overlay = this._overlays[i];
17001 if (!overlay) {
17002 return;
17003 }
17004 var lastI = this._overlays.length - 1;
17005 var currentOverlay = this._overlays[lastI];
17006 // Ensure always-on-top overlay stays on top.
17007 if (currentOverlay && this._shouldBeBehindOverlay(overlay, currentOverlay) ) {
17008 lastI--;
17009 }
17010 // If already the top element, return.
17011 if (i >= lastI) {
17012 return;
17013 }
17014 // Update z-index to be on top.
17015 var minimumZ = Math.max(this.currentOverlayZ(), this._minimumZ);
17016 if (this._getZ(overlay) <= minimumZ) {
17017 this._applyOverlayZ(overlay, minimumZ);
17018 }
17019
17020 // Shift other overlays behind the new on top.
17021 while (i < lastI) {
17022 this._overlays[i] = this._overlays[i + 1];
17023 i++;
17024 }
17025 this._overlays[lastI] = overlay;
17026 },
17027
17028 /**
17029 * Adds the overlay and updates its z-index if it's opened, or removes it if it's closed.
17030 * Also updates the backdrop z-index.
17031 * @param {!Element} overlay
17032 */
17033 addOrRemoveOverlay: function(overlay) {
17034 if (overlay.opened) {
17035 this.addOverlay(overlay);
17036 } else {
17037 this.removeOverlay(overlay);
17038 }
17039 },
17040
17041 /**
17042 * Tracks overlays for z-index and focus management.
17043 * Ensures the last added overlay with always-on-top remains on top.
17044 * @param {!Element} overlay
17045 */
17046 addOverlay: function(overlay) {
17047 var i = this._overlays.indexOf(overlay);
17048 if (i >= 0) {
17049 this._bringOverlayAtIndexToFront(i);
17050 this.trackBackdrop();
17051 return;
17052 }
17053 var insertionIndex = this._overlays.length;
17054 var currentOverlay = this._overlays[insertionIndex - 1];
17055 var minimumZ = Math.max(this._getZ(currentOverlay), this._minimumZ);
17056 var newZ = this._getZ(overlay);
17057
17058 // Ensure always-on-top overlay stays on top.
17059 if (currentOverlay && this._shouldBeBehindOverlay(overlay, currentOverlay) ) {
17060 // This bumps the z-index of +2.
17061 this._applyOverlayZ(currentOverlay, minimumZ);
17062 insertionIndex--;
17063 // Update minimumZ to match previous overlay's z-index.
17064 var previousOverlay = this._overlays[insertionIndex - 1];
17065 minimumZ = Math.max(this._getZ(previousOverlay), this._minimumZ);
17066 }
17067
17068 // Update z-index and insert overlay.
17069 if (newZ <= minimumZ) {
17070 this._applyOverlayZ(overlay, minimumZ);
17071 }
17072 this._overlays.splice(insertionIndex, 0, overlay);
17073
17074 // Get focused node.
17075 var element = this.deepActiveElement;
17076 overlay.restoreFocusNode = this._overlayParent(element) ? null : element;
17077 this.trackBackdrop();
17078 },
17079
17080 /**
17081 * @param {!Element} overlay
17082 */
17083 removeOverlay: function(overlay) {
17084 var i = this._overlays.indexOf(overlay);
17085 if (i === -1) {
17086 return;
17087 }
17088 this._overlays.splice(i, 1);
17089
17090 var node = overlay.restoreFocusOnClose ? overlay.restoreFocusNode : null;
17091 overlay.restoreFocusNode = null;
17092 // Focus back only if still contained in document.body
17093 if (node && Polymer.dom(document.body).deepContains(node)) {
17094 node.focus();
17095 }
17096 this.trackBackdrop();
17097 },
17098
17099 /**
17100 * Returns the current overlay.
17101 * @return {Element|undefined}
17102 */
17103 currentOverlay: function() {
17104 var i = this._overlays.length - 1;
17105 return this._overlays[i];
17106 },
17107
17108 /**
17109 * Returns the current overlay z-index.
17110 * @return {number}
17111 */
17112 currentOverlayZ: function() {
17113 return this._getZ(this.currentOverlay());
17114 },
17115
17116 /**
17117 * Ensures that the minimum z-index of new overlays is at least `minimumZ`.
17118 * This does not effect the z-index of any existing overlays.
17119 * @param {number} minimumZ
17120 */
17121 ensureMinimumZ: function(minimumZ) {
17122 this._minimumZ = Math.max(this._minimumZ, minimumZ);
17123 },
17124
17125 focusOverlay: function() {
17126 var current = /** @type {?} */ (this.currentOverlay());
17127 // We have to be careful to focus the next overlay _after_ any current
17128 // transitions are complete (due to the state being toggled prior to the
17129 // transition). Otherwise, we risk infinite recursion when a transitioning
17130 // (closed) overlay becomes the current overlay.
17131 //
17132 // NOTE: We make the assumption that any overlay that completes a transiti on
17133 // will call into focusOverlay to kick the process back off. Currently:
17134 // transitionend -> _applyFocus -> focusOverlay.
17135 if (current && !current.transitioning) {
17136 current._applyFocus();
17137 }
17138 },
17139
17140 /**
17141 * Updates the backdrop z-index.
17142 */
17143 trackBackdrop: function() {
17144 var overlay = this._overlayWithBackdrop();
17145 // Avoid creating the backdrop if there is no overlay with backdrop.
17146 if (!overlay && !this._backdropElement) {
17147 return;
17148 }
17149 this.backdropElement.style.zIndex = this._getZ(overlay) - 1;
17150 this.backdropElement.opened = !!overlay;
17151 },
17152
17153 /**
17154 * @return {Array<Element>}
17155 */
17156 getBackdrops: function() {
17157 var backdrops = [];
17158 for (var i = 0; i < this._overlays.length; i++) {
17159 if (this._overlays[i].withBackdrop) {
17160 backdrops.push(this._overlays[i]);
17161 }
17162 }
17163 return backdrops;
17164 },
17165
17166 /**
17167 * Returns the z-index for the backdrop.
17168 * @return {number}
17169 */
17170 backdropZ: function() {
17171 return this._getZ(this._overlayWithBackdrop()) - 1;
17172 },
17173
17174 /**
17175 * Returns the first opened overlay that has a backdrop.
17176 * @return {Element|undefined}
17177 * @private
17178 */
17179 _overlayWithBackdrop: function() {
17180 for (var i = 0; i < this._overlays.length; i++) {
17181 if (this._overlays[i].withBackdrop) {
17182 return this._overlays[i];
17183 }
17184 }
17185 },
17186
17187 /**
17188 * Calculates the minimum z-index for the overlay.
17189 * @param {Element=} overlay
17190 * @private
17191 */
17192 _getZ: function(overlay) {
17193 var z = this._minimumZ;
17194 if (overlay) {
17195 var z1 = Number(overlay.style.zIndex || window.getComputedStyle(overlay) .zIndex);
17196 // Check if is a number
17197 // Number.isNaN not supported in IE 10+
17198 if (z1 === z1) {
17199 z = z1;
17200 }
17201 }
17202 return z;
17203 },
17204
17205 /**
17206 * @param {!Element} element
17207 * @param {number|string} z
17208 * @private
17209 */
17210 _setZ: function(element, z) {
17211 element.style.zIndex = z;
17212 },
17213
17214 /**
17215 * @param {!Element} overlay
17216 * @param {number} aboveZ
17217 * @private
17218 */
17219 _applyOverlayZ: function(overlay, aboveZ) {
17220 this._setZ(overlay, aboveZ + 2);
17221 },
17222
17223 /**
17224 * Returns the overlay containing the provided node. If the node is an overl ay,
17225 * it returns the node.
17226 * @param {Element=} node
17227 * @return {Element|undefined}
17228 * @private
17229 */
17230 _overlayParent: function(node) {
17231 while (node && node !== document.body) {
17232 // Check if it is an overlay.
17233 if (node._manager === this) {
17234 return node;
17235 }
17236 // Use logical parentNode, or native ShadowRoot host.
17237 node = Polymer.dom(node).parentNode || node.host;
17238 }
17239 },
17240
17241 /**
17242 * Returns the deepest overlay in the path.
17243 * @param {Array<Element>=} path
17244 * @return {Element|undefined}
17245 * @suppress {missingProperties}
17246 * @private
17247 */
17248 _overlayInPath: function(path) {
17249 path = path || [];
17250 for (var i = 0; i < path.length; i++) {
17251 if (path[i]._manager === this) {
17252 return path[i];
17253 }
17254 }
17255 },
17256
17257 /**
17258 * Ensures the click event is delegated to the right overlay.
17259 * @param {!Event} event
17260 * @private
17261 */
17262 _onCaptureClick: function(event) {
17263 var overlay = /** @type {?} */ (this.currentOverlay());
17264 // Check if clicked outside of top overlay.
17265 if (overlay && this._overlayInPath(Polymer.dom(event).path) !== overlay) {
17266 overlay._onCaptureClick(event);
17267 }
17268 },
17269
17270 /**
17271 * Ensures the focus event is delegated to the right overlay.
17272 * @param {!Event} event
17273 * @private
17274 */
17275 _onCaptureFocus: function(event) {
17276 var overlay = /** @type {?} */ (this.currentOverlay());
17277 if (overlay) {
17278 overlay._onCaptureFocus(event);
17279 }
17280 },
17281
17282 /**
17283 * Ensures TAB and ESC keyboard events are delegated to the right overlay.
17284 * @param {!Event} event
17285 * @private
17286 */
17287 _onCaptureKeyDown: function(event) {
17288 var overlay = /** @type {?} */ (this.currentOverlay());
17289 if (overlay) {
17290 if (Polymer.IronA11yKeysBehavior.keyboardEventMatchesKeys(event, 'esc')) {
17291 overlay._onCaptureEsc(event);
17292 } else if (Polymer.IronA11yKeysBehavior.keyboardEventMatchesKeys(event, 'tab')) {
17293 overlay._onCaptureTab(event);
17294 }
17295 }
17296 },
17297
17298 /**
17299 * Returns if the overlay1 should be behind overlay2.
17300 * @param {!Element} overlay1
17301 * @param {!Element} overlay2
17302 * @return {boolean}
17303 * @suppress {missingProperties}
17304 * @private
17305 */
17306 _shouldBeBehindOverlay: function(overlay1, overlay2) {
17307 return !overlay1.alwaysOnTop && overlay2.alwaysOnTop;
17308 }
17309 };
17310
17311 Polymer.IronOverlayManager = new Polymer.IronOverlayManagerClass();
17312 </script>
17313 <script>
17314 (function() {
17315 'use strict';
17316
17317 /**
17318 Use `Polymer.IronOverlayBehavior` to implement an element that can be hidden or shown, and displays
17319 on top of other content. It includes an optional backdrop, and can be used to im plement a variety
17320 of UI controls including dialogs and drop downs. Multiple overlays may be displa yed at once.
17321
17322 See the [demo source code](https://github.com/PolymerElements/iron-overlay-behav ior/blob/master/demo/simple-overlay.html)
17323 for an example.
17324
17325 ### Closing and canceling
17326
17327 An overlay may be hidden by closing or canceling. The difference between close a nd cancel is user
17328 intent. Closing generally implies that the user acknowledged the content on the overlay. By default,
17329 it will cancel whenever the user taps outside it or presses the escape key. This behavior is
17330 configurable with the `no-cancel-on-esc-key` and the `no-cancel-on-outside-click ` properties.
17331 `close()` should be called explicitly by the implementer when the user interacts with a control
17332 in the overlay element. When the dialog is canceled, the overlay fires an 'iron- overlay-canceled'
17333 event. Call `preventDefault` on this event to prevent the overlay from closing.
17334
17335 ### Positioning
17336
17337 By default the element is sized and positioned to fit and centered inside the wi ndow. You can
17338 position and size it manually using CSS. See `Polymer.IronFitBehavior`.
17339
17340 ### Backdrop
17341
17342 Set the `with-backdrop` attribute to display a backdrop behind the overlay. The backdrop is
17343 appended to `<body>` and is of type `<iron-overlay-backdrop>`. See its doc page for styling
17344 options.
17345
17346 In addition, `with-backdrop` will wrap the focus within the content in the light DOM.
17347 Override the [`_focusableNodes` getter](#Polymer.IronOverlayBehavior:property-_f ocusableNodes)
17348 to achieve a different behavior.
17349
17350 ### Limitations
17351
17352 The element is styled to appear on top of other content by setting its `z-index` property. You
17353 must ensure no element has a stacking context with a higher `z-index` than its p arent stacking
17354 context. You should place this element as a child of `<body>` whenever possible.
17355
17356 @demo demo/index.html
17357 @polymerBehavior Polymer.IronOverlayBehavior
17358 */
17359
17360 Polymer.IronOverlayBehaviorImpl = {
17361
17362 properties: {
17363
17364 /**
17365 * True if the overlay is currently displayed.
17366 */
17367 opened: {
17368 observer: '_openedChanged',
17369 type: Boolean,
17370 value: false,
17371 notify: true
17372 },
17373
17374 /**
17375 * True if the overlay was canceled when it was last closed.
17376 */
17377 canceled: {
17378 observer: '_canceledChanged',
17379 readOnly: true,
17380 type: Boolean,
17381 value: false
17382 },
17383
17384 /**
17385 * Set to true to display a backdrop behind the overlay. It traps the focu s
17386 * within the light DOM of the overlay.
17387 */
17388 withBackdrop: {
17389 observer: '_withBackdropChanged',
17390 type: Boolean
17391 },
17392
17393 /**
17394 * Set to true to disable auto-focusing the overlay or child nodes with
17395 * the `autofocus` attribute` when the overlay is opened.
17396 */
17397 noAutoFocus: {
17398 type: Boolean,
17399 value: false
17400 },
17401
17402 /**
17403 * Set to true to disable canceling the overlay with the ESC key.
17404 */
17405 noCancelOnEscKey: {
17406 type: Boolean,
17407 value: false
17408 },
17409
17410 /**
17411 * Set to true to disable canceling the overlay by clicking outside it.
17412 */
17413 noCancelOnOutsideClick: {
17414 type: Boolean,
17415 value: false
17416 },
17417
17418 /**
17419 * Contains the reason(s) this overlay was last closed (see `iron-overlay- closed`).
17420 * `IronOverlayBehavior` provides the `canceled` reason; implementers of t he
17421 * behavior can provide other reasons in addition to `canceled`.
17422 */
17423 closingReason: {
17424 // was a getter before, but needs to be a property so other
17425 // behaviors can override this.
17426 type: Object
17427 },
17428
17429 /**
17430 * Set to true to enable restoring of focus when overlay is closed.
17431 */
17432 restoreFocusOnClose: {
17433 type: Boolean,
17434 value: false
17435 },
17436
17437 /**
17438 * Set to true to keep overlay always on top.
17439 */
17440 alwaysOnTop: {
17441 type: Boolean
17442 },
17443
17444 /**
17445 * Shortcut to access to the overlay manager.
17446 * @private
17447 * @type {Polymer.IronOverlayManagerClass}
17448 */
17449 _manager: {
17450 type: Object,
17451 value: Polymer.IronOverlayManager
17452 },
17453
17454 /**
17455 * The node being focused.
17456 * @type {?Node}
17457 */
17458 _focusedChild: {
17459 type: Object
17460 }
17461
17462 },
17463
17464 listeners: {
17465 'iron-resize': '_onIronResize'
17466 },
17467
17468 /**
17469 * The backdrop element.
17470 * @type {Element}
17471 */
17472 get backdropElement() {
17473 return this._manager.backdropElement;
17474 },
17475
17476 /**
17477 * Returns the node to give focus to.
17478 * @type {Node}
17479 */
17480 get _focusNode() {
17481 return this._focusedChild || Polymer.dom(this).querySelector('[autofocus]' ) || this;
17482 },
17483
17484 /**
17485 * Array of nodes that can receive focus (overlay included), ordered by `tab index`.
17486 * This is used to retrieve which is the first and last focusable nodes in o rder
17487 * to wrap the focus for overlays `with-backdrop`.
17488 *
17489 * If you know what is your content (specifically the first and last focusab le children),
17490 * you can override this method to return only `[firstFocusable, lastFocusab le];`
17491 * @type {Array<Node>}
17492 * @protected
17493 */
17494 get _focusableNodes() {
17495 // Elements that can be focused even if they have [disabled] attribute.
17496 var FOCUSABLE_WITH_DISABLED = [
17497 'a[href]',
17498 'area[href]',
17499 'iframe',
17500 '[tabindex]',
17501 '[contentEditable=true]'
17502 ];
17503
17504 // Elements that cannot be focused if they have [disabled] attribute.
17505 var FOCUSABLE_WITHOUT_DISABLED = [
17506 'input',
17507 'select',
17508 'textarea',
17509 'button'
17510 ];
17511
17512 // Discard elements with tabindex=-1 (makes them not focusable).
17513 var selector = FOCUSABLE_WITH_DISABLED.join(':not([tabindex="-1"]),') +
17514 ':not([tabindex="-1"]),' +
17515 FOCUSABLE_WITHOUT_DISABLED.join(':not([disabled]):not([tabindex="-1"]),' ) +
17516 ':not([disabled]):not([tabindex="-1"])';
17517
17518 var focusables = Polymer.dom(this).querySelectorAll(selector);
17519 if (this.tabIndex >= 0) {
17520 // Insert at the beginning because we might have all elements with tabIn dex = 0,
17521 // and the overlay should be the first of the list.
17522 focusables.splice(0, 0, this);
17523 }
17524 // Sort by tabindex.
17525 return focusables.sort(function (a, b) {
17526 if (a.tabIndex === b.tabIndex) {
17527 return 0;
17528 }
17529 if (a.tabIndex === 0 || a.tabIndex > b.tabIndex) {
17530 return 1;
17531 }
17532 return -1;
17533 });
17534 },
17535
17536 ready: function() {
17537 // Used to skip calls to notifyResize and refit while the overlay is anima ting.
17538 this.__isAnimating = false;
17539 // with-backdrop needs tabindex to be set in order to trap the focus.
17540 // If it is not set, IronOverlayBehavior will set it, and remove it if wit h-backdrop = false.
17541 this.__shouldRemoveTabIndex = false;
17542 // Used for wrapping the focus on TAB / Shift+TAB.
17543 this.__firstFocusableNode = this.__lastFocusableNode = null;
17544 // Used for requestAnimationFrame when opened changes.
17545 this.__openChangedAsync = null;
17546 // Used for requestAnimationFrame when iron-resize is fired.
17547 this.__onIronResizeAsync = null;
17548 this._ensureSetup();
17549 },
17550
17551 attached: function() {
17552 // Call _openedChanged here so that position can be computed correctly.
17553 if (this.opened) {
17554 this._openedChanged();
17555 }
17556 this._observer = Polymer.dom(this).observeNodes(this._onNodesChange);
17557 },
17558
17559 detached: function() {
17560 Polymer.dom(this).unobserveNodes(this._observer);
17561 this._observer = null;
17562 this.opened = false;
17563 },
17564
17565 /**
17566 * Toggle the opened state of the overlay.
17567 */
17568 toggle: function() {
17569 this._setCanceled(false);
17570 this.opened = !this.opened;
17571 },
17572
17573 /**
17574 * Open the overlay.
17575 */
17576 open: function() {
17577 this._setCanceled(false);
17578 this.opened = true;
17579 },
17580
17581 /**
17582 * Close the overlay.
17583 */
17584 close: function() {
17585 this._setCanceled(false);
17586 this.opened = false;
17587 },
17588
17589 /**
17590 * Cancels the overlay.
17591 * @param {Event=} event The original event
17592 */
17593 cancel: function(event) {
17594 var cancelEvent = this.fire('iron-overlay-canceled', event, {cancelable: t rue});
17595 if (cancelEvent.defaultPrevented) {
17596 return;
17597 }
17598
17599 this._setCanceled(true);
17600 this.opened = false;
17601 },
17602
17603 _ensureSetup: function() {
17604 if (this._overlaySetup) {
17605 return;
17606 }
17607 this._overlaySetup = true;
17608 this.style.outline = 'none';
17609 this.style.display = 'none';
17610 },
17611
17612 _openedChanged: function() {
17613 if (this.opened) {
17614 this.removeAttribute('aria-hidden');
17615 } else {
17616 this.setAttribute('aria-hidden', 'true');
17617 }
17618
17619 // wait to call after ready only if we're initially open
17620 if (!this._overlaySetup) {
17621 return;
17622 }
17623
17624 if (this.__openChangedAsync) {
17625 window.cancelAnimationFrame(this.__openChangedAsync);
17626 }
17627
17628 // Synchronously remove the overlay.
17629 // The adding is done asynchronously to go out of the scope of the event
17630 // which might have generated the opening.
17631 if (!this.opened) {
17632 this._manager.removeOverlay(this);
17633 }
17634
17635 // Defer any animation-related code on attached
17636 // (_openedChanged gets called again on attached).
17637 if (!this.isAttached) {
17638 return;
17639 }
17640
17641 this.__isAnimating = true;
17642
17643 // requestAnimationFrame for non-blocking rendering
17644 this.__openChangedAsync = window.requestAnimationFrame(function() {
17645 this.__openChangedAsync = null;
17646 if (this.opened) {
17647 this._manager.addOverlay(this);
17648 this._prepareRenderOpened();
17649 this._renderOpened();
17650 } else {
17651 // Move the focus before actually closing.
17652 this._applyFocus();
17653 this._renderClosed();
17654 }
17655 }.bind(this));
17656 },
17657
17658 _canceledChanged: function() {
17659 this.closingReason = this.closingReason || {};
17660 this.closingReason.canceled = this.canceled;
17661 },
17662
17663 _withBackdropChanged: function() {
17664 // If tabindex is already set, no need to override it.
17665 if (this.withBackdrop && !this.hasAttribute('tabindex')) {
17666 this.setAttribute('tabindex', '-1');
17667 this.__shouldRemoveTabIndex = true;
17668 } else if (this.__shouldRemoveTabIndex) {
17669 this.removeAttribute('tabindex');
17670 this.__shouldRemoveTabIndex = false;
17671 }
17672 if (this.opened && this.isAttached) {
17673 this._manager.trackBackdrop();
17674 }
17675 },
17676
17677 /**
17678 * tasks which must occur before opening; e.g. making the element visible.
17679 * @protected
17680 */
17681 _prepareRenderOpened: function() {
17682
17683 // Needed to calculate the size of the overlay so that transitions on its size
17684 // will have the correct starting points.
17685 this._preparePositioning();
17686 this.refit();
17687 this._finishPositioning();
17688
17689 // Move the focus to the child node with [autofocus].
17690 this._applyFocus();
17691
17692 // Safari will apply the focus to the autofocus element when displayed for the first time,
17693 // so we blur it. Later, _applyFocus will set the focus if necessary.
17694 if (this.noAutoFocus && document.activeElement === this._focusNode) {
17695 this._focusNode.blur();
17696 }
17697 },
17698
17699 /**
17700 * Tasks which cause the overlay to actually open; typically play an animati on.
17701 * @protected
17702 */
17703 _renderOpened: function() {
17704 this._finishRenderOpened();
17705 },
17706
17707 /**
17708 * Tasks which cause the overlay to actually close; typically play an animat ion.
17709 * @protected
17710 */
17711 _renderClosed: function() {
17712 this._finishRenderClosed();
17713 },
17714
17715 /**
17716 * Tasks to be performed at the end of open action. Will fire `iron-overlay- opened`.
17717 * @protected
17718 */
17719 _finishRenderOpened: function() {
17720
17721 this.notifyResize();
17722 this.__isAnimating = false;
17723
17724 // Store it so we don't query too much.
17725 var focusableNodes = this._focusableNodes;
17726 this.__firstFocusableNode = focusableNodes[0];
17727 this.__lastFocusableNode = focusableNodes[focusableNodes.length - 1];
17728
17729 this.fire('iron-overlay-opened');
17730 },
17731
17732 /**
17733 * Tasks to be performed at the end of close action. Will fire `iron-overlay -closed`.
17734 * @protected
17735 */
17736 _finishRenderClosed: function() {
17737 // Hide the overlay and remove the backdrop.
17738 this.style.display = 'none';
17739 // Reset z-index only at the end of the animation.
17740 this.style.zIndex = '';
17741
17742 this.notifyResize();
17743 this.__isAnimating = false;
17744 this.fire('iron-overlay-closed', this.closingReason);
17745 },
17746
17747 _preparePositioning: function() {
17748 this.style.transition = this.style.webkitTransition = 'none';
17749 this.style.transform = this.style.webkitTransform = 'none';
17750 this.style.display = '';
17751 },
17752
17753 _finishPositioning: function() {
17754 // First, make it invisible & reactivate animations.
17755 this.style.display = 'none';
17756 // Force reflow before re-enabling animations so that they don't start.
17757 // Set scrollTop to itself so that Closure Compiler doesn't remove this.
17758 this.scrollTop = this.scrollTop;
17759 this.style.transition = this.style.webkitTransition = '';
17760 this.style.transform = this.style.webkitTransform = '';
17761 // Now that animations are enabled, make it visible again
17762 this.style.display = '';
17763 // Force reflow, so that following animations are properly started.
17764 // Set scrollTop to itself so that Closure Compiler doesn't remove this.
17765 this.scrollTop = this.scrollTop;
17766 },
17767
17768 /**
17769 * Applies focus according to the opened state.
17770 * @protected
17771 */
17772 _applyFocus: function() {
17773 if (this.opened) {
17774 if (!this.noAutoFocus) {
17775 this._focusNode.focus();
17776 }
17777 } else {
17778 this._focusNode.blur();
17779 this._focusedChild = null;
17780 this._manager.focusOverlay();
17781 }
17782 },
17783
17784 /**
17785 * Cancels (closes) the overlay. Call when click happens outside the overlay .
17786 * @param {!Event} event
17787 * @protected
17788 */
17789 _onCaptureClick: function(event) {
17790 if (!this.noCancelOnOutsideClick) {
17791 this.cancel(event);
17792 }
17793 },
17794
17795 /**
17796 * Keeps track of the focused child. If withBackdrop, traps focus within ove rlay.
17797 * @param {!Event} event
17798 * @protected
17799 */
17800 _onCaptureFocus: function (event) {
17801 if (!this.withBackdrop) {
17802 return;
17803 }
17804 var path = Polymer.dom(event).path;
17805 if (path.indexOf(this) === -1) {
17806 event.stopPropagation();
17807 this._applyFocus();
17808 } else {
17809 this._focusedChild = path[0];
17810 }
17811 },
17812
17813 /**
17814 * Handles the ESC key event and cancels (closes) the overlay.
17815 * @param {!Event} event
17816 * @protected
17817 */
17818 _onCaptureEsc: function(event) {
17819 if (!this.noCancelOnEscKey) {
17820 this.cancel(event);
17821 }
17822 },
17823
17824 /**
17825 * Handles TAB key events to track focus changes.
17826 * Will wrap focus for overlays withBackdrop.
17827 * @param {!Event} event
17828 * @protected
17829 */
17830 _onCaptureTab: function(event) {
17831 if (!this.withBackdrop) {
17832 return;
17833 }
17834 // TAB wraps from last to first focusable.
17835 // Shift + TAB wraps from first to last focusable.
17836 var shift = event.shiftKey;
17837 var nodeToCheck = shift ? this.__firstFocusableNode : this.__lastFocusable Node;
17838 var nodeToSet = shift ? this.__lastFocusableNode : this.__firstFocusableNo de;
17839 var shouldWrap = false;
17840 if (nodeToCheck === nodeToSet) {
17841 // If nodeToCheck is the same as nodeToSet, it means we have an overlay
17842 // with 0 or 1 focusables; in either case we still need to trap the
17843 // focus within the overlay.
17844 shouldWrap = true;
17845 } else {
17846 // In dom=shadow, the manager will receive focus changes on the main
17847 // root but not the ones within other shadow roots, so we can't rely on
17848 // _focusedChild, but we should check the deepest active element.
17849 var focusedNode = this._manager.deepActiveElement;
17850 // If the active element is not the nodeToCheck but the overlay itself,
17851 // it means the focus is about to go outside the overlay, hence we
17852 // should prevent that (e.g. user opens the overlay and hit Shift+TAB).
17853 shouldWrap = (focusedNode === nodeToCheck || focusedNode === this);
17854 }
17855
17856 if (shouldWrap) {
17857 // When the overlay contains the last focusable element of the document
17858 // and it's already focused, pressing TAB would move the focus outside
17859 // the document (e.g. to the browser search bar). Similarly, when the
17860 // overlay contains the first focusable element of the document and it's
17861 // already focused, pressing Shift+TAB would move the focus outside the
17862 // document (e.g. to the browser search bar).
17863 // In both cases, we would not receive a focus event, but only a blur.
17864 // In order to achieve focus wrapping, we prevent this TAB event and
17865 // force the focus. This will also prevent the focus to temporarily move
17866 // outside the overlay, which might cause scrolling.
17867 event.preventDefault();
17868 this._focusedChild = nodeToSet;
17869 this._applyFocus();
17870 }
17871 },
17872
17873 /**
17874 * Refits if the overlay is opened and not animating.
17875 * @protected
17876 */
17877 _onIronResize: function() {
17878 if (this.__onIronResizeAsync) {
17879 window.cancelAnimationFrame(this.__onIronResizeAsync);
17880 this.__onIronResizeAsync = null;
17881 }
17882 if (this.opened && !this.__isAnimating) {
17883 this.__onIronResizeAsync = window.requestAnimationFrame(function() {
17884 this.__onIronResizeAsync = null;
17885 this.refit();
17886 }.bind(this));
17887 }
17888 },
17889
17890 /**
17891 * Will call notifyResize if overlay is opened.
17892 * Can be overridden in order to avoid multiple observers on the same node.
17893 * @protected
17894 */
17895 _onNodesChange: function() {
17896 if (this.opened && !this.__isAnimating) {
17897 this.notifyResize();
17898 }
17899 }
17900 };
17901
17902 /** @polymerBehavior */
17903 Polymer.IronOverlayBehavior = [Polymer.IronFitBehavior, Polymer.IronResizableB ehavior, Polymer.IronOverlayBehaviorImpl];
17904
17905 /**
17906 * Fired after the overlay opens.
17907 * @event iron-overlay-opened
17908 */
17909
17910 /**
17911 * Fired when the overlay is canceled, but before it is closed.
17912 * @event iron-overlay-canceled
17913 * @param {Event} event The closing of the overlay can be prevented
17914 * by calling `event.preventDefault()`. The `event.detail` is the original eve nt that
17915 * originated the canceling (e.g. ESC keyboard event or click event outside th e overlay).
17916 */
17917
17918 /**
17919 * Fired after the overlay closes.
17920 * @event iron-overlay-closed
17921 * @param {Event} event The `event.detail` is the `closingReason` property
17922 * (contains `canceled`, whether the overlay was canceled).
17923 */
17924
17925 })();
17926 </script>
17927
17928
17929 <dom-module id="paper-toast" assetpath="/res/imp/bower_components/paper-toast/">
17930 <template>
17931 <style>
17932 :host {
17933 display: block;
17934 position: fixed;
17935 background-color: var(--paper-toast-background-color, #323232);
17936 color: var(--paper-toast-color, #f1f1f1);
17937 min-height: 48px;
17938 min-width: 288px;
17939 padding: 16px 24px;
17940 box-sizing: border-box;
17941 box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.26);
17942 border-radius: 2px;
17943 margin: 12px;
17944 font-size: 14px;
17945 cursor: default;
17946 -webkit-transition: -webkit-transform 0.3s, opacity 0.3s;
17947 transition: transform 0.3s, opacity 0.3s;
17948 opacity: 0;
17949 -webkit-transform: translateY(100px);
17950 transform: translateY(100px);
17951 @apply(--paper-font-common-base);
17952 }
17953
17954 :host(.capsule) {
17955 border-radius: 24px;
17956 }
17957
17958 :host(.fit-bottom) {
17959 width: 100%;
17960 min-width: 0;
17961 border-radius: 0;
17962 margin: 0;
17963 }
17964
17965 :host(.paper-toast-open) {
17966 opacity: 1;
17967 -webkit-transform: translateY(0px);
17968 transform: translateY(0px);
17969 }
17970 </style>
17971
17972 <span id="label">{{text}}</span>
17973 <content></content>
17974 </template>
17975
17976 <script>
17977 (function() {
17978 // Keeps track of the toast currently opened.
17979 var currentToast = null;
17980
17981 Polymer({
17982 is: 'paper-toast',
17983
17984 behaviors: [
17985 Polymer.IronOverlayBehavior
17986 ],
17987
17988 properties: {
17989 /**
17990 * The element to fit `this` into.
17991 * Overridden from `Polymer.IronFitBehavior`.
17992 */
17993 fitInto: {
17994 type: Object,
17995 value: window,
17996 observer: '_onFitIntoChanged'
17997 },
17998
17999 /**
18000 * The orientation against which to align the dropdown content
18001 * horizontally relative to `positionTarget`.
18002 * Overridden from `Polymer.IronFitBehavior`.
18003 */
18004 horizontalAlign: {
18005 type: String,
18006 value: 'left'
18007 },
18008
18009 /**
18010 * The orientation against which to align the dropdown content
18011 * vertically relative to `positionTarget`.
18012 * Overridden from `Polymer.IronFitBehavior`.
18013 */
18014 verticalAlign: {
18015 type: String,
18016 value: 'bottom'
18017 },
18018
18019 /**
18020 * The duration in milliseconds to show the toast.
18021 * Set to `0`, a negative number, or `Infinity`, to disable the
18022 * toast auto-closing.
18023 */
18024 duration: {
18025 type: Number,
18026 value: 3000
18027 },
18028
18029 /**
18030 * The text to display in the toast.
18031 */
18032 text: {
18033 type: String,
18034 value: ''
18035 },
18036
18037 /**
18038 * Overridden from `IronOverlayBehavior`.
18039 * Set to false to enable closing of the toast by clicking outside it.
18040 */
18041 noCancelOnOutsideClick: {
18042 type: Boolean,
18043 value: true
18044 },
18045
18046 /**
18047 * Overridden from `IronOverlayBehavior`.
18048 * Set to true to disable auto-focusing the toast or child nodes with
18049 * the `autofocus` attribute` when the overlay is opened.
18050 */
18051 noAutoFocus: {
18052 type: Boolean,
18053 value: true
18054 }
18055 },
18056
18057 listeners: {
18058 'transitionend': '__onTransitionEnd'
18059 },
18060
18061 /**
18062 * Read-only. Deprecated. Use `opened` from `IronOverlayBehavior`.
18063 * @property visible
18064 * @deprecated
18065 */
18066 get visible() {
18067 Polymer.Base._warn('`visible` is deprecated, use `opened` instead');
18068 return this.opened;
18069 },
18070
18071 /**
18072 * Read-only. Can auto-close if duration is a positive finite number.
18073 * @property _canAutoClose
18074 */
18075 get _canAutoClose() {
18076 return this.duration > 0 && this.duration !== Infinity;
18077 },
18078
18079 created: function() {
18080 this._autoClose = null;
18081 Polymer.IronA11yAnnouncer.requestAvailability();
18082 },
18083
18084 /**
18085 * Show the toast. Without arguments, this is the same as `open()` from `IronOverlayBehavior`.
18086 * @param {(Object|string)=} properties Properties to be set before open ing the toast.
18087 * e.g. `toast.show('hello')` or `toast.show({text: 'hello', duration: 3 000})`
18088 */
18089 show: function(properties) {
18090 if (typeof properties == 'string') {
18091 properties = { text: properties };
18092 }
18093 for (var property in properties) {
18094 if (property.indexOf('_') === 0) {
18095 Polymer.Base._warn('The property "' + property + '" is private and was not set.');
18096 } else if (property in this) {
18097 this[property] = properties[property];
18098 } else {
18099 Polymer.Base._warn('The property "' + property + '" is not valid.' );
18100 }
18101 }
18102 this.open();
18103 },
18104
18105 /**
18106 * Hide the toast. Same as `close()` from `IronOverlayBehavior`.
18107 */
18108 hide: function() {
18109 this.close();
18110 },
18111
18112 /**
18113 * Called on transitions of the toast, indicating a finished animation
18114 * @private
18115 */
18116 __onTransitionEnd: function(e) {
18117 // there are different transitions that are happening when opening and
18118 // closing the toast. The last one so far is for `opacity`.
18119 // This marks the end of the transition, so we check for this to deter mine if this
18120 // is the correct event.
18121 if (e && e.target === this && e.propertyName === 'opacity') {
18122 if (this.opened) {
18123 this._finishRenderOpened();
18124 } else {
18125 this._finishRenderClosed();
18126 }
18127 }
18128 },
18129
18130 /**
18131 * Overridden from `IronOverlayBehavior`.
18132 * Called when the value of `opened` changes.
18133 */
18134 _openedChanged: function() {
18135 if (this._autoClose !== null) {
18136 this.cancelAsync(this._autoClose);
18137 this._autoClose = null;
18138 }
18139 if (this.opened) {
18140 if (currentToast && currentToast !== this) {
18141 currentToast.close();
18142 }
18143 currentToast = this;
18144 this.fire('iron-announce', {
18145 text: this.text
18146 });
18147 if (this._canAutoClose) {
18148 this._autoClose = this.async(this.close, this.duration);
18149 }
18150 } else if (currentToast === this) {
18151 currentToast = null;
18152 }
18153 Polymer.IronOverlayBehaviorImpl._openedChanged.apply(this, arguments);
18154 },
18155
18156 /**
18157 * Overridden from `IronOverlayBehavior`.
18158 */
18159 _renderOpened: function() {
18160 this.classList.add('paper-toast-open');
18161 },
18162
18163 /**
18164 * Overridden from `IronOverlayBehavior`.
18165 */
18166 _renderClosed: function() {
18167 this.classList.remove('paper-toast-open');
18168 },
18169
18170 /**
18171 * @private
18172 */
18173 _onFitIntoChanged: function(fitInto) {
18174 this.positionTarget = fitInto;
18175 }
18176
18177 /**
18178 * Fired when `paper-toast` is opened.
18179 *
18180 * @event 'iron-announce'
18181 * @param {{text: string}} detail Contains text that will be announced.
18182 */
18183 });
18184 })();
18185 </script>
18186 </dom-module>
18187 <dom-module id="url-param" assetpath="/res/imp/common/">
18188 <template>
18189 <paper-toast id="toast"></paper-toast>
18190 </template>
18191 <script>
18192 (function(){
18193 Polymer({
18194 is: 'url-param',
18195 properties: {
18196 default_value: {
18197 type: String,
18198 },
18199 default_values: {
18200 type: Array,
18201 },
18202 multi: {
18203 type: Boolean,
18204 value: false,
18205 },
18206 name: {
18207 type: String,
18208 },
18209 valid: {
18210 type: Array,
18211 },
18212 value: {
18213 type: String,
18214 value: '',
18215 notify: true,
18216 observer: 'valueChanged',
18217 },
18218
18219 _loaded: {
18220 type: Boolean,
18221 value: false,
18222 }
18223 },
18224 // Listens to array changes for multi urls
18225 observers: ["valueChanged(value.splices)"],
18226
18227 ready: function () {
18228 this._loaded = true;
18229
18230 // Read the URL parameters. If our variable is set, save its value.
18231 // Otherwise, place our value in the URL.
18232 var val = this.getURL();
18233 if (val && this.isValid(val)) {
18234 this.set('value', val);
18235 } else if (this.default_value && this.isValid(this.default_value)) {
18236 this.set('value', this.default_value);
18237 }
18238 else if (this.multi && this.default_values && this.isValid(this.default_ values)) {
18239 this.set('value', this.default_values);
18240 }
18241 else {
18242 this.putURL();
18243 }
18244 },
18245 // Retrieve the value for our variable from the URL.
18246 getURL: function () {
18247 var vals = sk.query.toParamSet(window.location.search.substring(1))[this .name];
18248 if (!vals) {
18249 return null;
18250 }
18251 if (this.multi) {
18252 return vals;
18253 }
18254 if (vals.length > 1) {
18255 this.error('Multiple values provided for ' + this.name + ' but only on e accepted: ' + vals);
18256 return null;
18257 }
18258 return vals[0];
18259 },
18260 // Store the value for our variable in the URL.
18261 putURL: function () {
18262 var params = sk.query.toParamSet(window.location.search.substring(1));
18263 delete params[this.name];
18264 if (!this.value || Array.isArray(this.value) && this.value.length == 0) {
18265 } else
18266 // Don't insert undefined/empty values.
18267 {
18268 if (this.multi) {
18269 params[this.name] = this.value;
18270 } else {
18271 params[this.name] = [this.value];
18272 }
18273 }
18274 var newUrl = window.location.href.split('?')[0] + '?' + sk.query.fromPar amSet(params);
18275 window.history.replaceState('', '', newUrl);
18276 },
18277 // Check to see whether the given value is valid.
18278 isValid: function (val) {
18279 var checkValid = function (val) {
18280 if (this.valid) {
18281 for (var i = 0; i < this.valid.length; i++) {
18282 if (val == this.valid[i]) {
18283 return true;
18284 }
18285 }
18286 this.error('Invalid value for ' + this.name + ': "' + val + '". Must be one of: ' + this.valid);
18287 return false;
18288 }
18289 return true;
18290 }.bind(this);
18291 if (this.multi) {
18292 // Verify that it's an array and that all elements are valid.
18293 if (!Array.isArray(val)) {
18294 this.error('url-param-sk: Value is not an array: ' + val);
18295 return false;
18296 }
18297 for (var i = 0; i < val.length; i++) {
18298 if (!checkValid(val[i])) {
18299 return false;
18300 }
18301 }
18302 } else {
18303 if (Array.isArray(val)) {
18304 this.error('Multiple values provided for ' + this.name + ' but only one accepted: ' + val);
18305 }
18306 return checkValid(val);
18307 }
18308 return true;
18309 },
18310 valueChanged: function () {
18311 if (this._loaded) {
18312 // Save our value to the URL.
18313 this.putURL();
18314 }
18315 },
18316 error: function (msg) {
18317 console.log('[ERROR] '+msg);
18318 this.set('$.toast.text', msg);
18319 this.$.toast.show();
18320 }
18321 });
18322 })()
18323 </script>
18324 </dom-module>
18325 <script>
15642 18326
15643 /** 18327 /**
15644 * @param {!Function} selectCallback 18328 * @param {!Function} selectCallback
15645 * @constructor 18329 * @constructor
15646 */ 18330 */
15647 Polymer.IronSelection = function(selectCallback) { 18331 Polymer.IronSelection = function(selectCallback) {
15648 this.selection = []; 18332 this.selection = [];
15649 this.selectCallback = selectCallback; 18333 this.selectCallback = selectCallback;
15650 }; 18334 };
15651 18335
(...skipping 1050 matching lines...) Expand 10 before | Expand all | Expand 10 after
16702 19386
16703 /** @polymerBehavior Polymer.IronCheckedElementBehavior */ 19387 /** @polymerBehavior Polymer.IronCheckedElementBehavior */
16704 Polymer.IronCheckedElementBehavior = [ 19388 Polymer.IronCheckedElementBehavior = [
16705 Polymer.IronFormElementBehavior, 19389 Polymer.IronFormElementBehavior,
16706 Polymer.IronValidatableBehavior, 19390 Polymer.IronValidatableBehavior,
16707 Polymer.IronCheckedElementBehaviorImpl 19391 Polymer.IronCheckedElementBehaviorImpl
16708 ]; 19392 ];
16709 19393
16710 </script> 19394 </script>
16711 <script> 19395 <script>
16712 (function() {
16713 'use strict';
16714
16715 /**
16716 * Chrome uses an older version of DOM Level 3 Keyboard Events
16717 *
16718 * Most keys are labeled as text, but some are Unicode codepoints.
16719 * Values taken from: http://www.w3.org/TR/2007/WD-DOM-Level-3-Events-200712 21/keyset.html#KeySet-Set
16720 */
16721 var KEY_IDENTIFIER = {
16722 'U+0008': 'backspace',
16723 'U+0009': 'tab',
16724 'U+001B': 'esc',
16725 'U+0020': 'space',
16726 'U+007F': 'del'
16727 };
16728
16729 /**
16730 * Special table for KeyboardEvent.keyCode.
16731 * KeyboardEvent.keyIdentifier is better, and KeyBoardEvent.key is even bett er
16732 * than that.
16733 *
16734 * Values from: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEve nt.keyCode#Value_of_keyCode
16735 */
16736 var KEY_CODE = {
16737 8: 'backspace',
16738 9: 'tab',
16739 13: 'enter',
16740 27: 'esc',
16741 33: 'pageup',
16742 34: 'pagedown',
16743 35: 'end',
16744 36: 'home',
16745 32: 'space',
16746 37: 'left',
16747 38: 'up',
16748 39: 'right',
16749 40: 'down',
16750 46: 'del',
16751 106: '*'
16752 };
16753
16754 /**
16755 * MODIFIER_KEYS maps the short name for modifier keys used in a key
16756 * combo string to the property name that references those same keys
16757 * in a KeyboardEvent instance.
16758 */
16759 var MODIFIER_KEYS = {
16760 'shift': 'shiftKey',
16761 'ctrl': 'ctrlKey',
16762 'alt': 'altKey',
16763 'meta': 'metaKey'
16764 };
16765
16766 /**
16767 * KeyboardEvent.key is mostly represented by printable character made by
16768 * the keyboard, with unprintable keys labeled nicely.
16769 *
16770 * However, on OS X, Alt+char can make a Unicode character that follows an
16771 * Apple-specific mapping. In this case, we fall back to .keyCode.
16772 */
16773 var KEY_CHAR = /[a-z0-9*]/;
16774
16775 /**
16776 * Matches a keyIdentifier string.
16777 */
16778 var IDENT_CHAR = /U\+/;
16779
16780 /**
16781 * Matches arrow keys in Gecko 27.0+
16782 */
16783 var ARROW_KEY = /^arrow/;
16784
16785 /**
16786 * Matches space keys everywhere (notably including IE10's exceptional name
16787 * `spacebar`).
16788 */
16789 var SPACE_KEY = /^space(bar)?/;
16790
16791 /**
16792 * Matches ESC key.
16793 *
16794 * Value from: http://w3c.github.io/uievents-key/#key-Escape
16795 */
16796 var ESC_KEY = /^escape$/;
16797
16798 /**
16799 * Transforms the key.
16800 * @param {string} key The KeyBoardEvent.key
16801 * @param {Boolean} [noSpecialChars] Limits the transformation to
16802 * alpha-numeric characters.
16803 */
16804 function transformKey(key, noSpecialChars) {
16805 var validKey = '';
16806 if (key) {
16807 var lKey = key.toLowerCase();
16808 if (lKey === ' ' || SPACE_KEY.test(lKey)) {
16809 validKey = 'space';
16810 } else if (ESC_KEY.test(lKey)) {
16811 validKey = 'esc';
16812 } else if (lKey.length == 1) {
16813 if (!noSpecialChars || KEY_CHAR.test(lKey)) {
16814 validKey = lKey;
16815 }
16816 } else if (ARROW_KEY.test(lKey)) {
16817 validKey = lKey.replace('arrow', '');
16818 } else if (lKey == 'multiply') {
16819 // numpad '*' can map to Multiply on IE/Windows
16820 validKey = '*';
16821 } else {
16822 validKey = lKey;
16823 }
16824 }
16825 return validKey;
16826 }
16827
16828 function transformKeyIdentifier(keyIdent) {
16829 var validKey = '';
16830 if (keyIdent) {
16831 if (keyIdent in KEY_IDENTIFIER) {
16832 validKey = KEY_IDENTIFIER[keyIdent];
16833 } else if (IDENT_CHAR.test(keyIdent)) {
16834 keyIdent = parseInt(keyIdent.replace('U+', '0x'), 16);
16835 validKey = String.fromCharCode(keyIdent).toLowerCase();
16836 } else {
16837 validKey = keyIdent.toLowerCase();
16838 }
16839 }
16840 return validKey;
16841 }
16842
16843 function transformKeyCode(keyCode) {
16844 var validKey = '';
16845 if (Number(keyCode)) {
16846 if (keyCode >= 65 && keyCode <= 90) {
16847 // ascii a-z
16848 // lowercase is 32 offset from uppercase
16849 validKey = String.fromCharCode(32 + keyCode);
16850 } else if (keyCode >= 112 && keyCode <= 123) {
16851 // function keys f1-f12
16852 validKey = 'f' + (keyCode - 112);
16853 } else if (keyCode >= 48 && keyCode <= 57) {
16854 // top 0-9 keys
16855 validKey = String(keyCode - 48);
16856 } else if (keyCode >= 96 && keyCode <= 105) {
16857 // num pad 0-9
16858 validKey = String(keyCode - 96);
16859 } else {
16860 validKey = KEY_CODE[keyCode];
16861 }
16862 }
16863 return validKey;
16864 }
16865
16866 /**
16867 * Calculates the normalized key for a KeyboardEvent.
16868 * @param {KeyboardEvent} keyEvent
16869 * @param {Boolean} [noSpecialChars] Set to true to limit keyEvent.key
16870 * transformation to alpha-numeric chars. This is useful with key
16871 * combinations like shift + 2, which on FF for MacOS produces
16872 * keyEvent.key = @
16873 * To get 2 returned, set noSpecialChars = true
16874 * To get @ returned, set noSpecialChars = false
16875 */
16876 function normalizedKeyForEvent(keyEvent, noSpecialChars) {
16877 // Fall back from .key, to .keyIdentifier, to .keyCode, and then to
16878 // .detail.key to support artificial keyboard events.
16879 return transformKey(keyEvent.key, noSpecialChars) ||
16880 transformKeyIdentifier(keyEvent.keyIdentifier) ||
16881 transformKeyCode(keyEvent.keyCode) ||
16882 transformKey(keyEvent.detail ? keyEvent.detail.key : keyEvent.detail, no SpecialChars) || '';
16883 }
16884
16885 function keyComboMatchesEvent(keyCombo, event) {
16886 // For combos with modifiers we support only alpha-numeric keys
16887 var keyEvent = normalizedKeyForEvent(event, keyCombo.hasModifiers);
16888 return keyEvent === keyCombo.key &&
16889 (!keyCombo.hasModifiers || (
16890 !!event.shiftKey === !!keyCombo.shiftKey &&
16891 !!event.ctrlKey === !!keyCombo.ctrlKey &&
16892 !!event.altKey === !!keyCombo.altKey &&
16893 !!event.metaKey === !!keyCombo.metaKey)
16894 );
16895 }
16896
16897 function parseKeyComboString(keyComboString) {
16898 if (keyComboString.length === 1) {
16899 return {
16900 combo: keyComboString,
16901 key: keyComboString,
16902 event: 'keydown'
16903 };
16904 }
16905 return keyComboString.split('+').reduce(function(parsedKeyCombo, keyComboP art) {
16906 var eventParts = keyComboPart.split(':');
16907 var keyName = eventParts[0];
16908 var event = eventParts[1];
16909
16910 if (keyName in MODIFIER_KEYS) {
16911 parsedKeyCombo[MODIFIER_KEYS[keyName]] = true;
16912 parsedKeyCombo.hasModifiers = true;
16913 } else {
16914 parsedKeyCombo.key = keyName;
16915 parsedKeyCombo.event = event || 'keydown';
16916 }
16917
16918 return parsedKeyCombo;
16919 }, {
16920 combo: keyComboString.split(':').shift()
16921 });
16922 }
16923
16924 function parseEventString(eventString) {
16925 return eventString.trim().split(' ').map(function(keyComboString) {
16926 return parseKeyComboString(keyComboString);
16927 });
16928 }
16929
16930 /**
16931 * `Polymer.IronA11yKeysBehavior` provides a normalized interface for proces sing
16932 * keyboard commands that pertain to [WAI-ARIA best practices](http://www.w3 .org/TR/wai-aria-practices/#kbd_general_binding).
16933 * The element takes care of browser differences with respect to Keyboard ev ents
16934 * and uses an expressive syntax to filter key presses.
16935 *
16936 * Use the `keyBindings` prototype property to express what combination of k eys
16937 * will trigger the callback. A key binding has the format
16938 * `"KEY+MODIFIER:EVENT": "callback"` (`"KEY": "callback"` or
16939 * `"KEY:EVENT": "callback"` are valid as well). Some examples:
16940 *
16941 * keyBindings: {
16942 * 'space': '_onKeydown', // same as 'space:keydown'
16943 * 'shift+tab': '_onKeydown',
16944 * 'enter:keypress': '_onKeypress',
16945 * 'esc:keyup': '_onKeyup'
16946 * }
16947 *
16948 * The callback will receive with an event containing the following informat ion in `event.detail`:
16949 *
16950 * _onKeydown: function(event) {
16951 * console.log(event.detail.combo); // KEY+MODIFIER, e.g. "shift+tab"
16952 * console.log(event.detail.key); // KEY only, e.g. "tab"
16953 * console.log(event.detail.event); // EVENT, e.g. "keydown"
16954 * console.log(event.detail.keyboardEvent); // the original KeyboardE vent
16955 * }
16956 *
16957 * Use the `keyEventTarget` attribute to set up event handlers on a specific
16958 * node.
16959 *
16960 * See the [demo source code](https://github.com/PolymerElements/iron-a11y-k eys-behavior/blob/master/demo/x-key-aware.html)
16961 * for an example.
16962 *
16963 * @demo demo/index.html
16964 * @polymerBehavior
16965 */
16966 Polymer.IronA11yKeysBehavior = {
16967 properties: {
16968 /**
16969 * The EventTarget that will be firing relevant KeyboardEvents. Set it t o
16970 * `null` to disable the listeners.
16971 * @type {?EventTarget}
16972 */
16973 keyEventTarget: {
16974 type: Object,
16975 value: function() {
16976 return this;
16977 }
16978 },
16979
16980 /**
16981 * If true, this property will cause the implementing element to
16982 * automatically stop propagation on any handled KeyboardEvents.
16983 */
16984 stopKeyboardEventPropagation: {
16985 type: Boolean,
16986 value: false
16987 },
16988
16989 _boundKeyHandlers: {
16990 type: Array,
16991 value: function() {
16992 return [];
16993 }
16994 },
16995
16996 // We use this due to a limitation in IE10 where instances will have
16997 // own properties of everything on the "prototype".
16998 _imperativeKeyBindings: {
16999 type: Object,
17000 value: function() {
17001 return {};
17002 }
17003 }
17004 },
17005
17006 observers: [
17007 '_resetKeyEventListeners(keyEventTarget, _boundKeyHandlers)'
17008 ],
17009
17010
17011 /**
17012 * To be used to express what combination of keys will trigger the relati ve
17013 * callback. e.g. `keyBindings: { 'esc': '_onEscPressed'}`
17014 * @type {Object}
17015 */
17016 keyBindings: {},
17017
17018 registered: function() {
17019 this._prepKeyBindings();
17020 },
17021
17022 attached: function() {
17023 this._listenKeyEventListeners();
17024 },
17025
17026 detached: function() {
17027 this._unlistenKeyEventListeners();
17028 },
17029
17030 /**
17031 * Can be used to imperatively add a key binding to the implementing
17032 * element. This is the imperative equivalent of declaring a keybinding
17033 * in the `keyBindings` prototype property.
17034 */
17035 addOwnKeyBinding: function(eventString, handlerName) {
17036 this._imperativeKeyBindings[eventString] = handlerName;
17037 this._prepKeyBindings();
17038 this._resetKeyEventListeners();
17039 },
17040
17041 /**
17042 * When called, will remove all imperatively-added key bindings.
17043 */
17044 removeOwnKeyBindings: function() {
17045 this._imperativeKeyBindings = {};
17046 this._prepKeyBindings();
17047 this._resetKeyEventListeners();
17048 },
17049
17050 /**
17051 * Returns true if a keyboard event matches `eventString`.
17052 *
17053 * @param {KeyboardEvent} event
17054 * @param {string} eventString
17055 * @return {boolean}
17056 */
17057 keyboardEventMatchesKeys: function(event, eventString) {
17058 var keyCombos = parseEventString(eventString);
17059 for (var i = 0; i < keyCombos.length; ++i) {
17060 if (keyComboMatchesEvent(keyCombos[i], event)) {
17061 return true;
17062 }
17063 }
17064 return false;
17065 },
17066
17067 _collectKeyBindings: function() {
17068 var keyBindings = this.behaviors.map(function(behavior) {
17069 return behavior.keyBindings;
17070 });
17071
17072 if (keyBindings.indexOf(this.keyBindings) === -1) {
17073 keyBindings.push(this.keyBindings);
17074 }
17075
17076 return keyBindings;
17077 },
17078
17079 _prepKeyBindings: function() {
17080 this._keyBindings = {};
17081
17082 this._collectKeyBindings().forEach(function(keyBindings) {
17083 for (var eventString in keyBindings) {
17084 this._addKeyBinding(eventString, keyBindings[eventString]);
17085 }
17086 }, this);
17087
17088 for (var eventString in this._imperativeKeyBindings) {
17089 this._addKeyBinding(eventString, this._imperativeKeyBindings[eventStri ng]);
17090 }
17091
17092 // Give precedence to combos with modifiers to be checked first.
17093 for (var eventName in this._keyBindings) {
17094 this._keyBindings[eventName].sort(function (kb1, kb2) {
17095 var b1 = kb1[0].hasModifiers;
17096 var b2 = kb2[0].hasModifiers;
17097 return (b1 === b2) ? 0 : b1 ? -1 : 1;
17098 })
17099 }
17100 },
17101
17102 _addKeyBinding: function(eventString, handlerName) {
17103 parseEventString(eventString).forEach(function(keyCombo) {
17104 this._keyBindings[keyCombo.event] =
17105 this._keyBindings[keyCombo.event] || [];
17106
17107 this._keyBindings[keyCombo.event].push([
17108 keyCombo,
17109 handlerName
17110 ]);
17111 }, this);
17112 },
17113
17114 _resetKeyEventListeners: function() {
17115 this._unlistenKeyEventListeners();
17116
17117 if (this.isAttached) {
17118 this._listenKeyEventListeners();
17119 }
17120 },
17121
17122 _listenKeyEventListeners: function() {
17123 if (!this.keyEventTarget) {
17124 return;
17125 }
17126 Object.keys(this._keyBindings).forEach(function(eventName) {
17127 var keyBindings = this._keyBindings[eventName];
17128 var boundKeyHandler = this._onKeyBindingEvent.bind(this, keyBindings);
17129
17130 this._boundKeyHandlers.push([this.keyEventTarget, eventName, boundKeyH andler]);
17131
17132 this.keyEventTarget.addEventListener(eventName, boundKeyHandler);
17133 }, this);
17134 },
17135
17136 _unlistenKeyEventListeners: function() {
17137 var keyHandlerTuple;
17138 var keyEventTarget;
17139 var eventName;
17140 var boundKeyHandler;
17141
17142 while (this._boundKeyHandlers.length) {
17143 // My kingdom for block-scope binding and destructuring assignment..
17144 keyHandlerTuple = this._boundKeyHandlers.pop();
17145 keyEventTarget = keyHandlerTuple[0];
17146 eventName = keyHandlerTuple[1];
17147 boundKeyHandler = keyHandlerTuple[2];
17148
17149 keyEventTarget.removeEventListener(eventName, boundKeyHandler);
17150 }
17151 },
17152
17153 _onKeyBindingEvent: function(keyBindings, event) {
17154 if (this.stopKeyboardEventPropagation) {
17155 event.stopPropagation();
17156 }
17157
17158 // if event has been already prevented, don't do anything
17159 if (event.defaultPrevented) {
17160 return;
17161 }
17162
17163 for (var i = 0; i < keyBindings.length; i++) {
17164 var keyCombo = keyBindings[i][0];
17165 var handlerName = keyBindings[i][1];
17166 if (keyComboMatchesEvent(keyCombo, event)) {
17167 this._triggerKeyHandler(keyCombo, handlerName, event);
17168 // exit the loop if eventDefault was prevented
17169 if (event.defaultPrevented) {
17170 return;
17171 }
17172 }
17173 }
17174 },
17175
17176 _triggerKeyHandler: function(keyCombo, handlerName, keyboardEvent) {
17177 var detail = Object.create(keyCombo);
17178 detail.keyboardEvent = keyboardEvent;
17179 var event = new CustomEvent(keyCombo.event, {
17180 detail: detail,
17181 cancelable: true
17182 });
17183 this[handlerName].call(this, event);
17184 if (event.defaultPrevented) {
17185 keyboardEvent.preventDefault();
17186 }
17187 }
17188 };
17189 })();
17190 </script>
17191 <script>
17192 19396
17193 /** 19397 /**
17194 * @demo demo/index.html 19398 * @demo demo/index.html
17195 * @polymerBehavior 19399 * @polymerBehavior
17196 */ 19400 */
17197 Polymer.IronControlState = { 19401 Polymer.IronControlState = {
17198 19402
17199 properties: { 19403 properties: {
17200 19404
17201 /** 19405 /**
(...skipping 1511 matching lines...) Expand 10 before | Expand all | Expand 10 after
18713 var label = this.getAttribute('aria-label'); 20917 var label = this.getAttribute('aria-label');
18714 20918
18715 // Don't stomp over a user-set aria-label. 20919 // Don't stomp over a user-set aria-label.
18716 if (!label || oldValue == label) { 20920 if (!label || oldValue == label) {
18717 this.setAttribute('aria-label', newValue); 20921 this.setAttribute('aria-label', newValue);
18718 } 20922 }
18719 } 20923 }
18720 }); 20924 });
18721 </script> 20925 </script>
18722 </dom-module> 20926 </dom-module>
18723
18724
18725 <dom-module id="iron-a11y-announcer" assetpath="/res/imp/bower_components/iron-a 11y-announcer/">
18726 <template>
18727 <style>
18728 :host {
18729 display: inline-block;
18730 position: fixed;
18731 clip: rect(0px,0px,0px,0px);
18732 }
18733 </style>
18734 <div aria-live$="[[mode]]">[[_text]]</div>
18735 </template>
18736
18737 <script>
18738
18739 (function() {
18740 'use strict';
18741
18742 Polymer.IronA11yAnnouncer = Polymer({
18743 is: 'iron-a11y-announcer',
18744
18745 properties: {
18746
18747 /**
18748 * The value of mode is used to set the `aria-live` attribute
18749 * for the element that will be announced. Valid values are: `off`,
18750 * `polite` and `assertive`.
18751 */
18752 mode: {
18753 type: String,
18754 value: 'polite'
18755 },
18756
18757 _text: {
18758 type: String,
18759 value: ''
18760 }
18761 },
18762
18763 created: function() {
18764 if (!Polymer.IronA11yAnnouncer.instance) {
18765 Polymer.IronA11yAnnouncer.instance = this;
18766 }
18767
18768 document.body.addEventListener('iron-announce', this._onIronAnnounce.b ind(this));
18769 },
18770
18771 /**
18772 * Cause a text string to be announced by screen readers.
18773 *
18774 * @param {string} text The text that should be announced.
18775 */
18776 announce: function(text) {
18777 this._text = '';
18778 this.async(function() {
18779 this._text = text;
18780 }, 100);
18781 },
18782
18783 _onIronAnnounce: function(event) {
18784 if (event.detail && event.detail.text) {
18785 this.announce(event.detail.text);
18786 }
18787 }
18788 });
18789
18790 Polymer.IronA11yAnnouncer.instance = null;
18791
18792 Polymer.IronA11yAnnouncer.requestAvailability = function() {
18793 if (!Polymer.IronA11yAnnouncer.instance) {
18794 Polymer.IronA11yAnnouncer.instance = document.createElement('iron-a11y -announcer');
18795 }
18796
18797 document.body.appendChild(Polymer.IronA11yAnnouncer.instance);
18798 };
18799 })();
18800
18801 </script>
18802 </dom-module>
18803 <script> 20927 <script>
18804 20928
18805 /* 20929 /*
18806 `<iron-input>` adds two-way binding and custom validators using `Polymer.IronVal idatorBehavior` 20930 `<iron-input>` adds two-way binding and custom validators using `Polymer.IronVal idatorBehavior`
18807 to `<input>`. 20931 to `<input>`.
18808 20932
18809 ### Two-way binding 20933 ### Two-way binding
18810 20934
18811 By default you can only get notified of changes to an `input`'s `value` due to u ser input: 20935 By default you can only get notified of changes to an `input`'s `value` due to u ser input:
18812 20936
(...skipping 1998 matching lines...) Expand 10 before | Expand all | Expand 10 after
20811 margin: 2px; 22935 margin: 2px;
20812 /* See https://sites.google.com/a/google.com/skia-infrastructure/design- docs/general-design-guidance */ 22936 /* See https://sites.google.com/a/google.com/skia-infrastructure/design- docs/general-design-guidance */
20813 --paper-checkbox-checked-color: black; 22937 --paper-checkbox-checked-color: black;
20814 --paper-checkbox-checked-ink-color: black; 22938 --paper-checkbox-checked-ink-color: black;
20815 --paper-checkbox-unchecked-color: black; 22939 --paper-checkbox-unchecked-color: black;
20816 --paper-checkbox-unchecked-ink-color: black; 22940 --paper-checkbox-unchecked-ink-color: black;
20817 --paper-checkbox-label-color: black; 22941 --paper-checkbox-label-color: black;
20818 } 22942 }
20819 </style> 22943 </style>
20820 22944
22945 <url-param name="filters" value="{{_filters}}" default_values="[]" multi="">
22946 </url-param>
22947 <url-param name="columns" value="{{columns}}" default_values="[&quot;id&quot ;,&quot;os&quot;,&quot;task&quot;,&quot;status&quot;]" multi="">
22948 </url-param>
22949 <url-param name="query" value="{{_query}}">
22950 </url-param>
22951 <url-param name="verbose" value="{{verbose}}">
22952 </url-param>
22953 <url-param name="limit" default_value="200" value="{{limit}}">
22954 </url-param>
22955
20821 <div class="container horizontal layout"> 22956 <div class="container horizontal layout">
20822 22957
20823 22958
20824 <div class="narrow-down-selector"> 22959 <div class="narrow-down-selector">
20825 <div> 22960 <div>
20826 <paper-input id="filter" label="Search columns and filters" placeholde r="gpu nvidia" value="{{_query}}"></paper-input> 22961 <paper-input id="filter" label="Search columns and filters" placeholde r="gpu nvidia" value="{{_query}}">
22962 </paper-input>
20827 </div> 22963 </div>
20828 22964
20829 <div class="selector side-by-side"> 22965 <div class="selector side-by-side" title="This shows all bot dimension n ames and other interesting bot properties. Mark the check box to add as a column . Select the row to see filter options.">
20830 <iron-selector attr-for-selected="label" selected="{{_primarySelected} }"> 22966 <iron-selector attr-for-selected="label" selected="{{_primarySelected} }">
20831 <template is="dom-repeat" items="[[_primaryItems]]" as="item"> 22967 <template is="dom-repeat" items="[[_primaryItems]]" as="item">
20832 <div class="selectable item horizontal layout" label="[[item]]"> 22968 <div class="selectable item horizontal layout" label="[[item]]">
20833 22969
20834 <span>[[_beforeBold(item,_query)]]<span class="bold">[[_bold(ite m,_query)]]</span>[[_afterBold(item,_query)]]</span> 22970 <span>[[_beforeBold(item,_query)]]<span class="bold">[[_bold(ite m,_query)]]</span>[[_afterBold(item,_query)]]</span>
20835 <span class="flex"></span> 22971 <span class="flex"></span>
20836 <paper-checkbox noink="" disabled$="[[_cantToggleColumn(item)]]" checked="[[_columnState(item,columns.*)]]" on-change="_toggleColumn"> 22972 <paper-checkbox noink="" disabled$="[[_cantToggleColumn(item)]]" checked="[[_columnState(item,columns.*)]]" on-change="_toggleColumn">
20837 </paper-checkbox> 22973 </paper-checkbox>
20838 </div> 22974 </div>
20839 </template> 22975 </template>
20840 </iron-selector> 22976 </iron-selector>
20841 </div> 22977 </div>
20842 22978
20843 <div class="selector side-by-side"> 22979 <div class="selector side-by-side" title="These are all options (if any) that the bot list can be filtered on.">
20844 <template is="dom-repeat" id="secondaryList" items="[[_secondaryItems] ]" as="item"> 22980 <template is="dom-repeat" id="secondaryList" items="[[_secondaryItems] ]" as="item">
20845 <div class="item horizontal layout" label="[[item]]"> 22981 <div class="item horizontal layout" label="[[item]]">
20846 22982
20847 <span>[[_beforeBold(item,_query)]]<span class="bold">[[_bold(item, _query)]]</span>[[_afterBold(item,_query)]]</span> 22983 <span>[[_beforeBold(item,_query)]]<span class="bold">[[_bold(item, _query)]]</span>[[_afterBold(item,_query)]]</span>
20848 <span class="flex"></span> 22984 <span class="flex"></span>
20849 <iron-icon class="icons" icon="icons:arrow-forward" hidden="[[_can tAddFilter(_primarySelected,item,_filters.*)]]" on-tap="_addFilter"> 22985 <iron-icon class="icons" icon="icons:arrow-forward" hidden="[[_can tAddFilter(_primarySelected,item,_filters.*)]]" on-tap="_addFilter">
20850 </iron-icon> 22986 </iron-icon>
20851 </div> 22987 </div>
20852 </template> 22988 </template>
20853 </div> 22989 </div>
20854 22990
20855 <div class="selector side-by-side"> 22991 <div class="selector side-by-side" title="These filters are AND'd togeth er and applied to all bots in
22992 the fleet.">
20856 <template is="dom-repeat" items="[[_filters]]" as="fil"> 22993 <template is="dom-repeat" items="[[_filters]]" as="fil">
20857 <div class="item horizontal layout" label="[[fil]]"> 22994 <div class="item horizontal layout" label="[[fil]]">
20858 <span>[[fil]]</span> 22995 <span>[[fil]]</span>
20859 <span class="flex"></span> 22996 <span class="flex"></span>
20860 <iron-icon class="icons" icon="icons:remove-circle-outline" hidden ="[[_cantRemoveFilter(fil,_filters.*)]]" on-tap="_removeFilter"> 22997 <iron-icon class="icons" icon="icons:remove-circle-outline" hidden ="[[_cantRemoveFilter(fil,_filters.*)]]" on-tap="_removeFilter">
20861 </iron-icon> 22998 </iron-icon>
20862 </div> 22999 </div>
20863 </template> 23000 </template>
20864 </div> 23001 </div>
20865 23002
23003 <div class="side-by-side">
20866 <paper-checkbox checked="{{verbose}}">Verbose Entries</paper-checkbox> 23004 <paper-checkbox checked="{{verbose}}">Verbose Entries</paper-checkbox>
23005 <paper-input id="limit" label="Limit Results" auto-validate="" min="0" max="1000" pattern="[0-9]+" value="{{limit}}">
23006 </paper-input>
23007 </div>
20867 </div> 23008 </div>
20868 23009
20869 </div> 23010 </div>
20870 23011
20871 </template> 23012 </template>
20872 <script> 23013 <script>
20873 (function(){ 23014 (function(){
20874 var FILTER_SEP = ":"; 23015 var FILTER_SEP = ":";
20875 // filterMap is a map of primary -> function. The function returns a 23016 // filterMap is a map of primary -> function. The function returns a
20876 // boolean "does the bot (first arg) match the second argument". These 23017 // boolean "does the bot (first arg) match the second argument". These
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after
20914 pool: function(bot, pool) { 23055 pool: function(bot, pool) {
20915 var o = this._attribute(bot, "pool"); 23056 var o = this._attribute(bot, "pool");
20916 return o.indexOf(pool) !== -1; 23057 return o.indexOf(pool) !== -1;
20917 }, 23058 },
20918 status: function(bot, status) { 23059 status: function(bot, status) {
20919 if (status === "quarantined") { 23060 if (status === "quarantined") {
20920 return bot.quarantined; 23061 return bot.quarantined;
20921 } else if (status === "dead") { 23062 } else if (status === "dead") {
20922 return bot.is_dead; 23063 return bot.is_dead;
20923 } else { 23064 } else {
20924 // Status must be "available". 23065 // Status must be "alive".
20925 return !bot.quarantined && !bot.is_dead; 23066 return !bot.quarantined && !bot.is_dead;
20926 } 23067 }
20927 }, 23068 },
20928 task: function(bot, task) { 23069 task: function(bot, task) {
20929 if (task === "idle") { 23070 if (task === "idle") {
20930 return this._taskId(bot) === "idle"; 23071 return this._taskId(bot) === "idle";
20931 } 23072 }
20932 // Task must be "busy". 23073 // Task must be "busy".
20933 return this._taskId(bot) !== "idle"; 23074 return this._taskId(bot) !== "idle";
20934 } 23075 }
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after
20976 primary_map: { 23117 primary_map: {
20977 type: Object, 23118 type: Object,
20978 }, 23119 },
20979 primary_arr: { 23120 primary_arr: {
20980 type: Array, 23121 type: Array,
20981 }, 23122 },
20982 23123
20983 // output 23124 // output
20984 columns: { 23125 columns: {
20985 type: Array, 23126 type: Array,
20986 value: function() {
20987 // TODO(kjlubick) back these up to URL params and load them from
20988 // there on reload.
20989 return ["id","os","task","status"];
20990 },
20991 notify: true,
20992 },
20993 dimensions: {
20994 type: Array,
20995 computed: "_extractDimensions(DIMENSIONS.*,_filters.*)",
20996 notify: true, 23127 notify: true,
20997 }, 23128 },
20998 filter: { 23129 filter: {
23130 type: Function,
23131 computed: "_makeFilter(_filters.*)",
23132 notify: true,
23133 },
23134 query_params: {
20999 type: Object, 23135 type: Object,
21000 computed: "_makeFilter(_filters.*)", 23136 computed: "_extractQueryParams(DIMENSIONS.*,_filters.*, limit)",
21001 notify: true, 23137 notify: true,
21002 }, 23138 },
21003 verbose: { 23139 verbose: {
21004 type: Boolean, 23140 type: Boolean,
21005 value: false,
21006 notify: true, 23141 notify: true,
21007 }, 23142 },
21008 23143
21009 // private 23144 // private
21010 _filters: { 23145 _filters: {
21011 type:Array, 23146 type:Array,
21012 value: function() { 23147 },
21013 return []; 23148 _limit: {
21014 } 23149 type: Number,
21015 }, 23150 },
21016 _primaryItems: { 23151 _primaryItems: {
21017 type: Array, 23152 type: Array,
21018 computed: "_primary(_query, primary_map, primary_arr, columns.*)", 23153 computed: "_primary(_query, primary_map, primary_arr, columns.*)",
21019 }, 23154 },
21020 _primarySelected: { 23155 _primarySelected: {
21021 type: String, 23156 type: String,
21022 value: "", 23157 value: "",
21023 }, 23158 },
21024 // query is treated as a space separated list. 23159 // query is treated as a space separated list.
21025 _query: { 23160 _query: {
21026 type:String, 23161 type:String,
21027 value: "",
21028 }, 23162 },
21029 _secondaryItems: { 23163 _secondaryItems: {
21030 type: Array, 23164 type: Array,
21031 computed: "_secondary(_primarySelected, _query, primary_map)", 23165 computed: "_secondary(_primarySelected, _query, primary_map)",
21032 }, 23166 },
21033 23167
21034 }, 23168 },
21035 23169
21036 _addFilter: function(e) { 23170 _addFilter: function(e) {
21037 // e.model.foo is a way to get access to the "foo" inside a dom-repeat 23171 // e.model.foo is a way to get access to the "foo" inside a dom-repeat
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after
21093 }, 23227 },
21094 23228
21095 _columnState: function(col) { 23229 _columnState: function(col) {
21096 if (!col) { 23230 if (!col) {
21097 return false; 23231 return false;
21098 } 23232 }
21099 return this.columns.indexOf(col) !== -1; 23233 return this.columns.indexOf(col) !== -1;
21100 }, 23234 },
21101 23235
21102 _makeFilter: function() { 23236 _makeFilter: function() {
21103 // The filters belonging to the same primary key will be or'd together. 23237 // All filters will be AND'd together.
21104 // Those groups will be and'd together.
21105 // filterGroups will be a map of primary (i.e. column) -> array of 23238 // filterGroups will be a map of primary (i.e. column) -> array of
21106 // options that should be filtered to. 23239 // options that should be filtered to.
21107 // e.g. "os" -> ["Windows", "Linux"] 23240 // e.g. "os" -> ["Windows", "Linux"]
21108 // Since they will be or'd together, order doesn't matter. 23241 // Since they will be or'd together, order doesn't matter.
21109 var filterGroups = {}; 23242 var filterGroups = {};
21110 this._filters.forEach(function(filterString){ 23243 this._filters.forEach(function(filterString){
21111 var idx = filterString.indexOf(FILTER_SEP); 23244 var idx = filterString.indexOf(FILTER_SEP);
21112 var primary = filterString.slice(0, idx); 23245 var primary = filterString.slice(0, idx);
21113 var param = filterString.slice(idx + FILTER_SEP.length); 23246 var param = filterString.slice(idx + FILTER_SEP.length);
21114 var arr = filterGroups[primary] || []; 23247 var arr = filterGroups[primary] || [];
21115 arr.push(param); 23248 arr.push(param);
21116 filterGroups[primary] = arr; 23249 filterGroups[primary] = arr;
21117 }); 23250 });
21118 return function(bot){ 23251 return function(bot){
21119 var retVal = true; 23252 var retVal = true;
21120 // Look up all the primary keys we are filter by, then look up how 23253 // Look up all the primary keys we are filter by, then look up how
21121 // to filter (in filterMap) and apply the filter for each filter 23254 // to filter (in filterMap) and apply the filter for each filter
21122 // option. 23255 // option.
21123 for (primary in filterGroups){ 23256 for (primary in filterGroups){
21124 var params = filterGroups[primary]; 23257 var params = filterGroups[primary];
21125 var filter = filterMap[primary]; 23258 var filter = filterMap[primary];
21126 var groupResult = false;
21127 if (filter) { 23259 if (filter) {
21128 params.forEach(function(param){ 23260 params.forEach(function(param){
21129 groupResult = groupResult || filter.bind(this)(bot,param); 23261 retVal = retVal && filter.bind(this)(bot,param);
21130 }.bind(this)); 23262 }.bind(this));
21131 } 23263 }
21132 retVal = retVal && groupResult;
21133 } 23264 }
21134 return retVal; 23265 return retVal;
21135 } 23266 }
21136 }, 23267 },
21137 23268
21138 _primary: function(query, primary_map, primary_arr) { 23269 _primary: function(query, primary_map, primary_arr) {
21139 // If the user has typed in a query, only show those primary keys that 23270 // If the user has typed in a query, only show those primary keys that
21140 // partially match the query or that have secondary values which 23271 // partially match the query or that have secondary values which
21141 // partially match. 23272 // partially match.
21142 var arr = this.primary_arr.filter(function(s){ 23273 var arr = this.primary_arr.filter(function(s){
(...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after
21213 }, 23344 },
21214 23345
21215 _afterBold: function(item, query) { 23346 _afterBold: function(item, query) {
21216 var match = matchPartCaseInsensitive(item, query); 23347 var match = matchPartCaseInsensitive(item, query);
21217 if (match.idx === -1) { 23348 if (match.idx === -1) {
21218 return ""; 23349 return "";
21219 } 23350 }
21220 return item.substring(match.idx + match.part.length); 23351 return item.substring(match.idx + match.part.length);
21221 }, 23352 },
21222 23353
21223 _extractDimensions: function() { 23354 _extractQueryParams: function() {
21224 var dims = [] 23355 var params = {};
23356 var dims = [];
21225 this._filters.forEach(function(f) { 23357 this._filters.forEach(function(f) {
21226 var split = f.split(FILTER_SEP, 1) 23358 var split = f.split(FILTER_SEP, 1)
21227 var col = split[0]; 23359 var col = split[0];
21228 if (this.DIMENSIONS.indexOf(col) !== -1) { 23360 if (this.DIMENSIONS.indexOf(col) !== -1) {
21229 var rest = f.substring(col.length + FILTER_SEP.length); 23361 var rest = f.substring(col.length + FILTER_SEP.length);
21230 dims.push(col + FILTER_SEP + this._unalias(rest)) 23362 dims.push(col + FILTER_SEP + this._unalias(rest))
21231 }; 23363 } else if (col === "status") {
23364 var rest = f.substring(col.length + FILTER_SEP.length);
23365 if (rest === "alive") {
23366 params["is_dead"] = "FALSE";
23367 params["quarantined"] = "FALSE";
23368 } else if (rest === "quarantined") {
23369 params["quarantined"] = "TRUE";
23370 } else if (rest === "dead") {
23371 params["is_dead"] = "TRUE";
23372 }
23373 }
21232 }.bind(this)); 23374 }.bind(this));
21233 return dims; 23375 params["dimensions"] = dims;
23376 var lim = Math.floor(this.limit)
23377 if (Number.isInteger(lim)) {
23378 // Clamp the limit
23379 lim = Math.max(lim, 1);
23380 lim = Math.min(1000, lim);
23381 params["limit"] = lim;
23382 }
23383 return params;
21234 } 23384 }
21235 23385
21236 }); 23386 });
21237 })(); 23387 })();
21238 </script> 23388 </script>
21239 </dom-module><dom-module id="bot-list-data" assetpath="/res/imp/botlist/"> 23389 </dom-module><dom-module id="bot-list-data" assetpath="/res/imp/botlist/">
21240 <template> 23390 <template>
21241 <iron-ajax id="botlist" url="/_ah/api/swarming/v1/bots/list" headers="[[auth _headers]]" params="[[_botlistParams(dimensions.*)]]" handle-as="json" last-resp onse="{{_list}}" loading="{{_busy1}}"> 23391 <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}}">
21242 </iron-ajax> 23392 </iron-ajax>
21243 23393
21244 <iron-ajax id="dimensions" url="/_ah/api/swarming/v1/bots/dimensions" header s="[[auth_headers]]" handle-as="json" last-response="{{_dimensions}}" loading="{ {_busy2}}"> 23394 <iron-ajax id="dimensions" url="/_ah/api/swarming/v1/bots/dimensions" header s="[[auth_headers]]" handle-as="json" last-response="{{_dimensions}}" loading="{ {_busy2}}">
21245 </iron-ajax> 23395 </iron-ajax>
21246 23396
21247 <iron-ajax id="fleet" url="/_ah/api/swarming/v1/bots/count" headers="[[auth_ headers]]" handle-as="json" last-response="{{_count}}" loading="{{_busy3}}"> 23397 <iron-ajax id="fleet" url="/_ah/api/swarming/v1/bots/count" headers="[[auth_ headers]]" handle-as="json" last-response="{{_count}}" loading="{{_busy3}}">
21248 </iron-ajax> 23398 </iron-ajax>
21249 </template> 23399 </template>
21250 <script> 23400 <script>
21251 (function(){ 23401 (function(){
21252 23402
21253 Polymer({ 23403 Polymer({
21254 is: 'bot-list-data', 23404 is: 'bot-list-data',
21255 23405
21256 behaviors: [SwarmingBehaviors.BotListBehavior], 23406 behaviors: [SwarmingBehaviors.BotListBehavior],
21257 23407
21258 properties: { 23408 properties: {
21259 // inputs 23409 // inputs
21260 auth_headers: { 23410 auth_headers: {
21261 type: Object, 23411 type: Object,
21262 observer: "signIn", 23412 observer: "signIn",
21263 }, 23413 },
21264 dimensions: { 23414 query_params: {
21265 type: Array, 23415 type: Object,
21266 }, 23416 },
21267 23417
21268 //outputs 23418 //outputs
21269 bots: { 23419 bots: {
21270 type: Array, 23420 type: Array,
21271 computed: "_bots(_list)", 23421 computed: "_bots(_list)",
21272 notify: true, 23422 notify: true,
21273 }, 23423 },
21274 busy: { 23424 busy: {
21275 type: Boolean, 23425 type: Boolean,
(...skipping 23 matching lines...) Expand all
21299 }, 23449 },
21300 _dimensions: { 23450 _dimensions: {
21301 type: Object, 23451 type: Object,
21302 }, 23452 },
21303 _list: { 23453 _list: {
21304 type: Object, 23454 type: Object,
21305 }, 23455 },
21306 }, 23456 },
21307 23457
21308 signIn: function(){ 23458 signIn: function(){
23459 // Auto on iron-ajax means to automatically re-make the request if
23460 // the url or the query params change. Auto does not trigger if the
23461 // [auth] headers change, so we wait until the user is signed in
23462 // before making any requests.
21309 this.$.botlist.auto = true; 23463 this.$.botlist.auto = true;
21310 this.$.dimensions.auto = true; 23464 this.$.dimensions.auto = true;
21311 this.$.fleet.auto = true; 23465 this.$.fleet.auto = true;
21312 }, 23466 },
21313 23467
21314 _botlistParams: function() {
21315 if (!this.dimensions) {
21316 return {};
21317 }
21318 return {
21319 dimensions: this.dimensions,
21320 };
21321 },
21322
21323 _bots: function(){ 23468 _bots: function(){
21324 if (!this._list || !this._list.items) { 23469 if (!this._list || !this._list.items) {
21325 return []; 23470 return [];
21326 } 23471 }
21327 // Do any preprocessing here 23472 // Do any preprocessing here
21328 this._list.items.forEach(function(bot){ 23473 this._list.items.forEach(function(bot){
21329 // Parse the state, which is a JSON string. This contains a lot of 23474 // Parse the state, which is a JSON string. This contains a lot of
21330 // interesting information like details about the devices attached. 23475 // interesting information like details about the devices attached.
21331 bot.state = JSON.parse(bot.state); 23476 bot.state = JSON.parse(bot.state);
21332 // get the disks in an easier to deal with format, sorted by size. 23477 // get the disks in an easier to deal with format, sorted by size.
(...skipping 14 matching lines...) Expand all
21347 23492
21348 }); 23493 });
21349 return this._list.items; 23494 return this._list.items;
21350 }, 23495 },
21351 23496
21352 _fleet: function() { 23497 _fleet: function() {
21353 if (!this._count) { 23498 if (!this._count) {
21354 return {}; 23499 return {};
21355 } 23500 }
21356 return { 23501 return {
21357 alive: this._count.count || -1, 23502 all: this._count.count || -1,
23503 alive: (this._count.count - this._count.dead) || -1,
21358 busy: this._count.busy || -1, 23504 busy: this._count.busy || -1,
21359 idle: this._count.count && this._count.busy && 23505 idle: (this._count.count - this._count.busy) || -1,
21360 this._count.count - this._count.busy,
21361 dead: this._count.dead || -1, 23506 dead: this._count.dead || -1,
21362 quarantined: this._count.quarantined || -1, 23507 quarantined: this._count.quarantined || -1,
21363 } 23508 }
21364 }, 23509 },
21365 23510
21366 _primaryArr: function(dimensions, properties) { 23511 _primaryArr: function(dimensions, properties) {
21367 return dimensions.concat(properties); 23512 return dimensions.concat(properties);
21368 }, 23513 },
21369 23514
21370 _primaryMap: function(dimensions){ 23515 _primaryMap: function(dimensions){
21371 // map will keep track of dimensions that we have seen at least once. 23516 // pMap will have a list of columns to available values (primary key
21372 // This will then basically get turned into an array to be used for 23517 // to secondary values). This includes bot dimensions, but also
21373 // filtering. 23518 // includes state like disk_space, quarantined, busy, etc.
21374 dimensions = dimensions.bots_dimensions; 23519 dimensions = dimensions.bots_dimensions;
21375 23520
21376 var pMap = {}; 23521 var pMap = {};
21377 dimensions.forEach(function(d){ 23522 dimensions.forEach(function(d){
21378 if (this.DIMENSIONS_WITH_ALIASES.indexOf(d.key) === -1) { 23523 if (this.DIMENSIONS_WITH_ALIASES.indexOf(d.key) === -1) {
21379 // value is an array of all seen values for the dimension d.key 23524 // value is an array of all seen values for the dimension d.key
21380 pMap[d.key] = d.value; 23525 pMap[d.key] = d.value;
21381 } else if (d.key === "gpu") { 23526 } else if (d.key === "gpu") {
21382 var gpus = []; 23527 var gpus = [];
21383 d.value.forEach(function(g){ 23528 d.value.forEach(function(g){
(...skipping 24 matching lines...) Expand all
21408 // Add some options that might not show up. 23553 // Add some options that might not show up.
21409 pMap["android_devices"].push("0"); 23554 pMap["android_devices"].push("0");
21410 pMap["device_os"].push("none"); 23555 pMap["device_os"].push("none");
21411 pMap["device_type"].push("none"); 23556 pMap["device_type"].push("none");
21412 23557
21413 pMap["id"] = []; 23558 pMap["id"] = [];
21414 23559
21415 // Create custom filter options 23560 // Create custom filter options
21416 pMap["disk_space"] = []; 23561 pMap["disk_space"] = [];
21417 pMap["task"] = ["busy", "idle"]; 23562 pMap["task"] = ["busy", "idle"];
21418 pMap["status"] = ["available", "dead", "quarantined"]; 23563 pMap["status"] = ["alive", "dead", "quarantined"];
21419 23564
21420 // No need to sort any of this, bot-filters sorts secondary items 23565 // No need to sort any of this, bot-filters sorts secondary items
21421 // automatically, especially when the user types a query. 23566 // automatically, especially when the user types a query.
21422 return pMap; 23567 return pMap;
21423 }, 23568 },
21424 23569
21425 }); 23570 });
21426 })(); 23571 })();
21427 </script> 23572 </script>
21428 </dom-module><dom-module id="bot-list-summary" assetpath="/res/imp/botlist/"> 23573 </dom-module><dom-module id="bot-list-summary" assetpath="/res/imp/botlist/">
(...skipping 11 matching lines...) Expand all
21440 } 23585 }
21441 .right { 23586 .right {
21442 text-align: right; 23587 text-align: right;
21443 } 23588 }
21444 .left { 23589 .left {
21445 text-align: left; 23590 text-align: left;
21446 } 23591 }
21447 </style> 23592 </style>
21448 23593
21449 <div class="header">Fleet</div> 23594 <div class="header">Fleet</div>
21450
21451 <table> 23595 <table>
21452 <tbody><tr> 23596 <tbody><tr>
21453 <td class="right"><a href="/newui/botlist?alive">Alive</a>:</td> 23597 <td class="right">
23598 <a href$="[[_makeURL('','',columns.*,filtered_bots.*,sort,verbose)]]"> All</a>:
23599 </td>
23600 <td class="left">[[fleet.all]]</td>
23601 </tr>
23602 <tr>
23603 <td class="right">
23604 <a href$="[[_makeURL('alive','',columns.*,filtered_bots.*,sort,verbose )]]">Alive</a>:
23605 </td>
21454 <td class="left">[[fleet.alive]]</td> 23606 <td class="left">[[fleet.alive]]</td>
21455 </tr> 23607 </tr>
21456 <tr> 23608 <tr>
21457 <td class="right"><a href="/newui/botlist?busy">Busy</a>:</td> 23609 <td class="right">
23610 <a href$="[[_makeURL('busy','',columns.*,filtered_bots.*,sort,verbose) ]]">Busy</a>:
23611 </td>
21458 <td class="left">[[fleet.busy]]</td> 23612 <td class="left">[[fleet.busy]]</td>
21459 </tr> 23613 </tr>
21460 <tr> 23614 <tr>
21461 <td class="right"><a href="/newui/botlist?idle">Idle</a>:</td> 23615 <td class="right">
23616 <a href$="[[_makeURL('idle','',columns.*,filtered_bots.*,sort,verbose) ]]">Idle</a>:
23617 </td>
21462 <td class="left">[[fleet.idle]]</td> 23618 <td class="left">[[fleet.idle]]</td>
21463 </tr> 23619 </tr>
21464 <tr> 23620 <tr>
21465 <td class="right"><a href="/newui/botlist?dead">Dead</a>:</td> 23621 <td class="right">
23622 <a href$="[[_makeURL('dead','',columns.*,filtered_bots.*,sort,verbose) ]]">Dead</a>:
23623 </td>
21466 <td class="left">[[fleet.dead]]</td> 23624 <td class="left">[[fleet.dead]]</td>
21467 </tr> 23625 </tr>
21468 <tr> 23626 <tr>
21469 <td class="right"><a href="/newui/botlist?quaren">Quarantined</a>:</td> 23627 <td class="right">
23628 <a href$="[[_makeURL('quarantined','',columns.*,filtered_bots.*,sort,v erbose)]]">Quarantined</a>:
23629 </td>
21470 <td class="left">[[fleet.quarantined]]</td> 23630 <td class="left">[[fleet.quarantined]]</td>
21471 </tr> 23631 </tr>
21472 </tbody></table> 23632 </tbody></table>
21473 23633
21474 <div class="header">Displayed</div> 23634 <div class="header">Displayed</div>
21475 <table> 23635 <table>
21476 <tbody><tr> 23636 <tbody><tr>
21477 <td class="right"><a href="/newui/botlist?alive2">Alive</a>:</td> 23637 <td class="right">
23638 All:
23639 </td>
23640 <td class="left">[[_currently_showing.all]]</td>
23641 </tr>
23642 <tr>
23643 <td class="right">
23644 <a href$="[[_makeURL('alive','true',columns.*,filtered_bots.*,sort,ver bose)]]">Alive</a>:
23645 </td>
21478 <td class="left">[[_currently_showing.alive]]</td> 23646 <td class="left">[[_currently_showing.alive]]</td>
21479 </tr> 23647 </tr>
21480 <tr> 23648 <tr>
21481 <td class="right"><a href="/newui/botlist?busy2">Busy</a>:</td> 23649 <td class="right">
23650 <a href$="[[_makeURL('busy','true',columns.*,filtered_bots.*,sort,verb ose)]]">Busy</a>:
23651 </td>
21482 <td class="left">[[_currently_showing.busy]]</td> 23652 <td class="left">[[_currently_showing.busy]]</td>
21483 </tr> 23653 </tr>
21484 <tr> 23654 <tr>
21485 <td class="right"><a href="/newui/botlist?idle2">Idle</a>:</td> 23655 <td class="right">
23656 <a href$="[[_makeURL('idle','true',columns.*,filtered_bots.*,sort,verb ose)]]">Idle</a>:
23657 </td>
21486 <td class="left">[[_currently_showing.idle]]</td> 23658 <td class="left">[[_currently_showing.idle]]</td>
21487 </tr> 23659 </tr>
21488 <tr> 23660 <tr>
21489 <td class="right"><a href="/newui/botlist?dead2">Dead</a>:</td> 23661 <td class="right">
23662 <a href$="[[_makeURL('dead','true',columns.*,filtered_bots.*,sort,verb ose)]]">Dead</a>:
23663 </td>
21490 <td class="left">[[_currently_showing.dead]]</td> 23664 <td class="left">[[_currently_showing.dead]]</td>
21491 </tr> 23665 </tr>
21492 <tr> 23666 <tr>
21493 <td class="right"><a href="/newui/botlist?quaren2">Quarantined</a>:</td> 23667 <td class="right">
23668 <a href$="[[_makeURL('quarantined','true',columns.*,filtered_bots.*,so rt,verbose)]]">Quarantined</a>:
23669 </td>
21494 <td class="left">[[_currently_showing.quarantined]]</td> 23670 <td class="left">[[_currently_showing.quarantined]]</td>
21495 </tr> 23671 </tr>
21496 </tbody></table> 23672 </tbody></table>
21497 23673
21498 </template> 23674 </template>
21499 <script> 23675 <script>
21500 Polymer({ 23676 Polymer({
21501 is: 'bot-list-summary', 23677 is: 'bot-list-summary',
21502 23678
21503 behaviors: [SwarmingBehaviors.BotListBehavior], 23679 behaviors: [SwarmingBehaviors.BotListBehavior],
21504 23680
21505 properties: { 23681 properties: {
23682 columns: {
23683 type: Array,
23684 },
21506 filtered_bots: { 23685 filtered_bots: {
21507 type: Array, 23686 type: Array,
21508 }, 23687 },
21509 fleet: { 23688 fleet: {
21510 type: Object, 23689 type: Object,
21511 }, 23690 },
23691 sort: {
23692 type: String,
23693 },
23694 verbose: {
23695 type: Boolean,
23696 },
21512 23697
21513 _currently_showing: { 23698 _currently_showing: {
21514 type: Object, 23699 type: Object,
21515 value: function() { 23700 value: function() {
21516 return { 23701 return {
23702 all: -1,
21517 alive: -1, 23703 alive: -1,
21518 busy: -1, 23704 busy: -1,
21519 idle: -1, 23705 idle: -1,
21520 dead: -1, 23706 dead: -1,
21521 quarantined: -1, 23707 quarantined: -1,
21522 }; 23708 };
21523 }, 23709 },
21524 }, 23710 },
21525 }, 23711 },
21526 23712
21527 // Do this because Array changes in Polymer don't always trigger normal 23713 // Do this because Array changes in Polymer don't always trigger normal
21528 // property observers 23714 // property observers
21529 observers: ["_recount(filtered_bots.*)"], 23715 observers: ["_recount(filtered_bots.*)"],
21530 23716
23717 _getFilterStr: function(filter) {
23718 if (!filter) {
23719 return "";
23720 }
23721 if (filter === "alive" || filter === "dead" ||
23722 filter === "quarantined") {
23723 return "status:" + filter;
23724 } else {
23725 return "task:" + filter;
23726 }
23727 },
23728
23729 _makeURL: function(filter, preserveOthers) {
23730 if (preserveOthers) {
23731 var fstr = encodeURIComponent(this._getFilterStr(filter));
23732 if (window.location.href.indexOf(fstr) === -1) {
23733 return window.location.href + "&filters=" + fstr;
23734 }
23735 // The filter is already on the list.
23736 return undefined;
23737 }
23738 var params = {
23739 sort: [this.sort],
23740 columns: this.columns,
23741 verbose: [this.verbose],
23742 }
23743 if (filter) {
23744 params["filters"] = [this._getFilterStr(filter)];
23745 }
23746
23747 return window.location.href.split('?')[0] + '?' + sk.query.fromParamSet( params);
23748 },
23749
21531 _recount: function() { 23750 _recount: function() {
21532 var curr = { 23751 var curr = {
23752 all: 0,
21533 alive: 0, 23753 alive: 0,
21534 busy: 0, 23754 busy: 0,
21535 idle: 0, 23755 idle: 0,
21536 dead: 0, 23756 dead: 0,
21537 quarantined: 0, 23757 quarantined: 0,
21538 }; 23758 };
21539 if (!this.filtered_bots) { 23759 if (!this.filtered_bots) {
21540 return curr; 23760 return curr;
21541 } 23761 }
21542 this.filtered_bots.forEach(function(bot) { 23762 this.filtered_bots.forEach(function(bot) {
21543 if (this._taskId(bot) === "idle") { 23763 if (this._taskId(bot) === "idle") {
21544 curr.idle++; 23764 curr.idle++;
21545 } else { 23765 } else {
21546 curr.busy++; 23766 curr.busy++;
21547 } 23767 }
21548 if (bot.quarantined) { 23768 if (bot.quarantined) {
21549 curr.quarantined++; 23769 curr.quarantined++;
21550 } 23770 }
21551 if (bot.is_dead) { 23771 if (bot.is_dead) {
21552 curr.dead++; 23772 curr.dead++;
21553 } else { 23773 } else {
21554 curr.alive++; 23774 curr.alive++;
21555 } 23775 }
23776 curr.all++;
21556 }.bind(this)); 23777 }.bind(this));
21557 this.set("_currently_showing", curr); 23778 this.set("_currently_showing", curr);
21558 } 23779 }
21559 }); 23780 });
21560 </script> 23781 </script>
21561 </dom-module><dom-module id="bot-list" assetpath="/res/imp/botlist/"> 23782 </dom-module><dom-module id="bot-list" assetpath="/res/imp/botlist/">
21562 <template> 23783 <template>
21563 <style include="iron-flex iron-flex-alignment iron-positioning swarming-app- style"> 23784 <style include="iron-flex iron-flex-alignment iron-positioning swarming-app- style">
21564 bot-filters, bot-list-summary { 23785 bot-filters, bot-list-summary {
21565 margin-bottom: 8px; 23786 margin-bottom: 8px;
(...skipping 28 matching lines...) Expand all
21594 position: absolute; 23815 position: absolute;
21595 right: 0; 23816 right: 0;
21596 top: 0.4em; 23817 top: 0.4em;
21597 } 23818 }
21598 .bot-list th > span { 23819 .bot-list th > span {
21599 /* Leave space for sort-toggle*/ 23820 /* Leave space for sort-toggle*/
21600 padding-right: 30px; 23821 padding-right: 30px;
21601 } 23822 }
21602 </style> 23823 </style>
21603 23824
23825 <url-param name="sort" value="{{_sortstr}}" default_value="id:asc">
23826 </url-param>
23827
21604 <swarming-app auth_headers="{{_auth_headers}}" signed_in="{{_signed_in}}" bu sy="[[_busy]]" name="Swarming Bot List"> 23828 <swarming-app auth_headers="{{_auth_headers}}" signed_in="{{_signed_in}}" bu sy="[[_busy]]" name="Swarming Bot List">
21605 23829
21606 <h2 hidden$="[[_signed_in]]">You must sign in to see anything useful.</h2> 23830 <h2 hidden$="[[_signed_in]]">You must sign in to see anything useful.</h2>
21607 23831
21608 <div hidden$="[[_not(_signed_in)]]"> 23832 <div hidden$="[[_not(_signed_in)]]">
21609 23833
21610 <div class="horizontal layout"> 23834 <div class="horizontal layout">
21611 23835
21612 <bot-filters primary_map="[[_primary_map]]" primary_arr="[[_primary_ar r]]" columns="{{_columns}}" dimensions="{{_dimensions}}" filter="{{_filter}}" ve rbose="{{_verbose}}"> 23836 <bot-filters primary_map="[[_primary_map]]" primary_arr="[[_primary_ar r]]" columns="{{_columns}}" query_params="{{_query_params}}" filter="{{_filter}} " verbose="{{_verbose}}">
21613 </bot-filters> 23837 </bot-filters>
21614 23838
21615 <bot-list-summary fleet="[[_fleet]]" filtered_bots="[[_filteredSortedB ots]]"> 23839 <bot-list-summary columns="[[_columns]]" fleet="[[_fleet]]" filtered_b ots="[[_filteredSortedBots]]" sort="[[_sortstr]]" verbose="[[_verbose]]">
21616 </bot-list-summary> 23840 </bot-list-summary>
21617 23841
21618 </div> 23842 </div>
21619 23843
21620 <bot-list-data auth_headers="[[_auth_headers]]" dimensions="[[_dimension s]]" bots="{{_bots}}" busy="{{_busy}}" fleet="{{_fleet}}" primary_map="{{_primar y_map}}" primary_arr="{{_primary_arr}}"> 23844 <bot-list-data auth_headers="[[_auth_headers]]" query_params="[[_query_p arams]]" bots="{{_bots}}" busy="{{_busy}}" fleet="{{_fleet}}" primary_map="{{_pr imary_map}}" primary_arr="{{_primary_arr}}">
21621 </bot-list-data> 23845 </bot-list-data>
21622 23846
21623 <table class="bot-list"> 23847 <table class="bot-list">
21624 <thead on-sort_change="_sortChange"> 23848 <thead on-sort_change="_sortChange">
21625 23849
21626 <tr> 23850 <tr>
21627 <th> 23851 <th>
21628 <span>Bot Id</span> 23852 <span>Bot Id</span>
21629 <sort-toggle name="id" current="[[_sort]]"> 23853 <sort-toggle name="id" current="[[_sort]]">
21630 </sort-toggle> 23854 </sort-toggle>
(...skipping 108 matching lines...) Expand 10 before | Expand all | Expand 10 after
21739 device_type: function(bot){ 23963 device_type: function(bot){
21740 var dt = this._attribute(bot, "device_type", "none"); 23964 var dt = this._attribute(bot, "device_type", "none");
21741 if (this._verbose) { 23965 if (this._verbose) {
21742 return dt.join(" | "); 23966 return dt.join(" | ");
21743 } 23967 }
21744 return dt[0]; 23968 return dt[0];
21745 }, 23969 },
21746 disk_space: function(bot) { 23970 disk_space: function(bot) {
21747 var aliased = []; 23971 var aliased = [];
21748 bot.disks.forEach(function(disk){ 23972 bot.disks.forEach(function(disk){
21749 var alias = swarming.humanBytes(disk.mb, swarming.MB); 23973 var alias = sk.human.bytes(disk.mb, swarming.MB);
21750 aliased.push(this._applyAlias(disk.mb, disk.id + " "+ alias)); 23974 aliased.push(this._applyAlias(disk.mb, disk.id + " "+ alias));
21751 }.bind(this)); 23975 }.bind(this));
21752 if (this._verbose) { 23976 if (this._verbose) {
21753 return aliased.join(" | "); 23977 return aliased.join(" | ");
21754 } 23978 }
21755 return aliased[0]; 23979 return aliased[0];
21756 }, 23980 },
21757 gpu: function(bot){ 23981 gpu: function(bot){
21758 var gpus = this._attribute(bot, "gpu", "none") 23982 var gpus = this._attribute(bot, "gpu", "none")
21759 var verbose = [] 23983 var verbose = []
(...skipping 30 matching lines...) Expand all
21790 return os[0]; 24014 return os[0];
21791 }, 24015 },
21792 pool: function(bot) { 24016 pool: function(bot) {
21793 var pool = this._attribute(bot, "pool"); 24017 var pool = this._attribute(bot, "pool");
21794 return pool.join(" | "); 24018 return pool.join(" | ");
21795 }, 24019 },
21796 status: function(bot) { 24020 status: function(bot) {
21797 // If a bot is both dead and quarantined, show the deadness over the 24021 // If a bot is both dead and quarantined, show the deadness over the
21798 // quarentinedness. 24022 // quarentinedness.
21799 if (bot.is_dead) { 24023 if (bot.is_dead) {
21800 return "Dead. Last seen " + swarming.diffDate(bot.last_seen_ts) + 24024 return "Dead. Last seen " + sk.human.diffDate(bot.last_seen_ts) +
21801 " ago"; 24025 " ago";
21802 } 24026 }
21803 if (bot.quarantined) { 24027 if (bot.quarantined) {
21804 return "Quarantined: " + this._attribute(bot, "quarantined"); 24028 return "Quarantined: " + this._attribute(bot, "quarantined");
21805 } 24029 }
21806 return "Alive"; 24030 return "Alive";
21807 }, 24031 },
21808 task: function(bot){ 24032 task: function(bot){
21809 return this._taskId(bot); 24033 return this._taskId(bot);
21810 }, 24034 },
(...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after
21878 }, 24102 },
21879 24103
21880 _plain_columns: { 24104 _plain_columns: {
21881 type: Array, 24105 type: Array,
21882 computed: "_stripSpecial(_columns.*)", 24106 computed: "_stripSpecial(_columns.*)",
21883 }, 24107 },
21884 24108
21885 // _sort is an Object {name:String, direction:String}. 24109 // _sort is an Object {name:String, direction:String}.
21886 _sort: { 24110 _sort: {
21887 type: Object, 24111 type: Object,
21888 value: function() { 24112 computed: "_makeObject(_sortstr)",
21889 return { 24113 },
21890 name: "id", 24114
21891 direction: "asc", 24115 // We can't (easily) stick an object in query params, so we use
21892 }; 24116 // a serialized string of key:dir and stick that in the query params.
21893 } 24117 // This is deserialized (into _sort) for easier use
24118 _sortstr: {
24119 type: String,
21894 }, 24120 },
21895 24121
21896 _verbose: { 24122 _verbose: {
21897 type: Boolean, 24123 type: Boolean,
21898 } 24124 }
21899 }, 24125 },
21900 24126
21901 _botClass: function(bot) { 24127 _botClass: function(bot) {
21902 if (bot.is_dead) { 24128 if (bot.is_dead) {
21903 return "dead"; 24129 return "dead";
(...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after
21954 }, 24180 },
21955 24181
21956 _header: function(col){ 24182 _header: function(col){
21957 return headerMap[col]; 24183 return headerMap[col];
21958 }, 24184 },
21959 24185
21960 _hide: function(col) { 24186 _hide: function(col) {
21961 return this._columns.indexOf(col) === -1; 24187 return this._columns.indexOf(col) === -1;
21962 }, 24188 },
21963 24189
24190 _makeObject: function(sortstr){
24191 if (!sortstr) {
24192 return undefined;
24193 }
24194 var pieces = sortstr.split(":");
24195 if (pieces.length != 2) {
24196 // fail safe
24197 return {name: "id", direction:"desc"};
24198 }
24199 return {
24200 name: pieces[0],
24201 direction: pieces[1],
24202 }
24203 },
24204
21964 _reRender: function(filter, sort) { 24205 _reRender: function(filter, sort) {
21965 this.$.bot_table.render(); 24206 this.$.bot_table.render();
21966 }, 24207 },
21967 24208
21968 _sortBotTable: function(botA, botB) { 24209 _sortBotTable: function(botA, botB) {
21969 if (!this._sort) { 24210 if (!this._sort) {
21970 return 0; 24211 return 0;
21971 } 24212 }
21972 var dir = 1; 24213 var dir = 1;
21973 if (this._sort.direction === "desc") { 24214 if (this._sort.direction === "desc") {
21974 dir = -1; 24215 dir = -1;
21975 } 24216 }
21976 var sort = specialSort[this._sort.name]; 24217 var sort = specialSort[this._sort.name];
21977 if (sort) { 24218 if (sort) {
21978 return sort.bind(this)(dir, botA, botB); 24219 return sort.bind(this)(dir, botA, botB);
21979 } 24220 }
21980 // Default to a natural compare of the columns. 24221 // Default to a natural compare of the columns.
21981 var botACol = this._column(this._sort.name, botA); 24222 var botACol = this._column(this._sort.name, botA);
21982 var botBCol = this._column(this._sort.name, botB); 24223 var botBCol = this._column(this._sort.name, botB);
21983 24224
21984 return dir * swarming.naturalCompare(botACol, botBCol); 24225 return dir * swarming.naturalCompare(botACol, botBCol);
21985 }, 24226 },
21986 24227
21987 _sortChange: function(e) { 24228 _sortChange: function(e) {
21988 // The event we get from sort-toggle tells us the name of what needs 24229 // The event we get from sort-toggle tells us the name of what needs
21989 // to be sorting and how to sort it. 24230 // to be sorting and how to sort it.
21990 if (!(e && e.detail && e.detail.name)) { 24231 if (!(e && e.detail && e.detail.name)) {
21991 return; 24232 return;
21992 } 24233 }
21993 // should trigger __filterAndSort 24234 // should trigger the computation of _sort and __filterAndSort
21994 this.set("_sort", e.detail); 24235 this.set("_sortstr", e.detail.name +":"+e.detail.direction);
21995 }, 24236 },
21996 24237
21997 // _stripSpecial removes the special columns and sorts the remaining 24238 // _stripSpecial removes the special columns and sorts the remaining
21998 // columns so they always appear in the same order, regardless of 24239 // columns so they always appear in the same order, regardless of
21999 // the order they are added. 24240 // the order they are added.
22000 _stripSpecial: function(){ 24241 _stripSpecial: function(){
22001 return this._columns.filter(function(c){ 24242 return this._columns.filter(function(c){
22002 return special_columns.indexOf(c) === -1; 24243 return special_columns.indexOf(c) === -1;
22003 }).sort(); 24244 }).sort();
22004 }, 24245 },
22005 24246
22006 _taskLink: function(data) { 24247 _taskLink: function(data) {
22007 if (data && data.task_id) { 24248 if (data && data.task_id) {
22008 return "/user/task/" + data.task_id; 24249 return "/user/task/" + data.task_id;
22009 } 24250 }
22010 return undefined; 24251 return undefined;
22011 } 24252 }
22012 24253
22013 }); 24254 });
22014 })(); 24255 })();
22015 </script> 24256 </script>
22016 </dom-module></div></body></html> 24257 </dom-module></div></body></html>
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698