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

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

Powered by Google App Engine
This is Rietveld 408576698