| Index: appengine/swarming/elements/build/index-build.html
|
| diff --git a/appengine/swarming/elements/build/index-build.html b/appengine/swarming/elements/build/index-build.html
|
| index 64869933d151529ae0d5553a77d9f50620796927..8027a65679d12181ef72d89b0019d0c7bc25e189 100644
|
| --- a/appengine/swarming/elements/build/index-build.html
|
| +++ b/appengine/swarming/elements/build/index-build.html
|
| @@ -1,5 +1,13 @@
|
| <!DOCTYPE html><html><head><!--
|
| @license
|
| +Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
|
| +This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
| +The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
| +The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
| +Code distributed by Google as part of the polymer project is also
|
| +subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
| +--><!--
|
| +@license
|
| Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
|
| This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
| The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
| @@ -9159,6 +9167,4237 @@ if (!window.Promise) {
|
|
|
|
|
| <script>
|
| + Polymer.AppLayout = Polymer.AppLayout || {};
|
| +
|
| + Polymer.AppLayout._scrollEffects = Polymer.AppLayout._scrollEffects || {};
|
| +
|
| + Polymer.AppLayout.scrollTimingFunction = function easeOutQuad(t, b, c, d) {
|
| + t /= d;
|
| + return -c * t*(t-2) + b;
|
| + };
|
| +
|
| + /**
|
| + * Registers a scroll effect to be used in elements that implement the
|
| + * `Polymer.AppScrollEffectsBehavior` behavior.
|
| + *
|
| + * @param {string} effectName The effect name.
|
| + * @param {Object} effectDef The effect definition.
|
| + */
|
| + Polymer.AppLayout.registerEffect = function registerEffect(effectName, effectDef) {
|
| + if (Polymer.AppLayout._scrollEffects[effectName] != null) {
|
| + throw new Error('effect `'+ effectName + '` is already registered.');
|
| + }
|
| + Polymer.AppLayout._scrollEffects[effectName] = effectDef;
|
| + };
|
| +
|
| + /**
|
| + * Scrolls to a particular set of coordinates in a scroll target.
|
| + * If the scroll target is not defined, then it would use the main document as the target.
|
| + *
|
| + * To scroll in a smooth fashion, you can set the option `behavior: 'smooth'`. e.g.
|
| + *
|
| + * ```js
|
| + * Polymer.AppLayout.scroll({top: 0, behavior: 'smooth'});
|
| + * ```
|
| + *
|
| + * To scroll in a silent mode, without notifying scroll changes to any app-layout elements,
|
| + * you can set the option `behavior: 'silent'`. This is particularly useful we you are using
|
| + * `app-header` and you desire to scroll to the top of a scrolling region without running
|
| + * scroll effects. e.g.
|
| + *
|
| + * ```js
|
| + * Polymer.AppLayout.scroll({top: 0, behavior: 'silent'});
|
| + * ```
|
| + *
|
| + * @param {Object} options {top: Number, left: Number, behavior: String(smooth | silent)}
|
| + */
|
| + Polymer.AppLayout.scroll = function scroll(options) {
|
| + options = options || {};
|
| +
|
| + var docEl = document.documentElement;
|
| + var target = options.target || docEl;
|
| + var hasNativeScrollBehavior = 'scrollBehavior' in target.style && target.scroll;
|
| + var scrollClassName = 'app-layout-silent-scroll';
|
| + var scrollTop = options.top || 0;
|
| + var scrollLeft = options.left || 0;
|
| + var scrollTo = target === docEl ? window.scrollTo :
|
| + function scrollTo(scrollLeft, scrollTop) {
|
| + target.scrollLeft = scrollLeft;
|
| + target.scrollTop = scrollTop;
|
| + };
|
| +
|
| + if (options.behavior === 'smooth') {
|
| +
|
| + if (hasNativeScrollBehavior) {
|
| +
|
| + target.scroll(options);
|
| +
|
| + } else {
|
| +
|
| + var timingFn = Polymer.AppLayout.scrollTimingFunction;
|
| + var startTime = Date.now();
|
| + var currentScrollTop = target === docEl ? window.pageYOffset : target.scrollTop;
|
| + var currentScrollLeft = target === docEl ? window.pageXOffset : target.scrollLeft;
|
| + var deltaScrollTop = scrollTop - currentScrollTop;
|
| + var deltaScrollLeft = scrollLeft - currentScrollLeft;
|
| + var duration = 300;
|
| + var updateFrame = (function updateFrame() {
|
| + var now = Date.now();
|
| + var elapsedTime = now - startTime;
|
| +
|
| + if (elapsedTime < duration) {
|
| + scrollTo(timingFn(elapsedTime, currentScrollLeft, deltaScrollLeft, duration),
|
| + timingFn(elapsedTime, currentScrollTop, deltaScrollTop, duration));
|
| + requestAnimationFrame(updateFrame);
|
| + } else {
|
| + scrollTo(scrollLeft, scrollTop);
|
| + }
|
| + }).bind(this);
|
| +
|
| + updateFrame();
|
| + }
|
| +
|
| + } else if (options.behavior === 'silent') {
|
| +
|
| + docEl.classList.add(scrollClassName);
|
| +
|
| + // Browsers keep the scroll momentum even if the bottom of the scrolling content
|
| + // was reached. This means that calling scroll({top: 0, behavior: 'silent'}) when
|
| + // the momentum is still going will result in more scroll events and thus scroll effects.
|
| + // This seems to only apply when using document scrolling.
|
| + // Therefore, when should we remove the class from the document element?
|
| +
|
| + clearInterval(Polymer.AppLayout._scrollTimer);
|
| +
|
| + Polymer.AppLayout._scrollTimer = setTimeout(function() {
|
| + docEl.classList.remove(scrollClassName);
|
| + Polymer.AppLayout._scrollTimer = null;
|
| + }, 100);
|
| +
|
| + scrollTo(scrollLeft, scrollTop);
|
| +
|
| + } else {
|
| +
|
| + scrollTo(scrollLeft, scrollTop);
|
| +
|
| + }
|
| + };
|
| +
|
| +</script>
|
| +
|
| +
|
| +<style>
|
| + /* IE 10 support for HTML5 hidden attr */
|
| + [hidden] {
|
| + display: none !important;
|
| + }
|
| +</style>
|
| +
|
| +<style is="custom-style">
|
| + :root {
|
| +
|
| + --layout: {
|
| + display: -ms-flexbox;
|
| + display: -webkit-flex;
|
| + display: flex;
|
| + };
|
| +
|
| + --layout-inline: {
|
| + display: -ms-inline-flexbox;
|
| + display: -webkit-inline-flex;
|
| + display: inline-flex;
|
| + };
|
| +
|
| + --layout-horizontal: {
|
| + @apply(--layout);
|
| +
|
| + -ms-flex-direction: row;
|
| + -webkit-flex-direction: row;
|
| + flex-direction: row;
|
| + };
|
| +
|
| + --layout-horizontal-reverse: {
|
| + @apply(--layout);
|
| +
|
| + -ms-flex-direction: row-reverse;
|
| + -webkit-flex-direction: row-reverse;
|
| + flex-direction: row-reverse;
|
| + };
|
| +
|
| + --layout-vertical: {
|
| + @apply(--layout);
|
| +
|
| + -ms-flex-direction: column;
|
| + -webkit-flex-direction: column;
|
| + flex-direction: column;
|
| + };
|
| +
|
| + --layout-vertical-reverse: {
|
| + @apply(--layout);
|
| +
|
| + -ms-flex-direction: column-reverse;
|
| + -webkit-flex-direction: column-reverse;
|
| + flex-direction: column-reverse;
|
| + };
|
| +
|
| + --layout-wrap: {
|
| + -ms-flex-wrap: wrap;
|
| + -webkit-flex-wrap: wrap;
|
| + flex-wrap: wrap;
|
| + };
|
| +
|
| + --layout-wrap-reverse: {
|
| + -ms-flex-wrap: wrap-reverse;
|
| + -webkit-flex-wrap: wrap-reverse;
|
| + flex-wrap: wrap-reverse;
|
| + };
|
| +
|
| + --layout-flex-auto: {
|
| + -ms-flex: 1 1 auto;
|
| + -webkit-flex: 1 1 auto;
|
| + flex: 1 1 auto;
|
| + };
|
| +
|
| + --layout-flex-none: {
|
| + -ms-flex: none;
|
| + -webkit-flex: none;
|
| + flex: none;
|
| + };
|
| +
|
| + --layout-flex: {
|
| + -ms-flex: 1 1 0.000000001px;
|
| + -webkit-flex: 1;
|
| + flex: 1;
|
| + -webkit-flex-basis: 0.000000001px;
|
| + flex-basis: 0.000000001px;
|
| + };
|
| +
|
| + --layout-flex-2: {
|
| + -ms-flex: 2;
|
| + -webkit-flex: 2;
|
| + flex: 2;
|
| + };
|
| +
|
| + --layout-flex-3: {
|
| + -ms-flex: 3;
|
| + -webkit-flex: 3;
|
| + flex: 3;
|
| + };
|
| +
|
| + --layout-flex-4: {
|
| + -ms-flex: 4;
|
| + -webkit-flex: 4;
|
| + flex: 4;
|
| + };
|
| +
|
| + --layout-flex-5: {
|
| + -ms-flex: 5;
|
| + -webkit-flex: 5;
|
| + flex: 5;
|
| + };
|
| +
|
| + --layout-flex-6: {
|
| + -ms-flex: 6;
|
| + -webkit-flex: 6;
|
| + flex: 6;
|
| + };
|
| +
|
| + --layout-flex-7: {
|
| + -ms-flex: 7;
|
| + -webkit-flex: 7;
|
| + flex: 7;
|
| + };
|
| +
|
| + --layout-flex-8: {
|
| + -ms-flex: 8;
|
| + -webkit-flex: 8;
|
| + flex: 8;
|
| + };
|
| +
|
| + --layout-flex-9: {
|
| + -ms-flex: 9;
|
| + -webkit-flex: 9;
|
| + flex: 9;
|
| + };
|
| +
|
| + --layout-flex-10: {
|
| + -ms-flex: 10;
|
| + -webkit-flex: 10;
|
| + flex: 10;
|
| + };
|
| +
|
| + --layout-flex-11: {
|
| + -ms-flex: 11;
|
| + -webkit-flex: 11;
|
| + flex: 11;
|
| + };
|
| +
|
| + --layout-flex-12: {
|
| + -ms-flex: 12;
|
| + -webkit-flex: 12;
|
| + flex: 12;
|
| + };
|
| +
|
| + /* alignment in cross axis */
|
| +
|
| + --layout-start: {
|
| + -ms-flex-align: start;
|
| + -webkit-align-items: flex-start;
|
| + align-items: flex-start;
|
| + };
|
| +
|
| + --layout-center: {
|
| + -ms-flex-align: center;
|
| + -webkit-align-items: center;
|
| + align-items: center;
|
| + };
|
| +
|
| + --layout-end: {
|
| + -ms-flex-align: end;
|
| + -webkit-align-items: flex-end;
|
| + align-items: flex-end;
|
| + };
|
| +
|
| + --layout-baseline: {
|
| + -ms-flex-align: baseline;
|
| + -webkit-align-items: baseline;
|
| + align-items: baseline;
|
| + };
|
| +
|
| + /* alignment in main axis */
|
| +
|
| + --layout-start-justified: {
|
| + -ms-flex-pack: start;
|
| + -webkit-justify-content: flex-start;
|
| + justify-content: flex-start;
|
| + };
|
| +
|
| + --layout-center-justified: {
|
| + -ms-flex-pack: center;
|
| + -webkit-justify-content: center;
|
| + justify-content: center;
|
| + };
|
| +
|
| + --layout-end-justified: {
|
| + -ms-flex-pack: end;
|
| + -webkit-justify-content: flex-end;
|
| + justify-content: flex-end;
|
| + };
|
| +
|
| + --layout-around-justified: {
|
| + -ms-flex-pack: distribute;
|
| + -webkit-justify-content: space-around;
|
| + justify-content: space-around;
|
| + };
|
| +
|
| + --layout-justified: {
|
| + -ms-flex-pack: justify;
|
| + -webkit-justify-content: space-between;
|
| + justify-content: space-between;
|
| + };
|
| +
|
| + --layout-center-center: {
|
| + @apply(--layout-center);
|
| + @apply(--layout-center-justified);
|
| + };
|
| +
|
| + /* self alignment */
|
| +
|
| + --layout-self-start: {
|
| + -ms-align-self: flex-start;
|
| + -webkit-align-self: flex-start;
|
| + align-self: flex-start;
|
| + };
|
| +
|
| + --layout-self-center: {
|
| + -ms-align-self: center;
|
| + -webkit-align-self: center;
|
| + align-self: center;
|
| + };
|
| +
|
| + --layout-self-end: {
|
| + -ms-align-self: flex-end;
|
| + -webkit-align-self: flex-end;
|
| + align-self: flex-end;
|
| + };
|
| +
|
| + --layout-self-stretch: {
|
| + -ms-align-self: stretch;
|
| + -webkit-align-self: stretch;
|
| + align-self: stretch;
|
| + };
|
| +
|
| + --layout-self-baseline: {
|
| + -ms-align-self: baseline;
|
| + -webkit-align-self: baseline;
|
| + align-self: baseline;
|
| + };
|
| +
|
| + /* multi-line alignment in main axis */
|
| +
|
| + --layout-start-aligned: {
|
| + -ms-flex-line-pack: start; /* IE10 */
|
| + -ms-align-content: flex-start;
|
| + -webkit-align-content: flex-start;
|
| + align-content: flex-start;
|
| + };
|
| +
|
| + --layout-end-aligned: {
|
| + -ms-flex-line-pack: end; /* IE10 */
|
| + -ms-align-content: flex-end;
|
| + -webkit-align-content: flex-end;
|
| + align-content: flex-end;
|
| + };
|
| +
|
| + --layout-center-aligned: {
|
| + -ms-flex-line-pack: center; /* IE10 */
|
| + -ms-align-content: center;
|
| + -webkit-align-content: center;
|
| + align-content: center;
|
| + };
|
| +
|
| + --layout-between-aligned: {
|
| + -ms-flex-line-pack: justify; /* IE10 */
|
| + -ms-align-content: space-between;
|
| + -webkit-align-content: space-between;
|
| + align-content: space-between;
|
| + };
|
| +
|
| + --layout-around-aligned: {
|
| + -ms-flex-line-pack: distribute; /* IE10 */
|
| + -ms-align-content: space-around;
|
| + -webkit-align-content: space-around;
|
| + align-content: space-around;
|
| + };
|
| +
|
| + /*******************************
|
| + Other Layout
|
| + *******************************/
|
| +
|
| + --layout-block: {
|
| + display: block;
|
| + };
|
| +
|
| + --layout-invisible: {
|
| + visibility: hidden !important;
|
| + };
|
| +
|
| + --layout-relative: {
|
| + position: relative;
|
| + };
|
| +
|
| + --layout-fit: {
|
| + position: absolute;
|
| + top: 0;
|
| + right: 0;
|
| + bottom: 0;
|
| + left: 0;
|
| + };
|
| +
|
| + --layout-scroll: {
|
| + -webkit-overflow-scrolling: touch;
|
| + overflow: auto;
|
| + };
|
| +
|
| + --layout-fullbleed: {
|
| + margin: 0;
|
| + height: 100vh;
|
| + };
|
| +
|
| + /* fixed position */
|
| +
|
| + --layout-fixed-top: {
|
| + position: fixed;
|
| + top: 0;
|
| + left: 0;
|
| + right: 0;
|
| + };
|
| +
|
| + --layout-fixed-right: {
|
| + position: fixed;
|
| + top: 0;
|
| + right: 0;
|
| + bottom: 0;
|
| + };
|
| +
|
| + --layout-fixed-bottom: {
|
| + position: fixed;
|
| + right: 0;
|
| + bottom: 0;
|
| + left: 0;
|
| + };
|
| +
|
| + --layout-fixed-left: {
|
| + position: fixed;
|
| + top: 0;
|
| + bottom: 0;
|
| + left: 0;
|
| + };
|
| +
|
| + }
|
| +
|
| +</style>
|
| +
|
| +
|
| +<dom-module id="app-drawer" assetpath="bower_components/app-layout/app-drawer/">
|
| + <template>
|
| + <style>
|
| + :host {
|
| + position: fixed;
|
| + top: -120px;
|
| + right: 0;
|
| + bottom: -120px;
|
| + left: 0;
|
| +
|
| + visibility: hidden;
|
| +
|
| + transition: visibility 0.2s ease;
|
| + }
|
| +
|
| + :host([opened]) {
|
| + visibility: visible;
|
| + }
|
| +
|
| + :host([persistent]) {
|
| + width: var(--app-drawer-width, 256px);
|
| + }
|
| +
|
| + :host([persistent][position=left]) {
|
| + right: auto;
|
| + }
|
| +
|
| + :host([persistent][position=right]) {
|
| + left: auto;
|
| + }
|
| +
|
| + #contentContainer {
|
| + position: absolute;
|
| + top: 0;
|
| + bottom: 0;
|
| + left: 0;
|
| +
|
| + width: var(--app-drawer-width, 256px);
|
| + padding: 120px 0;
|
| +
|
| + transition: 0.2s ease;
|
| + transition-property: -webkit-transform;
|
| + transition-property: transform;
|
| + -webkit-transform: translate3d(-100%, 0, 0);
|
| + transform: translate3d(-100%, 0, 0);
|
| +
|
| + background-color: #FFF;
|
| +
|
| + @apply(--app-drawer-content-container);
|
| + }
|
| +
|
| + :host([position=right]) > #contentContainer {
|
| + right: 0;
|
| + left: auto;
|
| +
|
| + -webkit-transform: translate3d(100%, 0, 0);
|
| + transform: translate3d(100%, 0, 0);
|
| + }
|
| +
|
| + :host([swipe-open]) > #contentContainer::after {
|
| + position: fixed;
|
| + top: 0;
|
| + bottom: 0;
|
| + left: 100%;
|
| +
|
| + visibility: visible;
|
| +
|
| + width: 20px;
|
| +
|
| + content: '';
|
| + }
|
| +
|
| + :host([swipe-open][position=right]) > #contentContainer::after {
|
| + right: 100%;
|
| + left: auto;
|
| + }
|
| +
|
| + :host([opened]) > #contentContainer {
|
| + -webkit-transform: translate3d(0, 0, 0);
|
| + transform: translate3d(0, 0, 0);
|
| + }
|
| +
|
| + #scrim {
|
| + position: absolute;
|
| + top: 0;
|
| + right: 0;
|
| + bottom: 0;
|
| + left: 0;
|
| +
|
| + transition: opacity 0.2s ease;
|
| + -webkit-transform: translateZ(0);
|
| + transform: translateZ(0);
|
| +
|
| + opacity: 0;
|
| + background: var(--app-drawer-scrim-background, rgba(0, 0, 0, 0.5));
|
| + }
|
| +
|
| + :host([opened]) > #scrim {
|
| + opacity: 1;
|
| + }
|
| +
|
| + :host([opened][persistent]) > #scrim {
|
| + visibility: hidden;
|
| + /**
|
| + * NOTE(keanulee): Keep both opacity: 0 and visibility: hidden to prevent the
|
| + * scrim from showing when toggling between closed and opened/persistent.
|
| + */
|
| +
|
| + opacity: 0;
|
| + }
|
| + </style>
|
| +
|
| + <div id="scrim" on-tap="close"></div>
|
| +
|
| + <div id="contentContainer">
|
| + <content></content>
|
| + </div>
|
| + </template>
|
| +
|
| + <script>
|
| +
|
| + Polymer({
|
| + is: 'app-drawer',
|
| +
|
| + properties: {
|
| + /**
|
| + * The opened state of the drawer.
|
| + */
|
| + opened: {
|
| + type: Boolean,
|
| + value: false,
|
| + notify: true,
|
| + reflectToAttribute: true
|
| + },
|
| +
|
| + /**
|
| + * The drawer does not have a scrim and cannot be swiped close.
|
| + */
|
| + persistent: {
|
| + type: Boolean,
|
| + value: false,
|
| + reflectToAttribute: true
|
| + },
|
| +
|
| + /**
|
| + * The alignment of the drawer on the screen ('left', 'right', 'start' or 'end').
|
| + * 'start' computes to left and 'end' to right in LTR layout and vice versa in RTL
|
| + * layout.
|
| + */
|
| + align: {
|
| + type: String,
|
| + value: 'left'
|
| + },
|
| +
|
| + /**
|
| + * The computed, read-only position of the drawer on the screen ('left' or 'right').
|
| + */
|
| + position: {
|
| + type: String,
|
| + readOnly: true,
|
| + reflectToAttribute: true
|
| + },
|
| +
|
| + /**
|
| + * Create an area at the edge of the screen to swipe open the drawer.
|
| + */
|
| + swipeOpen: {
|
| + type: Boolean,
|
| + value: false,
|
| + reflectToAttribute: true
|
| + },
|
| +
|
| + /**
|
| + * Trap keyboard focus when the drawer is opened and not persistent.
|
| + */
|
| + noFocusTrap: {
|
| + type: Boolean,
|
| + value: false
|
| + }
|
| + },
|
| +
|
| + observers: [
|
| + 'resetLayout(position, isAttached)',
|
| + '_resetPosition(align, isAttached)'
|
| + ],
|
| +
|
| + _translateOffset: 0,
|
| +
|
| + _trackDetails: null,
|
| +
|
| + _drawerState: 0,
|
| +
|
| + _boundEscKeydownHandler: null,
|
| +
|
| + _firstTabStop: null,
|
| +
|
| + _lastTabStop: null,
|
| +
|
| + ready: function() {
|
| + // Only transition the drawer after its first render (e.g. app-drawer-layout
|
| + // may need to set the initial opened state which should not be transitioned).
|
| + this._setTransitionDuration('0s');
|
| + },
|
| +
|
| + attached: function() {
|
| + // Only transition the drawer after its first render (e.g. app-drawer-layout
|
| + // may need to set the initial opened state which should not be transitioned).
|
| + Polymer.RenderStatus.afterNextRender(this, function() {
|
| + this._setTransitionDuration('');
|
| + this._boundEscKeydownHandler = this._escKeydownHandler.bind(this);
|
| + this._resetDrawerState();
|
| +
|
| + this.addEventListener('transitionend', this._transitionend.bind(this));
|
| + this.addEventListener('keydown', this._tabKeydownHandler.bind(this))
|
| +
|
| + // Only listen for horizontal track so you can vertically scroll inside the drawer.
|
| + this.listen(this, 'track', '_track');
|
| + this.setScrollDirection('y');
|
| + });
|
| + },
|
| +
|
| + detached: function() {
|
| + document.removeEventListener('keydown', this._boundEscKeydownHandler);
|
| + },
|
| +
|
| + /**
|
| + * Opens the drawer.
|
| + */
|
| + open: function() {
|
| + this.opened = true;
|
| + },
|
| +
|
| + /**
|
| + * Closes the drawer.
|
| + */
|
| + close: function() {
|
| + this.opened = false;
|
| + },
|
| +
|
| + /**
|
| + * Toggles the drawer open and close.
|
| + */
|
| + toggle: function() {
|
| + this.opened = !this.opened;
|
| + },
|
| +
|
| + /**
|
| + * Gets the width of the drawer.
|
| + *
|
| + * @return {number} The width of the drawer in pixels.
|
| + */
|
| + getWidth: function() {
|
| + return this.$.contentContainer.offsetWidth;
|
| + },
|
| +
|
| + /**
|
| + * Resets the layout. The event fired is used by app-drawer-layout to position the
|
| + * content.
|
| + *
|
| + * @method resetLayout
|
| + */
|
| + resetLayout: function() {
|
| + this.fire('app-drawer-reset-layout');
|
| + },
|
| +
|
| + _isRTL: function() {
|
| + return window.getComputedStyle(this).direction === 'rtl';
|
| + },
|
| +
|
| + _resetPosition: function() {
|
| + switch (this.align) {
|
| + case 'start':
|
| + this._setPosition(this._isRTL() ? 'right' : 'left');
|
| + return;
|
| + case 'end':
|
| + this._setPosition(this._isRTL() ? 'left' : 'right');
|
| + return;
|
| + }
|
| + this._setPosition(this.align);
|
| + },
|
| +
|
| + _escKeydownHandler: function(event) {
|
| + var ESC_KEYCODE = 27;
|
| + if (event.keyCode === ESC_KEYCODE) {
|
| + // Prevent any side effects if app-drawer closes.
|
| + event.preventDefault();
|
| + this.close();
|
| + }
|
| + },
|
| +
|
| + _track: function(event) {
|
| + if (this.persistent) {
|
| + return;
|
| + }
|
| +
|
| + // Disable user selection on desktop.
|
| + event.preventDefault();
|
| +
|
| + switch (event.detail.state) {
|
| + case 'start':
|
| + this._trackStart(event);
|
| + break;
|
| + case 'track':
|
| + this._trackMove(event);
|
| + break;
|
| + case 'end':
|
| + this._trackEnd(event);
|
| + break;
|
| + }
|
| + },
|
| +
|
| + _trackStart: function(event) {
|
| + this._drawerState = this._DRAWER_STATE.TRACKING;
|
| +
|
| + // Disable transitions since style attributes will reflect user track events.
|
| + this._setTransitionDuration('0s');
|
| + this.style.visibility = 'visible';
|
| +
|
| + var rect = this.$.contentContainer.getBoundingClientRect();
|
| + if (this.position === 'left') {
|
| + this._translateOffset = rect.left;
|
| + } else {
|
| + this._translateOffset = rect.right - window.innerWidth;
|
| + }
|
| +
|
| + this._trackDetails = [];
|
| + },
|
| +
|
| + _trackMove: function(event) {
|
| + this._translateDrawer(event.detail.dx + this._translateOffset);
|
| +
|
| + // Use Date.now() since event.timeStamp is inconsistent across browsers (e.g. most
|
| + // browsers use milliseconds but FF 44 uses microseconds).
|
| + this._trackDetails.push({
|
| + dx: event.detail.dx,
|
| + timeStamp: Date.now()
|
| + });
|
| + },
|
| +
|
| + _trackEnd: function(event) {
|
| + var x = event.detail.dx + this._translateOffset;
|
| + var drawerWidth = this.getWidth();
|
| + var isPositionLeft = this.position === 'left';
|
| + var isInEndState = isPositionLeft ? (x >= 0 || x <= -drawerWidth) :
|
| + (x <= 0 || x >= drawerWidth);
|
| +
|
| + if (!isInEndState) {
|
| + // No longer need the track events after this method returns - allow them to be GC'd.
|
| + var trackDetails = this._trackDetails;
|
| + this._trackDetails = null;
|
| +
|
| + this._flingDrawer(event, trackDetails);
|
| + if (this._drawerState === this._DRAWER_STATE.FLINGING) {
|
| + return;
|
| + }
|
| + }
|
| +
|
| + // If the drawer is not flinging, toggle the opened state based on the position of
|
| + // the drawer.
|
| + var halfWidth = drawerWidth / 2;
|
| + if (event.detail.dx < -halfWidth) {
|
| + this.opened = this.position === 'right';
|
| + } else if (event.detail.dx > halfWidth) {
|
| + this.opened = this.position === 'left';
|
| + }
|
| +
|
| + // Trigger app-drawer-transitioned now since there will be no transitionend event.
|
| + if (isInEndState) {
|
| + this._resetDrawerState();
|
| + }
|
| +
|
| + this._setTransitionDuration('');
|
| + this._resetDrawerTranslate();
|
| + this.style.visibility = '';
|
| + },
|
| +
|
| + _calculateVelocity: function(event, trackDetails) {
|
| + // Find the oldest track event that is within 100ms using binary search.
|
| + var now = Date.now();
|
| + var timeLowerBound = now - 100;
|
| + var trackDetail;
|
| + var min = 0;
|
| + var max = trackDetails.length - 1;
|
| +
|
| + while (min <= max) {
|
| + // Floor of average of min and max.
|
| + var mid = (min + max) >> 1;
|
| + var d = trackDetails[mid];
|
| + if (d.timeStamp >= timeLowerBound) {
|
| + trackDetail = d;
|
| + max = mid - 1;
|
| + } else {
|
| + min = mid + 1;
|
| + }
|
| + }
|
| +
|
| + if (trackDetail) {
|
| + var dx = event.detail.dx - trackDetail.dx;
|
| + var dt = (now - trackDetail.timeStamp) || 1;
|
| + return dx / dt;
|
| + }
|
| + return 0;
|
| + },
|
| +
|
| + _flingDrawer: function(event, trackDetails) {
|
| + var velocity = this._calculateVelocity(event, trackDetails);
|
| +
|
| + // Do not fling if velocity is not above a threshold.
|
| + if (Math.abs(velocity) < this._MIN_FLING_THRESHOLD) {
|
| + return;
|
| + }
|
| +
|
| + this._drawerState = this._DRAWER_STATE.FLINGING;
|
| +
|
| + var x = event.detail.dx + this._translateOffset;
|
| + var drawerWidth = this.getWidth();
|
| + var isPositionLeft = this.position === 'left';
|
| + var isVelocityPositive = velocity > 0;
|
| + var isClosingLeft = !isVelocityPositive && isPositionLeft;
|
| + var isClosingRight = isVelocityPositive && !isPositionLeft;
|
| + var dx;
|
| + if (isClosingLeft) {
|
| + dx = -(x + drawerWidth);
|
| + } else if (isClosingRight) {
|
| + dx = (drawerWidth - x);
|
| + } else {
|
| + dx = -x;
|
| + }
|
| +
|
| + // Enforce a minimum transition velocity to make the drawer feel snappy.
|
| + if (isVelocityPositive) {
|
| + velocity = Math.max(velocity, this._MIN_TRANSITION_VELOCITY);
|
| + this.opened = this.position === 'left';
|
| + } else {
|
| + velocity = Math.min(velocity, -this._MIN_TRANSITION_VELOCITY);
|
| + this.opened = this.position === 'right';
|
| + }
|
| +
|
| + // Calculate the amount of time needed to finish the transition based on the
|
| + // initial slope of the timing function.
|
| + this._setTransitionDuration((this._FLING_INITIAL_SLOPE * dx / velocity) + 'ms');
|
| + this._setTransitionTimingFunction(this._FLING_TIMING_FUNCTION);
|
| +
|
| + this._resetDrawerTranslate();
|
| + },
|
| +
|
| + _transitionend: function(event) {
|
| + // contentContainer will transition on opened state changed, and scrim will
|
| + // transition on persistent state changed when opened - these are the
|
| + // transitions we are interested in.
|
| + var target = Polymer.dom(event).rootTarget;
|
| + if (target === this.$.contentContainer || target === this.$.scrim) {
|
| +
|
| + // If the drawer was flinging, we need to reset the style attributes.
|
| + if (this._drawerState === this._DRAWER_STATE.FLINGING) {
|
| + this._setTransitionDuration('');
|
| + this._setTransitionTimingFunction('');
|
| + this.style.visibility = '';
|
| + }
|
| +
|
| + this._resetDrawerState();
|
| + }
|
| + },
|
| +
|
| + _setTransitionDuration: function(duration) {
|
| + this.$.contentContainer.style.transitionDuration = duration;
|
| + this.$.scrim.style.transitionDuration = duration;
|
| + },
|
| +
|
| + _setTransitionTimingFunction: function(timingFunction) {
|
| + this.$.contentContainer.style.transitionTimingFunction = timingFunction;
|
| + this.$.scrim.style.transitionTimingFunction = timingFunction;
|
| + },
|
| +
|
| + _translateDrawer: function(x) {
|
| + var drawerWidth = this.getWidth();
|
| +
|
| + if (this.position === 'left') {
|
| + x = Math.max(-drawerWidth, Math.min(x, 0));
|
| + this.$.scrim.style.opacity = 1 + x / drawerWidth;
|
| + } else {
|
| + x = Math.max(0, Math.min(x, drawerWidth));
|
| + this.$.scrim.style.opacity = 1 - x / drawerWidth;
|
| + }
|
| +
|
| + this.translate3d(x + 'px', '0', '0', this.$.contentContainer);
|
| + },
|
| +
|
| + _resetDrawerTranslate: function() {
|
| + this.$.scrim.style.opacity = '';
|
| + this.transform('', this.$.contentContainer);
|
| + },
|
| +
|
| + _resetDrawerState: function() {
|
| + var oldState = this._drawerState;
|
| + if (this.opened) {
|
| + this._drawerState = this.persistent ?
|
| + this._DRAWER_STATE.OPENED_PERSISTENT : this._DRAWER_STATE.OPENED;
|
| + } else {
|
| + this._drawerState = this._DRAWER_STATE.CLOSED;
|
| + }
|
| +
|
| + if (oldState !== this._drawerState) {
|
| + if (this._drawerState === this._DRAWER_STATE.OPENED) {
|
| + this._setKeyboardFocusTrap();
|
| + document.addEventListener('keydown', this._boundEscKeydownHandler);
|
| + document.body.style.overflow = 'hidden';
|
| + } else {
|
| + document.removeEventListener('keydown', this._boundEscKeydownHandler);
|
| + document.body.style.overflow = '';
|
| + }
|
| +
|
| + // Don't fire the event on initial load.
|
| + if (oldState !== this._DRAWER_STATE.INIT) {
|
| + this.fire('app-drawer-transitioned');
|
| + }
|
| + }
|
| + },
|
| +
|
| + _setKeyboardFocusTrap: function() {
|
| + if (this.noFocusTrap) {
|
| + return;
|
| + }
|
| +
|
| + // NOTE: Unless we use /deep/ (which we shouldn't since it's deprecated), this will
|
| + // not select focusable elements inside shadow roots.
|
| + var focusableElementsSelector = [
|
| + 'a[href]:not([tabindex="-1"])',
|
| + 'area[href]:not([tabindex="-1"])',
|
| + 'input:not([disabled]):not([tabindex="-1"])',
|
| + 'select:not([disabled]):not([tabindex="-1"])',
|
| + 'textarea:not([disabled]):not([tabindex="-1"])',
|
| + 'button:not([disabled]):not([tabindex="-1"])',
|
| + 'iframe:not([tabindex="-1"])',
|
| + '[tabindex]:not([tabindex="-1"])',
|
| + '[contentEditable=true]:not([tabindex="-1"])'
|
| + ].join(',');
|
| + var focusableElements = Polymer.dom(this).querySelectorAll(focusableElementsSelector);
|
| +
|
| + if (focusableElements.length > 0) {
|
| + this._firstTabStop = focusableElements[0];
|
| + this._lastTabStop = focusableElements[focusableElements.length - 1];
|
| + } else {
|
| + // Reset saved tab stops when there are no focusable elements in the drawer.
|
| + this._firstTabStop = null;
|
| + this._lastTabStop = null;
|
| + }
|
| +
|
| + // Focus on app-drawer if it has non-zero tabindex. Otherwise, focus the first focusable
|
| + // element in the drawer, if it exists. Use the tabindex attribute since the this.tabIndex
|
| + // property in IE/Edge returns 0 (instead of -1) when the attribute is not set.
|
| + var tabindex = this.getAttribute('tabindex');
|
| + if (tabindex && parseInt(tabindex, 10) > -1) {
|
| + this.focus();
|
| + } else if (this._firstTabStop) {
|
| + this._firstTabStop.focus();
|
| + }
|
| + },
|
| +
|
| + _tabKeydownHandler: function(event) {
|
| + if (this.noFocusTrap) {
|
| + return;
|
| + }
|
| +
|
| + var TAB_KEYCODE = 9;
|
| + if (this._drawerState === this._DRAWER_STATE.OPENED && event.keyCode === TAB_KEYCODE) {
|
| + if (event.shiftKey) {
|
| + if (this._firstTabStop && Polymer.dom(event).localTarget === this._firstTabStop) {
|
| + event.preventDefault();
|
| + this._lastTabStop.focus();
|
| + }
|
| + } else {
|
| + if (this._lastTabStop && Polymer.dom(event).localTarget === this._lastTabStop) {
|
| + event.preventDefault();
|
| + this._firstTabStop.focus();
|
| + }
|
| + }
|
| + }
|
| + },
|
| +
|
| + _MIN_FLING_THRESHOLD: 0.2,
|
| +
|
| + _MIN_TRANSITION_VELOCITY: 1.2,
|
| +
|
| + _FLING_TIMING_FUNCTION: 'cubic-bezier(0.667, 1, 0.667, 1)',
|
| +
|
| + _FLING_INITIAL_SLOPE: 1.5,
|
| +
|
| + _DRAWER_STATE: {
|
| + INIT: 0,
|
| + OPENED: 1,
|
| + OPENED_PERSISTENT: 2,
|
| + CLOSED: 3,
|
| + TRACKING: 4,
|
| + FLINGING: 5
|
| + }
|
| +
|
| + /**
|
| + * Fired when the layout of app-drawer has changed.
|
| + *
|
| + * @event app-drawer-reset-layout
|
| + */
|
| +
|
| + /**
|
| + * Fired when app-drawer has finished transitioning.
|
| + *
|
| + * @event app-drawer-transitioned
|
| + */
|
| + });
|
| + </script>
|
| +</dom-module>
|
| +
|
| +
|
| +<script>
|
| +
|
| + Polymer({
|
| +
|
| + is: 'iron-media-query',
|
| +
|
| + properties: {
|
| +
|
| + /**
|
| + * The Boolean return value of the media query.
|
| + */
|
| + queryMatches: {
|
| + type: Boolean,
|
| + value: false,
|
| + readOnly: true,
|
| + notify: true
|
| + },
|
| +
|
| + /**
|
| + * The CSS media query to evaluate.
|
| + */
|
| + query: {
|
| + type: String,
|
| + observer: 'queryChanged'
|
| + },
|
| +
|
| + /**
|
| + * If true, the query attribute is assumed to be a complete media query
|
| + * string rather than a single media feature.
|
| + */
|
| + full: {
|
| + type: Boolean,
|
| + value: false
|
| + },
|
| +
|
| + /**
|
| + * @type {function(MediaQueryList)}
|
| + */
|
| + _boundMQHandler: {
|
| + value: function() {
|
| + return this.queryHandler.bind(this);
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * @type {MediaQueryList}
|
| + */
|
| + _mq: {
|
| + value: null
|
| + }
|
| + },
|
| +
|
| + attached: function() {
|
| + this.style.display = 'none';
|
| + this.queryChanged();
|
| + },
|
| +
|
| + detached: function() {
|
| + this._remove();
|
| + },
|
| +
|
| + _add: function() {
|
| + if (this._mq) {
|
| + this._mq.addListener(this._boundMQHandler);
|
| + }
|
| + },
|
| +
|
| + _remove: function() {
|
| + if (this._mq) {
|
| + this._mq.removeListener(this._boundMQHandler);
|
| + }
|
| + this._mq = null;
|
| + },
|
| +
|
| + queryChanged: function() {
|
| + this._remove();
|
| + var query = this.query;
|
| + if (!query) {
|
| + return;
|
| + }
|
| + if (!this.full && query[0] !== '(') {
|
| + query = '(' + query + ')';
|
| + }
|
| + this._mq = window.matchMedia(query);
|
| + this._add();
|
| + this.queryHandler(this._mq);
|
| + },
|
| +
|
| + queryHandler: function(mq) {
|
| + this._setQueryMatches(mq.matches);
|
| + }
|
| +
|
| + });
|
| +
|
| +</script>
|
| +<script>
|
| + /**
|
| + * `IronResizableBehavior` is a behavior that can be used in Polymer elements to
|
| + * coordinate the flow of resize events between "resizers" (elements that control the
|
| + * size or hidden state of their children) and "resizables" (elements that need to be
|
| + * notified when they are resized or un-hidden by their parents in order to take
|
| + * action on their new measurements).
|
| + *
|
| + * Elements that perform measurement should add the `IronResizableBehavior` behavior to
|
| + * their element definition and listen for the `iron-resize` event on themselves.
|
| + * This event will be fired when they become showing after having been hidden,
|
| + * when they are resized explicitly by another resizable, or when the window has been
|
| + * resized.
|
| + *
|
| + * Note, the `iron-resize` event is non-bubbling.
|
| + *
|
| + * @polymerBehavior Polymer.IronResizableBehavior
|
| + * @demo demo/index.html
|
| + **/
|
| + Polymer.IronResizableBehavior = {
|
| + properties: {
|
| + /**
|
| + * The closest ancestor element that implements `IronResizableBehavior`.
|
| + */
|
| + _parentResizable: {
|
| + type: Object,
|
| + observer: '_parentResizableChanged'
|
| + },
|
| +
|
| + /**
|
| + * True if this element is currently notifying its descedant elements of
|
| + * resize.
|
| + */
|
| + _notifyingDescendant: {
|
| + type: Boolean,
|
| + value: false
|
| + }
|
| + },
|
| +
|
| + listeners: {
|
| + 'iron-request-resize-notifications': '_onIronRequestResizeNotifications'
|
| + },
|
| +
|
| + created: function() {
|
| + // We don't really need property effects on these, and also we want them
|
| + // to be created before the `_parentResizable` observer fires:
|
| + this._interestedResizables = [];
|
| + this._boundNotifyResize = this.notifyResize.bind(this);
|
| + },
|
| +
|
| + attached: function() {
|
| + this.fire('iron-request-resize-notifications', null, {
|
| + node: this,
|
| + bubbles: true,
|
| + cancelable: true
|
| + });
|
| +
|
| + if (!this._parentResizable) {
|
| + window.addEventListener('resize', this._boundNotifyResize);
|
| + this.notifyResize();
|
| + }
|
| + },
|
| +
|
| + detached: function() {
|
| + if (this._parentResizable) {
|
| + this._parentResizable.stopResizeNotificationsFor(this);
|
| + } else {
|
| + window.removeEventListener('resize', this._boundNotifyResize);
|
| + }
|
| +
|
| + this._parentResizable = null;
|
| + },
|
| +
|
| + /**
|
| + * Can be called to manually notify a resizable and its descendant
|
| + * resizables of a resize change.
|
| + */
|
| + notifyResize: function() {
|
| + if (!this.isAttached) {
|
| + return;
|
| + }
|
| +
|
| + this._interestedResizables.forEach(function(resizable) {
|
| + if (this.resizerShouldNotify(resizable)) {
|
| + this._notifyDescendant(resizable);
|
| + }
|
| + }, this);
|
| +
|
| + this._fireResize();
|
| + },
|
| +
|
| + /**
|
| + * Used to assign the closest resizable ancestor to this resizable
|
| + * if the ancestor detects a request for notifications.
|
| + */
|
| + assignParentResizable: function(parentResizable) {
|
| + this._parentResizable = parentResizable;
|
| + },
|
| +
|
| + /**
|
| + * Used to remove a resizable descendant from the list of descendants
|
| + * that should be notified of a resize change.
|
| + */
|
| + stopResizeNotificationsFor: function(target) {
|
| + var index = this._interestedResizables.indexOf(target);
|
| +
|
| + if (index > -1) {
|
| + this._interestedResizables.splice(index, 1);
|
| + this.unlisten(target, 'iron-resize', '_onDescendantIronResize');
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * This method can be overridden to filter nested elements that should or
|
| + * should not be notified by the current element. Return true if an element
|
| + * should be notified, or false if it should not be notified.
|
| + *
|
| + * @param {HTMLElement} element A candidate descendant element that
|
| + * implements `IronResizableBehavior`.
|
| + * @return {boolean} True if the `element` should be notified of resize.
|
| + */
|
| + resizerShouldNotify: function(element) { return true; },
|
| +
|
| + _onDescendantIronResize: function(event) {
|
| + if (this._notifyingDescendant) {
|
| + event.stopPropagation();
|
| + return;
|
| + }
|
| +
|
| + // NOTE(cdata): In ShadowDOM, event retargetting makes echoing of the
|
| + // otherwise non-bubbling event "just work." We do it manually here for
|
| + // the case where Polymer is not using shadow roots for whatever reason:
|
| + if (!Polymer.Settings.useShadow) {
|
| + this._fireResize();
|
| + }
|
| + },
|
| +
|
| + _fireResize: function() {
|
| + this.fire('iron-resize', null, {
|
| + node: this,
|
| + bubbles: false
|
| + });
|
| + },
|
| +
|
| + _onIronRequestResizeNotifications: function(event) {
|
| + var target = event.path ? event.path[0] : event.target;
|
| +
|
| + if (target === this) {
|
| + return;
|
| + }
|
| +
|
| + if (this._interestedResizables.indexOf(target) === -1) {
|
| + this._interestedResizables.push(target);
|
| + this.listen(target, 'iron-resize', '_onDescendantIronResize');
|
| + }
|
| +
|
| + target.assignParentResizable(this);
|
| + this._notifyDescendant(target);
|
| +
|
| + event.stopPropagation();
|
| + },
|
| +
|
| + _parentResizableChanged: function(parentResizable) {
|
| + if (parentResizable) {
|
| + window.removeEventListener('resize', this._boundNotifyResize);
|
| + }
|
| + },
|
| +
|
| + _notifyDescendant: function(descendant) {
|
| + // NOTE(cdata): In IE10, attached is fired on children first, so it's
|
| + // important not to notify them if the parent is not attached yet (or
|
| + // else they will get redundantly notified when the parent attaches).
|
| + if (!this.isAttached) {
|
| + return;
|
| + }
|
| +
|
| + this._notifyingDescendant = true;
|
| + descendant.notifyResize();
|
| + this._notifyingDescendant = false;
|
| + }
|
| + };
|
| +</script>
|
| +
|
| +
|
| +
|
| +<dom-module id="app-drawer-layout" assetpath="bower_components/app-layout/app-drawer-layout/">
|
| + <template>
|
| + <style>
|
| + :host {
|
| + display: block;
|
| + }
|
| +
|
| + :host([fullbleed]) {
|
| + @apply(--layout-fit);
|
| + }
|
| +
|
| + #contentContainer {
|
| + position: relative;
|
| +
|
| + height: 100%;
|
| +
|
| + transition: var(--app-drawer-layout-content-transition, none);
|
| + }
|
| +
|
| + #contentContainer:not(.narrow) > ::content [drawer-toggle] {
|
| + display: none;
|
| + }
|
| + </style>
|
| +
|
| + <div id="contentContainer">
|
| + <content select=":not(app-drawer)"></content>
|
| + </div>
|
| +
|
| + <content id="drawerContent" select="app-drawer"></content>
|
| +
|
| + <iron-media-query query="[[_computeMediaQuery(forceNarrow, responsiveWidth)]]" on-query-matches-changed="_onQueryMatchesChanged"></iron-media-query>
|
| + </template>
|
| +
|
| + <script>
|
| + Polymer({
|
| + is: 'app-drawer-layout',
|
| +
|
| + behaviors: [
|
| + Polymer.IronResizableBehavior
|
| + ],
|
| +
|
| + properties: {
|
| + /**
|
| + * If true, ignore `responsiveWidth` setting and force the narrow layout.
|
| + */
|
| + forceNarrow: {
|
| + type: Boolean,
|
| + value: false
|
| + },
|
| +
|
| + /**
|
| + * If the viewport's width is smaller than this value, the panel will change to narrow
|
| + * layout. In the mode the drawer will be closed.
|
| + */
|
| + responsiveWidth: {
|
| + type: String,
|
| + value: '640px'
|
| + },
|
| +
|
| + /**
|
| + * Returns true if it is in narrow layout. This is useful if you need to show/hide
|
| + * elements based on the layout.
|
| + */
|
| + narrow: {
|
| + type: Boolean,
|
| + readOnly: true,
|
| + notify: true
|
| + }
|
| + },
|
| +
|
| + listeners: {
|
| + 'tap': '_tapHandler',
|
| + 'app-drawer-reset-layout': 'resetLayout'
|
| + },
|
| +
|
| + observers: [
|
| + 'resetLayout(narrow, isAttached)'
|
| + ],
|
| +
|
| + /**
|
| + * A reference to the app-drawer element.
|
| + *
|
| + * @property drawer
|
| + */
|
| + get drawer() {
|
| + return Polymer.dom(this.$.drawerContent).getDistributedNodes()[0];
|
| + },
|
| +
|
| + _tapHandler: function(e) {
|
| + var target = Polymer.dom(e).localTarget;
|
| + if (target && target.hasAttribute('drawer-toggle')) {
|
| + this.drawer.toggle();
|
| + }
|
| + },
|
| +
|
| + resetLayout: function() {
|
| + this.debounce('_resetLayout', function() {
|
| + var drawer = this.drawer;
|
| + var contentContainer = this.$.contentContainer;
|
| +
|
| + if (this.narrow) {
|
| + drawer.opened = drawer.persistent = false;
|
| + contentContainer.classList.add('narrow');
|
| +
|
| + contentContainer.style.marginLeft = '';
|
| + contentContainer.style.marginRight = '';
|
| + } else {
|
| + drawer.opened = drawer.persistent = true;
|
| + contentContainer.classList.remove('narrow');
|
| +
|
| + var drawerWidth = this.drawer.getWidth();
|
| + if (drawer.position == 'right') {
|
| + contentContainer.style.marginLeft = '';
|
| + contentContainer.style.marginRight = drawerWidth + 'px';
|
| + } else {
|
| + contentContainer.style.marginLeft = drawerWidth + 'px';
|
| + contentContainer.style.marginRight = '';
|
| + }
|
| + }
|
| +
|
| + this.notifyResize();
|
| + });
|
| + },
|
| +
|
| + _onQueryMatchesChanged: function(event) {
|
| + this._setNarrow(event.detail.value);
|
| + },
|
| +
|
| + _computeMediaQuery: function(forceNarrow, responsiveWidth) {
|
| + return forceNarrow ? '(min-width: 0px)' : '(max-width: ' + responsiveWidth + ')';
|
| + }
|
| + });
|
| + </script>
|
| +</dom-module>
|
| +
|
| +
|
| +<dom-module id="app-grid-style" assetpath="bower_components/app-layout/app-grid/">
|
| + <template>
|
| + <style>
|
| +
|
| + :host {
|
| + /**
|
| + * The width for the expandible item is:
|
| + * ((100% - subPixelAdjustment) / columns * itemColumns - gutter * (columns - itemColumns)
|
| + *
|
| + * - subPixelAdjustment: 0.1px (Required for IE 11)
|
| + * - gutter: var(--app-grid-gutter)
|
| + * - columns: var(--app-grid-columns)
|
| + * - itemColumn: var(--app-grid-expandible-item-columns)
|
| + */
|
| + --app-grid-expandible-item: {
|
| + -webkit-flex-basis: calc((100% - 0.1px) / var(--app-grid-columns, 1) * var(--app-grid-expandible-item-columns, 1) - (var(--app-grid-gutter, 0px) * (var(--app-grid-columns, 1) - var(--app-grid-expandible-item-columns, 1)))) !important;
|
| + flex-basis: calc((100% - 0.1px) / var(--app-grid-columns, 1) * var(--app-grid-expandible-item-columns, 1) - (var(--app-grid-gutter, 0px) * (var(--app-grid-columns, 1) - var(--app-grid-expandible-item-columns, 1)))) !important;
|
| + max-width: calc((100% - 0.1px) / var(--app-grid-columns, 1) * var(--app-grid-expandible-item-columns, 1) - (var(--app-grid-gutter, 0px) * (var(--app-grid-columns, 1) - var(--app-grid-expandible-item-columns, 1)))) !important;
|
| +
|
| + };
|
| + }
|
| +
|
| + .app-grid {
|
| + display: -ms-flexbox;
|
| + display: -webkit-flex;
|
| + display: flex;
|
| +
|
| + -ms-flex-direction: row;
|
| + -webkit-flex-direction: row;
|
| + flex-direction: row;
|
| +
|
| + -ms-flex-wrap: wrap;
|
| + -webkit-flex-wrap: wrap;
|
| + flex-wrap: wrap;
|
| +
|
| + margin-top: var(--app-grid-gutter, 0px);
|
| + margin-left: var(--app-grid-gutter, 0px);
|
| + }
|
| +
|
| + .app-grid > * {
|
| + /* Required for IE 10 */
|
| + -ms-flex: 1 1 100%;
|
| + -webkit-flex: 1;
|
| + flex: 1;
|
| +
|
| + /* The width for an item is: (100% - subPixelAdjustment - gutter * columns) / columns */
|
| + -webkit-flex-basis: calc((100% - 0.1px - (var(--app-grid-gutter, 0px) * var(--app-grid-columns, 1))) / var(--app-grid-columns, 1));
|
| + flex-basis: calc((100% - 0.1px - (var(--app-grid-gutter, 0px) * var(--app-grid-columns, 1))) / var(--app-grid-columns, 1));
|
| +
|
| + max-width: calc((100% - 0.1px - (var(--app-grid-gutter, 0px) * var(--app-grid-columns, 1))) / var(--app-grid-columns, 1));
|
| + margin-bottom: var(--app-grid-gutter, 0px);
|
| + margin-right: var(--app-grid-gutter, 0px);
|
| + height: var(--app-grid-item-height);
|
| + box-sizing: border-box;
|
| + }
|
| +
|
| + .app-grid[has-aspect-ratio] > * {
|
| + position: relative;
|
| + }
|
| +
|
| + .app-grid[has-aspect-ratio] > *::before {
|
| + display: block;
|
| + content: "";
|
| + padding-top: var(--app-grid-item-height, 100%);
|
| + }
|
| +
|
| + .app-grid[has-aspect-ratio] > * > * {
|
| + position: absolute;
|
| + top: 0;
|
| + right: 0;
|
| + bottom: 0;
|
| + left: 0;
|
| + }
|
| +
|
| + </style>
|
| + </template>
|
| +</dom-module>
|
| +<script>
|
| +
|
| + /**
|
| + * `Polymer.IronScrollTargetBehavior` allows an element to respond to scroll events from a
|
| + * designated scroll target.
|
| + *
|
| + * Elements that consume this behavior can override the `_scrollHandler`
|
| + * method to add logic on the scroll event.
|
| + *
|
| + * @demo demo/scrolling-region.html Scrolling Region
|
| + * @demo demo/document.html Document Element
|
| + * @polymerBehavior
|
| + */
|
| + Polymer.IronScrollTargetBehavior = {
|
| +
|
| + properties: {
|
| +
|
| + /**
|
| + * Specifies the element that will handle the scroll event
|
| + * on the behalf of the current element. This is typically a reference to an element,
|
| + * but there are a few more posibilities:
|
| + *
|
| + * ### Elements id
|
| + *
|
| + *```html
|
| + * <div id="scrollable-element" style="overflow: auto;">
|
| + * <x-element scroll-target="scrollable-element">
|
| + * <!-- Content-->
|
| + * </x-element>
|
| + * </div>
|
| + *```
|
| + * In this case, the `scrollTarget` will point to the outer div element.
|
| + *
|
| + * ### Document scrolling
|
| + *
|
| + * For document scrolling, you can use the reserved word `document`:
|
| + *
|
| + *```html
|
| + * <x-element scroll-target="document">
|
| + * <!-- Content -->
|
| + * </x-element>
|
| + *```
|
| + *
|
| + * ### Elements reference
|
| + *
|
| + *```js
|
| + * appHeader.scrollTarget = document.querySelector('#scrollable-element');
|
| + *```
|
| + *
|
| + * @type {HTMLElement}
|
| + */
|
| + scrollTarget: {
|
| + type: HTMLElement,
|
| + value: function() {
|
| + return this._defaultScrollTarget;
|
| + }
|
| + }
|
| + },
|
| +
|
| + observers: [
|
| + '_scrollTargetChanged(scrollTarget, isAttached)'
|
| + ],
|
| +
|
| + _scrollTargetChanged: function(scrollTarget, isAttached) {
|
| + var eventTarget;
|
| +
|
| + if (this._oldScrollTarget) {
|
| + eventTarget = this._oldScrollTarget === this._doc ? window : this._oldScrollTarget;
|
| + eventTarget.removeEventListener('scroll', this._boundScrollHandler);
|
| + this._oldScrollTarget = null;
|
| + }
|
| +
|
| + if (!isAttached) {
|
| + return;
|
| + }
|
| + // Support element id references
|
| + if (scrollTarget === 'document') {
|
| +
|
| + this.scrollTarget = this._doc;
|
| +
|
| + } else if (typeof scrollTarget === 'string') {
|
| +
|
| + this.scrollTarget = this.domHost ? this.domHost.$[scrollTarget] :
|
| + Polymer.dom(this.ownerDocument).querySelector('#' + scrollTarget);
|
| +
|
| + } else if (this._isValidScrollTarget()) {
|
| +
|
| + eventTarget = scrollTarget === this._doc ? window : scrollTarget;
|
| + this._boundScrollHandler = this._boundScrollHandler || this._scrollHandler.bind(this);
|
| + this._oldScrollTarget = scrollTarget;
|
| +
|
| + eventTarget.addEventListener('scroll', this._boundScrollHandler);
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Runs on every scroll event. Consumer of this behavior may override this method.
|
| + *
|
| + * @protected
|
| + */
|
| + _scrollHandler: function scrollHandler() {},
|
| +
|
| + /**
|
| + * The default scroll target. Consumers of this behavior may want to customize
|
| + * the default scroll target.
|
| + *
|
| + * @type {Element}
|
| + */
|
| + get _defaultScrollTarget() {
|
| + return this._doc;
|
| + },
|
| +
|
| + /**
|
| + * Shortcut for the document element
|
| + *
|
| + * @type {Element}
|
| + */
|
| + get _doc() {
|
| + return this.ownerDocument.documentElement;
|
| + },
|
| +
|
| + /**
|
| + * Gets the number of pixels that the content of an element is scrolled upward.
|
| + *
|
| + * @type {number}
|
| + */
|
| + get _scrollTop() {
|
| + if (this._isValidScrollTarget()) {
|
| + return this.scrollTarget === this._doc ? window.pageYOffset : this.scrollTarget.scrollTop;
|
| + }
|
| + return 0;
|
| + },
|
| +
|
| + /**
|
| + * Gets the number of pixels that the content of an element is scrolled to the left.
|
| + *
|
| + * @type {number}
|
| + */
|
| + get _scrollLeft() {
|
| + if (this._isValidScrollTarget()) {
|
| + return this.scrollTarget === this._doc ? window.pageXOffset : this.scrollTarget.scrollLeft;
|
| + }
|
| + return 0;
|
| + },
|
| +
|
| + /**
|
| + * Sets the number of pixels that the content of an element is scrolled upward.
|
| + *
|
| + * @type {number}
|
| + */
|
| + set _scrollTop(top) {
|
| + if (this.scrollTarget === this._doc) {
|
| + window.scrollTo(window.pageXOffset, top);
|
| + } else if (this._isValidScrollTarget()) {
|
| + this.scrollTarget.scrollTop = top;
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Sets the number of pixels that the content of an element is scrolled to the left.
|
| + *
|
| + * @type {number}
|
| + */
|
| + set _scrollLeft(left) {
|
| + if (this.scrollTarget === this._doc) {
|
| + window.scrollTo(left, window.pageYOffset);
|
| + } else if (this._isValidScrollTarget()) {
|
| + this.scrollTarget.scrollLeft = left;
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Scrolls the content to a particular place.
|
| + *
|
| + * @method scroll
|
| + * @param {number} left The left position
|
| + * @param {number} top The top position
|
| + */
|
| + scroll: function(left, top) {
|
| + if (this.scrollTarget === this._doc) {
|
| + window.scrollTo(left, top);
|
| + } else if (this._isValidScrollTarget()) {
|
| + this.scrollTarget.scrollLeft = left;
|
| + this.scrollTarget.scrollTop = top;
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Gets the width of the scroll target.
|
| + *
|
| + * @type {number}
|
| + */
|
| + get _scrollTargetWidth() {
|
| + if (this._isValidScrollTarget()) {
|
| + return this.scrollTarget === this._doc ? window.innerWidth : this.scrollTarget.offsetWidth;
|
| + }
|
| + return 0;
|
| + },
|
| +
|
| + /**
|
| + * Gets the height of the scroll target.
|
| + *
|
| + * @type {number}
|
| + */
|
| + get _scrollTargetHeight() {
|
| + if (this._isValidScrollTarget()) {
|
| + return this.scrollTarget === this._doc ? window.innerHeight : this.scrollTarget.offsetHeight;
|
| + }
|
| + return 0;
|
| + },
|
| +
|
| + /**
|
| + * Returns true if the scroll target is a valid HTMLElement.
|
| + *
|
| + * @return {boolean}
|
| + */
|
| + _isValidScrollTarget: function() {
|
| + return this.scrollTarget instanceof HTMLElement;
|
| + }
|
| + };
|
| +
|
| +</script>
|
| +<script>
|
| + /**
|
| + * `Polymer.AppScrollEffectsBehavior` provides an interface that allows an element to use scrolls effects.
|
| + *
|
| + * ### Importing the app-layout effects
|
| + *
|
| + * app-layout provides a set of scroll effects that can be used by explicitly importing
|
| + * `app-scroll-effects.html`:
|
| + *
|
| + * ```html
|
| + * <link rel="import" href="/bower_components/app-layout/app-scroll-effects/app-scroll-effects.html">
|
| + * ```
|
| + *
|
| + * The scroll effects can also be used by individually importing
|
| + * `app-layout/app-scroll-effects/effects/[effectName].html`. For example:
|
| + *
|
| + * ```html
|
| + * <link rel="import" href="/bower_components/app-layout/app-scroll-effects/effects/waterfall.html">
|
| + * ```
|
| + *
|
| + * ### Consuming effects
|
| + *
|
| + * Effects can be consumed via the `effects` property. For example:
|
| + *
|
| + * ```html
|
| + * <app-header effects="waterfall"></app-header>
|
| + * ```
|
| + *
|
| + * ### Creating scroll effects
|
| + *
|
| + * You may want to create a custom scroll effect if you need to modify the CSS of an element
|
| + * based on the scroll position.
|
| + *
|
| + * A scroll effect definition is an object with `setUp()`, `tearDown()` and `run()` functions.
|
| + *
|
| + * To register the effect, you can use `Polymer.AppLayout.registerEffect(effectName, effectDef)`
|
| + * For example, let's define an effect that resizes the header's logo:
|
| + *
|
| + * ```js
|
| + * Polymer.AppLayout.registerEffect('resizable-logo', {
|
| + * setUp: function(config) {
|
| + * // the effect's config is passed to the setUp.
|
| + * this._fxResizeLogo = { logo: Polymer.dom(this).querySelector('[logo]') };
|
| + * },
|
| + *
|
| + * run: function(progress) {
|
| + * // the progress of the effect
|
| + * this.transform('scale3d(' + progress + ', '+ progress +', 1)', this._fxResizeLogo.logo);
|
| + * },
|
| + *
|
| + * tearDown: function() {
|
| + * // clean up and reset of states
|
| + * delete this._fxResizeLogo;
|
| + * }
|
| + * });
|
| + * ```
|
| + * Now, you can consume the effect:
|
| + *
|
| + * ```html
|
| + * <app-header id="appHeader" effects="resizable-logo">
|
| + * <img logo src="logo.svg">
|
| + * </app-header>
|
| + * ```
|
| + *
|
| + * ### Imperative API
|
| + *
|
| + * ```js
|
| + * var logoEffect = appHeader.createEffect('resizable-logo', effectConfig);
|
| + * // run the effect: logoEffect.run(progress);
|
| + * // tear down the effect: logoEffect.tearDown();
|
| + * ```
|
| + *
|
| + * ### Configuring effects
|
| + *
|
| + * For effects installed via the `effects` property, their configuration can be set
|
| + * via the `effectsConfig` property. For example:
|
| + *
|
| + * ```html
|
| + * <app-header effects="waterfall"
|
| + * effects-config='{"waterfall": {"startsAt": 0, "endsAt": 0.5}}'>
|
| + * </app-header>
|
| + * ```
|
| + *
|
| + * All effects have a `startsAt` and `endsAt` config property. They specify at what
|
| + * point the effect should start and end. This value goes from 0 to 1 inclusive.
|
| + *
|
| + * @polymerBehavior
|
| + */
|
| + Polymer.AppScrollEffectsBehavior = [
|
| + Polymer.IronScrollTargetBehavior,
|
| + {
|
| +
|
| + properties: {
|
| +
|
| + /**
|
| + * A space-separated list of the effects names that will be triggered when the user scrolls.
|
| + * e.g. `waterfall parallax-background` installs the `waterfall` and `parallax-background`.
|
| + */
|
| + effects: {
|
| + type: String
|
| + },
|
| +
|
| + /**
|
| + * An object that configurates the effects installed via the `effects` property. e.g.
|
| + * ```js
|
| + * element.effectsConfig = {
|
| + * "blend-background": {
|
| + * "startsAt": 0.5
|
| + * }
|
| + * };
|
| + * ```
|
| + * Every effect has at least two config properties: `startsAt` and `endsAt`.
|
| + * These properties indicate when the event should start and end respectively
|
| + * and relative to the overall element progress. So for example, if `blend-background`
|
| + * starts at `0.5`, the effect will only start once the current element reaches 0.5
|
| + * of its progress. In this context, the progress is a value in the range of `[0, 1]`
|
| + * that indicates where this element is on the screen relative to the viewport.
|
| + */
|
| + effectsConfig: {
|
| + type: Object,
|
| + value: function() {
|
| + return {};
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Disables CSS transitions and scroll effects on the element.
|
| + */
|
| + disabled: {
|
| + type: Boolean,
|
| + reflectToAttribute: true,
|
| + value: false
|
| + }
|
| + },
|
| +
|
| + observers: [
|
| + '_effectsChanged(effects, effectsConfig, isAttached)'
|
| + ],
|
| +
|
| + /**
|
| + * Updates the scroll state. This method should be overridden
|
| + * by the consumer of this behavior.
|
| + *
|
| + * @method _updateScrollState
|
| + */
|
| + _updateScrollState: function() {},
|
| +
|
| + /**
|
| + * Returns true if the current element is on the screen.
|
| + * That is, visible in the current viewport. This method should be
|
| + * overridden by the consumer of this behavior.
|
| + *
|
| + * @method isOnScreen
|
| + * @return {boolean}
|
| + */
|
| + isOnScreen: function() {
|
| + return false;
|
| + },
|
| +
|
| + /**
|
| + * Returns true if there's content below the current element. This method
|
| + * should be overridden by the consumer of this behavior.
|
| + *
|
| + * @method isContentBelow
|
| + * @return {boolean}
|
| + */
|
| + isContentBelow: function() {
|
| + return false;
|
| + },
|
| +
|
| + /**
|
| + * List of effects handlers that will take place during scroll.
|
| + *
|
| + * @type {Array<Function>}
|
| + */
|
| + _effectsRunFn: null,
|
| +
|
| + /**
|
| + * List of the effects definitions installed via the `effects` property.
|
| + *
|
| + * @type {Array<Object>}
|
| + */
|
| + _effects: null,
|
| +
|
| + /**
|
| + * The clamped value of `_scrollTop`.
|
| + * @type number
|
| + */
|
| + get _clampedScrollTop() {
|
| + return Math.max(0, this._scrollTop);
|
| + },
|
| +
|
| + detached: function() {
|
| + this._tearDownEffects();
|
| + },
|
| +
|
| + /**
|
| + * Creates an effect object from an effect's name that can be used to run
|
| + * effects programmatically.
|
| + *
|
| + * @method createEffect
|
| + * @param {string} effectName The effect's name registered via `Polymer.AppLayout.registerEffect`.
|
| + * @param {Object=} effectConfig The effect config object. (Optional)
|
| + * @return {Object} An effect object with the following functions:
|
| + *
|
| + * * `effect.setUp()`, Sets up the requirements for the effect.
|
| + * This function is called automatically before the `effect` function returns.
|
| + * * `effect.run(progress, y)`, Runs the effect given a `progress`.
|
| + * * `effect.tearDown()`, Cleans up any DOM nodes or element references used by the effect.
|
| + *
|
| + * Example:
|
| + * ```js
|
| + * var parallax = element.createEffect('parallax-background');
|
| + * // runs the effect
|
| + * parallax.run(0.5, 0);
|
| + * ```
|
| + */
|
| + createEffect: function(effectName, effectConfig) {
|
| + var effectDef = Polymer.AppLayout._scrollEffects[effectName];
|
| + if (!effectDef) {
|
| + throw new ReferenceError(this._getUndefinedMsg(effectName));
|
| + }
|
| + var prop = this._boundEffect(effectDef, effectConfig || {});
|
| + prop.setUp();
|
| + return prop;
|
| + },
|
| +
|
| + /**
|
| + * Called when `effects` or `effectsConfig` changes.
|
| + */
|
| + _effectsChanged: function(effects, effectsConfig, isAttached) {
|
| + this._tearDownEffects();
|
| +
|
| + if (effects === '' || !isAttached) {
|
| + return;
|
| + }
|
| + effects.split(' ').forEach(function(effectName) {
|
| + var effectDef;
|
| + if (effectName !== '') {
|
| + if ((effectDef = Polymer.AppLayout._scrollEffects[effectName])) {
|
| + this._effects.push(this._boundEffect(effectDef, effectsConfig[effectName]));
|
| + } else {
|
| + this._warn(this._logf('_effectsChanged', this._getUndefinedMsg(effectName)));
|
| + }
|
| + }
|
| + }, this);
|
| +
|
| + this._setUpEffect();
|
| + },
|
| +
|
| + /**
|
| + * Forces layout
|
| + */
|
| + _layoutIfDirty: function() {
|
| + return this.offsetWidth;
|
| + },
|
| +
|
| + /**
|
| + * Returns an effect object bound to the current context.
|
| + *
|
| + * @param {Object} effectDef
|
| + * @param {Object=} effectsConfig The effect config object if the effect accepts config values. (Optional)
|
| + */
|
| + _boundEffect: function(effectDef, effectsConfig) {
|
| + effectsConfig = effectsConfig || {};
|
| + var startsAt = parseFloat(effectsConfig.startsAt || 0);
|
| + var endsAt = parseFloat(effectsConfig.endsAt || 1);
|
| + var deltaS = endsAt - startsAt;
|
| + var noop = Function();
|
| + // fast path if possible
|
| + var runFn = (startsAt === 0 && endsAt === 1) ? effectDef.run :
|
| + function(progress, y) {
|
| + effectDef.run.call(this,
|
| + Math.max(0, (progress - startsAt) / deltaS), y);
|
| + };
|
| + return {
|
| + setUp: effectDef.setUp ? effectDef.setUp.bind(this, effectsConfig) : noop,
|
| + run: effectDef.run ? runFn.bind(this) : noop,
|
| + tearDown: effectDef.tearDown ? effectDef.tearDown.bind(this) : noop
|
| + };
|
| + },
|
| +
|
| + /**
|
| + * Sets up the effects.
|
| + */
|
| + _setUpEffect: function() {
|
| + if (this.isAttached && this._effects) {
|
| + this._effectsRunFn = [];
|
| + this._effects.forEach(function(effectDef) {
|
| + // install the effect only if no error was reported
|
| + if (effectDef.setUp() !== false) {
|
| + this._effectsRunFn.push(effectDef.run);
|
| + }
|
| + }, this);
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Tears down the effects.
|
| + */
|
| + _tearDownEffects: function() {
|
| + if (this._effects) {
|
| + this._effects.forEach(function(effectDef) {
|
| + effectDef.tearDown();
|
| + });
|
| + }
|
| + this._effectsRunFn = [];
|
| + this._effects = [];
|
| + },
|
| +
|
| + /**
|
| + * Runs the effects.
|
| + *
|
| + * @param {number} p The progress
|
| + * @param {number} y The top position of the current element relative to the viewport.
|
| + */
|
| + _runEffects: function(p, y) {
|
| + if (this._effectsRunFn) {
|
| + this._effectsRunFn.forEach(function(run) {
|
| + run(p, y);
|
| + });
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Overrides the `_scrollHandler`.
|
| + */
|
| + _scrollHandler: function() {
|
| + if (!this.disabled) {
|
| + this._updateScrollState(this._clampedScrollTop);
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Override this method to return a reference to a node in the local DOM.
|
| + * The node is consumed by a scroll effect.
|
| + *
|
| + * @param {string} id The id for the node.
|
| + */
|
| + _getDOMRef: function(id) {
|
| + this._warn(this._logf('_getDOMRef', '`'+ id +'` is undefined'));
|
| + },
|
| +
|
| + _getUndefinedMsg: function(effectName) {
|
| + return 'Scroll effect `' + effectName + '` is undefined. ' +
|
| + 'Did you forget to import app-layout/app-scroll-effects/effects/' + effectName + '.html ?';
|
| + }
|
| +
|
| + }];
|
| +
|
| +</script>
|
| +
|
| +
|
| +<dom-module id="app-header" assetpath="bower_components/app-layout/app-header/">
|
| + <template>
|
| + <style>
|
| + :host {
|
| + position: relative;
|
| + display: block;
|
| + transition-timing-function: linear;
|
| + transition-property: -webkit-transform;
|
| + transition-property: transform;
|
| + }
|
| +
|
| + :host::after {
|
| + position: absolute;
|
| + right: 0px;
|
| + bottom: -5px;
|
| + left: 0px;
|
| + width: 100%;
|
| + height: 5px;
|
| + content: "";
|
| + transition: opacity 0.4s;
|
| + pointer-events: none;
|
| + opacity: 0;
|
| + box-shadow: inset 0px 5px 6px -3px rgba(0, 0, 0, 0.4);
|
| + will-change: opacity;
|
| + @apply(--app-header-shadow);
|
| + }
|
| +
|
| + :host([shadow])::after {
|
| + opacity: 1;
|
| + }
|
| +
|
| + #contentContainer > ::content [condensed-title] {
|
| + -webkit-transform-origin: left top;
|
| + transform-origin: left top;
|
| + white-space: nowrap;
|
| + opacity: 0;
|
| + }
|
| +
|
| + #contentContainer > ::content [main-title] {
|
| + -webkit-transform-origin: left top;
|
| + transform-origin: left top;
|
| + white-space: nowrap;
|
| + }
|
| +
|
| + #background {
|
| + @apply(--layout-fit);
|
| + overflow: hidden;
|
| + }
|
| +
|
| + #backgroundFrontLayer,
|
| + #backgroundRearLayer {
|
| + @apply(--layout-fit);
|
| + height: 100%;
|
| + pointer-events: none;
|
| + background-size: cover;
|
| + }
|
| +
|
| + #backgroundFrontLayer {
|
| + @apply(--app-header-background-front-layer);
|
| + }
|
| +
|
| + #backgroundRearLayer {
|
| + opacity: 0;
|
| + @apply(--app-header-background-rear-layer);
|
| + }
|
| +
|
| + #contentContainer {
|
| + position: relative;
|
| + width: 100%;
|
| + height: 100%;
|
| + }
|
| +
|
| + :host([disabled]),
|
| + :host([disabled])::after,
|
| + :host([disabled]) #backgroundFrontLayer,
|
| + :host([disabled]) #backgroundRearLayer,
|
| + :host([disabled]) ::content > app-toolbar:first-of-type,
|
| + :host([disabled]) ::content > [sticky],
|
| + /* Silent scrolling should not run CSS transitions */
|
| + :host-context(.app-layout-silent-scroll),
|
| + :host-context(.app-layout-silent-scroll)::after,
|
| + :host-context(.app-layout-silent-scroll) #backgroundFrontLayer,
|
| + :host-context(.app-layout-silent-scroll) #backgroundRearLayer,
|
| + :host-context(.app-layout-silent-scroll) ::content > app-toolbar:first-of-type,
|
| + :host-context(.app-layout-silent-scroll) ::content > [sticky] {
|
| + transition: none !important;
|
| + }
|
| + </style>
|
| + <div id="contentContainer">
|
| + <content id="content"></content>
|
| + </div>
|
| + </template>
|
| +
|
| + <script>
|
| + Polymer({
|
| + is: 'app-header',
|
| +
|
| + behaviors: [
|
| + Polymer.AppScrollEffectsBehavior,
|
| + Polymer.IronResizableBehavior
|
| + ],
|
| +
|
| + properties: {
|
| + /**
|
| + * If true, the header will automatically collapse when scrolling down.
|
| + * That is, the `sticky` element remains visible when the header is fully condensed
|
| + * whereas the rest of the elements will collapse below `sticky` element.
|
| + *
|
| + * By default, the `sticky` element is the first toolbar in the light DOM:
|
| + *
|
| + *```html
|
| + * <app-header condenses>
|
| + * <app-toolbar>This toolbar remains on top</app-toolbar>
|
| + * <app-toolbar></app-toolbar>
|
| + * <app-toolbar></app-toolbar>
|
| + * </app-header>
|
| + * ```
|
| + *
|
| + * Additionally, you can specify which toolbar or element remains visible in condensed mode
|
| + * by adding the `sticky` attribute to that element. For example: if we want the last
|
| + * toolbar to remain visible, we can add the `sticky` attribute to it.
|
| + *
|
| + *```html
|
| + * <app-header condenses>
|
| + * <app-toolbar></app-toolbar>
|
| + * <app-toolbar></app-toolbar>
|
| + * <app-toolbar sticky>This toolbar remains on top</app-toolbar>
|
| + * </app-header>
|
| + * ```
|
| + *
|
| + * Note the `sticky` element must be a direct child of `app-header`.
|
| + */
|
| + condenses: {
|
| + type: Boolean,
|
| + value: false
|
| + },
|
| +
|
| + /**
|
| + * Mantains the header fixed at the top so it never moves away.
|
| + */
|
| + fixed: {
|
| + type: Boolean,
|
| + value: false
|
| + },
|
| +
|
| + /**
|
| + * Slides back the header when scrolling back up.
|
| + */
|
| + reveals: {
|
| + type: Boolean,
|
| + value: false
|
| + },
|
| +
|
| + /**
|
| + * Displays a shadow below the header.
|
| + */
|
| + shadow: {
|
| + type: Boolean,
|
| + reflectToAttribute: true,
|
| + value: false
|
| + }
|
| + },
|
| +
|
| + observers: [
|
| + 'resetLayout(isAttached, condenses, fixed)'
|
| + ],
|
| +
|
| + listeners: {
|
| + 'iron-resize': '_resizeHandler'
|
| + },
|
| +
|
| + /**
|
| + * A cached offsetHeight of the current element.
|
| + *
|
| + * @type {number}
|
| + */
|
| + _height: 0,
|
| +
|
| + /**
|
| + * The distance in pixels the header will be translated to when scrolling.
|
| + *
|
| + * @type {number}
|
| + */
|
| + _dHeight: 0,
|
| +
|
| + /**
|
| + * The offsetTop of `_stickyEl`
|
| + *
|
| + * @type {number}
|
| + */
|
| + _stickyElTop: 0,
|
| +
|
| + /**
|
| + * The element that remains visible when the header condenses.
|
| + *
|
| + * @type {HTMLElement}
|
| + */
|
| + _stickyEl: null,
|
| +
|
| + /**
|
| + * The header's top value used for the `transformY`
|
| + *
|
| + * @type {number}
|
| + */
|
| + _top: 0,
|
| +
|
| + /**
|
| + * The current scroll progress.
|
| + *
|
| + * @type {number}
|
| + */
|
| + _progress: 0,
|
| +
|
| + _wasScrollingDown: false,
|
| + _initScrollTop: 0,
|
| + _initTimestamp: 0,
|
| + _lastTimestamp: 0,
|
| + _lastScrollTop: 0,
|
| +
|
| + /**
|
| + * The distance the header is allowed to move away.
|
| + *
|
| + * @type {number}
|
| + */
|
| + get _maxHeaderTop() {
|
| + return this.fixed ? this._dHeight : this._height + 5;
|
| + },
|
| +
|
| + /**
|
| + * Returns a reference to the sticky element.
|
| + *
|
| + * @return {HTMLElement}?
|
| + */
|
| + _getStickyEl: function() {
|
| + /** @type {HTMLElement} */
|
| + var stickyEl;
|
| + var nodes = Polymer.dom(this.$.content).getDistributedNodes();
|
| +
|
| + for (var i = 0; i < nodes.length; i++) {
|
| + if (nodes[i].nodeType === Node.ELEMENT_NODE) {
|
| + var node = /** @type {HTMLElement} */ (nodes[i]);
|
| + if (node.hasAttribute('sticky')) {
|
| + stickyEl = node;
|
| + break;
|
| + } else if (!stickyEl) {
|
| + stickyEl = node;
|
| + }
|
| + }
|
| + }
|
| + return stickyEl;
|
| + },
|
| +
|
| + /**
|
| + * Resets the layout. If you changed the size of app-header via CSS
|
| + * you can notify the changes by either firing the `iron-resize` event
|
| + * or calling `resetLayout` directly.
|
| + *
|
| + * @method resetLayout
|
| + */
|
| + resetLayout: function() {
|
| + this.fire('app-header-reset-layout');
|
| +
|
| + this.debounce('_resetLayout', function() {
|
| + // noop if the header isn't visible
|
| + if (this.offsetWidth === 0 && this.offsetHeight === 0) {
|
| + return;
|
| + }
|
| +
|
| + var scrollTop = this._clampedScrollTop;
|
| + var firstSetup = this._height === 0 || scrollTop === 0;
|
| + var currentDisabled = this.disabled;
|
| +
|
| + this._height = this.offsetHeight;
|
| + this._stickyEl = this._getStickyEl();
|
| + this.disabled = true;
|
| +
|
| + // prepare for measurement
|
| + if (!firstSetup) {
|
| + this._updateScrollState(0, true);
|
| + }
|
| +
|
| + if (this._mayMove()) {
|
| + this._dHeight = this._stickyEl ? this._height - this._stickyEl.offsetHeight : 0;
|
| + } else {
|
| + this._dHeight = 0;
|
| + }
|
| +
|
| + this._stickyElTop = this._stickyEl ? this._stickyEl.offsetTop : 0;
|
| + this._setUpEffect();
|
| +
|
| + if (firstSetup) {
|
| + this._updateScrollState(scrollTop, true);
|
| + } else {
|
| + this._updateScrollState(this._lastScrollTop, true);
|
| + this._layoutIfDirty();
|
| + }
|
| + // restore no transition
|
| + this.disabled = currentDisabled;
|
| + });
|
| + },
|
| +
|
| + /**
|
| + * Updates the scroll state.
|
| + *
|
| + * @param {number} scrollTop
|
| + * @param {boolean=} forceUpdate (default: false)
|
| + */
|
| + _updateScrollState: function(scrollTop, forceUpdate) {
|
| + if (this._height === 0) {
|
| + return;
|
| + }
|
| +
|
| + var progress = 0;
|
| + var top = 0;
|
| + var lastTop = this._top;
|
| + var lastScrollTop = this._lastScrollTop;
|
| + var maxHeaderTop = this._maxHeaderTop;
|
| + var dScrollTop = scrollTop - this._lastScrollTop;
|
| + var absDScrollTop = Math.abs(dScrollTop);
|
| + var isScrollingDown = scrollTop > this._lastScrollTop;
|
| + var now = Date.now();
|
| +
|
| + if (this._mayMove()) {
|
| + top = this._clamp(this.reveals ? lastTop + dScrollTop : scrollTop, 0, maxHeaderTop);
|
| + }
|
| +
|
| + if (scrollTop >= this._dHeight) {
|
| + top = this.condenses && !this.fixed ? Math.max(this._dHeight, top) : top;
|
| + this.style.transitionDuration = '0ms';
|
| + }
|
| +
|
| + if (this.reveals && !this.disabled && absDScrollTop < 100) {
|
| + // set the initial scroll position
|
| + if (now - this._initTimestamp > 300 || this._wasScrollingDown !== isScrollingDown) {
|
| + this._initScrollTop = scrollTop;
|
| + this._initTimestamp = now;
|
| + }
|
| +
|
| + if (scrollTop >= maxHeaderTop) {
|
| + // check if the header is allowed to snap
|
| + if (Math.abs(this._initScrollTop - scrollTop) > 30 || absDScrollTop > 10) {
|
| + if (isScrollingDown && scrollTop >= maxHeaderTop) {
|
| + top = maxHeaderTop;
|
| + } else if (!isScrollingDown && scrollTop >= this._dHeight) {
|
| + top = this.condenses && !this.fixed ? this._dHeight : 0;
|
| + }
|
| + var scrollVelocity = dScrollTop / (now - this._lastTimestamp);
|
| + this.style.transitionDuration = this._clamp((top - lastTop) / scrollVelocity, 0, 300) + 'ms';
|
| + } else {
|
| + top = this._top;
|
| + }
|
| + }
|
| + }
|
| +
|
| + if (this._dHeight === 0) {
|
| + progress = scrollTop > 0 ? 1 : 0;
|
| + } else {
|
| + progress = top / this._dHeight;
|
| + }
|
| +
|
| + if (!forceUpdate) {
|
| + this._lastScrollTop = scrollTop;
|
| + this._top = top;
|
| + this._wasScrollingDown = isScrollingDown;
|
| + this._lastTimestamp = now;
|
| + }
|
| +
|
| + if (forceUpdate || progress !== this._progress || lastTop !== top || scrollTop === 0) {
|
| + this._progress = progress;
|
| + this._runEffects(progress, top);
|
| + this._transformHeader(top);
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Returns true if the current header is allowed to move as the user scrolls.
|
| + *
|
| + * @return {boolean}
|
| + */
|
| + _mayMove: function() {
|
| + return this.condenses || !this.fixed;
|
| + },
|
| +
|
| + /**
|
| + * Returns true if the current header will condense based on the size of the header
|
| + * and the `consenses` property.
|
| + *
|
| + * @return {boolean}
|
| + */
|
| + willCondense: function() {
|
| + return this._dHeight > 0 && this.condenses;
|
| + },
|
| +
|
| + /**
|
| + * Returns true if the current element is on the screen.
|
| + * That is, visible in the current viewport.
|
| + *
|
| + * @method isOnScreen
|
| + * @return {boolean}
|
| + */
|
| + isOnScreen: function() {
|
| + return this._height !== 0 && this._top < this._height;
|
| + },
|
| +
|
| + /**
|
| + * Returns true if there's content below the current element.
|
| + *
|
| + * @method isContentBelow
|
| + * @return {boolean}
|
| + */
|
| + isContentBelow: function() {
|
| + if (this._top === 0) {
|
| + return this._clampedScrollTop > 0;
|
| + }
|
| + return this._clampedScrollTop - this._maxHeaderTop >= 0;
|
| + },
|
| +
|
| + /**
|
| + * Transforms the header.
|
| + *
|
| + * @param {number} y
|
| + */
|
| + _transformHeader: function(y) {
|
| + this.translate3d(0, (-y) + 'px', 0);
|
| + if (this._stickyEl && this.condenses && y >= this._stickyElTop) {
|
| + this.translate3d(0, (Math.min(y, this._dHeight) - this._stickyElTop) + 'px', 0,
|
| + this._stickyEl);
|
| + }
|
| + },
|
| +
|
| + _resizeHandler: function() {
|
| + this.resetLayout();
|
| + },
|
| +
|
| + _clamp: function(v, min, max) {
|
| + return Math.min(max, Math.max(min, v));
|
| + },
|
| +
|
| + _ensureBgContainers: function() {
|
| + if (!this._bgContainer) {
|
| + this._bgContainer = document.createElement('div');
|
| + this._bgContainer.id = 'background';
|
| +
|
| + this._bgRear = document.createElement('div');
|
| + this._bgRear.id = 'backgroundRearLayer';
|
| + this._bgContainer.appendChild(this._bgRear);
|
| +
|
| + this._bgFront = document.createElement('div');
|
| + this._bgFront.id = 'backgroundFrontLayer';
|
| + this._bgContainer.appendChild(this._bgFront);
|
| +
|
| + Polymer.dom(this.root).insertBefore(this._bgContainer, this.$.contentContainer);
|
| + }
|
| + },
|
| +
|
| + _getDOMRef: function(id) {
|
| + switch (id) {
|
| + case 'backgroundFrontLayer':
|
| + this._ensureBgContainers();
|
| + return this._bgFront;
|
| + case 'backgroundRearLayer':
|
| + this._ensureBgContainers();
|
| + return this._bgRear;
|
| + case 'background':
|
| + this._ensureBgContainers();
|
| + return this._bgContainer;
|
| + case 'mainTitle':
|
| + return Polymer.dom(this).querySelector('[main-title]');
|
| + case 'condensedTitle':
|
| + return Polymer.dom(this).querySelector('[condensed-title]');
|
| + }
|
| + return null;
|
| + },
|
| +
|
| + /**
|
| + * Returns an object containing the progress value of the scroll effects
|
| + * and the top position of the header.
|
| + *
|
| + * @method getScrollState
|
| + * @return {Object}
|
| + */
|
| + getScrollState: function() {
|
| + return { progress: this._progress, top: this._top };
|
| + }
|
| +
|
| + /**
|
| + * Fires when the layout of `app-header` changed.
|
| + *
|
| + * @event app-header-reset-layout
|
| + */
|
| + });
|
| + </script>
|
| +</dom-module>
|
| +
|
| +
|
| +<dom-module id="app-header-layout" assetpath="bower_components/app-layout/app-header-layout/">
|
| + <template>
|
| + <style>
|
| + :host {
|
| + display: block;
|
| + /**
|
| + * Force app-header-layout to have its own stacking context so that its parent can
|
| + * control the stacking of it relative to other elements (e.g. app-drawer-layout).
|
| + * This could be done using `isolation: isolate`, but that's not well supported
|
| + * across browsers.
|
| + */
|
| + position: relative;
|
| + z-index: 0;
|
| + }
|
| +
|
| + :host > ::content > app-header {
|
| + @apply(--layout-fixed-top);
|
| + z-index: 1;
|
| + }
|
| +
|
| + :host([has-scrolling-region]) {
|
| + height: 100%;
|
| + }
|
| +
|
| + :host([has-scrolling-region]) > ::content > app-header {
|
| + position: absolute;
|
| + }
|
| +
|
| + :host([has-scrolling-region]) > #contentContainer {
|
| + @apply(--layout-fit);
|
| + overflow-y: auto;
|
| + -webkit-overflow-scrolling: touch;
|
| + }
|
| +
|
| + :host([fullbleed]) {
|
| + @apply(--layout-vertical);
|
| + @apply(--layout-fit);
|
| + }
|
| +
|
| + :host([fullbleed]) > #contentContainer {
|
| + @apply(--layout-vertical);
|
| + @apply(--layout-flex);
|
| + }
|
| +
|
| + #contentContainer {
|
| + /* Create a stacking context here so that all children appear below the header. */
|
| + position: relative;
|
| + z-index: 0;
|
| + }
|
| +
|
| + </style>
|
| +
|
| + <content id="header" select="app-header"></content>
|
| +
|
| + <div id="contentContainer">
|
| + <content></content>
|
| + </div>
|
| +
|
| + </template>
|
| +
|
| + <script>
|
| + Polymer({
|
| + is: 'app-header-layout',
|
| +
|
| + behaviors: [
|
| + Polymer.IronResizableBehavior
|
| + ],
|
| +
|
| + properties: {
|
| + /**
|
| + * If true, the current element will have its own scrolling region.
|
| + * Otherwise, it will use the document scroll to control the header.
|
| + */
|
| + hasScrollingRegion: {
|
| + type: Boolean,
|
| + value: false,
|
| + reflectToAttribute: true
|
| + }
|
| + },
|
| +
|
| + listeners: {
|
| + 'iron-resize': '_resizeHandler',
|
| + 'app-header-reset-layout': 'resetLayout'
|
| + },
|
| +
|
| + observers: [
|
| + 'resetLayout(isAttached, hasScrollingRegion)'
|
| + ],
|
| +
|
| + /**
|
| + * A reference to the app-header element.
|
| + *
|
| + * @property header
|
| + */
|
| + get header() {
|
| + return Polymer.dom(this.$.header).getDistributedNodes()[0];
|
| + },
|
| +
|
| + /**
|
| + * Resets the layout. This method is automatically called when the element is attached
|
| + * to the DOM.
|
| + *
|
| + * @method resetLayout
|
| + */
|
| + resetLayout: function() {
|
| + this._updateScroller();
|
| + this.debounce('_resetLayout', this._updateContentPosition);
|
| + },
|
| +
|
| + _updateContentPosition: function() {
|
| + var header = this.header;
|
| + if (!this.isAttached || !header) {
|
| + return;
|
| + }
|
| + // Get header height here so that style reads are batched together before style writes
|
| + // (i.e. getBoundingClientRect() below).
|
| + var headerHeight = header.offsetHeight;
|
| + // Update the header position.
|
| + if (!this.hasScrollingRegion) {
|
| + var rect = this.getBoundingClientRect();
|
| + var rightOffset = document.documentElement.clientWidth - rect.right;
|
| + header.style.left = rect.left + 'px';
|
| + header.style.right = rightOffset + 'px';
|
| + } else {
|
| + header.style.left = '';
|
| + header.style.right = '';
|
| + }
|
| + // Update the content container position.
|
| + var containerStyle = this.$.contentContainer.style;
|
| + if (header.fixed && !header.willCondense() && this.hasScrollingRegion) {
|
| + // If the header size does not change and we're using a scrolling region, exclude
|
| + // the header area from the scrolling region so that the header doesn't overlap
|
| + // the scrollbar.
|
| + containerStyle.marginTop = headerHeight + 'px';
|
| + containerStyle.paddingTop = '';
|
| + } else {
|
| + containerStyle.paddingTop = headerHeight + 'px';
|
| + containerStyle.marginTop = '';
|
| + }
|
| + },
|
| +
|
| + _updateScroller: function() {
|
| + if (!this.isAttached) {
|
| + return;
|
| + }
|
| + var header = this.header;
|
| + if (header) {
|
| + header.scrollTarget = this.hasScrollingRegion ?
|
| + this.$.contentContainer : this.ownerDocument.documentElement;
|
| + }
|
| + },
|
| +
|
| + _resizeHandler: function() {
|
| + this.resetLayout();
|
| + }
|
| + });
|
| + </script>
|
| +</dom-module>
|
| +
|
| +
|
| +<dom-module id="app-scrollpos-control" assetpath="bower_components/app-layout/app-scrollpos-control/">
|
| + <script>
|
| + Polymer({
|
| + is: 'app-scrollpos-control',
|
| +
|
| + behaviors: [
|
| + Polymer.IronScrollTargetBehavior
|
| + ],
|
| +
|
| + properties: {
|
| + /**
|
| + * The selected page.
|
| + */
|
| + selected: {
|
| + type: String,
|
| + observer: '_selectedChanged'
|
| + },
|
| +
|
| + /**
|
| + * Reset the scroll position to 0.
|
| + */
|
| + reset: {
|
| + type: Boolean,
|
| + value: false
|
| + }
|
| + },
|
| +
|
| + observers: [
|
| + '_updateScrollPos(selected, reset)'
|
| + ],
|
| +
|
| + created: function() {
|
| + this._scrollposMap = {};
|
| + },
|
| +
|
| + _selectedChanged: function(selected, old) {
|
| + if (old != null) {
|
| + this._scrollposMap[old] = {x: this._scrollLeft, y: this._scrollTop};
|
| + }
|
| + },
|
| +
|
| + _updateScrollPos: function(selected, reset) {
|
| + this.debounce('_updateScrollPos', function() {
|
| + var pos = this._scrollposMap[this.selected];
|
| + if (pos != null && !this.reset) {
|
| + this.scroll(pos.x, pos.y);
|
| + } else {
|
| + this.scroll(0, 0);
|
| + }
|
| + });
|
| + }
|
| + });
|
| + </script>
|
| +</dom-module>
|
| +
|
| +
|
| +<dom-module id="app-toolbar" assetpath="bower_components/app-layout/app-toolbar/">
|
| + <template>
|
| + <style>
|
| + :host {
|
| + position: relative;
|
| +
|
| + @apply(--layout-horizontal);
|
| + @apply(--layout-center);
|
| +
|
| + height: 64px;
|
| + padding: 0 16px;
|
| +
|
| + pointer-events: none;
|
| +
|
| + font-size: var(--app-toolbar-font-size, 20px);
|
| + }
|
| +
|
| + :host > ::content > * {
|
| + pointer-events: auto;
|
| + }
|
| +
|
| + :host > ::content > paper-icon-button {
|
| + /* paper-icon-button/issues/33 */
|
| + font-size: 0;
|
| + }
|
| +
|
| + :host > ::content > [main-title],
|
| + :host > ::content > [condensed-title] {
|
| + pointer-events: none;
|
| + @apply(--layout-flex);
|
| + }
|
| +
|
| + :host > ::content > [bottom-item] {
|
| + position: absolute;
|
| + right: 0;
|
| + bottom: 0;
|
| + left: 0;
|
| + }
|
| +
|
| + :host > ::content > [top-item] {
|
| + position: absolute;
|
| + top: 0;
|
| + right: 0;
|
| + left: 0;
|
| + }
|
| +
|
| + :host > ::content > [spacer] {
|
| + margin-left: 64px;
|
| + }
|
| + </style>
|
| +
|
| + <content></content>
|
| + </template>
|
| +
|
| + <script>
|
| + Polymer({
|
| + is: 'app-toolbar'
|
| + });
|
| + </script>
|
| +</dom-module>
|
| +
|
| +
|
| +<dom-module id="app-box" assetpath="bower_components/app-layout/app-box/">
|
| + <template>
|
| + <style>
|
| + :host {
|
| + position: relative;
|
| +
|
| + display: block;
|
| + }
|
| +
|
| + #background {
|
| + @apply(--layout-fit);
|
| +
|
| + overflow: hidden;
|
| +
|
| + height: 100%;
|
| + }
|
| +
|
| + #backgroundFrontLayer {
|
| + min-height: 100%;
|
| +
|
| + pointer-events: none;
|
| +
|
| + background-size: cover;
|
| +
|
| + @apply(--app-box-background-front-layer);
|
| + }
|
| +
|
| + #contentContainer {
|
| + position: relative;
|
| +
|
| + width: 100%;
|
| + height: 100%;
|
| + }
|
| +
|
| + :host([disabled]),
|
| + :host([disabled]) #backgroundFrontLayer {
|
| + transition: none !important;
|
| + }
|
| + </style>
|
| +
|
| + <div id="background">
|
| + <div id="backgroundFrontLayer">
|
| + <content select="[background]"></content>
|
| + </div>
|
| + </div>
|
| + <div id="contentContainer">
|
| + <content id="content"></content>
|
| + </div>
|
| + </template>
|
| +
|
| + <script>
|
| + Polymer({
|
| + is: 'app-box',
|
| +
|
| + behaviors: [
|
| + Polymer.AppScrollEffectsBehavior,
|
| + Polymer.IronResizableBehavior
|
| + ],
|
| +
|
| + listeners: {
|
| + 'iron-resize': '_resizeHandler'
|
| + },
|
| +
|
| + /**
|
| + * The current scroll progress.
|
| + *
|
| + * @type {number}
|
| + */
|
| + _progress: 0,
|
| +
|
| + attached: function() {
|
| + this.resetLayout();
|
| + },
|
| +
|
| + /**
|
| + * Resets the layout. This method is automatically called when the element is attached to the DOM.
|
| + *
|
| + * @method resetLayout
|
| + */
|
| + resetLayout: function() {
|
| + this.debounce('_resetLayout', function() {
|
| + // noop if the box isn't in the rendered tree
|
| + if (this.offsetWidth === 0 && this.offsetHeight === 0) {
|
| + return;
|
| + }
|
| +
|
| + var scrollTop = this._clampedScrollTop;
|
| + var savedDisabled = this.disabled;
|
| +
|
| + this.disabled = true;
|
| + this._elementTop = this._getElementTop();
|
| + this._elementHeight = this.offsetHeight;
|
| + this._cachedScrollTargetHeight = this._scrollTargetHeight;
|
| + this._setUpEffect();
|
| + this._updateScrollState(scrollTop);
|
| + this.disabled = savedDisabled;
|
| + }, 1);
|
| + },
|
| +
|
| + _getElementTop: function() {
|
| + var currentNode = this;
|
| + var top = 0;
|
| +
|
| + while (currentNode && currentNode !== this.scrollTarget) {
|
| + top += currentNode.offsetTop;
|
| + currentNode = currentNode.offsetParent;
|
| + }
|
| + return top;
|
| + },
|
| +
|
| + _updateScrollState: function(scrollTop) {
|
| + if (this.isOnScreen()) {
|
| + var viewportTop = this._elementTop - scrollTop;
|
| + this._progress = 1 - (viewportTop + this._elementHeight) / this._cachedScrollTargetHeight;
|
| + this._runEffects(this._progress, scrollTop);
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Returns true if this app-box is on the screen.
|
| + * That is, visible in the current viewport.
|
| + *
|
| + * @method isOnScreen
|
| + * @return {boolean}
|
| + */
|
| + isOnScreen: function() {
|
| + return this._elementTop < this._scrollTop + this._cachedScrollTargetHeight
|
| + && this._elementTop + this._elementHeight > this._scrollTop;
|
| + },
|
| +
|
| + _resizeHandler: function() {
|
| + this.resetLayout();
|
| + },
|
| +
|
| + _getDOMRef: function(id) {
|
| + if (id === 'background') {
|
| + return this.$.background;
|
| + }
|
| + if (id === 'backgroundFrontLayer') {
|
| + return this.$.backgroundFrontLayer;
|
| + }
|
| + },
|
| +
|
| + /**
|
| + * Returns an object containing the progress value of the scroll effects.
|
| + *
|
| + * @method getScrollState
|
| + * @return {Object}
|
| + */
|
| + getScrollState: function() {
|
| + return { progress: this._progress };
|
| + }
|
| + });
|
| + </script>
|
| +</dom-module>
|
| +<style is="custom-style">
|
| +
|
| + :root {
|
| +
|
| + /* Material Design color palette for Google products */
|
| +
|
| + --google-red-100: #f4c7c3;
|
| + --google-red-300: #e67c73;
|
| + --google-red-500: #db4437;
|
| + --google-red-700: #c53929;
|
| +
|
| + --google-blue-100: #c6dafc;
|
| + --google-blue-300: #7baaf7;
|
| + --google-blue-500: #4285f4;
|
| + --google-blue-700: #3367d6;
|
| +
|
| + --google-green-100: #b7e1cd;
|
| + --google-green-300: #57bb8a;
|
| + --google-green-500: #0f9d58;
|
| + --google-green-700: #0b8043;
|
| +
|
| + --google-yellow-100: #fce8b2;
|
| + --google-yellow-300: #f7cb4d;
|
| + --google-yellow-500: #f4b400;
|
| + --google-yellow-700: #f09300;
|
| +
|
| + --google-grey-100: #f5f5f5;
|
| + --google-grey-300: #e0e0e0;
|
| + --google-grey-500: #9e9e9e;
|
| + --google-grey-700: #616161;
|
| +
|
| + /* Material Design color palette from online spec document */
|
| +
|
| + --paper-red-50: #ffebee;
|
| + --paper-red-100: #ffcdd2;
|
| + --paper-red-200: #ef9a9a;
|
| + --paper-red-300: #e57373;
|
| + --paper-red-400: #ef5350;
|
| + --paper-red-500: #f44336;
|
| + --paper-red-600: #e53935;
|
| + --paper-red-700: #d32f2f;
|
| + --paper-red-800: #c62828;
|
| + --paper-red-900: #b71c1c;
|
| + --paper-red-a100: #ff8a80;
|
| + --paper-red-a200: #ff5252;
|
| + --paper-red-a400: #ff1744;
|
| + --paper-red-a700: #d50000;
|
| +
|
| + --paper-pink-50: #fce4ec;
|
| + --paper-pink-100: #f8bbd0;
|
| + --paper-pink-200: #f48fb1;
|
| + --paper-pink-300: #f06292;
|
| + --paper-pink-400: #ec407a;
|
| + --paper-pink-500: #e91e63;
|
| + --paper-pink-600: #d81b60;
|
| + --paper-pink-700: #c2185b;
|
| + --paper-pink-800: #ad1457;
|
| + --paper-pink-900: #880e4f;
|
| + --paper-pink-a100: #ff80ab;
|
| + --paper-pink-a200: #ff4081;
|
| + --paper-pink-a400: #f50057;
|
| + --paper-pink-a700: #c51162;
|
| +
|
| + --paper-purple-50: #f3e5f5;
|
| + --paper-purple-100: #e1bee7;
|
| + --paper-purple-200: #ce93d8;
|
| + --paper-purple-300: #ba68c8;
|
| + --paper-purple-400: #ab47bc;
|
| + --paper-purple-500: #9c27b0;
|
| + --paper-purple-600: #8e24aa;
|
| + --paper-purple-700: #7b1fa2;
|
| + --paper-purple-800: #6a1b9a;
|
| + --paper-purple-900: #4a148c;
|
| + --paper-purple-a100: #ea80fc;
|
| + --paper-purple-a200: #e040fb;
|
| + --paper-purple-a400: #d500f9;
|
| + --paper-purple-a700: #aa00ff;
|
| +
|
| + --paper-deep-purple-50: #ede7f6;
|
| + --paper-deep-purple-100: #d1c4e9;
|
| + --paper-deep-purple-200: #b39ddb;
|
| + --paper-deep-purple-300: #9575cd;
|
| + --paper-deep-purple-400: #7e57c2;
|
| + --paper-deep-purple-500: #673ab7;
|
| + --paper-deep-purple-600: #5e35b1;
|
| + --paper-deep-purple-700: #512da8;
|
| + --paper-deep-purple-800: #4527a0;
|
| + --paper-deep-purple-900: #311b92;
|
| + --paper-deep-purple-a100: #b388ff;
|
| + --paper-deep-purple-a200: #7c4dff;
|
| + --paper-deep-purple-a400: #651fff;
|
| + --paper-deep-purple-a700: #6200ea;
|
| +
|
| + --paper-indigo-50: #e8eaf6;
|
| + --paper-indigo-100: #c5cae9;
|
| + --paper-indigo-200: #9fa8da;
|
| + --paper-indigo-300: #7986cb;
|
| + --paper-indigo-400: #5c6bc0;
|
| + --paper-indigo-500: #3f51b5;
|
| + --paper-indigo-600: #3949ab;
|
| + --paper-indigo-700: #303f9f;
|
| + --paper-indigo-800: #283593;
|
| + --paper-indigo-900: #1a237e;
|
| + --paper-indigo-a100: #8c9eff;
|
| + --paper-indigo-a200: #536dfe;
|
| + --paper-indigo-a400: #3d5afe;
|
| + --paper-indigo-a700: #304ffe;
|
| +
|
| + --paper-blue-50: #e3f2fd;
|
| + --paper-blue-100: #bbdefb;
|
| + --paper-blue-200: #90caf9;
|
| + --paper-blue-300: #64b5f6;
|
| + --paper-blue-400: #42a5f5;
|
| + --paper-blue-500: #2196f3;
|
| + --paper-blue-600: #1e88e5;
|
| + --paper-blue-700: #1976d2;
|
| + --paper-blue-800: #1565c0;
|
| + --paper-blue-900: #0d47a1;
|
| + --paper-blue-a100: #82b1ff;
|
| + --paper-blue-a200: #448aff;
|
| + --paper-blue-a400: #2979ff;
|
| + --paper-blue-a700: #2962ff;
|
| +
|
| + --paper-light-blue-50: #e1f5fe;
|
| + --paper-light-blue-100: #b3e5fc;
|
| + --paper-light-blue-200: #81d4fa;
|
| + --paper-light-blue-300: #4fc3f7;
|
| + --paper-light-blue-400: #29b6f6;
|
| + --paper-light-blue-500: #03a9f4;
|
| + --paper-light-blue-600: #039be5;
|
| + --paper-light-blue-700: #0288d1;
|
| + --paper-light-blue-800: #0277bd;
|
| + --paper-light-blue-900: #01579b;
|
| + --paper-light-blue-a100: #80d8ff;
|
| + --paper-light-blue-a200: #40c4ff;
|
| + --paper-light-blue-a400: #00b0ff;
|
| + --paper-light-blue-a700: #0091ea;
|
| +
|
| + --paper-cyan-50: #e0f7fa;
|
| + --paper-cyan-100: #b2ebf2;
|
| + --paper-cyan-200: #80deea;
|
| + --paper-cyan-300: #4dd0e1;
|
| + --paper-cyan-400: #26c6da;
|
| + --paper-cyan-500: #00bcd4;
|
| + --paper-cyan-600: #00acc1;
|
| + --paper-cyan-700: #0097a7;
|
| + --paper-cyan-800: #00838f;
|
| + --paper-cyan-900: #006064;
|
| + --paper-cyan-a100: #84ffff;
|
| + --paper-cyan-a200: #18ffff;
|
| + --paper-cyan-a400: #00e5ff;
|
| + --paper-cyan-a700: #00b8d4;
|
| +
|
| + --paper-teal-50: #e0f2f1;
|
| + --paper-teal-100: #b2dfdb;
|
| + --paper-teal-200: #80cbc4;
|
| + --paper-teal-300: #4db6ac;
|
| + --paper-teal-400: #26a69a;
|
| + --paper-teal-500: #009688;
|
| + --paper-teal-600: #00897b;
|
| + --paper-teal-700: #00796b;
|
| + --paper-teal-800: #00695c;
|
| + --paper-teal-900: #004d40;
|
| + --paper-teal-a100: #a7ffeb;
|
| + --paper-teal-a200: #64ffda;
|
| + --paper-teal-a400: #1de9b6;
|
| + --paper-teal-a700: #00bfa5;
|
| +
|
| + --paper-green-50: #e8f5e9;
|
| + --paper-green-100: #c8e6c9;
|
| + --paper-green-200: #a5d6a7;
|
| + --paper-green-300: #81c784;
|
| + --paper-green-400: #66bb6a;
|
| + --paper-green-500: #4caf50;
|
| + --paper-green-600: #43a047;
|
| + --paper-green-700: #388e3c;
|
| + --paper-green-800: #2e7d32;
|
| + --paper-green-900: #1b5e20;
|
| + --paper-green-a100: #b9f6ca;
|
| + --paper-green-a200: #69f0ae;
|
| + --paper-green-a400: #00e676;
|
| + --paper-green-a700: #00c853;
|
| +
|
| + --paper-light-green-50: #f1f8e9;
|
| + --paper-light-green-100: #dcedc8;
|
| + --paper-light-green-200: #c5e1a5;
|
| + --paper-light-green-300: #aed581;
|
| + --paper-light-green-400: #9ccc65;
|
| + --paper-light-green-500: #8bc34a;
|
| + --paper-light-green-600: #7cb342;
|
| + --paper-light-green-700: #689f38;
|
| + --paper-light-green-800: #558b2f;
|
| + --paper-light-green-900: #33691e;
|
| + --paper-light-green-a100: #ccff90;
|
| + --paper-light-green-a200: #b2ff59;
|
| + --paper-light-green-a400: #76ff03;
|
| + --paper-light-green-a700: #64dd17;
|
| +
|
| + --paper-lime-50: #f9fbe7;
|
| + --paper-lime-100: #f0f4c3;
|
| + --paper-lime-200: #e6ee9c;
|
| + --paper-lime-300: #dce775;
|
| + --paper-lime-400: #d4e157;
|
| + --paper-lime-500: #cddc39;
|
| + --paper-lime-600: #c0ca33;
|
| + --paper-lime-700: #afb42b;
|
| + --paper-lime-800: #9e9d24;
|
| + --paper-lime-900: #827717;
|
| + --paper-lime-a100: #f4ff81;
|
| + --paper-lime-a200: #eeff41;
|
| + --paper-lime-a400: #c6ff00;
|
| + --paper-lime-a700: #aeea00;
|
| +
|
| + --paper-yellow-50: #fffde7;
|
| + --paper-yellow-100: #fff9c4;
|
| + --paper-yellow-200: #fff59d;
|
| + --paper-yellow-300: #fff176;
|
| + --paper-yellow-400: #ffee58;
|
| + --paper-yellow-500: #ffeb3b;
|
| + --paper-yellow-600: #fdd835;
|
| + --paper-yellow-700: #fbc02d;
|
| + --paper-yellow-800: #f9a825;
|
| + --paper-yellow-900: #f57f17;
|
| + --paper-yellow-a100: #ffff8d;
|
| + --paper-yellow-a200: #ffff00;
|
| + --paper-yellow-a400: #ffea00;
|
| + --paper-yellow-a700: #ffd600;
|
| +
|
| + --paper-amber-50: #fff8e1;
|
| + --paper-amber-100: #ffecb3;
|
| + --paper-amber-200: #ffe082;
|
| + --paper-amber-300: #ffd54f;
|
| + --paper-amber-400: #ffca28;
|
| + --paper-amber-500: #ffc107;
|
| + --paper-amber-600: #ffb300;
|
| + --paper-amber-700: #ffa000;
|
| + --paper-amber-800: #ff8f00;
|
| + --paper-amber-900: #ff6f00;
|
| + --paper-amber-a100: #ffe57f;
|
| + --paper-amber-a200: #ffd740;
|
| + --paper-amber-a400: #ffc400;
|
| + --paper-amber-a700: #ffab00;
|
| +
|
| + --paper-orange-50: #fff3e0;
|
| + --paper-orange-100: #ffe0b2;
|
| + --paper-orange-200: #ffcc80;
|
| + --paper-orange-300: #ffb74d;
|
| + --paper-orange-400: #ffa726;
|
| + --paper-orange-500: #ff9800;
|
| + --paper-orange-600: #fb8c00;
|
| + --paper-orange-700: #f57c00;
|
| + --paper-orange-800: #ef6c00;
|
| + --paper-orange-900: #e65100;
|
| + --paper-orange-a100: #ffd180;
|
| + --paper-orange-a200: #ffab40;
|
| + --paper-orange-a400: #ff9100;
|
| + --paper-orange-a700: #ff6500;
|
| +
|
| + --paper-deep-orange-50: #fbe9e7;
|
| + --paper-deep-orange-100: #ffccbc;
|
| + --paper-deep-orange-200: #ffab91;
|
| + --paper-deep-orange-300: #ff8a65;
|
| + --paper-deep-orange-400: #ff7043;
|
| + --paper-deep-orange-500: #ff5722;
|
| + --paper-deep-orange-600: #f4511e;
|
| + --paper-deep-orange-700: #e64a19;
|
| + --paper-deep-orange-800: #d84315;
|
| + --paper-deep-orange-900: #bf360c;
|
| + --paper-deep-orange-a100: #ff9e80;
|
| + --paper-deep-orange-a200: #ff6e40;
|
| + --paper-deep-orange-a400: #ff3d00;
|
| + --paper-deep-orange-a700: #dd2c00;
|
| +
|
| + --paper-brown-50: #efebe9;
|
| + --paper-brown-100: #d7ccc8;
|
| + --paper-brown-200: #bcaaa4;
|
| + --paper-brown-300: #a1887f;
|
| + --paper-brown-400: #8d6e63;
|
| + --paper-brown-500: #795548;
|
| + --paper-brown-600: #6d4c41;
|
| + --paper-brown-700: #5d4037;
|
| + --paper-brown-800: #4e342e;
|
| + --paper-brown-900: #3e2723;
|
| +
|
| + --paper-grey-50: #fafafa;
|
| + --paper-grey-100: #f5f5f5;
|
| + --paper-grey-200: #eeeeee;
|
| + --paper-grey-300: #e0e0e0;
|
| + --paper-grey-400: #bdbdbd;
|
| + --paper-grey-500: #9e9e9e;
|
| + --paper-grey-600: #757575;
|
| + --paper-grey-700: #616161;
|
| + --paper-grey-800: #424242;
|
| + --paper-grey-900: #212121;
|
| +
|
| + --paper-blue-grey-50: #eceff1;
|
| + --paper-blue-grey-100: #cfd8dc;
|
| + --paper-blue-grey-200: #b0bec5;
|
| + --paper-blue-grey-300: #90a4ae;
|
| + --paper-blue-grey-400: #78909c;
|
| + --paper-blue-grey-500: #607d8b;
|
| + --paper-blue-grey-600: #546e7a;
|
| + --paper-blue-grey-700: #455a64;
|
| + --paper-blue-grey-800: #37474f;
|
| + --paper-blue-grey-900: #263238;
|
| +
|
| + /* opacity for dark text on a light background */
|
| + --dark-divider-opacity: 0.12;
|
| + --dark-disabled-opacity: 0.38; /* or hint text or icon */
|
| + --dark-secondary-opacity: 0.54;
|
| + --dark-primary-opacity: 0.87;
|
| +
|
| + /* opacity for light text on a dark background */
|
| + --light-divider-opacity: 0.12;
|
| + --light-disabled-opacity: 0.3; /* or hint text or icon */
|
| + --light-secondary-opacity: 0.7;
|
| + --light-primary-opacity: 1.0;
|
| +
|
| + }
|
| +
|
| +</style>
|
| +<script>
|
| +
|
| + /** @polymerBehavior */
|
| + Polymer.PaperSpinnerBehavior = {
|
| +
|
| + listeners: {
|
| + 'animationend': '__reset',
|
| + 'webkitAnimationEnd': '__reset'
|
| + },
|
| +
|
| + properties: {
|
| + /**
|
| + * Displays the spinner.
|
| + */
|
| + active: {
|
| + type: Boolean,
|
| + value: false,
|
| + reflectToAttribute: true,
|
| + observer: '__activeChanged'
|
| + },
|
| +
|
| + /**
|
| + * Alternative text content for accessibility support.
|
| + * If alt is present, it will add an aria-label whose content matches alt when active.
|
| + * If alt is not present, it will default to 'loading' as the alt value.
|
| + */
|
| + alt: {
|
| + type: String,
|
| + value: 'loading',
|
| + observer: '__altChanged'
|
| + },
|
| +
|
| + __coolingDown: {
|
| + type: Boolean,
|
| + value: false
|
| + }
|
| + },
|
| +
|
| + __computeContainerClasses: function(active, coolingDown) {
|
| + return [
|
| + active || coolingDown ? 'active' : '',
|
| + coolingDown ? 'cooldown' : ''
|
| + ].join(' ');
|
| + },
|
| +
|
| + __activeChanged: function(active, old) {
|
| + this.__setAriaHidden(!active);
|
| + this.__coolingDown = !active && old;
|
| + },
|
| +
|
| + __altChanged: function(alt) {
|
| + // user-provided `aria-label` takes precedence over prototype default
|
| + if (alt === this.getPropertyInfo('alt').value) {
|
| + this.alt = this.getAttribute('aria-label') || alt;
|
| + } else {
|
| + this.__setAriaHidden(alt==='');
|
| + this.setAttribute('aria-label', alt);
|
| + }
|
| + },
|
| +
|
| + __setAriaHidden: function(hidden) {
|
| + var attr = 'aria-hidden';
|
| + if (hidden) {
|
| + this.setAttribute(attr, 'true');
|
| + } else {
|
| + this.removeAttribute(attr);
|
| + }
|
| + },
|
| +
|
| + __reset: function() {
|
| + this.active = false;
|
| + this.__coolingDown = false;
|
| + }
|
| + };
|
| +</script>
|
| +<dom-module id="paper-spinner-styles" assetpath="bower_components/paper-spinner/">
|
| + <template>
|
| + <style>
|
| + /*
|
| + /**************************/
|
| + /* STYLES FOR THE SPINNER */
|
| + /**************************/
|
| +
|
| + /*
|
| + * Constants:
|
| + * ARCSIZE = 270 degrees (amount of circle the arc takes up)
|
| + * ARCTIME = 1333ms (time it takes to expand and contract arc)
|
| + * ARCSTARTROT = 216 degrees (how much the start location of the arc
|
| + * should rotate each time, 216 gives us a
|
| + * 5 pointed star shape (it's 360/5 * 3).
|
| + * For a 7 pointed star, we might do
|
| + * 360/7 * 3 = 154.286)
|
| + * SHRINK_TIME = 400ms
|
| + */
|
| +
|
| + :host {
|
| + display: inline-block;
|
| + position: relative;
|
| + width: 28px;
|
| + height: 28px;
|
| +
|
| + /* 360 * ARCTIME / (ARCSTARTROT + (360-ARCSIZE)) */
|
| + --paper-spinner-container-rotation-duration: 1568ms;
|
| +
|
| + /* ARCTIME */
|
| + --paper-spinner-expand-contract-duration: 1333ms;
|
| +
|
| + /* 4 * ARCTIME */
|
| + --paper-spinner-full-cycle-duration: 5332ms;
|
| +
|
| + /* SHRINK_TIME */
|
| + --paper-spinner-cooldown-duration: 400ms;
|
| + }
|
| +
|
| + #spinnerContainer {
|
| + width: 100%;
|
| + height: 100%;
|
| +
|
| + /* The spinner does not have any contents that would have to be
|
| + * flipped if the direction changes. Always use ltr so that the
|
| + * style works out correctly in both cases. */
|
| + direction: ltr;
|
| + }
|
| +
|
| + #spinnerContainer.active {
|
| + -webkit-animation: container-rotate var(--paper-spinner-container-rotation-duration) linear infinite;
|
| + animation: container-rotate var(--paper-spinner-container-rotation-duration) linear infinite;
|
| + }
|
| +
|
| + @-webkit-keyframes container-rotate {
|
| + to { -webkit-transform: rotate(360deg) }
|
| + }
|
| +
|
| + @keyframes container-rotate {
|
| + to { transform: rotate(360deg) }
|
| + }
|
| +
|
| + .spinner-layer {
|
| + position: absolute;
|
| + width: 100%;
|
| + height: 100%;
|
| + opacity: 0;
|
| + white-space: nowrap;
|
| + border-color: var(--paper-spinner-color, --google-blue-500);
|
| + }
|
| +
|
| + .layer-1 {
|
| + border-color: var(--paper-spinner-layer-1-color, --google-blue-500);
|
| + }
|
| +
|
| + .layer-2 {
|
| + border-color: var(--paper-spinner-layer-2-color, --google-red-500);
|
| + }
|
| +
|
| + .layer-3 {
|
| + border-color: var(--paper-spinner-layer-3-color, --google-yellow-500);
|
| + }
|
| +
|
| + .layer-4 {
|
| + border-color: var(--paper-spinner-layer-4-color, --google-green-500);
|
| + }
|
| +
|
| + /**
|
| + * IMPORTANT NOTE ABOUT CSS ANIMATION PROPERTIES (keanulee):
|
| + *
|
| + * iOS Safari (tested on iOS 8.1) does not handle animation-delay very well - it doesn't
|
| + * guarantee that the animation will start _exactly_ after that value. So we avoid using
|
| + * animation-delay and instead set custom keyframes for each color (as layer-2undant as it
|
| + * seems).
|
| + */
|
| + .active .spinner-layer {
|
| + -webkit-animation-name: fill-unfill-rotate;
|
| + -webkit-animation-duration: var(--paper-spinner-full-cycle-duration);
|
| + -webkit-animation-timing-function: cubic-bezier(0.4, 0.0, 0.2, 1);
|
| + -webkit-animation-iteration-count: infinite;
|
| + animation-name: fill-unfill-rotate;
|
| + animation-duration: var(--paper-spinner-full-cycle-duration);
|
| + animation-timing-function: cubic-bezier(0.4, 0.0, 0.2, 1);
|
| + animation-iteration-count: infinite;
|
| + opacity: 1;
|
| + }
|
| +
|
| + .active .spinner-layer.layer-1 {
|
| + -webkit-animation-name: fill-unfill-rotate, layer-1-fade-in-out;
|
| + animation-name: fill-unfill-rotate, layer-1-fade-in-out;
|
| + }
|
| +
|
| + .active .spinner-layer.layer-2 {
|
| + -webkit-animation-name: fill-unfill-rotate, layer-2-fade-in-out;
|
| + animation-name: fill-unfill-rotate, layer-2-fade-in-out;
|
| + }
|
| +
|
| + .active .spinner-layer.layer-3 {
|
| + -webkit-animation-name: fill-unfill-rotate, layer-3-fade-in-out;
|
| + animation-name: fill-unfill-rotate, layer-3-fade-in-out;
|
| + }
|
| +
|
| + .active .spinner-layer.layer-4 {
|
| + -webkit-animation-name: fill-unfill-rotate, layer-4-fade-in-out;
|
| + animation-name: fill-unfill-rotate, layer-4-fade-in-out;
|
| + }
|
| +
|
| + @-webkit-keyframes fill-unfill-rotate {
|
| + 12.5% { -webkit-transform: rotate(135deg) } /* 0.5 * ARCSIZE */
|
| + 25% { -webkit-transform: rotate(270deg) } /* 1 * ARCSIZE */
|
| + 37.5% { -webkit-transform: rotate(405deg) } /* 1.5 * ARCSIZE */
|
| + 50% { -webkit-transform: rotate(540deg) } /* 2 * ARCSIZE */
|
| + 62.5% { -webkit-transform: rotate(675deg) } /* 2.5 * ARCSIZE */
|
| + 75% { -webkit-transform: rotate(810deg) } /* 3 * ARCSIZE */
|
| + 87.5% { -webkit-transform: rotate(945deg) } /* 3.5 * ARCSIZE */
|
| + to { -webkit-transform: rotate(1080deg) } /* 4 * ARCSIZE */
|
| + }
|
| +
|
| + @keyframes fill-unfill-rotate {
|
| + 12.5% { transform: rotate(135deg) } /* 0.5 * ARCSIZE */
|
| + 25% { transform: rotate(270deg) } /* 1 * ARCSIZE */
|
| + 37.5% { transform: rotate(405deg) } /* 1.5 * ARCSIZE */
|
| + 50% { transform: rotate(540deg) } /* 2 * ARCSIZE */
|
| + 62.5% { transform: rotate(675deg) } /* 2.5 * ARCSIZE */
|
| + 75% { transform: rotate(810deg) } /* 3 * ARCSIZE */
|
| + 87.5% { transform: rotate(945deg) } /* 3.5 * ARCSIZE */
|
| + to { transform: rotate(1080deg) } /* 4 * ARCSIZE */
|
| + }
|
| +
|
| + @-webkit-keyframes layer-1-fade-in-out {
|
| + 0% { opacity: 1 }
|
| + 25% { opacity: 1 }
|
| + 26% { opacity: 0 }
|
| + 89% { opacity: 0 }
|
| + 90% { opacity: 1 }
|
| + to { opacity: 1 }
|
| + }
|
| +
|
| + @keyframes layer-1-fade-in-out {
|
| + 0% { opacity: 1 }
|
| + 25% { opacity: 1 }
|
| + 26% { opacity: 0 }
|
| + 89% { opacity: 0 }
|
| + 90% { opacity: 1 }
|
| + to { opacity: 1 }
|
| + }
|
| +
|
| + @-webkit-keyframes layer-2-fade-in-out {
|
| + 0% { opacity: 0 }
|
| + 15% { opacity: 0 }
|
| + 25% { opacity: 1 }
|
| + 50% { opacity: 1 }
|
| + 51% { opacity: 0 }
|
| + to { opacity: 0 }
|
| + }
|
| +
|
| + @keyframes layer-2-fade-in-out {
|
| + 0% { opacity: 0 }
|
| + 15% { opacity: 0 }
|
| + 25% { opacity: 1 }
|
| + 50% { opacity: 1 }
|
| + 51% { opacity: 0 }
|
| + to { opacity: 0 }
|
| + }
|
| +
|
| + @-webkit-keyframes layer-3-fade-in-out {
|
| + 0% { opacity: 0 }
|
| + 40% { opacity: 0 }
|
| + 50% { opacity: 1 }
|
| + 75% { opacity: 1 }
|
| + 76% { opacity: 0 }
|
| + to { opacity: 0 }
|
| + }
|
| +
|
| + @keyframes layer-3-fade-in-out {
|
| + 0% { opacity: 0 }
|
| + 40% { opacity: 0 }
|
| + 50% { opacity: 1 }
|
| + 75% { opacity: 1 }
|
| + 76% { opacity: 0 }
|
| + to { opacity: 0 }
|
| + }
|
| +
|
| + @-webkit-keyframes layer-4-fade-in-out {
|
| + 0% { opacity: 0 }
|
| + 65% { opacity: 0 }
|
| + 75% { opacity: 1 }
|
| + 90% { opacity: 1 }
|
| + to { opacity: 0 }
|
| + }
|
| +
|
| + @keyframes layer-4-fade-in-out {
|
| + 0% { opacity: 0 }
|
| + 65% { opacity: 0 }
|
| + 75% { opacity: 1 }
|
| + 90% { opacity: 1 }
|
| + to { opacity: 0 }
|
| + }
|
| +
|
| + .circle-clipper {
|
| + display: inline-block;
|
| + position: relative;
|
| + width: 50%;
|
| + height: 100%;
|
| + overflow: hidden;
|
| + border-color: inherit;
|
| + }
|
| +
|
| + /**
|
| + * Patch the gap that appear between the two adjacent div.circle-clipper while the
|
| + * spinner is rotating (appears on Chrome 50, Safari 9.1.1, and Edge).
|
| + */
|
| + .spinner-layer::after {
|
| + left: 45%;
|
| + width: 10%;
|
| + border-top-style: solid;
|
| + }
|
| +
|
| + .spinner-layer::after,
|
| + .circle-clipper::after {
|
| + content: '';
|
| + box-sizing: border-box;
|
| + position: absolute;
|
| + top: 0;
|
| + border-width: var(--paper-spinner-stroke-width, 3px);
|
| + border-color: inherit;
|
| + border-radius: 50%;
|
| + }
|
| +
|
| + .circle-clipper::after {
|
| + bottom: 0;
|
| + width: 200%;
|
| + border-style: solid;
|
| + border-bottom-color: transparent !important;
|
| + }
|
| +
|
| + .circle-clipper.left::after {
|
| + left: 0;
|
| + border-right-color: transparent !important;
|
| + -webkit-transform: rotate(129deg);
|
| + transform: rotate(129deg);
|
| + }
|
| +
|
| + .circle-clipper.right::after {
|
| + left: -100%;
|
| + border-left-color: transparent !important;
|
| + -webkit-transform: rotate(-129deg);
|
| + transform: rotate(-129deg);
|
| + }
|
| +
|
| + .active .gap-patch::after,
|
| + .active .circle-clipper::after {
|
| + -webkit-animation-duration: var(--paper-spinner-expand-contract-duration);
|
| + -webkit-animation-timing-function: cubic-bezier(0.4, 0.0, 0.2, 1);
|
| + -webkit-animation-iteration-count: infinite;
|
| + animation-duration: var(--paper-spinner-expand-contract-duration);
|
| + animation-timing-function: cubic-bezier(0.4, 0.0, 0.2, 1);
|
| + animation-iteration-count: infinite;
|
| + }
|
| +
|
| + .active .circle-clipper.left::after {
|
| + -webkit-animation-name: left-spin;
|
| + animation-name: left-spin;
|
| + }
|
| +
|
| + .active .circle-clipper.right::after {
|
| + -webkit-animation-name: right-spin;
|
| + animation-name: right-spin;
|
| + }
|
| +
|
| + @-webkit-keyframes left-spin {
|
| + 0% { -webkit-transform: rotate(130deg) }
|
| + 50% { -webkit-transform: rotate(-5deg) }
|
| + to { -webkit-transform: rotate(130deg) }
|
| + }
|
| +
|
| + @keyframes left-spin {
|
| + 0% { transform: rotate(130deg) }
|
| + 50% { transform: rotate(-5deg) }
|
| + to { transform: rotate(130deg) }
|
| + }
|
| +
|
| + @-webkit-keyframes right-spin {
|
| + 0% { -webkit-transform: rotate(-130deg) }
|
| + 50% { -webkit-transform: rotate(5deg) }
|
| + to { -webkit-transform: rotate(-130deg) }
|
| + }
|
| +
|
| + @keyframes right-spin {
|
| + 0% { transform: rotate(-130deg) }
|
| + 50% { transform: rotate(5deg) }
|
| + to { transform: rotate(-130deg) }
|
| + }
|
| +
|
| + #spinnerContainer.cooldown {
|
| + -webkit-animation: container-rotate var(--paper-spinner-container-rotation-duration) linear infinite, fade-out var(--paper-spinner-cooldown-duration) cubic-bezier(0.4, 0.0, 0.2, 1);
|
| + animation: container-rotate var(--paper-spinner-container-rotation-duration) linear infinite, fade-out var(--paper-spinner-cooldown-duration) cubic-bezier(0.4, 0.0, 0.2, 1);
|
| + }
|
| +
|
| + @-webkit-keyframes fade-out {
|
| + 0% { opacity: 1 }
|
| + to { opacity: 0 }
|
| + }
|
| +
|
| + @keyframes fade-out {
|
| + 0% { opacity: 1 }
|
| + to { opacity: 0 }
|
| + }
|
| + </style>
|
| + </template>
|
| +</dom-module>
|
| +
|
| +
|
| +<dom-module id="paper-spinner-lite" assetpath="bower_components/paper-spinner/">
|
| + <template strip-whitespace="">
|
| + <style include="paper-spinner-styles"></style>
|
| +
|
| + <div id="spinnerContainer" class-name="[[__computeContainerClasses(active, __coolingDown)]]">
|
| + <div class="spinner-layer">
|
| + <div class="circle-clipper left"></div>
|
| + <div class="circle-clipper right"></div>
|
| + </div>
|
| + </div>
|
| + </template>
|
| +
|
| + <script>
|
| + Polymer({
|
| + is: 'paper-spinner-lite',
|
| +
|
| + behaviors: [
|
| + Polymer.PaperSpinnerBehavior
|
| + ]
|
| + });
|
| + </script>
|
| +</dom-module>
|
| +
|
| +<dom-module id="iron-flex" assetpath="bower_components/iron-flex-layout/">
|
| + <template>
|
| + <style>
|
| + .layout.horizontal,
|
| + .layout.vertical {
|
| + display: -ms-flexbox;
|
| + display: -webkit-flex;
|
| + display: flex;
|
| + }
|
| +
|
| + .layout.inline {
|
| + display: -ms-inline-flexbox;
|
| + display: -webkit-inline-flex;
|
| + display: inline-flex;
|
| + }
|
| +
|
| + .layout.horizontal {
|
| + -ms-flex-direction: row;
|
| + -webkit-flex-direction: row;
|
| + flex-direction: row;
|
| + }
|
| +
|
| + .layout.vertical {
|
| + -ms-flex-direction: column;
|
| + -webkit-flex-direction: column;
|
| + flex-direction: column;
|
| + }
|
| +
|
| + .layout.wrap {
|
| + -ms-flex-wrap: wrap;
|
| + -webkit-flex-wrap: wrap;
|
| + flex-wrap: wrap;
|
| + }
|
| +
|
| + .layout.center,
|
| + .layout.center-center {
|
| + -ms-flex-align: center;
|
| + -webkit-align-items: center;
|
| + align-items: center;
|
| + }
|
| +
|
| + .layout.center-justified,
|
| + .layout.center-center {
|
| + -ms-flex-pack: center;
|
| + -webkit-justify-content: center;
|
| + justify-content: center;
|
| + }
|
| +
|
| + .flex {
|
| + -ms-flex: 1 1 0.000000001px;
|
| + -webkit-flex: 1;
|
| + flex: 1;
|
| + -webkit-flex-basis: 0.000000001px;
|
| + flex-basis: 0.000000001px;
|
| + }
|
| +
|
| + .flex-auto {
|
| + -ms-flex: 1 1 auto;
|
| + -webkit-flex: 1 1 auto;
|
| + flex: 1 1 auto;
|
| + }
|
| +
|
| + .flex-none {
|
| + -ms-flex: none;
|
| + -webkit-flex: none;
|
| + flex: none;
|
| + }
|
| + </style>
|
| + </template>
|
| +</dom-module>
|
| +
|
| +
|
| +<dom-module id="iron-flex-reverse" assetpath="bower_components/iron-flex-layout/">
|
| + <template>
|
| + <style>
|
| + .layout.horizontal-reverse,
|
| + .layout.vertical-reverse {
|
| + display: -ms-flexbox;
|
| + display: -webkit-flex;
|
| + display: flex;
|
| + }
|
| +
|
| + .layout.horizontal-reverse {
|
| + -ms-flex-direction: row-reverse;
|
| + -webkit-flex-direction: row-reverse;
|
| + flex-direction: row-reverse;
|
| + }
|
| +
|
| + .layout.vertical-reverse {
|
| + -ms-flex-direction: column-reverse;
|
| + -webkit-flex-direction: column-reverse;
|
| + flex-direction: column-reverse;
|
| + }
|
| +
|
| + .layout.wrap-reverse {
|
| + -ms-flex-wrap: wrap-reverse;
|
| + -webkit-flex-wrap: wrap-reverse;
|
| + flex-wrap: wrap-reverse;
|
| + }
|
| + </style>
|
| + </template>
|
| +</dom-module>
|
| +
|
| +
|
| +<dom-module id="iron-flex-alignment" assetpath="bower_components/iron-flex-layout/">
|
| + <template>
|
| + <style>
|
| + /**
|
| + * Alignment in cross axis.
|
| + */
|
| + .layout.start {
|
| + -ms-flex-align: start;
|
| + -webkit-align-items: flex-start;
|
| + align-items: flex-start;
|
| + }
|
| +
|
| + .layout.center,
|
| + .layout.center-center {
|
| + -ms-flex-align: center;
|
| + -webkit-align-items: center;
|
| + align-items: center;
|
| + }
|
| +
|
| + .layout.end {
|
| + -ms-flex-align: end;
|
| + -webkit-align-items: flex-end;
|
| + align-items: flex-end;
|
| + }
|
| +
|
| + .layout.baseline {
|
| + -ms-flex-align: baseline;
|
| + -webkit-align-items: baseline;
|
| + align-items: baseline;
|
| + }
|
| +
|
| + /**
|
| + * Alignment in main axis.
|
| + */
|
| + .layout.start-justified {
|
| + -ms-flex-pack: start;
|
| + -webkit-justify-content: flex-start;
|
| + justify-content: flex-start;
|
| + }
|
| +
|
| + .layout.center-justified,
|
| + .layout.center-center {
|
| + -ms-flex-pack: center;
|
| + -webkit-justify-content: center;
|
| + justify-content: center;
|
| + }
|
| +
|
| + .layout.end-justified {
|
| + -ms-flex-pack: end;
|
| + -webkit-justify-content: flex-end;
|
| + justify-content: flex-end;
|
| + }
|
| +
|
| + .layout.around-justified {
|
| + -ms-flex-pack: distribute;
|
| + -webkit-justify-content: space-around;
|
| + justify-content: space-around;
|
| + }
|
| +
|
| + .layout.justified {
|
| + -ms-flex-pack: justify;
|
| + -webkit-justify-content: space-between;
|
| + justify-content: space-between;
|
| + }
|
| +
|
| + /**
|
| + * Self alignment.
|
| + */
|
| + .self-start {
|
| + -ms-align-self: flex-start;
|
| + -webkit-align-self: flex-start;
|
| + align-self: flex-start;
|
| + }
|
| +
|
| + .self-center {
|
| + -ms-align-self: center;
|
| + -webkit-align-self: center;
|
| + align-self: center;
|
| + }
|
| +
|
| + .self-end {
|
| + -ms-align-self: flex-end;
|
| + -webkit-align-self: flex-end;
|
| + align-self: flex-end;
|
| + }
|
| +
|
| + .self-stretch {
|
| + -ms-align-self: stretch;
|
| + -webkit-align-self: stretch;
|
| + align-self: stretch;
|
| + }
|
| +
|
| + .self-baseline {
|
| + -ms-align-self: baseline;
|
| + -webkit-align-self: baseline;
|
| + align-self: baseline;
|
| + };
|
| +
|
| + /**
|
| + * multi-line alignment in main axis.
|
| + */
|
| + .layout.start-aligned {
|
| + -ms-flex-line-pack: start; /* IE10 */
|
| + -ms-align-content: flex-start;
|
| + -webkit-align-content: flex-start;
|
| + align-content: flex-start;
|
| + }
|
| +
|
| + .layout.end-aligned {
|
| + -ms-flex-line-pack: end; /* IE10 */
|
| + -ms-align-content: flex-end;
|
| + -webkit-align-content: flex-end;
|
| + align-content: flex-end;
|
| + }
|
| +
|
| + .layout.center-aligned {
|
| + -ms-flex-line-pack: center; /* IE10 */
|
| + -ms-align-content: center;
|
| + -webkit-align-content: center;
|
| + align-content: center;
|
| + }
|
| +
|
| + .layout.between-aligned {
|
| + -ms-flex-line-pack: justify; /* IE10 */
|
| + -ms-align-content: space-between;
|
| + -webkit-align-content: space-between;
|
| + align-content: space-between;
|
| + }
|
| +
|
| + .layout.around-aligned {
|
| + -ms-flex-line-pack: distribute; /* IE10 */
|
| + -ms-align-content: space-around;
|
| + -webkit-align-content: space-around;
|
| + align-content: space-around;
|
| + }
|
| + </style>
|
| + </template>
|
| +</dom-module>
|
| +
|
| +<dom-module id="iron-flex-factors" assetpath="bower_components/iron-flex-layout/">
|
| + <template>
|
| + <style>
|
| + .flex,
|
| + .flex-1 {
|
| + -ms-flex: 1 1 0.000000001px;
|
| + -webkit-flex: 1;
|
| + flex: 1;
|
| + -webkit-flex-basis: 0.000000001px;
|
| + flex-basis: 0.000000001px;
|
| + }
|
| +
|
| + .flex-2 {
|
| + -ms-flex: 2;
|
| + -webkit-flex: 2;
|
| + flex: 2;
|
| + }
|
| +
|
| + .flex-3 {
|
| + -ms-flex: 3;
|
| + -webkit-flex: 3;
|
| + flex: 3;
|
| + }
|
| +
|
| + .flex-4 {
|
| + -ms-flex: 4;
|
| + -webkit-flex: 4;
|
| + flex: 4;
|
| + }
|
| +
|
| + .flex-5 {
|
| + -ms-flex: 5;
|
| + -webkit-flex: 5;
|
| + flex: 5;
|
| + }
|
| +
|
| + .flex-6 {
|
| + -ms-flex: 6;
|
| + -webkit-flex: 6;
|
| + flex: 6;
|
| + }
|
| +
|
| + .flex-7 {
|
| + -ms-flex: 7;
|
| + -webkit-flex: 7;
|
| + flex: 7;
|
| + }
|
| +
|
| + .flex-8 {
|
| + -ms-flex: 8;
|
| + -webkit-flex: 8;
|
| + flex: 8;
|
| + }
|
| +
|
| + .flex-9 {
|
| + -ms-flex: 9;
|
| + -webkit-flex: 9;
|
| + flex: 9;
|
| + }
|
| +
|
| + .flex-10 {
|
| + -ms-flex: 10;
|
| + -webkit-flex: 10;
|
| + flex: 10;
|
| + }
|
| +
|
| + .flex-11 {
|
| + -ms-flex: 11;
|
| + -webkit-flex: 11;
|
| + flex: 11;
|
| + }
|
| +
|
| + .flex-12 {
|
| + -ms-flex: 12;
|
| + -webkit-flex: 12;
|
| + flex: 12;
|
| + }
|
| + </style>
|
| + </template>
|
| +</dom-module>
|
| +
|
| +
|
| +<dom-module id="iron-positioning" assetpath="bower_components/iron-flex-layout/">
|
| + <template>
|
| + <style>
|
| + .block {
|
| + display: block;
|
| + }
|
| +
|
| + /* IE 10 support for HTML5 hidden attr */
|
| + [hidden] {
|
| + display: none !important;
|
| + }
|
| +
|
| + .invisible {
|
| + visibility: hidden !important;
|
| + }
|
| +
|
| + .relative {
|
| + position: relative;
|
| + }
|
| +
|
| + .fit {
|
| + position: absolute;
|
| + top: 0;
|
| + right: 0;
|
| + bottom: 0;
|
| + left: 0;
|
| + }
|
| +
|
| + body.fullbleed {
|
| + margin: 0;
|
| + height: 100vh;
|
| + }
|
| +
|
| + .scroll {
|
| + -webkit-overflow-scrolling: touch;
|
| + overflow: auto;
|
| + }
|
| +
|
| + /* fixed position */
|
| + .fixed-bottom,
|
| + .fixed-left,
|
| + .fixed-right,
|
| + .fixed-top {
|
| + position: fixed;
|
| + }
|
| +
|
| + .fixed-top {
|
| + top: 0;
|
| + left: 0;
|
| + right: 0;
|
| + }
|
| +
|
| + .fixed-right {
|
| + top: 0;
|
| + right: 0;
|
| + bottom: 0;
|
| + }
|
| +
|
| + .fixed-bottom {
|
| + right: 0;
|
| + bottom: 0;
|
| + left: 0;
|
| + }
|
| +
|
| + .fixed-left {
|
| + top: 0;
|
| + bottom: 0;
|
| + left: 0;
|
| + }
|
| + </style>
|
| + </template>
|
| +</dom-module>
|
| +<script>
|
| (function() {
|
| "use strict";
|
| /**
|
| @@ -10226,8 +14465,11 @@ You can bind to `isAuthorized` property to monitor authorization state.
|
| #avatar {
|
| border-radius: 5px;
|
| }
|
| - #signinContainer {
|
| - margin-top: 14px;
|
| + a {
|
| + color: white;
|
| + }
|
| + .center {
|
| + vertical-align: middle;
|
| }
|
| </style>
|
|
|
| @@ -10241,10 +14483,10 @@ You can bind to `isAuthorized` property to monitor authorization state.
|
| </template>
|
|
|
| <template is="dom-if" if="[[signedIn]]">
|
| - <img id="avatar" src="[[profile.imageUrl]]" width="30" height="30">
|
| - <span>[[profile.email]]</span>
|
| - <span>|</span>
|
| - <a on-tap="signOut" href="#">Sign out</a>
|
| + <img class="center" id="avatar" src="[[profile.imageUrl]]" width="30" height="30">
|
| + <span class="center">[[profile.email]]</span>
|
| + <span class="center">|</span>
|
| + <a class="center" on-tap="signOut" href="#">Sign out</a>
|
| </template>
|
| </template>
|
| <script>
|
| @@ -10310,6 +14552,74 @@ You can bind to `isAuthorized` property to monitor authorization state.
|
| }
|
| });
|
| </script>
|
| +</dom-module><dom-module id="swarming-app" assetpath="imp/common/">
|
| + <template>
|
| + <style include="iron-flex">
|
| + :host {
|
| + position: absolute;
|
| + top: 0;
|
| + bottom: 0;
|
| + left: 0;
|
| + right: 0;
|
| + }
|
| +
|
| + app-toolbar {
|
| + background-color: #4285f4;
|
| + color: #fff;
|
| + }
|
| +
|
| + app-toolbar a {
|
| + color: #fff;
|
| + }
|
| + .left {
|
| + margin-right:15px;
|
| + }
|
| + .right {
|
| + margin-left:15px;
|
| + }
|
| +
|
| + paper-spinner-lite {
|
| + --paper-spinner-color: var(--google-yellow-500);
|
| + }
|
| + </style>
|
| + <app-header-layout>
|
| + <app-header fixed="">
|
| + <app-toolbar>
|
| + <div class="title left">[[name]]</div>
|
| + <paper-spinner-lite class="left" active="[[busy]]"></paper-spinner-lite>
|
| +
|
| + <a class="left" href="/newui/">Home</a>
|
| + <a class="left" href="/newui/botlist">Bot List</a>
|
| + <div class="flex"></div>
|
| +
|
| + <auth-signin class="right" client-id="20770472288-t5smpbpjptka4nd888fv0ctd23ftba2o.apps.googleusercontent.com" auth-headers="{{auth_headers}}">
|
| + </auth-signin>
|
| + </app-toolbar>
|
| + </app-header>
|
| + <div on-swarming-busy-start="_busyStart()" on-swarming-busy-end="_busyEnd()">
|
| + <content></content>
|
| + </div>
|
| + </app-header-layout>
|
| +
|
| + </template>
|
| + <script>
|
| + Polymer({
|
| + is: 'swarming-app',
|
| + properties: {
|
| + auth_headers: {
|
| + type: Object,
|
| + notify: true,
|
| + },
|
| + busy: {
|
| + type: Boolean,
|
| + },
|
| + name: {
|
| + type: String,
|
| + },
|
| + },
|
| +
|
| + });
|
| + </script>
|
| </dom-module><dom-module id="swarming-index" assetpath="imp/index/">
|
| <template>
|
| <style>
|
| @@ -10318,27 +14628,37 @@ You can bind to `isAuthorized` property to monitor authorization state.
|
| }
|
| </style>
|
|
|
| - <h1>HELLO WORLD</h1><h1>
|
| + <swarming-app auth_headers="{{auth_headers}}" name="Swarming" busy="[[busy]]">
|
|
|
| -
|
| - <auth-signin auth-headers="{{auth}}" client-id="20770472288-t5smpbpjptka4nd888fv0ctd23ftba2o.apps.googleusercontent.com" on-auth-signin="signIn">
|
| - </auth-signin>
|
| + <iron-ajax id="request" url="/_ah/api/swarming/v1/server/details" headers="[[auth_headers]]" handle-as="json" last-response="{{serverDetails}}" loading="{{busy}}">
|
| + </iron-ajax>
|
|
|
| - <iron-ajax id="request" url="/_ah/api/swarming/v1/server/details" headers="[[auth]]" handle-as="json" on-response="handleResponse">
|
| - </iron-ajax>
|
| + <h1>HELLO WORLD</h1>
|
|
|
| - </h1></template>
|
| + <div>Server Version: [[serverDetails.server_version]]</div>
|
| +
|
| + </swarming-app>
|
| +
|
| + </template>
|
| <script>
|
| Polymer({
|
| is: 'swarming-index',
|
|
|
| + properties: {
|
| + auth_headers: {
|
| + type: Object,
|
| + observer: "signIn",
|
| + },
|
| +
|
| + serverDetails: {
|
| + type: String,
|
| + }
|
| + },
|
| +
|
| signIn: function(){
|
| this.$.request.generateRequest();
|
| },
|
|
|
| - handleResponse: function(a,b){
|
| - console.log(this.$.request.lastResponse);
|
| - }
|
| });
|
| </script>
|
| </dom-module></div>
|
|
|