snapdrop

A Progressive Web App for local file sharing
git clone http://git.hanabi.in/repos/snapdrop.git
Log | Files | Refs | README | LICENSE

commit 663db5cbb39ab804b20f9cb6466effd9ed0e2d0c
parent 81252d301c603911c93659ced96542d1ead6e1a9
Author: RobinLinus <robinlinus@users.noreply.github.com>
Date:   Fri, 21 Sep 2018 16:05:03 +0200

Version 2 initial commit

Diffstat:
D.bowerrc | 3---
D.editorconfig | 21---------------------
D.gitattributes | 2--
M.gitignore | 5++---
D.jscsrc | 8--------
D.jshintrc | 25-------------------------
DLICENSE.md | 20--------------------
Dapp/cache-config.json | 4----
Dapp/elements/buddy-finder/buddy-avatar.html | 168-------------------------------------------------------------------------------
Dapp/elements/buddy-finder/buddy-finder.html | 186-------------------------------------------------------------------------------
Dapp/elements/buddy-finder/device-name-dialog.html | 51---------------------------------------------------
Dapp/elements/buddy-finder/device-name.html | 99-------------------------------------------------------------------------------
Dapp/elements/buddy-finder/personal-avatar.html | 85-------------------------------------------------------------------------------
Dapp/elements/donate-dialog/donate-dialog.html | 95-------------------------------------------------------------------------------
Dapp/elements/elements.html | 16----------------
Dapp/elements/file-sharing/file-button-behavior.html | 37-------------------------------------
Dapp/elements/file-sharing/file-button.html | 14--------------
Dapp/elements/file-sharing/file-drop-behavior.html | 48------------------------------------------------
Dapp/elements/file-sharing/file-input-behavior.html | 7-------
Dapp/elements/file-sharing/file-receiver.html | 100-------------------------------------------------------------------------------
Dapp/elements/file-sharing/file-selection-behavior.html | 21---------------------
Dapp/elements/invitation-link/invitation-link-behavior.html | 45---------------------------------------------
Dapp/elements/invitation-link/invitation-link.html | 29-----------------------------
Dapp/elements/p2p-network/binaryjs.html | 1346-------------------------------------------------------------------------------
Dapp/elements/p2p-network/connection-wrapper.html | 59-----------------------------------------------------------
Dapp/elements/p2p-network/file-transfer-protocol.html | 159-------------------------------------------------------------------------------
Dapp/elements/p2p-network/p2p-network.html | 171-------------------------------------------------------------------------------
Dapp/elements/p2p-network/tab-active.html | 25-------------------------
Dapp/elements/p2p-network/web-socket.html | 89-------------------------------------------------------------------------------
Dapp/elements/sound-notification/sound-notification-behavior.html | 24------------------------
Dapp/elements/sound-notification/sound-notification.html | 56--------------------------------------------------------
Dapp/elements/text-sharing/clipboard-behavior.html | 36------------------------------------
Dapp/elements/text-sharing/linkify.html | 6------
Dapp/elements/text-sharing/text-input-behavior.html | 48------------------------------------------------
Dapp/elements/text-sharing/text-input-dialog.html | 210-------------------------------------------------------------------------------
Dapp/elements/x-cards/about-page.html | 229-------------------------------------------------------------------------------
Dapp/elements/x-cards/settings-page.html | 86-------------------------------------------------------------------------------
Dapp/elements/x-cards/x-cards.html | 105-------------------------------------------------------------------------------
Dapp/favicon.ico | 0
Dapp/images/touch/apple-touch-icon.png | 0
Dapp/images/touch/chrome-splashscreen-icon-384x384.png | 0
Dapp/images/touch/chrome-touch-icon-192x192.png | 0
Dapp/images/touch/icon-128x128.png | 0
Dapp/images/touch/logo.png | 0
Dapp/images/touch/ms-icon-144x144-precomposed.png | 0
Dapp/images/touch/ms-icon-144x144.png | 0
Dapp/images/touch/snapdrop-icon.png | 0
Dapp/index.html | 100-------------------------------------------------------------------------------
Dapp/manifest.json | 29-----------------------------
Dapp/robots.txt | 4----
Dapp/scripts/animated-bg.js | 65-----------------------------------------------------------------
Dapp/scripts/app.js | 47-----------------------------------------------
Dapp/styles/app-theme.html | 35-----------------------------------
Dapp/styles/icons.html | 64----------------------------------------------------------------
Dapp/styles/main.css | 16----------------
Dapp/sw-import.js | 1-
Dbower.json | 22----------------------
Aclient/images/android-chrome-192x192.png | 0
Aclient/images/android-chrome-512x512.png | 0
Aclient/images/apple-touch-icon.png | 0
Aclient/images/favicon-96x96.png | 0
Aclient/images/logo_blue_512x512.png | 0
Aclient/images/logo_transparent_128x128.png | 0
Aclient/images/logo_transparent_512x512.png | 0
Aclient/images/logo_transparent_white_512x512.png | 0
Aclient/images/logo_white_512x512.png | 0
Aclient/images/mstile-150x150.png | 0
Aclient/images/safari-pinned-tab.svg | 251+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aclient/images/snapdrop-graphics.sketch | 0
Aclient/images/twitter-stream.jpg | 0
Aclient/index.html | 201+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aclient/manifest.json | 29+++++++++++++++++++++++++++++
Aclient/scripts/network-v2.js | 35+++++++++++++++++++++++++++++++++++
Aclient/scripts/network.js | 476+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aclient/scripts/ui.js | 522+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rtest.txt -> client/service-worker.js | 0
Rapp/sounds/blop.mp3 -> client/sounds/blop.mp3 | 0
Rapp/sounds/blop.ogg -> client/sounds/blop.ogg | 0
Aclient/styles.css | 641+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ddist/index.js | 23-----------------------
Ddist/package.json | 14--------------
Ddist/public/elements/elements.html | 22671-------------------------------------------------------------------------------
Ddist/public/index.html | 18------------------
Ddist/public/manifest.json | 29-----------------------------
Ddist/public/scripts/app.js | 2--
Ddist/public/sounds/blop.mp3 | 0
Ddist/public/sounds/blop.ogg | 0
Ddist/public/styles/main.css | 2--
Ddist/readme.md | 6------
Ddist/server/ws-server.js | 154-------------------------------------------------------------------------------
Dgulpfile.js | 359-------------------------------------------------------------------------------
Dindex.js | 23-----------------------
Anginx.conf.example | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dpackage.json | 54------------------------------------------------------
Aserver/index.js | 218+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aserver/package-lock.json | 26++++++++++++++++++++++++++
Aserver/package.json | 15+++++++++++++++
Dserver/ws-server.js | 154-------------------------------------------------------------------------------
Dtasks/ensure-files.js | 34----------------------------------
Dwct.conf.js | 18------------------
100 files changed, 2476 insertions(+), 27680 deletions(-)

diff --git a/.bowerrc b/.bowerrc @@ -1,3 +0,0 @@ -{ - "directory": "app/bower_components" -} diff --git a/.editorconfig b/.editorconfig @@ -1,21 +0,0 @@ -# EditorConfig helps developers define and maintain consistent -# coding styles between different editors and IDEs -# editorconfig.org - -root = true - - -[*] - -# Change these settings to your own preference -indent_style = space -indent_size = 2 - -# We recommend you to keep these unchanged -end_of_line = lf -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true - -[*.md] -trim_trailing_whitespace = false diff --git a/.gitattributes b/.gitattributes @@ -1 +0,0 @@ -* text=auto -\ No newline at end of file diff --git a/.gitignore b/.gitignore @@ -1,4 +1,2 @@ node_modules -bower_components -.tmp -.publish/ +.DS_Store +\ No newline at end of file diff --git a/.jscsrc b/.jscsrc @@ -1,8 +0,0 @@ -{ - "preset": "google", - "disallowSpacesInAnonymousFunctionExpression": null, - "disallowTrailingWhitespace": null, - "validateIndentation": null, - "maximumLineLength": 100, - "excludeFiles": ["node_modules/**"] -} diff --git a/.jshintrc b/.jshintrc @@ -1,25 +0,0 @@ -{ - "node": true, - "browser": true, - "bitwise": true, - "camelcase": true, - "curly": true, - "eqeqeq": true, - "immed": true, - "indent": 2, - "latedef": true, - "noarg": true, - "quotmark": "single", - "undef": true, - "unused": true, - "newcap": false, - "globals": { - "wrap": true, - "unwrap": true, - "Polymer": true, - "Platform": true, - "page": true, - "app": true, - "Chat": true - } -} diff --git a/LICENSE.md b/LICENSE.md @@ -1,19 +0,0 @@ -# License - -Everything in this repo is BSD style license unless otherwise specified. - -Copyright (c) 2015 Robin Linus. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. -* Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. -* Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -\ No newline at end of file diff --git a/app/cache-config.json b/app/cache-config.json @@ -1,4 +0,0 @@ -{ - "README": "This is the cache config for the dev server. The service worker cache is disabled, and it is recommended that you leave this as-is. In the dist environment, this file will be auto-generated based on the contents of your dist/ directory.", - "disabled": true -} diff --git a/app/elements/buddy-finder/buddy-avatar.html b/app/elements/buddy-finder/buddy-avatar.html @@ -1,168 +0,0 @@ -<link rel="import" href="../../bower_components/paper-icon-button/paper-icon-button.html"> -<link rel="import" href="../file-sharing/file-input-behavior.html"> -<link rel="import" href="../text-sharing/text-input-behavior.html"> -<dom-module id="buddy-avatar"> - <template> - <style> - :host { - display: block; - @apply(--layout-vertical); - @apply(--layout-center-center); - width: 120px; - height: 124px; - cursor: pointer; - } - - paper-icon-button { - display: inline-block; - width: 64px !important; - height: 64px !important; - border-radius: 50%; - overflow: hidden; - padding: 12px; - margin-bottom: 6px; - background-color: #4285f4; - color: white; - } - - :host:hover paper-icon-button { - transform: scale(1.05); - } - - .paper-font-subhead { - text-align: center; - margin-top: 0px; - line-height: 18px; - } - - .paper-font-body1 { - text-align: center; - width: 100%; - font-size: 13px; - color: grey; - margin-top: 0px !important; - line-height: 16px; - } - - :host, - .paper-font-subhead, - .paper-font-body1 { - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - width: 120px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - - @media all and (min-height: 440px) { - :host([only]) { - padding: 20vh; - } - } - - .container { - @apply(--layout-vertical); - @apply(--layout-center); - height: 112px; - padding-top: 16px; - display: block; - } - </style> - <div class="container"> - <paper-icon-button icon="{{_displayIcon}}"></paper-icon-button> - <div class="paper-font-subhead">{{_displayName}}</div> - <div class="paper-font-body1">{{status}}</div> - </div> - </template> - <script> - 'use strict'; - Polymer({ - is: 'buddy-avatar', - behaviors: [Snapdrop.FileInputBehavior, Snapdrop.TextInputBehavior], - properties: { - contact: Object, - _displayName: { - computed: '_computeDisplayName(contact)' - }, - _displayIcon: { - computed: '_computeDisplayIcon(contact)' - }, - status: { - type: String, - value: '', - }, - defaultStatus: { - computed: '_computeDefaultStatus(contact)' - } - }, - _computeDisplayName: function(contact) { - if (!contact.name.os) { - return contact.name; - } - return this._computeDeviceName(contact.name); - }, - _computeDeviceName: function(contact) { - if (contact.model) { - return contact.os + ' ' + contact.model; - } - contact.os = contact.os.replace('Mac OS', 'Mac'); - return contact.os + ' ' + contact.browser; - }, - _computeDisplayIcon: function(contact) { - contact = contact.device || contact.name; - if (contact.type === 'mobile') { - return 'chat:phone-iphone'; - } - if (contact.type === 'tablet') { - return 'chat:tablet-mac'; - } - return 'chat:desktop-mac'; - }, - _computeDefaultStatus: function(contact) { - var status = contact.device ? this._computeDeviceName(contact.device) : ''; - this.status = status; - return status; - }, - attached: function() { - this.async(function() { - app.conn.addEventListener('file-offered', function(e) { - if (e.detail.to === this.contact.peerId) { - this.status = 'Waiting to accept...'; - } - }.bind(this), false); - app.conn.addEventListener('upload-started', function(e) { - if (e.detail.to === this.contact.peerId) { - this.status = 'Uploading...'; - } - }.bind(this), false); - app.conn.addEventListener('download-started', function(e) { - if (e.detail.from === this.contact.peerId) { - this.status = 'Downloading...'; - } - }.bind(this), false); - app.conn.addEventListener('upload-complete', function(e) { - if (e.detail.from === this.contact.peerId) { - this.status = this.defaultStatus; - } - }.bind(this), false); - app.conn.addEventListener('download-complete', function(e) { - if (e.detail.from === this.contact.peerId) { - this.status = this.defaultStatus; - } - }.bind(this), false); - app.conn.addEventListener('file-declined', function(e) { - if (e.detail.from === this.contact.peerId) { - this.status = this.defaultStatus; - } - }.bind(this), false); - app.conn.addEventListener('upload-error', function(e) { - this.status = this.defaultStatus; - }.bind(this), false); - }, 200); - } - }); - </script> -</dom-module> diff --git a/app/elements/buddy-finder/buddy-finder.html b/app/elements/buddy-finder/buddy-finder.html @@ -1,186 +0,0 @@ -<link rel="import" href="../../bower_components/iron-ajax/iron-ajax.html"> -<link rel="import" href="../../bower_components/paper-styles/paper-styles.html"> -<link rel="import" href="buddy-avatar.html"> -<link rel="import" href="personal-avatar.html"> -<dom-module id="buddy-finder"> - <template> - <style> - :host { - background-color: transparent; - @apply(--layout-fit); - @apply(--layout-horizontal); - @apply(--layout-center-center); - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - margin: 0; - --paper-tooltip: { - font-size: 14px; - background-color: #4285f4; - } - } - - .buddies { - z-index: 1; - @apply(--layout-horizontal); - @apply(--layout-center-center); - @apply(--layout-wrap); - } - - .explanation { - @apply(--paper-font-headline); - color: #4285f4; - text-align: center; - cursor: default; - } - - .short { - font-size: 14px; - line-height: 20px; - color: #727272; - } - - .short a { - color: #4285f4; - text-decoration: none; - } - - .explanation:hover a { - transform: scale(1.1); - } - - @media all and (max-width: 600px) { - .explanation { - width: 340px; - } - } - - @media all and (max-height: 440px) { - .buddies { - padding-top: 56px; - @apply(--layout-self-start); - } - } - - @media all and (max-height: 600px) { - .buddies[two-lines] { - padding-top: 56px; - @apply(--layout-self-start); - } - } - - .explanation2 { - display: none; - position: absolute; - width: 296px; - margin-left: -148px; - left: 50%; - @apply(--paper-font-title); - color: rgba(66, 133, 244, 0.65); - text-align: center; - opacity: 0; - -webkit-transition: opacity 500ms ease-out; - -moz-transition: opacity 500ms ease-out; - -o-transition: opacity 500ms ease-out; - transition: opacity 500ms ease-out; - z-index: 0; - } - - @media all and (min-height: 441px) and (max-height: 559px) { - .explanation2 { - display: block; - top: 64px; - opacity: 1; - } - } - - @media all and (min-height: 560px) { - .explanation2 { - display: block; - top: 128px; - opacity: 1; - } - } - </style> - <div class="explanation2" hidden$="{{!_showExplanation}}"> - {{_clickExplanation1}} Device to send File. - <br> {{_clickExplanation2}} to send a Text. - </div> - <div class="buddies" two-lines$="{{twoLinesOfBuddies}}"> - <template is="dom-repeat" items="{{buddies}}"> - <buddy-avatar on-file-selected="_fileSelected" only$="{{!buddies.1}}" contact="{{item}}"></buddy-avatar> - </template> - </div> - <div hidden$="{{buddies.0}}" class="explanation"> - Open Snapdrop on other devices to send files. - <div class="short"> - Short link: <a href="http://yg.gl" target="_blank">yg.gl</a> - </div> - </div> - <personal-avatar class="me"></personal-avatar> - </template> - <script> - 'use strict'; - (function() { - var isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); - Polymer({ - is: 'buddy-finder', - properties: { - buddies: { - type: Array, - notify: true - }, - me: { - type: String, - }, - _showExplanation: { - computed: '_computeShowExplanation(buddies.length)', - value: false - }, - twoLinesOfBuddies: { - value: false - }, - _clickExplanation1: { - value: (function() { - if (isMobile) { - return 'Tap'; - } else { - return 'Click'; - } - }) - }, - _clickExplanation2: { - value: (function() { - if (isMobile) { - return 'Long Press'; - } else { - return 'Right Click'; - } - }) - } - }, - observers: [ - '_buddiesChanged(buddies.splices)' - ], - _fileSelected: function(e) { - var peerId = e.model.item.peerId; - var file = e.detail; - app.conn.sendFile(peerId, file); - }, - _computeShowExplanation: function(nBuddies) { - if (!nBuddies || nBuddies === 0) { - return false; - } - if (nBuddies < 3) { - return true; - } - }, - _buddiesChanged: function() { - var length = window.innerWidth / 120; - this.set('twoLinesOfBuddies', (this.buddies.length > length)); - } - }); - }()); - </script> -</dom-module> diff --git a/app/elements/buddy-finder/device-name-dialog.html b/app/elements/buddy-finder/device-name-dialog.html @@ -1,51 +0,0 @@ -<link rel="import" href="../../bower_components/paper-dialog/paper-dialog.html"> -<link rel="import" href="../../bower_components/paper-input/paper-input.html"> -<dom-module id="device-name-dialog"> - <template> - <style> - :host { - display: block; - } - - paper-dialog { - width: 400px; - max-width: 90% - } - </style> - <paper-dialog id="dialog" entry-animation="scale-up-animation" exit-animation="fade-out-animation" with-backdrop> - <h2>Name this Device</h2> - <p> - <paper-input id="input" value="{{deviceName}}" label="Name this Device" char-counter maxlength="18" on-keypress="_keyPressed" autofocus></paper-input> - </p> - <div class="buttons"> - <paper-button dialog-dismiss>Cancel</paper-button> - <paper-button on-tap="_save">Rename</paper-button> - </div> - </paper-dialog> - </template> - <script> - 'use strict'; - Polymer({ - is: 'device-name-dialog', - properties: { - deviceName: { - notify: true - } - }, - open: function() { - this.$.dialog.open(); - }, - _keyPressed: function(e) { - if (e.which === 13 || e.charCode === 13) { - this.$.input.inputElement.blur(); - this._save(); - } - }, - _save: function() { - this.$.dialog.close(); - this.fire('save-device-name', this.deviceName); - } - - }); - </script> -</dom-module> diff --git a/app/elements/buddy-finder/device-name.html b/app/elements/buddy-finder/device-name.html @@ -1,99 +0,0 @@ -<link rel="import" href="../../bower_components/paper-input/paper-input.html"> -<link rel="import" href="../../bower_components/iron-localstorage/iron-localstorage.html"> -<link rel="import" href="device-name-dialog.html"> -<dom-module id="device-name"> - <template> - <style> - :host { - display: inline-block; - cursor: pointer; - } - - .name-label { - @apply(--paper-font-subhead); - text-align: center; - cursor: pointer; - width: 160px; - line-height: 12px !important; - } - - :root { - /* Label and underline color when the input is not focused */ - --paper-input-container-color: #333; - /* Label and underline color when the input is focused */ - --paper-input-container-focus-color: #4285f4; - /* Label and underline color when the input is invalid */ - --paper-input-container-invalid-color: red; - /* Input foreground color */ - --paper-input-container-input-color: #333; - } - - @media all and (max-height: 370px) { - :host {} - } - - paper-dialog { - width: 300px; - text-align: left; - } - </style> - <div class="name-label" hidden$="{{name}}">My Device</div> - <div class="name-label" hidden$="{{!name}}">{{name}}</div> - <iron-localstorage name="device-name" value="{{name}}" iron-localstorage-load="_nameChanged"></iron-localstorage> - </template> - <script> - 'use strict'; - Polymer({ - is: 'device-name', - properties: { - name: { - observer: '_nameChanged' - } - }, - open: function() { - this.deviceNameDialog.open(); - }, - _nameChanged: function(name) { - if (!name) { - return; - } - this.cancelAsync(this.timer); - this.timer = this.async(function() { - if (!app.conn.notifyServer) { - this._nameChanged(name); - return; - } - try { - this._sendNameToServer(name); - } catch (e) { - this._nameChanged(name); - } - - }, 300); - - }, - _sendNameToServer: function(name) { - app.conn.notifyServer({ - serverMsg: 'device-name', - name: name - }); - }, - _initialize: function() { - console.log('initialize name'); - }, - get deviceNameDialog() { - var deviceNameDialog = document.querySelector('device-name-dialog'); - if (!deviceNameDialog) { - deviceNameDialog = Polymer.Base.create('device-name-dialog'); - deviceNameDialog.addEventListener('save-device-name', function(e) { - this.name = e.detail; - console.log(this.name); - }.bind(this)); - document.body.appendChild(deviceNameDialog); - }; - deviceNameDialog.deviceName = this.name; - return deviceNameDialog; - } - }); - </script> -</dom-module> diff --git a/app/elements/buddy-finder/personal-avatar.html b/app/elements/buddy-finder/personal-avatar.html @@ -1,85 +0,0 @@ -<link rel="import" href="../../bower_components/iron-icon/iron-icon.html"> -<link rel="import" href="../../styles/icons.html"> -<link rel="import" href="device-name.html"> -<dom-module id="personal-avatar"> - <template> - <style> - :host { - @apply(--layout-vertical); - @apply(--layout-center); - width: 360px; - position: absolute; - bottom: 16px; - left: 50%; - margin-left: -180px; - z-index: 12; - cursor: pointer; - } - - :host:hover iron-icon, - :host:hover device-name { - color: #3367d6; - } - - :host:hover iron-icon { - transform: scale(1.05); - } - - iron-icon { - width: 80px; - height: 80px; - color: #4285f4; - margin-bottom: 6px; - } - - .paper-font-body1 { - font-size: 13px; - } - - .discover { - color: #4285f4; - line-height: 18px; - } - - @media all and (max-width: 370px) { - .discover { - font-size: 12px; - } - } - - @media all and (max-height: 380px) { - :host { - bottom: 4px; - } - iron-icon { - width: 32px; - height: 32px; - margin-bottom: 2px; - } - .slogan { - display: none; - } - } - </style> - <device-name id="deviceName"></device-name> - <iron-icon icon="chat:wifi-tethering"></iron-icon> - <div class="paper-font-body1 slogan"> - The easiest way to transfer data across devices. - </div> - <div class="paper-font-body1 discover"> - Allow me to be discovered by: Everyone in this network. - </div> - </template> - <script> - 'use strict'; - Polymer({ - is: 'personal-avatar', - listeners: { - 'tap': '_openDeviceNameDialog' - }, - _openDeviceNameDialog: function() { - this.$.deviceName.open(); - } - }); - </script> -</dom-module> diff --git a/app/elements/donate-dialog/donate-dialog.html b/app/elements/donate-dialog/donate-dialog.html @@ -1,95 +0,0 @@ -<link rel="import" href="../../bower_components/paper-dialog/paper-dialog.html"> -<dom-module id="donate-dialog-impl"> - <template> - <style> - :host { - display: block; - } - - paper-dialog { - max-width: 324px; - } - - iron-icon { - width: 72px; - height: 72px; - display: block; - margin: 0 auto; - margin-bottom: 4px; - color: #4285f4; - } - - p { - padding: 0 48px; - margin: 4px; - @apply(--paper-font-subhead); - } - - paper-dialog > a { - text-decoration: none; - color: #52524F; - text-align: center; - } - - @media screen and (max-width: 500px) { - paper-dialog { - margin: 24px - } - } - </style> - <paper-dialog id="dialog" with-backdrop entry-animation="scale-up-animation" exit-animation="fade-out-animation" modal> - <h2>Do you like Snapdrop?</h2> - <a href="https://www.paypal.com/cgi-bin/webscr?cmd=_donations&cmd=_donations&business=robin@capira.de&currency_code=USD&item_name=Snapdrop&shipping=0" target="_blank" dialog-confirm> - <p> - <iron-icon icon="chat:local-cafe"></iron-icon> - <span>Donate a cup of Coffee <br>to the Developers!</span> - </p> - </a> - <div class="buttons"> - <paper-button dialog-dismiss>No, thanks.</paper-button> - <a href="https://www.paypal.com/cgi-bin/webscr?cmd=_donations&cmd=_donations&business=robin@capira.de&currency_code=USD&item_name=Snapdrop&shipping=0" target="_blank"> - <paper-button dialog-confirm>Donate</paper-button> - </a> - </div> - </paper-dialog> - </template> - <script> - 'use strict'; - Polymer({ - is: 'donate-dialog-impl', - open: function() { - this.$.dialog.open(); - } - }); - </script> -</dom-module> -<dom-module id="donate-dialog"> - <script> - 'use strict'; - Polymer({ - is: 'donate-dialog', - properties: { - chance: { - value: 1 / 6 - } - }, - attached: function() { - window.donateDialog = this; - }, - get dialog() { - var dialog = document.querySelector('donate-dialog-impl'); - if (!dialog) { - dialog = Polymer.Base.create('donate-dialog-impl'); - document.body.appendChild(dialog); - } - return dialog; - }, - open: function() { - var chance = Math.random() <= this.chance; - if (chance) { - this.dialog.open(); - } - } - }); - </script> -</dom-module> diff --git a/app/elements/elements.html b/app/elements/elements.html @@ -1,15 +0,0 @@ -<link rel="import" href="../bower_components/platinum-sw/platinum-sw-cache.html"> -<link rel="import" href="../bower_components/platinum-sw/platinum-sw-register.html"> -<link rel="import" href="../bower_components/paper-toast/paper-toast.html"> -<link rel="import" href="../bower_components/paper-progress/paper-progress.html"> -<link rel="import" href="../bower_components/neon-animation/neon-animated-pages.html"> - -<!-- Add your elements here --> -<link rel="import" href="../styles/app-theme.html"> -<link rel="import" href="x-cards/about-page.html"> -<link rel="import" href="x-cards/x-cards.html"> -<link rel="import" href="buddy-finder/buddy-finder.html"> -<link rel="import" href="p2p-network/connection-wrapper.html"> -<link rel="import" href="file-sharing/file-receiver.html"> -<link rel="import" href="donate-dialog/donate-dialog.html"> - -\ No newline at end of file diff --git a/app/elements/file-sharing/file-button-behavior.html b/app/elements/file-sharing/file-button-behavior.html @@ -1,37 +0,0 @@ -<link rel="import" href="file-selection-behavior.html"> -<script> -'use strict'; -window.Snapdrop = window.Snapdrop || {}; -Snapdrop.FileButtonBehaviorImpl = { - get fileInput() { - var fileInput = Polymer.dom(this).querySelector('.fileInput'); - if (!fileInput) { - fileInput = document.createElement('input'); - fileInput.type = 'file'; - fileInput.multiple = 'false'; - fileInput.className = 'fileInput'; - fileInput.style.position = 'fixed'; - fileInput.style.top = '-10000px'; - fileInput.style.left = '-10000px'; - fileInput.style.opacity = 0; - Polymer.dom(this).appendChild(fileInput); - } - return fileInput; - }, - attached: function() { - this.fileInput.onchange = function() { - var files = this.fileInput.files; - this.notifyFilesSelection(files); - }.bind(this); - this.addEventListener('click', function(e) { - var button = e.which || e.button; - if (button !== 1) { - return; - } - this.fileInput.value = null; - this.fileInput.click(); - }.bind(this), false); - } -}; -Snapdrop.FileButtonBehavior = [Snapdrop.FileButtonBehaviorImpl, Snapdrop.FileSelectionBehavior]; -</script> diff --git a/app/elements/file-sharing/file-button.html b/app/elements/file-sharing/file-button.html @@ -1,14 +0,0 @@ -<link rel="import" href="../../bower_components/paper-icon-button/paper-icon-button.html"> -<link rel="import" href="file-button-behavior.html"> -<dom-module id="file-button"> - <template> - <paper-icon-button id="btn" icon="chat:attach-file" on-tap="_upload"></paper-icon-button> - </template> - <script> - 'use strict'; - Polymer({ - is: 'file-button', - behaviors: [Snapdrop.FileButtonBehavior] - }); - </script> -</dom-module> diff --git a/app/elements/file-sharing/file-drop-behavior.html b/app/elements/file-sharing/file-drop-behavior.html @@ -1,48 +0,0 @@ -<link rel="import" href="file-selection-behavior.html"> -<script> -'use strict'; -window.Snapdrop = window.Snapdrop || {}; -Snapdrop.FileDropBehaviorImpl = { - attached: function() { - var dropZone = this; - - dropZone.addEventListener('dragover', function(e) { - e.stopPropagation(); - e.preventDefault(); - e.dataTransfer.dropEffect = 'copy'; - dropZone.style.transform = 'scale(1.2)'; - }, false); - - var dragEnd = function() { - dropZone.style.transform = 'scale(1)'; - }; - - dropZone.addEventListener('dragleave', dragEnd, false); - dropZone.addEventListener('dragexit', dragEnd, false); - dropZone.addEventListener('dragend', dragEnd, false); - - // Get file data on drop - dropZone.addEventListener('drop', function(event) { - event.stopPropagation(); - event.preventDefault(); - - //drop is a dragend - dragEnd(); - - // Get files - var files = event.dataTransfer.files; - // Notify Selection - this.notifyFilesSelection(files); - }); - } -}; -document.body.addEventListener('dragover', function(e) { - e.stopPropagation(); - e.preventDefault(); -}, false); -document.body.addEventListener('drop', function(event) { - event.stopPropagation(); - event.preventDefault(); -}); -Snapdrop.FileDropBehavior = [Snapdrop.FileDropBehaviorImpl, Snapdrop.FileSelectionBehavior]; -</script> diff --git a/app/elements/file-sharing/file-input-behavior.html b/app/elements/file-sharing/file-input-behavior.html @@ -1,6 +0,0 @@ -<link rel="import" href="file-drop-behavior.html"> -<link rel="import" href="file-button-behavior.html"> -<script> - 'use strict'; - Snapdrop.FileInputBehavior = [Snapdrop.FileDropBehavior,Snapdrop.FileButtonBehavior]; -</script> -\ No newline at end of file diff --git a/app/elements/file-sharing/file-receiver.html b/app/elements/file-sharing/file-receiver.html @@ -1,100 +0,0 @@ -<link rel="import" href="../../bower_components/paper-dialog/paper-dialog.html"> -<link rel="import" href="../../bower_components/paper-button/paper-button.html"> -<link rel="import" href="../../bower_components/neon-animation/animations/scale-up-animation.html"> -<link rel="import" href="../../bower_components/neon-animation/animations/fade-out-animation.html"> -<link rel="import" href="../../bower_components/iron-pages/iron-pages.html"> -<link rel="import" href="../../bower_components/paper-spinner/paper-spinner.html"> -<link rel="import" href="../sound-notification/sound-notification-behavior.html"> -<dom-module id="file-receiver"> - <template> - <style> - :host { - display: block; - } - - #dialog, - #download { - width: 324px; - z-index: 101; - margin: 16px; - } - - .filename { - word-break: break-all; - word-break: break-word; - } - </style> - <paper-dialog id="dialog" entry-animation="scale-up-animation" exit-animation="fade-out-animation" with-backdrop modal> - <h2>Download File</h2> - <p><b class="filename">{{file.name}}</b></p> - <div class="buttons"> - <paper-button dialog-dismiss on-tap="_decline">Ignore</paper-button> - <paper-button dialog-confirm on-tap="_accept" autofocus>Download</paper-button> - </div> - </paper-dialog> - <paper-dialog id="download" entry-animation="scale-up-animation" exit-animation="fade-out-animation" with-backdrop modal> - <h2>File Received</h2> - <p>Open File or Right Click and "Save as"...</p> - <div class="buttons"> - <paper-button dialog-dismiss>Discard</paper-button> - <a href="{{dataUri}}" target="_blank"> - <paper-button dialog-confirm autofocus>Open File</paper-button> - </a> - </div> - </paper-dialog> - </template> - <script> - 'use strict'; - (function() { - Polymer({ - is: 'file-receiver', - behaviors: [Snapdrop.SoundNotificationBehavior], - attached: function() { - this.async(function() { - app.conn.addEventListener('file-offer', function(e) { - this.file = e.detail; - this.$.dialog.open(); - this.playSound(); - }.bind(this), false); - app.conn.addEventListener('file-received', function(e) { - this._fileReceived(e.detail); - }.bind(this), false); - app.conn.addEventListener('file-declined', function(e) { - app.displayToast('User declined file ' + e.detail.name); - }.bind(this), false); - app.conn.addEventListener('upload-complete', function(e) { - app.displayToast('User received file ' + e.detail.name); - }.bind(this), false); - app.conn.addEventListener('upload-error', function(e) { - app.displayToast('The other device did not respond. Please try again.'); - }.bind(this), false); - }, 200); - }, - _fileReceived: function(file) { - this.downloadURI(file); - }, - _decline: function() { - app.conn.decline(this.file); - }, - _accept: function() { - app.conn.accept(this.file); - }, - downloadURI: function(file) { - var link = document.createElement('a'); - var uri = (window.URL || window.webkitURL).createObjectURL(file.blob); - if (typeof link.download !== 'undefined') { - //download attribute is supported - link.href = uri; - link.download = file.name || 'blank'; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - } else { - this.dataUri = uri; - this.$.download.open(); - } - } - }); - }()); - </script> -</dom-module> diff --git a/app/elements/file-sharing/file-selection-behavior.html b/app/elements/file-sharing/file-selection-behavior.html @@ -1,21 +0,0 @@ -<script> -'use strict'; -window.Snapdrop = window.Snapdrop || {}; -Snapdrop.FileSelectionBehavior = { - notifyFilesSelection: function(files) { - if (!files) { - console.log('no files selected...'); - return; - } - this._fileSelected(files[0]); //single select - }, - _fileSelected: function(file) { - if (file) { - this.fire('file-selected', { - file: file, - name: file.name - }); - } - } -}; -</script> diff --git a/app/elements/invitation-link/invitation-link-behavior.html b/app/elements/invitation-link/invitation-link-behavior.html @@ -1,45 +0,0 @@ -<script> -'use strict'; -window.Snapdrop = window.Snapdrop || {}; -Snapdrop.InvitationLinkBehavior = { - properties: { - contact: { - type: String - } - }, - _copy: function(e) { - if (e) { - e.preventDefault(); - e.stopPropagation(); - } - Polymer.Base.create('textarea'); - var copyTextarea = this.textarea; - copyTextarea.value = this.link; - copyTextarea.select(); - try { - var successful = document.execCommand('copy'); - if (successful) { - app.displayToast('Copied invitation link to clipboard. Share it to send files to friends!'); - } - } catch (err) { - console.log('Oops, unable to copy', err); - } - copyTextarea.blur(); - }, - get link() { - return 'http://' + window.location.host + '/' + this.contact; - }, - get textarea() { - var textarea = document.querySelector('#copytextarea'); - if (!textarea) { - textarea = Polymer.Base.create('textarea'); - textarea.id = 'copytextarea'; - var style = textarea.style; - style.position = 'absolute'; - style.top = '-10000px'; - document.body.appendChild(textarea); - } - return textarea; - } -}; -</script> diff --git a/app/elements/invitation-link/invitation-link.html b/app/elements/invitation-link/invitation-link.html @@ -1,29 +0,0 @@ -<link rel="import" href="invitation-link-behavior.html"> -<link rel="import" href="../../bower_components/paper-tooltip/paper-tooltip.html"> -<dom-module id="invitation-link"> - <template> - <style> - :host { - display: block; - position: absolute; - top: 16px; - left: 16px; - z-index: 3; - } - </style> - <paper-icon-button icon="chat:share" on-tap="_copy" id="btn"></paper-icon-button> - <paper-tooltip - for="btn" - position="bottom" - offset="14"> - Get an Invitation Link to send files accross different networks. - </paper-tooltip> - </template> - <script> - 'use strict'; - Polymer({ - is: 'invitation-link', - behaviors: [Snapdrop.InvitationLinkBehavior] - }); - </script> -</dom-module> diff --git a/app/elements/p2p-network/binaryjs.html b/app/elements/p2p-network/binaryjs.html @@ -1,1345 +0,0 @@ -<script> - /*! binary.js build:0.2.2, development. Copyright(c) 2012 Eric Zhang <eric@ericzhang.com> MIT Licensed */ -(function(exports){ -/*! binarypack.js build:0.0.9, production. Copyright(c) 2012 Eric Zhang <eric@ericzhang.com> MIT Licensed */(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ -var BufferBuilder = require('./bufferbuilder').BufferBuilder; -var binaryFeatures = require('./bufferbuilder').binaryFeatures; - -var BinaryPack = { - unpack: function(data){ - var unpacker = new Unpacker(data); - return unpacker.unpack(); - }, - pack: function(data){ - var packer = new Packer(); - packer.pack(data); - var buffer = packer.getBuffer(); - return buffer; - } -}; - -module.exports = BinaryPack; - -function Unpacker (data){ - // Data is ArrayBuffer - this.index = 0; - this.dataBuffer = data; - this.dataView = new Uint8Array(this.dataBuffer); - this.length = this.dataBuffer.byteLength; -} - -Unpacker.prototype.unpack = function(){ - var type = this.unpack_uint8(); - if (type < 0x80){ - var positive_fixnum = type; - return positive_fixnum; - } else if ((type ^ 0xe0) < 0x20){ - var negative_fixnum = (type ^ 0xe0) - 0x20; - return negative_fixnum; - } - var size; - if ((size = type ^ 0xa0) <= 0x0f){ - return this.unpack_raw(size); - } else if ((size = type ^ 0xb0) <= 0x0f){ - return this.unpack_string(size); - } else if ((size = type ^ 0x90) <= 0x0f){ - return this.unpack_array(size); - } else if ((size = type ^ 0x80) <= 0x0f){ - return this.unpack_map(size); - } - switch(type){ - case 0xc0: - return null; - case 0xc1: - return undefined; - case 0xc2: - return false; - case 0xc3: - return true; - case 0xca: - return this.unpack_float(); - case 0xcb: - return this.unpack_double(); - case 0xcc: - return this.unpack_uint8(); - case 0xcd: - return this.unpack_uint16(); - case 0xce: - return this.unpack_uint32(); - case 0xcf: - return this.unpack_uint64(); - case 0xd0: - return this.unpack_int8(); - case 0xd1: - return this.unpack_int16(); - case 0xd2: - return this.unpack_int32(); - case 0xd3: - return this.unpack_int64(); - case 0xd4: - return undefined; - case 0xd5: - return undefined; - case 0xd6: - return undefined; - case 0xd7: - return undefined; - case 0xd8: - size = this.unpack_uint16(); - return this.unpack_string(size); - case 0xd9: - size = this.unpack_uint32(); - return this.unpack_string(size); - case 0xda: - size = this.unpack_uint16(); - return this.unpack_raw(size); - case 0xdb: - size = this.unpack_uint32(); - return this.unpack_raw(size); - case 0xdc: - size = this.unpack_uint16(); - return this.unpack_array(size); - case 0xdd: - size = this.unpack_uint32(); - return this.unpack_array(size); - case 0xde: - size = this.unpack_uint16(); - return this.unpack_map(size); - case 0xdf: - size = this.unpack_uint32(); - return this.unpack_map(size); - } -} - -Unpacker.prototype.unpack_uint8 = function(){ - var byte = this.dataView[this.index] & 0xff; - this.index++; - return byte; -}; - -Unpacker.prototype.unpack_uint16 = function(){ - var bytes = this.read(2); - var uint16 = - ((bytes[0] & 0xff) * 256) + (bytes[1] & 0xff); - this.index += 2; - return uint16; -} - -Unpacker.prototype.unpack_uint32 = function(){ - var bytes = this.read(4); - var uint32 = - ((bytes[0] * 256 + - bytes[1]) * 256 + - bytes[2]) * 256 + - bytes[3]; - this.index += 4; - return uint32; -} - -Unpacker.prototype.unpack_uint64 = function(){ - var bytes = this.read(8); - var uint64 = - ((((((bytes[0] * 256 + - bytes[1]) * 256 + - bytes[2]) * 256 + - bytes[3]) * 256 + - bytes[4]) * 256 + - bytes[5]) * 256 + - bytes[6]) * 256 + - bytes[7]; - this.index += 8; - return uint64; -} - - -Unpacker.prototype.unpack_int8 = function(){ - var uint8 = this.unpack_uint8(); - return (uint8 < 0x80 ) ? uint8 : uint8 - (1 << 8); -}; - -Unpacker.prototype.unpack_int16 = function(){ - var uint16 = this.unpack_uint16(); - return (uint16 < 0x8000 ) ? uint16 : uint16 - (1 << 16); -} - -Unpacker.prototype.unpack_int32 = function(){ - var uint32 = this.unpack_uint32(); - return (uint32 < Math.pow(2, 31) ) ? uint32 : - uint32 - Math.pow(2, 32); -} - -Unpacker.prototype.unpack_int64 = function(){ - var uint64 = this.unpack_uint64(); - return (uint64 < Math.pow(2, 63) ) ? uint64 : - uint64 - Math.pow(2, 64); -} - -Unpacker.prototype.unpack_raw = function(size){ - if ( this.length < this.index + size){ - throw new Error('BinaryPackFailure: index is out of range' - + ' ' + this.index + ' ' + size + ' ' + this.length); - } - var buf = this.dataBuffer.slice(this.index, this.index + size); - this.index += size; - - //buf = util.bufferToString(buf); - - return buf; -} - -Unpacker.prototype.unpack_string = function(size){ - var bytes = this.read(size); - var i = 0, str = '', c, code; - while(i < size){ - c = bytes[i]; - if ( c < 128){ - str += String.fromCharCode(c); - i++; - } else if ((c ^ 0xc0) < 32){ - code = ((c ^ 0xc0) << 6) | (bytes[i+1] & 63); - str += String.fromCharCode(code); - i += 2; - } else { - code = ((c & 15) << 12) | ((bytes[i+1] & 63) << 6) | - (bytes[i+2] & 63); - str += String.fromCharCode(code); - i += 3; - } - } - this.index += size; - return str; -} - -Unpacker.prototype.unpack_array = function(size){ - var objects = new Array(size); - for(var i = 0; i < size ; i++){ - objects[i] = this.unpack(); - } - return objects; -} - -Unpacker.prototype.unpack_map = function(size){ - var map = {}; - for(var i = 0; i < size ; i++){ - var key = this.unpack(); - var value = this.unpack(); - map[key] = value; - } - return map; -} - -Unpacker.prototype.unpack_float = function(){ - var uint32 = this.unpack_uint32(); - var sign = uint32 >> 31; - var exp = ((uint32 >> 23) & 0xff) - 127; - var fraction = ( uint32 & 0x7fffff ) | 0x800000; - return (sign == 0 ? 1 : -1) * - fraction * Math.pow(2, exp - 23); -} - -Unpacker.prototype.unpack_double = function(){ - var h32 = this.unpack_uint32(); - var l32 = this.unpack_uint32(); - var sign = h32 >> 31; - var exp = ((h32 >> 20) & 0x7ff) - 1023; - var hfrac = ( h32 & 0xfffff ) | 0x100000; - var frac = hfrac * Math.pow(2, exp - 20) + - l32 * Math.pow(2, exp - 52); - return (sign == 0 ? 1 : -1) * frac; -} - -Unpacker.prototype.read = function(length){ - var j = this.index; - if (j + length <= this.length) { - return this.dataView.subarray(j, j + length); - } else { - throw new Error('BinaryPackFailure: read index out of range'); - } -} - -function Packer(){ - this.bufferBuilder = new BufferBuilder(); -} - -Packer.prototype.getBuffer = function(){ - return this.bufferBuilder.getBuffer(); -} - -Packer.prototype.pack = function(value){ - var type = typeof(value); - if (type == 'string'){ - this.pack_string(value); - } else if (type == 'number'){ - if (Math.floor(value) === value){ - this.pack_integer(value); - } else{ - this.pack_double(value); - } - } else if (type == 'boolean'){ - if (value === true){ - this.bufferBuilder.append(0xc3); - } else if (value === false){ - this.bufferBuilder.append(0xc2); - } - } else if (type == 'undefined'){ - this.bufferBuilder.append(0xc0); - } else if (type == 'object'){ - if (value === null){ - this.bufferBuilder.append(0xc0); - } else { - var constructor = value.constructor; - if (constructor == Array){ - this.pack_array(value); - } else if (constructor == Blob || constructor == File) { - this.pack_bin(value); - } else if (constructor == ArrayBuffer) { - if(binaryFeatures.useArrayBufferView) { - this.pack_bin(new Uint8Array(value)); - } else { - this.pack_bin(value); - } - } else if ('BYTES_PER_ELEMENT' in value){ - if(binaryFeatures.useArrayBufferView) { - this.pack_bin(new Uint8Array(value.buffer)); - } else { - this.pack_bin(value.buffer); - } - } else if (constructor == Object){ - this.pack_object(value); - } else if (constructor == Date){ - this.pack_string(value.toString()); - } else if (typeof value.toBinaryPack == 'function'){ - this.bufferBuilder.append(value.toBinaryPack()); - } else { - throw new Error('Type "' + constructor.toString() + '" not yet supported'); - } - } - } else { - throw new Error('Type "' + type + '" not yet supported'); - } - this.bufferBuilder.flush(); -} - - -Packer.prototype.pack_bin = function(blob){ - var length = blob.length || blob.byteLength || blob.size; - if (length <= 0x0f){ - this.pack_uint8(0xa0 + length); - } else if (length <= 0xffff){ - this.bufferBuilder.append(0xda) ; - this.pack_uint16(length); - } else if (length <= 0xffffffff){ - this.bufferBuilder.append(0xdb); - this.pack_uint32(length); - } else{ - throw new Error('Invalid length'); - } - this.bufferBuilder.append(blob); -} - -Packer.prototype.pack_string = function(str){ - var length = utf8Length(str); - - if (length <= 0x0f){ - this.pack_uint8(0xb0 + length); - } else if (length <= 0xffff){ - this.bufferBuilder.append(0xd8) ; - this.pack_uint16(length); - } else if (length <= 0xffffffff){ - this.bufferBuilder.append(0xd9); - this.pack_uint32(length); - } else{ - throw new Error('Invalid length'); - } - this.bufferBuilder.append(str); -} - -Packer.prototype.pack_array = function(ary){ - var length = ary.length; - if (length <= 0x0f){ - this.pack_uint8(0x90 + length); - } else if (length <= 0xffff){ - this.bufferBuilder.append(0xdc) - this.pack_uint16(length); - } else if (length <= 0xffffffff){ - this.bufferBuilder.append(0xdd); - this.pack_uint32(length); - } else{ - throw new Error('Invalid length'); - } - for(var i = 0; i < length ; i++){ - this.pack(ary[i]); - } -} - -Packer.prototype.pack_integer = function(num){ - if ( -0x20 <= num && num <= 0x7f){ - this.bufferBuilder.append(num & 0xff); - } else if (0x00 <= num && num <= 0xff){ - this.bufferBuilder.append(0xcc); - this.pack_uint8(num); - } else if (-0x80 <= num && num <= 0x7f){ - this.bufferBuilder.append(0xd0); - this.pack_int8(num); - } else if ( 0x0000 <= num && num <= 0xffff){ - this.bufferBuilder.append(0xcd); - this.pack_uint16(num); - } else if (-0x8000 <= num && num <= 0x7fff){ - this.bufferBuilder.append(0xd1); - this.pack_int16(num); - } else if ( 0x00000000 <= num && num <= 0xffffffff){ - this.bufferBuilder.append(0xce); - this.pack_uint32(num); - } else if (-0x80000000 <= num && num <= 0x7fffffff){ - this.bufferBuilder.append(0xd2); - this.pack_int32(num); - } else if (-0x8000000000000000 <= num && num <= 0x7FFFFFFFFFFFFFFF){ - this.bufferBuilder.append(0xd3); - this.pack_int64(num); - } else if (0x0000000000000000 <= num && num <= 0xFFFFFFFFFFFFFFFF){ - this.bufferBuilder.append(0xcf); - this.pack_uint64(num); - } else{ - throw new Error('Invalid integer'); - } -} - -Packer.prototype.pack_double = function(num){ - var sign = 0; - if (num < 0){ - sign = 1; - num = -num; - } - var exp = Math.floor(Math.log(num) / Math.LN2); - var frac0 = num / Math.pow(2, exp) - 1; - var frac1 = Math.floor(frac0 * Math.pow(2, 52)); - var b32 = Math.pow(2, 32); - var h32 = (sign << 31) | ((exp+1023) << 20) | - (frac1 / b32) & 0x0fffff; - var l32 = frac1 % b32; - this.bufferBuilder.append(0xcb); - this.pack_int32(h32); - this.pack_int32(l32); -} - -Packer.prototype.pack_object = function(obj){ - var keys = Object.keys(obj); - var length = keys.length; - if (length <= 0x0f){ - this.pack_uint8(0x80 + length); - } else if (length <= 0xffff){ - this.bufferBuilder.append(0xde); - this.pack_uint16(length); - } else if (length <= 0xffffffff){ - this.bufferBuilder.append(0xdf); - this.pack_uint32(length); - } else{ - throw new Error('Invalid length'); - } - for(var prop in obj){ - if (obj.hasOwnProperty(prop)){ - this.pack(prop); - this.pack(obj[prop]); - } - } -} - -Packer.prototype.pack_uint8 = function(num){ - this.bufferBuilder.append(num); -} - -Packer.prototype.pack_uint16 = function(num){ - this.bufferBuilder.append(num >> 8); - this.bufferBuilder.append(num & 0xff); -} - -Packer.prototype.pack_uint32 = function(num){ - var n = num & 0xffffffff; - this.bufferBuilder.append((n & 0xff000000) >>> 24); - this.bufferBuilder.append((n & 0x00ff0000) >>> 16); - this.bufferBuilder.append((n & 0x0000ff00) >>> 8); - this.bufferBuilder.append((n & 0x000000ff)); -} - -Packer.prototype.pack_uint64 = function(num){ - var high = num / Math.pow(2, 32); - var low = num % Math.pow(2, 32); - this.bufferBuilder.append((high & 0xff000000) >>> 24); - this.bufferBuilder.append((high & 0x00ff0000) >>> 16); - this.bufferBuilder.append((high & 0x0000ff00) >>> 8); - this.bufferBuilder.append((high & 0x000000ff)); - this.bufferBuilder.append((low & 0xff000000) >>> 24); - this.bufferBuilder.append((low & 0x00ff0000) >>> 16); - this.bufferBuilder.append((low & 0x0000ff00) >>> 8); - this.bufferBuilder.append((low & 0x000000ff)); -} - -Packer.prototype.pack_int8 = function(num){ - this.bufferBuilder.append(num & 0xff); -} - -Packer.prototype.pack_int16 = function(num){ - this.bufferBuilder.append((num & 0xff00) >> 8); - this.bufferBuilder.append(num & 0xff); -} - -Packer.prototype.pack_int32 = function(num){ - this.bufferBuilder.append((num >>> 24) & 0xff); - this.bufferBuilder.append((num & 0x00ff0000) >>> 16); - this.bufferBuilder.append((num & 0x0000ff00) >>> 8); - this.bufferBuilder.append((num & 0x000000ff)); -} - -Packer.prototype.pack_int64 = function(num){ - var high = Math.floor(num / Math.pow(2, 32)); - var low = num % Math.pow(2, 32); - this.bufferBuilder.append((high & 0xff000000) >>> 24); - this.bufferBuilder.append((high & 0x00ff0000) >>> 16); - this.bufferBuilder.append((high & 0x0000ff00) >>> 8); - this.bufferBuilder.append((high & 0x000000ff)); - this.bufferBuilder.append((low & 0xff000000) >>> 24); - this.bufferBuilder.append((low & 0x00ff0000) >>> 16); - this.bufferBuilder.append((low & 0x0000ff00) >>> 8); - this.bufferBuilder.append((low & 0x000000ff)); -} - -function _utf8Replace(m){ - var code = m.charCodeAt(0); - - if(code <= 0x7ff) return '00'; - if(code <= 0xffff) return '000'; - if(code <= 0x1fffff) return '0000'; - if(code <= 0x3ffffff) return '00000'; - return '000000'; -} - -function utf8Length(str){ - if (str.length > 600) { - // Blob method faster for large strings - return (new Blob([str])).size; - } else { - return str.replace(/[^\u0000-\u007F]/g, _utf8Replace).length; - } -} - -},{"./bufferbuilder":2}],2:[function(require,module,exports){ -var binaryFeatures = {}; -binaryFeatures.useBlobBuilder = (function(){ - try { - new Blob([]); - return false; - } catch (e) { - return true; - } -})(); - -binaryFeatures.useArrayBufferView = !binaryFeatures.useBlobBuilder && (function(){ - try { - return (new Blob([new Uint8Array([])])).size === 0; - } catch (e) { - return true; - } -})(); - -module.exports.binaryFeatures = binaryFeatures; -var BlobBuilder = module.exports.BlobBuilder; -if (typeof window != 'undefined') { - BlobBuilder = module.exports.BlobBuilder = window.WebKitBlobBuilder || - window.MozBlobBuilder || window.MSBlobBuilder || window.BlobBuilder; -} - -function BufferBuilder(){ - this._pieces = []; - this._parts = []; -} - -BufferBuilder.prototype.append = function(data) { - if(typeof data === 'number') { - this._pieces.push(data); - } else { - this.flush(); - this._parts.push(data); - } -}; - -BufferBuilder.prototype.flush = function() { - if (this._pieces.length > 0) { - var buf = new Uint8Array(this._pieces); - if(!binaryFeatures.useArrayBufferView) { - buf = buf.buffer; - } - this._parts.push(buf); - this._pieces = []; - } -}; - -BufferBuilder.prototype.getBuffer = function() { - this.flush(); - if(binaryFeatures.useBlobBuilder) { - var builder = new BlobBuilder(); - for(var i = 0, ii = this._parts.length; i < ii; i++) { - builder.append(this._parts[i]); - } - return builder.getBlob(); - } else { - return new Blob(this._parts); - } -}; - -module.exports.BufferBuilder = BufferBuilder; - -},{}],3:[function(require,module,exports){ -var BufferBuilderExports = require('./bufferbuilder'); - -window.BufferBuilder = BufferBuilderExports.BufferBuilder; -window.binaryFeatures = BufferBuilderExports.binaryFeatures; -window.BlobBuilder = BufferBuilderExports.BlobBuilder; -window.BinaryPack = require('./binarypack'); - -},{"./binarypack":1,"./bufferbuilder":2}]},{},[3]); -/** - * Light EventEmitter. Ported from Node.js/events.js - * Eric Zhang - */ - -/** - * EventEmitter class - * Creates an object with event registering and firing methods - */ -function EventEmitter() { - // Initialise required storage variables - this._events = {}; -} - -var isArray = Array.isArray; - - -EventEmitter.prototype.addListener = function(type, listener, scope, once) { - if ('function' !== typeof listener) { - throw new Error('addListener only takes instances of Function'); - } - - // To avoid recursion in the case that type == "newListeners"! Before - // adding it to the listeners, first emit "newListeners". - this.emit('newListener', type, typeof listener.listener === 'function' ? - listener.listener : listener); - - if (!this._events[type]) { - // Optimize the case of one listener. Don't need the extra array object. - this._events[type] = listener; - } else if (isArray(this._events[type])) { - - // If we've already got an array, just append. - this._events[type].push(listener); - - } else { - // Adding the second element, need to change to array. - this._events[type] = [this._events[type], listener]; - } - -}; - -EventEmitter.prototype.on = EventEmitter.prototype.addListener; - -EventEmitter.prototype.once = function(type, listener, scope) { - if ('function' !== typeof listener) { - throw new Error('.once only takes instances of Function'); - } - - var self = this; - function g() { - self.removeListener(type, g); - listener.apply(this, arguments); - }; - - g.listener = listener; - self.on(type, g); - - return this; -}; - -EventEmitter.prototype.removeListener = function(type, listener, scope) { - if ('function' !== typeof listener) { - throw new Error('removeListener only takes instances of Function'); - } - - // does not use listeners(), so no side effect of creating _events[type] - if (!this._events[type]) return this; - - var list = this._events[type]; - - if (isArray(list)) { - var position = -1; - for (var i = 0, length = list.length; i < length; i++) { - if (list[i] === listener || - (list[i].listener && list[i].listener === listener)) - { - position = i; - break; - } - } - - if (position < 0) return this; - list.splice(position, 1); - if (list.length == 0) - delete this._events[type]; - } else if (list === listener || - (list.listener && list.listener === listener)) - { - delete this._events[type]; - } - - return this; -}; - - -EventEmitter.prototype.off = EventEmitter.prototype.removeListener; - - -EventEmitter.prototype.removeAllListeners = function(type) { - if (arguments.length === 0) { - this._events = {}; - return this; - } - - // does not use listeners(), so no side effect of creating _events[type] - if (type && this._events && this._events[type]) this._events[type] = null; - return this; -}; - -EventEmitter.prototype.listeners = function(type) { - if (!this._events[type]) this._events[type] = []; - if (!isArray(this._events[type])) { - this._events[type] = [this._events[type]]; - } - return this._events[type]; -}; - -EventEmitter.prototype.emit = function(type) { - var type = arguments[0]; - var handler = this._events[type]; - if (!handler) return false; - - if (typeof handler == 'function') { - switch (arguments.length) { - // fast cases - case 1: - handler.call(this); - break; - case 2: - handler.call(this, arguments[1]); - break; - case 3: - handler.call(this, arguments[1], arguments[2]); - break; - // slower - default: - var l = arguments.length; - var args = new Array(l - 1); - for (var i = 1; i < l; i++) args[i - 1] = arguments[i]; - handler.apply(this, args); - } - return true; - - } else if (isArray(handler)) { - var l = arguments.length; - var args = new Array(l - 1); - for (var i = 1; i < l; i++) args[i - 1] = arguments[i]; - - var listeners = handler.slice(); - for (var i = 0, l = listeners.length; i < l; i++) { - listeners[i].apply(this, args); - } - return true; - } else { - return false; - } -}; - - - - -var util = { - inherits: function(ctor, superCtor) { - ctor.super_ = superCtor; - ctor.prototype = Object.create(superCtor.prototype, { - constructor: { - value: ctor, - enumerable: false, - writable: true, - configurable: true - } - }); - }, - extend: function(dest, source) { - for(var key in source) { - if(source.hasOwnProperty(key)) { - dest[key] = source[key]; - } - } - return dest; - }, - pack: BinaryPack.pack, - unpack: BinaryPack.unpack, - setZeroTimeout: (function(global) { - var timeouts = []; - var messageName = 'zero-timeout-message'; - - // Like setTimeout, but only takes a function argument. There's - // no time argument (always zero) and no arguments (you have to - // use a closure). - function setZeroTimeoutPostMessage(fn) { - timeouts.push(fn); - global.postMessage(messageName, '*'); - } - - function handleMessage(event) { - if (event.source == global && event.data == messageName) { - if (event.stopPropagation) { - event.stopPropagation(); - } - if (timeouts.length) { - timeouts.shift()(); - } - } - } - if (global.addEventListener) { - global.addEventListener('message', handleMessage, true); - } else if (global.attachEvent) { - global.attachEvent('onmessage', handleMessage); - } - return setZeroTimeoutPostMessage; - }(this)) -}; - -exports.util = util; - - -function Stream() { - EventEmitter.call(this); -} - -util.inherits(Stream, EventEmitter); - -Stream.prototype.pipe = function(dest, options) { - var source = this; - - function ondata(chunk) { - if (dest.writable) { - if (false === dest.write(chunk) && source.pause) { - source.pause(); - } - } - } - - source.on('data', ondata); - - function ondrain() { - if (source.readable && source.resume) { - source.resume(); - } - } - - dest.on('drain', ondrain); - - // If the 'end' option is not supplied, dest.end() will be called when - // source gets the 'end' or 'close' events. Only dest.end() once. - if (!dest._isStdio && (!options || options.end !== false)) { - source.on('end', onend); - source.on('close', onclose); - } - - var didOnEnd = false; - function onend() { - if (didOnEnd) return; - didOnEnd = true; - - dest.end(); - } - - - function onclose() { - if (didOnEnd) return; - didOnEnd = true; - - dest.destroy(); - } - - // don't leave dangling pipes when there are errors. - function onerror(er) { - cleanup(); - if (this.listeners('error').length === 0) { - throw er; // Unhandled stream error in pipe. - } - } - - source.on('error', onerror); - dest.on('error', onerror); - - // remove all the event listeners that were added. - function cleanup() { - source.removeListener('data', ondata); - dest.removeListener('drain', ondrain); - - source.removeListener('end', onend); - source.removeListener('close', onclose); - - source.removeListener('error', onerror); - dest.removeListener('error', onerror); - - source.removeListener('end', cleanup); - source.removeListener('close', cleanup); - - dest.removeListener('end', cleanup); - dest.removeListener('close', cleanup); - } - - source.on('end', cleanup); - source.on('close', cleanup); - - dest.on('end', cleanup); - dest.on('close', cleanup); - - dest.emit('pipe', source); - - // Allow for unix-like usage: A.pipe(B).pipe(C) - return dest; -}; - -exports.Stream = Stream; -function BlobReadStream(source, options){ - Stream.call(this); - - options = util.extend({ - readDelay: 0, - paused: false - }, options); - - this._source = source; - this._start = 0; - this._readChunkSize = options.chunkSize || source.size; - this._readDelay = options.readDelay; - - this.readable = true; - this.paused = options.paused; - - this._read(); -} - -util.inherits(BlobReadStream, Stream); - - -BlobReadStream.prototype.pause = function(){ - this.paused = true; -}; - -BlobReadStream.prototype.resume = function(){ - this.paused = false; - this._read(); -}; - -BlobReadStream.prototype.destroy = function(){ - this.readable = false; - clearTimeout(this._timeoutId); -}; - -BlobReadStream.prototype._read = function(){ - - var self = this; - - function emitReadChunk(){ - self._emitReadChunk(); - } - - var readDelay = this._readDelay; - if (readDelay !== 0){ - this._timeoutId = setTimeout(emitReadChunk, readDelay); - } else { - util.setZeroTimeout(emitReadChunk); - } - -}; - -BlobReadStream.prototype._emitReadChunk = function(){ - - if(this.paused || !this.readable) return; - - var chunkSize = Math.min(this._source.size - this._start, this._readChunkSize); - - if(chunkSize === 0){ - this.readable = false; - this.emit("end"); - return; - } - - var sourceEnd = this._start + chunkSize; - var chunk = (this._source.slice || this._source.webkitSlice || this._source.mozSlice).call(this._source, this._start, sourceEnd); - - this._start = sourceEnd; - this._read(); - - this.emit("data", chunk); - -}; - - -exports.BlobReadStream = BlobReadStream; - -function BinaryStream(socket, id, create, meta) { - if (!(this instanceof BinaryStream)) return new BinaryStream(options); - - var self = this; - - Stream.call(this); - - - this.id = id; - this._socket = socket; - - this.writable = true; - this.readable = true; - this.paused = false; - - this._closed = false; - this._ended = false; - - if(create) { - // This is a stream we are creating - this._write(1, meta, this.id); - } -} - -util.inherits(BinaryStream, Stream); - - -BinaryStream.prototype._onDrain = function() { - if(!this.paused) { - this.emit('drain'); - } -}; - -BinaryStream.prototype._onClose = function() { - // Emit close event - if (this._closed) { - return; - } - this.readable = false; - this.writable = false; - this._closed = true; - this.emit('close'); -}; - -BinaryStream.prototype._onError = function(error){ - this.readable = false; - this.writable = false; - this.emit('error', error); -}; - -// Write stream - -BinaryStream.prototype._onPause = function() { - // Emit pause event - this.paused = true; - this.emit('pause'); -}; - -BinaryStream.prototype._onResume = function() { - // Emit resume event - this.paused = false; - this.emit('resume'); - this.emit('drain'); -}; - -BinaryStream.prototype._write = function(code, data, bonus) { - if (this._socket.readyState !== this._socket.constructor.OPEN) { - return false; - } - var message = util.pack([code, data, bonus]); - return this._socket.send(message) !== false; -}; - -BinaryStream.prototype.write = function(data) { - if(this.writable) { - var out = this._write(2, data, this.id); - return !this.paused && out; - } else { - this.emit('error', new Error('Stream is not writable')); - return false; - } -}; - -BinaryStream.prototype.end = function() { - this._ended = true; - this.readable = false; - this._write(5, null, this.id); -}; - -BinaryStream.prototype.destroy = BinaryStream.prototype.destroySoon = function() { - this._onClose(); - this._write(6, null, this.id); -}; - - -// Read stream - -BinaryStream.prototype._onEnd = function() { - if(this._ended) { - return; - } - this._ended = true; - this.readable = false; - this.emit('end'); -}; - -BinaryStream.prototype._onData = function(data) { - // Dispatch - this.emit('data', data); -}; - -BinaryStream.prototype.pause = function() { - this._onPause(); - this._write(3, null, this.id); -}; - -BinaryStream.prototype.resume = function() { - this._onResume(); - this._write(4, null, this.id); -}; - - -function BinaryClient(socket, options) { - if (!(this instanceof BinaryClient)) return new BinaryClient(socket, options); - - EventEmitter.call(this); - - var self = this; - - this._options = util.extend({ - chunkSize: 40960 - }, options); - - this.streams = {}; - - if(typeof socket === 'string') { - this._nextId = 0; - this._socket = new WebSocket(socket); - } else { - // Use odd numbered ids for server originated streams - this._nextId = 1; - this._socket = socket; - } - - this._socket.binaryType = 'arraybuffer'; - - this._socket.addEventListener('open', function(){ - self.emit('open'); - }); - this._socket.addEventListener('error', function(error){ - var ids = Object.keys(self.streams); - for (var i = 0, ii = ids.length; i < ii; i++) { - self.streams[ids[i]]._onError(error); - } - self.emit('error', error); - }); - this._socket.addEventListener('close', function(code, message){ - var ids = Object.keys(self.streams); - for (var i = 0, ii = ids.length; i < ii; i++) { - self.streams[ids[i]]._onClose(); - } - self.emit('close', code, message); - }); - this._socket.addEventListener('message', function(data, flags){ - util.setZeroTimeout(function(){ - - // Message format - // [type, payload, bonus ] - // - // Reserved - // [ 0 , X , X ] - // - // - // New stream - // [ 1 , Meta , new streamId ] - // - // - // Data - // [ 2 , Data , streamId ] - // - // - // Pause - // [ 3 , null , streamId ] - // - // - // Resume - // [ 4 , null , streamId ] - // - // - // End - // [ 5 , null , streamId ] - // - // - // Close - // [ 6 , null , streamId ] - // - - data = data.data; - - try { - data = util.unpack(data); - } catch (ex) { - return self.emit('error', new Error('Received unparsable message: ' + ex)); - } - if (!(data instanceof Array)) - return self.emit('error', new Error('Received non-array message')); - if (data.length != 3) - return self.emit('error', new Error('Received message with wrong part count: ' + data.length)); - if ('number' != typeof data[0]) - return self.emit('error', new Error('Received message with non-number type: ' + data[0])); - - switch(data[0]) { - case 0: - // Reserved - break; - case 1: - var meta = data[1]; - var streamId = data[2]; - var binaryStream = self._receiveStream(streamId); - self.emit('stream', binaryStream, meta); - break; - case 2: - var payload = data[1]; - var streamId = data[2]; - var binaryStream = self.streams[streamId]; - if(binaryStream) { - binaryStream._onData(payload); - } else { - self.emit('error', new Error('Received `data` message for unknown stream: ' + streamId)); - } - break; - case 3: - var streamId = data[2]; - var binaryStream = self.streams[streamId]; - if(binaryStream) { - binaryStream._onPause(); - } else { - self.emit('error', new Error('Received `pause` message for unknown stream: ' + streamId)); - } - break; - case 4: - var streamId = data[2]; - var binaryStream = self.streams[streamId]; - if(binaryStream) { - binaryStream._onResume(); - } else { - self.emit('error', new Error('Received `resume` message for unknown stream: ' + streamId)); - } - break; - case 5: - var streamId = data[2]; - var binaryStream = self.streams[streamId]; - if(binaryStream) { - binaryStream._onEnd(); - } else { - self.emit('error', new Error('Received `end` message for unknown stream: ' + streamId)); - } - break; - case 6: - var streamId = data[2]; - var binaryStream = self.streams[streamId]; - if(binaryStream) { - binaryStream._onClose(); - } else { - self.emit('error', new Error('Received `close` message for unknown stream: ' + streamId)); - } - break; - default: - self.emit('error', new Error('Unrecognized message type received: ' + data[0])); - } - }); - }); -} - -util.inherits(BinaryClient, EventEmitter); - -BinaryClient.prototype.send = function(data, meta){ - var stream = this.createStream(meta); - if(data instanceof Stream) { - data.pipe(stream); - } else if (util.isNode === true) { - if(Buffer.isBuffer(data)) { - (new BufferReadStream(data, {chunkSize: this._options.chunkSize})).pipe(stream); - } else { - stream.write(data); - } - } else if (util.isNode !== true) { - if(data.constructor == Blob || data.constructor == File) { - (new BlobReadStream(data, {chunkSize: this._options.chunkSize})).pipe(stream); - } else if (data.constructor == ArrayBuffer) { - var blob; - if(binaryFeatures.useArrayBufferView) { - data = new Uint8Array(data); - } - if(binaryFeatures.useBlobBuilder) { - var builder = new BlobBuilder(); - builder.append(data); - blob = builder.getBlob() - } else { - blob = new Blob([data]); - } - (new BlobReadStream(blob, {chunkSize: this._options.chunkSize})).pipe(stream); - } else if (typeof data === 'object' && 'BYTES_PER_ELEMENT' in data) { - var blob; - if(!binaryFeatures.useArrayBufferView) { - // Warn - data = data.buffer; - } - if(binaryFeatures.useBlobBuilder) { - var builder = new BlobBuilder(); - builder.append(data); - blob = builder.getBlob() - } else { - blob = new Blob([data]); - } - (new BlobReadStream(blob, {chunkSize: this._options.chunkSize})).pipe(stream); - } else { - stream.write(data); - } - } - return stream; -}; - -BinaryClient.prototype._receiveStream = function(streamId){ - var self = this; - var binaryStream = new BinaryStream(this._socket, streamId, false); - binaryStream.on('close', function(){ - delete self.streams[streamId]; - }); - this.streams[streamId] = binaryStream; - return binaryStream; -}; - -BinaryClient.prototype.createStream = function(meta){ - if(this._socket.readyState !== WebSocket.OPEN) { - throw new Error('Client is not yet connected or has closed'); - return; - } - var self = this; - var streamId = this._nextId; - this._nextId += 2; - var binaryStream = new BinaryStream(this._socket, streamId, true, meta); - binaryStream.on('close', function(){ - delete self.streams[streamId]; - }); - this.streams[streamId] = binaryStream; - return binaryStream; -}; - -BinaryClient.prototype.close = BinaryClient.prototype.destroy = function() { - this._socket.close(); -}; - -exports.BinaryClient = BinaryClient; - -})(this); - -</script> -\ No newline at end of file diff --git a/app/elements/p2p-network/connection-wrapper.html b/app/elements/p2p-network/connection-wrapper.html @@ -1,59 +0,0 @@ -<link rel="import" href="p2p-network.html"> -<link rel="import" href="web-socket.html"> -<dom-module id="connection-wrapper"> - <template> - <p2p-network id="p2p" me="{{me}}"></p2p-network> - <web-socket id="ws" me="{{me}}"></web-socket> - </template> - <script> - 'use strict'; - (function() { - window.webRTCSupported = !!(window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection || window.webkitRTCPeerConnection); - - function rtcConnectionSupported(peerId) { - return window.webRTCSupported && (peerId.indexOf('rtc_') === 0); - } - Polymer({ - is: 'connection-wrapper', - properties: { - me: { - notify: true - }, - }, - behaviors: [Snapdrop.FileTransferProtocol], - _sendFile: function(toPeer, file) { - if (!rtcConnectionSupported(toPeer)) { - this.$.ws._sendFile(toPeer, file); - } else { - this.$.p2p._sendFile(toPeer, file); - } - }, - _sendSystemEvent: function(toPeer, event) { - console.log('system event', toPeer, event); - if (!rtcConnectionSupported(toPeer)) { - this.$.ws._sendSystemEvent(toPeer, event); - } else { - this.$.p2p._sendSystemEvent(toPeer, event); - } - }, - connectToPeer: function(toPeer, callback) { - if (!rtcConnectionSupported(toPeer)) { - callback(); - } else { - this.$.p2p.connectToPeer(toPeer, callback); - } - }, - _onHandshake: function(event) { - var me = event.uuid; - this.set('me', me); - if (window.webRTCSupported) { - this.$.p2p.initialize(); - } - }, - notifyServer: function(msg) { - this.$.ws.client.send({}, msg); - } - }); - })(); - </script> -</dom-module> diff --git a/app/elements/p2p-network/file-transfer-protocol.html b/app/elements/p2p-network/file-transfer-protocol.html @@ -1,159 +0,0 @@ -<script> -'use strict'; -window.Snapdrop = window.Snapdrop || {}; -Snapdrop.FileTransferProtocol = { - properties: { - loading: { - type: Boolean, - notify: true, - value: false, - observer: '_loadingChanged' - }, - buddies: { - notify: true - } - }, - listeners: { - 'system-event': '_onSystemMsg', - 'file-received': '_onFileReceived', - }, - _onSystemMsg: function(event) { - var msg = event.detail; - console.log('FTP received sysMsg:', msg); - - switch (msg.type) { - case 'handshake': - this._onHandshake(msg); - break; - case 'offer': - this._onOffered(msg); - break; - case 'decline': - this._onDeclined(msg); - break; - case 'accept': - this._onAccepted(msg); - break; - case 'transfer': - this._onTransfer(msg); - break; - case 'received': - this._onReceived(msg); - break; - case 'buddies': - this._onBuddies(msg); - break; - case 'text': - this._onTextReceived(msg); - break; - } - }, - sendFile: function(peerId, file) { - this.set('loading', true); - this.fileToSend = file; - this.fire('file-offered', { - to: peerId - }); - this.connectToPeer(peerId, function() { - this._offer(peerId, file); - }.bind(this)); - - //set 15sec timeout - this._timeoutTimer = this.async(function() { - this._onError(); - }, 15000); - }, - _offer: function(toPeer, file) { - console.log('FTP offer file:', file, 'To:', toPeer); - - this._sendSystemEvent(toPeer, { - type: 'offer', - name: file.name - }); - }, - _onOffered: function(offer) { - console.log('FTP offered file:', offer.name, 'From:', offer.from); - this.fire('file-offer', { - from: offer.from, - name: offer.name - }); - }, - decline: function(offer) { - this._sendSystemEvent(offer.from, { - type: 'decline', - name: offer.name - }); - }, - _onDeclined: function(offer) { - this.cancelAsync(this._timeoutTimer); - delete this.fileToSend; - this.set('loading', false); - this.fire('file-declined', offer); - }, - accept: function(offer) { - this._sendSystemEvent(offer.from, { - type: 'accept', - name: offer.name - }); - this.fire('download-started', { - from: offer.from - }); - }, - _onAccepted: function(offer) { - this.cancelAsync(this._timeoutTimer); - this._sendSystemEvent(offer.from, { - type: 'transfer', - name: offer.name - }); - this.fire('upload-started', { - to: offer.from - }); - this._sendFile(offer.from, this.fileToSend); - }, - _onTransfer: function() { - this.loading = true; - }, - _onFileReceived: function(event) { - var file = event.detail; - this.loading = false; - this._sendSystemEvent(file.from, { - type: 'received', - name: file.name - }); - this.fire('download-complete', { - from: file.from - }); - console.log('FTP received:', file); - }, - _onReceived: function(offer) { - this.loading = false; - this.fire('upload-complete', offer); - if(window.donateDialog){ - window.donateDialog.open(); - } - }, - _onError: function() { - this.loading = false; - this.fire('upload-error'); - }, - _loadingChanged: function(loading) { - window.anim(loading); - }, - _onBuddies: function(msg) { - this.set('buddies', msg.buddies); - }, - sendText: function(toPeer, text) { - console.log('FTP send text:', text, 'To:', toPeer); - this.connectToPeer(toPeer, function() { - this._sendSystemEvent(toPeer, { - type: 'text', - text: text - }); - }.bind(this)); - - }, - _onTextReceived: function(msg) { - this.fire('text-received', msg); - } -}; -</script> diff --git a/app/elements/p2p-network/p2p-network.html b/app/elements/p2p-network/p2p-network.html @@ -1,171 +0,0 @@ -<script src="../../bower_components/peerjs/peer.min.js"></script> -<link rel="import" href="file-transfer-protocol.html"> -<dom-module id="p2p-network"> - <template> - </template> - <script> - 'use strict'; - Polymer({ - is: 'p2p-network', - properties: { - me: { - type: String - } - }, - attached: function() { - this._connectedPeers = {}; - this._initCallbacks = []; - this._unsendMsgs = {}; - window.onunload = window.onbeforeunload = function() { - if (!!this._peer && !this._peer.destroyed) { - this._peer.destroy(); - } - }.bind(this); - }, - initialize: function() { - clearInterval(this.reconnectTimer); - this.reconnectTimer = undefined; - var options; - if (window.debug) { - options = { - host: window.location.hostname, - port: 3002, - path: 'peerjs' - }; - } else { - options = { - host: 'snapdrop.net', - port: 443, - path: 'peerjs', - secure: true - }; - } - this._peer = new Peer(this.me, options); - this._peer.on('open', function(id) { - console.log('My peer ID is: ' + id); - this.set('me', id); - this._peerOpen = true; - this._initCallbacks.forEach(function(cb) { - cb(); - }); - }.bind(this)); - - this._peer.on('connection', this.connect.bind(this)); - this._peer.on('error', function(err) { - console.error(err); - //ugly hack to find out error type - if (err.message.indexOf('Could not connect to peer') > -1) { - delete this._connectedPeers[this.peer]; - return; - } - if (err.message.indexOf('Lost connection to server') > -1) { - this._peer.destroy(); - this._reconnect(); - return; - } - }.bind(this)); - }, - - connect: function(c) { - var peer = c.peer; - - if (c.label === 'file') { - c.on('data', function(data) { - console.log(data); - var dataView = new Uint8Array(data.file); - var dataBlob = new Blob([dataView]); - this.fire('file-received', { - from: peer, - blob: dataBlob, - name: data.name, - }); - - }.bind(this)); - } - - if (c.label === 'system') { - c.on('data', function(data) { - data.from = peer; - this.fire('system-event', data); - }.bind(this)); - } - }, - connectToPeer: (function() { - function request(requestedPeer, callback) { - return function() { - - //system messages channel - var s = this._peer.connect(requestedPeer, { - label: 'system' - }); - - s.on('open', function() { - this.connect(s); - if (callback) { - callback(); - } - }.bind(this)); - s.on('error', function(err) { - console.log(err); - if (err.message.indexOf('Connection is not open') > -1) { - console.error('Handle this error!!', err); - } - }.bind(this)); - - //files channel - var f = this._peer.connect(requestedPeer, { - label: 'file', - reliable: true - }); - f.on('open', function() { - this.connect(f); - - }.bind(this)); - f.on('error', function(err) { - console.log(err); - }); - }; - } - return function(requestedPeer, callback) { - if (this._peer.connections[requestedPeer]) { - callback(); - return; - } - if (this._peerOpen) { - request(requestedPeer, callback).bind(this)(); - } else { - this._initCallbacks.push(request(requestedPeer, callback).bind(this)); - } - - }; - }()), - _sendFile: function(peerId, file) { - var conns = this._peer.connections[peerId]; - if (conns) { - conns.forEach(function(conn) { - if (conn.label === 'file') { - conn.send(file); - console.log('send file via WebRTC'); - } - }.bind(this)); - } - }, - _sendSystemEvent: function(peerId, msg) { - var conns = this._peer.connections[peerId]; - if (conns) { - conns.forEach(function(conn) { - if (conn.label === 'system') { - conn.send(msg); - } - }.bind(this)); - } - }, - _reconnect: function(e) { - //try to reconnect after 3s - if (!this.reconnectTimer) { - this.reconnectTimer = setInterval(this.initialize.bind(this), 3000); - } - } - }); - </script> -</dom-module> diff --git a/app/elements/p2p-network/tab-active.html b/app/elements/p2p-network/tab-active.html @@ -1,25 +0,0 @@ -<script> -'use strict'; -var vis = (function() { - var stateKey, - eventKey, - keys = { - hidden: "visibilitychange", - webkitHidden: "webkitvisibilitychange", - mozHidden: "mozvisibilitychange", - msHidden: "msvisibilitychange" - }; - for (stateKey in keys) { - if (stateKey in document) { - eventKey = keys[stateKey]; - break; - } - } - return function(c) { - if (c) { - document.addEventListener(eventKey, c); - } - return !document[stateKey]; - }; -})(); -</script> diff --git a/app/elements/p2p-network/web-socket.html b/app/elements/p2p-network/web-socket.html @@ -1,89 +0,0 @@ -<link rel="import" href="binaryjs.html"> -<dom-module id="web-socket"> - <template> - <style> - :host { - display: block; - } - </style> - </template> - <script> - 'use strict'; - Polymer({ - is: 'web-socket', - attached: function() { - this.init(); - }, - init: function() { - clearInterval(this.reconnectTimer); - this.reconnectTimer = undefined; - var websocketUrl = (window.debug ? 'ws://' + window.location.hostname + ':3002' : 'wss://snapdrop.net') + '/binary'; - this.client = new BinaryClient(websocketUrl); - this.client.on('stream', function(stream, meta) { - // collect stream data - var parts = []; - stream.on('data', function(data) { - //console.log('part received', meta, data); - if (data.isSystemEvent) { - if (meta) { - data.from = meta.from; - } - this.fire('system-event', data); - } else { - parts.push(data); - } - }.bind(this)); - stream.on('end', function() { - var blob = new Blob(parts, { - type: meta.type - }); - console.log('file received', blob, meta); - this.fire('file-received', { - blob: blob, - name: meta.name, - from: meta.from - }); - }.bind(this)); - }.bind(this)); - this.client.on('open', function(e) { - console.log(e); - this.client.send({}, { - serverMsg: 'rtc-support', - rtc: window.webRTCSupported - }); - }.bind(this)); - this.client.on('error', function(e) { - this._reconnect(e); - }.bind(this)); - this.client.on('close', function(e) { - this._reconnect(e); - }.bind(this)); - }, - _sendFile: function(toPeer, file) { - console.log('send file via WebSocket', file); - this.client.send(file.file, { - name: file.file.name, - type: file.file.type, - toPeer: toPeer - }); - }, - connectToPeer: function(peer, callback) { - callback(); - }, - _sendSystemEvent: function(toPeer, event) { - console.log('system event', toPeer, event); - event.isSystemEvent = true; - this.client.send(event, { - toPeer: toPeer - }); - }, - _reconnect: function(e) { - console.log('disconnected', e); - //try to reconnect after 3s - if (!this.reconnectTimer) { - this.reconnectTimer = setInterval(this.init.bind(this), 3000); - } - } - }); - </script> -</dom-module> diff --git a/app/elements/sound-notification/sound-notification-behavior.html b/app/elements/sound-notification/sound-notification-behavior.html @@ -1,24 +0,0 @@ -<link rel="import" href="sound-notification.html"> -<script> -'use strict'; -Snapdrop = window.Snapdrop || {}; -Snapdrop.SoundNotificationBehavior = { - sounds: function() { - var sounds = document.querySelector('sound-notification'); - if (!sounds) { - sounds = Polymer.Base.create('sound-notification'); - document.body.appendChild(sounds); - } - return sounds; - }, - attached: function() { - //lazy load sound files - setTimeout(function() { - this.sounds(); - }.bind(this), 1000); - }, - playSound: function(e) { - this.sounds().play(); - } -}; -</script> diff --git a/app/elements/sound-notification/sound-notification.html b/app/elements/sound-notification/sound-notification.html @@ -1,56 +0,0 @@ -<dom-module id="sound-notification"> - <template> - <audio id="blop" preload="auto" autobuffer="true"> - <source src="/sounds/blop.mp3" id="mp3Source" type="audio/mpeg"> - <source src="/sounds/blop.ogg" id="oggSource" type="audio/ogg"> - </audio> - </template> -</dom-module> -<script> -'use strict'; -Polymer({ - is: 'sound-notification', - properties: { - volumes: { - value: { - 'blop': 0.8, - } - } - }, - attached: function() { - // mobiles don't like autoplay - the first play must be triggert by user interaction - var that = this; - var hackListener = function() { - that.volumes.blop = 0.1; - that.play(); - document.body.removeEventListener('touchstart', hackListener, false); - that.volumes.blop = 0.8; - }; - document.body.addEventListener('touchstart', hackListener, false); - }, - play: function() { - this._play('blop'); - }, - _play: function(sound) { - var audio = this.$[sound]; - if (!audio) { - console.warn('audio ', sound, ' doesn\'t exist.'); - return; - } - - if (audio.readyState > 0) { - audio.volume = this.volumes[sound]; - audio.pause(); - audio.currentTime = 0; - audio.play(); - } else { - console.warn('audio not ready yet...'); - //play when ready - //TODO: play only if ready within next ~500ms - audio.addEventListener('loadedmetadata', function() { - this._play(sound); - }.bind(this), false); - } - } -}); -</script> diff --git a/app/elements/text-sharing/clipboard-behavior.html b/app/elements/text-sharing/clipboard-behavior.html @@ -1,36 +0,0 @@ -<script> -'use strict'; -(function(document) { - var copyTextarea = document.createElement('textarea'); - copyTextarea.setAttribute('id', 'clipboard-textarea'); - var style = copyTextarea.style; - style.position = 'absolute'; - style.top = '-10000px'; - document.body.appendChild(copyTextarea); - - window.Snapdrop.ClipboardBehavior = { - copyToClipboard: function(content) { - copyTextarea.value = content; - var range = document.createRange(); - range.selectNode(copyTextarea); - window.getSelection().addRange(range); - - try { - // Now that we've selected the anchor text, execute the copy command - var successful = document.execCommand('copy'); - if (successful) { - app.displayToast('Copied text to clipboard. Paste it where you want!'); - } else { - console.log('failed to copy to clipboard', successful); - } - } catch (err) { - console.log('Oops, unable to copy', err); - } - - // Remove the selections - NOTE: Should use - // removeRange(range) when it is supported - window.getSelection().removeAllRanges(); - } - }; -}(document)); -</script> diff --git a/app/elements/text-sharing/linkify.html b/app/elements/text-sharing/linkify.html @@ -1,5 +0,0 @@ -<script> -'use strict'; -!function(){"use strict";function t(t,n){if("function"!=typeof n&&null!==n)throw new TypeError("Super expression must either be null or a function, not "+typeof n);t.prototype=Object.create(n&&n.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),n&&(Object.setPrototypeOf?Object.setPrototypeOf(t,n):t.__proto__=n)}function n(t,n){if(!(t instanceof n))throw new TypeError("Cannot call a class as a function")}function e(t,n,e,o){for(var r=0,i=t.length,a=[],s=void 0;i>r&&(s=n.next(t[r]));)n=s,r++;if(r>=i)return[];for(;i-1>r;)s=new h(o),a.push(s),n.on(t[r],s),n=s,r++;return s=new h(e),a.push(s),n.on(t[i-1],s),a}function o(t,n){if("function"!=typeof n&&null!==n)throw new TypeError("Super expression must either be null or a function, not "+typeof n);t.prototype=Object.create(n&&n.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),n&&(Object.setPrototypeOf?Object.setPrototypeOf(t,n):t.__proto__=n)}function r(t,n){if(!(t instanceof n))throw new TypeError("Cannot call a class as a function")}function i(t){return t instanceof d||t instanceof P}function a(t){return t}function s(t,n){return"url"===n?"_blank":null}function u(t){return t=t||{},{attributes:t.linkAttributes||null,defaultProtocol:t.defaultProtocol||"http",events:t.events||null,format:t.format||a,formatHref:t.formatHref||a,newLine:t.newLine||!1,nl2br:!!t.newLine||t.nl2br||!1,tagName:t.tagName||"a",target:t.target||s,linkClass:t.linkClass||"linkified"}}function c(t){for(var n=arguments.length,e=Array(n>1?n-1:0),o=1;n>o;o++)e[o-1]=arguments[o];return"function"==typeof t?t.apply(void 0,e):t}function l(t){if(t&&t.__esModule)return t;var n={};if(null!=t)for(var e in t)Object.prototype.hasOwnProperty.call(t,e)&&(n[e]=t[e]);return n["default"]=t,n}var p={__esModule:!0},f=function(){function t(e){n(this,t),this.j=[],this.T=e||null}return t.prototype.on=function(t,n){if(t instanceof Array)for(var e=0;e<t.length;e++)this.j.push([t[e],n]);else this.j.push([t,n])},t.prototype.next=function(t){for(var n=0;n<this.j.length;n++){var e=this.j[n],o=e[1];if(this.test(t,e[0]))return o}return!1},t.prototype.accepts=function(){return!!this.T},t.prototype.test=function(t,n){return t===n},t.prototype.emit=function(){return this.T},t}(),h=function(e){function o(){n(this,o),e.apply(this,arguments)}return t(o,e),o.prototype.test=function(t,n){return t===n||n instanceof RegExp&&n.test(t)},o}(f),g=function(e){function o(){n(this,o),e.apply(this,arguments)}return t(o,e),o.prototype.test=function(t,n){return t instanceof n},o}(f);p.CharacterState=h,p.TokenState=g,p.stateify=e;var m={__esModule:!0},y=function(){function t(n){r(this,t),this.v=n}return t.prototype.toString=function(){return this.v+""},t}(),d=function(t){function n(){r(this,n),t.apply(this,arguments)}return o(n,t),n}(y),b=function(t){function n(){r(this,n),t.call(this,"@")}return o(n,t),n}(y),v=function(t){function n(){r(this,n),t.call(this,":")}return o(n,t),n}(y),x=function(t){function n(){r(this,n),t.call(this,".")}return o(n,t),n}(y),k=function(t){function n(){r(this,n),t.apply(this,arguments)}return o(n,t),n}(y),w=function(t){function n(){r(this,n),t.apply(this,arguments)}return o(n,t),n}(y),O=function(t){function n(){r(this,n),t.call(this,"\n")}return o(n,t),n}(y),E=function(t){function n(){r(this,n),t.apply(this,arguments)}return o(n,t),n}(y),L=function(t){function n(){r(this,n),t.call(this,"+")}return o(n,t),n}(y),S=function(t){function n(){r(this,n),t.call(this,"#")}return o(n,t),n}(y),A=function(t){function n(){r(this,n),t.apply(this,arguments)}return o(n,t),n}(y),C=function(t){function n(){r(this,n),t.call(this,"?")}return o(n,t),n}(y),T=function(t){function n(){r(this,n),t.call(this,"/")}return o(n,t),n}(y),N=function(t){function n(){r(this,n),t.apply(this,arguments)}return o(n,t),n}(y),P=function(t){function n(){r(this,n),t.apply(this,arguments)}return o(n,t),n}(y),j=function(t){function n(){r(this,n),t.apply(this,arguments)}return o(n,t),n}(y),z=function(t){function n(){r(this,n),t.call(this,"{")}return o(n,t),n}(y),R=function(t){function n(){r(this,n),t.call(this,"[")}return o(n,t),n}(y),_=function(t){function n(){r(this,n),t.call(this,"(")}return o(n,t),n}(y),U=function(t){function n(){r(this,n),t.call(this,"}")}return o(n,t),n}(y),M=function(t){function n(){r(this,n),t.call(this,"]")}return o(n,t),n}(y),B=function(t){function n(){r(this,n),t.call(this,")")}return o(n,t),n}(y),D={Base:y,DOMAIN:d,AT:b,COLON:v,DOT:x,PUNCTUATION:k,LOCALHOST:w,NL:O,NUM:E,PLUS:L,POUND:S,QUERY:C,PROTOCOL:A,SLASH:T,SYM:N,TLD:P,WS:j,OPENBRACE:z,OPENBRACKET:R,OPENPAREN:_,CLOSEBRACE:U,CLOSEBRACKET:M,CLOSEPAREN:B},H=function(){function t(n){r(this,t),this.v=n,this.type="token",this.isLink=!1}return t.prototype.toString=function(){for(var t=[],n=0;n<this.v.length;n++)t.push(this.v[n].toString());return t.join("")},t.prototype.toHref=function(){return this.toString()},t.prototype.toObject=function(){return{type:this.type,value:this.toString(),href:this.toHref(0>=arguments.length||void 0===arguments[0]?"http":arguments[0])}},t}(),q=function(t){function n(e){r(this,n),t.call(this,e),this.type="email",this.isLink=!0}return o(n,t),n.prototype.toHref=function(){return"mailto:"+this.toString()},n}(H),I=function(t){function n(e){r(this,n),t.call(this,e),this.type="text"}return o(n,t),n}(H),K=function(t){function n(e){r(this,n),t.call(this,e),this.type="nl"}return o(n,t),n}(H),Y=function(t){function n(e){r(this,n),t.call(this,e),this.type="url",this.isLink=!0}return o(n,t),n.prototype.toHref=function(){for(var t=0>=arguments.length||void 0===arguments[0]?"http":arguments[0],n=!1,e=!1,o=this.v,r=[],a=0;o[a]instanceof A;)n=!0,r.push(o[a].toString().toLowerCase()),a++;for(;o[a]instanceof T;)e=!0,r.push(o[a].toString()),a++;for(;i(o[a]);)r.push(o[a].toString().toLowerCase()),a++;for(;a<o.length;a++)r.push(o[a].toString());return r=r.join(""),n||e||(r=t+"://"+r),r},n.prototype.hasProtocol=function(){return this.v[0]instanceof A},n}(H),Q={Base:H,EMAIL:q,NL:K,TEXT:I,URL:Y};m.text=D,m.multi=Q;var W={__esModule:!0},X=m,Z=p,F=function(t){return new Z.TokenState(t)},G=X.text.DOMAIN,J=X.text.AT,V=X.text.COLON,$=X.text.DOT,tt=X.text.PUNCTUATION,nt=X.text.LOCALHOST,et=X.text.NL,ot=X.text.NUM,rt=X.text.PLUS,it=X.text.POUND,at=X.text.PROTOCOL,st=X.text.QUERY,ut=X.text.SLASH,ct=X.text.SYM,lt=X.text.TLD,pt=X.text.OPENBRACE,ft=X.text.OPENBRACKET,ht=X.text.OPENPAREN,gt=X.text.CLOSEBRACE,mt=X.text.CLOSEBRACKET,yt=X.text.CLOSEPAREN,dt=X.multi.EMAIL,bt=X.multi.NL,vt=X.multi.TEXT,xt=X.multi.URL,kt=F(),wt=F(),Ot=F(),Et=F(),Lt=F(),St=F(),At=F(xt),Ct=F(),Tt=F(xt),Nt=F(),Pt=F(),jt=F(xt),zt=F(),Rt=F(xt),_t=F(xt),Ut=F(),Mt=F(),Bt=F(),Dt=F(),Ht=F(xt),qt=F(xt),It=F(xt),Kt=F(),Yt=F(),Qt=F(),Wt=F(),Xt=F(),Zt=F(dt),Ft=F(),Gt=F(dt),Jt=F(),Vt=F(),$t=F(),tn=F(bt);kt.on(et,tn),kt.on(at,wt),kt.on(ut,Ot),wt.on(ut,Ot),Ot.on(ut,Et),kt.on(lt,Lt),kt.on(G,Lt),kt.on(nt,At),kt.on(ot,Lt),Et.on(lt,Nt),Et.on(G,Nt),Et.on(ot,Nt),Et.on(nt,jt),Lt.on($,St),Nt.on($,Pt),Wt.on($,Xt),St.on(lt,At),St.on(G,Lt),St.on(ot,Lt),St.on(nt,Lt),Pt.on(lt,jt),Pt.on(G,Nt),Pt.on(ot,Nt),Pt.on(nt,Nt),Xt.on(lt,Zt),Xt.on(G,Wt),Xt.on(ot,Wt),Xt.on(nt,Wt),At.on($,St),jt.on($,Pt),Zt.on($,Xt),At.on(V,Ct),At.on(ut,_t),Ct.on(ot,Tt),Tt.on(ut,_t),jt.on(V,zt),jt.on(ut,_t),zt.on(ot,Rt),Rt.on(ut,_t),Zt.on(V,Ft),Ft.on(ot,Gt);var nn=[G,J,nt,ot,rt,it,at,ut,lt],en=[V,$,st,tt,gt,mt,yt,pt,ft,ht,ct];_t.on(pt,Mt),_t.on(ft,Bt),_t.on(ht,Dt),Ut.on(pt,Mt),Ut.on(ft,Bt),Ut.on(ht,Dt),Mt.on(gt,_t),Bt.on(mt,_t),Dt.on(yt,_t),Ht.on(gt,_t),qt.on(mt,_t),It.on(yt,_t),Kt.on(gt,_t),Yt.on(mt,_t),Qt.on(yt,_t),Mt.on(nn,Ht),Bt.on(nn,qt),Dt.on(nn,It),Mt.on(en,Kt),Bt.on(en,Yt),Dt.on(en,Qt),Ht.on(nn,Ht),qt.on(nn,qt),It.on(nn,It),Ht.on(en,Ht),qt.on(en,qt),It.on(en,It),Kt.on(nn,Ht),Yt.on(nn,qt),Qt.on(nn,It),Kt.on(en,Kt),Yt.on(en,Yt),Qt.on(en,Qt),_t.on(nn,_t),Ut.on(nn,_t),_t.on(en,Ut),Ut.on(en,Ut);var on=[G,ot,rt,it,st,ct,lt];Lt.on(on,Jt),Lt.on(J,Vt),St.on(on,Jt),At.on(on,Jt),At.on(J,Vt),Jt.on(on,Jt),Jt.on(J,Vt),Jt.on($,$t),$t.on(on,Jt),Vt.on(lt,Wt),Vt.on(G,Wt),Vt.on(nt,Zt);var rn=function(t){for(var n=t.length,e=0,o=[],r=[];n>e;){for(var i=kt,a=null,s=null,u=0,c=null,l=-1;n>e&&!(a=i.next(t[e]));)r.push(t[e++]);for(;n>e&&(s=a||i.next(t[e]));)a=null,i=s,i.accepts()?(l=0,c=i):l>=0&&l++,e++,u++;if(0>l)for(u=e-u;e>u;u++)r.push(t[u]);else 0<r.length&&(o.push(new vt(r)),r=[]),e-=l,u-=l,i=c.emit(),o.push(new i(t.slice(e-u,e)))}return 0<r.length&&o.push(new vt(r)),o},an=X.multi,sn=kt;W.State=Z.TokenState,W.TOKENS=an,W.run=rn,W.start=sn;var un={__esModule:!0},cn=m,ln=p,pn="abogado ac academy accountants active actor ad adult ae aero af ag agency ai airforce al allfinanz alsace am an android ao aq aquarelle ar archi army arpa as asia associates at attorney au auction audio autos aw ax axa az ba band bar bargains bayern bb bd be beer berlin best bf bg bh bi bid bike bio biz bj black blackfriday bloomberg blue bm bmw bn bnpparibas bo boo boutique br brussels bs bt budapest build builders business buzz bv bw by bz bzh ca cab cal camera camp cancerresearch capetown capital caravan cards care career careers casa cash cat catering cc cd center ceo cern cf cg ch channel cheap christmas chrome church ci citic city ck cl claims cleaning click clinic clothing club cm cn co coach codes coffee college cologne com community company computer condos construction consulting contractors cooking cool coop country cr credit creditcard cricket crs cruises cu cuisinella cv cw cx cy cymru cz dad dance dating day de deals degree delivery democrat dental dentist desi diamonds diet digital direct directory discount dj dk dm dnp do domains durban dvag dz eat ec edu education ee eg email emerck energy engineer engineering enterprises equipment er es esq estate et eu eurovision eus events everbank exchange expert exposed fail farm fashion feedback fi finance financial firmdale fish fishing fitness fj fk flights florist flsmidth fly fm fo foo forsale foundation fr frl frogans fund furniture futbol ga gal gallery gb gbiz gd ge gent gf gg gh gi gift gifts gives gl glass gle global globo gm gmail gmo gmx gn google gop gov gp gq gr graphics gratis green gripe gs gt gu guide guitars guru gw gy hamburg haus healthcare help here hiphop hiv hk hm hn holdings holiday homes horse host hosting house how hr ht hu ibm id ie il im immo immobilien in industries info ing ink institute insure int international investments io iq ir irish is it je jetzt jm jo jobs joburg jp juegos kaufen ke kg kh ki kim kitchen kiwi km kn koeln kp kr krd kred kw ky kz la lacaixa land latrobe lawyer lb lc lds lease legal lgbt li life lighting limited limo link lk loans london lotto lr ls lt ltda lu luxe luxury lv ly ma madrid maison management mango market marketing mc md me media meet melbourne meme memorial menu mg mh miami mil mini mk ml mm mn mo mobi moda moe monash money mormon mortgage moscow motorcycles mov mp mq mr ms mt mu museum mv mw mx my mz na nagoya name navy nc ne net network neustar new nexus nf ng ngo nhk ni ninja nl no np nr nra nrw nu nyc nz okinawa om ong onl ooo org organic otsuka ovh pa paris partners parts party pe pf pg ph pharmacy photo photography photos physio pics pictures pink pizza pk pl place plumbing pm pn pohl poker porn post pr praxi press pro prod productions prof properties property ps pt pub pw py qa qpon quebec re realtor recipes red rehab reise reisen reit ren rentals repair report republican rest restaurant reviews rich rio rip ro rocks rodeo rs rsvp ru ruhr rw ryukyu sa saarland sarl sb sc sca scb schmidt schule science scot sd se services sexy sg sh shiksha shoes si singles sj sk sl sm sn so social software sohu solar solutions soy space spiegel sr st su supplies supply support surf surgery suzuki sv sx sy sydney systems sz taipei tatar tattoo tax tc td technology tel tf tg th tienda tips tirol tj tk tl tm tn to today tokyo tools top town toys tp tr trade training travel trust tt tui tv tw tz ua ug uk university uno uol us uy uz va vacations vc ve vegas ventures versicherung vet vg vi viajes villas vision vlaanderen vn vodka vote voting voto voyage vu wales wang watch webcam website wed wedding wf whoswho wien wiki williamhill wme work works world ws wtc wtf xxx xyz yachts yandex ye yoga yokohama youtube yt za zip zm zone zw".split(" "),fn=/[0-9]/,hn=/[a-z0-9]/,gn=":",mn=[],yn=function(t){return new ln.CharacterState(t)},dn=cn.text.DOMAIN,bn=cn.text.LOCALHOST,vn=cn.text.NUM,xn=cn.text.PROTOCOL,kn=cn.text.TLD,wn=cn.text.WS,On=yn(),En=yn(vn),Ln=yn(dn),Sn=yn(),An=yn(wn);On.on("@",yn(cn.text.AT)),On.on(".",yn(cn.text.DOT)),On.on("+",yn(cn.text.PLUS)),On.on("#",yn(cn.text.POUND)),On.on("?",yn(cn.text.QUERY)),On.on("/",yn(cn.text.SLASH)),On.on(gn,yn(cn.text.COLON)),On.on("{",yn(cn.text.OPENBRACE)),On.on("[",yn(cn.text.OPENBRACKET)),On.on("(",yn(cn.text.OPENPAREN)),On.on("}",yn(cn.text.CLOSEBRACE)),On.on("]",yn(cn.text.CLOSEBRACKET)),On.on(")",yn(cn.text.CLOSEPAREN)),On.on(/[,;!]/,yn(cn.text.PUNCTUATION)),On.on(/\n/,yn(cn.text.NL)),On.on(/\s/,An),An.on(/[^\S\n]/,An);for(var Cn=0;Cn<pn.length;Cn++){var Tn=ln.stateify(pn[Cn],On,kn,dn);mn.push.apply(mn,Tn)}var Nn=ln.stateify("file",On,dn,dn),Pn=ln.stateify("ftp",On,dn,dn),jn=ln.stateify("http",On,dn,dn);mn.push.apply(mn,Nn),mn.push.apply(mn,Pn),mn.push.apply(mn,jn);var zn=Nn.pop(),Rn=Pn.pop(),_n=jn.pop(),Un=yn(dn),Mn=yn(xn);Rn.on("s",Un),_n.on("s",Un),mn.push(Un),zn.on(gn,Mn),Rn.on(gn,Mn),_n.on(gn,Mn),Un.on(gn,Mn);var Bn=ln.stateify("localhost",On,bn,dn);for(mn.push.apply(mn,Bn),On.on(fn,En),En.on("-",Sn),En.on(fn,En),En.on(hn,Ln),Ln.on("-",Sn),Ln.on(hn,Ln),Cn=0;Cn<mn.length;Cn++)mn[Cn].on("-",Sn),mn[Cn].on(hn,Ln);Sn.on("-",Sn),Sn.on(fn,Ln),Sn.on(hn,Ln),On.on(/./,yn(cn.text.SYM));var Dn=function(t){for(var n=t.replace(/[A-Z]/g,function(t){return t.toLowerCase()}),e=t.length,o=[],r=0;e>r;){for(var i=On,a=null,s=0,u=null,c=-1;e>r&&(a=i.next(n[r]));)i=a,i.accepts()?(c=0,u=i):c>=0&&c++,s++,r++;0>c||(r-=c,s-=c,i=u.emit(),o.push(new i(t.substr(r-s,s))))}return o},Hn=On;un.State=ln.CharacterState,un.TOKENS=cn.text,un.run=Dn,un.start=Hn;var qn={__esModule:!0};qn.normalize=u,qn.resolve=c;var In={__esModule:!0},Kn=qn,Yn=l(Kn),Qn=un,Wn=l(Qn),Xn=W,Zn=l(Xn);Array.isArray||(Array.isArray=function(t){return"[object Array]"===Object.prototype.toString.call(t)});var Fn=function(t){return Zn.run(Wn.run(t))},Gn=function(t){for(var n=1>=arguments.length||void 0===arguments[1]?null:arguments[1],e=Fn(t),o=[],r=0;r<e.length;r++)!e[r].isLink||n&&e[r].type!==n||o.push(e[r].toObject());return o},Jn=function(t){var n=1>=arguments.length||void 0===arguments[1]?null:arguments[1],e=Fn(t);return 1===e.length&&e[0].isLink&&(!n||e[0].type===n)};In.find=Gn,In.options=Yn,In.parser=Zn,In.scanner=Wn,In.test=Jn,In.tokenize=Fn,window.linkify=In}(); -!function(e){"use strict";function t(e,t,n){var r=n[n.length-1];e.replaceChild(r,t);for(var o=n.length-2;o>=0;o--)e.insertBefore(n[o],r),r=n[o]}function n(e,t,n){for(var r=[],o=0;o<e.length;o++){var i=e[o];if(i.isLink){var l=i.toHref(t.defaultProtocol),s=a.resolve(t.format,i.toString(),i.type),f=a.resolve(t.formatHref,l,i.type),u=a.resolve(t.attributes,l,i.type),d=a.resolve(t.tagName,l,i.type),v=a.resolve(t.linkClass,l,i.type),c=a.resolve(t.target,l,i.type),m=a.resolve(t.events,l,i.type),p=n.createElement(d);if(p.setAttribute("href",f),p.setAttribute("class",v),c&&p.setAttribute("target",c),u)for(var h in u)p.setAttribute(h,u[h]);if(m)for(var g in m)p.addEventListener?p.addEventListener(g,m[g]):p.attachEvent&&p.attachEvent("on"+g,m[g]);p.appendChild(n.createTextNode(s)),r.push(p)}else"nl"===i.type&&t.nl2br?r.push(n.createElement("br")):r.push(n.createTextNode(i.toString()))}return r}function r(e,o,a){if(!e||"object"!=typeof e||e.nodeType!==l)throw new Error("Cannot linkify "+e+" - Invalid DOM Node type");if("A"===e.tagName)return e;for(var f=e.firstChild;f;){switch(f.nodeType){case l:r(f,o,a);break;case s:var u=f.nodeValue,d=i(u),v=n(d,o,a);t(e,f,v),f=v[v.length-1]}f=f.nextSibling}return e}function o(e,t){var n=arguments.length<=2||void 0===arguments[2]?null:arguments[2];try{n=n||window&&window.document||global&&global.document}catch(o){}if(!n)throw new Error("Cannot find document implementation. If you are in a non-browser environment like Node.js, pass the document implementation as the third argument to linkifyElement.");return t=a.normalize(t),r(e,t,n)}var i=e.tokenize,a=e.options,l=1,s=3;o.helper=r,o.normalize=a.normalize,window.linkifyElement=o}(window.linkify); -</script> -\ No newline at end of file diff --git a/app/elements/text-sharing/text-input-behavior.html b/app/elements/text-sharing/text-input-behavior.html @@ -1,48 +0,0 @@ -<link rel="import" href="text-input-dialog.html"> -<script> -'use strict'; -window.Snapdrop = window.Snapdrop || {}; -(function() { - var textInput = Polymer.Base.create('text-input-dialog'); - textInput.className = 'textInput'; - document.body.appendChild(textInput); - Snapdrop.TextInputBehavior = { - properties: { - contact: Object, - }, - get textInput() { - var textInput = Polymer.dom(document).querySelector('.textInput'); - return textInput; - }, - openTextDialog: function() { - this.textInput.open(this.contact); - }, - - listeners: { - 'contextmenu': '_handleContextMenu', - 'down': '_handleDown', - 'up': '_handleUp', - }, - _handleContextMenu: function(ev) { - ev.preventDefault(); - ev.stopPropagation(); - ev.cancelBubble = true; - this.cancelAsync(this.pressTimer); - this.openTextDialog(); - return false; - }, - _handleUp: function(e) { - this.cancelAsync(this.pressTimer); - }, - _handleDown: function(ev) { - this.pressTimer = this.async(function() { - this.openTextDialog(); - ev.preventDefault(); - ev.stopPropagation(); - ev.cancelBubble = true; - return false; - }, 800); - }, - }; -}()); -</script> diff --git a/app/elements/text-sharing/text-input-dialog.html b/app/elements/text-sharing/text-input-dialog.html @@ -1,210 +0,0 @@ -<link rel="import" href="../../bower_components/paper-dialog/paper-dialog.html"> -<link rel="import" href="../../bower_components/paper-button/paper-button.html"> -<link rel="import" href="../../bower_components/neon-animation/animations/scale-up-animation.html"> -<link rel="import" href="../../bower_components/neon-animation/animations/fade-out-animation.html"> -<link rel="import" href="../../bower_components/iron-pages/iron-pages.html"> -<link rel="import" href="../../bower_components/paper-spinner/paper-spinner.html"> -<link rel="import" href="../../bower_components/paper-input/paper-textarea.html"> -<link rel="import" href="linkify.html"> -<link rel="import" href="clipboard-behavior.html"> -<link rel="import" href="../sound-notification/sound-notification-behavior.html"> -<dom-module id="text-input-dialog"> - <template> - <style> - :host { - display: block; - } - - #sendDialog, - #receiveDialog { - width: 324px; - z-index: 101; - max-height: 320px; - overflow: hidden; - margin: 16px; - } - - @media all and (max-height: 600px) { - #sendDialog { - padding-top: 24px; - top: 0px !important; - } - } - - #receivedText { - word-break: break-all; - word-break: break-word; - } - - paper-textarea { - max-height: 200px; - width: calc(100% - 48px); - overflow-x: hidden; - overflow-y: auto; - } - - #receivedText { - max-height: 200px; - overflow: hidden; - width: calc(100% - 48px); - text-overflow: ellipsis; - -webkit-line-clamp: 9; - clamp: 9; - } - </style> - <paper-dialog id="sendDialog" entry-animation="scale-up-animation" exit-animation="fade-out-animation" with-backdrop modal> - <h2>Send Text</h2> - <paper-textarea id="textInput" label="Enter Text" value="{{textToSend}}" autofocus></paper-textarea> - <div class="buttons"> - <paper-button dialog-dismiss>Discard</paper-button> - <paper-button dialog-dismiss on-tap="_send">Send</paper-button> - </div> - </paper-dialog> - <paper-dialog id="receiveDialog" entry-animation="scale-up-animation" exit-animation="fade-out-animation" with-backdrop modal> - <h2>Text Received</h2> - <div> - <div id="receivedText"> - </div> - </div> - <div class="buttons"> - <paper-button dialog-dismiss>Close</paper-button> - <paper-button on-tap="_copy" autofocus hidden$="{{!clipboardSupported}}">Copy</paper-button> - <a href="tel:{{tel}}" hidden$="{{!tel}}"> - <paper-button autofocus dialog-dismiss>Call</paper-button> - </a> - <a href="{{url}}" hidden$="{{!url}}" target="_blank"> - <paper-button autofocus dialog-dismiss>Open</paper-button> - </a> - </div> - </paper-dialog> - </template> - <script> - 'use strict'; - (function() { - /* - * - * /^\+?[0-9x]*$/ is the first usuful Text sent via Snapdrop 2015/1/2 5:30 - * - */ - var phoneNumbers = /^\+?[0-9x/ ]*$/; - var urls = /((([A-Za-z]{3,9}:(?:\/\/)?)(?:[\-;:&=\+\$,\w]+@)?[A-Za-z0-9\.\-]+|(?:www\.|[\-;:&=\+\$,\w]+@)[A-Za-z0-9\.\-]+)((?:\/[\+~%\/\.\w\-_]*)?\??(?:[\-\+=&;%@\.\w_]*)#?(?:[\.\!\/\\\w]*))?)/; - - Polymer({ - is: 'text-input-dialog', - behaviors: [Snapdrop.ClipboardBehavior, Snapdrop.SoundNotificationBehavior], - properties: { - textToSend: { - type: String - }, - receivedText: { - type: String - }, - contact: { - type: Object - }, - tel: { - computed: '_isPhoneNumber(receivedText)', - value: false - }, - url: { - computed: '_isUrl(receivedText)', - value: false - }, - clipboardSupported: { - value: false - }, - fallback: { - computed: '_isFallback(url,tel,clipboardSupported)', - value: false - } - }, - open: function(contact) { - this.contact = contact; - this.$.sendDialog.open(); - }, - attached: function() { - // clipboard must be initalized by user interaction - var that = this; - var hackListener = function() { - document.body.removeEventListener('touchstart', hackListener, false); - document.body.removeEventListener('click', hackListener, false); - // wait 1s to tell the ui that copy is supported - that.async(function() { - that.clipboardSupported = document.queryCommandSupported && document.queryCommandSupported('copy'); - }, 1000); - }; - document.body.addEventListener('touchstart', hackListener, false); - document.body.addEventListener('click', hackListener, false); - - - this.async(function() { - app.conn.addEventListener('text-received', function(e) { - var receivedText = e.detail.text; - if (!receivedText || receivedText.trim() === '') { - this.playSound(); - return; - } - this.receivedText = receivedText; - this.$.receivedText.textContent = receivedText; - window.linkifyElement(this.$.receivedText, {}, document); - this.$.receiveDialog.open(); - this.playSound(); - }.bind(this), false); - }, 200); - - this.$.textInput.addEventListener('keypress', function(e) { - if (e.which === 13 || e.charCode === 13) { - var key; - var isShift; - if (window.event) { - key = window.event.keyCode; - isShift = !!window.event.shiftKey; // typecast to boolean - } else { - key = e.which; - isShift = !!e.shiftKey; - } - if (!isShift) { - e.preventDefault(); - e.stopPropagation(); - this._send(); - } - } - }.bind(this), false); - - - }, - _send: function() { - this.$.sendDialog.close(); - app.conn.sendText(this.contact.peerId, this.textToSend); - }, - _copy: function() { - this.copyToClipboard(this.receivedText); - - this.$.receiveDialog.close(); - console.log('text copied', this.receivedText); - }, - _isPhoneNumber: function(text) { - if (!text || text.length < 5 || text.length > 100) { - return false; - } - if (phoneNumbers.test(text)) { - return text; - } - - }, - _isUrl: function(text) { - if (!text) { - return false; - } - if (urls.test(text)) { - return text; - } - - }, - _isFallback: function(url, tel, clipboardSupported) { - return (!url && !tel && !clipboardSupported); - } - }); - }()); - </script> -</dom-module> diff --git a/app/elements/x-cards/about-page.html b/app/elements/x-cards/about-page.html @@ -1,229 +0,0 @@ -<link rel="import" href="../../bower_components/iron-flex-layout/iron-flex-layout.html"> -<link rel="import" href="../../bower_components/neon-animation/neon-shared-element-animatable-behavior.html"> -<link rel="import" href="../../bower_components/neon-animation/neon-animations.html"> -<link rel="import" href="../../bower_components/paper-styles/paper-styles-classes.html"> -<link rel="import" href="../../bower_components/iron-icon/iron-icon.html"> -<dom-module id="about-page"> - <template> - <style> - :host { - display: block; - overflow: hidden; - color: white; - z-index: 3; - --paper-tooltip: { - font-size: 14px; - background-color: white; - color: #4285f4; - } - --paper-tooltip-opacity:0.95; - } - - #placeholder { - opacity: 0; - background-color: #4285f4; - @apply(--layout-fit); - } - - #container { - @apply(--layout-fit); - @apply(--layout-vertical); - @apply(--layout-center-center); - background-color: #4285f4; - padding: 64px 32px 64px 32px; - box-sizing: border-box; - } - - .logo { - width: 96px; - height: 96px; - } - - .slogan { - text-align: center; - } - - .paper-font-headline { - margin-bottom: 8px; - font-size: 32px; - } - - a { - text-decoration: none; - color: white; - } - - .center { - @apply(--layout-vertical); - @apply(--layout-center-center); - } - - #footer { - position: absolute; - left: 50%; - margin-left: -160px; - width: 320px; - bottom: 24px; - text-align: center; - } - - @media all and (max-height: 370px) { - #footer { - width: 320px; - bottom: 8px; - } - } - - a { - display: inline-block; - text-align: center; - } - - .donate-icon { - padding-top: 32px; - width: 32px; - height: 32px; - } - - .paper-font-subhead { - font-size: 20px; - } - - a.paper-font-subhead { - padding-top: 32px; - } - - .share { - position: absolute; - top: 16px; - right: 16px; - z-index: 10; - } - - .social { - margin: 16px; - width: 228px; - } - - #btn, - .social a { - color: white; - } - - .social a { - text-decoration: none; - padding: 2px 9px 0 9px; - } - - .share a:hover, - #btn:hover { - color: white; - } - </style> - <div id="placeholder"></div> - <div id="container"> - <div class="share"> - <paper-icon-button id="btn" icon="chat:close" on-tap="_switch"></paper-icon-button> - </div> - <div class="center"> - <iron-icon icon="chat:wifi-tethering" class="logo"></iron-icon> - <div class="paper-font-headline">Snapdrop</div> - <div class="slogan">The easiest way to transfer files across devices.</div> - <div class="social"> - <a href="https://twitter.com/intent/tweet?text=https://snapdrop.net%20by%20@robin_linus%20&" target="_blank"> - <iron-icon icon="chat:twitter"></iron-icon> - <paper-tooltip position="bottom" offset="14"> - Tweet about Snapdrop! - </paper-tooltip> - </a> - <a href="https://www.facebook.com/RobinLinus" target="_blank"> - <iron-icon icon="chat:facebook"></iron-icon> - <paper-tooltip position="bottom" offset="14"> - Like my Facebook Page! - </paper-tooltip> - </a> - <a href="https://www.paypal.com/cgi-bin/webscr?cmd=_donations&cmd=_donations&business=robin@capira.de&currency_code=USD&item_name=Snapdrop&shipping=0" target="_blank"> - <iron-icon icon="chat:local-cafe"></iron-icon> - <paper-tooltip position="bottom" offset="14"> - You like Snapdrop? - <br> Buy me a cup of coffee! - </paper-tooltip> - </a> - <a href="https://github.com/yougrow/snapdrop" target="_blank" class="github"> - <iron-icon icon="chat:github"></iron-icon> - <paper-tooltip position="bottom" offset="14"> - Get involved! - </paper-tooltip> - </a> - <a href="https://github.com/yougrow/snapdrop#frequently-asked-questions" target="_blank" class="github"> - <iron-icon icon="chat:help-outline"></iron-icon> - <paper-tooltip position="bottom" offset="14"> - Frequently Asked Questions - </paper-tooltip> - </a> - </div> - </div> - <span id="footer"><a href="https://twitter.com/robin_linus" target="_blank">Built with &#9829; by Robin Linus</a></span> - </div> - </template> -</dom-module> -<script> -(function() { - Polymer({ - is: 'about-page', - behaviors: [ - Polymer.NeonSharedElementAnimatableBehavior - ], - properties: { - animationConfig: { - value: function() { - return { - 'entry': [{ - name: 'ripple-animation', - id: 'ripple', - toPage: this - }, { - name: 'fade-out-animation', - node: this.$.placeholder, - timing: { - delay: 250 - } - }, { - name: 'fade-in-animation', - node: this.$.container, - timing: { - delay: 50 - } - }], - 'exit': [{ - name: 'opaque-animation', - node: this.$.placeholder - }, { - name: 'fade-out-animation', - node: this.$.container, - timing: { - duration: 0 - } - }, { - name: 'reverse-ripple-animation', - id: 'reverse-ripple', - fromPage: this - }] - }; - } - }, - sharedElements: { - value: function() { - return { - 'ripple': this.$.placeholder, - 'reverse-ripple': this.$.placeholder - }; - } - } - }, - _switch: function() { - document.querySelector('#pages').select(0); - } - }); -})(); -</script> diff --git a/app/elements/x-cards/settings-page.html b/app/elements/x-cards/settings-page.html @@ -1,86 +0,0 @@ -<dom-module id="settings-page"> - <template> - <style> - :host { - display: block; - overflow: hidden; - color: white; - z-index: 3 - } - - #placeholder { - opacity: 0; - background-color: #4285f4; - @apply(--layout-fit); - } - - #container { - @apply(--layout-fit); - @apply(--layout-vertical); - @apply(--layout-center-center); - background-color: #4285f4; - padding: 64px 32px 64px 32px; - box-sizing: border-box; - } - </style> - </template> - <script> - 'use strict'; - Polymer({ - is: 'settings-page', - behaviors: [ - Polymer.NeonSharedElementAnimatableBehavior - ], - properties: { - animationConfig: { - value: function() { - return { - 'entry': [{ - name: 'ripple-animation', - id: 'ripple', - toPage: this - }, { - name: 'fade-out-animation', - node: this.$.placeholder, - timing: { - delay: 250 - } - }, { - name: 'fade-in-animation', - node: this.$.container, - timing: { - delay: 50 - } - }], - 'exit': [{ - name: 'opaque-animation', - node: this.$.placeholder - }, { - name: 'fade-out-animation', - node: this.$.container, - timing: { - duration: 0 - } - }, { - name: 'reverse-ripple-animation', - id: 'reverse-ripple', - fromPage: this - }] - }; - } - }, - sharedElements: { - value: function() { - return { - 'ripple': this.$.placeholder, - 'reverse-ripple': this.$.placeholder - }; - } - } - }, - _switch: function() { - document.querySelector('#pages').select(0); - } - }); - </script> -</dom-module> diff --git a/app/elements/x-cards/x-cards.html b/app/elements/x-cards/x-cards.html @@ -1,105 +0,0 @@ -<link rel="import" href="../../bower_components/iron-flex-layout/iron-flex-layout.html"> -<link rel="import" href="../../bower_components/neon-animation/neon-shared-element-animatable-behavior.html"> -<link rel="import" href="../../bower_components/neon-animation/neon-animations.html"> -<link rel="import" href="../../bower_components/paper-icon-button/paper-icon-button.html"> -<link rel="import" href="../../bower_components/paper-tooltip/paper-tooltip.html"> -<link rel="import" href="../../styles/icons.html"> -<link rel="import" href="../invitation-link/invitation-link.html"> - -<dom-module id="x-cards"> - <template> - <style> - :host { - display: block; - overflow: hidden; - } - - #placeholder { - opacity: 0; - background-color: grey; - @apply(--layout-fit); - } - - paper-icon-button { - color: #52524F; - } - - paper-icon-button:hover { - color: #4285f4; - } - - .share { - position: absolute; - top: 16px; - right: 16px; - z-index: 10; - } - - .share a { - color: #52524F; - text-decoration: none; - padding-left: 8px; - padding-right: 8px; - } - - .share a:hover, - #btn:hover { - color: #4285f4; - } - - </style> - <div id="placeholder"></div> - <div id="container"> - <div class="share"> - <paper-icon-button id="btn" icon="chat:info-outline" on-tap="_switch"></paper-icon-button> - </div> - <content select="div"></content> - </div> - </template> -</dom-module> -<script> -(function() { - Polymer({ - is: 'x-cards', - behaviors: [ - Polymer.NeonSharedElementAnimatableBehavior - ], - properties: { - animationConfig: { - value: function() { - return { - 'entry': [{ - name: 'reverse-ripple-animation', - id: 'reverse-ripple', - toPage: this - }], - 'exit': [{ - name: 'fade-out-animation', - node: this.$.container, - timing: { - delay: 150, - duration: 0 - } - }, { - name: 'ripple-animation', - id: 'ripple', - fromPage: this - }] - }; - } - }, - sharedElements: { - value: function() { - return { - 'ripple': this.$.btn, - 'reverse-ripple': this.$.btn - }; - } - } - }, - _switch: function() { - document.querySelector('#pages').select(1); - } - }); -})(); -</script> diff --git a/app/favicon.ico b/app/favicon.ico Binary files differ. diff --git a/app/images/touch/apple-touch-icon.png b/app/images/touch/apple-touch-icon.png Binary files differ. diff --git a/app/images/touch/chrome-splashscreen-icon-384x384.png b/app/images/touch/chrome-splashscreen-icon-384x384.png Binary files differ. diff --git a/app/images/touch/chrome-touch-icon-192x192.png b/app/images/touch/chrome-touch-icon-192x192.png Binary files differ. diff --git a/app/images/touch/icon-128x128.png b/app/images/touch/icon-128x128.png Binary files differ. diff --git a/app/images/touch/logo.png b/app/images/touch/logo.png Binary files differ. diff --git a/app/images/touch/ms-icon-144x144-precomposed.png b/app/images/touch/ms-icon-144x144-precomposed.png Binary files differ. diff --git a/app/images/touch/ms-icon-144x144.png b/app/images/touch/ms-icon-144x144.png Binary files differ. diff --git a/app/images/touch/snapdrop-icon.png b/app/images/touch/snapdrop-icon.png Binary files differ. diff --git a/app/index.html b/app/index.html @@ -1,100 +0,0 @@ -<!doctype html> -<html lang="en"> - -<head> - <meta charset="utf-8"> - <meta name="viewport" content="width=device-width initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> - <meta name="generator" content="Snapdrop"> - <title>Snapdrop</title> - <link rel="shortcut icon" href="favicon.ico?v=3" /> - <!-- Place favicon.ico in the `app/` directory --> - <!-- Chrome for Android theme color --> - <meta name="theme-color" content="#3367d6"> - <!-- Web Application Manifest --> - <link rel="manifest" href="manifest.json"> - <!-- Tile color for Win8 --> - <meta name="msapplication-TileColor" content="#3372DF"> - <!-- Add to homescreen for Chrome on Android --> - <meta name="mobile-web-app-capable" content="yes"> - <meta name="application-name" content="PSK"> - <link rel="icon" sizes="192x192" href="images/touch/chrome-touch-icon-192x192.png"> - <link rel="fluid-icon" type="image/png" href="images/touch/chrome-touch-icon-192x192.png"> - <meta name="description" content="Snapdrop is an easy way to transfer files. Instantly share images, video, PDF, and links across devices. Peer2Peer, Private, Secure and Open Source. No Setup, No Signup."> - <meta property="og:image" content="https://snapdrop.net/images/touch/chrome-splashscreen-icon-384x384.png" /> - <meta property="og:url" content="https://snapdrop.net/" /> - <meta name="twitter:image" content="https://snapdrop.net/images/touch/chrome-splashscreen-icon-384x384.png" /> - <meta name="twitter:author" content="@RobinLinus" /> - <meta property="og:type" content="article" /> - <meta property="og:author" content="https://facebook.com/RobinLinus" /> - <meta property="fb:pages" content="451189218422617" /> - <meta property="fb:profile_id" content="451189218422617" /> - <meta name="twitter:description" content="Snapdrop is an easy way to transfer files. Instantly share images, video, PDF, and links across devices. Peer2Peer, Private, Secure and Open Source. No Setup, No Signup."> - <!-- Add to homescreen for Safari on iOS --> - <meta name="apple-mobile-web-app-capable" content="yes"> - <meta name="apple-mobile-web-app-status-bar-style" content="black"> - <meta name="apple-mobile-web-app-title" content="Snapdrop"> - <link rel="apple-touch-icon" href="images/touch/apple-touch-icon.png"> - <!-- Tile icon for Win8 (144x144) --> - <meta name="msapplication-TileImage" content="images/touch/ms-touch-icon-144x144-precomposed.png"> - <!-- build:css styles/main.css --> - <link rel="stylesheet" href="styles/main.css"> - <!-- endbuild--> - <!-- build:js bower_components/webcomponentsjs/webcomponents-lite.min.js async foo="1" --> - <script src="bower_components/webcomponentsjs/webcomponents-lite.js"></script> - <!-- endbuild --> - <!-- Because this project uses vulcanize this should be your only html import - in this file. All other imports should go in elements.html --> - <link rel="import" href="elements/elements.html" async> - <script> - window.debug = true; - </script> -</head> - -<body class="layout vertical"> - <script src="scripts/animated-bg.js" inline></script> - <span id="browser-sync-binding"></span> - <template is="dom-bind" id="app"> - <connection-wrapper me="{{me}}" loading="{{loading}}" buddies="{{buddies}}"></connection-wrapper> - <neon-animated-pages id="pages" selected="0"> - <x-cards on-switch="_showAbout"> - <div> - <paper-progress indeterminate hidden$="{{!loading}}"></paper-progress> - <buddy-finder me="{{me}}" active$="{{loading}}" buddies="{{buddies}}"></buddy-finder> - </div> - </x-cards> - <about-page on-switch="_showApp"> - </about-page> - </neon-animated-pages> - <file-receiver></file-receiver> - <paper-toast id="toast" duration="6000"> - </paper-toast> - <paper-toast id="caching-complete" duration="6000" text="Caching complete! This app will work offline."> - </paper-toast> - <donate-dialog></donate-dialog> - <platinum-sw-register auto-register clients-claim skip-waiting base-uri="bower_components/platinum-sw/bootstrap" on-service-worker-installed="displayInstalledToast"> - <platinum-sw-cache default-cache-strategy="fastest" cache-config-file="cache-config.json"> - </platinum-sw-cache> - </platinum-sw-register> - </template> - <!-- build:js scripts/app.js --> - <script src="scripts/app.js"></script> - <!-- endbuild--> - <script> - (function(i, s, o, g, r, a, m) { - i['GoogleAnalyticsObject'] = r; - i[r] = i[r] || function() { - (i[r].q = i[r].q || []).push(arguments) - }, i[r].l = 1 * new Date(); - a = s.createElement(o), - m = s.getElementsByTagName(o)[0]; - a.async = 1; - a.src = g; - m.parentNode.insertBefore(a, m) - })(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga'); - - ga('create', 'UA-71686975-1', 'auto'); - ga('send', 'pageview'); - </script> -</body> - -</html> diff --git a/app/manifest.json b/app/manifest.json @@ -1,29 +0,0 @@ -{ - "name": "Snapdrop", - "short_name": "Snapdrop", - "icons": [{ - "src": "images/touch/icon-128x128.png", - "sizes": "128x128", - "type": "image/png" - }, { - "src": "images/touch/apple-touch-icon.png", - "sizes": "152x152", - "type": "image/png" - }, { - "src": "images/touch/ms-touch-icon-144x144-precomposed.png", - "sizes": "144x144", - "type": "image/png" - }, { - "src": "images/touch/chrome-touch-icon-192x192.png", - "sizes": "192x192", - "type": "image/png" - }, { - "src": "images/touch/chrome-splashscreen-icon-384x384.png", - "sizes": "384x384", - "type": "image/png" - }], - "background_color": "#3367d6", - "start_url": "index.html", - "display": "standalone", - "theme_color": "#3367d6" -} diff --git a/app/robots.txt b/app/robots.txt @@ -1,4 +0,0 @@ -# www.robotstxt.org - -User-agent: * -Disallow: diff --git a/app/scripts/animated-bg.js b/app/scripts/animated-bg.js @@ -1,65 +0,0 @@ -'use strict'; -(function() { - var requestAnimFrame = (function() { - return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || - function(callback) { - window.setTimeout(callback, 1000 / 60); - }; - })(); - var c = document.createElement('canvas'); - document.body.appendChild(c); - var style = c.style; - style.width = '100%'; - style.position = 'absolute'; - var ctx = c.getContext('2d'); - var x0, y0, w, h, dw; - - function init() { - w = window.innerWidth; - h = window.innerHeight; - c.width = w; - c.height = h; - var offset = h > 380 ? 100 : 65; - x0 = w / 2; - y0 = h - offset; - dw = Math.max(w, h, 1000) / 13; - drawCircles(); - } - window.onresize = init; - - function drawCicrle(radius) { - ctx.beginPath(); - var color = Math.round(255 * (1 - radius / Math.max(w, h))); - ctx.strokeStyle = 'rgba(' + color + ',' + color + ',' + color + ',0.1)'; - ctx.arc(x0, y0, radius, 0, 2 * Math.PI); - ctx.stroke(); - ctx.lineWidth = 2; - } - - var step = 0; - - function drawCircles() { - ctx.clearRect(0, 0, w, h); - for (var i = 0; i < 8; i++) { - drawCicrle(dw * i + step % dw); - } - step += 1; - } - - var loading = true; - - function animate() { - if (loading || step % dw < dw - 5) { - requestAnimFrame(function() { - drawCircles(); - animate(); - }); - } - } - window.anim = function(l) { - loading = l; - animate(); - }; - init(); - animate(); -}()); diff --git a/app/scripts/app.js b/app/scripts/app.js @@ -1,47 +0,0 @@ -(function(document) { - 'use strict'; - - var app = document.querySelector('#app'); - - // Sets app default base URL - app.baseUrl = '/'; - - - // don't display the install prompt if the user has *already* installed - window.addEventListener('beforeinstallprompt', function(event) { - if (window.matchMedia('(display-mode: standalone)').matches) { - return event.preventDefault(); - } - }); - - - app.displayInstalledToast = function() { - // Check to make sure caching is actually enabled—it won't be in the dev environment. - if (!Polymer.dom(document).querySelector('platinum-sw-cache').disabled) { - Polymer.dom(document).querySelector('#caching-complete').show(); - } - }; - - app.displayToast = function(msg) { - var toast = Polymer.dom(document).querySelector('#toast'); - toast.text = msg; - toast.show(); - }; - - // Listen for template bound event to know when bindings - // have resolved and content has been stamped to the page - app.addEventListener('dom-change', function() { - console.log('Our app is ready to rock!'); - app.conn = document.querySelector('connection-wrapper'); - }); - - // See https://github.com/Polymer/polymer/issues/1381 - window.addEventListener('WebComponentsReady', function() { - // imports are loaded and elements have been registered - - }); - - app._showAbout=function(){ - document.querySelector('#pages').select(0); - }; -})(document); diff --git a/app/styles/app-theme.html b/app/styles/app-theme.html @@ -1,35 +0,0 @@ -<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:400,300,500,700"> -<link rel="import" href="../bower_components/polymer/polymer.html"> -<style is="custom-style"> -:root { - --dark-primary-color: #303F9F; - --default-primary-color: #3F51B5; - --light-primary-color: #C5CAE9; - --text-primary-color: #ffffff; - /*text/icons*/ - --accent-color: #FF4081; - --primary-background-color: #c5cae9; - --primary-text-color: #212121; - --secondary-text-color: #727272; - --disabled-text-color: #bdbdbd; - --divider-color: #B6B6B6; - /* Components */ - /* paper-drawer-panel */ - --drawer-menu-color: #ffffff; - --drawer-border-color: 1px solid #ccc; - --drawer-toolbar-border-color: 1px solid rgba(0, 0, 0, 0.22); - /* paper-menu */ - --paper-menu-background-color: #fff; - --menu-link-color: #111111; -} - -paper-progress { - width: 100%; - z-index: 10000; - position: absolute; - top: 0; -} -neon-animated-pages{ - height: 100vh; -} -</style> diff --git a/app/styles/icons.html b/app/styles/icons.html @@ -1,64 +0,0 @@ -<link rel="import" href="../bower_components/iron-iconset-svg/iron-iconset-svg.html"> -<iron-iconset-svg name="chat" size="24"> - <svg> - <defs> - <g id="notifications-off"> - <path d="M11.5 22c1.1 0 2-.9 2-2h-4c0 1.1.9 2 2 2zM18 10.5c0-3.07-2.13-5.64-5-6.32V3.5c0-.83-.67-1.5-1.5-1.5S10 2.67 10 3.5v.68c-.51.12-.99.32-1.45.56L18 14.18V10.5zm-.27 8.5l2 2L21 19.73 4.27 3 3 4.27l2.92 2.92C5.34 8.16 5 9.29 5 10.5V16l-2 2v1h14.73z" /> - </g> - <g id="share"> - <path d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81 1.66 0 3-1.34 3-3s-1.34-3-3-3-3 1.34-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9c-1.66 0-3 1.34-3 3s1.34 3 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.16c-.05.21-.08.43-.08.65 0 1.61 1.31 2.92 2.92 2.92 1.61 0 2.92-1.31 2.92-2.92s-1.31-2.92-2.92-2.92z" /> - </g> - <g id="call"> - <path d="M6.62 10.79c1.44 2.83 3.76 5.14 6.59 6.59l2.2-2.2c.27-.27.67-.36 1.02-.24 1.12.37 2.33.57 3.57.57.55 0 1 .45 1 1V20c0 .55-.45 1-1 1-9.39 0-17-7.61-17-17 0-.55.45-1 1-1h3.5c.55 0 1 .45 1 1 0 1.25.2 2.45.57 3.57.11.35.03.74-.25 1.02l-2.2 2.2z" /> - </g> - <g id="wifi-tethering"> - <path d="M12 11c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 2c0-3.31-2.69-6-6-6s-6 2.69-6 6c0 2.22 1.21 4.15 3 5.19l1-1.74c-1.19-.7-2-1.97-2-3.45 0-2.21 1.79-4 4-4s4 1.79 4 4c0 1.48-.81 2.75-2 3.45l1 1.74c1.79-1.04 3-2.97 3-5.19zM12 3C6.48 3 2 7.48 2 13c0 3.7 2.01 6.92 4.99 8.65l1-1.73C5.61 18.53 4 15.96 4 13c0-4.42 3.58-8 8-8s8 3.58 8 8c0 2.96-1.61 5.53-4 6.92l1 1.73c2.99-1.73 5-4.95 5-8.65 0-5.52-4.48-10-10-10z" /> - </g> - <g id="attach-file"> - <path d="M16.5 6v11.5c0 2.21-1.79 4-4 4s-4-1.79-4-4V5c0-1.38 1.12-2.5 2.5-2.5s2.5 1.12 2.5 2.5v10.5c0 .55-.45 1-1 1s-1-.45-1-1V6H10v9.5c0 1.38 1.12 2.5 2.5 2.5s2.5-1.12 2.5-2.5V5c0-2.21-1.79-4-4-4S7 2.79 7 5v12.5c0 3.04 2.46 5.5 5.5 5.5s5.5-2.46 5.5-5.5V6h-1.5z" /> - </g> - <g id="desktop-mac"> - <path d="M21 2H3c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h7l-2 3v1h8v-1l-2-3h7c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 12H3V4h18v10z" /> - </g> - <g id="desktop-windows"> - <path d="M21 2H3c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h7v2H8v2h8v-2h-2v-2h7c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H3V4h18v12z" /> - </g> - <g id="smartphone"> - <path d="M17 1.01L7 1c-1.1 0-2 .9-2 2v18c0 1.1.9 2 2 2h10c1.1 0 2-.9 2-2V3c0-1.1-.9-1.99-2-1.99zM17 19H7V5h10v14z" /> - </g> - <g id="phone-iphone"> - <path d="M15.5 1h-8C6.12 1 5 2.12 5 3.5v17C5 21.88 6.12 23 7.5 23h8c1.38 0 2.5-1.12 2.5-2.5v-17C18 2.12 16.88 1 15.5 1zm-4 21c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zm4.5-4H7V4h9v14z" /> - </g> - <g id="tablet-mac"> - <path d="M18.5 0h-14C3.12 0 2 1.12 2 2.5v19C2 22.88 3.12 24 4.5 24h14c1.38 0 2.5-1.12 2.5-2.5v-19C21 1.12 19.88 0 18.5 0zm-7 23c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zm7.5-4H4V3h15v16z" /> - </g> - <g id="info-outline"> - <path d="M11 17h2v-6h-2v6zm1-15C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zM11 9h2V7h-2v2z" /> - </g> - <g id="close"> - <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" /> - </g> - <g id="local-cafe"> - <path d="M20 3H4v10c0 2.21 1.79 4 4 4h6c2.21 0 4-1.79 4-4v-3h2c1.11 0 2-.89 2-2V5c0-1.11-.89-2-2-2zm0 5h-2V5h2v3zM2 21h18v-2H2v2z" /> - </g> - <g id="menu"> - <path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z" /> - </g> - <g id="help-outline"> - <path d="M11 18h2v-2h-2v2zm1-16C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm0-14c-2.21 0-4 1.79-4 4h2c0-1.1.9-2 2-2s2 .9 2 2c0 2-3 1.75-3 5h2c0-2.25 3-2.5 3-5 0-2.21-1.79-4-4-4z" /> - </g> - <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="twitter" x="0px" y="0px" width="430.117px" height="430.117px" viewBox="0 0 430.117 430.117" style="enable-background:new 0 0 430.117 430.117;" xml:space="preserve"> - <g> - <path id="twitter1" d="M381.384,198.639c24.157-1.993,40.543-12.975,46.849-27.876 c-8.714,5.353-35.764,11.189-50.703,5.631c-0.732-3.51-1.55-6.844-2.353-9.854c-11.383-41.798-50.357-75.472-91.194-71.404 c3.304-1.334,6.655-2.576,9.996-3.691c4.495-1.61,30.868-5.901,26.715-15.21c-3.5-8.188-35.722,6.188-41.789,8.067 c8.009-3.012,21.254-8.193,22.673-17.396c-12.27,1.683-24.315,7.484-33.622,15.919c3.36-3.617,5.909-8.025,6.45-12.769 C241.68,90.963,222.563,133.113,207.092,174c-12.148-11.773-22.915-21.044-32.574-26.192 c-27.097-14.531-59.496-29.692-110.355-48.572c-1.561,16.827,8.322,39.201,36.8,54.08c-6.17-0.826-17.453,1.017-26.477,3.178 c3.675,19.277,15.677,35.159,48.169,42.839c-14.849,0.98-22.523,4.359-29.478,11.642c6.763,13.407,23.266,29.186,52.953,25.947 c-33.006,14.226-13.458,40.571,13.399,36.642C113.713,320.887,41.479,317.409,0,277.828 c108.299,147.572,343.716,87.274,378.799-54.866c26.285,0.224,41.737-9.105,51.318-19.39 C414.973,206.142,393.023,203.486,381.384,198.639z" /> - </g> - </svg> - <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="facebook" x="0px" y="0px" width="90px" height="90px" viewBox="0 0 90 90" style="enable-background:new 0 0 90 90;" xml:space="preserve"> - <path id="Facebook__x28_alt_x29_" d="M90,15.001C90,7.119,82.884,0,75,0H15C7.116,0,0,7.119,0,15.001v59.998 C0,82.881,7.116,90,15.001,90H45V56H34V41h11v-5.844C45,25.077,52.568,16,61.875,16H74v15H61.875C60.548,31,59,32.611,59,35.024V41 h15v15H59v34h16c7.884,0,15-7.119,15-15.001V15.001z" /> - </svg> - <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="github" x="0px" y="0px" width="438.549px" height="438.549px" viewBox="0 0 438.549 438.549" style="enable-background:new 0 0 438.549 438.549;" xml:space="preserve"> - <g> - <path d="M409.132,114.573c-19.608-33.596-46.205-60.194-79.798-79.8C295.736,15.166,259.057,5.365,219.271,5.365 c-39.781,0-76.472,9.804-110.063,29.408c-33.596,19.605-60.192,46.204-79.8,79.8C9.803,148.168,0,184.854,0,224.63 c0,47.78,13.94,90.745,41.827,128.906c27.884,38.164,63.906,64.572,108.063,79.227c5.14,0.954,8.945,0.283,11.419-1.996 c2.475-2.282,3.711-5.14,3.711-8.562c0-0.571-0.049-5.708-0.144-15.417c-0.098-9.709-0.144-18.179-0.144-25.406l-6.567,1.136 c-4.187,0.767-9.469,1.092-15.846,1c-6.374-0.089-12.991-0.757-19.842-1.999c-6.854-1.231-13.229-4.086-19.13-8.559 c-5.898-4.473-10.085-10.328-12.56-17.556l-2.855-6.57c-1.903-4.374-4.899-9.233-8.992-14.559 c-4.093-5.331-8.232-8.945-12.419-10.848l-1.999-1.431c-1.332-0.951-2.568-2.098-3.711-3.429c-1.142-1.331-1.997-2.663-2.568-3.997 c-0.572-1.335-0.098-2.43,1.427-3.289c1.525-0.859,4.281-1.276,8.28-1.276l5.708,0.853c3.807,0.763,8.516,3.042,14.133,6.851 c5.614,3.806,10.229,8.754,13.846,14.842c4.38,7.806,9.657,13.754,15.846,17.847c6.184,4.093,12.419,6.136,18.699,6.136 c6.28,0,11.704-0.476,16.274-1.423c4.565-0.952,8.848-2.383,12.847-4.285c1.713-12.758,6.377-22.559,13.988-29.41 c-10.848-1.14-20.601-2.857-29.264-5.14c-8.658-2.286-17.605-5.996-26.835-11.14c-9.235-5.137-16.896-11.516-22.985-19.126 c-6.09-7.614-11.088-17.61-14.987-29.979c-3.901-12.374-5.852-26.648-5.852-42.826c0-23.035,7.52-42.637,22.557-58.817 c-7.044-17.318-6.379-36.732,1.997-58.24c5.52-1.715,13.706-0.428,24.554,3.853c10.85,4.283,18.794,7.952,23.84,10.994 c5.046,3.041,9.089,5.618,12.135,7.708c17.705-4.947,35.976-7.421,54.818-7.421s37.117,2.474,54.823,7.421l10.849-6.849 c7.419-4.57,16.18-8.758,26.262-12.565c10.088-3.805,17.802-4.853,23.134-3.138c8.562,21.509,9.325,40.922,2.279,58.24 c15.036,16.18,22.559,35.787,22.559,58.817c0,16.178-1.958,30.497-5.853,42.966c-3.9,12.471-8.941,22.457-15.125,29.979 c-6.191,7.521-13.901,13.85-23.131,18.986c-9.232,5.14-18.182,8.85-26.84,11.136c-8.662,2.286-18.415,4.004-29.263,5.146 c9.894,8.562,14.842,22.077,14.842,40.539v60.237c0,3.422,1.19,6.279,3.572,8.562c2.379,2.279,6.136,2.95,11.276,1.995 c44.163-14.653,80.185-41.062,108.068-79.226c27.88-38.161,41.825-81.126,41.825-128.906 C438.536,184.851,428.728,148.168,409.132,114.573z" /> - </svg> - </defs> - </svg> -</iron-iconset-svg> diff --git a/app/styles/main.css b/app/styles/main.css @@ -1,16 +0,0 @@ -html, -body { - height: 100%; - width: 100%; - padding: 0; - margin: 0; -} - -body { - background: #fafafa; - font-family: 'Roboto', 'Helvetica Neue', Helvetica, Arial, sans-serif; - color: #333; - -webkit-font-smoothing: antialiased; - overflow-x: hidden; -} - diff --git a/app/sw-import.js b/app/sw-import.js @@ -1 +0,0 @@ -importScripts('bower_components/platinum-sw/service-worker.js'); diff --git a/bower.json b/bower.json @@ -1,22 +0,0 @@ -{ - "name": "sharewithme", - "private": true, - "dependencies": { - "iron-elements": "PolymerElements/iron-elements#^1.0.0", - "neon-elements": "PolymerElements/neon-elements#^1.0.0", - "page": "visionmedia/page.js#~1.6.4", - "paper-elements": "PolymerElements/paper-elements#^1.0.1", - "platinum-elements": "PolymerElements/platinum-elements#^1.1.0", - "polymer": "Polymer/polymer#^1.2.0", - "paper-menu": "PolymerElements/paper-menu#4fecb43601", - "peerjs": "~0.3.14", - "clipboard": "~1.5.5" - }, - "devDependencies": { - "web-component-tester": "*" - }, - "ignore": [], - "resolutions": { - "paper-menu": "4fecb43601" - } -} diff --git a/client/images/android-chrome-192x192.png b/client/images/android-chrome-192x192.png Binary files differ. diff --git a/client/images/android-chrome-512x512.png b/client/images/android-chrome-512x512.png Binary files differ. diff --git a/client/images/apple-touch-icon.png b/client/images/apple-touch-icon.png Binary files differ. diff --git a/client/images/favicon-96x96.png b/client/images/favicon-96x96.png Binary files differ. diff --git a/client/images/logo_blue_512x512.png b/client/images/logo_blue_512x512.png Binary files differ. diff --git a/client/images/logo_transparent_128x128.png b/client/images/logo_transparent_128x128.png Binary files differ. diff --git a/client/images/logo_transparent_512x512.png b/client/images/logo_transparent_512x512.png Binary files differ. diff --git a/client/images/logo_transparent_white_512x512.png b/client/images/logo_transparent_white_512x512.png Binary files differ. diff --git a/client/images/logo_white_512x512.png b/client/images/logo_white_512x512.png Binary files differ. diff --git a/client/images/mstile-150x150.png b/client/images/mstile-150x150.png Binary files differ. diff --git a/client/images/safari-pinned-tab.svg b/client/images/safari-pinned-tab.svg @@ -0,0 +1,251 @@ +<?xml version="1.0" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" + "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> +<svg version="1.0" xmlns="http://www.w3.org/2000/svg" + width="512.000000pt" height="512.000000pt" viewBox="0 0 512.000000 512.000000" + preserveAspectRatio="xMidYMid meet"> +<metadata> +Created by potrace 1.11, written by Peter Selinger 2001-2013 +</metadata> +<g transform="translate(0.000000,512.000000) scale(0.100000,-0.100000)" +fill="#000000" stroke="none"> +<path d="M2325 4214 c-225 -34 -366 -76 -544 -161 -361 -172 -651 -455 -830 +-809 -135 -268 -194 -532 -186 -839 2 -88 6 -173 9 -190 3 -16 8 -43 11 -60 3 +-16 7 -41 10 -55 3 -14 7 -36 10 -50 9 -51 54 -188 86 -265 94 -229 217 -413 +389 -585 111 -111 139 -136 212 -186 29 -20 60 -42 68 -49 8 -7 34 -22 57 -35 +36 -20 43 -21 55 -9 7 8 14 20 15 26 2 7 20 38 40 70 21 32 38 63 38 68 0 6 +10 9 22 9 12 -1 21 4 20 10 -1 6 3 10 8 9 6 -1 12 7 12 18 2 18 -1 17 -19 -6 +-29 -38 -32 -25 -5 24 13 22 26 38 30 35 3 -3 4 2 2 13 -2 10 -4 20 -4 23 -1 +3 -21 16 -46 29 -28 15 -44 29 -41 37 3 8 0 11 -10 7 -18 -7 -46 16 -37 31 3 +6 3 8 -2 4 -5 -5 -24 4 -44 19 -55 40 -180 166 -181 181 0 7 -4 10 -10 7 -5 +-3 -10 1 -10 9 0 9 -4 16 -9 16 -4 0 -27 26 -49 58 -29 42 -38 62 -30 70 7 7 +5 10 -7 9 -10 -1 -20 4 -23 12 -3 9 0 11 9 6 8 -5 11 -4 6 1 -5 5 -14 9 -20 9 +-7 0 -13 9 -15 19 -2 11 -10 26 -18 34 -8 7 -12 18 -9 23 4 5 2 9 -2 9 -5 0 +-14 9 -21 21 -10 15 -10 24 -2 34 8 10 8 15 -1 21 -8 4 -9 3 -5 -4 4 -7 3 -12 +-2 -12 -9 0 -39 69 -47 108 -2 11 -8 29 -13 39 -5 10 -8 28 -7 40 1 12 -2 20 +-7 17 -4 -3 -9 4 -9 15 -2 23 -3 29 -12 51 -11 29 -29 161 -23 175 3 8 2 15 +-2 15 -4 0 -7 24 -8 54 -1 48 1 54 17 51 10 -2 21 0 24 4 2 5 -5 8 -18 7 -19 +-1 -22 4 -22 34 0 27 4 35 17 34 10 -1 15 3 12 8 -3 5 -12 7 -20 4 -17 -7 -18 +6 -1 23 6 8 7 11 2 8 -7 -3 -9 9 -7 34 3 25 8 37 16 32 8 -4 8 -3 0 6 -8 9 -9 +25 -2 54 5 23 12 51 14 62 2 11 6 30 9 43 3 12 7 32 10 43 11 53 28 95 43 107 +10 7 11 12 4 12 -9 0 -9 5 -3 18 5 9 22 45 38 80 19 42 32 61 42 57 11 -4 11 +-2 2 9 -7 8 -8 16 -4 18 4 1 18 23 31 48 24 47 50 85 92 130 14 15 30 36 36 +46 6 11 16 19 22 19 6 0 14 4 19 9 5 5 3 6 -4 2 -20 -11 -15 1 11 29 14 15 29 +24 34 21 6 -3 9 2 8 12 0 10 6 16 16 16 10 -1 15 3 12 8 -7 10 36 45 49 40 5 +-1 6 2 3 7 -8 12 21 34 34 26 6 -4 9 -2 8 3 -4 12 52 62 70 62 6 0 12 4 12 8 +0 4 15 16 33 27 17 10 34 21 37 25 12 16 101 51 111 44 8 -5 9 -3 4 6 -6 10 +-4 12 9 7 10 -4 15 -3 11 3 -6 10 56 40 88 43 9 1 17 5 17 10 0 4 4 6 8 3 4 +-2 14 -1 22 4 9 5 19 5 27 -2 11 -8 12 -7 7 7 -5 12 -4 16 4 11 6 -3 13 -2 16 +3 4 5 14 8 24 7 9 -2 19 0 22 5 3 4 21 10 40 12 19 3 35 6 35 7 0 3 42 10 87 +14 26 2 51 7 56 10 6 3 13 0 15 -6 4 -10 6 -10 6 0 1 16 151 17 151 1 0 -6 6 +-4 14 3 17 17 89 12 79 -6 -4 -6 1 -5 10 3 9 7 17 10 17 5 0 -5 19 -9 43 -10 +43 -1 91 -7 137 -19 14 -4 32 -8 40 -9 8 -1 22 -5 30 -8 8 -3 51 -17 95 -32 +43 -15 82 -30 85 -34 3 -4 12 -8 20 -10 16 -3 121 -53 130 -62 3 -3 17 -11 32 +-19 15 -8 36 -22 47 -32 10 -10 21 -15 25 -12 3 3 8 -2 12 -11 3 -9 11 -16 17 +-16 33 0 237 -202 320 -317 180 -253 268 -536 262 -847 -1 -83 -5 -162 -9 +-176 -3 -13 -8 -41 -11 -61 -3 -20 -10 -50 -15 -68 -5 -17 -9 -35 -9 -39 -1 +-4 -13 -43 -27 -87 -35 -107 -110 -257 -180 -357 -78 -112 -258 -290 -366 +-362 -49 -32 -88 -62 -88 -67 0 -4 10 -25 21 -46 33 -60 142 -247 146 -253 3 +-2 18 3 34 12 16 10 37 15 47 12 14 -5 15 -4 2 5 -8 6 -12 11 -7 12 4 1 10 2 +15 3 4 0 18 11 31 23 13 12 27 20 31 18 5 -3 11 2 14 11 4 9 13 14 22 10 8 -3 +12 -2 9 3 -7 12 84 83 96 75 5 -3 6 2 3 10 -4 11 0 16 11 16 9 0 13 5 10 10 +-6 10 9 15 30 10 5 -1 7 2 3 6 -11 10 12 35 25 27 5 -3 7 -2 4 4 -4 5 5 20 18 +31 14 12 25 27 25 34 0 6 3 9 6 6 6 -7 33 18 51 48 6 10 16 18 23 16 6 -1 9 2 +5 7 -3 6 2 18 12 28 10 10 30 38 46 61 15 23 32 42 37 42 6 0 9 6 8 13 -2 6 3 +11 11 9 8 -2 11 3 8 11 -7 19 21 59 35 51 6 -4 8 1 3 15 -4 15 -2 21 9 21 9 0 +13 6 10 14 -3 8 0 17 5 21 6 3 9 11 6 16 -4 5 -2 9 3 9 4 0 15 16 22 35 9 24 +19 34 28 30 12 -4 13 -3 2 10 -9 11 -9 15 -1 15 7 0 10 4 7 9 -3 5 3 28 13 52 +17 40 22 54 32 84 1 6 10 17 19 25 9 8 10 11 3 6 -10 -6 -10 1 1 32 8 23 20 +68 27 101 6 34 15 59 18 56 4 -2 5 10 3 27 -3 17 -1 28 4 25 5 -3 9 11 10 31 +1 97 5 133 12 129 4 -3 7 6 7 19 0 13 -3 24 -6 24 -4 0 -3 17 0 38 4 20 6 61 +4 90 -1 29 2 50 7 47 5 -3 12 0 16 6 4 8 3 9 -4 5 -16 -10 -34 28 -20 42 9 9 +8 12 -2 12 -13 0 -13 2 0 10 12 8 12 10 1 10 -11 0 -11 3 0 17 8 9 9 14 3 10 +-12 -7 -29 97 -19 114 4 5 2 9 -2 9 -5 0 -10 14 -11 31 -1 17 -7 39 -12 49 -6 +10 -5 21 1 28 5 7 6 10 2 7 -8 -6 -65 168 -67 202 -1 11 -5 19 -10 15 -5 -3 +-7 2 -3 11 4 10 2 17 -4 17 -6 0 -8 7 -5 16 3 8 2 12 -4 9 -9 -6 -14 8 -11 28 +0 4 -4 7 -10 7 -5 0 -8 4 -5 9 4 5 1 11 -4 13 -6 2 -12 10 -14 18 -1 8 -8 25 +-14 38 -7 12 -9 22 -5 22 4 0 -1 6 -12 13 -11 9 -15 19 -10 27 5 9 4 11 -3 6 +-7 -4 -12 -2 -12 4 0 6 -11 27 -25 47 -13 20 -21 41 -18 47 3 6 2 8 -2 3 -11 +-9 -46 43 -38 57 3 6 3 8 -2 4 -4 -4 -24 14 -44 40 -45 59 -49 64 -89 107 -19 +20 -30 40 -26 47 4 6 3 8 -4 5 -11 -8 -112 85 -112 103 0 6 -4 9 -9 5 -5 -3 +-17 5 -25 17 -9 12 -16 18 -16 13 0 -6 -3 -6 -8 0 -11 16 -111 89 -182 134 +-36 22 -69 44 -75 48 -5 5 -45 24 -87 44 -43 20 -74 40 -70 44 4 5 2 5 -4 2 +-11 -6 -54 7 -109 33 -23 11 -128 43 -195 58 -84 20 -104 24 -230 41 -91 13 +-339 10 -435 -5z"/> +<path d="M2470 3856 c0 -2 8 -10 18 -17 15 -13 16 -12 3 4 -13 16 -21 21 -21 +13z"/> +<path d="M2333 3825 c0 -8 4 -12 9 -9 5 3 6 10 3 15 -9 13 -12 11 -12 -6z"/> +<path d="M2365 3820 c-3 -6 1 -7 9 -4 18 7 21 14 7 14 -6 0 -13 -4 -16 -10z"/> +<path d="M1993 3715 c0 -8 4 -12 9 -9 5 3 6 10 3 15 -9 13 -12 11 -12 -6z"/> +<path d="M1936 3658 c3 -5 10 -6 15 -3 13 9 11 12 -6 12 -8 0 -12 -4 -9 -9z"/> +<path d="M1710 3555 c5 -7 6 -16 2 -21 -4 -5 -3 -5 2 -1 15 12 15 34 0 34 -9 +0 -11 -4 -4 -12z"/> +<path d="M1650 3515 c5 -7 6 -16 2 -21 -4 -5 -3 -5 2 -1 15 12 15 34 0 34 -9 +0 -11 -4 -4 -12z"/> +<path d="M2425 3508 c-152 -17 -331 -84 -468 -175 -88 -57 -238 -206 -292 +-288 -86 -132 -146 -280 -170 -423 -16 -99 -14 -299 5 -377 6 -27 14 -61 16 +-74 10 -48 65 -178 108 -254 48 -84 140 -202 181 -233 14 -10 25 -25 25 -32 0 +-7 4 -11 8 -8 4 2 23 -9 42 -26 31 -27 94 -72 127 -91 7 -5 24 13 44 47 18 30 +36 53 40 50 4 -2 6 4 5 14 0 9 4 16 11 15 7 -2 10 3 7 11 -3 7 2 21 11 30 9 9 +13 16 9 16 -3 0 -2 6 3 13 22 28 75 130 65 123 -13 -8 -100 48 -109 70 -3 8 +-9 14 -14 14 -17 0 -99 95 -94 108 3 8 0 10 -7 6 -8 -5 -9 -2 -5 10 4 10 3 15 +-3 11 -12 -7 -44 41 -35 55 4 6 1 9 -5 8 -12 -3 -51 87 -42 96 3 4 1 6 -5 6 +-18 0 -37 121 -36 225 2 84 7 140 13 140 2 0 5 16 14 59 3 16 13 38 23 49 10 +11 13 17 6 14 -8 -5 -8 -1 0 18 8 16 17 23 27 19 11 -4 12 -2 3 7 -9 9 -6 20 +10 49 34 58 79 110 90 103 6 -3 8 -2 4 3 -3 5 1 18 9 29 8 10 14 16 14 12 0 +-4 13 7 29 24 17 17 35 28 41 24 6 -3 9 -1 8 7 -2 7 5 12 14 12 10 -1 16 3 15 +9 -2 11 124 77 136 70 4 -2 7 1 7 7 0 7 6 10 14 7 8 -3 16 -2 18 3 2 4 19 11 +38 15 19 3 51 9 70 12 46 9 186 9 230 0 19 -4 50 -10 67 -13 19 -4 30 -11 26 +-17 -3 -6 -1 -7 6 -3 16 10 53 -3 45 -16 -4 -7 -2 -8 5 -4 16 11 83 -22 75 +-36 -4 -6 -3 -8 4 -5 14 9 43 -3 36 -14 -3 -5 1 -7 8 -4 7 3 27 -8 44 -23 17 +-15 47 -41 66 -58 53 -47 114 -133 152 -215 96 -207 83 -452 -34 -649 -43 -74 +-145 -181 -213 -227 -53 -35 -60 -12 57 -210 l78 -132 24 15 c13 9 24 13 24 9 +0 -5 8 2 19 13 10 12 21 19 25 15 3 -3 6 -1 6 6 0 8 6 11 16 7 8 -3 12 -2 9 4 +-3 6 -1 10 6 10 7 0 9 3 6 7 -4 3 9 17 28 30 19 13 35 29 35 35 0 6 10 3 22 +-8 16 -14 19 -14 10 -2 -11 14 -9 21 18 50 18 18 28 26 24 18 -4 -9 -3 -12 2 +-7 5 5 9 16 9 25 0 9 11 22 25 29 14 7 19 13 12 13 -10 0 -9 4 2 16 9 8 21 24 +27 35 9 17 13 18 27 7 15 -12 16 -11 4 4 -12 15 -11 22 7 47 12 16 21 32 21 +36 0 4 9 20 21 36 11 16 17 29 13 29 -4 0 1 7 12 16 10 8 12 12 4 8 -12 -6 +-13 -5 -4 7 6 8 17 35 25 62 7 26 16 47 20 47 4 0 6 6 6 13 -3 34 28 165 37 +160 8 -5 8 -3 0 8 -9 13 -10 25 -4 47 6 21 4 172 -3 172 -4 0 -3 10 4 21 6 12 +7 19 1 15 -5 -3 -12 15 -16 42 -3 26 -9 61 -12 77 -4 17 -7 36 -7 43 0 6 -4 +12 -9 12 -5 0 -7 4 -3 9 3 5 1 12 -4 15 -5 4 -12 24 -16 46 -4 22 -10 40 -14 +40 -5 0 -15 18 -23 39 -15 36 -14 39 1 35 10 -4 9 -1 -4 7 -29 18 -45 42 -39 +59 3 8 2 11 -2 7 -4 -4 -16 5 -25 20 -14 21 -15 29 -6 36 8 6 6 7 -6 3 -11 -4 +-20 1 -24 12 -4 9 -13 22 -21 28 -8 6 -12 16 -8 23 4 6 4 10 -1 9 -5 -2 -38 +25 -73 59 -36 34 -89 80 -117 101 -29 21 -49 43 -45 47 4 5 2 5 -5 2 -6 -4 +-19 0 -27 9 -9 8 -16 13 -16 10 0 -4 -17 4 -37 17 -162 99 -432 151 -658 125z"/> +<path d="M1435 3301 c-3 -5 -2 -12 3 -15 5 -3 9 1 9 9 0 17 -3 19 -12 6z"/> +<path d="M1461 3276 c-9 -11 -9 -16 1 -22 7 -4 10 -4 6 1 -4 4 -3 14 3 22 6 7 +9 13 6 13 -2 0 -10 -6 -16 -14z"/> +<path d="M1389 3217 c6 -8 7 -18 3 -22 -4 -5 -1 -5 6 -1 10 6 10 11 1 22 -6 8 +-14 14 -16 14 -3 0 0 -6 6 -13z"/> +<path d="M1350 3200 c0 -5 5 -10 11 -10 5 0 7 5 4 10 -3 6 -8 10 -11 10 -2 0 +-4 -4 -4 -10z"/> +<path d="M2520 3140 c-9 -6 -10 -10 -3 -10 6 0 15 5 18 10 8 12 4 12 -15 0z"/> +<path d="M1339 3113 c-13 -16 -12 -17 4 -4 16 13 21 21 13 21 -2 0 -10 -8 -17 +-17z"/> +<path d="M2595 3120 c-3 -5 -1 -10 4 -10 6 0 11 5 11 10 0 6 -2 10 -4 10 -3 0 +-8 -4 -11 -10z"/> +<path d="M1336 3075 c-9 -26 -7 -32 5 -12 6 10 9 21 6 23 -2 3 -7 -2 -11 -11z"/> +<path d="M2330 3079 c0 -5 5 -7 10 -4 6 3 10 8 10 11 0 2 -4 4 -10 4 -5 0 -10 +-5 -10 -11z"/> +<path d="M2239 3068 c-5 -18 -6 -38 -1 -34 7 8 12 36 6 36 -2 0 -4 -1 -5 -2z"/> +<path d="M1274 3049 c-3 -6 -2 -15 3 -20 5 -5 9 -1 9 11 0 23 -2 24 -12 9z"/> +<path d="M2185 3020 c-3 -6 1 -7 9 -4 18 7 21 14 7 14 -6 0 -13 -4 -16 -10z"/> +<path d="M2255 3019 c-3 -4 2 -6 10 -5 21 3 28 13 10 13 -9 0 -18 -4 -20 -8z"/> +<path d="M2153 2995 c0 -8 4 -12 9 -9 5 3 6 10 3 15 -9 13 -12 11 -12 -6z"/> +<path d="M2116 2982 c-3 -5 1 -9 9 -9 8 0 12 4 9 9 -3 4 -7 8 -9 8 -2 0 -6 -4 +-9 -8z"/> +<path d="M2086 2962 c-3 -5 1 -9 9 -9 8 0 12 4 9 9 -3 4 -7 8 -9 8 -2 0 -6 -4 +-9 -8z"/> +<path d="M1216 2922 c-3 -5 1 -9 9 -9 8 0 12 4 9 9 -3 4 -7 8 -9 8 -2 0 -6 -4 +-9 -8z"/> +<path d="M2070 2919 c0 -5 5 -7 10 -4 6 3 10 8 10 11 0 2 -4 4 -10 4 -5 0 -10 +-5 -10 -11z"/> +<path d="M1210 2896 c0 -2 8 -10 18 -17 15 -13 16 -12 3 4 -13 16 -21 21 -21 +13z"/> +<path d="M1195 2860 c-3 -5 -1 -10 4 -10 6 0 11 5 11 10 0 6 -2 10 -4 10 -3 0 +-8 -4 -11 -10z"/> +<path d="M1256 2858 c3 -5 10 -6 15 -3 13 9 11 12 -6 12 -8 0 -12 -4 -9 -9z"/> +<path d="M1993 2835 c0 -8 4 -12 9 -9 4 3 8 9 8 15 0 5 -4 9 -8 9 -5 0 -9 -7 +-9 -15z"/> +<path d="M2030 2839 c0 -5 5 -7 10 -4 6 3 10 8 10 11 0 2 -4 4 -10 4 -5 0 -10 +-5 -10 -11z"/> +<path d="M1190 2825 c0 -7 30 -13 34 -7 3 4 -4 9 -15 9 -10 1 -19 0 -19 -2z"/> +<path d="M1193 2803 c4 -3 1 -13 -6 -22 -11 -14 -10 -14 5 -2 16 12 16 31 1 +31 -4 0 -3 -3 0 -7z"/> +<path d="M2493 2795 c-122 -27 -209 -94 -260 -202 -64 -138 -24 -318 92 -415 +39 -33 101 -68 120 -68 8 0 15 -4 15 -8 0 -13 143 -14 196 -1 27 7 68 25 91 +41 23 15 47 28 53 28 6 0 9 3 7 8 -3 4 1 13 9 20 8 7 14 9 14 5 1 -4 10 9 21 +30 11 20 24 38 29 38 6 1 14 2 19 3 5 0 13 4 17 8 3 4 -3 6 -15 3 -23 -4 -29 +10 -7 18 7 3 14 16 14 29 2 41 9 63 20 63 6 0 14 4 19 8 4 5 1 7 -7 5 -12 -2 +-15 7 -15 45 0 26 -4 47 -9 47 -5 0 -2 8 5 17 10 11 10 14 2 9 -8 -4 -13 -2 +-13 8 0 8 -5 27 -11 43 -6 15 -11 30 -12 33 -2 7 -24 34 -64 80 -40 45 -132 +96 -193 105 -25 3 -52 8 -60 10 -8 2 -43 -3 -77 -10z"/> +<path d="M1953 2775 c0 -8 4 -12 9 -9 5 3 6 10 3 15 -9 13 -12 11 -12 -6z"/> +<path d="M1987 2779 c7 -7 15 -10 18 -7 3 3 -2 9 -12 12 -14 6 -15 5 -6 -5z"/> +<path d="M4375 2761 c-3 -5 -2 -12 3 -15 5 -3 9 1 9 9 0 17 -3 19 -12 6z"/> +<path d="M3630 2739 c0 -5 5 -7 10 -4 6 3 10 8 10 11 0 2 -4 4 -10 4 -5 0 -10 +-5 -10 -11z"/> +<path d="M1929 2723 c-13 -16 -12 -17 4 -4 16 13 21 21 13 21 -2 0 -10 -8 -17 +-17z"/> +<path d="M1987 2719 c7 -7 15 -10 18 -7 3 3 -2 9 -12 12 -14 6 -15 5 -6 -5z"/> +<path d="M1949 2683 c-13 -16 -12 -17 4 -4 16 13 21 21 13 21 -2 0 -10 -8 -17 +-17z"/> +<path d="M1910 2681 c0 -6 4 -12 8 -15 5 -3 9 1 9 9 0 8 -4 15 -9 15 -4 0 -8 +-4 -8 -9z"/> +<path d="M1155 2661 c-3 -5 -2 -12 3 -15 5 -3 9 1 9 9 0 17 -3 19 -12 6z"/> +<path d="M1894 2640 c0 -13 4 -16 10 -10 7 7 7 13 0 20 -6 6 -10 3 -10 -10z"/> +<path d="M3667 2639 c7 -7 15 -10 18 -7 3 3 -2 9 -12 12 -14 6 -15 5 -6 -5z"/> +<path d="M1879 2593 c-13 -16 -12 -17 4 -4 16 13 21 21 13 21 -2 0 -10 -8 -17 +-17z"/> +<path d="M4416 2542 c-3 -5 1 -9 9 -9 8 0 12 4 9 9 -3 4 -7 8 -9 8 -2 0 -6 -4 +-9 -8z"/> +<path d="M1894 2476 c1 -8 5 -18 8 -22 4 -3 5 1 4 10 -1 8 -5 18 -8 22 -4 3 +-5 -1 -4 -10z"/> +<path d="M2936 2447 c3 -10 9 -15 12 -12 3 3 0 11 -7 18 -10 9 -11 8 -5 -6z"/> +<path d="M4415 2441 c-3 -5 -2 -12 3 -15 5 -3 9 1 9 9 0 17 -3 19 -12 6z"/> +<path d="M1890 2421 c0 -6 4 -13 10 -16 6 -3 7 1 4 9 -7 18 -14 21 -14 7z"/> +<path d="M1186 2358 c3 -5 10 -6 15 -3 13 9 11 12 -6 12 -8 0 -12 -4 -9 -9z"/> +<path d="M1865 2360 c-3 -6 1 -7 9 -4 18 7 21 14 7 14 -6 0 -13 -4 -16 -10z"/> +<path d="M2926 2258 c3 -5 10 -6 15 -3 13 9 11 12 -6 12 -8 0 -12 -4 -9 -9z"/> +<path d="M1153 2235 c0 -8 4 -12 9 -9 5 3 6 10 3 15 -9 13 -12 11 -12 -6z"/> +<path d="M3633 2215 c0 -8 4 -12 9 -9 5 3 6 10 3 15 -9 13 -12 11 -12 -6z"/> +<path d="M2873 2175 c0 -8 4 -12 9 -9 5 3 6 10 3 15 -9 13 -12 11 -12 -6z"/> +<path d="M1186 2158 c3 -5 10 -6 15 -3 13 9 11 12 -6 12 -8 0 -12 -4 -9 -9z"/> +<path d="M3653 2149 c-2 -23 3 -25 10 -4 4 8 3 16 -1 19 -4 3 -9 -4 -9 -15z"/> +<path d="M4385 2120 c-3 -6 1 -7 9 -4 18 7 21 14 7 14 -6 0 -13 -4 -16 -10z"/> +<path d="M2770 2099 c0 -5 5 -7 10 -4 6 3 10 8 10 11 0 2 -4 4 -10 4 -5 0 -10 +-5 -10 -11z"/> +<path d="M1230 1939 c0 -5 5 -7 10 -4 6 3 10 8 10 11 0 2 -4 4 -10 4 -5 0 -10 +-5 -10 -11z"/> +<path d="M3519 1833 c-13 -16 -12 -17 4 -4 16 13 21 21 13 21 -2 0 -10 -8 -17 +-17z"/> +<path d="M4260 1846 c0 -2 8 -10 18 -17 15 -13 16 -12 3 4 -13 16 -21 21 -21 +13z"/> +<path d="M4251 1804 c0 -11 3 -14 6 -6 3 7 2 16 -1 19 -3 4 -6 -2 -5 -13z"/> +<path d="M2210 1779 c0 -5 5 -7 10 -4 6 3 10 8 10 11 0 2 -4 4 -10 4 -5 0 -10 +-5 -10 -11z"/> +<path d="M3467 1779 c7 -7 15 -10 18 -7 3 3 -2 9 -12 12 -14 6 -15 5 -6 -5z"/> +<path d="M3430 1739 c0 -5 5 -7 10 -4 6 3 10 8 10 11 0 2 -4 4 -10 4 -5 0 -10 +-5 -10 -11z"/> +<path d="M4230 1746 c0 -2 7 -7 16 -10 8 -3 12 -2 9 4 -6 10 -25 14 -25 6z"/> +<path d="M3396 1713 c-6 -14 -5 -15 5 -6 7 7 10 15 7 18 -3 3 -9 -2 -12 -12z"/> +<path d="M2136 1679 c4 -8 30 -6 38 2 3 3 -5 5 -19 5 -13 0 -22 -3 -19 -7z"/> +<path d="M4255 1680 c-3 -5 -1 -10 4 -10 6 0 11 5 11 10 0 6 -2 10 -4 10 -3 0 +-8 -4 -11 -10z"/> +<path d="M3354 1662 c4 -3 14 -7 22 -8 9 -1 13 0 10 4 -4 3 -14 7 -22 8 -9 1 +-13 0 -10 -4z"/> +<path d="M3296 1638 c3 -5 10 -6 15 -3 13 9 11 12 -6 12 -8 0 -12 -4 -9 -9z"/> +<path d="M4236 1638 c3 -5 10 -6 15 -3 13 9 11 12 -6 12 -8 0 -12 -4 -9 -9z"/> +<path d="M3294 1595 c0 -13 3 -22 7 -19 8 4 6 30 -2 38 -3 3 -5 -5 -5 -19z"/> +<path d="M1435 1600 c-3 -5 -1 -10 4 -10 6 0 11 5 11 10 0 6 -2 10 -4 10 -3 0 +-8 -4 -11 -10z"/> +<path d="M2076 1601 c-3 -5 2 -15 12 -22 15 -12 16 -12 5 2 -7 9 -10 19 -6 22 +3 4 4 7 0 7 -3 0 -8 -4 -11 -9z"/> +<path d="M3253 1595 c0 -8 4 -12 9 -9 5 3 6 10 3 15 -9 13 -12 11 -12 -6z"/> +<path d="M2115 1580 c3 -5 8 -10 11 -10 2 0 4 5 4 10 0 6 -5 10 -11 10 -5 0 +-7 -4 -4 -10z"/> +<path d="M3199 1553 c-13 -16 -12 -17 4 -4 9 7 17 15 17 17 0 8 -8 3 -21 -13z"/> +<path d="M3240 1520 c-9 -6 -10 -10 -3 -10 6 0 15 5 18 10 8 12 4 12 -15 0z"/> +<path d="M4105 1479 c-3 -4 2 -6 10 -5 21 3 28 13 10 13 -9 0 -18 -4 -20 -8z"/> +<path d="M4033 1355 c0 -8 4 -12 9 -9 5 3 6 10 3 15 -9 13 -12 11 -12 -6z"/> +<path d="M4006 1298 c3 -5 10 -6 15 -3 13 9 11 12 -6 12 -8 0 -12 -4 -9 -9z"/> +<path d="M3940 1285 c0 -2 6 -8 13 -14 10 -8 14 -7 14 2 0 8 -6 14 -14 14 -7 +0 -13 -1 -13 -2z"/> +<path d="M3827 1139 c7 -9 10 -19 6 -22 -3 -4 -1 -7 5 -7 17 0 15 16 -5 31 +-16 12 -17 12 -6 -2z"/> +<path d="M1810 1059 c0 -5 5 -7 10 -4 6 3 10 8 10 11 0 2 -4 4 -10 4 -5 0 -10 +-5 -10 -11z"/> +<path d="M3719 1053 c-13 -16 -12 -17 4 -4 16 13 21 21 13 21 -2 0 -10 -8 -17 +-17z"/> +<path d="M1774 1049 c-3 -6 -2 -15 3 -20 5 -5 9 -1 9 11 0 23 -2 24 -12 9z"/> +<path d="M3679 1028 c-5 -16 -4 -46 2 -42 4 2 7 13 6 24 -1 17 -5 26 -8 18z"/> +<path d="M1710 965 c0 -7 30 -13 34 -7 3 4 -4 9 -15 9 -10 1 -19 0 -19 -2z"/> +<path d="M3610 939 c0 -5 5 -7 10 -4 6 3 10 8 10 11 0 2 -4 4 -10 4 -5 0 -10 +-5 -10 -11z"/> +<path d="M3570 879 c0 -5 5 -7 10 -4 6 3 10 8 10 11 0 2 -4 4 -10 4 -5 0 -10 +-5 -10 -11z"/> +</g> +</svg> diff --git a/client/images/snapdrop-graphics.sketch b/client/images/snapdrop-graphics.sketch Binary files differ. diff --git a/client/images/twitter-stream.jpg b/client/images/twitter-stream.jpg Binary files differ. diff --git a/client/index.html b/client/index.html @@ -0,0 +1,200 @@ +<!DOCTYPE html> +<html lang="en"> + +<head> + <meta charset="utf-8"> + <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> + <!-- Web App Config --> + <title>Snapdrop</title> + <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no"> + <meta name="theme-color" content="#3367d6"> + <meta name="apple-mobile-web-app-capable" content="yes"> + <meta name="apple-mobile-web-app-status-bar-style" content="black"> + <meta name="apple-mobile-web-app-title" content="Snapdrop"> + <meta name="msapplication-TileColor" content="#3372DF"> + <meta name="mobile-web-app-capable" content="yes"> + <!-- Descriptions --> + <meta property="og:title" content="Snapdrop"> + <meta property="og:type" content="article"> + <meta property="og:url" content="https://snapdrop.net/"> + <meta property="og:author" content="https://facebook.com/RobinLinus"> + <meta name="twitter:author" content="@RobinLinus"> + <meta name="twitter:card" content="summary_large_image"> + <meta name="twitter:description" content="Instantly share images, videos, PDFs, and links with people nearby. Peer2Peer and Open Source. No Setup, No Signup."> + <meta name="og:description" content="Instantly share images, videos, PDFs, and links with people nearby. Peer2Peer and Open Source. No Setup, No Signup."> + <!-- Icons --> + <link rel="icon" sizes="96x96" href="images/favicon-96x96.png?"> + <link rel="shortcut icon" href="images/favicon-96x96.png?"> + <link rel="apple-touch-icon" href="images/apple-touch-icon.png"> + <meta name="msapplication-TileImage" content="images/mstile-150x150.png"> + <link rel="fluid-icon" type="image/png" href="images/android-chrome-192x192.png"> + <meta name="twitter:image" content="https://snapdrop.net/images/twitter-stream.jpg"> + <meta property="og:image" content="https://snapdrop.net/images/twitter-stream.jpg"> + <!-- Resources --> + <link rel="stylesheet" type="text/css" href="styles.css"> + <link rel="manifest" href="manifest.json"> +</head> + +<body> + <!-- Peers --> + <x-peers class="center"></x-peers> + <x-no-peers> + <h2>Open Snapdrop on other devices to send files.</h2> + <div class="font-body1">Short link: <a href="http://yg.gl">yg.gl</a></div> + </x-no-peers> + <x-instructions desktop="Click to send files or right click to send a message." mobile="Tap to send files or long tap to send a message."></x-instructions> + <!-- Footer --> + <footer class="column"> + <svg class="icon logo"> + <use xlink:href="#wifi-tethering" /> + </svg> + <div>The easiest way to transfer data across devices.</div> + <div class="font-body2">Allow me to be discovered by: Everyone in this network.</div> + </footer> + <!-- Receive Dialog --> + <x-dialog id="receiveDialog"> + <x-background class="full center"> + <x-paper shadow="2"> + <h3>File Received</h3> + <div class="font-subheading" id="fileName">Filename</div> + <div class="font-body2" id="fileSize"></div> + <div class="row-reverse"> + <a class="button" close id="download" title="Download File" autofocus>Download</a> + <button class="button" close>Ignore</button> + </div> + </x-paper> + </x-background> + </x-dialog> + <!-- Send Text Dialog --> + <x-dialog id="sendTextDialog"> + <form action="#"> + <x-background class="full center"> + <x-paper shadow="2"> + <h3>Send a Message</h3> + <input id="textInput" placeholder="Send a message" autocomplete="off" autofocus> + <div class="row-reverse"> + <button class="button" type="submit" close>Send</button> + <a class="button" close>Cancel</a> + </div> + </x-paper> + </x-background> + </form> + </x-dialog> + <!-- Receive Text Dialog --> + <x-dialog id="receiveTextDialog"> + <x-background class="full center"> + <x-paper shadow="2"> + <h3>Message Received</h3> + <div class="font-subheading" id="text"></div> + <div class="row-reverse"> + <button class="button" id="copy" close autofocus>Copy</button> + <button class="button" close>Close</button> + </div> + </x-paper> + </x-background> + </x-dialog> + <!-- Toast --> + <div class="toast-container full center"> + <x-toast class="row" shadow="1" id="toast">File Transfer Completed</x-toast> + </div> + <!-- Info Page --> + <div id="info" class="full center column"> + <a href="#" class="close icon-button"> + <svg class="icon"> + <use xlink:href="#close" /> + </svg> + </a> + <svg class="icon logo"> + <use xlink:href="#wifi-tethering" /> + </svg> + <h1>Snapdrop</h1> + <div class="font-subheading">The easiest way to transfer files across devices.</div> + <div class="row"> + <a class="icon-button" target="_blank" href="https://github.com/RobinLinus/snapdrop" title="Snapdrop on Github"> + <svg class="icon"> + <use xlink:href="#github" /> + </svg> + </a> + <a class="icon-button" target="_blank" href="https://twitter.com/intent/tweet?text=https://snapdrop.net%20by%20@robin_linus%20&" title="tweet about Snapdrop"> + <svg class="icon"> + <use xlink:href="#twitter" /> + </svg> + </a> + <a class="icon-button" target="_blank" href="https://github.com/RobinLinus/snapdrop#frequently-asked-questions" title="frequently asked questions"> + <svg class="icon"> + <use xlink:href="#help-outline" /> + </svg> + </a> + </div> + </div> + <a href="#info" class="icon-button" title="about Snapdrop"> + <svg class="icon"> + <use xlink:href="#info-outline" /> + </svg> + <div class="info-background"></div> + </a> + <a id="notification" class="icon-button" hidden title="enable Notifications"> + <svg class="icon"> + <use xlink:href="#notifications" /> + </svg> + </a> + <!-- SVG Icon Library --> + <svg style="display: none;"> + <symbol id=wifi-tethering viewBox="0 0 24 24"> + <path d="M12 11c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 2c0-3.31-2.69-6-6-6s-6 2.69-6 6c0 2.22 1.21 4.15 3 5.19l1-1.74c-1.19-.7-2-1.97-2-3.45 0-2.21 1.79-4 4-4s4 1.79 4 4c0 1.48-.81 2.75-2 3.45l1 1.74c1.79-1.04 3-2.97 3-5.19zM12 3C6.48 3 2 7.48 2 13c0 3.7 2.01 6.92 4.99 8.65l1-1.73C5.61 18.53 4 15.96 4 13c0-4.42 3.58-8 8-8s8 3.58 8 8c0 2.96-1.61 5.53-4 6.92l1 1.73c2.99-1.73 5-4.95 5-8.65 0-5.52-4.48-10-10-10z"></path> + </symbol> + <symbol id=desktop-mac viewBox="0 0 24 24"> + <path d="M21 2H3c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h7l-2 3v1h8v-1l-2-3h7c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 12H3V4h18v10z"></path> + </symbol> + <symbol id=phone-iphone viewBox="0 0 24 24"> + <path d="M15.5 1h-8C6.12 1 5 2.12 5 3.5v17C5 21.88 6.12 23 7.5 23h8c1.38 0 2.5-1.12 2.5-2.5v-17C18 2.12 16.88 1 15.5 1zm-4 21c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zm4.5-4H7V4h9v14z"></path> + </symbol> + <symbol id=tablet-mac viewBox="0 0 24 24"> + <path d="M18.5 0h-14C3.12 0 2 1.12 2 2.5v19C2 22.88 3.12 24 4.5 24h14c1.38 0 2.5-1.12 2.5-2.5v-19C21 1.12 19.88 0 18.5 0zm-7 23c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zm7.5-4H4V3h15v16z"></path> + </symbol> + <symbol id=info-outline viewBox="0 0 24 24"> + <path d="M11 17h2v-6h-2v6zm1-15C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zM11 9h2V7h-2v2z"></path> + </symbol> + <symbol id=close viewBox="0 0 24 24"> + <path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"></path> + </symbol> + <symbol id=help-outline viewBox="0 0 24 24"> + <path d="M11 18h2v-2h-2v2zm1-16C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm0-14c-2.21 0-4 1.79-4 4h2c0-1.1.9-2 2-2s2 .9 2 2c0 2-3 1.75-3 5h2c0-2.25 3-2.5 3-5 0-2.21-1.79-4-4-4z"></path> + </symbol> + <symbol id="twitter"> + <path d="M23.954 4.569c-.885.389-1.83.654-2.825.775 1.014-.611 1.794-1.574 2.163-2.723-.951.555-2.005.959-3.127 1.184-.896-.959-2.173-1.559-3.591-1.559-2.717 0-4.92 2.203-4.92 4.917 0 .39.045.765.127 1.124C7.691 8.094 4.066 6.13 1.64 3.161c-.427.722-.666 1.561-.666 2.475 0 1.71.87 3.213 2.188 4.096-.807-.026-1.566-.248-2.228-.616v.061c0 2.385 1.693 4.374 3.946 4.827-.413.111-.849.171-1.296.171-.314 0-.615-.03-.916-.086.631 1.953 2.445 3.377 4.604 3.417-1.68 1.319-3.809 2.105-6.102 2.105-.39 0-.779-.023-1.17-.067 2.189 1.394 4.768 2.209 7.557 2.209 9.054 0 13.999-7.496 13.999-13.986 0-.209 0-.42-.015-.63.961-.689 1.8-1.56 2.46-2.548l-.047-.02z" /> + </symbol> + <symbol id="github"> + <path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" /> + </symbol> + <g id="notifications"> + <path d="M12 22c1.1 0 2-.9 2-2h-4c0 1.1.89 2 2 2zm6-6v-5c0-3.07-1.64-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.63 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2z" /> + </g> + </svg> + <!-- Scripts --> + <script type="text/javascript" src="scripts/network.js"></script> + <script type="text/javascript" src="scripts/ui.js"></script> + <!-- Sounds --> + <audio id="blop" preload="auto" autobuffer="true"> + <source src="/sounds/blop.mp3" type="audio/mpeg"> + <source src="/sounds/blop.ogg" type="audio/ogg"> + </audio> + <!-- no script --> + <noscript> + <x-noscript class="full center column"> + <h1>Enable Javascript</h1> + <h3>Snapdrop works only with Javascript.</h3> + </x-noscript> + <style> + x-noscript { + background: #599cfc; + color: white; + z-index: 2; + } + + a[href="#info"] { + color: white; + } + </style> + </noscript> +</body> +\ No newline at end of file diff --git a/client/manifest.json b/client/manifest.json @@ -0,0 +1,29 @@ +{ + "name": "Snapdrop", + "short_name": "Snapdrop", + "icons": [{ + "src": "images/favicon-96x96.png", + "sizes": "96x96", + "type": "image/png" + }, { + "src": "images/apple-touch-icon.png", + "sizes": "152x152", + "type": "image/png" + }, { + "src": "images/mstile-150x150.png", + "sizes": "144x144", + "type": "image/png" + }, { + "src": "images/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, { + "src": "logo_transparent_white_512x512.png", + "sizes": "512x512", + "type": "image/png" + }], + "background_color": "#3367d6", + "start_url": "/", + "display": "standalone", + "theme_color": "#3367d6" +} diff --git a/client/scripts/network-v2.js b/client/scripts/network-v2.js @@ -0,0 +1,34 @@ +class ServerConnection { + +} + +class Connection { + +} + +class WSConnection extends Connection { + +} + +class RTCConnection extends Connection { + +} + +class Peer { + + constructor(serverConnection) { + this._ws = new WSConnection(serverConnection); + this._rtc = new RTCConnection(serverConnection); + this._fileReceiver = new FileReceiver(this); + this._fileSender = new FileSender(this); + } + + send(message) { + + } + +} + +class Peers { + +} +\ No newline at end of file diff --git a/client/scripts/network.js b/client/scripts/network.js @@ -0,0 +1,475 @@ +class ServerConnection { + + constructor() { + this._connect(); + Events.on('beforeunload', e => this._disconnect(), false); + } + + _connect() { + const ws = new WebSocket(this._endpoint()); + ws.binaryType = 'arraybuffer'; + ws.onopen = e => console.log('WS: server connection opened'); + ws.onmessage = e => this._onMessage(e.data); + ws.onclose = e => this._onDisconnect(); + ws.onerror = e => console.error(e); + this._socket = ws; + clearTimeout(this._reconnectTimer); + } + + _onMessage(msg) { + msg = JSON.parse(msg); + console.log('WS:', msg); + switch (msg.type) { + case 'peers': + Events.fire('peers', msg.peers); + break; + case 'peer-joined': + Events.fire('peer-joined', msg.peer); + break; + case 'peer-left': + Events.fire('peer-left', msg.peerId); + break; + case 'signal': + Events.fire('signal', msg); + break; + case 'ping': + this.send({ type: 'pong' }); + break; + default: + console.error('WS: unkown message type', msg) + } + } + + send(message) { + if (this._socket.readyState !== this._socket.OPEN) return; + this._socket.send(JSON.stringify(message)); + } + + _endpoint() { + // hack to detect if deployment or development environment + const protocol = location.protocol.startsWith('https') ? 'wss' : 'ws'; + const host = location.hostname.startsWith('localhost') ? 'localhost:3000' : (location.host + '/server'); + const webrtc = window.isRtcSupported ? '/webrtc' : '/fallback'; + const url = protocol + '://' + host + webrtc; + return url; + } + + _disconnect() { + this.send({ type: 'disconnect' }); + this._socket.close(); + } + + _onDisconnect() { + console.log('WS: server disconnected'); + Events.fire('notify-user', 'Connection lost. Retry in 5 seconds...'); + clearTimeout(this._reconnectTimer); + this._reconnectTimer = setTimeout(_ => this._connect(), 5000); + } +} + +class Peer { + + constructor(serverConnection, peerId) { + this._server = serverConnection; + this._peerId = peerId; + this._filesQueue = []; + this._busy = false; + } + + sendJSON(message) { + this._send(JSON.stringify(message)); + } + + sendFiles(files) { + for (let i = 0; i < files.length; i++) { + this._filesQueue.push(files[i]); + } + if (this._busy) return; + this._dequeueFile(); + } + + _dequeueFile() { + if (!this._filesQueue.length) return; + this._busy = true; + const file = this._filesQueue.shift(); + this._sendFile(file); + } + + _sendFile(file) { + this.sendJSON({ + type: 'header', + name: file.name, + mime: file.type, + size: file.size, + }); + this._chunker = new FileChunker(file, + chunk => this._send(chunk), + offset => this._onPartitionEnd(offset)); + this._chunker.nextPartition(); + } + + _onPartitionEnd(offset) { + this.sendJSON({ type: 'partition', offset: offset }); + } + + _onReceivedPartitionEnd(offset) { + this.sendJSON({ type: 'partition_received', offset: offset }); + } + + _sendNextPartition() { + if (!this._chunker || this._chunker.isFileEnd()) return; + this._chunker.nextPartition(); + } + + _sendProgress(progress) { + this.sendJSON({ type: 'progress', progress: progress }); + } + + _onMessage(message) { + if (typeof message !== 'string') { + this._onChunkReceived(message); + return; + } + message = JSON.parse(message); + console.log('RTC:', message); + switch (message.type) { + case 'header': + this._onFileHeader(message); + break; + case 'partition': + this._onReceivedPartitionEnd(message); + break; + case 'partition_received': + this._sendNextPartition(); + break; + case 'progress': + this._onDownloadProgress(message.progress); + break; + case 'transfer-complete': + this._onTransferCompleted(); + break; + case 'text': + this._onTextReceived(message); + break; + } + } + + _onFileHeader(header) { + this._lastProgress = 0; + this._digester = new FileDigester({ + name: header.name, + mime: header.mime, + size: header.size + }, file => this._onFileReceived(file)); + } + + _onChunkReceived(chunk) { + this._digester.unchunk(chunk); + const progress = this._digester.progress; + this._onDownloadProgress(progress); + + // occasionally notify sender about our progress + if (progress - this._lastProgress < 0.01) return; + this._lastProgress = progress; + this._sendProgress(progress); + } + + _onDownloadProgress(progress) { + Events.fire('file-progress', { + sender: this._peerId, + progress: progress + }); + } + + _onFileReceived(proxyFile) { + Events.fire('file-received', proxyFile); + this.sendJSON({ type: 'transfer-complete' }); + // this._digester = null; + } + + _onTransferCompleted() { + this._onDownloadProgress(1); + this._reader = null; + this._busy = false; + this._dequeueFile(); + Events.fire('notify-user', 'File transfer completed.'); + } + + sendText(text) { + this.sendJSON({ + type: 'text', + text: btoa(unescape(encodeURIComponent(text))) + }); + } + + _onTextReceived(message) { + Events.fire('text-received', { + text: decodeURIComponent(escape(atob(message.text))), + sender: this._peerId + }); + } +} + +class RTCPeer extends Peer { + + constructor(serverConnection, peerId) { + super(serverConnection, peerId); + if (!peerId) return; // we will listen for a caller + this._start(peerId, true); + } + + _start(peerId, isCaller) { + if (!this._peer) { + this._isCaller = isCaller; + this._peerId = peerId; + this._peer = new RTCPeerConnection(RTCPeer.config); + this._peer.onicecandidate = e => this._onIceCandidate(e); + this._peer.onconnectionstatechange = e => console.log('RTC: state changed:', this._peer.connectionState); + } + + if (isCaller) { + this._createChannel(); + } else { + this._peer.ondatachannel = e => this._onChannelOpened(e); + } + } + + _createChannel() { + const channel = this._peer.createDataChannel('data-channel', { reliable: true }); + channel.binaryType = 'arraybuffer'; + channel.onopen = e => this._onChannelOpened(e) + this._peer.createOffer(d => this._onDescription(d), e => this._onError(e)); + } + + _onDescription(description) { + // description.sdp = description.sdp.replace('b=AS:30', 'b=AS:1638400'); + this._peer.setLocalDescription(description, + _ => this._sendSignal({ sdp: description }), + e => this._onError(e)); + } + + _onIceCandidate(event) { + if (!event.candidate) return; + this._sendSignal({ ice: event.candidate }); + } + + _sendSignal(signal) { + signal.type = 'signal'; + signal.to = this._peerId; + this._server.send(signal); + } + + onServerMessage(message) { + if (!this._peer) this._start(message.sender, false); + const conn = this._peer; + + if (message.sdp) { + this._peer.setRemoteDescription(new RTCSessionDescription(message.sdp), () => { + if (message.sdp.type !== 'offer') return; + this._peer.createAnswer(d => this._onDescription(d), e => this._onError(e)); + }, e => this._onError(e)); + } else if (message.ice) { + this._peer.addIceCandidate(new RTCIceCandidate(message.ice)); + } + } + + _onChannelOpened(event) { + console.log('RTC: channel opened with', this._peerId); + const channel = event.channel || event.target; + channel.onmessage = e => this._onMessage(e.data); + channel.onclose = e => this._onChannelClosed(); + this._channel = channel; + } + + _onChannelClosed() { + console.log('RTC: channel closed ', this._peerId); + if (!this.isCaller) return; + this._start(this._peerId, true); // reopen the channel + } + + _send(message) { + this._channel.send(message); + } + + _onError(error) { + console.error(error); + } + + refresh() { + // check if channel open. otherwise create one + if (this._peer && this._channel && this._channel.readyState !== 'open') return; + this._createChannel(this._peerId, this._isCaller); + } +} + +class PeersManager { + + constructor(serverConnection) { + this.peers = {}; + this._server = serverConnection; + Events.on('signal', e => this._onMessage(e.detail)); + Events.on('peers', e => this._onPeers(e.detail)); + Events.on('files-selected', e => this._onFilesSelected(e.detail)); + Events.on('send-text', e => this._onSendText(e.detail)); + Events.on('peer-left', e => this._onPeerLeft(e.detail)); + } + + _onMessage(message) { + if (!this.peers[message.sender]) { + this.peers[message.sender] = new RTCPeer(this._server); + } + this.peers[message.sender].onServerMessage(message); + } + + _onPeers(peers) { + peers.forEach(peer => { + if (this.peers[peer.id]) { + this.peers[peer.id].refresh(); + return; + } + if (window.isRtcSupported && peer.rtcSupported) { + this.peers[peer.id] = new RTCPeer(this._server, peer.id); + } else { + this.peers[peer.id] = new WSPeer(this._server, peer.id); + } + }) + } + + sendTo(peerId, message) { + this.peers[peerId].send(message); + } + + _onFilesSelected(message) { + this.peers[message.to].sendFiles(message.files); + } + + _onSendText(message) { + this.peers[message.to].sendText(message.text); + } + + _onPeerLeft(peerId) { + const peer = this.peers[peerId]; + delete this.peers[peerId]; + if (!peer || !peer._peer) return; + peer._peer.close(); + } + +} + +class WSPeer { + _send(message) { + message.to = this._peerId; + this._server.send(message); + } +} + +class FileChunker { + + constructor(file, onChunk, onPartitionEnd) { + this._chunkSize = 64000; + this._maxPartitionSize = 1e6; + this._offset = 0; + this._partitionSize = 0; + this._file = file; + this._onChunk = onChunk; + this._onPartitionEnd = onPartitionEnd; + this._reader = new FileReader(); + this._reader.addEventListener('load', e => this._onChunkRead(e.target.result)); + } + + nextPartition() { + this._partitionSize = 0; + this._readChunk(); + } + + _readChunk() { + const chunk = this._file.slice(this._offset, this._offset + this._chunkSize); + this._reader.readAsArrayBuffer(chunk); + } + + _onChunkRead(chunk) { + this._offset += chunk.byteLength; + this._partitionSize += chunk.byteLength; + this._onChunk(chunk); + if (this._isPartitionEnd() || this.isFileEnd()) { + this._onPartitionEnd(this._partitionSize); + return; + } + this._readChunk(); + } + + repeatPartition() { + this._offset -= this._partitionSize; + this._nextPartition(); + } + + _isPartitionEnd() { + return this._partitionSize >= this._maxPartitionSize; + } + + isFileEnd() { + return this._offset > this._file.size; + } + + get progress() { + return this._offset / this._file.size; + } +} + +class FileDigester { + constructor(meta, callback) { + this._buffer = []; + this._bytesReceived = 0; + this._size = meta.size; + this._mime = meta.mime || 'application/octet-stream'; + this._name = meta.name; + this._callback = callback; + } + + unchunk(chunk) { + this._buffer.push(chunk); + this._bytesReceived += chunk.byteLength || chunk.size; + const totalChunks = this._buffer.length; + this.progress = this._bytesReceived / this._size; + if (this._bytesReceived < this._size) return; + + let received = new Blob(this._buffer, { type: this._mime }); // pass a useful mime type here + let url = URL.createObjectURL(received); + this._callback({ + name: this._name, + mime: this._mime, + size: this._size, + url: url + }); + this._callback = null; + } +} + +class Events { + static fire(type, detail) { + window.dispatchEvent(new CustomEvent(type, { detail: detail })); + } + + static on(type, callback) { + return window.addEventListener(type, callback, false); + } +} + + +window.isRtcSupported = !!(window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection); + +RTCPeer.config = { + 'iceServers': [{ + urls: 'stun:stun.stunprotocol.org:3478' + }, { + urls: 'stun:stun.l.google.com:19302' + }, { + urls: 'turn:turn.bistri.com:80', + credential: 'homeo', + username: 'homeo' + }, { + urls: 'turn:turn.anyfirewall.com:443?transport=tcp', + credential: 'webrtc', + username: 'webrtc' + }] +} +\ No newline at end of file diff --git a/client/scripts/ui.js b/client/scripts/ui.js @@ -0,0 +1,521 @@ +const $ = query => document.getElementById(query); +const $$ = query => document.body.querySelector(query); +const isURL = text => /^((https?:\/\/|www)[^\s]+)/g.test(text.toLowerCase()); +const isDownloadSupported = (typeof document.createElement('a').download !== 'undefined'); +const isProductionEnvironment = !window.location.host.startsWith('localhost'); + +class PeersUI { + + constructor() { + Events.on('peer-joined', e => this._onPeerJoined(e.detail)); + Events.on('peer-left', e => this._onPeerLeft(e.detail)); + Events.on('peers', e => this._onPeers(e.detail)); + Events.on('file-progress', e => this._onFileProgress(e.detail)); + } + + _onPeerJoined(peer) { + if (document.getElementById(peer.id)) return; + const peerUI = new PeerUI(peer); + $$('x-peers').appendChild(peerUI.$el); + } + + _onPeers(peers) { + this._clearPeers(); + peers.forEach(peer => this._onPeerJoined(peer)); + } + + _onPeerLeft(peerId) { + const peer = $(peerId); + if (!peer) return; + peer.remove(); + } + + _onFileProgress(progress) { + const peerId = progress.sender || progress.recipient; + const peer = $(peerId); + if (!peer) return; + peer.ui.setProgress(progress.progress); + } + + _clearPeers() { + const $peers = $$('x-peers').innerHTML = ''; + } +} + +class PeerUI { + + html() { + return ` + <label class="column center"> + <input type="file" multiple> + <x-icon shadow="1"> + <svg class="icon"><use xlink:href="#"/></svg> + </x-icon> + <div class="progress"> + <div class="circle"></div> + <div class="circle right"></div> + </div> + <div class="name font-subheading"></div> + <div class="status font-body2"></div> + </label>` + } + + constructor(peer) { + this._peer = peer; + this._initDom(); + this._bindListeners(this.$el); + } + + _initDom() { + const el = document.createElement('x-peer'); + el.id = this._peer.id; + el.innerHTML = this.html(); + el.ui = this; + el.querySelector('svg use').setAttribute('xlink:href', this._icon()); + el.querySelector('.name').textContent = this._name(); + this.$el = el; + this.$progress = el.querySelector('.progress'); + } + + _bindListeners(el) { + el.querySelector('input').addEventListener('change', e => this._onFilesSelected(e)); + el.addEventListener('drop', e => this._onDrop(e)); + el.addEventListener('dragend', e => this._onDragEnd(e)); + el.addEventListener('dragleave', e => this._onDragEnd(e)); + el.addEventListener('dragover', e => this._onDragOver(e)); + el.addEventListener('contextmenu', e => this._onRightClick(e)); + el.addEventListener('touchstart', e => this._onTouchStart(e)); + el.addEventListener('touchend', e => this._onTouchEnd(e)); + // prevent browser's default file drop behavior + Events.on('dragover', e => e.preventDefault()); + Events.on('drop', e => e.preventDefault()); + } + + _name() { + if (this._peer.name.model) { + return this._peer.name.os + ' ' + this._peer.name.model; + } + this._peer.name.os = this._peer.name.os.replace('Mac OS', 'Mac'); + return this._peer.name.os + ' ' + this._peer.name.browser; + } + + _icon() { + const device = this._peer.name.device || this._peer.name; + if (device.type === 'mobile') { + return '#phone-iphone'; + } + if (device.type === 'tablet') { + return '#tablet-mac'; + } + return '#desktop-mac'; + } + + _onFilesSelected(e) { + const $input = e.target; + const files = $input.files; + Events.fire('files-selected', { + files: files, + to: this._peer.id + }); + $input.value = null; // reset input + this.setProgress(0.01); + } + + setProgress(progress) { + if (progress > 0) { + this.$el.setAttribute('transfer', '1'); + } + if (progress > 0.5) { + this.$progress.classList.add('over50'); + } else { + this.$progress.classList.remove('over50'); + } + const degrees = `rotate(${360 * progress}deg)`; + this.$progress.style.setProperty('--progress', degrees); + if (progress >= 1) { + this.setProgress(0); + this.$el.removeAttribute('transfer'); + } + } + + _onDrop(e) { + e.preventDefault(); + const files = e.dataTransfer.files; + Events.fire('files-selected', { + files: files, + to: this._peer.id + }); + this._onDragEnd(); + } + + _onDragOver() { + this.$el.setAttribute('drop', 1); + } + + _onDragEnd() { + this.$el.removeAttribute('drop'); + } + + _onRightClick(e) { + e.preventDefault(); + Events.fire('text-recipient', this._peer.id); + } + + _onTouchStart(e) { + this._touchStart = Date.now(); + this._touchTimer = setTimeout(_ => this._onTouchEnd(), 610); + } + + _onTouchEnd(e) { + if (Date.now() - this._touchStart < 500) { + clearTimeout(this._touchTimer); + } else { // this was a long tap + if (e) e.preventDefault(); + Events.fire('text-recipient', this._peer.id); + } + } +} + + +class Dialog { + constructor(id) { + this.$el = $(id); + this.$el.querySelectorAll('[close]').forEach(el => el.addEventListener('click', e => this.hide())) + this.$autoFocus = this.$el.querySelector('[autofocus]'); + } + + show() { + this.$el.setAttribute('show', 1); + if (this.$autoFocus) this.$autoFocus.focus(); + } + + hide() { + this.$el.removeAttribute('show'); + document.activeElement.blur(); + window.blur(); + } +} + +class ReceiveDialog extends Dialog { + + constructor() { + super('receiveDialog'); + Events.on('file-received', e => { + this._nextFile(e.detail); + window.blop.play(); + }); + this._filesQueue = []; + } + + _nextFile(nextFile) { + if (nextFile) this._filesQueue.push(nextFile); + if (this._busy) return; + this._busy = true; + const file = this._filesQueue.shift(); + this._displayFile(file); + } + + _dequeueFile() { + if (!this._filesQueue.length) { // nothing to do + this._busy = false; + return; + } + // dequeue next file + setTimeout(_ => { + this._busy = false; + this._nextFile(); + }, 300); + } + + _displayFile(file) { + const $a = this.$el.querySelector('#download'); + $a.href = file.url; + $a.download = file.name; + + this.$el.querySelector('#fileName').textContent = file.name; + this.$el.querySelector('#fileSize').textContent = this._formatFileSize(file.size); + this.show(); + + if (!isDownloadSupported) return; + // $a.target = "_blank"; // fallback + $a.target = "_system"; // fallback + $a.href = 'external:' + $a.href; + } + + _formatFileSize(bytes) { + if (bytes >= 1e9) { + return (Math.round(bytes / 1e8) / 10) + ' GB'; + } else if (bytes >= 1e6) { + return (Math.round(bytes / 1e5) / 10) + ' MB'; + } else if (bytes > 1000) { + return Math.round(bytes / 1000) + ' KB'; + } else { + return bytes + ' Bytes'; + } + } + + hide() { + super.hide(); + this._dequeueFile(); + } +} + + +class SendTextDialog extends Dialog { + constructor() { + super('sendTextDialog'); + Events.on('text-recipient', e => this._onRecipient(e.detail)) + this.$text = this.$el.querySelector('#textInput'); + const button = this.$el.querySelector('form'); + button.addEventListener('submit', e => this._send(e)); + } + + _onRecipient(recipient) { + this._recipient = recipient; + this.show(); + this.$text.setSelectionRange(0, this.$text.value.length) + } + + _send(e) { + e.preventDefault(); + Events.fire('send-text', { + to: this._recipient, + text: this.$text.value + }); + } +} + +class ReceiveTextDialog extends Dialog { + constructor() { + super('receiveTextDialog'); + Events.on('text-received', e => this._onText(e.detail)) + this.$text = this.$el.querySelector('#text'); + const $copy = this.$el.querySelector('#copy'); + copy.addEventListener('click', _ => this._onCopy()); + } + + _onText(e) { + this.$text.innerHTML = ''; + const text = e.text; + if (isURL(text)) { + const $a = document.createElement('a'); + $a.href = text; + $a.target = '_blank'; + $a.textContent = text; + this.$text.appendChild($a); + } else { + this.$text.textContent = text; + } + this.show(); + window.blop.play(); + } + + _onCopy() { + if (!document.copy(this.$text.textContent)) return; + Events.fire('notify-user', 'Copied to clipboard'); + } +} + +class Toast extends Dialog { + constructor() { + super('toast'); + Events.on('notify-user', e => this._onNotfiy(e.detail)); + } + + _onNotfiy(message) { + this.$el.textContent = message; + this.show(); + setTimeout(_ => this.hide(), 3000); + } +} + + +class Notifications { + + constructor() { + // Check if the browser supports notifications + if (!('Notification' in window)) return; + // Check whether notification permissions have already been granted + + if (Notification.permission !== 'granted') { + this.$button = $('notification'); + this.$button.removeAttribute('hidden'); + this.$button.addEventListener('click', e => this._requestPermission()); + } + Events.on('text-received', e => this._messageNotification(e.detail.text)); + Events.on('file-received', e => this._downloadNotification(e.detail.name)); + } + + _requestPermission() { + Notification.requestPermission(permission => { + if (permission !== 'granted') { + Events.fire('notify-user', Notifications.PERMISSION_ERROR || 'Error'); + return; + } + this._notify('Even more snappy sharing!'); + this.$button.setAttribute('hidden', 1); + }); + } + + _notify(message, body) { + var img = '/images/logo_transparent_128x128.png'; + return new Notification(message, { + body: body, + icon: img, + vibrate: [200, 100, 200, 100, 200, 100, 400], + }); + } + + _messageNotification(message) { + if (isURL(message)) { + const notification = this._notify(message, 'Click to open link'); + notification.onclick = e => window.open(message, '_blank', null, true); + } else { + const notification = this._notify(message, 'Click to copy text'); + notification.onclick = e => document.copy(message); + } + } + + _downloadNotification(message) { + const notification = this._notify(message, 'Click to download'); + if (window.isDownloadSupported) return; + notification.onclick = e => { + document.querySelector('x-dialog [download]').click(); + }; + } + +} + +class Snapdrop { + constructor() { + const server = new ServerConnection(); + const peers = new PeersManager(server); + const peersUI = new PeersUI(); + Events.on('load', e => { + const receiveDialog = new ReceiveDialog(); + const sendTextDialog = new SendTextDialog(); + const receiveTextDialog = new ReceiveTextDialog(); + const toast = new Toast(); + const notifications = new Notifications(); + }) + } +} + +const snapdrop = new Snapdrop(); + +document.copy = text => { + // A <span> contains the text to copy + const span = document.createElement('span'); + span.textContent = text; + span.style.whiteSpace = 'pre'; // Preserve consecutive spaces and newlines + + // Paint the span outside the viewport + span.style.position = 'absolute'; + span.style.left = '-9999px'; + span.style.top = '-9999px'; + + const win = window; + const selection = win.getSelection(); + win.document.body.appendChild(span); + + const range = win.document.createRange(); + selection.removeAllRanges(); + range.selectNode(span); + selection.addRange(range); + + let success = false; + try { + success = win.document.execCommand('copy'); + } catch (err) {} + + selection.removeAllRanges(); + span.remove(); + + return success; +} + +if ('serviceWorker' in navigator && isProductionEnvironment) { + navigator.serviceWorker + .register('/service-worker.js') + .then(e => console.log("Service Worker Registered")); +} + +// Background Animation +Events.on('load', () => { + var requestAnimFrame = (function() { + return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || + function(callback) { + window.setTimeout(callback, 1000 / 60); + }; + })(); + var c = document.createElement('canvas'); + document.body.appendChild(c); + var style = c.style; + style.width = '100%'; + style.position = 'absolute'; + style.zIndex = -1; + var ctx = c.getContext('2d'); + var x0, y0, w, h, dw; + + function init() { + w = window.innerWidth; + h = window.innerHeight; + c.width = w; + c.height = h; + var offset = h > 380 ? 100 : 65; + x0 = w / 2; + y0 = h - offset; + dw = Math.max(w, h, 1000) / 13; + drawCircles(); + } + window.onresize = init; + + function drawCicrle(radius) { + ctx.beginPath(); + var color = Math.round(255 * (1 - radius / Math.max(w, h))); + ctx.strokeStyle = 'rgba(' + color + ',' + color + ',' + color + ',0.1)'; + ctx.arc(x0, y0, radius, 0, 2 * Math.PI); + ctx.stroke(); + ctx.lineWidth = 2; + } + + var step = 0; + + function drawCircles() { + ctx.clearRect(0, 0, w, h); + for (var i = 0; i < 8; i++) { + drawCicrle(dw * i + step % dw); + } + step += 1; + } + + var loading = true; + + function animate() { + if (loading || step % dw < dw - 5) { + requestAnimFrame(function() { + drawCircles(); + animate(); + }); + } + } + window.animateBackground = function(l) { + loading = l; + animate(); + }; + init(); + animate(); + setTimeout(e => window.animateBackground(false), 3000); +}); + +Notifications.PERMISSION_ERROR = ` +Notifications permission has been blocked +as the user has dismissed the permission prompt several times. +This can be reset in Page Info +which can be accessed by clicking the lock icon next to the URL.`; + +document.body.onclick = e => { // safari hack to fix audio + document.body.onclick = null; + if (!(/.*Version.*Safari.*/.test(navigator.userAgent))) return; + blop.play(); +} +\ No newline at end of file diff --git a/test.txt b/client/service-worker.js diff --git a/app/sounds/blop.mp3 b/client/sounds/blop.mp3 Binary files differ. diff --git a/app/sounds/blop.ogg b/client/sounds/blop.ogg Binary files differ. diff --git a/client/styles.css b/client/styles.css @@ -0,0 +1,640 @@ +/* Constants */ + +:root { + --icon-size: 24px; + --primary-color: #4285f4; + --peer-width: 120px; +} + +/* Layout */ + +html { + height: 100%; +} + +html, +body { + margin: 0; + display: flex; + flex-direction: column; + width: 100%; + overflow-x: hidden; +} + +body { + flex-grow: 1; + align-items: center; + justify-content: center; + overflow-y: hidden; +} + +.row-reverse { + display: flex; + flex-direction: row-reverse; +} + +.row { + display: flex; + flex-direction: row; +} + +.column { + display: flex; + flex-direction: column; +} + +.center { + display: flex; + align-items: center; + justify-content: center; +} + +.full { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; +} + +[hidden] { + display: none !important; +} + +/* Typography */ + +body { + background: #fafafa; + font-family: -apple-system, BlinkMacSystemFont, Roboto, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + color: #333; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +h1 { + font-size: 34px; + font-weight: 400; + letter-spacing: -.01em; + line-height: 40px; + margin: 8px 0 0; +} + +h2 { + font-size: 24px; + font-weight: 400; + letter-spacing: -.012em; + line-height: 32px; +} + +h3 { + font-size: 20px; + font-weight: 500; + margin: 16px 0; +} + +.font-subheading { + font-size: 16px; + font-weight: 400; + line-height: 24px; +} + +.font-body1, +body { + font-size: 14px; + font-weight: 400; + line-height: 20px; +} + +.font-body2 { + font-size: 12px; + line-height: 18px; +} + +a { + text-decoration: none; + color: var(--primary-color); + cursor: pointer; +} + + + +/* Icons */ + +.icon { + width: var(--icon-size); + height: var(--icon-size); + fill: currentColor; +} + + + +/* Shadows */ + +[shadow="1"] { + box-shadow: 0 3px 4px 0 rgba(0, 0, 0, 0.14), + 0 1px 8px 0 rgba(0, 0, 0, 0.12), + 0 3px 3px -2px rgba(0, 0, 0, 0.4); +} + +[shadow="2"] { + box-shadow: 0 4px 5px 0 rgba(0, 0, 0, 0.14), + 0 1px 10px 0 rgba(0, 0, 0, 0.12), + 0 2px 4px -1px rgba(0, 0, 0, 0.4); +} + + + + +/* Animations */ + +@keyframes fade-in { + 0% { + opacity: 0; + } +} + + + +/* Peers List */ + +x-peers { + width: 100%; + overflow: hidden; + flex-flow: row wrap; + z-index: 2; +} + + + +/* Empty Peers List */ + +x-no-peers { + padding: 8px; + text-align: center; + /* prevent flickering on load */ + animation: fade-in 300ms; + animation-delay: 500ms; + animation-fill-mode: backwards; +} + +x-no-peers h2 { + color: var(--primary-color); +} + +x-peers:not(:empty)+x-no-peers { + display: none; +} + + + +/* Peer */ + +x-peer { + -webkit-user-select: none; + user-select: none; +} + +x-peer label { + width: var(--peer-width); + padding: 8px; + cursor: pointer; + touch-action: manipulation; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + position: relative; +} + +x-peer .name { + width: var(--peer-width); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + text-align: center; +} + +input[type="file"] { + visibility: hidden; + position: absolute; +} + +x-peer x-icon { + --icon-size: 40px; + width: var(--icon-size); + padding: 12px; + border-radius: 50%; + background: var(--primary-color); + color: white; + display: flex; + margin-bottom: 8px; + transition: transform 150ms; + will-change: transform; +} + +x-peer:not([transfer]):hover x-icon, +x-peer:not([transfer]):focus x-icon { + transform: scale(1.05); +} + +x-peer[transfer] x-icon { + box-shadow: none; + opacity: 0.8; + transform: scale(1); +} + +.status { + height: 18px; + opacity: 0.7; +} + +x-peer[transfer] .status:before { + content: 'Transfering...'; +} + +x-peer x-icon { + animation: pop 600ms ease-out 1; +} + +@keyframes pop { + 0% { + transform: scale(0.7); + } + 40% { + transform: scale(1.2); + } +} + +x-peer[drop] x-icon { + transform: scale(1.1); +} + +/* Footer */ + +footer { + position: absolute; + bottom: 0; + left: 0; + right: 0; + align-items: center; + padding: 0 0 16px 0; +} + +footer .logo { + --icon-size: 80px; + margin-bottom: 8px; + color: var(--primary-color); +} + +footer .font-body2 { + color: var(--primary-color); +} + +@media (min-height: 800px) { + footer { + margin-bottom: 16px; + } +} + + + +/* Dialog */ + +x-dialog x-background { + background: rgba(0, 0, 0, 0.61); + z-index: 10; + transition: opacity 300ms; + padding: 16px; +} + +x-dialog x-paper { + z-index: 3; + background: white; + border-radius: 8px; + padding: 16px 24px; + width: 100%; + max-width: 400px; + box-sizing: border-box; + transition: transform 300ms; +} + +x-dialog:not([show]) { + pointer-events: none; +} + +x-dialog:not([show]) x-paper { + transform: scale(0.1); +} + +x-dialog:not([show]) x-background { + opacity: 0; +} + +x-dialog .row-reverse>.button { + margin-top: 16px; + margin-left: 8px; +} + + + +/* Receive Dialog */ + +#receiveTextDialog #text { + width: 100%; + word-break: break-all; + max-height: 300px; + overflow-x: hidden; + overflow-y: auto; + -webkit-user-select: all; + -moz-user-select: all; + user-select: all; +} + + + + +/* Button */ + +.button { + padding: 0 16px; + box-sizing: border-box; + min-height: 36px; + border: none; + outline: none; + min-width: 100px; + font-size: 14px; + line-height: 24px; + font-weight: 700; + letter-spacing: 0.12em; + text-transform: uppercase; + white-space: nowrap; + cursor: pointer; + user-select: none; + background: inherit; + color: var(--primary-color); +} + +.button, +.icon-button { + position: relative; + display: flex; + align-items: center; + justify-content: center; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + touch-action: manipulation; +} + +.button:before, +.icon-button:before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: currentColor; + opacity: 0; + transition: opacity 300ms; +} + +.button:hover:before, +.icon-button:hover:before { + opacity: 0.1; +} + +.button:before { + border-radius: 8px; +} + +.button:focus:before { + opacity: 0.2; +} + +button::-moz-focus-inner { + border: 0; +} + + + +/* Icon Button */ + +.icon-button { + width: 40px; + height: 40px; +} + +.icon-button:before { + border-radius: 50%; +} + + + +/* Text Input */ + +input { + width: 100%; + box-sizing: border-box; + border: none; + outline: none; + padding: 16px 24px; + background: #f1f3f4; + border-radius: 50px; + margin: 8px 0; + line-height: 16px; + font-size: 14px; +} + + + +/* Info Animation */ + +#info { + text-align: center; + color: white; + transition: opacity 300ms; + will-change: opacity; + z-index: 11; + transition-delay: 300ms; +} + +#info:not(:target) { + opacity: 0; + pointer-events: none; + transition-delay: 0; +} + +#info .logo { + --icon-size: 96px; +} + +#info .close { + position: absolute; + top: 12px; + right: 12px; + color: white; + border-radius: 50%; +} + +.info-background { + position: relative; +} + +.info-background:before { + content: ''; + position: absolute; + width: 40px; + height: 40px; + top: -20px; + left: -32px; + border-radius: 50%; + background: var(--primary-color); + transform: scale(0); + transition: transform 800ms cubic-bezier(0.77, 0, 0.175, 1); + will-change: transform; +} + +#info:target+a>.info-background:before { + transform: scale(100); +} + +a[href="#info"] { + position: absolute; + top: 12px; + right: 12px; + color: #333; + z-index: 10; +} + +#info .row a { + color: currentColor; + margin: 8px 8px -16px; +} + + + + +/* Loading Indicator */ + +.progress { + width: 80px; + height: 80px; + position: absolute; + top: 0px; + clip: rect(0px, 80px, 80px, 40px); + --progress: rotate(0deg); + transition: transform 200ms; +} + +.circle { + width: 72px; + height: 72px; + border: 4px solid var(--primary-color); + border-radius: 40px; + position: absolute; + clip: rect(0px, 40px, 80px, 0px); + will-change: transform; + transform: var(--progress); +} + +.over50 { + clip: rect(auto, auto, auto, auto); +} + +.over50 .circle.right { + transform: rotate(180deg); +} + + + +/* Toast */ + +.toast-container { + padding: 0 8px 24px; + overflow: hidden; + pointer-events: none; +} + +x-toast { + position: absolute; + min-height: 48px; + bottom: 24px; + width: 100%; + max-width: 344px; + border-radius: 8px; + background-color: #323232; + color: rgba(255, 255, 255, 0.95); + align-items: center; + box-sizing: border-box; + padding: 8px 24px; + z-index: 20; + transition: opacity 200ms, transform 300ms ease-out; + cursor: default; + line-height: 24px; + border-radius: 6px; + pointer-events: all; +} + +x-toast:not([show]):not(:hover) { + opacity: 0; + transform: translateY(100px); +} + +#notification { + position: absolute; + right: 56px; + top: 12px; + color: #333; +} + +/* Instructions */ + +x-instructions { + position: absolute; + top: 120px; + opacity: 0.5; + transition: opacity 300ms; + z-index: -1; +} + +x-instructions:before { + content: attr(mobile); +} + +x-peers:empty~x-instructions { + opacity: 0; +} + +/* Responsive Styles */ + +@media (min-height: 800px) { + x-toast { + right: 24px; + } + footer { + margin-bottom: 16px; + } +} + +@media screen and (min-height: 800px), +screen and (min-width: 1100px) { + x-instructions:before { + content: attr(desktop); + } +} + +@media (max-height: 420px) { + x-instructions { + top: 24px; + } + footer .logo { + --icon-size: 40px; + } +} + +@supports (-webkit-overflow-scrolling: touch) { + /* CSS specific to iOS devices */ + html { + position: fixed; + } + + x-instructions:before { + content: attr(mobile); + } +} +\ No newline at end of file diff --git a/dist/index.js b/dist/index.js @@ -1,23 +0,0 @@ -'use strict'; -var express = require('express'); -var compression = require('compression'); -var app = express(); -var http = require('http'); -var ExpressPeerServer = require('peer').ExpressPeerServer; -var wsServer = require('./server/ws-server.js'); - -var server = http.createServer(app); - -// Serve up content from public directory -app.use(compression()); -app.use(express.static(__dirname + '/public')); - -var port = process.env.PORT || 3002; -server.listen(port); -wsServer.create(server); -app.use('/peerjs', ExpressPeerServer(server, { - debug: true -})); - - -console.log('listening on port ' + port); diff --git a/dist/package.json b/dist/package.json @@ -1,14 +0,0 @@ -{ - "private": true, - "engines": { - "node": ">=0.10.0" - }, - "dependencies": { - "binaryjs": "^0.2.1", - "compression": "^1.6.0", - "express": "^4.13.3", - "peer": "^0.2.8", - "ua-parser-js": "^0.7.10", - "ws": "^1.1.1" - } -} diff --git a/dist/public/elements/elements.html b/dist/public/elements/elements.html @@ -1,22671 +0,0 @@ -<html><head> - -<meta charset=UTF-8><script>(function () { -function resolve() { -document.body.removeAttribute('unresolved'); -} -if (window.WebComponents) { -addEventListener('WebComponentsReady', resolve); -} else { -if (document.readyState === 'interactive' || document.readyState === 'complete') { -resolve(); -} else { -addEventListener('DOMContentLoaded', resolve); -} -} -}()); -window.Polymer = { -Settings: function () { -var user = window.Polymer || {}; -var parts = location.search.slice(1).split('&'); -for (var i = 0, o; i < parts.length && (o = parts[i]); i++) { -o = o.split('='); -o[0] && (user[o[0]] = o[1] || true); -} -var wantShadow = user.dom === 'shadow'; -var hasShadow = Boolean(Element.prototype.createShadowRoot); -var nativeShadow = hasShadow && !window.ShadowDOMPolyfill; -var useShadow = wantShadow && hasShadow; -var hasNativeImports = Boolean('import' in document.createElement('link')); -var useNativeImports = hasNativeImports; -var useNativeCustomElements = !window.CustomElements || window.CustomElements.useNative; -return { -wantShadow: wantShadow, -hasShadow: hasShadow, -nativeShadow: nativeShadow, -useShadow: useShadow, -useNativeShadow: useShadow && nativeShadow, -useNativeImports: useNativeImports, -useNativeCustomElements: useNativeCustomElements -}; -}() -}; -(function () { -var userPolymer = window.Polymer; -window.Polymer = function (prototype) { -if (typeof prototype === 'function') { -prototype = prototype.prototype; -} -if (!prototype) { -prototype = {}; -} -var factory = desugar(prototype); -prototype = factory.prototype; -var options = { prototype: prototype }; -if (prototype.extends) { -options.extends = prototype.extends; -} -Polymer.telemetry._registrate(prototype); -document.registerElement(prototype.is, options); -return factory; -}; -var desugar = function (prototype) { -var base = Polymer.Base; -if (prototype.extends) { -base = Polymer.Base._getExtendedPrototype(prototype.extends); -} -prototype = Polymer.Base.chainObject(prototype, base); -prototype.registerCallback(); -return prototype.constructor; -}; -window.Polymer = Polymer; -if (userPolymer) { -for (var i in userPolymer) { -Polymer[i] = userPolymer[i]; -} -} -Polymer.Class = desugar; -}()); -Polymer.telemetry = { -registrations: [], -_regLog: function (prototype) { -console.log('[' + prototype.is + ']: registered'); -}, -_registrate: function (prototype) { -this.registrations.push(prototype); -Polymer.log && this._regLog(prototype); -}, -dumpRegistrations: function () { -this.registrations.forEach(this._regLog); -} -}; -Object.defineProperty(window, 'currentImport', { -enumerable: true, -configurable: true, -get: function () { -return (document._currentScript || document.currentScript).ownerDocument; -} -}); -Polymer.RenderStatus = { -_ready: false, -_callbacks: [], -whenReady: function (cb) { -if (this._ready) { -cb(); -} else { -this._callbacks.push(cb); -} -}, -_makeReady: function () { -this._ready = true; -for (var i = 0; i < this._callbacks.length; i++) { -this._callbacks[i](); -} -this._callbacks = []; -}, -_catchFirstRender: function () { -requestAnimationFrame(function () { -Polymer.RenderStatus._makeReady(); -}); -}, -_afterNextRenderQueue: [], -_waitingNextRender: false, -afterNextRender: function (element, fn, args) { -this._watchNextRender(); -this._afterNextRenderQueue.push([ -element, -fn, -args -]); -}, -_watchNextRender: function () { -if (!this._waitingNextRender) { -this._waitingNextRender = true; -var fn = function () { -Polymer.RenderStatus._flushNextRender(); -}; -if (!this._ready) { -this.whenReady(fn); -} else { -requestAnimationFrame(fn); -} -} -}, -_flushNextRender: function () { -var self = this; -setTimeout(function () { -self._flushRenderCallbacks(self._afterNextRenderQueue); -self._afterNextRenderQueue = []; -self._waitingNextRender = false; -}); -}, -_flushRenderCallbacks: function (callbacks) { -for (var i = 0, h; i < callbacks.length; i++) { -h = callbacks[i]; -h[1].apply(h[0], h[2] || Polymer.nar); -} -; -} -}; -if (window.HTMLImports) { -HTMLImports.whenReady(function () { -Polymer.RenderStatus._catchFirstRender(); -}); -} else { -Polymer.RenderStatus._catchFirstRender(); -} -Polymer.ImportStatus = Polymer.RenderStatus; -Polymer.ImportStatus.whenLoaded = Polymer.ImportStatus.whenReady; -Polymer.Base = { -__isPolymerInstance__: true, -_addFeature: function (feature) { -this.extend(this, feature); -}, -registerCallback: function () { -this._desugarBehaviors(); -this._doBehavior('beforeRegister'); -this._registerFeatures(); -this._doBehavior('registered'); -}, -createdCallback: function () { -Polymer.telemetry.instanceCount++; -this.root = this; -this._doBehavior('created'); -this._initFeatures(); -}, -attachedCallback: function () { -var self = this; -Polymer.RenderStatus.whenReady(function () { -self.isAttached = true; -self._doBehavior('attached'); -}); -}, -detachedCallback: function () { -this.isAttached = false; -this._doBehavior('detached'); -}, -attributeChangedCallback: function (name, oldValue, newValue) { -this._attributeChangedImpl(name); -this._doBehavior('attributeChanged', [ -name, -oldValue, -newValue -]); -}, -_attributeChangedImpl: function (name) { -this._setAttributeToProperty(this, name); -}, -extend: function (prototype, api) { -if (prototype && api) { -var n$ = Object.getOwnPropertyNames(api); -for (var i = 0, n; i < n$.length && (n = n$[i]); i++) { -this.copyOwnProperty(n, api, prototype); -} -} -return prototype || api; -}, -mixin: function (target, source) { -for (var i in source) { -target[i] = source[i]; -} -return target; -}, -copyOwnProperty: function (name, source, target) { -var pd = Object.getOwnPropertyDescriptor(source, name); -if (pd) { -Object.defineProperty(target, name, pd); -} -}, -_log: console.log.apply.bind(console.log, console), -_warn: console.warn.apply.bind(console.warn, console), -_error: console.error.apply.bind(console.error, console), -_logf: function () { -return this._logPrefix.concat([this.is]).concat(Array.prototype.slice.call(arguments, 0)); -} -}; -Polymer.Base._logPrefix = function () { -var color = window.chrome || /firefox/i.test(navigator.userAgent); -return color ? [ -'%c[%s::%s]:', -'font-weight: bold; background-color:#EEEE00;' -] : ['[%s::%s]:']; -}(); -Polymer.Base.chainObject = function (object, inherited) { -if (object && inherited && object !== inherited) { -if (!Object.__proto__) { -object = Polymer.Base.extend(Object.create(inherited), object); -} -object.__proto__ = inherited; -} -return object; -}; -Polymer.Base = Polymer.Base.chainObject(Polymer.Base, HTMLElement.prototype); -if (window.CustomElements) { -Polymer.instanceof = CustomElements.instanceof; -} else { -Polymer.instanceof = function (obj, ctor) { -return obj instanceof ctor; -}; -} -Polymer.isInstance = function (obj) { -return Boolean(obj && obj.__isPolymerInstance__); -}; -Polymer.telemetry.instanceCount = 0; -(function () { -var modules = {}; -var lcModules = {}; -var findModule = function (id) { -return modules[id] || lcModules[id.toLowerCase()]; -}; -var DomModule = function () { -return document.createElement('dom-module'); -}; -DomModule.prototype = Object.create(HTMLElement.prototype); -Polymer.Base.extend(DomModule.prototype, { -constructor: DomModule, -createdCallback: function () { -this.register(); -}, -register: function (id) { -var id = id || this.id || this.getAttribute('name') || this.getAttribute('is'); -if (id) { -this.id = id; -modules[id] = this; -lcModules[id.toLowerCase()] = this; -} -}, -import: function (id, selector) { -if (id) { -var m = findModule(id); -if (!m) { -forceDomModulesUpgrade(); -m = findModule(id); -} -if (m && selector) { -m = m.querySelector(selector); -} -return m; -} -} -}); -var cePolyfill = window.CustomElements && !CustomElements.useNative; -document.registerElement('dom-module', DomModule); -function forceDomModulesUpgrade() { -if (cePolyfill) { -var script = document._currentScript || document.currentScript; -var doc = script && script.ownerDocument || document; -var modules = doc.querySelectorAll('dom-module'); -for (var i = modules.length - 1, m; i >= 0 && (m = modules[i]); i--) { -if (m.__upgraded__) { -return; -} else { -CustomElements.upgrade(m); -} -} -} -} -}()); -Polymer.Base._addFeature({ -_prepIs: function () { -if (!this.is) { -var module = (document._currentScript || document.currentScript).parentNode; -if (module.localName === 'dom-module') { -var id = module.id || module.getAttribute('name') || module.getAttribute('is'); -this.is = id; -} -} -if (this.is) { -this.is = this.is.toLowerCase(); -} -} -}); -Polymer.Base._addFeature({ -behaviors: [], -_desugarBehaviors: function () { -if (this.behaviors.length) { -this.behaviors = this._desugarSomeBehaviors(this.behaviors); -} -}, -_desugarSomeBehaviors: function (behaviors) { -behaviors = this._flattenBehaviorsList(behaviors); -for (var i = behaviors.length - 1; i >= 0; i--) { -this._mixinBehavior(behaviors[i]); -} -return behaviors; -}, -_flattenBehaviorsList: function (behaviors) { -var flat = []; -for (var i = 0; i < behaviors.length; i++) { -var b = behaviors[i]; -if (b instanceof Array) { -flat = flat.concat(this._flattenBehaviorsList(b)); -} else if (b) { -flat.push(b); -} else { -this._warn(this._logf('_flattenBehaviorsList', 'behavior is null, check for missing or 404 import')); -} -} -return flat; -}, -_mixinBehavior: function (b) { -var n$ = Object.getOwnPropertyNames(b); -for (var i = 0, n; i < n$.length && (n = n$[i]); i++) { -if (!Polymer.Base._behaviorProperties[n] && !this.hasOwnProperty(n)) { -this.copyOwnProperty(n, b, this); -} -} -}, -_prepBehaviors: function () { -this._prepFlattenedBehaviors(this.behaviors); -}, -_prepFlattenedBehaviors: function (behaviors) { -for (var i = 0, l = behaviors.length; i < l; i++) { -this._prepBehavior(behaviors[i]); -} -this._prepBehavior(this); -}, -_doBehavior: function (name, args) { -for (var i = 0; i < this.behaviors.length; i++) { -this._invokeBehavior(this.behaviors[i], name, args); -} -this._invokeBehavior(this, name, args); -}, -_invokeBehavior: function (b, name, args) { -var fn = b[name]; -if (fn) { -fn.apply(this, args || Polymer.nar); -} -}, -_marshalBehaviors: function () { -for (var i = 0; i < this.behaviors.length; i++) { -this._marshalBehavior(this.behaviors[i]); -} -this._marshalBehavior(this); -} -}); -Polymer.Base._behaviorProperties = { -hostAttributes: true, -registered: true, -properties: true, -observers: true, -listeners: true, -created: true, -attached: true, -detached: true, -attributeChanged: true, -ready: true -}; -Polymer.Base._addFeature({ -_getExtendedPrototype: function (tag) { -return this._getExtendedNativePrototype(tag); -}, -_nativePrototypes: {}, -_getExtendedNativePrototype: function (tag) { -var p = this._nativePrototypes[tag]; -if (!p) { -var np = this.getNativePrototype(tag); -p = this.extend(Object.create(np), Polymer.Base); -this._nativePrototypes[tag] = p; -} -return p; -}, -getNativePrototype: function (tag) { -return Object.getPrototypeOf(document.createElement(tag)); -} -}); -Polymer.Base._addFeature({ -_prepConstructor: function () { -this._factoryArgs = this.extends ? [ -this.extends, -this.is -] : [this.is]; -var ctor = function () { -return this._factory(arguments); -}; -if (this.hasOwnProperty('extends')) { -ctor.extends = this.extends; -} -Object.defineProperty(this, 'constructor', { -value: ctor, -writable: true, -configurable: true -}); -ctor.prototype = this; -}, -_factory: function (args) { -var elt = document.createElement.apply(document, this._factoryArgs); -if (this.factoryImpl) { -this.factoryImpl.apply(elt, args); -} -return elt; -} -}); -Polymer.nob = Object.create(null); -Polymer.Base._addFeature({ -properties: {}, -getPropertyInfo: function (property) { -var info = this._getPropertyInfo(property, this.properties); -if (!info) { -for (var i = 0; i < this.behaviors.length; i++) { -info = this._getPropertyInfo(property, this.behaviors[i].properties); -if (info) { -return info; -} -} -; -} -return info || Polymer.nob; -}, -_getPropertyInfo: function (property, properties) { -var p = properties && properties[property]; -if (typeof p === 'function') { -p = properties[property] = { type: p }; -} -if (p) { -p.defined = true; -} -return p; -}, -_prepPropertyInfo: function () { -this._propertyInfo = {}; -for (var i = 0, p; i < this.behaviors.length; i++) { -this._addPropertyInfo(this._propertyInfo, this.behaviors[i].properties); -} -this._addPropertyInfo(this._propertyInfo, this.properties); -this._addPropertyInfo(this._propertyInfo, this._propertyEffects); -}, -_addPropertyInfo: function (target, source) { -if (source) { -var t, s; -for (var i in source) { -t = target[i]; -s = source[i]; -if (i[0] === '_' && !s.readOnly) { -continue; -} -if (!target[i]) { -target[i] = { -type: typeof s === 'function' ? s : s.type, -readOnly: s.readOnly, -attribute: Polymer.CaseMap.camelToDashCase(i) -}; -} else { -if (!t.type) { -t.type = s.type; -} -if (!t.readOnly) { -t.readOnly = s.readOnly; -} -} -} -} -} -}); -Polymer.CaseMap = { -_caseMap: {}, -dashToCamelCase: function (dash) { -var mapped = Polymer.CaseMap._caseMap[dash]; -if (mapped) { -return mapped; -} -if (dash.indexOf('-') < 0) { -return Polymer.CaseMap._caseMap[dash] = dash; -} -return Polymer.CaseMap._caseMap[dash] = dash.replace(/-([a-z])/g, function (m) { -return m[1].toUpperCase(); -}); -}, -camelToDashCase: function (camel) { -var mapped = Polymer.CaseMap._caseMap[camel]; -if (mapped) { -return mapped; -} -return Polymer.CaseMap._caseMap[camel] = camel.replace(/([a-z][A-Z])/g, function (g) { -return g[0] + '-' + g[1].toLowerCase(); -}); -} -}; -Polymer.Base._addFeature({ -_addHostAttributes: function (attributes) { -if (!this._aggregatedAttributes) { -this._aggregatedAttributes = {}; -} -if (attributes) { -this.mixin(this._aggregatedAttributes, attributes); -} -}, -_marshalHostAttributes: function () { -if (this._aggregatedAttributes) { -this._applyAttributes(this, this._aggregatedAttributes); -} -}, -_applyAttributes: function (node, attr$) { -for (var n in attr$) { -if (!this.hasAttribute(n) && n !== 'class') { -var v = attr$[n]; -this.serializeValueToAttribute(v, n, this); -} -} -}, -_marshalAttributes: function () { -this._takeAttributesToModel(this); -}, -_takeAttributesToModel: function (model) { -if (this.hasAttributes()) { -for (var i in this._propertyInfo) { -var info = this._propertyInfo[i]; -if (this.hasAttribute(info.attribute)) { -this._setAttributeToProperty(model, info.attribute, i, info); -} -} -} -}, -_setAttributeToProperty: function (model, attribute, property, info) { -if (!this._serializing) { -var property = property || Polymer.CaseMap.dashToCamelCase(attribute); -info = info || this._propertyInfo && this._propertyInfo[property]; -if (info && !info.readOnly) { -var v = this.getAttribute(attribute); -model[property] = this.deserialize(v, info.type); -} -} -}, -_serializing: false, -reflectPropertyToAttribute: function (property, attribute, value) { -this._serializing = true; -value = value === undefined ? this[property] : value; -this.serializeValueToAttribute(value, attribute || Polymer.CaseMap.camelToDashCase(property)); -this._serializing = false; -}, -serializeValueToAttribute: function (value, attribute, node) { -var str = this.serialize(value); -node = node || this; -if (str === undefined) { -node.removeAttribute(attribute); -} else { -node.setAttribute(attribute, str); -} -}, -deserialize: function (value, type) { -switch (type) { -case Number: -value = Number(value); -break; -case Boolean: -value = value !== null; -break; -case Object: -try { -value = JSON.parse(value); -} catch (x) { -} -break; -case Array: -try { -value = JSON.parse(value); -} catch (x) { -value = null; -console.warn('Polymer::Attributes: couldn`t decode Array as JSON'); -} -break; -case Date: -value = new Date(value); -break; -case String: -default: -break; -} -return value; -}, -serialize: function (value) { -switch (typeof value) { -case 'boolean': -return value ? '' : undefined; -case 'object': -if (value instanceof Date) { -return value; -} else if (value) { -try { -return JSON.stringify(value); -} catch (x) { -return ''; -} -} -default: -return value != null ? value : undefined; -} -} -}); -Polymer.Base._addFeature({ -_setupDebouncers: function () { -this._debouncers = {}; -}, -debounce: function (jobName, callback, wait) { -return this._debouncers[jobName] = Polymer.Debounce.call(this, this._debouncers[jobName], callback, wait); -}, -isDebouncerActive: function (jobName) { -var debouncer = this._debouncers[jobName]; -return debouncer && debouncer.finish; -}, -flushDebouncer: function (jobName) { -var debouncer = this._debouncers[jobName]; -if (debouncer) { -debouncer.complete(); -} -}, -cancelDebouncer: function (jobName) { -var debouncer = this._debouncers[jobName]; -if (debouncer) { -debouncer.stop(); -} -} -}); -Polymer.version = '1.2.3'; -Polymer.Base._addFeature({ -_registerFeatures: function () { -this._prepIs(); -this._prepBehaviors(); -this._prepConstructor(); -this._prepPropertyInfo(); -}, -_prepBehavior: function (b) { -this._addHostAttributes(b.hostAttributes); -}, -_marshalBehavior: function (b) { -}, -_initFeatures: function () { -this._marshalHostAttributes(); -this._setupDebouncers(); -this._marshalBehaviors(); -} -});</script><script>Polymer.Base._addFeature({ -_prepTemplate: function () { -if (this._template === undefined) { -this._template = Polymer.DomModule.import(this.is, 'template'); -} -if (this._template && this._template.hasAttribute('is')) { -this._warn(this._logf('_prepTemplate', 'top-level Polymer template ' + 'must not be a type-extension, found', this._template, 'Move inside simple <template>.')); -} -if (this._template && !this._template.content && window.HTMLTemplateElement && HTMLTemplateElement.decorate) { -HTMLTemplateElement.decorate(this._template); -} -}, -_stampTemplate: function () { -if (this._template) { -this.root = this.instanceTemplate(this._template); -} -}, -instanceTemplate: function (template) { -var dom = document.importNode(template._content || template.content, true); -return dom; -} -}); -(function () { -var baseAttachedCallback = Polymer.Base.attachedCallback; -Polymer.Base._addFeature({ -_hostStack: [], -ready: function () { -}, -_registerHost: function (host) { -this.dataHost = host = host || Polymer.Base._hostStack[Polymer.Base._hostStack.length - 1]; -if (host && host._clients) { -host._clients.push(this); -} -}, -_beginHosting: function () { -Polymer.Base._hostStack.push(this); -if (!this._clients) { -this._clients = []; -} -}, -_endHosting: function () { -Polymer.Base._hostStack.pop(); -}, -_tryReady: function () { -if (this._canReady()) { -this._ready(); -} -}, -_canReady: function () { -return !this.dataHost || this.dataHost._clientsReadied; -}, -_ready: function () { -this._beforeClientsReady(); -if (this._template) { -this._setupRoot(); -this._readyClients(); -} -this._clientsReadied = true; -this._clients = null; -this._afterClientsReady(); -this._readySelf(); -}, -_readyClients: function () { -this._beginDistribute(); -var c$ = this._clients; -if (c$) { -for (var i = 0, l = c$.length, c; i < l && (c = c$[i]); i++) { -c._ready(); -} -} -this._finishDistribute(); -}, -_readySelf: function () { -this._doBehavior('ready'); -this._readied = true; -if (this._attachedPending) { -this._attachedPending = false; -this.attachedCallback(); -} -}, -_beforeClientsReady: function () { -}, -_afterClientsReady: function () { -}, -_beforeAttached: function () { -}, -attachedCallback: function () { -if (this._readied) { -this._beforeAttached(); -baseAttachedCallback.call(this); -} else { -this._attachedPending = true; -} -} -}); -}()); -Polymer.ArraySplice = function () { -function newSplice(index, removed, addedCount) { -return { -index: index, -removed: removed, -addedCount: addedCount -}; -} -var EDIT_LEAVE = 0; -var EDIT_UPDATE = 1; -var EDIT_ADD = 2; -var EDIT_DELETE = 3; -function ArraySplice() { -} -ArraySplice.prototype = { -calcEditDistances: function (current, currentStart, currentEnd, old, oldStart, oldEnd) { -var rowCount = oldEnd - oldStart + 1; -var columnCount = currentEnd - currentStart + 1; -var distances = new Array(rowCount); -for (var i = 0; i < rowCount; i++) { -distances[i] = new Array(columnCount); -distances[i][0] = i; -} -for (var j = 0; j < columnCount; j++) -distances[0][j] = j; -for (var i = 1; i < rowCount; i++) { -for (var j = 1; j < columnCount; j++) { -if (this.equals(current[currentStart + j - 1], old[oldStart + i - 1])) -distances[i][j] = distances[i - 1][j - 1]; -else { -var north = distances[i - 1][j] + 1; -var west = distances[i][j - 1] + 1; -distances[i][j] = north < west ? north : west; -} -} -} -return distances; -}, -spliceOperationsFromEditDistances: function (distances) { -var i = distances.length - 1; -var j = distances[0].length - 1; -var current = distances[i][j]; -var edits = []; -while (i > 0 || j > 0) { -if (i == 0) { -edits.push(EDIT_ADD); -j--; -continue; -} -if (j == 0) { -edits.push(EDIT_DELETE); -i--; -continue; -} -var northWest = distances[i - 1][j - 1]; -var west = distances[i - 1][j]; -var north = distances[i][j - 1]; -var min; -if (west < north) -min = west < northWest ? west : northWest; -else -min = north < northWest ? north : northWest; -if (min == northWest) { -if (northWest == current) { -edits.push(EDIT_LEAVE); -} else { -edits.push(EDIT_UPDATE); -current = northWest; -} -i--; -j--; -} else if (min == west) { -edits.push(EDIT_DELETE); -i--; -current = west; -} else { -edits.push(EDIT_ADD); -j--; -current = north; -} -} -edits.reverse(); -return edits; -}, -calcSplices: function (current, currentStart, currentEnd, old, oldStart, oldEnd) { -var prefixCount = 0; -var suffixCount = 0; -var minLength = Math.min(currentEnd - currentStart, oldEnd - oldStart); -if (currentStart == 0 && oldStart == 0) -prefixCount = this.sharedPrefix(current, old, minLength); -if (currentEnd == current.length && oldEnd == old.length) -suffixCount = this.sharedSuffix(current, old, minLength - prefixCount); -currentStart += prefixCount; -oldStart += prefixCount; -currentEnd -= suffixCount; -oldEnd -= suffixCount; -if (currentEnd - currentStart == 0 && oldEnd - oldStart == 0) -return []; -if (currentStart == currentEnd) { -var splice = newSplice(currentStart, [], 0); -while (oldStart < oldEnd) -splice.removed.push(old[oldStart++]); -return [splice]; -} else if (oldStart == oldEnd) -return [newSplice(currentStart, [], currentEnd - currentStart)]; -var ops = this.spliceOperationsFromEditDistances(this.calcEditDistances(current, currentStart, currentEnd, old, oldStart, oldEnd)); -var splice = undefined; -var splices = []; -var index = currentStart; -var oldIndex = oldStart; -for (var i = 0; i < ops.length; i++) { -switch (ops[i]) { -case EDIT_LEAVE: -if (splice) { -splices.push(splice); -splice = undefined; -} -index++; -oldIndex++; -break; -case EDIT_UPDATE: -if (!splice) -splice = newSplice(index, [], 0); -splice.addedCount++; -index++; -splice.removed.push(old[oldIndex]); -oldIndex++; -break; -case EDIT_ADD: -if (!splice) -splice = newSplice(index, [], 0); -splice.addedCount++; -index++; -break; -case EDIT_DELETE: -if (!splice) -splice = newSplice(index, [], 0); -splice.removed.push(old[oldIndex]); -oldIndex++; -break; -} -} -if (splice) { -splices.push(splice); -} -return splices; -}, -sharedPrefix: function (current, old, searchLength) { -for (var i = 0; i < searchLength; i++) -if (!this.equals(current[i], old[i])) -return i; -return searchLength; -}, -sharedSuffix: function (current, old, searchLength) { -var index1 = current.length; -var index2 = old.length; -var count = 0; -while (count < searchLength && this.equals(current[--index1], old[--index2])) -count++; -return count; -}, -calculateSplices: function (current, previous) { -return this.calcSplices(current, 0, current.length, previous, 0, previous.length); -}, -equals: function (currentValue, previousValue) { -return currentValue === previousValue; -} -}; -return new ArraySplice(); -}(); -Polymer.domInnerHTML = function () { -var escapeAttrRegExp = /[&\u00A0"]/g; -var escapeDataRegExp = /[&\u00A0<>]/g; -function escapeReplace(c) { -switch (c) { -case '&': -return '&amp;'; -case '<': -return '&lt;'; -case '>': -return '&gt;'; -case '"': -return '&quot;'; -case '\xA0': -return '&nbsp;'; -} -} -function escapeAttr(s) { -return s.replace(escapeAttrRegExp, escapeReplace); -} -function escapeData(s) { -return s.replace(escapeDataRegExp, escapeReplace); -} -function makeSet(arr) { -var set = {}; -for (var i = 0; i < arr.length; i++) { -set[arr[i]] = true; -} -return set; -} -var voidElements = makeSet([ -'area', -'base', -'br', -'col', -'command', -'embed', -'hr', -'img', -'input', -'keygen', -'link', -'meta', -'param', -'source', -'track', -'wbr' -]); -var plaintextParents = makeSet([ -'style', -'script', -'xmp', -'iframe', -'noembed', -'noframes', -'plaintext', -'noscript' -]); -function getOuterHTML(node, parentNode, composed) { -switch (node.nodeType) { -case Node.ELEMENT_NODE: -var tagName = node.localName; -var s = '<' + tagName; -var attrs = node.attributes; -for (var i = 0, attr; attr = attrs[i]; i++) { -s += ' ' + attr.name + '="' + escapeAttr(attr.value) + '"'; -} -s += '>'; -if (voidElements[tagName]) { -return s; -} -return s + getInnerHTML(node, composed) + '</' + tagName + '>'; -case Node.TEXT_NODE: -var data = node.data; -if (parentNode && plaintextParents[parentNode.localName]) { -return data; -} -return escapeData(data); -case Node.COMMENT_NODE: -return '<!--' + node.data + '-->'; -default: -console.error(node); -throw new Error('not implemented'); -} -} -function getInnerHTML(node, composed) { -if (node instanceof HTMLTemplateElement) -node = node.content; -var s = ''; -var c$ = Polymer.dom(node).childNodes; -c$ = composed ? node._composedChildren : c$; -for (var i = 0, l = c$.length, child; i < l && (child = c$[i]); i++) { -s += getOuterHTML(child, node, composed); -} -return s; -} -return { getInnerHTML: getInnerHTML }; -}(); -Polymer.DomApi = function () { -'use strict'; -var Settings = Polymer.Settings; -var getInnerHTML = Polymer.domInnerHTML.getInnerHTML; -var nativeInsertBefore = Element.prototype.insertBefore; -var nativeRemoveChild = Element.prototype.removeChild; -var nativeAppendChild = Element.prototype.appendChild; -var nativeCloneNode = Element.prototype.cloneNode; -var nativeImportNode = Document.prototype.importNode; -var needsToWrap = Settings.hasShadow && !Settings.nativeShadow; -var wrap = window.wrap ? window.wrap : function (node) { -return node; -}; -var DomApi = function (node) { -this.node = needsToWrap ? wrap(node) : node; -if (this.patch) { -this.patch(); -} -}; -DomApi.prototype = { -flush: function () { -Polymer.dom.flush(); -}, -deepContains: function (node) { -if (this.node.contains(node)) { -return true; -} -var n = node; -var wrappedDocument = wrap(document); -while (n && n !== wrappedDocument && n !== this.node) { -n = Polymer.dom(n).parentNode || n.host; -} -return n === this.node; -}, -_lazyDistribute: function (host) { -if (host.shadyRoot && host.shadyRoot._distributionClean) { -host.shadyRoot._distributionClean = false; -Polymer.dom.addDebouncer(host.debounce('_distribute', host._distributeContent)); -} -}, -appendChild: function (node) { -return this._addNode(node); -}, -insertBefore: function (node, ref_node) { -return this._addNode(node, ref_node); -}, -_addNode: function (node, ref_node) { -this._removeNodeFromParent(node); -var addedInsertionPoint; -var root = this.getOwnerRoot(); -if (root) { -addedInsertionPoint = this._maybeAddInsertionPoint(node, this.node); -} -if (this._nodeHasLogicalChildren(this.node)) { -if (ref_node) { -var children = this.childNodes; -var index = children.indexOf(ref_node); -if (index < 0) { -throw Error('The ref_node to be inserted before is not a child ' + 'of this node'); -} -} -this._addLogicalInfo(node, this.node, index); -} -this._addNodeToHost(node); -if (!this._maybeDistribute(node, this.node) && !this._tryRemoveUndistributedNode(node)) { -if (ref_node) { -ref_node = ref_node.localName === CONTENT ? this._firstComposedNode(ref_node) : ref_node; -} -var container = this.node._isShadyRoot ? this.node.host : this.node; -addToComposedParent(container, node, ref_node); -if (ref_node) { -nativeInsertBefore.call(container, node, ref_node); -} else { -nativeAppendChild.call(container, node); -} -} -if (addedInsertionPoint) { -this._updateInsertionPoints(root.host); -} -this.notifyObserver(); -return node; -}, -removeChild: function (node) { -if (factory(node).parentNode !== this.node) { -console.warn('The node to be removed is not a child of this node', node); -} -this._removeNodeFromHost(node); -if (!this._maybeDistribute(node, this.node)) { -var container = this.node._isShadyRoot ? this.node.host : this.node; -if (container === node.parentNode) { -removeFromComposedParent(container, node); -nativeRemoveChild.call(container, node); -} -} -this.notifyObserver(); -return node; -}, -replaceChild: function (node, ref_node) { -this.insertBefore(node, ref_node); -this.removeChild(ref_node); -return node; -}, -_hasCachedOwnerRoot: function (node) { -return Boolean(node._ownerShadyRoot !== undefined); -}, -getOwnerRoot: function () { -return this._ownerShadyRootForNode(this.node); -}, -_ownerShadyRootForNode: function (node) { -if (!node) { -return; -} -if (node._ownerShadyRoot === undefined) { -var root; -if (node._isShadyRoot) { -root = node; -} else { -var parent = Polymer.dom(node).parentNode; -if (parent) { -root = parent._isShadyRoot ? parent : this._ownerShadyRootForNode(parent); -} else { -root = null; -} -} -node._ownerShadyRoot = root; -} -return node._ownerShadyRoot; -}, -_maybeDistribute: function (node, parent) { -var fragContent = node.nodeType === Node.DOCUMENT_FRAGMENT_NODE && !node.__noContent && Polymer.dom(node).querySelector(CONTENT); -var wrappedContent = fragContent && Polymer.dom(fragContent).parentNode.nodeType !== Node.DOCUMENT_FRAGMENT_NODE; -var hasContent = fragContent || node.localName === CONTENT; -if (hasContent) { -var root = this._ownerShadyRootForNode(parent); -if (root) { -var host = root.host; -this._lazyDistribute(host); -} -} -var parentNeedsDist = this._parentNeedsDistribution(parent); -if (parentNeedsDist) { -this._lazyDistribute(parent); -} -return parentNeedsDist || hasContent && !wrappedContent; -}, -_maybeAddInsertionPoint: function (node, parent) { -var added; -if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE && !node.__noContent) { -var c$ = factory(node).querySelectorAll(CONTENT); -for (var i = 0, n, np, na; i < c$.length && (n = c$[i]); i++) { -np = factory(n).parentNode; -if (np === node) { -np = parent; -} -na = this._maybeAddInsertionPoint(n, np); -added = added || na; -} -} else if (node.localName === CONTENT) { -saveLightChildrenIfNeeded(parent); -saveLightChildrenIfNeeded(node); -added = true; -} -return added; -}, -_tryRemoveUndistributedNode: function (node) { -if (this.node.shadyRoot) { -var parent = getComposedParent(node); -if (parent) { -nativeRemoveChild.call(parent, node); -} -return true; -} -}, -_updateInsertionPoints: function (host) { -var i$ = host.shadyRoot._insertionPoints = factory(host.shadyRoot).querySelectorAll(CONTENT); -for (var i = 0, c; i < i$.length; i++) { -c = i$[i]; -saveLightChildrenIfNeeded(c); -saveLightChildrenIfNeeded(factory(c).parentNode); -} -}, -_nodeHasLogicalChildren: function (node) { -return Boolean(node._lightChildren !== undefined); -}, -_parentNeedsDistribution: function (parent) { -return parent && parent.shadyRoot && hasInsertionPoint(parent.shadyRoot); -}, -_removeNodeFromParent: function (node) { -var parent = node._lightParent || node.parentNode; -if (parent && hasDomApi(parent)) { -factory(parent).notifyObserver(); -} -this._removeNodeFromHost(node, true); -}, -_removeNodeFromHost: function (node, ensureComposedRemoval) { -var hostNeedsDist; -var root; -var parent = node._lightParent; -if (parent) { -factory(node)._distributeParent(); -root = this._ownerShadyRootForNode(node); -if (root) { -root.host._elementRemove(node); -hostNeedsDist = this._removeDistributedChildren(root, node); -} -this._removeLogicalInfo(node, parent); -} -this._removeOwnerShadyRoot(node); -if (root && hostNeedsDist) { -this._updateInsertionPoints(root.host); -this._lazyDistribute(root.host); -} else if (ensureComposedRemoval) { -removeFromComposedParent(getComposedParent(node), node); -} -}, -_removeDistributedChildren: function (root, container) { -var hostNeedsDist; -var ip$ = root._insertionPoints; -for (var i = 0; i < ip$.length; i++) { -var content = ip$[i]; -if (this._contains(container, content)) { -var dc$ = factory(content).getDistributedNodes(); -for (var j = 0; j < dc$.length; j++) { -hostNeedsDist = true; -var node = dc$[j]; -var parent = node.parentNode; -if (parent) { -removeFromComposedParent(parent, node); -nativeRemoveChild.call(parent, node); -} -} -} -} -return hostNeedsDist; -}, -_contains: function (container, node) { -while (node) { -if (node == container) { -return true; -} -node = factory(node).parentNode; -} -}, -_addNodeToHost: function (node) { -var root = this.getOwnerRoot(); -if (root) { -root.host._elementAdd(node); -} -}, -_addLogicalInfo: function (node, container, index) { -var children = factory(container).childNodes; -index = index === undefined ? children.length : index; -if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { -var c$ = arrayCopyChildNodes(node); -for (var i = 0, n; i < c$.length && (n = c$[i]); i++) { -children.splice(index++, 0, n); -n._lightParent = container; -} -} else { -children.splice(index, 0, node); -node._lightParent = container; -} -}, -_removeLogicalInfo: function (node, container) { -var children = factory(container).childNodes; -var index = children.indexOf(node); -if (index < 0 || container !== node._lightParent) { -throw Error('The node to be removed is not a child of this node'); -} -children.splice(index, 1); -node._lightParent = null; -}, -_removeOwnerShadyRoot: function (node) { -if (this._hasCachedOwnerRoot(node)) { -var c$ = factory(node).childNodes; -for (var i = 0, l = c$.length, n; i < l && (n = c$[i]); i++) { -this._removeOwnerShadyRoot(n); -} -} -node._ownerShadyRoot = undefined; -}, -_firstComposedNode: function (content) { -var n$ = factory(content).getDistributedNodes(); -for (var i = 0, l = n$.length, n, p$; i < l && (n = n$[i]); i++) { -p$ = factory(n).getDestinationInsertionPoints(); -if (p$[p$.length - 1] === content) { -return n; -} -} -}, -querySelector: function (selector) { -return this.querySelectorAll(selector)[0]; -}, -querySelectorAll: function (selector) { -return this._query(function (n) { -return matchesSelector.call(n, selector); -}, this.node); -}, -_query: function (matcher, node) { -node = node || this.node; -var list = []; -this._queryElements(factory(node).childNodes, matcher, list); -return list; -}, -_queryElements: function (elements, matcher, list) { -for (var i = 0, l = elements.length, c; i < l && (c = elements[i]); i++) { -if (c.nodeType === Node.ELEMENT_NODE) { -this._queryElement(c, matcher, list); -} -} -}, -_queryElement: function (node, matcher, list) { -if (matcher(node)) { -list.push(node); -} -this._queryElements(factory(node).childNodes, matcher, list); -}, -getDestinationInsertionPoints: function () { -return this.node._destinationInsertionPoints || []; -}, -getDistributedNodes: function () { -return this.node._distributedNodes || []; -}, -queryDistributedElements: function (selector) { -var c$ = this.getEffectiveChildNodes(); -var list = []; -for (var i = 0, l = c$.length, c; i < l && (c = c$[i]); i++) { -if (c.nodeType === Node.ELEMENT_NODE && matchesSelector.call(c, selector)) { -list.push(c); -} -} -return list; -}, -getEffectiveChildNodes: function () { -var list = []; -var c$ = this.childNodes; -for (var i = 0, l = c$.length, c; i < l && (c = c$[i]); i++) { -if (c.localName === CONTENT) { -var d$ = factory(c).getDistributedNodes(); -for (var j = 0; j < d$.length; j++) { -list.push(d$[j]); -} -} else { -list.push(c); -} -} -return list; -}, -_clear: function () { -while (this.childNodes.length) { -this.removeChild(this.childNodes[0]); -} -}, -setAttribute: function (name, value) { -this.node.setAttribute(name, value); -this._distributeParent(); -}, -removeAttribute: function (name) { -this.node.removeAttribute(name); -this._distributeParent(); -}, -_distributeParent: function () { -if (this._parentNeedsDistribution(this.parentNode)) { -this._lazyDistribute(this.parentNode); -} -}, -cloneNode: function (deep) { -var n = nativeCloneNode.call(this.node, false); -if (deep) { -var c$ = this.childNodes; -var d = factory(n); -for (var i = 0, nc; i < c$.length; i++) { -nc = factory(c$[i]).cloneNode(true); -d.appendChild(nc); -} -} -return n; -}, -importNode: function (externalNode, deep) { -var doc = this.node instanceof Document ? this.node : this.node.ownerDocument; -var n = nativeImportNode.call(doc, externalNode, false); -if (deep) { -var c$ = factory(externalNode).childNodes; -var d = factory(n); -for (var i = 0, nc; i < c$.length; i++) { -nc = factory(doc).importNode(c$[i], true); -d.appendChild(nc); -} -} -return n; -}, -observeNodes: function (callback) { -if (callback) { -if (!this.observer) { -this.observer = this.node.localName === CONTENT ? new DomApi.DistributedNodesObserver(this) : new DomApi.EffectiveNodesObserver(this); -} -return this.observer.addListener(callback); -} -}, -unobserveNodes: function (handle) { -if (this.observer) { -this.observer.removeListener(handle); -} -}, -notifyObserver: function () { -if (this.observer) { -this.observer.notify(); -} -} -}; -if (!Settings.useShadow) { -Object.defineProperties(DomApi.prototype, { -childNodes: { -get: function () { -var c$ = getLightChildren(this.node); -return Array.isArray(c$) ? c$ : arrayCopyChildNodes(this.node); -}, -configurable: true -}, -children: { -get: function () { -return Array.prototype.filter.call(this.childNodes, function (n) { -return n.nodeType === Node.ELEMENT_NODE; -}); -}, -configurable: true -}, -parentNode: { -get: function () { -return this.node._lightParent || getComposedParent(this.node); -}, -configurable: true -}, -firstChild: { -get: function () { -return this.childNodes[0]; -}, -configurable: true -}, -lastChild: { -get: function () { -var c$ = this.childNodes; -return c$[c$.length - 1]; -}, -configurable: true -}, -nextSibling: { -get: function () { -var c$ = this.parentNode && factory(this.parentNode).childNodes; -if (c$) { -return c$[Array.prototype.indexOf.call(c$, this.node) + 1]; -} -}, -configurable: true -}, -previousSibling: { -get: function () { -var c$ = this.parentNode && factory(this.parentNode).childNodes; -if (c$) { -return c$[Array.prototype.indexOf.call(c$, this.node) - 1]; -} -}, -configurable: true -}, -firstElementChild: { -get: function () { -return this.children[0]; -}, -configurable: true -}, -lastElementChild: { -get: function () { -var c$ = this.children; -return c$[c$.length - 1]; -}, -configurable: true -}, -nextElementSibling: { -get: function () { -var c$ = this.parentNode && factory(this.parentNode).children; -if (c$) { -return c$[Array.prototype.indexOf.call(c$, this.node) + 1]; -} -}, -configurable: true -}, -previousElementSibling: { -get: function () { -var c$ = this.parentNode && factory(this.parentNode).children; -if (c$) { -return c$[Array.prototype.indexOf.call(c$, this.node) - 1]; -} -}, -configurable: true -}, -textContent: { -get: function () { -var nt = this.node.nodeType; -if (nt === Node.TEXT_NODE || nt === Node.COMMENT_NODE) { -return this.node.textContent; -} else { -var tc = []; -for (var i = 0, cn = this.childNodes, c; c = cn[i]; i++) { -if (c.nodeType !== Node.COMMENT_NODE) { -tc.push(c.textContent); -} -} -return tc.join(''); -} -}, -set: function (text) { -var nt = this.node.nodeType; -if (nt === Node.TEXT_NODE || nt === Node.COMMENT_NODE) { -this.node.textContent = text; -} else { -this._clear(); -if (text) { -this.appendChild(document.createTextNode(text)); -} -} -}, -configurable: true -}, -innerHTML: { -get: function () { -var nt = this.node.nodeType; -if (nt === Node.TEXT_NODE || nt === Node.COMMENT_NODE) { -return null; -} else { -return getInnerHTML(this.node); -} -}, -set: function (text) { -var nt = this.node.nodeType; -if (nt !== Node.TEXT_NODE || nt !== Node.COMMENT_NODE) { -this._clear(); -var d = document.createElement('div'); -d.innerHTML = text; -var c$ = arrayCopyChildNodes(d); -for (var i = 0; i < c$.length; i++) { -this.appendChild(c$[i]); -} -} -}, -configurable: true -} -}); -DomApi.prototype._getComposedInnerHTML = function () { -return getInnerHTML(this.node, true); -}; -} else { -var forwardMethods = function (m$) { -for (var i = 0; i < m$.length; i++) { -forwardMethod(m$[i]); -} -}; -var forwardMethod = function (method) { -DomApi.prototype[method] = function () { -return this.node[method].apply(this.node, arguments); -}; -}; -forwardMethods([ -'cloneNode', -'appendChild', -'insertBefore', -'removeChild', -'replaceChild' -]); -DomApi.prototype.querySelectorAll = function (selector) { -return arrayCopy(this.node.querySelectorAll(selector)); -}; -DomApi.prototype.getOwnerRoot = function () { -var n = this.node; -while (n) { -if (n.nodeType === Node.DOCUMENT_FRAGMENT_NODE && n.host) { -return n; -} -n = n.parentNode; -} -}; -DomApi.prototype.importNode = function (externalNode, deep) { -var doc = this.node instanceof Document ? this.node : this.node.ownerDocument; -return doc.importNode(externalNode, deep); -}; -DomApi.prototype.getDestinationInsertionPoints = function () { -var n$ = this.node.getDestinationInsertionPoints && this.node.getDestinationInsertionPoints(); -return n$ ? arrayCopy(n$) : []; -}; -DomApi.prototype.getDistributedNodes = function () { -var n$ = this.node.getDistributedNodes && this.node.getDistributedNodes(); -return n$ ? arrayCopy(n$) : []; -}; -DomApi.prototype._distributeParent = function () { -}; -Object.defineProperties(DomApi.prototype, { -childNodes: { -get: function () { -return arrayCopyChildNodes(this.node); -}, -configurable: true -}, -children: { -get: function () { -return arrayCopyChildren(this.node); -}, -configurable: true -}, -textContent: { -get: function () { -return this.node.textContent; -}, -set: function (value) { -return this.node.textContent = value; -}, -configurable: true -}, -innerHTML: { -get: function () { -return this.node.innerHTML; -}, -set: function (value) { -return this.node.innerHTML = value; -}, -configurable: true -} -}); -var forwardProperties = function (f$) { -for (var i = 0; i < f$.length; i++) { -forwardProperty(f$[i]); -} -}; -var forwardProperty = function (name) { -Object.defineProperty(DomApi.prototype, name, { -get: function () { -return this.node[name]; -}, -configurable: true -}); -}; -forwardProperties([ -'parentNode', -'firstChild', -'lastChild', -'nextSibling', -'previousSibling', -'firstElementChild', -'lastElementChild', -'nextElementSibling', -'previousElementSibling' -]); -} -var CONTENT = 'content'; -function factory(node, patch) { -node = node || document; -if (!node.__domApi) { -node.__domApi = new DomApi(node, patch); -} -return node.__domApi; -} -; -function hasDomApi(node) { -return Boolean(node.__domApi); -} -; -Polymer.dom = function (obj, patch) { -if (obj instanceof Event) { -return Polymer.EventApi.factory(obj); -} else { -return factory(obj, patch); -} -}; -function getLightChildren(node) { -var children = node._lightChildren; -return children ? children : node.childNodes; -} -function getComposedChildren(node) { -if (!node._composedChildren) { -node._composedChildren = arrayCopyChildNodes(node); -} -return node._composedChildren; -} -function addToComposedParent(parent, node, ref_node) { -var children = getComposedChildren(parent); -var i = ref_node ? children.indexOf(ref_node) : -1; -if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { -var fragChildren = getComposedChildren(node); -for (var j = 0; j < fragChildren.length; j++) { -addNodeToComposedChildren(fragChildren[j], parent, children, i + j); -} -node._composedChildren = null; -} else { -addNodeToComposedChildren(node, parent, children, i); -} -} -function getComposedParent(node) { -return node.__patched ? node._composedParent : node.parentNode; -} -function addNodeToComposedChildren(node, parent, children, i) { -node._composedParent = parent; -children.splice(i >= 0 ? i : children.length, 0, node); -} -function removeFromComposedParent(parent, node) { -node._composedParent = null; -if (parent) { -var children = getComposedChildren(parent); -var i = children.indexOf(node); -if (i >= 0) { -children.splice(i, 1); -} -} -} -function saveLightChildrenIfNeeded(node) { -if (!node._lightChildren) { -var c$ = arrayCopyChildNodes(node); -for (var i = 0, l = c$.length, child; i < l && (child = c$[i]); i++) { -child._lightParent = child._lightParent || node; -} -node._lightChildren = c$; -} -} -function arrayCopyChildNodes(parent) { -var copy = [], i = 0; -for (var n = parent.firstChild; n; n = n.nextSibling) { -copy[i++] = n; -} -return copy; -} -function arrayCopyChildren(parent) { -var copy = [], i = 0; -for (var n = parent.firstElementChild; n; n = n.nextElementSibling) { -copy[i++] = n; -} -return copy; -} -function arrayCopy(a$) { -var l = a$.length; -var copy = new Array(l); -for (var i = 0; i < l; i++) { -copy[i] = a$[i]; -} -return copy; -} -function hasInsertionPoint(root) { -return Boolean(root && root._insertionPoints.length); -} -var p = Element.prototype; -var matchesSelector = p.matches || p.matchesSelector || p.mozMatchesSelector || p.msMatchesSelector || p.oMatchesSelector || p.webkitMatchesSelector; -return { -getLightChildren: getLightChildren, -getComposedParent: getComposedParent, -getComposedChildren: getComposedChildren, -removeFromComposedParent: removeFromComposedParent, -saveLightChildrenIfNeeded: saveLightChildrenIfNeeded, -matchesSelector: matchesSelector, -hasInsertionPoint: hasInsertionPoint, -ctor: DomApi, -factory: factory, -hasDomApi: hasDomApi, -arrayCopy: arrayCopy, -arrayCopyChildNodes: arrayCopyChildNodes, -arrayCopyChildren: arrayCopyChildren, -wrap: wrap -}; -}(); -Polymer.Base.extend(Polymer.dom, { -_flushGuard: 0, -_FLUSH_MAX: 100, -_needsTakeRecords: !Polymer.Settings.useNativeCustomElements, -_debouncers: [], -_staticFlushList: [], -_finishDebouncer: null, -flush: function () { -this._flushGuard = 0; -this._prepareFlush(); -while (this._debouncers.length && this._flushGuard < this._FLUSH_MAX) { -for (var i = 0; i < this._debouncers.length; i++) { -this._debouncers[i].complete(); -} -if (this._finishDebouncer) { -this._finishDebouncer.complete(); -} -this._prepareFlush(); -this._flushGuard++; -} -if (this._flushGuard >= this._FLUSH_MAX) { -console.warn('Polymer.dom.flush aborted. Flush may not be complete.'); -} -}, -_prepareFlush: function () { -if (this._needsTakeRecords) { -CustomElements.takeRecords(); -} -for (var i = 0; i < this._staticFlushList.length; i++) { -this._staticFlushList[i](); -} -}, -addStaticFlush: function (fn) { -this._staticFlushList.push(fn); -}, -removeStaticFlush: function (fn) { -var i = this._staticFlushList.indexOf(fn); -if (i >= 0) { -this._staticFlushList.splice(i, 1); -} -}, -addDebouncer: function (debouncer) { -this._debouncers.push(debouncer); -this._finishDebouncer = Polymer.Debounce(this._finishDebouncer, this._finishFlush); -}, -_finishFlush: function () { -Polymer.dom._debouncers = []; -} -}); -Polymer.EventApi = function () { -'use strict'; -var DomApi = Polymer.DomApi.ctor; -var Settings = Polymer.Settings; -DomApi.Event = function (event) { -this.event = event; -}; -if (Settings.useShadow) { -DomApi.Event.prototype = { -get rootTarget() { -return this.event.path[0]; -}, -get localTarget() { -return this.event.target; -}, -get path() { -return this.event.path; -} -}; -} else { -DomApi.Event.prototype = { -get rootTarget() { -return this.event.target; -}, -get localTarget() { -var current = this.event.currentTarget; -var currentRoot = current && Polymer.dom(current).getOwnerRoot(); -var p$ = this.path; -for (var i = 0; i < p$.length; i++) { -if (Polymer.dom(p$[i]).getOwnerRoot() === currentRoot) { -return p$[i]; -} -} -}, -get path() { -if (!this.event._path) { -var path = []; -var o = this.rootTarget; -while (o) { -path.push(o); -o = Polymer.dom(o).parentNode || o.host; -} -path.push(window); -this.event._path = path; -} -return this.event._path; -} -}; -} -var factory = function (event) { -if (!event.__eventApi) { -event.__eventApi = new DomApi.Event(event); -} -return event.__eventApi; -}; -return { factory: factory }; -}(); -(function () { -'use strict'; -var DomApi = Polymer.DomApi.ctor; -Object.defineProperty(DomApi.prototype, 'classList', { -get: function () { -if (!this._classList) { -this._classList = new DomApi.ClassList(this); -} -return this._classList; -}, -configurable: true -}); -DomApi.ClassList = function (host) { -this.domApi = host; -this.node = host.node; -}; -DomApi.ClassList.prototype = { -add: function () { -this.node.classList.add.apply(this.node.classList, arguments); -this.domApi._distributeParent(); -}, -remove: function () { -this.node.classList.remove.apply(this.node.classList, arguments); -this.domApi._distributeParent(); -}, -toggle: function () { -this.node.classList.toggle.apply(this.node.classList, arguments); -this.domApi._distributeParent(); -}, -contains: function () { -return this.node.classList.contains.apply(this.node.classList, arguments); -} -}; -}()); -(function () { -'use strict'; -var DomApi = Polymer.DomApi.ctor; -var Settings = Polymer.Settings; -var hasDomApi = Polymer.DomApi.hasDomApi; -DomApi.EffectiveNodesObserver = function (domApi) { -this.domApi = domApi; -this.node = this.domApi.node; -this._listeners = []; -}; -DomApi.EffectiveNodesObserver.prototype = { -addListener: function (callback) { -if (!this._isSetup) { -this._setup(); -this._isSetup = true; -} -var listener = { -fn: callback, -_nodes: [] -}; -this._listeners.push(listener); -this._scheduleNotify(); -return listener; -}, -removeListener: function (handle) { -var i = this._listeners.indexOf(handle); -if (i >= 0) { -this._listeners.splice(i, 1); -handle._nodes = []; -} -if (!this._hasListeners()) { -this._cleanup(); -this._isSetup = false; -} -}, -_setup: function () { -this._observeContentElements(this.domApi.childNodes); -}, -_cleanup: function () { -this._unobserveContentElements(this.domApi.childNodes); -}, -_hasListeners: function () { -return Boolean(this._listeners.length); -}, -_scheduleNotify: function () { -if (this._debouncer) { -this._debouncer.stop(); -} -this._debouncer = Polymer.Debounce(this._debouncer, this._notify); -this._debouncer.context = this; -Polymer.dom.addDebouncer(this._debouncer); -}, -notify: function () { -if (this._hasListeners()) { -this._scheduleNotify(); -} -}, -_notify: function (mxns) { -this._beforeCallListeners(); -this._callListeners(); -}, -_beforeCallListeners: function () { -this._updateContentElements(); -}, -_updateContentElements: function () { -this._observeContentElements(this.domApi.childNodes); -}, -_observeContentElements: function (elements) { -for (var i = 0, n; i < elements.length && (n = elements[i]); i++) { -if (this._isContent(n)) { -n.__observeNodesMap = n.__observeNodesMap || new WeakMap(); -if (!n.__observeNodesMap.has(this)) { -n.__observeNodesMap.set(this, this._observeContent(n)); -} -} -} -}, -_observeContent: function (content) { -var self = this; -var h = Polymer.dom(content).observeNodes(function () { -self._scheduleNotify(); -}); -h._avoidChangeCalculation = true; -return h; -}, -_unobserveContentElements: function (elements) { -for (var i = 0, n, h; i < elements.length && (n = elements[i]); i++) { -if (this._isContent(n)) { -h = n.__observeNodesMap.get(this); -if (h) { -Polymer.dom(n).unobserveNodes(h); -n.__observeNodesMap.delete(this); -} -} -} -}, -_isContent: function (node) { -return node.localName === 'content'; -}, -_callListeners: function () { -var o$ = this._listeners; -var nodes = this._getEffectiveNodes(); -for (var i = 0, o; i < o$.length && (o = o$[i]); i++) { -var info = this._generateListenerInfo(o, nodes); -if (info || o._alwaysNotify) { -this._callListener(o, info); -} -} -}, -_getEffectiveNodes: function () { -return this.domApi.getEffectiveChildNodes(); -}, -_generateListenerInfo: function (listener, newNodes) { -if (listener._avoidChangeCalculation) { -return true; -} -var oldNodes = listener._nodes; -var info = { -target: this.node, -addedNodes: [], -removedNodes: [] -}; -var splices = Polymer.ArraySplice.calculateSplices(newNodes, oldNodes); -for (var i = 0, s; i < splices.length && (s = splices[i]); i++) { -for (var j = 0, n; j < s.removed.length && (n = s.removed[j]); j++) { -info.removedNodes.push(n); -} -} -for (var i = 0, s; i < splices.length && (s = splices[i]); i++) { -for (var j = s.index; j < s.index + s.addedCount; j++) { -info.addedNodes.push(newNodes[j]); -} -} -listener._nodes = newNodes; -if (info.addedNodes.length || info.removedNodes.length) { -return info; -} -}, -_callListener: function (listener, info) { -return listener.fn.call(this.node, info); -}, -enableShadowAttributeTracking: function () { -} -}; -if (Settings.useShadow) { -var baseSetup = DomApi.EffectiveNodesObserver.prototype._setup; -var baseCleanup = DomApi.EffectiveNodesObserver.prototype._cleanup; -var beforeCallListeners = DomApi.EffectiveNodesObserver.prototype._beforeCallListeners; -Polymer.Base.extend(DomApi.EffectiveNodesObserver.prototype, { -_setup: function () { -if (!this._observer) { -var self = this; -this._mutationHandler = function (mxns) { -if (mxns && mxns.length) { -self._scheduleNotify(); -} -}; -this._observer = new MutationObserver(this._mutationHandler); -this._boundFlush = function () { -self._flush(); -}; -Polymer.dom.addStaticFlush(this._boundFlush); -this._observer.observe(this.node, { childList: true }); -} -baseSetup.call(this); -}, -_cleanup: function () { -this._observer.disconnect(); -this._observer = null; -this._mutationHandler = null; -Polymer.dom.removeStaticFlush(this._boundFlush); -baseCleanup.call(this); -}, -_flush: function () { -if (this._observer) { -this._mutationHandler(this._observer.takeRecords()); -} -}, -enableShadowAttributeTracking: function () { -if (this._observer) { -this._makeContentListenersAlwaysNotify(); -this._observer.disconnect(); -this._observer.observe(this.node, { -childList: true, -attributes: true, -subtree: true -}); -var root = this.domApi.getOwnerRoot(); -var host = root && root.host; -if (host && Polymer.dom(host).observer) { -Polymer.dom(host).observer.enableShadowAttributeTracking(); -} -} -}, -_makeContentListenersAlwaysNotify: function () { -for (var i = 0, h; i < this._listeners.length; i++) { -h = this._listeners[i]; -h._alwaysNotify = h._isContentListener; -} -} -}); -} -}()); -(function () { -'use strict'; -var DomApi = Polymer.DomApi.ctor; -var Settings = Polymer.Settings; -DomApi.DistributedNodesObserver = function (domApi) { -DomApi.EffectiveNodesObserver.call(this, domApi); -}; -DomApi.DistributedNodesObserver.prototype = Object.create(DomApi.EffectiveNodesObserver.prototype); -Polymer.Base.extend(DomApi.DistributedNodesObserver.prototype, { -_setup: function () { -}, -_cleanup: function () { -}, -_beforeCallListeners: function () { -}, -_getEffectiveNodes: function () { -return this.domApi.getDistributedNodes(); -} -}); -if (Settings.useShadow) { -Polymer.Base.extend(DomApi.DistributedNodesObserver.prototype, { -_setup: function () { -if (!this._observer) { -var root = this.domApi.getOwnerRoot(); -var host = root && root.host; -if (host) { -var self = this; -this._observer = Polymer.dom(host).observeNodes(function () { -self._scheduleNotify(); -}); -this._observer._isContentListener = true; -if (this._hasAttrSelect()) { -Polymer.dom(host).observer.enableShadowAttributeTracking(); -} -} -} -}, -_hasAttrSelect: function () { -var select = this.node.getAttribute('select'); -return select && select.match(/[[.]+/); -}, -_cleanup: function () { -var root = this.domApi.getOwnerRoot(); -var host = root && root.host; -if (host) { -Polymer.dom(host).unobserveNodes(this._observer); -} -this._observer = null; -} -}); -} -}()); -(function () { -var hasDomApi = Polymer.DomApi.hasDomApi; -Polymer.Base._addFeature({ -_prepShady: function () { -this._useContent = this._useContent || Boolean(this._template); -}, -_poolContent: function () { -if (this._useContent) { -saveLightChildrenIfNeeded(this); -} -}, -_setupRoot: function () { -if (this._useContent) { -this._createLocalRoot(); -if (!this.dataHost) { -upgradeLightChildren(this._lightChildren); -} -} -}, -_createLocalRoot: function () { -this.shadyRoot = this.root; -this.shadyRoot._distributionClean = false; -this.shadyRoot._hasDistributed = false; -this.shadyRoot._isShadyRoot = true; -this.shadyRoot._dirtyRoots = []; -var i$ = this.shadyRoot._insertionPoints = !this._notes || this._notes._hasContent ? this.shadyRoot.querySelectorAll('content') : []; -saveLightChildrenIfNeeded(this.shadyRoot); -for (var i = 0, c; i < i$.length; i++) { -c = i$[i]; -saveLightChildrenIfNeeded(c); -saveLightChildrenIfNeeded(c.parentNode); -} -this.shadyRoot.host = this; -}, -get domHost() { -var root = Polymer.dom(this).getOwnerRoot(); -return root && root.host; -}, -distributeContent: function (updateInsertionPoints) { -if (this.shadyRoot) { -var dom = Polymer.dom(this); -if (updateInsertionPoints) { -dom._updateInsertionPoints(this); -} -var host = getTopDistributingHost(this); -dom._lazyDistribute(host); -} -}, -_distributeContent: function () { -if (this._useContent && !this.shadyRoot._distributionClean) { -this._beginDistribute(); -this._distributeDirtyRoots(); -this._finishDistribute(); -} -}, -_beginDistribute: function () { -if (this._useContent && hasInsertionPoint(this.shadyRoot)) { -this._resetDistribution(); -this._distributePool(this.shadyRoot, this._collectPool()); -} -}, -_distributeDirtyRoots: function () { -var c$ = this.shadyRoot._dirtyRoots; -for (var i = 0, l = c$.length, c; i < l && (c = c$[i]); i++) { -c._distributeContent(); -} -this.shadyRoot._dirtyRoots = []; -}, -_finishDistribute: function () { -if (this._useContent) { -this.shadyRoot._distributionClean = true; -if (hasInsertionPoint(this.shadyRoot)) { -this._composeTree(); -notifyContentObservers(this.shadyRoot); -} else { -if (!this.shadyRoot._hasDistributed) { -this.textContent = ''; -this._composedChildren = null; -this.appendChild(this.shadyRoot); -} else { -var children = this._composeNode(this); -this._updateChildNodes(this, children); -} -} -if (!this.shadyRoot._hasDistributed) { -notifyInitialDistribution(this); -} -this.shadyRoot._hasDistributed = true; -} -}, -elementMatches: function (selector, node) { -node = node || this; -return matchesSelector.call(node, selector); -}, -_resetDistribution: function () { -var children = getLightChildren(this); -for (var i = 0; i < children.length; i++) { -var child = children[i]; -if (child._destinationInsertionPoints) { -child._destinationInsertionPoints = undefined; -} -if (isInsertionPoint(child)) { -clearDistributedDestinationInsertionPoints(child); -} -} -var root = this.shadyRoot; -var p$ = root._insertionPoints; -for (var j = 0; j < p$.length; j++) { -p$[j]._distributedNodes = []; -} -}, -_collectPool: function () { -var pool = []; -var children = getLightChildren(this); -for (var i = 0; i < children.length; i++) { -var child = children[i]; -if (isInsertionPoint(child)) { -pool.push.apply(pool, child._distributedNodes); -} else { -pool.push(child); -} -} -return pool; -}, -_distributePool: function (node, pool) { -var p$ = node._insertionPoints; -for (var i = 0, l = p$.length, p; i < l && (p = p$[i]); i++) { -this._distributeInsertionPoint(p, pool); -maybeRedistributeParent(p, this); -} -}, -_distributeInsertionPoint: function (content, pool) { -var anyDistributed = false; -for (var i = 0, l = pool.length, node; i < l; i++) { -node = pool[i]; -if (!node) { -continue; -} -if (this._matchesContentSelect(node, content)) { -distributeNodeInto(node, content); -pool[i] = undefined; -anyDistributed = true; -} -} -if (!anyDistributed) { -var children = getLightChildren(content); -for (var j = 0; j < children.length; j++) { -distributeNodeInto(children[j], content); -} -} -}, -_composeTree: function () { -this._updateChildNodes(this, this._composeNode(this)); -var p$ = this.shadyRoot._insertionPoints; -for (var i = 0, l = p$.length, p, parent; i < l && (p = p$[i]); i++) { -parent = p._lightParent || p.parentNode; -if (!parent._useContent && parent !== this && parent !== this.shadyRoot) { -this._updateChildNodes(parent, this._composeNode(parent)); -} -} -}, -_composeNode: function (node) { -var children = []; -var c$ = getLightChildren(node.shadyRoot || node); -for (var i = 0; i < c$.length; i++) { -var child = c$[i]; -if (isInsertionPoint(child)) { -var distributedNodes = child._distributedNodes; -for (var j = 0; j < distributedNodes.length; j++) { -var distributedNode = distributedNodes[j]; -if (isFinalDestination(child, distributedNode)) { -children.push(distributedNode); -} -} -} else { -children.push(child); -} -} -return children; -}, -_updateChildNodes: function (container, children) { -var composed = getComposedChildren(container); -var splices = Polymer.ArraySplice.calculateSplices(children, composed); -for (var i = 0, d = 0, s; i < splices.length && (s = splices[i]); i++) { -for (var j = 0, n; j < s.removed.length && (n = s.removed[j]); j++) { -if (getComposedParent(n) === container) { -remove(n); -} -composed.splice(s.index + d, 1); -} -d -= s.addedCount; -} -for (var i = 0, s, next; i < splices.length && (s = splices[i]); i++) { -next = composed[s.index]; -for (var j = s.index, n; j < s.index + s.addedCount; j++) { -n = children[j]; -insertBefore(container, n, next); -composed.splice(j, 0, n); -} -} -ensureComposedParent(container, children); -}, -_matchesContentSelect: function (node, contentElement) { -var select = contentElement.getAttribute('select'); -if (!select) { -return true; -} -select = select.trim(); -if (!select) { -return true; -} -if (!(node instanceof Element)) { -return false; -} -var validSelectors = /^(:not\()?[*.#[a-zA-Z_|]/; -if (!validSelectors.test(select)) { -return false; -} -return this.elementMatches(select, node); -}, -_elementAdd: function () { -}, -_elementRemove: function () { -} -}); -var saveLightChildrenIfNeeded = Polymer.DomApi.saveLightChildrenIfNeeded; -var getLightChildren = Polymer.DomApi.getLightChildren; -var matchesSelector = Polymer.DomApi.matchesSelector; -var hasInsertionPoint = Polymer.DomApi.hasInsertionPoint; -var getComposedChildren = Polymer.DomApi.getComposedChildren; -var getComposedParent = Polymer.DomApi.getComposedParent; -var removeFromComposedParent = Polymer.DomApi.removeFromComposedParent; -function distributeNodeInto(child, insertionPoint) { -insertionPoint._distributedNodes.push(child); -var points = child._destinationInsertionPoints; -if (!points) { -child._destinationInsertionPoints = [insertionPoint]; -} else { -points.push(insertionPoint); -} -} -function clearDistributedDestinationInsertionPoints(content) { -var e$ = content._distributedNodes; -if (e$) { -for (var i = 0; i < e$.length; i++) { -var d = e$[i]._destinationInsertionPoints; -if (d) { -d.splice(d.indexOf(content) + 1, d.length); -} -} -} -} -function maybeRedistributeParent(content, host) { -var parent = content._lightParent; -if (parent && parent.shadyRoot && hasInsertionPoint(parent.shadyRoot) && parent.shadyRoot._distributionClean) { -parent.shadyRoot._distributionClean = false; -host.shadyRoot._dirtyRoots.push(parent); -} -} -function isFinalDestination(insertionPoint, node) { -var points = node._destinationInsertionPoints; -return points && points[points.length - 1] === insertionPoint; -} -function isInsertionPoint(node) { -return node.localName == 'content'; -} -var nativeInsertBefore = Element.prototype.insertBefore; -var nativeRemoveChild = Element.prototype.removeChild; -function insertBefore(parentNode, newChild, refChild) { -var newChildParent = getComposedParent(newChild); -if (newChildParent !== parentNode) { -removeFromComposedParent(newChildParent, newChild); -} -remove(newChild); -nativeInsertBefore.call(parentNode, newChild, refChild || null); -newChild._composedParent = parentNode; -} -function remove(node) { -var parentNode = getComposedParent(node); -if (parentNode) { -node._composedParent = null; -nativeRemoveChild.call(parentNode, node); -} -} -function ensureComposedParent(parent, children) { -for (var i = 0, n; i < children.length; i++) { -children[i]._composedParent = parent; -} -} -function getTopDistributingHost(host) { -while (host && hostNeedsRedistribution(host)) { -host = host.domHost; -} -return host; -} -function hostNeedsRedistribution(host) { -var c$ = Polymer.dom(host).children; -for (var i = 0, c; i < c$.length; i++) { -c = c$[i]; -if (c.localName === 'content') { -return host.domHost; -} -} -} -function notifyContentObservers(root) { -for (var i = 0, c; i < root._insertionPoints.length; i++) { -c = root._insertionPoints[i]; -if (hasDomApi(c)) { -Polymer.dom(c).notifyObserver(); -} -} -} -function notifyInitialDistribution(host) { -if (hasDomApi(host)) { -Polymer.dom(host).notifyObserver(); -} -} -var needsUpgrade = window.CustomElements && !CustomElements.useNative; -function upgradeLightChildren(children) { -if (needsUpgrade && children) { -for (var i = 0; i < children.length; i++) { -CustomElements.upgrade(children[i]); -} -} -} -}()); -if (Polymer.Settings.useShadow) { -Polymer.Base._addFeature({ -_poolContent: function () { -}, -_beginDistribute: function () { -}, -distributeContent: function () { -}, -_distributeContent: function () { -}, -_finishDistribute: function () { -}, -_createLocalRoot: function () { -this.createShadowRoot(); -this.shadowRoot.appendChild(this.root); -this.root = this.shadowRoot; -} -}); -} -Polymer.DomModule = document.createElement('dom-module'); -Polymer.Base._addFeature({ -_registerFeatures: function () { -this._prepIs(); -this._prepBehaviors(); -this._prepConstructor(); -this._prepTemplate(); -this._prepShady(); -this._prepPropertyInfo(); -}, -_prepBehavior: function (b) { -this._addHostAttributes(b.hostAttributes); -}, -_initFeatures: function () { -this._registerHost(); -if (this._template) { -this._poolContent(); -this._beginHosting(); -this._stampTemplate(); -this._endHosting(); -} -this._marshalHostAttributes(); -this._setupDebouncers(); -this._marshalBehaviors(); -this._tryReady(); -}, -_marshalBehavior: function (b) { -} -});</script><script>Polymer.nar = []; -Polymer.Annotations = { -parseAnnotations: function (template) { -var list = []; -var content = template._content || template.content; -this._parseNodeAnnotations(content, list, template.hasAttribute('strip-whitespace')); -return list; -}, -_parseNodeAnnotations: function (node, list, stripWhiteSpace) { -return node.nodeType === Node.TEXT_NODE ? this._parseTextNodeAnnotation(node, list) : this._parseElementAnnotations(node, list, stripWhiteSpace); -}, -_bindingRegex: /([^{[]*)(\{\{|\[\[)(?!\}\}|\]\])(.+?)(?:\]\]|\}\})/g, -_parseBindings: function (text) { -var re = this._bindingRegex; -var parts = []; -var m, lastIndex; -while ((m = re.exec(text)) !== null) { -if (m[1]) { -parts.push({ literal: m[1] }); -} -var mode = m[2][0]; -var value = m[3].trim(); -var negate = false; -if (value[0] == '!') { -negate = true; -value = value.substring(1).trim(); -} -var customEvent, notifyEvent, colon; -if (mode == '{' && (colon = value.indexOf('::')) > 0) { -notifyEvent = value.substring(colon + 2); -value = value.substring(0, colon); -customEvent = true; -} -parts.push({ -compoundIndex: parts.length, -value: value, -mode: mode, -negate: negate, -event: notifyEvent, -customEvent: customEvent -}); -lastIndex = re.lastIndex; -} -if (lastIndex && lastIndex < text.length) { -var literal = text.substring(lastIndex); -if (literal) { -parts.push({ literal: literal }); -} -} -if (parts.length) { -return parts; -} -}, -_literalFromParts: function (parts) { -var s = ''; -for (var i = 0; i < parts.length; i++) { -var literal = parts[i].literal; -s += literal || ''; -} -return s; -}, -_parseTextNodeAnnotation: function (node, list) { -var parts = this._parseBindings(node.textContent); -if (parts) { -node.textContent = this._literalFromParts(parts) || ' '; -var annote = { -bindings: [{ -kind: 'text', -name: 'textContent', -parts: parts, -isCompound: parts.length !== 1 -}] -}; -list.push(annote); -return annote; -} -}, -_parseElementAnnotations: function (element, list, stripWhiteSpace) { -var annote = { -bindings: [], -events: [] -}; -if (element.localName === 'content') { -list._hasContent = true; -} -this._parseChildNodesAnnotations(element, annote, list, stripWhiteSpace); -if (element.attributes) { -this._parseNodeAttributeAnnotations(element, annote, list); -if (this.prepElement) { -this.prepElement(element); -} -} -if (annote.bindings.length || annote.events.length || annote.id) { -list.push(annote); -} -return annote; -}, -_parseChildNodesAnnotations: function (root, annote, list, stripWhiteSpace) { -if (root.firstChild) { -var node = root.firstChild; -var i = 0; -while (node) { -var next = node.nextSibling; -if (node.localName === 'template' && !node.hasAttribute('preserve-content')) { -this._parseTemplate(node, i, list, annote); -} -if (node.nodeType === Node.TEXT_NODE) { -var n = next; -while (n && n.nodeType === Node.TEXT_NODE) { -node.textContent += n.textContent; -next = n.nextSibling; -root.removeChild(n); -n = next; -} -if (stripWhiteSpace && !node.textContent.trim()) { -root.removeChild(node); -i--; -} -} -if (node.parentNode) { -var childAnnotation = this._parseNodeAnnotations(node, list, stripWhiteSpace); -if (childAnnotation) { -childAnnotation.parent = annote; -childAnnotation.index = i; -} -} -node = next; -i++; -} -} -}, -_parseTemplate: function (node, index, list, parent) { -var content = document.createDocumentFragment(); -content._notes = this.parseAnnotations(node); -content.appendChild(node.content); -list.push({ -bindings: Polymer.nar, -events: Polymer.nar, -templateContent: content, -parent: parent, -index: index -}); -}, -_parseNodeAttributeAnnotations: function (node, annotation) { -var attrs = Array.prototype.slice.call(node.attributes); -for (var i = attrs.length - 1, a; a = attrs[i]; i--) { -var n = a.name; -var v = a.value; -var b; -if (n.slice(0, 3) === 'on-') { -node.removeAttribute(n); -annotation.events.push({ -name: n.slice(3), -value: v -}); -} else if (b = this._parseNodeAttributeAnnotation(node, n, v)) { -annotation.bindings.push(b); -} else if (n === 'id') { -annotation.id = v; -} -} -}, -_parseNodeAttributeAnnotation: function (node, name, value) { -var parts = this._parseBindings(value); -if (parts) { -var origName = name; -var kind = 'property'; -if (name[name.length - 1] == '$') { -name = name.slice(0, -1); -kind = 'attribute'; -} -var literal = this._literalFromParts(parts); -if (literal && kind == 'attribute') { -node.setAttribute(name, literal); -} -if (node.localName == 'input' && name == 'value') { -node.setAttribute(origName, ''); -} -node.removeAttribute(origName); -if (kind === 'property') { -name = Polymer.CaseMap.dashToCamelCase(name); -} -return { -kind: kind, -name: name, -parts: parts, -literal: literal, -isCompound: parts.length !== 1 -}; -} -}, -_localSubTree: function (node, host) { -return node === host ? node.childNodes : node._lightChildren || node.childNodes; -}, -findAnnotatedNode: function (root, annote) { -var parent = annote.parent && Polymer.Annotations.findAnnotatedNode(root, annote.parent); -return !parent ? root : Polymer.Annotations._localSubTree(parent, root)[annote.index]; -} -}; -(function () { -function resolveCss(cssText, ownerDocument) { -return cssText.replace(CSS_URL_RX, function (m, pre, url, post) { -return pre + '\'' + resolve(url.replace(/["']/g, ''), ownerDocument) + '\'' + post; -}); -} -function resolveAttrs(element, ownerDocument) { -for (var name in URL_ATTRS) { -var a$ = URL_ATTRS[name]; -for (var i = 0, l = a$.length, a, at, v; i < l && (a = a$[i]); i++) { -if (name === '*' || element.localName === name) { -at = element.attributes[a]; -v = at && at.value; -if (v && v.search(BINDING_RX) < 0) { -at.value = a === 'style' ? resolveCss(v, ownerDocument) : resolve(v, ownerDocument); -} -} -} -} -} -function resolve(url, ownerDocument) { -if (url && url[0] === '#') { -return url; -} -var resolver = getUrlResolver(ownerDocument); -resolver.href = url; -return resolver.href || url; -} -var tempDoc; -var tempDocBase; -function resolveUrl(url, baseUri) { -if (!tempDoc) { -tempDoc = document.implementation.createHTMLDocument('temp'); -tempDocBase = tempDoc.createElement('base'); -tempDoc.head.appendChild(tempDocBase); -} -tempDocBase.href = baseUri; -return resolve(url, tempDoc); -} -function getUrlResolver(ownerDocument) { -return ownerDocument.__urlResolver || (ownerDocument.__urlResolver = ownerDocument.createElement('a')); -} -var CSS_URL_RX = /(url\()([^)]*)(\))/g; -var URL_ATTRS = { -'*': [ -'href', -'src', -'style', -'url' -], -form: ['action'] -}; -var BINDING_RX = /\{\{|\[\[/; -Polymer.ResolveUrl = { -resolveCss: resolveCss, -resolveAttrs: resolveAttrs, -resolveUrl: resolveUrl -}; -}()); -Polymer.Base._addFeature({ -_prepAnnotations: function () { -if (!this._template) { -this._notes = []; -} else { -var self = this; -Polymer.Annotations.prepElement = function (element) { -self._prepElement(element); -}; -if (this._template._content && this._template._content._notes) { -this._notes = this._template._content._notes; -} else { -this._notes = Polymer.Annotations.parseAnnotations(this._template); -} -this._processAnnotations(this._notes); -Polymer.Annotations.prepElement = null; -} -}, -_processAnnotations: function (notes) { -for (var i = 0; i < notes.length; i++) { -var note = notes[i]; -for (var j = 0; j < note.bindings.length; j++) { -var b = note.bindings[j]; -for (var k = 0; k < b.parts.length; k++) { -var p = b.parts[k]; -if (!p.literal) { -p.signature = this._parseMethod(p.value); -if (!p.signature) { -p.model = this._modelForPath(p.value); -} -} -} -} -if (note.templateContent) { -this._processAnnotations(note.templateContent._notes); -var pp = note.templateContent._parentProps = this._discoverTemplateParentProps(note.templateContent._notes); -var bindings = []; -for (var prop in pp) { -bindings.push({ -index: note.index, -kind: 'property', -name: '_parent_' + prop, -parts: [{ -mode: '{', -model: prop, -value: prop -}] -}); -} -note.bindings = note.bindings.concat(bindings); -} -} -}, -_discoverTemplateParentProps: function (notes) { -var pp = {}; -for (var i = 0, n; i < notes.length && (n = notes[i]); i++) { -for (var j = 0, b$ = n.bindings, b; j < b$.length && (b = b$[j]); j++) { -for (var k = 0, p$ = b.parts, p; k < p$.length && (p = p$[k]); k++) { -if (p.signature) { -var args = p.signature.args; -for (var kk = 0; kk < args.length; kk++) { -pp[args[kk].model] = true; -} -} else { -pp[p.model] = true; -} -} -} -if (n.templateContent) { -var tpp = n.templateContent._parentProps; -Polymer.Base.mixin(pp, tpp); -} -} -return pp; -}, -_prepElement: function (element) { -Polymer.ResolveUrl.resolveAttrs(element, this._template.ownerDocument); -}, -_findAnnotatedNode: Polymer.Annotations.findAnnotatedNode, -_marshalAnnotationReferences: function () { -if (this._template) { -this._marshalIdNodes(); -this._marshalAnnotatedNodes(); -this._marshalAnnotatedListeners(); -} -}, -_configureAnnotationReferences: function (config) { -var notes = this._notes; -var nodes = this._nodes; -for (var i = 0; i < notes.length; i++) { -var note = notes[i]; -var node = nodes[i]; -this._configureTemplateContent(note, node); -this._configureCompoundBindings(note, node); -} -}, -_configureTemplateContent: function (note, node) { -if (note.templateContent) { -node._content = note.templateContent; -} -}, -_configureCompoundBindings: function (note, node) { -var bindings = note.bindings; -for (var i = 0; i < bindings.length; i++) { -var binding = bindings[i]; -if (binding.isCompound) { -var storage = node.__compoundStorage__ || (node.__compoundStorage__ = {}); -var parts = binding.parts; -var literals = new Array(parts.length); -for (var j = 0; j < parts.length; j++) { -literals[j] = parts[j].literal; -} -var name = binding.name; -storage[name] = literals; -if (binding.literal && binding.kind == 'property') { -if (node._configValue) { -node._configValue(name, binding.literal); -} else { -node[name] = binding.literal; -} -} -} -} -}, -_marshalIdNodes: function () { -this.$ = {}; -for (var i = 0, l = this._notes.length, a; i < l && (a = this._notes[i]); i++) { -if (a.id) { -this.$[a.id] = this._findAnnotatedNode(this.root, a); -} -} -}, -_marshalAnnotatedNodes: function () { -if (this._notes && this._notes.length) { -var r = new Array(this._notes.length); -for (var i = 0; i < this._notes.length; i++) { -r[i] = this._findAnnotatedNode(this.root, this._notes[i]); -} -this._nodes = r; -} -}, -_marshalAnnotatedListeners: function () { -for (var i = 0, l = this._notes.length, a; i < l && (a = this._notes[i]); i++) { -if (a.events && a.events.length) { -var node = this._findAnnotatedNode(this.root, a); -for (var j = 0, e$ = a.events, e; j < e$.length && (e = e$[j]); j++) { -this.listen(node, e.name, e.value); -} -} -} -} -}); -Polymer.Base._addFeature({ -listeners: {}, -_listenListeners: function (listeners) { -var node, name, eventName; -for (eventName in listeners) { -if (eventName.indexOf('.') < 0) { -node = this; -name = eventName; -} else { -name = eventName.split('.'); -node = this.$[name[0]]; -name = name[1]; -} -this.listen(node, name, listeners[eventName]); -} -}, -listen: function (node, eventName, methodName) { -var handler = this._recallEventHandler(this, eventName, node, methodName); -if (!handler) { -handler = this._createEventHandler(node, eventName, methodName); -} -if (handler._listening) { -return; -} -this._listen(node, eventName, handler); -handler._listening = true; -}, -_boundListenerKey: function (eventName, methodName) { -return eventName + ':' + methodName; -}, -_recordEventHandler: function (host, eventName, target, methodName, handler) { -var hbl = host.__boundListeners; -if (!hbl) { -hbl = host.__boundListeners = new WeakMap(); -} -var bl = hbl.get(target); -if (!bl) { -bl = {}; -hbl.set(target, bl); -} -var key = this._boundListenerKey(eventName, methodName); -bl[key] = handler; -}, -_recallEventHandler: function (host, eventName, target, methodName) { -var hbl = host.__boundListeners; -if (!hbl) { -return; -} -var bl = hbl.get(target); -if (!bl) { -return; -} -var key = this._boundListenerKey(eventName, methodName); -return bl[key]; -}, -_createEventHandler: function (node, eventName, methodName) { -var host = this; -var handler = function (e) { -if (host[methodName]) { -host[methodName](e, e.detail); -} else { -host._warn(host._logf('_createEventHandler', 'listener method `' + methodName + '` not defined')); -} -}; -handler._listening = false; -this._recordEventHandler(host, eventName, node, methodName, handler); -return handler; -}, -unlisten: function (node, eventName, methodName) { -var handler = this._recallEventHandler(this, eventName, node, methodName); -if (handler) { -this._unlisten(node, eventName, handler); -handler._listening = false; -} -}, -_listen: function (node, eventName, handler) { -node.addEventListener(eventName, handler); -}, -_unlisten: function (node, eventName, handler) { -node.removeEventListener(eventName, handler); -} -}); -(function () { -'use strict'; -var wrap = Polymer.DomApi.wrap; -var HAS_NATIVE_TA = typeof document.head.style.touchAction === 'string'; -var GESTURE_KEY = '__polymerGestures'; -var HANDLED_OBJ = '__polymerGesturesHandled'; -var TOUCH_ACTION = '__polymerGesturesTouchAction'; -var TAP_DISTANCE = 25; -var TRACK_DISTANCE = 5; -var TRACK_LENGTH = 2; -var MOUSE_TIMEOUT = 2500; -var MOUSE_EVENTS = [ -'mousedown', -'mousemove', -'mouseup', -'click' -]; -var MOUSE_WHICH_TO_BUTTONS = [ -0, -1, -4, -2 -]; -var MOUSE_HAS_BUTTONS = function () { -try { -return new MouseEvent('test', { buttons: 1 }).buttons === 1; -} catch (e) { -return false; -} -}(); -var IS_TOUCH_ONLY = navigator.userAgent.match(/iP(?:[oa]d|hone)|Android/); -var mouseCanceller = function (mouseEvent) { -mouseEvent[HANDLED_OBJ] = { skip: true }; -if (mouseEvent.type === 'click') { -var path = Polymer.dom(mouseEvent).path; -for (var i = 0; i < path.length; i++) { -if (path[i] === POINTERSTATE.mouse.target) { -return; -} -} -mouseEvent.preventDefault(); -mouseEvent.stopPropagation(); -} -}; -function setupTeardownMouseCanceller(setup) { -for (var i = 0, en; i < MOUSE_EVENTS.length; i++) { -en = MOUSE_EVENTS[i]; -if (setup) { -document.addEventListener(en, mouseCanceller, true); -} else { -document.removeEventListener(en, mouseCanceller, true); -} -} -} -function ignoreMouse() { -if (IS_TOUCH_ONLY) { -return; -} -if (!POINTERSTATE.mouse.mouseIgnoreJob) { -setupTeardownMouseCanceller(true); -} -var unset = function () { -setupTeardownMouseCanceller(); -POINTERSTATE.mouse.target = null; -POINTERSTATE.mouse.mouseIgnoreJob = null; -}; -POINTERSTATE.mouse.mouseIgnoreJob = Polymer.Debounce(POINTERSTATE.mouse.mouseIgnoreJob, unset, MOUSE_TIMEOUT); -} -function hasLeftMouseButton(ev) { -var type = ev.type; -if (MOUSE_EVENTS.indexOf(type) === -1) { -return false; -} -if (type === 'mousemove') { -var buttons = ev.buttons === undefined ? 1 : ev.buttons; -if (ev instanceof window.MouseEvent && !MOUSE_HAS_BUTTONS) { -buttons = MOUSE_WHICH_TO_BUTTONS[ev.which] || 0; -} -return Boolean(buttons & 1); -} else { -var button = ev.button === undefined ? 0 : ev.button; -return button === 0; -} -} -function isSyntheticClick(ev) { -if (ev.type === 'click') { -if (ev.detail === 0) { -return true; -} -var t = Gestures.findOriginalTarget(ev); -var bcr = t.getBoundingClientRect(); -var x = ev.pageX, y = ev.pageY; -return !(x >= bcr.left && x <= bcr.right && (y >= bcr.top && y <= bcr.bottom)); -} -return false; -} -var POINTERSTATE = { -mouse: { -target: null, -mouseIgnoreJob: null -}, -touch: { -x: 0, -y: 0, -id: -1, -scrollDecided: false -} -}; -function firstTouchAction(ev) { -var path = Polymer.dom(ev).path; -var ta = 'auto'; -for (var i = 0, n; i < path.length; i++) { -n = path[i]; -if (n[TOUCH_ACTION]) { -ta = n[TOUCH_ACTION]; -break; -} -} -return ta; -} -function trackDocument(stateObj, movefn, upfn) { -stateObj.movefn = movefn; -stateObj.upfn = upfn; -document.addEventListener('mousemove', movefn); -document.addEventListener('mouseup', upfn); -} -function untrackDocument(stateObj) { -document.removeEventListener('mousemove', stateObj.movefn); -document.removeEventListener('mouseup', stateObj.upfn); -} -var Gestures = { -gestures: {}, -recognizers: [], -deepTargetFind: function (x, y) { -var node = document.elementFromPoint(x, y); -var next = node; -while (next && next.shadowRoot) { -next = next.shadowRoot.elementFromPoint(x, y); -if (next) { -node = next; -} -} -return node; -}, -findOriginalTarget: function (ev) { -if (ev.path) { -return ev.path[0]; -} -return ev.target; -}, -handleNative: function (ev) { -var handled; -var type = ev.type; -var node = wrap(ev.currentTarget); -var gobj = node[GESTURE_KEY]; -if (!gobj) { -return; -} -var gs = gobj[type]; -if (!gs) { -return; -} -if (!ev[HANDLED_OBJ]) { -ev[HANDLED_OBJ] = {}; -if (type.slice(0, 5) === 'touch') { -var t = ev.changedTouches[0]; -if (type === 'touchstart') { -if (ev.touches.length === 1) { -POINTERSTATE.touch.id = t.identifier; -} -} -if (POINTERSTATE.touch.id !== t.identifier) { -return; -} -if (!HAS_NATIVE_TA) { -if (type === 'touchstart' || type === 'touchmove') { -Gestures.handleTouchAction(ev); -} -} -if (type === 'touchend') { -POINTERSTATE.mouse.target = Polymer.dom(ev).rootTarget; -ignoreMouse(true); -} -} -} -handled = ev[HANDLED_OBJ]; -if (handled.skip) { -return; -} -var recognizers = Gestures.recognizers; -for (var i = 0, r; i < recognizers.length; i++) { -r = recognizers[i]; -if (gs[r.name] && !handled[r.name]) { -if (r.flow && r.flow.start.indexOf(ev.type) > -1) { -if (r.reset) { -r.reset(); -} -} -} -} -for (var i = 0, r; i < recognizers.length; i++) { -r = recognizers[i]; -if (gs[r.name] && !handled[r.name]) { -handled[r.name] = true; -r[type](ev); -} -} -}, -handleTouchAction: function (ev) { -var t = ev.changedTouches[0]; -var type = ev.type; -if (type === 'touchstart') { -POINTERSTATE.touch.x = t.clientX; -POINTERSTATE.touch.y = t.clientY; -POINTERSTATE.touch.scrollDecided = false; -} else if (type === 'touchmove') { -if (POINTERSTATE.touch.scrollDecided) { -return; -} -POINTERSTATE.touch.scrollDecided = true; -var ta = firstTouchAction(ev); -var prevent = false; -var dx = Math.abs(POINTERSTATE.touch.x - t.clientX); -var dy = Math.abs(POINTERSTATE.touch.y - t.clientY); -if (!ev.cancelable) { -} else if (ta === 'none') { -prevent = true; -} else if (ta === 'pan-x') { -prevent = dy > dx; -} else if (ta === 'pan-y') { -prevent = dx > dy; -} -if (prevent) { -ev.preventDefault(); -} else { -Gestures.prevent('track'); -} -} -}, -add: function (node, evType, handler) { -node = wrap(node); -var recognizer = this.gestures[evType]; -var deps = recognizer.deps; -var name = recognizer.name; -var gobj = node[GESTURE_KEY]; -if (!gobj) { -node[GESTURE_KEY] = gobj = {}; -} -for (var i = 0, dep, gd; i < deps.length; i++) { -dep = deps[i]; -if (IS_TOUCH_ONLY && MOUSE_EVENTS.indexOf(dep) > -1) { -continue; -} -gd = gobj[dep]; -if (!gd) { -gobj[dep] = gd = { _count: 0 }; -} -if (gd._count === 0) { -node.addEventListener(dep, this.handleNative); -} -gd[name] = (gd[name] || 0) + 1; -gd._count = (gd._count || 0) + 1; -} -node.addEventListener(evType, handler); -if (recognizer.touchAction) { -this.setTouchAction(node, recognizer.touchAction); -} -}, -remove: function (node, evType, handler) { -node = wrap(node); -var recognizer = this.gestures[evType]; -var deps = recognizer.deps; -var name = recognizer.name; -var gobj = node[GESTURE_KEY]; -if (gobj) { -for (var i = 0, dep, gd; i < deps.length; i++) { -dep = deps[i]; -gd = gobj[dep]; -if (gd && gd[name]) { -gd[name] = (gd[name] || 1) - 1; -gd._count = (gd._count || 1) - 1; -if (gd._count === 0) { -node.removeEventListener(dep, this.handleNative); -} -} -} -} -node.removeEventListener(evType, handler); -}, -register: function (recog) { -this.recognizers.push(recog); -for (var i = 0; i < recog.emits.length; i++) { -this.gestures[recog.emits[i]] = recog; -} -}, -findRecognizerByEvent: function (evName) { -for (var i = 0, r; i < this.recognizers.length; i++) { -r = this.recognizers[i]; -for (var j = 0, n; j < r.emits.length; j++) { -n = r.emits[j]; -if (n === evName) { -return r; -} -} -} -return null; -}, -setTouchAction: function (node, value) { -if (HAS_NATIVE_TA) { -node.style.touchAction = value; -} -node[TOUCH_ACTION] = value; -}, -fire: function (target, type, detail) { -var ev = Polymer.Base.fire(type, detail, { -node: target, -bubbles: true, -cancelable: true -}); -if (ev.defaultPrevented) { -var se = detail.sourceEvent; -if (se && se.preventDefault) { -se.preventDefault(); -} -} -}, -prevent: function (evName) { -var recognizer = this.findRecognizerByEvent(evName); -if (recognizer.info) { -recognizer.info.prevent = true; -} -} -}; -Gestures.register({ -name: 'downup', -deps: [ -'mousedown', -'touchstart', -'touchend' -], -flow: { -start: [ -'mousedown', -'touchstart' -], -end: [ -'mouseup', -'touchend' -] -}, -emits: [ -'down', -'up' -], -info: { -movefn: function () { -}, -upfn: function () { -} -}, -reset: function () { -untrackDocument(this.info); -}, -mousedown: function (e) { -if (!hasLeftMouseButton(e)) { -return; -} -var t = Gestures.findOriginalTarget(e); -var self = this; -var movefn = function movefn(e) { -if (!hasLeftMouseButton(e)) { -self.fire('up', t, e); -untrackDocument(self.info); -} -}; -var upfn = function upfn(e) { -if (hasLeftMouseButton(e)) { -self.fire('up', t, e); -} -untrackDocument(self.info); -}; -trackDocument(this.info, movefn, upfn); -this.fire('down', t, e); -}, -touchstart: function (e) { -this.fire('down', Gestures.findOriginalTarget(e), e.changedTouches[0]); -}, -touchend: function (e) { -this.fire('up', Gestures.findOriginalTarget(e), e.changedTouches[0]); -}, -fire: function (type, target, event) { -var self = this; -Gestures.fire(target, type, { -x: event.clientX, -y: event.clientY, -sourceEvent: event, -prevent: function (e) { -return Gestures.prevent(e); -} -}); -} -}); -Gestures.register({ -name: 'track', -touchAction: 'none', -deps: [ -'mousedown', -'touchstart', -'touchmove', -'touchend' -], -flow: { -start: [ -'mousedown', -'touchstart' -], -end: [ -'mouseup', -'touchend' -] -}, -emits: ['track'], -info: { -x: 0, -y: 0, -state: 'start', -started: false, -moves: [], -addMove: function (move) { -if (this.moves.length > TRACK_LENGTH) { -this.moves.shift(); -} -this.moves.push(move); -}, -movefn: function () { -}, -upfn: function () { -}, -prevent: false -}, -reset: function () { -this.info.state = 'start'; -this.info.started = false; -this.info.moves = []; -this.info.x = 0; -this.info.y = 0; -this.info.prevent = false; -untrackDocument(this.info); -}, -hasMovedEnough: function (x, y) { -if (this.info.prevent) { -return false; -} -if (this.info.started) { -return true; -} -var dx = Math.abs(this.info.x - x); -var dy = Math.abs(this.info.y - y); -return dx >= TRACK_DISTANCE || dy >= TRACK_DISTANCE; -}, -mousedown: function (e) { -if (!hasLeftMouseButton(e)) { -return; -} -var t = Gestures.findOriginalTarget(e); -var self = this; -var movefn = function movefn(e) { -var x = e.clientX, y = e.clientY; -if (self.hasMovedEnough(x, y)) { -self.info.state = self.info.started ? e.type === 'mouseup' ? 'end' : 'track' : 'start'; -self.info.addMove({ -x: x, -y: y -}); -if (!hasLeftMouseButton(e)) { -self.info.state = 'end'; -untrackDocument(self.info); -} -self.fire(t, e); -self.info.started = true; -} -}; -var upfn = function upfn(e) { -if (self.info.started) { -Gestures.prevent('tap'); -movefn(e); -} -untrackDocument(self.info); -}; -trackDocument(this.info, movefn, upfn); -this.info.x = e.clientX; -this.info.y = e.clientY; -}, -touchstart: function (e) { -var ct = e.changedTouches[0]; -this.info.x = ct.clientX; -this.info.y = ct.clientY; -}, -touchmove: function (e) { -var t = Gestures.findOriginalTarget(e); -var ct = e.changedTouches[0]; -var x = ct.clientX, y = ct.clientY; -if (this.hasMovedEnough(x, y)) { -this.info.addMove({ -x: x, -y: y -}); -this.fire(t, ct); -this.info.state = 'track'; -this.info.started = true; -} -}, -touchend: function (e) { -var t = Gestures.findOriginalTarget(e); -var ct = e.changedTouches[0]; -if (this.info.started) { -Gestures.prevent('tap'); -this.info.state = 'end'; -this.info.addMove({ -x: ct.clientX, -y: ct.clientY -}); -this.fire(t, ct); -} -}, -fire: function (target, touch) { -var secondlast = this.info.moves[this.info.moves.length - 2]; -var lastmove = this.info.moves[this.info.moves.length - 1]; -var dx = lastmove.x - this.info.x; -var dy = lastmove.y - this.info.y; -var ddx, ddy = 0; -if (secondlast) { -ddx = lastmove.x - secondlast.x; -ddy = lastmove.y - secondlast.y; -} -return Gestures.fire(target, 'track', { -state: this.info.state, -x: touch.clientX, -y: touch.clientY, -dx: dx, -dy: dy, -ddx: ddx, -ddy: ddy, -sourceEvent: touch, -hover: function () { -return Gestures.deepTargetFind(touch.clientX, touch.clientY); -} -}); -} -}); -Gestures.register({ -name: 'tap', -deps: [ -'mousedown', -'click', -'touchstart', -'touchend' -], -flow: { -start: [ -'mousedown', -'touchstart' -], -end: [ -'click', -'touchend' -] -}, -emits: ['tap'], -info: { -x: NaN, -y: NaN, -prevent: false -}, -reset: function () { -this.info.x = NaN; -this.info.y = NaN; -this.info.prevent = false; -}, -save: function (e) { -this.info.x = e.clientX; -this.info.y = e.clientY; -}, -mousedown: function (e) { -if (hasLeftMouseButton(e)) { -this.save(e); -} -}, -click: function (e) { -if (hasLeftMouseButton(e)) { -this.forward(e); -} -}, -touchstart: function (e) { -this.save(e.changedTouches[0]); -}, -touchend: function (e) { -this.forward(e.changedTouches[0]); -}, -forward: function (e) { -var dx = Math.abs(e.clientX - this.info.x); -var dy = Math.abs(e.clientY - this.info.y); -var t = Gestures.findOriginalTarget(e); -if (isNaN(dx) || isNaN(dy) || dx <= TAP_DISTANCE && dy <= TAP_DISTANCE || isSyntheticClick(e)) { -if (!this.info.prevent) { -Gestures.fire(t, 'tap', { -x: e.clientX, -y: e.clientY, -sourceEvent: e -}); -} -} -} -}); -var DIRECTION_MAP = { -x: 'pan-x', -y: 'pan-y', -none: 'none', -all: 'auto' -}; -Polymer.Base._addFeature({ -_listen: function (node, eventName, handler) { -if (Gestures.gestures[eventName]) { -Gestures.add(node, eventName, handler); -} else { -node.addEventListener(eventName, handler); -} -}, -_unlisten: function (node, eventName, handler) { -if (Gestures.gestures[eventName]) { -Gestures.remove(node, eventName, handler); -} else { -node.removeEventListener(eventName, handler); -} -}, -setScrollDirection: function (direction, node) { -node = node || this; -Gestures.setTouchAction(node, DIRECTION_MAP[direction] || 'auto'); -} -}); -Polymer.Gestures = Gestures; -}()); -Polymer.Async = { -_currVal: 0, -_lastVal: 0, -_callbacks: [], -_twiddleContent: 0, -_twiddle: document.createTextNode(''), -run: function (callback, waitTime) { -if (waitTime > 0) { -return ~setTimeout(callback, waitTime); -} else { -this._twiddle.textContent = this._twiddleContent++; -this._callbacks.push(callback); -return this._currVal++; -} -}, -cancel: function (handle) { -if (handle < 0) { -clearTimeout(~handle); -} else { -var idx = handle - this._lastVal; -if (idx >= 0) { -if (!this._callbacks[idx]) { -throw 'invalid async handle: ' + handle; -} -this._callbacks[idx] = null; -} -} -}, -_atEndOfMicrotask: function () { -var len = this._callbacks.length; -for (var i = 0; i < len; i++) { -var cb = this._callbacks[i]; -if (cb) { -try { -cb(); -} catch (e) { -i++; -this._callbacks.splice(0, i); -this._lastVal += i; -this._twiddle.textContent = this._twiddleContent++; -throw e; -} -} -} -this._callbacks.splice(0, len); -this._lastVal += len; -} -}; -new window.MutationObserver(function () { -Polymer.Async._atEndOfMicrotask(); -}).observe(Polymer.Async._twiddle, { characterData: true }); -Polymer.Debounce = function () { -var Async = Polymer.Async; -var Debouncer = function (context) { -this.context = context; -var self = this; -this.boundComplete = function () { -self.complete(); -}; -}; -Debouncer.prototype = { -go: function (callback, wait) { -var h; -this.finish = function () { -Async.cancel(h); -}; -h = Async.run(this.boundComplete, wait); -this.callback = callback; -}, -stop: function () { -if (this.finish) { -this.finish(); -this.finish = null; -} -}, -complete: function () { -if (this.finish) { -this.stop(); -this.callback.call(this.context); -} -} -}; -function debounce(debouncer, callback, wait) { -if (debouncer) { -debouncer.stop(); -} else { -debouncer = new Debouncer(this); -} -debouncer.go(callback, wait); -return debouncer; -} -return debounce; -}(); -Polymer.Base._addFeature({ -$$: function (slctr) { -return Polymer.dom(this.root).querySelector(slctr); -}, -toggleClass: function (name, bool, node) { -node = node || this; -if (arguments.length == 1) { -bool = !node.classList.contains(name); -} -if (bool) { -Polymer.dom(node).classList.add(name); -} else { -Polymer.dom(node).classList.remove(name); -} -}, -toggleAttribute: function (name, bool, node) { -node = node || this; -if (arguments.length == 1) { -bool = !node.hasAttribute(name); -} -if (bool) { -Polymer.dom(node).setAttribute(name, ''); -} else { -Polymer.dom(node).removeAttribute(name); -} -}, -classFollows: function (name, toElement, fromElement) { -if (fromElement) { -Polymer.dom(fromElement).classList.remove(name); -} -if (toElement) { -Polymer.dom(toElement).classList.add(name); -} -}, -attributeFollows: function (name, toElement, fromElement) { -if (fromElement) { -Polymer.dom(fromElement).removeAttribute(name); -} -if (toElement) { -Polymer.dom(toElement).setAttribute(name, ''); -} -}, -getEffectiveChildNodes: function () { -return Polymer.dom(this).getEffectiveChildNodes(); -}, -getEffectiveChildren: function () { -var list = Polymer.dom(this).getEffectiveChildNodes(); -return list.filter(function (n) { -return n.nodeType === Node.ELEMENT_NODE; -}); -}, -getEffectiveTextContent: function () { -var cn = this.getEffectiveChildNodes(); -var tc = []; -for (var i = 0, c; c = cn[i]; i++) { -if (c.nodeType !== Node.COMMENT_NODE) { -tc.push(Polymer.dom(c).textContent); -} -} -return tc.join(''); -}, -queryEffectiveChildren: function (slctr) { -var e$ = Polymer.dom(this).queryDistributedElements(slctr); -return e$ && e$[0]; -}, -queryAllEffectiveChildren: function (slctr) { -return Polymer.dom(this).queryDistributedElements(slctr); -}, -getContentChildNodes: function (slctr) { -var content = Polymer.dom(this.root).querySelector(slctr || 'content'); -return content ? Polymer.dom(content).getDistributedNodes() : []; -}, -getContentChildren: function (slctr) { -return this.getContentChildNodes(slctr).filter(function (n) { -return n.nodeType === Node.ELEMENT_NODE; -}); -}, -fire: function (type, detail, options) { -options = options || Polymer.nob; -var node = options.node || this; -var detail = detail === null || detail === undefined ? {} : detail; -var bubbles = options.bubbles === undefined ? true : options.bubbles; -var cancelable = Boolean(options.cancelable); -var useCache = options._useCache; -var event = this._getEvent(type, bubbles, cancelable, useCache); -event.detail = detail; -if (useCache) { -this.__eventCache[type] = null; -} -node.dispatchEvent(event); -if (useCache) { -this.__eventCache[type] = event; -} -return event; -}, -__eventCache: {}, -_getEvent: function (type, bubbles, cancelable, useCache) { -var event = useCache && this.__eventCache[type]; -if (!event || (event.bubbles != bubbles || event.cancelable != cancelable)) { -event = new Event(type, { -bubbles: Boolean(bubbles), -cancelable: cancelable -}); -} -return event; -}, -async: function (callback, waitTime) { -var self = this; -return Polymer.Async.run(function () { -callback.call(self); -}, waitTime); -}, -cancelAsync: function (handle) { -Polymer.Async.cancel(handle); -}, -arrayDelete: function (path, item) { -var index; -if (Array.isArray(path)) { -index = path.indexOf(item); -if (index >= 0) { -return path.splice(index, 1); -} -} else { -var arr = this._get(path); -index = arr.indexOf(item); -if (index >= 0) { -return this.splice(path, index, 1); -} -} -}, -transform: function (transform, node) { -node = node || this; -node.style.webkitTransform = transform; -node.style.transform = transform; -}, -translate3d: function (x, y, z, node) { -node = node || this; -this.transform('translate3d(' + x + ',' + y + ',' + z + ')', node); -}, -importHref: function (href, onload, onerror) { -var l = document.createElement('link'); -l.rel = 'import'; -l.href = href; -var self = this; -if (onload) { -l.onload = function (e) { -return onload.call(self, e); -}; -} -if (onerror) { -l.onerror = function (e) { -return onerror.call(self, e); -}; -} -document.head.appendChild(l); -return l; -}, -create: function (tag, props) { -var elt = document.createElement(tag); -if (props) { -for (var n in props) { -elt[n] = props[n]; -} -} -return elt; -}, -isLightDescendant: function (node) { -return this !== node && this.contains(node) && Polymer.dom(this).getOwnerRoot() === Polymer.dom(node).getOwnerRoot(); -}, -isLocalDescendant: function (node) { -return this.root === Polymer.dom(node).getOwnerRoot(); -} -}); -Polymer.Bind = { -_dataEventCache: {}, -prepareModel: function (model) { -Polymer.Base.mixin(model, this._modelApi); -}, -_modelApi: { -_notifyChange: function (source, event, value) { -value = value === undefined ? this[source] : value; -event = event || Polymer.CaseMap.camelToDashCase(source) + '-changed'; -this.fire(event, { value: value }, { -bubbles: false, -cancelable: false, -_useCache: true -}); -}, -_propertySetter: function (property, value, effects, fromAbove) { -var old = this.__data__[property]; -if (old !== value && (old === old || value === value)) { -this.__data__[property] = value; -if (typeof value == 'object') { -this._clearPath(property); -} -if (this._propertyChanged) { -this._propertyChanged(property, value, old); -} -if (effects) { -this._effectEffects(property, value, effects, old, fromAbove); -} -} -return old; -}, -__setProperty: function (property, value, quiet, node) { -node = node || this; -var effects = node._propertyEffects && node._propertyEffects[property]; -if (effects) { -node._propertySetter(property, value, effects, quiet); -} else { -node[property] = value; -} -}, -_effectEffects: function (property, value, effects, old, fromAbove) { -for (var i = 0, l = effects.length, fx; i < l && (fx = effects[i]); i++) { -fx.fn.call(this, property, value, fx.effect, old, fromAbove); -} -}, -_clearPath: function (path) { -for (var prop in this.__data__) { -if (prop.indexOf(path + '.') === 0) { -this.__data__[prop] = undefined; -} -} -} -}, -ensurePropertyEffects: function (model, property) { -if (!model._propertyEffects) { -model._propertyEffects = {}; -} -var fx = model._propertyEffects[property]; -if (!fx) { -fx = model._propertyEffects[property] = []; -} -return fx; -}, -addPropertyEffect: function (model, property, kind, effect) { -var fx = this.ensurePropertyEffects(model, property); -var propEffect = { -kind: kind, -effect: effect, -fn: Polymer.Bind['_' + kind + 'Effect'] -}; -fx.push(propEffect); -return propEffect; -}, -createBindings: function (model) { -var fx$ = model._propertyEffects; -if (fx$) { -for (var n in fx$) { -var fx = fx$[n]; -fx.sort(this._sortPropertyEffects); -this._createAccessors(model, n, fx); -} -} -}, -_sortPropertyEffects: function () { -var EFFECT_ORDER = { -'compute': 0, -'annotation': 1, -'computedAnnotation': 2, -'reflect': 3, -'notify': 4, -'observer': 5, -'complexObserver': 6, -'function': 7 -}; -return function (a, b) { -return EFFECT_ORDER[a.kind] - EFFECT_ORDER[b.kind]; -}; -}(), -_createAccessors: function (model, property, effects) { -var defun = { -get: function () { -return this.__data__[property]; -} -}; -var setter = function (value) { -this._propertySetter(property, value, effects); -}; -var info = model.getPropertyInfo && model.getPropertyInfo(property); -if (info && info.readOnly) { -if (!info.computed) { -model['_set' + this.upper(property)] = setter; -} -} else { -defun.set = setter; -} -Object.defineProperty(model, property, defun); -}, -upper: function (name) { -return name[0].toUpperCase() + name.substring(1); -}, -_addAnnotatedListener: function (model, index, property, path, event) { -if (!model._bindListeners) { -model._bindListeners = []; -} -var fn = this._notedListenerFactory(property, path, this._isStructured(path)); -var eventName = event || Polymer.CaseMap.camelToDashCase(property) + '-changed'; -model._bindListeners.push({ -index: index, -property: property, -path: path, -changedFn: fn, -event: eventName -}); -}, -_isStructured: function (path) { -return path.indexOf('.') > 0; -}, -_isEventBogus: function (e, target) { -return e.path && e.path[0] !== target; -}, -_notedListenerFactory: function (property, path, isStructured) { -return function (target, value, targetPath) { -if (targetPath) { -this._notifyPath(this._fixPath(path, property, targetPath), value); -} else { -value = target[property]; -if (!isStructured) { -this[path] = value; -} else { -if (this.__data__[path] != value) { -this.set(path, value); -} -} -} -}; -}, -prepareInstance: function (inst) { -inst.__data__ = Object.create(null); -}, -setupBindListeners: function (inst) { -var b$ = inst._bindListeners; -for (var i = 0, l = b$.length, info; i < l && (info = b$[i]); i++) { -var node = inst._nodes[info.index]; -this._addNotifyListener(node, inst, info.event, info.changedFn); -} -; -}, -_addNotifyListener: function (element, context, event, changedFn) { -element.addEventListener(event, function (e) { -return context._notifyListener(changedFn, e); -}); -} -}; -Polymer.Base.extend(Polymer.Bind, { -_shouldAddListener: function (effect) { -return effect.name && effect.kind != 'attribute' && effect.kind != 'text' && !effect.isCompound && effect.parts[0].mode === '{' && !effect.parts[0].negate; -}, -_annotationEffect: function (source, value, effect) { -if (source != effect.value) { -value = this._get(effect.value); -this.__data__[effect.value] = value; -} -var calc = effect.negate ? !value : value; -if (!effect.customEvent || this._nodes[effect.index][effect.name] !== calc) { -return this._applyEffectValue(effect, calc); -} -}, -_reflectEffect: function (source, value, effect) { -this.reflectPropertyToAttribute(source, effect.attribute, value); -}, -_notifyEffect: function (source, value, effect, old, fromAbove) { -if (!fromAbove) { -this._notifyChange(source, effect.event, value); -} -}, -_functionEffect: function (source, value, fn, old, fromAbove) { -fn.call(this, source, value, old, fromAbove); -}, -_observerEffect: function (source, value, effect, old) { -var fn = this[effect.method]; -if (fn) { -fn.call(this, value, old); -} else { -this._warn(this._logf('_observerEffect', 'observer method `' + effect.method + '` not defined')); -} -}, -_complexObserverEffect: function (source, value, effect) { -var fn = this[effect.method]; -if (fn) { -var args = Polymer.Bind._marshalArgs(this.__data__, effect, source, value); -if (args) { -fn.apply(this, args); -} -} else { -this._warn(this._logf('_complexObserverEffect', 'observer method `' + effect.method + '` not defined')); -} -}, -_computeEffect: function (source, value, effect) { -var args = Polymer.Bind._marshalArgs(this.__data__, effect, source, value); -if (args) { -var fn = this[effect.method]; -if (fn) { -this.__setProperty(effect.name, fn.apply(this, args)); -} else { -this._warn(this._logf('_computeEffect', 'compute method `' + effect.method + '` not defined')); -} -} -}, -_annotatedComputationEffect: function (source, value, effect) { -var computedHost = this._rootDataHost || this; -var fn = computedHost[effect.method]; -if (fn) { -var args = Polymer.Bind._marshalArgs(this.__data__, effect, source, value); -if (args) { -var computedvalue = fn.apply(computedHost, args); -if (effect.negate) { -computedvalue = !computedvalue; -} -this._applyEffectValue(effect, computedvalue); -} -} else { -computedHost._warn(computedHost._logf('_annotatedComputationEffect', 'compute method `' + effect.method + '` not defined')); -} -}, -_marshalArgs: function (model, effect, path, value) { -var values = []; -var args = effect.args; -for (var i = 0, l = args.length; i < l; i++) { -var arg = args[i]; -var name = arg.name; -var v; -if (arg.literal) { -v = arg.value; -} else if (arg.structured) { -v = Polymer.Base._get(name, model); -} else { -v = model[name]; -} -if (args.length > 1 && v === undefined) { -return; -} -if (arg.wildcard) { -var baseChanged = name.indexOf(path + '.') === 0; -var matches = effect.trigger.name.indexOf(name) === 0 && !baseChanged; -values[i] = { -path: matches ? path : name, -value: matches ? value : v, -base: v -}; -} else { -values[i] = v; -} -} -return values; -} -}); -Polymer.Base._addFeature({ -_addPropertyEffect: function (property, kind, effect) { -var prop = Polymer.Bind.addPropertyEffect(this, property, kind, effect); -prop.pathFn = this['_' + prop.kind + 'PathEffect']; -}, -_prepEffects: function () { -Polymer.Bind.prepareModel(this); -this._addAnnotationEffects(this._notes); -}, -_prepBindings: function () { -Polymer.Bind.createBindings(this); -}, -_addPropertyEffects: function (properties) { -if (properties) { -for (var p in properties) { -var prop = properties[p]; -if (prop.observer) { -this._addObserverEffect(p, prop.observer); -} -if (prop.computed) { -prop.readOnly = true; -this._addComputedEffect(p, prop.computed); -} -if (prop.notify) { -this._addPropertyEffect(p, 'notify', { event: Polymer.CaseMap.camelToDashCase(p) + '-changed' }); -} -if (prop.reflectToAttribute) { -this._addPropertyEffect(p, 'reflect', { attribute: Polymer.CaseMap.camelToDashCase(p) }); -} -if (prop.readOnly) { -Polymer.Bind.ensurePropertyEffects(this, p); -} -} -} -}, -_addComputedEffect: function (name, expression) { -var sig = this._parseMethod(expression); -for (var i = 0, arg; i < sig.args.length && (arg = sig.args[i]); i++) { -this._addPropertyEffect(arg.model, 'compute', { -method: sig.method, -args: sig.args, -trigger: arg, -name: name -}); -} -}, -_addObserverEffect: function (property, observer) { -this._addPropertyEffect(property, 'observer', { -method: observer, -property: property -}); -}, -_addComplexObserverEffects: function (observers) { -if (observers) { -for (var i = 0, o; i < observers.length && (o = observers[i]); i++) { -this._addComplexObserverEffect(o); -} -} -}, -_addComplexObserverEffect: function (observer) { -var sig = this._parseMethod(observer); -for (var i = 0, arg; i < sig.args.length && (arg = sig.args[i]); i++) { -this._addPropertyEffect(arg.model, 'complexObserver', { -method: sig.method, -args: sig.args, -trigger: arg -}); -} -}, -_addAnnotationEffects: function (notes) { -for (var i = 0, note; i < notes.length && (note = notes[i]); i++) { -var b$ = note.bindings; -for (var j = 0, binding; j < b$.length && (binding = b$[j]); j++) { -this._addAnnotationEffect(binding, i); -} -} -}, -_addAnnotationEffect: function (note, index) { -if (Polymer.Bind._shouldAddListener(note)) { -Polymer.Bind._addAnnotatedListener(this, index, note.name, note.parts[0].value, note.parts[0].event); -} -for (var i = 0; i < note.parts.length; i++) { -var part = note.parts[i]; -if (part.signature) { -this._addAnnotatedComputationEffect(note, part, index); -} else if (!part.literal) { -this._addPropertyEffect(part.model, 'annotation', { -kind: note.kind, -index: index, -name: note.name, -value: part.value, -isCompound: note.isCompound, -compoundIndex: part.compoundIndex, -event: part.event, -customEvent: part.customEvent, -negate: part.negate -}); -} -} -}, -_addAnnotatedComputationEffect: function (note, part, index) { -var sig = part.signature; -if (sig.static) { -this.__addAnnotatedComputationEffect('__static__', index, note, part, null); -} else { -for (var i = 0, arg; i < sig.args.length && (arg = sig.args[i]); i++) { -if (!arg.literal) { -this.__addAnnotatedComputationEffect(arg.model, index, note, part, arg); -} -} -} -}, -__addAnnotatedComputationEffect: function (property, index, note, part, trigger) { -this._addPropertyEffect(property, 'annotatedComputation', { -index: index, -isCompound: note.isCompound, -compoundIndex: part.compoundIndex, -kind: note.kind, -name: note.name, -negate: part.negate, -method: part.signature.method, -args: part.signature.args, -trigger: trigger -}); -}, -_parseMethod: function (expression) { -var m = expression.match(/([^\s]+)\((.*)\)/); -if (m) { -var sig = { -method: m[1], -static: true -}; -if (m[2].trim()) { -var args = m[2].replace(/\\,/g, '&comma;').split(','); -return this._parseArgs(args, sig); -} else { -sig.args = Polymer.nar; -return sig; -} -} -}, -_parseArgs: function (argList, sig) { -sig.args = argList.map(function (rawArg) { -var arg = this._parseArg(rawArg); -if (!arg.literal) { -sig.static = false; -} -return arg; -}, this); -return sig; -}, -_parseArg: function (rawArg) { -var arg = rawArg.trim().replace(/&comma;/g, ',').replace(/\\(.)/g, '$1'); -var a = { -name: arg, -model: this._modelForPath(arg) -}; -var fc = arg[0]; -if (fc === '-') { -fc = arg[1]; -} -if (fc >= '0' && fc <= '9') { -fc = '#'; -} -switch (fc) { -case '\'': -case '"': -a.value = arg.slice(1, -1); -a.literal = true; -break; -case '#': -a.value = Number(arg); -a.literal = true; -break; -} -if (!a.literal) { -a.structured = arg.indexOf('.') > 0; -if (a.structured) { -a.wildcard = arg.slice(-2) == '.*'; -if (a.wildcard) { -a.name = arg.slice(0, -2); -} -} -} -return a; -}, -_marshalInstanceEffects: function () { -Polymer.Bind.prepareInstance(this); -if (this._bindListeners) { -Polymer.Bind.setupBindListeners(this); -} -}, -_applyEffectValue: function (info, value) { -var node = this._nodes[info.index]; -var property = info.name; -if (info.isCompound) { -var storage = node.__compoundStorage__[property]; -storage[info.compoundIndex] = value; -value = storage.join(''); -} -if (info.kind == 'attribute') { -this.serializeValueToAttribute(value, property, node); -} else { -if (property === 'className') { -value = this._scopeElementClass(node, value); -} -if (property === 'textContent' || node.localName == 'input' && property == 'value') { -value = value == undefined ? '' : value; -} -var pinfo; -if (!node._propertyInfo || !(pinfo = node._propertyInfo[property]) || !pinfo.readOnly) { -this.__setProperty(property, value, true, node); -} -} -}, -_executeStaticEffects: function () { -if (this._propertyEffects && this._propertyEffects.__static__) { -this._effectEffects('__static__', null, this._propertyEffects.__static__); -} -} -}); -Polymer.Base._addFeature({ -_setupConfigure: function (initialConfig) { -this._config = {}; -this._handlers = []; -if (initialConfig) { -for (var i in initialConfig) { -if (initialConfig[i] !== undefined) { -this._config[i] = initialConfig[i]; -} -} -} -}, -_marshalAttributes: function () { -this._takeAttributesToModel(this._config); -}, -_attributeChangedImpl: function (name) { -var model = this._clientsReadied ? this : this._config; -this._setAttributeToProperty(model, name); -}, -_configValue: function (name, value) { -var info = this._propertyInfo[name]; -if (!info || !info.readOnly) { -this._config[name] = value; -} -}, -_beforeClientsReady: function () { -this._configure(); -}, -_configure: function () { -this._configureAnnotationReferences(); -this._aboveConfig = this.mixin({}, this._config); -var config = {}; -for (var i = 0; i < this.behaviors.length; i++) { -this._configureProperties(this.behaviors[i].properties, config); -} -this._configureProperties(this.properties, config); -this.mixin(config, this._aboveConfig); -this._config = config; -if (this._clients && this._clients.length) { -this._distributeConfig(this._config); -} -}, -_configureProperties: function (properties, config) { -for (var i in properties) { -var c = properties[i]; -if (c.value !== undefined) { -var value = c.value; -if (typeof value == 'function') { -value = value.call(this, this._config); -} -config[i] = value; -} -} -}, -_distributeConfig: function (config) { -var fx$ = this._propertyEffects; -if (fx$) { -for (var p in config) { -var fx = fx$[p]; -if (fx) { -for (var i = 0, l = fx.length, x; i < l && (x = fx[i]); i++) { -if (x.kind === 'annotation' && !x.isCompound) { -var node = this._nodes[x.effect.index]; -if (node._configValue) { -var value = p === x.effect.value ? config[p] : this._get(x.effect.value, config); -node._configValue(x.effect.name, value); -} -} -} -} -} -} -}, -_afterClientsReady: function () { -this._executeStaticEffects(); -this._applyConfig(this._config, this._aboveConfig); -this._flushHandlers(); -}, -_applyConfig: function (config, aboveConfig) { -for (var n in config) { -if (this[n] === undefined) { -this.__setProperty(n, config[n], n in aboveConfig); -} -} -}, -_notifyListener: function (fn, e) { -if (!Polymer.Bind._isEventBogus(e, e.target)) { -var value, path; -if (e.detail) { -value = e.detail.value; -path = e.detail.path; -} -if (!this._clientsReadied) { -this._queueHandler([ -fn, -e.target, -value, -path -]); -} else { -return fn.call(this, e.target, value, path); -} -} -}, -_queueHandler: function (args) { -this._handlers.push(args); -}, -_flushHandlers: function () { -var h$ = this._handlers; -for (var i = 0, l = h$.length, h; i < l && (h = h$[i]); i++) { -h[0].call(this, h[1], h[2], h[3]); -} -this._handlers = []; -} -}); -(function () { -'use strict'; -Polymer.Base._addFeature({ -notifyPath: function (path, value, fromAbove) { -var info = {}; -this._get(path, this, info); -this._notifyPath(info.path, value, fromAbove); -}, -_notifyPath: function (path, value, fromAbove) { -var old = this._propertySetter(path, value); -if (old !== value && (old === old || value === value)) { -this._pathEffector(path, value); -if (!fromAbove) { -this._notifyPathUp(path, value); -} -return true; -} -}, -_getPathParts: function (path) { -if (Array.isArray(path)) { -var parts = []; -for (var i = 0; i < path.length; i++) { -var args = path[i].toString().split('.'); -for (var j = 0; j < args.length; j++) { -parts.push(args[j]); -} -} -return parts; -} else { -return path.toString().split('.'); -} -}, -set: function (path, value, root) { -var prop = root || this; -var parts = this._getPathParts(path); -var array; -var last = parts[parts.length - 1]; -if (parts.length > 1) { -for (var i = 0; i < parts.length - 1; i++) { -var part = parts[i]; -if (array && part[0] == '#') { -prop = Polymer.Collection.get(array).getItem(part); -} else { -prop = prop[part]; -if (array && parseInt(part, 10) == part) { -parts[i] = Polymer.Collection.get(array).getKey(prop); -} -} -if (!prop) { -return; -} -array = Array.isArray(prop) ? prop : null; -} -if (array) { -var coll = Polymer.Collection.get(array); -if (last[0] == '#') { -var key = last; -var old = coll.getItem(key); -last = array.indexOf(old); -coll.setItem(key, value); -} else if (parseInt(last, 10) == last) { -var old = prop[last]; -var key = coll.getKey(old); -parts[i] = key; -coll.setItem(key, value); -} -} -prop[last] = value; -if (!root) { -this._notifyPath(parts.join('.'), value); -} -} else { -prop[path] = value; -} -}, -get: function (path, root) { -return this._get(path, root); -}, -_get: function (path, root, info) { -var prop = root || this; -var parts = this._getPathParts(path); -var array; -for (var i = 0; i < parts.length; i++) { -if (!prop) { -return; -} -var part = parts[i]; -if (array && part[0] == '#') { -prop = Polymer.Collection.get(array).getItem(part); -} else { -prop = prop[part]; -if (info && array && parseInt(part, 10) == part) { -parts[i] = Polymer.Collection.get(array).getKey(prop); -} -} -array = Array.isArray(prop) ? prop : null; -} -if (info) { -info.path = parts.join('.'); -} -return prop; -}, -_pathEffector: function (path, value) { -var model = this._modelForPath(path); -var fx$ = this._propertyEffects && this._propertyEffects[model]; -if (fx$) { -for (var i = 0, fx; i < fx$.length && (fx = fx$[i]); i++) { -var fxFn = fx.pathFn; -if (fxFn) { -fxFn.call(this, path, value, fx.effect); -} -} -} -if (this._boundPaths) { -this._notifyBoundPaths(path, value); -} -}, -_annotationPathEffect: function (path, value, effect) { -if (effect.value === path || effect.value.indexOf(path + '.') === 0) { -Polymer.Bind._annotationEffect.call(this, path, value, effect); -} else if (path.indexOf(effect.value + '.') === 0 && !effect.negate) { -var node = this._nodes[effect.index]; -if (node && node._notifyPath) { -var p = this._fixPath(effect.name, effect.value, path); -node._notifyPath(p, value, true); -} -} -}, -_complexObserverPathEffect: function (path, value, effect) { -if (this._pathMatchesEffect(path, effect)) { -Polymer.Bind._complexObserverEffect.call(this, path, value, effect); -} -}, -_computePathEffect: function (path, value, effect) { -if (this._pathMatchesEffect(path, effect)) { -Polymer.Bind._computeEffect.call(this, path, value, effect); -} -}, -_annotatedComputationPathEffect: function (path, value, effect) { -if (this._pathMatchesEffect(path, effect)) { -Polymer.Bind._annotatedComputationEffect.call(this, path, value, effect); -} -}, -_pathMatchesEffect: function (path, effect) { -var effectArg = effect.trigger.name; -return effectArg == path || effectArg.indexOf(path + '.') === 0 || effect.trigger.wildcard && path.indexOf(effectArg) === 0; -}, -linkPaths: function (to, from) { -this._boundPaths = this._boundPaths || {}; -if (from) { -this._boundPaths[to] = from; -} else { -this.unlinkPaths(to); -} -}, -unlinkPaths: function (path) { -if (this._boundPaths) { -delete this._boundPaths[path]; -} -}, -_notifyBoundPaths: function (path, value) { -for (var a in this._boundPaths) { -var b = this._boundPaths[a]; -if (path.indexOf(a + '.') == 0) { -this._notifyPath(this._fixPath(b, a, path), value); -} else if (path.indexOf(b + '.') == 0) { -this._notifyPath(this._fixPath(a, b, path), value); -} -} -}, -_fixPath: function (property, root, path) { -return property + path.slice(root.length); -}, -_notifyPathUp: function (path, value) { -var rootName = this._modelForPath(path); -var dashCaseName = Polymer.CaseMap.camelToDashCase(rootName); -var eventName = dashCaseName + this._EVENT_CHANGED; -this.fire(eventName, { -path: path, -value: value -}, { -bubbles: false, -_useCache: true -}); -}, -_modelForPath: function (path) { -var dot = path.indexOf('.'); -return dot < 0 ? path : path.slice(0, dot); -}, -_EVENT_CHANGED: '-changed', -notifySplices: function (path, splices) { -var info = {}; -var array = this._get(path, this, info); -this._notifySplices(array, info.path, splices); -}, -_notifySplices: function (array, path, splices) { -var change = { -keySplices: Polymer.Collection.applySplices(array, splices), -indexSplices: splices -}; -if (!array.hasOwnProperty('splices')) { -Object.defineProperty(array, 'splices', { -configurable: true, -writable: true -}); -} -array.splices = change; -this._notifyPath(path + '.splices', change); -this._notifyPath(path + '.length', array.length); -change.keySplices = null; -change.indexSplices = null; -}, -_notifySplice: function (array, path, index, added, removed) { -this._notifySplices(array, path, [{ -index: index, -addedCount: added, -removed: removed, -object: array, -type: 'splice' -}]); -}, -push: function (path) { -var info = {}; -var array = this._get(path, this, info); -var args = Array.prototype.slice.call(arguments, 1); -var len = array.length; -var ret = array.push.apply(array, args); -if (args.length) { -this._notifySplice(array, info.path, len, args.length, []); -} -return ret; -}, -pop: function (path) { -var info = {}; -var array = this._get(path, this, info); -var hadLength = Boolean(array.length); -var args = Array.prototype.slice.call(arguments, 1); -var ret = array.pop.apply(array, args); -if (hadLength) { -this._notifySplice(array, info.path, array.length, 0, [ret]); -} -return ret; -}, -splice: function (path, start, deleteCount) { -var info = {}; -var array = this._get(path, this, info); -if (start < 0) { -start = array.length - Math.floor(-start); -} else { -start = Math.floor(start); -} -if (!start) { -start = 0; -} -var args = Array.prototype.slice.call(arguments, 1); -var ret = array.splice.apply(array, args); -var addedCount = Math.max(args.length - 2, 0); -if (addedCount || ret.length) { -this._notifySplice(array, info.path, start, addedCount, ret); -} -return ret; -}, -shift: function (path) { -var info = {}; -var array = this._get(path, this, info); -var hadLength = Boolean(array.length); -var args = Array.prototype.slice.call(arguments, 1); -var ret = array.shift.apply(array, args); -if (hadLength) { -this._notifySplice(array, info.path, 0, 0, [ret]); -} -return ret; -}, -unshift: function (path) { -var info = {}; -var array = this._get(path, this, info); -var args = Array.prototype.slice.call(arguments, 1); -var ret = array.unshift.apply(array, args); -if (args.length) { -this._notifySplice(array, info.path, 0, args.length, []); -} -return ret; -}, -prepareModelNotifyPath: function (model) { -this.mixin(model, { -fire: Polymer.Base.fire, -_getEvent: Polymer.Base._getEvent, -__eventCache: Polymer.Base.__eventCache, -notifyPath: Polymer.Base.notifyPath, -_get: Polymer.Base._get, -_EVENT_CHANGED: Polymer.Base._EVENT_CHANGED, -_notifyPath: Polymer.Base._notifyPath, -_notifyPathUp: Polymer.Base._notifyPathUp, -_pathEffector: Polymer.Base._pathEffector, -_annotationPathEffect: Polymer.Base._annotationPathEffect, -_complexObserverPathEffect: Polymer.Base._complexObserverPathEffect, -_annotatedComputationPathEffect: Polymer.Base._annotatedComputationPathEffect, -_computePathEffect: Polymer.Base._computePathEffect, -_modelForPath: Polymer.Base._modelForPath, -_pathMatchesEffect: Polymer.Base._pathMatchesEffect, -_notifyBoundPaths: Polymer.Base._notifyBoundPaths, -_getPathParts: Polymer.Base._getPathParts -}); -} -}); -}()); -Polymer.Base._addFeature({ -resolveUrl: function (url) { -var module = Polymer.DomModule.import(this.is); -var root = ''; -if (module) { -var assetPath = module.getAttribute('assetpath') || ''; -root = Polymer.ResolveUrl.resolveUrl(assetPath, module.ownerDocument.baseURI); -} -return Polymer.ResolveUrl.resolveUrl(url, root); -} -}); -Polymer.CssParse = function () { -var api = { -parse: function (text) { -text = this._clean(text); -return this._parseCss(this._lex(text), text); -}, -_clean: function (cssText) { -return cssText.replace(this._rx.comments, '').replace(this._rx.port, ''); -}, -_lex: function (text) { -var root = { -start: 0, -end: text.length -}; -var n = root; -for (var i = 0, s = 0, l = text.length; i < l; i++) { -switch (text[i]) { -case this.OPEN_BRACE: -if (!n.rules) { -n.rules = []; -} -var p = n; -var previous = p.rules[p.rules.length - 1]; -n = { -start: i + 1, -parent: p, -previous: previous -}; -p.rules.push(n); -break; -case this.CLOSE_BRACE: -n.end = i + 1; -n = n.parent || root; -break; -} -} -return root; -}, -_parseCss: function (node, text) { -var t = text.substring(node.start, node.end - 1); -node.parsedCssText = node.cssText = t.trim(); -if (node.parent) { -var ss = node.previous ? node.previous.end : node.parent.start; -t = text.substring(ss, node.start - 1); -t = this._expandUnicodeEscapes(t); -t = t.replace(this._rx.multipleSpaces, ' '); -t = t.substring(t.lastIndexOf(';') + 1); -var s = node.parsedSelector = node.selector = t.trim(); -node.atRule = s.indexOf(this.AT_START) === 0; -if (node.atRule) { -if (s.indexOf(this.MEDIA_START) === 0) { -node.type = this.types.MEDIA_RULE; -} else if (s.match(this._rx.keyframesRule)) { -node.type = this.types.KEYFRAMES_RULE; -} -} else { -if (s.indexOf(this.VAR_START) === 0) { -node.type = this.types.MIXIN_RULE; -} else { -node.type = this.types.STYLE_RULE; -} -} -} -var r$ = node.rules; -if (r$) { -for (var i = 0, l = r$.length, r; i < l && (r = r$[i]); i++) { -this._parseCss(r, text); -} -} -return node; -}, -_expandUnicodeEscapes: function (s) { -return s.replace(/\\([0-9a-f]{1,6})\s/gi, function () { -var code = arguments[1], repeat = 6 - code.length; -while (repeat--) { -code = '0' + code; -} -return '\\' + code; -}); -}, -stringify: function (node, preserveProperties, text) { -text = text || ''; -var cssText = ''; -if (node.cssText || node.rules) { -var r$ = node.rules; -if (r$ && (preserveProperties || !this._hasMixinRules(r$))) { -for (var i = 0, l = r$.length, r; i < l && (r = r$[i]); i++) { -cssText = this.stringify(r, preserveProperties, cssText); -} -} else { -cssText = preserveProperties ? node.cssText : this.removeCustomProps(node.cssText); -cssText = cssText.trim(); -if (cssText) { -cssText = ' ' + cssText + '\n'; -} -} -} -if (cssText) { -if (node.selector) { -text += node.selector + ' ' + this.OPEN_BRACE + '\n'; -} -text += cssText; -if (node.selector) { -text += this.CLOSE_BRACE + '\n\n'; -} -} -return text; -}, -_hasMixinRules: function (rules) { -return rules[0].selector.indexOf(this.VAR_START) === 0; -}, -removeCustomProps: function (cssText) { -cssText = this.removeCustomPropAssignment(cssText); -return this.removeCustomPropApply(cssText); -}, -removeCustomPropAssignment: function (cssText) { -return cssText.replace(this._rx.customProp, '').replace(this._rx.mixinProp, ''); -}, -removeCustomPropApply: function (cssText) { -return cssText.replace(this._rx.mixinApply, '').replace(this._rx.varApply, ''); -}, -types: { -STYLE_RULE: 1, -KEYFRAMES_RULE: 7, -MEDIA_RULE: 4, -MIXIN_RULE: 1000 -}, -OPEN_BRACE: '{', -CLOSE_BRACE: '}', -_rx: { -comments: /\/\*[^*]*\*+([^\/*][^*]*\*+)*\//gim, -port: /@import[^;]*;/gim, -customProp: /(?:^|[\s;])--[^;{]*?:[^{};]*?(?:[;\n]|$)/gim, -mixinProp: /(?:^|[\s;])?--[^;{]*?:[^{;]*?{[^}]*?}(?:[;\n]|$)?/gim, -mixinApply: /@apply[\s]*\([^)]*?\)[\s]*(?:[;\n]|$)?/gim, -varApply: /[^;:]*?:[^;]*?var\([^;]*\)(?:[;\n]|$)?/gim, -keyframesRule: /^@[^\s]*keyframes/, -multipleSpaces: /\s+/g -}, -VAR_START: '--', -MEDIA_START: '@media', -AT_START: '@' -}; -return api; -}(); -Polymer.StyleUtil = function () { -return { -MODULE_STYLES_SELECTOR: 'style, link[rel=import][type~=css], template', -INCLUDE_ATTR: 'include', -toCssText: function (rules, callback, preserveProperties) { -if (typeof rules === 'string') { -rules = this.parser.parse(rules); -} -if (callback) { -this.forEachStyleRule(rules, callback); -} -return this.parser.stringify(rules, preserveProperties); -}, -forRulesInStyles: function (styles, callback) { -if (styles) { -for (var i = 0, l = styles.length, s; i < l && (s = styles[i]); i++) { -this.forEachStyleRule(this.rulesForStyle(s), callback); -} -} -}, -rulesForStyle: function (style) { -if (!style.__cssRules && style.textContent) { -style.__cssRules = this.parser.parse(style.textContent); -} -return style.__cssRules; -}, -clearStyleRules: function (style) { -style.__cssRules = null; -}, -forEachStyleRule: function (node, callback) { -if (!node) { -return; -} -var s = node.parsedSelector; -var skipRules = false; -if (node.type === this.ruleTypes.STYLE_RULE) { -callback(node); -} else if (node.type === this.ruleTypes.KEYFRAMES_RULE || node.type === this.ruleTypes.MIXIN_RULE) { -skipRules = true; -} -var r$ = node.rules; -if (r$ && !skipRules) { -for (var i = 0, l = r$.length, r; i < l && (r = r$[i]); i++) { -this.forEachStyleRule(r, callback); -} -} -}, -applyCss: function (cssText, moniker, target, afterNode) { -var style = document.createElement('style'); -if (moniker) { -style.setAttribute('scope', moniker); -} -style.textContent = cssText; -target = target || document.head; -if (!afterNode) { -var n$ = target.querySelectorAll('style[scope]'); -afterNode = n$[n$.length - 1]; -} -target.insertBefore(style, afterNode && afterNode.nextSibling || target.firstChild); -return style; -}, -cssFromModules: function (moduleIds, warnIfNotFound) { -var modules = moduleIds.trim().split(' '); -var cssText = ''; -for (var i = 0; i < modules.length; i++) { -cssText += this.cssFromModule(modules[i], warnIfNotFound); -} -return cssText; -}, -cssFromModule: function (moduleId, warnIfNotFound) { -var m = Polymer.DomModule.import(moduleId); -if (m && !m._cssText) { -m._cssText = this.cssFromElement(m); -} -if (!m && warnIfNotFound) { -console.warn('Could not find style data in module named', moduleId); -} -return m && m._cssText || ''; -}, -cssFromElement: function (element) { -var cssText = ''; -var content = element.content || element; -var e$ = Polymer.DomApi.arrayCopy(content.querySelectorAll(this.MODULE_STYLES_SELECTOR)); -for (var i = 0, e; i < e$.length; i++) { -e = e$[i]; -if (e.localName === 'template') { -cssText += this.cssFromElement(e); -} else { -if (e.localName === 'style') { -var include = e.getAttribute(this.INCLUDE_ATTR); -if (include) { -cssText += this.cssFromModules(include, true); -} -e = e.__appliedElement || e; -e.parentNode.removeChild(e); -cssText += this.resolveCss(e.textContent, element.ownerDocument); -} else if (e.import && e.import.body) { -cssText += this.resolveCss(e.import.body.textContent, e.import); -} -} -} -return cssText; -}, -resolveCss: Polymer.ResolveUrl.resolveCss, -parser: Polymer.CssParse, -ruleTypes: Polymer.CssParse.types -}; -}(); -Polymer.StyleTransformer = function () { -var nativeShadow = Polymer.Settings.useNativeShadow; -var styleUtil = Polymer.StyleUtil; -var api = { -dom: function (node, scope, useAttr, shouldRemoveScope) { -this._transformDom(node, scope || '', useAttr, shouldRemoveScope); -}, -_transformDom: function (node, selector, useAttr, shouldRemoveScope) { -if (node.setAttribute) { -this.element(node, selector, useAttr, shouldRemoveScope); -} -var c$ = Polymer.dom(node).childNodes; -for (var i = 0; i < c$.length; i++) { -this._transformDom(c$[i], selector, useAttr, shouldRemoveScope); -} -}, -element: function (element, scope, useAttr, shouldRemoveScope) { -if (useAttr) { -if (shouldRemoveScope) { -element.removeAttribute(SCOPE_NAME); -} else { -element.setAttribute(SCOPE_NAME, scope); -} -} else { -if (scope) { -if (element.classList) { -if (shouldRemoveScope) { -element.classList.remove(SCOPE_NAME); -element.classList.remove(scope); -} else { -element.classList.add(SCOPE_NAME); -element.classList.add(scope); -} -} else if (element.getAttribute) { -var c = element.getAttribute(CLASS); -if (shouldRemoveScope) { -if (c) { -element.setAttribute(CLASS, c.replace(SCOPE_NAME, '').replace(scope, '')); -} -} else { -element.setAttribute(CLASS, c + (c ? ' ' : '') + SCOPE_NAME + ' ' + scope); -} -} -} -} -}, -elementStyles: function (element, callback) { -var styles = element._styles; -var cssText = ''; -for (var i = 0, l = styles.length, s, text; i < l && (s = styles[i]); i++) { -var rules = styleUtil.rulesForStyle(s); -cssText += nativeShadow ? styleUtil.toCssText(rules, callback) : this.css(rules, element.is, element.extends, callback, element._scopeCssViaAttr) + '\n\n'; -} -return cssText.trim(); -}, -css: function (rules, scope, ext, callback, useAttr) { -var hostScope = this._calcHostScope(scope, ext); -scope = this._calcElementScope(scope, useAttr); -var self = this; -return styleUtil.toCssText(rules, function (rule) { -if (!rule.isScoped) { -self.rule(rule, scope, hostScope); -rule.isScoped = true; -} -if (callback) { -callback(rule, scope, hostScope); -} -}); -}, -_calcElementScope: function (scope, useAttr) { -if (scope) { -return useAttr ? CSS_ATTR_PREFIX + scope + CSS_ATTR_SUFFIX : CSS_CLASS_PREFIX + scope; -} else { -return ''; -} -}, -_calcHostScope: function (scope, ext) { -return ext ? '[is=' + scope + ']' : scope; -}, -rule: function (rule, scope, hostScope) { -this._transformRule(rule, this._transformComplexSelector, scope, hostScope); -}, -_transformRule: function (rule, transformer, scope, hostScope) { -var p$ = rule.selector.split(COMPLEX_SELECTOR_SEP); -for (var i = 0, l = p$.length, p; i < l && (p = p$[i]); i++) { -p$[i] = transformer.call(this, p, scope, hostScope); -} -rule.selector = rule.transformedSelector = p$.join(COMPLEX_SELECTOR_SEP); -}, -_transformComplexSelector: function (selector, scope, hostScope) { -var stop = false; -var hostContext = false; -var self = this; -selector = selector.replace(SIMPLE_SELECTOR_SEP, function (m, c, s) { -if (!stop) { -var info = self._transformCompoundSelector(s, c, scope, hostScope); -stop = stop || info.stop; -hostContext = hostContext || info.hostContext; -c = info.combinator; -s = info.value; -} else { -s = s.replace(SCOPE_JUMP, ' '); -} -return c + s; -}); -if (hostContext) { -selector = selector.replace(HOST_CONTEXT_PAREN, function (m, pre, paren, post) { -return pre + paren + ' ' + hostScope + post + COMPLEX_SELECTOR_SEP + ' ' + pre + hostScope + paren + post; -}); -} -return selector; -}, -_transformCompoundSelector: function (selector, combinator, scope, hostScope) { -var jumpIndex = selector.search(SCOPE_JUMP); -var hostContext = false; -if (selector.indexOf(HOST_CONTEXT) >= 0) { -hostContext = true; -} else if (selector.indexOf(HOST) >= 0) { -selector = selector.replace(HOST_PAREN, function (m, host, paren) { -return hostScope + paren; -}); -selector = selector.replace(HOST, hostScope); -} else if (jumpIndex !== 0) { -selector = scope ? this._transformSimpleSelector(selector, scope) : selector; -} -if (selector.indexOf(CONTENT) >= 0) { -combinator = ''; -} -var stop; -if (jumpIndex >= 0) { -selector = selector.replace(SCOPE_JUMP, ' '); -stop = true; -} -return { -value: selector, -combinator: combinator, -stop: stop, -hostContext: hostContext -}; -}, -_transformSimpleSelector: function (selector, scope) { -var p$ = selector.split(PSEUDO_PREFIX); -p$[0] += scope; -return p$.join(PSEUDO_PREFIX); -}, -documentRule: function (rule) { -rule.selector = rule.parsedSelector; -this.normalizeRootSelector(rule); -if (!nativeShadow) { -this._transformRule(rule, this._transformDocumentSelector); -} -}, -normalizeRootSelector: function (rule) { -if (rule.selector === ROOT) { -rule.selector = 'body'; -} -}, -_transformDocumentSelector: function (selector) { -return selector.match(SCOPE_JUMP) ? this._transformComplexSelector(selector, SCOPE_DOC_SELECTOR) : this._transformSimpleSelector(selector.trim(), SCOPE_DOC_SELECTOR); -}, -SCOPE_NAME: 'style-scope' -}; -var SCOPE_NAME = api.SCOPE_NAME; -var SCOPE_DOC_SELECTOR = ':not([' + SCOPE_NAME + '])' + ':not(.' + SCOPE_NAME + ')'; -var COMPLEX_SELECTOR_SEP = ','; -var SIMPLE_SELECTOR_SEP = /(^|[\s>+~]+)([^\s>+~]+)/g; -var HOST = ':host'; -var ROOT = ':root'; -var HOST_PAREN = /(\:host)(?:\(((?:\([^)(]*\)|[^)(]*)+?)\))/g; -var HOST_CONTEXT = ':host-context'; -var HOST_CONTEXT_PAREN = /(.*)(?:\:host-context)(?:\(((?:\([^)(]*\)|[^)(]*)+?)\))(.*)/; -var CONTENT = '::content'; -var SCOPE_JUMP = /\:\:content|\:\:shadow|\/deep\//; -var CSS_CLASS_PREFIX = '.'; -var CSS_ATTR_PREFIX = '[' + SCOPE_NAME + '~='; -var CSS_ATTR_SUFFIX = ']'; -var PSEUDO_PREFIX = ':'; -var CLASS = 'class'; -return api; -}(); -Polymer.StyleExtends = function () { -var styleUtil = Polymer.StyleUtil; -return { -hasExtends: function (cssText) { -return Boolean(cssText.match(this.rx.EXTEND)); -}, -transform: function (style) { -var rules = styleUtil.rulesForStyle(style); -var self = this; -styleUtil.forEachStyleRule(rules, function (rule) { -var map = self._mapRule(rule); -if (rule.parent) { -var m; -while (m = self.rx.EXTEND.exec(rule.cssText)) { -var extend = m[1]; -var extendor = self._findExtendor(extend, rule); -if (extendor) { -self._extendRule(rule, extendor); -} -} -} -rule.cssText = rule.cssText.replace(self.rx.EXTEND, ''); -}); -return styleUtil.toCssText(rules, function (rule) { -if (rule.selector.match(self.rx.STRIP)) { -rule.cssText = ''; -} -}, true); -}, -_mapRule: function (rule) { -if (rule.parent) { -var map = rule.parent.map || (rule.parent.map = {}); -var parts = rule.selector.split(','); -for (var i = 0, p; i < parts.length; i++) { -p = parts[i]; -map[p.trim()] = rule; -} -return map; -} -}, -_findExtendor: function (extend, rule) { -return rule.parent && rule.parent.map && rule.parent.map[extend] || this._findExtendor(extend, rule.parent); -}, -_extendRule: function (target, source) { -if (target.parent !== source.parent) { -this._cloneAndAddRuleToParent(source, target.parent); -} -target.extends = target.extends || []; -target.extends.push(source); -source.selector = source.selector.replace(this.rx.STRIP, ''); -source.selector = (source.selector && source.selector + ',\n') + target.selector; -if (source.extends) { -source.extends.forEach(function (e) { -this._extendRule(target, e); -}, this); -} -}, -_cloneAndAddRuleToParent: function (rule, parent) { -rule = Object.create(rule); -rule.parent = parent; -if (rule.extends) { -rule.extends = rule.extends.slice(); -} -parent.rules.push(rule); -}, -rx: { -EXTEND: /@extends\(([^)]*)\)\s*?;/gim, -STRIP: /%[^,]*$/ -} -}; -}(); -(function () { -var prepElement = Polymer.Base._prepElement; -var nativeShadow = Polymer.Settings.useNativeShadow; -var styleUtil = Polymer.StyleUtil; -var styleTransformer = Polymer.StyleTransformer; -var styleExtends = Polymer.StyleExtends; -Polymer.Base._addFeature({ -_prepElement: function (element) { -if (this._encapsulateStyle) { -styleTransformer.element(element, this.is, this._scopeCssViaAttr); -} -prepElement.call(this, element); -}, -_prepStyles: function () { -if (this._encapsulateStyle === undefined) { -this._encapsulateStyle = !nativeShadow && Boolean(this._template); -} -if (this._template) { -this._styles = this._collectStyles(); -var cssText = styleTransformer.elementStyles(this); -if (cssText) { -var style = styleUtil.applyCss(cssText, this.is, nativeShadow ? this._template.content : null); -if (!nativeShadow) { -this._scopeStyle = style; -} -} -} else { -this._styles = []; -} -}, -_collectStyles: function () { -var styles = []; -var cssText = '', m$ = this.styleModules; -if (m$) { -for (var i = 0, l = m$.length, m; i < l && (m = m$[i]); i++) { -cssText += styleUtil.cssFromModule(m); -} -} -cssText += styleUtil.cssFromModule(this.is); -var p = this._template && this._template.parentNode; -if (this._template && (!p || p.id.toLowerCase() !== this.is)) { -cssText += styleUtil.cssFromElement(this._template); -} -if (cssText) { -var style = document.createElement('style'); -style.textContent = cssText; -if (styleExtends.hasExtends(style.textContent)) { -cssText = styleExtends.transform(style); -} -styles.push(style); -} -return styles; -}, -_elementAdd: function (node) { -if (this._encapsulateStyle) { -if (node.__styleScoped) { -node.__styleScoped = false; -} else { -styleTransformer.dom(node, this.is, this._scopeCssViaAttr); -} -} -}, -_elementRemove: function (node) { -if (this._encapsulateStyle) { -styleTransformer.dom(node, this.is, this._scopeCssViaAttr, true); -} -}, -scopeSubtree: function (container, shouldObserve) { -if (nativeShadow) { -return; -} -var self = this; -var scopify = function (node) { -if (node.nodeType === Node.ELEMENT_NODE) { -node.className = self._scopeElementClass(node, node.className); -var n$ = node.querySelectorAll('*'); -for (var i = 0, n; i < n$.length && (n = n$[i]); i++) { -n.className = self._scopeElementClass(n, n.className); -} -} -}; -scopify(container); -if (shouldObserve) { -var mo = new MutationObserver(function (mxns) { -for (var i = 0, m; i < mxns.length && (m = mxns[i]); i++) { -if (m.addedNodes) { -for (var j = 0; j < m.addedNodes.length; j++) { -scopify(m.addedNodes[j]); -} -} -} -}); -mo.observe(container, { -childList: true, -subtree: true -}); -return mo; -} -} -}); -}()); -Polymer.StyleProperties = function () { -'use strict'; -var nativeShadow = Polymer.Settings.useNativeShadow; -var matchesSelector = Polymer.DomApi.matchesSelector; -var styleUtil = Polymer.StyleUtil; -var styleTransformer = Polymer.StyleTransformer; -return { -decorateStyles: function (styles) { -var self = this, props = {}; -styleUtil.forRulesInStyles(styles, function (rule) { -self.decorateRule(rule); -self.collectPropertiesInCssText(rule.propertyInfo.cssText, props); -}); -var names = []; -for (var i in props) { -names.push(i); -} -return names; -}, -decorateRule: function (rule) { -if (rule.propertyInfo) { -return rule.propertyInfo; -} -var info = {}, properties = {}; -var hasProperties = this.collectProperties(rule, properties); -if (hasProperties) { -info.properties = properties; -rule.rules = null; -} -info.cssText = this.collectCssText(rule); -rule.propertyInfo = info; -return info; -}, -collectProperties: function (rule, properties) { -var info = rule.propertyInfo; -if (info) { -if (info.properties) { -Polymer.Base.mixin(properties, info.properties); -return true; -} -} else { -var m, rx = this.rx.VAR_ASSIGN; -var cssText = rule.parsedCssText; -var any; -while (m = rx.exec(cssText)) { -properties[m[1]] = (m[2] || m[3]).trim(); -any = true; -} -return any; -} -}, -collectCssText: function (rule) { -var customCssText = ''; -var cssText = rule.parsedCssText; -cssText = cssText.replace(this.rx.BRACKETED, '').replace(this.rx.VAR_ASSIGN, ''); -var parts = cssText.split(';'); -for (var i = 0, p; i < parts.length; i++) { -p = parts[i]; -if (p.match(this.rx.MIXIN_MATCH) || p.match(this.rx.VAR_MATCH)) { -customCssText += p + ';\n'; -} -} -return customCssText; -}, -collectPropertiesInCssText: function (cssText, props) { -var m; -while (m = this.rx.VAR_CAPTURE.exec(cssText)) { -props[m[1]] = true; -var def = m[2]; -if (def && def.match(this.rx.IS_VAR)) { -props[def] = true; -} -} -}, -reify: function (props) { -var names = Object.getOwnPropertyNames(props); -for (var i = 0, n; i < names.length; i++) { -n = names[i]; -props[n] = this.valueForProperty(props[n], props); -} -}, -valueForProperty: function (property, props) { -if (property) { -if (property.indexOf(';') >= 0) { -property = this.valueForProperties(property, props); -} else { -var self = this; -var fn = function (all, prefix, value, fallback) { -var propertyValue = self.valueForProperty(props[value], props) || (props[fallback] ? self.valueForProperty(props[fallback], props) : fallback); -return prefix + (propertyValue || ''); -}; -property = property.replace(this.rx.VAR_MATCH, fn); -} -} -return property && property.trim() || ''; -}, -valueForProperties: function (property, props) { -var parts = property.split(';'); -for (var i = 0, p, m; i < parts.length; i++) { -if (p = parts[i]) { -m = p.match(this.rx.MIXIN_MATCH); -if (m) { -p = this.valueForProperty(props[m[1]], props); -} else { -var pp = p.split(':'); -if (pp[1]) { -pp[1] = pp[1].trim(); -pp[1] = this.valueForProperty(pp[1], props) || pp[1]; -} -p = pp.join(':'); -} -parts[i] = p && p.lastIndexOf(';') === p.length - 1 ? p.slice(0, -1) : p || ''; -} -} -return parts.filter(function (v) { -return v; -}).join(';'); -}, -applyProperties: function (rule, props) { -var output = ''; -if (!rule.propertyInfo) { -this.decorateRule(rule); -} -if (rule.propertyInfo.cssText) { -output = this.valueForProperties(rule.propertyInfo.cssText, props); -} -rule.cssText = output; -}, -propertyDataFromStyles: function (styles, element) { -var props = {}, self = this; -var o = [], i = 0; -styleUtil.forRulesInStyles(styles, function (rule) { -if (!rule.propertyInfo) { -self.decorateRule(rule); -} -if (element && rule.propertyInfo.properties && matchesSelector.call(element, rule.transformedSelector || rule.parsedSelector)) { -self.collectProperties(rule, props); -addToBitMask(i, o); -} -i++; -}); -return { -properties: props, -key: o -}; -}, -scopePropertiesFromStyles: function (styles) { -if (!styles._scopeStyleProperties) { -styles._scopeStyleProperties = this.selectedPropertiesFromStyles(styles, this.SCOPE_SELECTORS); -} -return styles._scopeStyleProperties; -}, -hostPropertiesFromStyles: function (styles) { -if (!styles._hostStyleProperties) { -styles._hostStyleProperties = this.selectedPropertiesFromStyles(styles, this.HOST_SELECTORS); -} -return styles._hostStyleProperties; -}, -selectedPropertiesFromStyles: function (styles, selectors) { -var props = {}, self = this; -styleUtil.forRulesInStyles(styles, function (rule) { -if (!rule.propertyInfo) { -self.decorateRule(rule); -} -for (var i = 0; i < selectors.length; i++) { -if (rule.parsedSelector === selectors[i]) { -self.collectProperties(rule, props); -return; -} -} -}); -return props; -}, -transformStyles: function (element, properties, scopeSelector) { -var self = this; -var hostSelector = styleTransformer._calcHostScope(element.is, element.extends); -var rxHostSelector = element.extends ? '\\' + hostSelector.slice(0, -1) + '\\]' : hostSelector; -var hostRx = new RegExp(this.rx.HOST_PREFIX + rxHostSelector + this.rx.HOST_SUFFIX); -return styleTransformer.elementStyles(element, function (rule) { -self.applyProperties(rule, properties); -if (rule.cssText && !nativeShadow) { -self._scopeSelector(rule, hostRx, hostSelector, element._scopeCssViaAttr, scopeSelector); -} -}); -}, -_scopeSelector: function (rule, hostRx, hostSelector, viaAttr, scopeId) { -rule.transformedSelector = rule.transformedSelector || rule.selector; -var selector = rule.transformedSelector; -var scope = viaAttr ? '[' + styleTransformer.SCOPE_NAME + '~=' + scopeId + ']' : '.' + scopeId; -var parts = selector.split(','); -for (var i = 0, l = parts.length, p; i < l && (p = parts[i]); i++) { -parts[i] = p.match(hostRx) ? p.replace(hostSelector, hostSelector + scope) : scope + ' ' + p; -} -rule.selector = parts.join(','); -}, -applyElementScopeSelector: function (element, selector, old, viaAttr) { -var c = viaAttr ? element.getAttribute(styleTransformer.SCOPE_NAME) : element.className; -var v = old ? c.replace(old, selector) : (c ? c + ' ' : '') + this.XSCOPE_NAME + ' ' + selector; -if (c !== v) { -if (viaAttr) { -element.setAttribute(styleTransformer.SCOPE_NAME, v); -} else { -element.className = v; -} -} -}, -applyElementStyle: function (element, properties, selector, style) { -var cssText = style ? style.textContent || '' : this.transformStyles(element, properties, selector); -var s = element._customStyle; -if (s && !nativeShadow && s !== style) { -s._useCount--; -if (s._useCount <= 0 && s.parentNode) { -s.parentNode.removeChild(s); -} -} -if (nativeShadow || (!style || !style.parentNode)) { -if (nativeShadow && element._customStyle) { -element._customStyle.textContent = cssText; -style = element._customStyle; -} else if (cssText) { -style = styleUtil.applyCss(cssText, selector, nativeShadow ? element.root : null, element._scopeStyle); -} -} -if (style) { -style._useCount = style._useCount || 0; -if (element._customStyle != style) { -style._useCount++; -} -element._customStyle = style; -} -return style; -}, -mixinCustomStyle: function (props, customStyle) { -var v; -for (var i in customStyle) { -v = customStyle[i]; -if (v || v === 0) { -props[i] = v; -} -} -}, -rx: { -VAR_ASSIGN: /(?:^|[;\s{]\s*)(--[\w-]*?)\s*:\s*(?:([^;{]*)|{([^}]*)})(?:(?=[;\s}])|$)/gi, -MIXIN_MATCH: /(?:^|\W+)@apply[\s]*\(([^)]*)\)/i, -VAR_MATCH: /(^|\W+)var\([\s]*([^,)]*)[\s]*,?[\s]*((?:[^,)]*)|(?:[^;]*\([^;)]*\)))[\s]*?\)/gi, -VAR_CAPTURE: /\([\s]*(--[^,\s)]*)(?:,[\s]*(--[^,\s)]*))?(?:\)|,)/gi, -IS_VAR: /^--/, -BRACKETED: /\{[^}]*\}/g, -HOST_PREFIX: '(?:^|[^.#[:])', -HOST_SUFFIX: '($|[.:[\\s>+~])' -}, -HOST_SELECTORS: [':host'], -SCOPE_SELECTORS: [':root'], -XSCOPE_NAME: 'x-scope' -}; -function addToBitMask(n, bits) { -var o = parseInt(n / 32); -var v = 1 << n % 32; -bits[o] = (bits[o] || 0) | v; -} -}(); -(function () { -Polymer.StyleCache = function () { -this.cache = {}; -}; -Polymer.StyleCache.prototype = { -MAX: 100, -store: function (is, data, keyValues, keyStyles) { -data.keyValues = keyValues; -data.styles = keyStyles; -var s$ = this.cache[is] = this.cache[is] || []; -s$.push(data); -if (s$.length > this.MAX) { -s$.shift(); -} -}, -retrieve: function (is, keyValues, keyStyles) { -var cache = this.cache[is]; -if (cache) { -for (var i = cache.length - 1, data; i >= 0; i--) { -data = cache[i]; -if (keyStyles === data.styles && this._objectsEqual(keyValues, data.keyValues)) { -return data; -} -} -} -}, -clear: function () { -this.cache = {}; -}, -_objectsEqual: function (target, source) { -var t, s; -for (var i in target) { -t = target[i], s = source[i]; -if (!(typeof t === 'object' && t ? this._objectsStrictlyEqual(t, s) : t === s)) { -return false; -} -} -if (Array.isArray(target)) { -return target.length === source.length; -} -return true; -}, -_objectsStrictlyEqual: function (target, source) { -return this._objectsEqual(target, source) && this._objectsEqual(source, target); -} -}; -}()); -Polymer.StyleDefaults = function () { -var styleProperties = Polymer.StyleProperties; -var styleUtil = Polymer.StyleUtil; -var StyleCache = Polymer.StyleCache; -var api = { -_styles: [], -_properties: null, -customStyle: {}, -_styleCache: new StyleCache(), -addStyle: function (style) { -this._styles.push(style); -this._properties = null; -}, -get _styleProperties() { -if (!this._properties) { -styleProperties.decorateStyles(this._styles); -this._styles._scopeStyleProperties = null; -this._properties = styleProperties.scopePropertiesFromStyles(this._styles); -styleProperties.mixinCustomStyle(this._properties, this.customStyle); -styleProperties.reify(this._properties); -} -return this._properties; -}, -_needsStyleProperties: function () { -}, -_computeStyleProperties: function () { -return this._styleProperties; -}, -updateStyles: function (properties) { -this._properties = null; -if (properties) { -Polymer.Base.mixin(this.customStyle, properties); -} -this._styleCache.clear(); -for (var i = 0, s; i < this._styles.length; i++) { -s = this._styles[i]; -s = s.__importElement || s; -s._apply(); -} -} -}; -return api; -}(); -(function () { -'use strict'; -var serializeValueToAttribute = Polymer.Base.serializeValueToAttribute; -var propertyUtils = Polymer.StyleProperties; -var styleTransformer = Polymer.StyleTransformer; -var styleUtil = Polymer.StyleUtil; -var styleDefaults = Polymer.StyleDefaults; -var nativeShadow = Polymer.Settings.useNativeShadow; -Polymer.Base._addFeature({ -_prepStyleProperties: function () { -this._ownStylePropertyNames = this._styles ? propertyUtils.decorateStyles(this._styles) : null; -}, -customStyle: null, -getComputedStyleValue: function (property) { -return this._styleProperties && this._styleProperties[property] || getComputedStyle(this).getPropertyValue(property); -}, -_setupStyleProperties: function () { -this.customStyle = {}; -}, -_needsStyleProperties: function () { -return Boolean(this._ownStylePropertyNames && this._ownStylePropertyNames.length); -}, -_beforeAttached: function () { -if (!this._scopeSelector && this._needsStyleProperties()) { -this._updateStyleProperties(); -} -}, -_findStyleHost: function () { -var e = this, root; -while (root = Polymer.dom(e).getOwnerRoot()) { -if (Polymer.isInstance(root.host)) { -return root.host; -} -e = root.host; -} -return styleDefaults; -}, -_updateStyleProperties: function () { -var info, scope = this._findStyleHost(); -if (!scope._styleCache) { -scope._styleCache = new Polymer.StyleCache(); -} -var scopeData = propertyUtils.propertyDataFromStyles(scope._styles, this); -scopeData.key.customStyle = this.customStyle; -info = scope._styleCache.retrieve(this.is, scopeData.key, this._styles); -var scopeCached = Boolean(info); -if (scopeCached) { -this._styleProperties = info._styleProperties; -} else { -this._computeStyleProperties(scopeData.properties); -} -this._computeOwnStyleProperties(); -if (!scopeCached) { -info = styleCache.retrieve(this.is, this._ownStyleProperties, this._styles); -} -var globalCached = Boolean(info) && !scopeCached; -var style = this._applyStyleProperties(info); -if (!scopeCached) { -style = style && nativeShadow ? style.cloneNode(true) : style; -info = { -style: style, -_scopeSelector: this._scopeSelector, -_styleProperties: this._styleProperties -}; -scopeData.key.customStyle = {}; -this.mixin(scopeData.key.customStyle, this.customStyle); -scope._styleCache.store(this.is, info, scopeData.key, this._styles); -if (!globalCached) { -styleCache.store(this.is, Object.create(info), this._ownStyleProperties, this._styles); -} -} -}, -_computeStyleProperties: function (scopeProps) { -var scope = this._findStyleHost(); -if (!scope._styleProperties) { -scope._computeStyleProperties(); -} -var props = Object.create(scope._styleProperties); -this.mixin(props, propertyUtils.hostPropertiesFromStyles(this._styles)); -scopeProps = scopeProps || propertyUtils.propertyDataFromStyles(scope._styles, this).properties; -this.mixin(props, scopeProps); -this.mixin(props, propertyUtils.scopePropertiesFromStyles(this._styles)); -propertyUtils.mixinCustomStyle(props, this.customStyle); -propertyUtils.reify(props); -this._styleProperties = props; -}, -_computeOwnStyleProperties: function () { -var props = {}; -for (var i = 0, n; i < this._ownStylePropertyNames.length; i++) { -n = this._ownStylePropertyNames[i]; -props[n] = this._styleProperties[n]; -} -this._ownStyleProperties = props; -}, -_scopeCount: 0, -_applyStyleProperties: function (info) { -var oldScopeSelector = this._scopeSelector; -this._scopeSelector = info ? info._scopeSelector : this.is + '-' + this.__proto__._scopeCount++; -var style = propertyUtils.applyElementStyle(this, this._styleProperties, this._scopeSelector, info && info.style); -if (!nativeShadow) { -propertyUtils.applyElementScopeSelector(this, this._scopeSelector, oldScopeSelector, this._scopeCssViaAttr); -} -return style; -}, -serializeValueToAttribute: function (value, attribute, node) { -node = node || this; -if (attribute === 'class' && !nativeShadow) { -var host = node === this ? this.domHost || this.dataHost : this; -if (host) { -value = host._scopeElementClass(node, value); -} -} -node = this.shadyRoot && this.shadyRoot._hasDistributed ? Polymer.dom(node) : node; -serializeValueToAttribute.call(this, value, attribute, node); -}, -_scopeElementClass: function (element, selector) { -if (!nativeShadow && !this._scopeCssViaAttr) { -selector += (selector ? ' ' : '') + SCOPE_NAME + ' ' + this.is + (element._scopeSelector ? ' ' + XSCOPE_NAME + ' ' + element._scopeSelector : ''); -} -return selector; -}, -updateStyles: function (properties) { -if (this.isAttached) { -if (properties) { -this.mixin(this.customStyle, properties); -} -if (this._needsStyleProperties()) { -this._updateStyleProperties(); -} else { -this._styleProperties = null; -} -if (this._styleCache) { -this._styleCache.clear(); -} -this._updateRootStyles(); -} -}, -_updateRootStyles: function (root) { -root = root || this.root; -var c$ = Polymer.dom(root)._query(function (e) { -return e.shadyRoot || e.shadowRoot; -}); -for (var i = 0, l = c$.length, c; i < l && (c = c$[i]); i++) { -if (c.updateStyles) { -c.updateStyles(); -} -} -} -}); -Polymer.updateStyles = function (properties) { -styleDefaults.updateStyles(properties); -Polymer.Base._updateRootStyles(document); -}; -var styleCache = new Polymer.StyleCache(); -Polymer.customStyleCache = styleCache; -var SCOPE_NAME = styleTransformer.SCOPE_NAME; -var XSCOPE_NAME = propertyUtils.XSCOPE_NAME; -}()); -Polymer.Base._addFeature({ -_registerFeatures: function () { -this._prepIs(); -this._prepConstructor(); -this._prepTemplate(); -this._prepStyles(); -this._prepStyleProperties(); -this._prepAnnotations(); -this._prepEffects(); -this._prepBehaviors(); -this._prepPropertyInfo(); -this._prepBindings(); -this._prepShady(); -}, -_prepBehavior: function (b) { -this._addPropertyEffects(b.properties); -this._addComplexObserverEffects(b.observers); -this._addHostAttributes(b.hostAttributes); -}, -_initFeatures: function () { -this._setupConfigure(); -this._setupStyleProperties(); -this._setupDebouncers(); -this._registerHost(); -if (this._template) { -this._poolContent(); -this._beginHosting(); -this._stampTemplate(); -this._endHosting(); -this._marshalAnnotationReferences(); -} -this._marshalInstanceEffects(); -this._marshalBehaviors(); -this._marshalHostAttributes(); -this._marshalAttributes(); -this._tryReady(); -}, -_marshalBehavior: function (b) { -if (b.listeners) { -this._listenListeners(b.listeners); -} -} -}); -(function () { -var nativeShadow = Polymer.Settings.useNativeShadow; -var propertyUtils = Polymer.StyleProperties; -var styleUtil = Polymer.StyleUtil; -var cssParse = Polymer.CssParse; -var styleDefaults = Polymer.StyleDefaults; -var styleTransformer = Polymer.StyleTransformer; -Polymer({ -is: 'custom-style', -extends: 'style', -_template: null, -properties: { include: String }, -ready: function () { -this._tryApply(); -}, -attached: function () { -this._tryApply(); -}, -_tryApply: function () { -if (!this._appliesToDocument) { -if (this.parentNode && this.parentNode.localName !== 'dom-module') { -this._appliesToDocument = true; -var e = this.__appliedElement || this; -styleDefaults.addStyle(e); -if (e.textContent || this.include) { -this._apply(true); -} else { -var self = this; -var observer = new MutationObserver(function () { -observer.disconnect(); -self._apply(true); -}); -observer.observe(e, { childList: true }); -} -} -} -}, -_apply: function (deferProperties) { -var e = this.__appliedElement || this; -if (this.include) { -e.textContent = styleUtil.cssFromModules(this.include, true) + e.textContent; -} -if (e.textContent) { -styleUtil.forEachStyleRule(styleUtil.rulesForStyle(e), function (rule) { -styleTransformer.documentRule(rule); -}); -var self = this; -function fn() { -self._applyCustomProperties(e); -} -if (this._pendingApplyProperties) { -cancelAnimationFrame(this._pendingApplyProperties); -this._pendingApplyProperties = null; -} -if (deferProperties) { -this._pendingApplyProperties = requestAnimationFrame(fn); -} else { -fn(); -} -} -}, -_applyCustomProperties: function (element) { -this._computeStyleProperties(); -var props = this._styleProperties; -var rules = styleUtil.rulesForStyle(element); -element.textContent = styleUtil.toCssText(rules, function (rule) { -var css = rule.cssText = rule.parsedCssText; -if (rule.propertyInfo && rule.propertyInfo.cssText) { -css = cssParse.removeCustomPropAssignment(css); -rule.cssText = propertyUtils.valueForProperties(css, props); -} -}); -} -}); -}()); -Polymer.Templatizer = { -properties: { __hideTemplateChildren__: { observer: '_showHideChildren' } }, -_instanceProps: Polymer.nob, -_parentPropPrefix: '_parent_', -templatize: function (template) { -this._templatized = template; -if (!template._content) { -template._content = template.content; -} -if (template._content._ctor) { -this.ctor = template._content._ctor; -this._prepParentProperties(this.ctor.prototype, template); -return; -} -var archetype = Object.create(Polymer.Base); -this._customPrepAnnotations(archetype, template); -this._prepParentProperties(archetype, template); -archetype._prepEffects(); -this._customPrepEffects(archetype); -archetype._prepBehaviors(); -archetype._prepPropertyInfo(); -archetype._prepBindings(); -archetype._notifyPathUp = this._notifyPathUpImpl; -archetype._scopeElementClass = this._scopeElementClassImpl; -archetype.listen = this._listenImpl; -archetype._showHideChildren = this._showHideChildrenImpl; -var _constructor = this._constructorImpl; -var ctor = function TemplateInstance(model, host) { -_constructor.call(this, model, host); -}; -ctor.prototype = archetype; -archetype.constructor = ctor; -template._content._ctor = ctor; -this.ctor = ctor; -}, -_getRootDataHost: function () { -return this.dataHost && this.dataHost._rootDataHost || this.dataHost; -}, -_showHideChildrenImpl: function (hide) { -var c = this._children; -for (var i = 0; i < c.length; i++) { -var n = c[i]; -if (Boolean(hide) != Boolean(n.__hideTemplateChildren__)) { -if (n.nodeType === Node.TEXT_NODE) { -if (hide) { -n.__polymerTextContent__ = n.textContent; -n.textContent = ''; -} else { -n.textContent = n.__polymerTextContent__; -} -} else if (n.style) { -if (hide) { -n.__polymerDisplay__ = n.style.display; -n.style.display = 'none'; -} else { -n.style.display = n.__polymerDisplay__; -} -} -} -n.__hideTemplateChildren__ = hide; -} -}, -_debounceTemplate: function (fn) { -Polymer.dom.addDebouncer(this.debounce('_debounceTemplate', fn)); -}, -_flushTemplates: function (debouncerExpired) { -Polymer.dom.flush(); -}, -_customPrepEffects: function (archetype) { -var parentProps = archetype._parentProps; -for (var prop in parentProps) { -archetype._addPropertyEffect(prop, 'function', this._createHostPropEffector(prop)); -} -for (var prop in this._instanceProps) { -archetype._addPropertyEffect(prop, 'function', this._createInstancePropEffector(prop)); -} -}, -_customPrepAnnotations: function (archetype, template) { -archetype._template = template; -var c = template._content; -if (!c._notes) { -var rootDataHost = archetype._rootDataHost; -if (rootDataHost) { -Polymer.Annotations.prepElement = function () { -rootDataHost._prepElement(); -}; -} -c._notes = Polymer.Annotations.parseAnnotations(template); -Polymer.Annotations.prepElement = null; -this._processAnnotations(c._notes); -} -archetype._notes = c._notes; -archetype._parentProps = c._parentProps; -}, -_prepParentProperties: function (archetype, template) { -var parentProps = this._parentProps = archetype._parentProps; -if (this._forwardParentProp && parentProps) { -var proto = archetype._parentPropProto; -var prop; -if (!proto) { -for (prop in this._instanceProps) { -delete parentProps[prop]; -} -proto = archetype._parentPropProto = Object.create(null); -if (template != this) { -Polymer.Bind.prepareModel(proto); -Polymer.Base.prepareModelNotifyPath(proto); -} -for (prop in parentProps) { -var parentProp = this._parentPropPrefix + prop; -var effects = [ -{ -kind: 'function', -effect: this._createForwardPropEffector(prop), -fn: Polymer.Bind._functionEffect -}, -{ -kind: 'notify', -fn: Polymer.Bind._notifyEffect, -effect: { event: Polymer.CaseMap.camelToDashCase(parentProp) + '-changed' } -} -]; -Polymer.Bind._createAccessors(proto, parentProp, effects); -} -} -var self = this; -if (template != this) { -Polymer.Bind.prepareInstance(template); -template._forwardParentProp = function (source, value) { -self._forwardParentProp(source, value); -}; -} -this._extendTemplate(template, proto); -template._pathEffector = function (path, value, fromAbove) { -return self._pathEffectorImpl(path, value, fromAbove); -}; -} -}, -_createForwardPropEffector: function (prop) { -return function (source, value) { -this._forwardParentProp(prop, value); -}; -}, -_createHostPropEffector: function (prop) { -var prefix = this._parentPropPrefix; -return function (source, value) { -this.dataHost._templatized[prefix + prop] = value; -}; -}, -_createInstancePropEffector: function (prop) { -return function (source, value, old, fromAbove) { -if (!fromAbove) { -this.dataHost._forwardInstanceProp(this, prop, value); -} -}; -}, -_extendTemplate: function (template, proto) { -var n$ = Object.getOwnPropertyNames(proto); -for (var i = 0, n; i < n$.length && (n = n$[i]); i++) { -var val = template[n]; -var pd = Object.getOwnPropertyDescriptor(proto, n); -Object.defineProperty(template, n, pd); -if (val !== undefined) { -template._propertySetter(n, val); -} -} -}, -_showHideChildren: function (hidden) { -}, -_forwardInstancePath: function (inst, path, value) { -}, -_forwardInstanceProp: function (inst, prop, value) { -}, -_notifyPathUpImpl: function (path, value) { -var dataHost = this.dataHost; -var dot = path.indexOf('.'); -var root = dot < 0 ? path : path.slice(0, dot); -dataHost._forwardInstancePath.call(dataHost, this, path, value); -if (root in dataHost._parentProps) { -dataHost._templatized.notifyPath(dataHost._parentPropPrefix + path, value); -} -}, -_pathEffectorImpl: function (path, value, fromAbove) { -if (this._forwardParentPath) { -if (path.indexOf(this._parentPropPrefix) === 0) { -var subPath = path.substring(this._parentPropPrefix.length); -var model = this._modelForPath(subPath); -if (model in this._parentProps) { -this._forwardParentPath(subPath, value); -} -} -} -Polymer.Base._pathEffector.call(this._templatized, path, value, fromAbove); -}, -_constructorImpl: function (model, host) { -this._rootDataHost = host._getRootDataHost(); -this._setupConfigure(model); -this._registerHost(host); -this._beginHosting(); -this.root = this.instanceTemplate(this._template); -this.root.__noContent = !this._notes._hasContent; -this.root.__styleScoped = true; -this._endHosting(); -this._marshalAnnotatedNodes(); -this._marshalInstanceEffects(); -this._marshalAnnotatedListeners(); -var children = []; -for (var n = this.root.firstChild; n; n = n.nextSibling) { -children.push(n); -n._templateInstance = this; -} -this._children = children; -if (host.__hideTemplateChildren__) { -this._showHideChildren(true); -} -this._tryReady(); -}, -_listenImpl: function (node, eventName, methodName) { -var model = this; -var host = this._rootDataHost; -var handler = host._createEventHandler(node, eventName, methodName); -var decorated = function (e) { -e.model = model; -handler(e); -}; -host._listen(node, eventName, decorated); -}, -_scopeElementClassImpl: function (node, value) { -var host = this._rootDataHost; -if (host) { -return host._scopeElementClass(node, value); -} -}, -stamp: function (model) { -model = model || {}; -if (this._parentProps) { -var templatized = this._templatized; -for (var prop in this._parentProps) { -model[prop] = templatized[this._parentPropPrefix + prop]; -} -} -return new this.ctor(model, this); -}, -modelForElement: function (el) { -var model; -while (el) { -if (model = el._templateInstance) { -if (model.dataHost != this) { -el = model.dataHost; -} else { -return model; -} -} else { -el = el.parentNode; -} -} -} -}; -Polymer({ -is: 'dom-template', -extends: 'template', -_template: null, -behaviors: [Polymer.Templatizer], -ready: function () { -this.templatize(this); -} -}); -Polymer._collections = new WeakMap(); -Polymer.Collection = function (userArray) { -Polymer._collections.set(userArray, this); -this.userArray = userArray; -this.store = userArray.slice(); -this.initMap(); -}; -Polymer.Collection.prototype = { -constructor: Polymer.Collection, -initMap: function () { -var omap = this.omap = new WeakMap(); -var pmap = this.pmap = {}; -var s = this.store; -for (var i = 0; i < s.length; i++) { -var item = s[i]; -if (item && typeof item == 'object') { -omap.set(item, i); -} else { -pmap[item] = i; -} -} -}, -add: function (item) { -var key = this.store.push(item) - 1; -if (item && typeof item == 'object') { -this.omap.set(item, key); -} else { -this.pmap[item] = key; -} -return '#' + key; -}, -removeKey: function (key) { -key = this._parseKey(key); -this._removeFromMap(this.store[key]); -delete this.store[key]; -}, -_removeFromMap: function (item) { -if (item && typeof item == 'object') { -this.omap.delete(item); -} else { -delete this.pmap[item]; -} -}, -remove: function (item) { -var key = this.getKey(item); -this.removeKey(key); -return key; -}, -getKey: function (item) { -var key; -if (item && typeof item == 'object') { -key = this.omap.get(item); -} else { -key = this.pmap[item]; -} -if (key != undefined) { -return '#' + key; -} -}, -getKeys: function () { -return Object.keys(this.store).map(function (key) { -return '#' + key; -}); -}, -_parseKey: function (key) { -if (key[0] == '#') { -return key.slice(1); -} -throw new Error('unexpected key ' + key); -}, -setItem: function (key, item) { -key = this._parseKey(key); -var old = this.store[key]; -if (old) { -this._removeFromMap(old); -} -if (item && typeof item == 'object') { -this.omap.set(item, key); -} else { -this.pmap[item] = key; -} -this.store[key] = item; -}, -getItem: function (key) { -key = this._parseKey(key); -return this.store[key]; -}, -getItems: function () { -var items = [], store = this.store; -for (var key in store) { -items.push(store[key]); -} -return items; -}, -_applySplices: function (splices) { -var keyMap = {}, key; -for (var i = 0, s; i < splices.length && (s = splices[i]); i++) { -s.addedKeys = []; -for (var j = 0; j < s.removed.length; j++) { -key = this.getKey(s.removed[j]); -keyMap[key] = keyMap[key] ? null : -1; -} -for (var j = 0; j < s.addedCount; j++) { -var item = this.userArray[s.index + j]; -key = this.getKey(item); -key = key === undefined ? this.add(item) : key; -keyMap[key] = keyMap[key] ? null : 1; -s.addedKeys.push(key); -} -} -var removed = []; -var added = []; -for (var key in keyMap) { -if (keyMap[key] < 0) { -this.removeKey(key); -removed.push(key); -} -if (keyMap[key] > 0) { -added.push(key); -} -} -return [{ -removed: removed, -added: added -}]; -} -}; -Polymer.Collection.get = function (userArray) { -return Polymer._collections.get(userArray) || new Polymer.Collection(userArray); -}; -Polymer.Collection.applySplices = function (userArray, splices) { -var coll = Polymer._collections.get(userArray); -return coll ? coll._applySplices(splices) : null; -}; -Polymer({ -is: 'dom-repeat', -extends: 'template', -_template: null, -properties: { -items: { type: Array }, -as: { -type: String, -value: 'item' -}, -indexAs: { -type: String, -value: 'index' -}, -sort: { -type: Function, -observer: '_sortChanged' -}, -filter: { -type: Function, -observer: '_filterChanged' -}, -observe: { -type: String, -observer: '_observeChanged' -}, -delay: Number, -initialCount: { -type: Number, -observer: '_initializeChunking' -}, -targetFramerate: { -type: Number, -value: 20 -}, -_targetFrameTime: { computed: '_computeFrameTime(targetFramerate)' } -}, -behaviors: [Polymer.Templatizer], -observers: ['_itemsChanged(items.*)'], -created: function () { -this._instances = []; -this._pool = []; -this._limit = Infinity; -var self = this; -this._boundRenderChunk = function () { -self._renderChunk(); -}; -}, -detached: function () { -for (var i = 0; i < this._instances.length; i++) { -this._detachInstance(i); -} -}, -attached: function () { -var parent = Polymer.dom(Polymer.dom(this).parentNode); -for (var i = 0; i < this._instances.length; i++) { -this._attachInstance(i, parent); -} -}, -ready: function () { -this._instanceProps = { __key__: true }; -this._instanceProps[this.as] = true; -this._instanceProps[this.indexAs] = true; -if (!this.ctor) { -this.templatize(this); -} -}, -_sortChanged: function (sort) { -var dataHost = this._getRootDataHost(); -this._sortFn = sort && (typeof sort == 'function' ? sort : function () { -return dataHost[sort].apply(dataHost, arguments); -}); -this._needFullRefresh = true; -if (this.items) { -this._debounceTemplate(this._render); -} -}, -_filterChanged: function (filter) { -var dataHost = this._getRootDataHost(); -this._filterFn = filter && (typeof filter == 'function' ? filter : function () { -return dataHost[filter].apply(dataHost, arguments); -}); -this._needFullRefresh = true; -if (this.items) { -this._debounceTemplate(this._render); -} -}, -_computeFrameTime: function (rate) { -return Math.ceil(1000 / rate); -}, -_initializeChunking: function () { -if (this.initialCount) { -this._limit = this.initialCount; -this._chunkCount = this.initialCount; -this._lastChunkTime = performance.now(); -} -}, -_tryRenderChunk: function () { -if (this.items && this._limit < this.items.length) { -this.debounce('renderChunk', this._requestRenderChunk); -} -}, -_requestRenderChunk: function () { -requestAnimationFrame(this._boundRenderChunk); -}, -_renderChunk: function () { -var currChunkTime = performance.now(); -var ratio = this._targetFrameTime / (currChunkTime - this._lastChunkTime); -this._chunkCount = Math.round(this._chunkCount * ratio) || 1; -this._limit += this._chunkCount; -this._lastChunkTime = currChunkTime; -this._debounceTemplate(this._render); -}, -_observeChanged: function () { -this._observePaths = this.observe && this.observe.replace('.*', '.').split(' '); -}, -_itemsChanged: function (change) { -if (change.path == 'items') { -if (Array.isArray(this.items)) { -this.collection = Polymer.Collection.get(this.items); -} else if (!this.items) { -this.collection = null; -} else { -this._error(this._logf('dom-repeat', 'expected array for `items`,' + ' found', this.items)); -} -this._keySplices = []; -this._indexSplices = []; -this._needFullRefresh = true; -this._initializeChunking(); -this._debounceTemplate(this._render); -} else if (change.path == 'items.splices') { -this._keySplices = this._keySplices.concat(change.value.keySplices); -this._indexSplices = this._indexSplices.concat(change.value.indexSplices); -this._debounceTemplate(this._render); -} else { -var subpath = change.path.slice(6); -this._forwardItemPath(subpath, change.value); -this._checkObservedPaths(subpath); -} -}, -_checkObservedPaths: function (path) { -if (this._observePaths) { -path = path.substring(path.indexOf('.') + 1); -var paths = this._observePaths; -for (var i = 0; i < paths.length; i++) { -if (path.indexOf(paths[i]) === 0) { -this._needFullRefresh = true; -if (this.delay) { -this.debounce('render', this._render, this.delay); -} else { -this._debounceTemplate(this._render); -} -return; -} -} -} -}, -render: function () { -this._needFullRefresh = true; -this._debounceTemplate(this._render); -this._flushTemplates(); -}, -_render: function () { -var c = this.collection; -if (this._needFullRefresh) { -this._applyFullRefresh(); -this._needFullRefresh = false; -} else if (this._keySplices.length) { -if (this._sortFn) { -this._applySplicesUserSort(this._keySplices); -} else { -if (this._filterFn) { -this._applyFullRefresh(); -} else { -this._applySplicesArrayOrder(this._indexSplices); -} -} -} else { -} -this._keySplices = []; -this._indexSplices = []; -var keyToIdx = this._keyToInstIdx = {}; -for (var i = this._instances.length - 1; i >= 0; i--) { -var inst = this._instances[i]; -if (inst.isPlaceholder && i < this._limit) { -inst = this._insertInstance(i, inst.__key__); -} else if (!inst.isPlaceholder && i >= this._limit) { -inst = this._downgradeInstance(i, inst.__key__); -} -keyToIdx[inst.__key__] = i; -if (!inst.isPlaceholder) { -inst.__setProperty(this.indexAs, i, true); -} -} -this._pool.length = 0; -this.fire('dom-change'); -this._tryRenderChunk(); -}, -_applyFullRefresh: function () { -var c = this.collection; -var keys; -if (this._sortFn) { -keys = c ? c.getKeys() : []; -} else { -keys = []; -var items = this.items; -if (items) { -for (var i = 0; i < items.length; i++) { -keys.push(c.getKey(items[i])); -} -} -} -var self = this; -if (this._filterFn) { -keys = keys.filter(function (a) { -return self._filterFn(c.getItem(a)); -}); -} -if (this._sortFn) { -keys.sort(function (a, b) { -return self._sortFn(c.getItem(a), c.getItem(b)); -}); -} -for (var i = 0; i < keys.length; i++) { -var key = keys[i]; -var inst = this._instances[i]; -if (inst) { -inst.__key__ = key; -if (!inst.isPlaceholder && i < this._limit) { -inst.__setProperty(this.as, c.getItem(key), true); -} -} else if (i < this._limit) { -this._insertInstance(i, key); -} else { -this._insertPlaceholder(i, key); -} -} -for (var j = this._instances.length - 1; j >= i; j--) { -this._detachAndRemoveInstance(j); -} -}, -_numericSort: function (a, b) { -return a - b; -}, -_applySplicesUserSort: function (splices) { -var c = this.collection; -var instances = this._instances; -var keyMap = {}; -for (var i = 0, s; i < splices.length && (s = splices[i]); i++) { -for (var j = 0; j < s.removed.length; j++) { -var key = s.removed[j]; -keyMap[key] = keyMap[key] ? null : -1; -} -for (var j = 0; j < s.added.length; j++) { -var key = s.added[j]; -keyMap[key] = keyMap[key] ? null : 1; -} -} -var removedIdxs = []; -var addedKeys = []; -for (var key in keyMap) { -if (keyMap[key] === -1) { -removedIdxs.push(this._keyToInstIdx[key]); -} -if (keyMap[key] === 1) { -addedKeys.push(key); -} -} -if (removedIdxs.length) { -removedIdxs.sort(this._numericSort); -for (var i = removedIdxs.length - 1; i >= 0; i--) { -var idx = removedIdxs[i]; -if (idx !== undefined) { -this._detachAndRemoveInstance(idx); -} -} -} -var self = this; -if (addedKeys.length) { -if (this._filterFn) { -addedKeys = addedKeys.filter(function (a) { -return self._filterFn(c.getItem(a)); -}); -} -addedKeys.sort(function (a, b) { -return self._sortFn(c.getItem(a), c.getItem(b)); -}); -var start = 0; -for (var i = 0; i < addedKeys.length; i++) { -start = this._insertRowUserSort(start, addedKeys[i]); -} -} -}, -_insertRowUserSort: function (start, key) { -var c = this.collection; -var item = c.getItem(key); -var end = this._instances.length - 1; -var idx = -1; -while (start <= end) { -var mid = start + end >> 1; -var midKey = this._instances[mid].__key__; -var cmp = this._sortFn(c.getItem(midKey), item); -if (cmp < 0) { -start = mid + 1; -} else if (cmp > 0) { -end = mid - 1; -} else { -idx = mid; -break; -} -} -if (idx < 0) { -idx = end + 1; -} -this._insertPlaceholder(idx, key); -return idx; -}, -_applySplicesArrayOrder: function (splices) { -var c = this.collection; -for (var i = 0, s; i < splices.length && (s = splices[i]); i++) { -for (var j = 0; j < s.removed.length; j++) { -this._detachAndRemoveInstance(s.index); -} -for (var j = 0; j < s.addedKeys.length; j++) { -this._insertPlaceholder(s.index + j, s.addedKeys[j]); -} -} -}, -_detachInstance: function (idx) { -var inst = this._instances[idx]; -if (!inst.isPlaceholder) { -for (var i = 0; i < inst._children.length; i++) { -var el = inst._children[i]; -Polymer.dom(inst.root).appendChild(el); -} -return inst; -} -}, -_attachInstance: function (idx, parent) { -var inst = this._instances[idx]; -if (!inst.isPlaceholder) { -parent.insertBefore(inst.root, this); -} -}, -_detachAndRemoveInstance: function (idx) { -var inst = this._detachInstance(idx); -if (inst) { -this._pool.push(inst); -} -this._instances.splice(idx, 1); -}, -_insertPlaceholder: function (idx, key) { -this._instances.splice(idx, 0, { -isPlaceholder: true, -__key__: key -}); -}, -_stampInstance: function (idx, key) { -var model = { __key__: key }; -model[this.as] = this.collection.getItem(key); -model[this.indexAs] = idx; -return this.stamp(model); -}, -_insertInstance: function (idx, key) { -var inst = this._pool.pop(); -if (inst) { -inst.__setProperty(this.as, this.collection.getItem(key), true); -inst.__setProperty('__key__', key, true); -} else { -inst = this._stampInstance(idx, key); -} -var beforeRow = this._instances[idx + 1]; -var beforeNode = beforeRow && !beforeRow.isPlaceholder ? beforeRow._children[0] : this; -var parentNode = Polymer.dom(this).parentNode; -Polymer.dom(parentNode).insertBefore(inst.root, beforeNode); -this._instances[idx] = inst; -return inst; -}, -_downgradeInstance: function (idx, key) { -var inst = this._detachInstance(idx); -if (inst) { -this._pool.push(inst); -} -inst = { -isPlaceholder: true, -__key__: key -}; -this._instances[idx] = inst; -return inst; -}, -_showHideChildren: function (hidden) { -for (var i = 0; i < this._instances.length; i++) { -this._instances[i]._showHideChildren(hidden); -} -}, -_forwardInstanceProp: function (inst, prop, value) { -if (prop == this.as) { -var idx; -if (this._sortFn || this._filterFn) { -idx = this.items.indexOf(this.collection.getItem(inst.__key__)); -} else { -idx = inst[this.indexAs]; -} -this.set('items.' + idx, value); -} -}, -_forwardInstancePath: function (inst, path, value) { -if (path.indexOf(this.as + '.') === 0) { -this._notifyPath('items.' + inst.__key__ + '.' + path.slice(this.as.length + 1), value); -} -}, -_forwardParentProp: function (prop, value) { -var i$ = this._instances; -for (var i = 0, inst; i < i$.length && (inst = i$[i]); i++) { -if (!inst.isPlaceholder) { -inst.__setProperty(prop, value, true); -} -} -}, -_forwardParentPath: function (path, value) { -var i$ = this._instances; -for (var i = 0, inst; i < i$.length && (inst = i$[i]); i++) { -if (!inst.isPlaceholder) { -inst._notifyPath(path, value, true); -} -} -}, -_forwardItemPath: function (path, value) { -if (this._keyToInstIdx) { -var dot = path.indexOf('.'); -var key = path.substring(0, dot < 0 ? path.length : dot); -var idx = this._keyToInstIdx[key]; -var inst = this._instances[idx]; -if (inst && !inst.isPlaceholder) { -if (dot >= 0) { -path = this.as + '.' + path.substring(dot + 1); -inst._notifyPath(path, value, true); -} else { -inst.__setProperty(this.as, value, true); -} -} -} -}, -itemForElement: function (el) { -var instance = this.modelForElement(el); -return instance && instance[this.as]; -}, -keyForElement: function (el) { -var instance = this.modelForElement(el); -return instance && instance.__key__; -}, -indexForElement: function (el) { -var instance = this.modelForElement(el); -return instance && instance[this.indexAs]; -} -}); -Polymer({ -is: 'array-selector', -_template: null, -properties: { -items: { -type: Array, -observer: 'clearSelection' -}, -multi: { -type: Boolean, -value: false, -observer: 'clearSelection' -}, -selected: { -type: Object, -notify: true -}, -selectedItem: { -type: Object, -notify: true -}, -toggle: { -type: Boolean, -value: false -} -}, -clearSelection: function () { -if (Array.isArray(this.selected)) { -for (var i = 0; i < this.selected.length; i++) { -this.unlinkPaths('selected.' + i); -} -} else { -this.unlinkPaths('selected'); -this.unlinkPaths('selectedItem'); -} -if (this.multi) { -if (!this.selected || this.selected.length) { -this.selected = []; -this._selectedColl = Polymer.Collection.get(this.selected); -} -} else { -this.selected = null; -this._selectedColl = null; -} -this.selectedItem = null; -}, -isSelected: function (item) { -if (this.multi) { -return this._selectedColl.getKey(item) !== undefined; -} else { -return this.selected == item; -} -}, -deselect: function (item) { -if (this.multi) { -if (this.isSelected(item)) { -var skey = this._selectedColl.getKey(item); -this.arrayDelete('selected', item); -this.unlinkPaths('selected.' + skey); -} -} else { -this.selected = null; -this.selectedItem = null; -this.unlinkPaths('selected'); -this.unlinkPaths('selectedItem'); -} -}, -select: function (item) { -var icol = Polymer.Collection.get(this.items); -var key = icol.getKey(item); -if (this.multi) { -if (this.isSelected(item)) { -if (this.toggle) { -this.deselect(item); -} -} else { -this.push('selected', item); -var skey = this._selectedColl.getKey(item); -this.linkPaths('selected.' + skey, 'items.' + key); -} -} else { -if (this.toggle && item == this.selected) { -this.deselect(); -} else { -this.selected = item; -this.selectedItem = item; -this.linkPaths('selected', 'items.' + key); -this.linkPaths('selectedItem', 'items.' + key); -} -} -} -}); -Polymer({ -is: 'dom-if', -extends: 'template', -_template: null, -properties: { -'if': { -type: Boolean, -value: false, -observer: '_queueRender' -}, -restamp: { -type: Boolean, -value: false, -observer: '_queueRender' -} -}, -behaviors: [Polymer.Templatizer], -_queueRender: function () { -this._debounceTemplate(this._render); -}, -detached: function () { -this._teardownInstance(); -}, -attached: function () { -if (this.if && this.ctor) { -this.async(this._ensureInstance); -} -}, -render: function () { -this._flushTemplates(); -}, -_render: function () { -if (this.if) { -if (!this.ctor) { -this.templatize(this); -} -this._ensureInstance(); -this._showHideChildren(); -} else if (this.restamp) { -this._teardownInstance(); -} -if (!this.restamp && this._instance) { -this._showHideChildren(); -} -if (this.if != this._lastIf) { -this.fire('dom-change'); -this._lastIf = this.if; -} -}, -_ensureInstance: function () { -if (!this._instance) { -var parentNode = Polymer.dom(this).parentNode; -if (parentNode) { -var parent = Polymer.dom(parentNode); -this._instance = this.stamp(); -var root = this._instance.root; -parent.insertBefore(root, this); -} -} -}, -_teardownInstance: function () { -if (this._instance) { -var c$ = this._instance._children; -if (c$) { -var parent = Polymer.dom(Polymer.dom(c$[0]).parentNode); -for (var i = 0, n; i < c$.length && (n = c$[i]); i++) { -parent.removeChild(n); -} -} -this._instance = null; -} -}, -_showHideChildren: function () { -var hidden = this.__hideTemplateChildren__ || !this.if; -if (this._instance) { -this._instance._showHideChildren(hidden); -} -}, -_forwardParentProp: function (prop, value) { -if (this._instance) { -this._instance[prop] = value; -} -}, -_forwardParentPath: function (path, value) { -if (this._instance) { -this._instance._notifyPath(path, value, true); -} -} -}); -Polymer({ -is: 'dom-bind', -extends: 'template', -_template: null, -created: function () { -var self = this; -Polymer.RenderStatus.whenReady(function () { -self._markImportsReady(); -}); -}, -_ensureReady: function () { -if (!this._readied) { -this._readySelf(); -} -}, -_markImportsReady: function () { -this._importsReady = true; -this._ensureReady(); -}, -_registerFeatures: function () { -this._prepConstructor(); -}, -_insertChildren: function () { -var parentDom = Polymer.dom(Polymer.dom(this).parentNode); -parentDom.insertBefore(this.root, this); -}, -_removeChildren: function () { -if (this._children) { -for (var i = 0; i < this._children.length; i++) { -this.root.appendChild(this._children[i]); -} -} -}, -_initFeatures: function () { -}, -_scopeElementClass: function (element, selector) { -if (this.dataHost) { -return this.dataHost._scopeElementClass(element, selector); -} else { -return selector; -} -}, -_prepConfigure: function () { -var config = {}; -for (var prop in this._propertyEffects) { -config[prop] = this[prop]; -} -var setupConfigure = this._setupConfigure; -this._setupConfigure = function () { -setupConfigure.call(this, config); -}; -}, -attached: function () { -if (this._importsReady) { -this.render(); -} -}, -detached: function () { -this._removeChildren(); -}, -render: function () { -this._ensureReady(); -if (!this._children) { -this._template = this; -this._prepAnnotations(); -this._prepEffects(); -this._prepBehaviors(); -this._prepConfigure(); -this._prepBindings(); -this._prepPropertyInfo(); -Polymer.Base._initFeatures.call(this); -this._children = Polymer.DomApi.arrayCopyChildNodes(this.root); -} -this._insertChildren(); -this.fire('dom-change'); -} -});</script><script> - /** - * The `<platinum-sw-cache>` element makes it easy to precache specific resources, perform runtime - * caching, and serve your cached resources when a network is unavailable. - * Under the hood, the [sw-toolbox](https://github.com/googlechrome/sw-toolbox) library is used - * for all the caching and request handling logic. - * `<platinum-sw-cache>` needs to be a child element of `<platinum-sw-register>`. - * A simple, yet useful configuration is - * - * <platinum-sw-register auto-register> - * <platinum-sw-cache></platinum-sw-cache> - * </platinum-sw-register> - * - * This is enough to have all of the resources your site uses cached at runtime, both local and - * cross-origin. - * (It uses the default `defaultCacheStrategy` of "networkFirst".) - * When there's a network available, visits to your site will go against the network copy of the - * resources, but if someone visits your site when they're offline, all the cached resources will - * be used. - * - * @demo demo/index.html An offline-capable eReader demo. - */ - Polymer({ - is: 'platinum-sw-cache', - - properties: { - /** - * Used to configure `<platinum-sw-precache>` behavior via a JSON file instead of via - * attributes. This can come in handy when the configuration (e.g. which files to precache) - * depends on the results of a build script. - * - * If configuration for the same properties are provided in both the JSON file and via the - * element's attributes, then in general the JSON file's values take precedence. The one - * exception is the `precache` property. Any values in the element's `precache` attribute will - * be concatenated with the values in the JSON file's `precache` property and the set of files - * that are precached will be the union of the two. - * - * There's one additional option, `precacheFingerprint`, that can be set in the JSON. If using - * a build script that might output a large number of files to precache, its recommended - * that your build script generate a unique "fingerprint" of the files. Any changes to the - * `precacheFingerprint` value will result in the underlying service worker kicking off the - * process of caching the files listed in `precache`. - * While there are a few different strategies for generating an appropriate - * `precacheFingerprint` value, a process that makes sense is to use a stable hash of the - * serialized `precache` array. That way, any changes to the list of files in `precache` - * will result in a new `precacheFingerprint` value. - * If your build script is Node.js based, one way to generate this hash is: - * - * var md5 = require('crypto').createHash('md5'); - * md5.update(JSON.stringify(precache)); - * var precacheFingerprint = md5.digest('hex'); - * - * Alternatively, you could use something like the - * [SHA-1 signature](http://stackoverflow.com/questions/1161869/how-to-get-sha-of-the-latest-commit-from-remote-git-repository) - * of your latest `git` commit for the `precacheFingerprint` value. - * - * An example file may look like: - * - * { - * "cacheId": "my-cache-id", - * "defaultCacheStrategy": "fastest", - * "disabled": false, - * "precache": ["file1.html", "file2.css"], - * "precacheFingerprint": "FINGERPRINT_OF_FILES_IN_PRECACHE" - * } - */ - cacheConfigFile: String, - - /** - * An id used to construct the name for the - * [Cache](https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#cache) - * in which all the resources will be stored. - * - * If nothing is provided, the default value set in - * [`toolbox.options.cacheName`](https://github.com/GoogleChrome/sw-toolbox/blob/8763dcc9fbc9352d58f184050e2131c42f7b6d68/lib/options.js#L28) - * will be used. - * - * The `cacheId` is combined with the service worker's scope to construct the cache name, so - * two `<platinum-sw-cache>` elements that are associated with different scopes will use - * different caches. - */ - cacheId: String, - - /** - * The caching strategy used for all requests, both for local and cross-origin resources. - * - * For a list of strategies, see the [`sw-toolbox` documentation](https://github.com/GoogleChrome/sw-toolbox#built-in-handlers). - * Specify a strategy as a string, without the "toolbox" prefix. E.g., for - * `toolbox.networkFirst`, set `defaultCacheStrategy` to "networkFirst". - * - * Note that the "cacheFirst" and "cacheOnly" strategies are not recommended, and may be - * explicitly prevented in a future release. More information can be found at - * https://github.com/PolymerElements/platinum-sw#cacheonly--cachefirst-defaultcachestrategy-considered-harmful - * - * @see {@link https://github.com/GoogleChrome/sw-toolbox#built-in-handlers} - */ - defaultCacheStrategy: { - type: String, - value: 'networkFirst' - }, - - /** - * If set to true, this element will not set up service worker caching. This is useful to - * conditionally enable or disable caching depending on the build environment. - */ - disabled: { - type: Boolean, - value: false - }, - - /** - * Used to provide a list of URLs that are always precached as soon as the service worker is - * installed. Corresponds to [`sw-toolbox`'s `precache()` method](https://github.com/GoogleChrome/sw-toolbox#toolboxprecachearrayofurls). - * - * This is useful for URLs that that wouldn't necessarily be picked up by runtime caching, - * i.e. a list of resources that are needed by one of the subpages of your site, or a list of - * resources that are only loaded via user interaction. - * - * `precache` can be used in conjunction with `cacheConfigFile`, and the two arrays will be - * concatenated. - * - * @see {@link https://github.com/GoogleChrome/sw-toolbox#toolboxprecachearrayofurls} - */ - precache: { - type: Array, - value: function() { return []; } - } - }, - - _getParameters: function(baseURI) { - return new Promise(function(resolve) { - var params = { - importscriptLate: new URL('bootstrap/sw-toolbox-setup.js', baseURI).href, - defaultCacheStrategy: this.defaultCacheStrategy, - precache: this.precache - }; - - if (this.cacheConfigFile) { - params.cacheConfigFile = this.cacheConfigFile; - window.fetch(this.cacheConfigFile).then(function(response) { - if (!response.ok) { - throw Error('unable to load ' + this.cacheConfigFile); - } - return response.json(); - }.bind(this)).then(function(config) { - this.disabled = config.disabled; - if (this.disabled) { - // Use an empty set of parameters to effectively disable caching. - params = {}; - } else { - // If there's a hash of the list of files to precache provided in the config file, - // then copy that over to the params that will be used to construct the service worker - // URL. This works around the issue where a potentially large number of precache - // files could result in a longer URL than a browser will allow. - // The actual list of files to precache (in config.precache) will be dealt by the - // service worker during the install phase, so we can ignore it here. - // See https://github.com/PolymerElements/platinum-sw/issues/53 - if (config.precacheFingerprint) { - params.precacheFingerprint = config.precacheFingerprint; - } else { - params.precache = params.precache.concat(config.precache); - } - params.cacheId = config.cacheId || params.cacheId; - params.defaultCacheStrategy = config.defaultCacheStrategy || - params.defaultCacheStrategy; - } - }.bind(this)).catch(function(error) { - console.info('Skipping precaching: ' + error.message); - }).then(function() { - resolve(params); - }); - } else { - resolve(params); - } - }.bind(this)); - } - }); -</script><script> - /** - * The `<platinum-sw-register>` element handles - * [service worker](http://www.html5rocks.com/en/tutorials/service-worker/introduction/) - * registration, reflects the overall service worker state, and coordinates the configuration - * provided by other Service Worker Elements. - * `<platinum-sw-register>` is used as a parent element for child elements in the - * `<platinum-sw-*>` group. - * - * <platinum-sw-register skip-waiting - * clients-claim - * auto-register - * state="{{state}}" - * on-service-worker-error="handleSWError" - * on-service-worker-updated="handleSWUpdated" - * on-service-worker-installed="handleSWInstalled"> - * ...one or more <platinum-sw-*> children which share the service worker registration... - * </platinum-sw-register> - * - * Please see https://github.com/PolymerElements/platinum-sw#top-level-sw-importjs for a - * *crucial* prerequisite file you must create before `<platinum-sw-register>` can be used! - * - * @demo demo/index.html An offline-capable eReader demo. - */ - Polymer({ - is: 'platinum-sw-register', - - // Used as an "emergency" switch if we make breaking changes in the way <platinum-sw-register> - // talks to service-worker.js. Otherwise, it shouldn't need to change, and isn't meant to be - // kept in sync with the element's release number. - _version: '1.0', - - /** - * Fired when the initial service worker installation completes successfully. - * The service worker will normally only be installed once, the first time a page with a - * `<platinum-sw-register>` element is visited in a given browser. If the same page is visited - * again, the existing service worker will be reused, and there won't be another - * `service-worker-installed` fired. - * - * @event service-worker-installed - * @param {String} A message indicating that the installation succeeded. - */ - - /** - * Fired when the service worker update flow completes successfully. - * If you make changes to your `<platinum-sw-register>` configuration (i.e. by adding in new - * `<platinum-sw-*>` child elements, or changing their attributes), users who had the old - * service worker installed will get the update installed when they see the modified elements. - * - * @event service-worker-updated - * @param {String} A message indicating that the update succeeded. - */ - - /** - * Fired when an error prevents the service worker installation from completing. - * - * @event service-worker-error - * @param {String} A message indicating what went wrong. - */ - - properties: { - /** - * Whether this element should automatically register the corresponding service worker as - * soon as its added to a page. - * - * If set to `false`, then the service worker won't be automatically registered, and you - * must call this element's `register()` method if you want service worker functionality. - * This is useful if, for example, the service worker needs to be configured using - * information that isn't immediately available at the time the page loads. - * - * If set to `true`, the service worker will be automatically registered without having to - * call any methods. - */ - autoRegister: { - type: Boolean, - value: false - }, - - /** - * The URI used as a base when constructing relative paths to service worker helper libraries - * that need to be loaded. - * - * This can normally be kept set to the default, which will use the directory containing this - * element as the base. However, if you [Vulcanize](https://github.com/polymer/vulcanize) your - * elements, then the default base might not be appropriate anymore. This will allow you to - * override it. - * - * See https://github.com/PolymerElements/platinum-sw#relative-paths--vulcanization for more - * information. - */ - baseUri: { - type: String, - // Grab the URI of this file to use as a base when resolving relative paths. - // Fallback to './' as a default, though current browsers that don't support - // document.currentScript also don't support service workers. - value: document.currentScript ? document.currentScript.baseURI : './' - }, - - /** - * Whether the activated service worker should [take immediate control](https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#clients-claim-method) - * of any pages under its scope. - * - * If this is `false`, the service worker won't have any effect until the next time the page - * is visited/reloaded. - * If this is `true`, it will take control and start handling events for the current page - * (and any pages under the same scope open in other tabs/windows) as soon it's active. - * @see {@link https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#clients-claim-method} - */ - clientsClaim: { - type: Boolean, - value: false - }, - - /** - * The service worker script that is [registered](https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#navigator-service-worker-register). - * The script *should* be located at the top level of your site, to ensure that it is able - * to control all the pages on your site. - * - * It's *strongly* recommended that you create a top-level file named `sw-import.js` - * containing only: - * - * `importScripts('bower_components/platinum-sw/service-worker.js');` - * - * (adjust to match the path where your `platinum-sw` element directory can be found). - * - * This will ensure that your service worker script contains everything needed to play - * nicely with the Service Worker Elements group. - * - * @see {@link https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#navigator-service-worker-register} - */ - href: { - type: String, - value: 'sw-import.js' - }, - - /** - * Whether the page should be automatically reloaded (via `window.location.reload()`) when - * the service worker is successfully installed. - * - * While it's perfectly valid to continue using a page with a freshly installed service - * worker, it's a common pattern to want to reload it immediately following the install. - * This ensures that, for example, if you're using a `<platinum-sw-cache>` with an on the - * fly caching strategy, it will get a chance to intercept all the requests needed to render - * your page and store them in the cache. - * - * If you don't immediately reload your page, then any resources that were loaded before the - * service worker was installed (e.g. this `platinum-sw-register.html` file) won't be present - * in the cache until the next time the page is loaded. - * - * Note that this reload will only happen when a service worker is installed for the first - * time. If the service worker is subsequently updated, it won't trigger another reload. - */ - reloadOnInstall: { - type: Boolean, - value: false - }, - - /** - * The scope of the service worker, relative to the registered service worker script. - * All pages that fall under this scope will be controlled by the registered service worker. - * - * Normally, this would not need to be changed, unless you want the service worker to only - * apply to a subset of your site. - * - * @see {@link https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#navigator-service-worker-register} - */ - scope: { - type: String, - value: './' - }, - - /** - * Whether an updated service worker should [bypass the `waiting` state](https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#service-worker-global-scope-skipwaiting) - * and immediately become `active`. - * - * Normally, during an update, the new service worker stays in the - * `waiting` state until the current page and any other tabs/windows that are using the old - * service worker are unloaded. - * - * If this is `false`, an updated service worker won't be activated until all instances of - * the old server worker have been unloaded. - * - * If this is `true`, an updated service worker will become `active` immediately. - * @see {@link https://slightlyoff.github.io/ServiceWorker/spec/service_worker/#service-worker-global-scope-skipwaiting} - */ - skipWaiting: { - type: Boolean, - value: false - }, - - /** - * The current state of the service worker registered by this element. - * - * One of: - * - 'installed' - * - 'updated' - * - 'error' - * - 'unsupported' - */ - state: { - notify: true, - readOnly: true, - type: String - } - }, - - /** - * Registers the service worker based on the configuration options in this element and any - * child elements. - * - * If you set the `autoRegister` property to `true`, then this method is called automatically - * at page load. - * It can be useful to set `autoRegister` to `false` and then explicitly call this method if - * there are options that are only configured after the page is loaded. - */ - register: function() { - if ('serviceWorker' in navigator) { - this._constructServiceWorkerUrl().then(function(serviceWorkerUrl) { - this._registerServiceWorker(serviceWorkerUrl); - }.bind(this)); - } else { - this._setState('unsupported'); - this.fire('service-worker-error', 'Service workers are not available in the current browser.'); - } - }, - - _constructServiceWorkerUrl: function() { - var paramsPromises = []; - var children = Polymer.dom(this).children; - var baseUri = new URL(this.baseUri, window.location.href); - - for (var i = 0; i < children.length; i++) { - if (typeof children[i]._getParameters === 'function') { - paramsPromises.push(children[i]._getParameters(baseUri)); - } - } - - return Promise.all(paramsPromises).then(function(paramsResolutions) { - var params = { - baseURI: baseUri, - version: this._version - }; - - paramsResolutions.forEach(function(childParams) { - Object.keys(childParams).forEach(function(key) { - if (Array.isArray(params[key])) { - params[key] = params[key].concat(childParams[key]); - } else { - params[key] = [].concat(childParams[key]); - } - }); - }); - - return params; - }.bind(this)).then(function(params) { - if (params.importscriptLate) { - if (params.importscript) { - params.importscript = params.importscript.concat(params.importscriptLate); - } else { - params.importscript = params.importscriptLate; - } - } - - if (params.importscript) { - params.importscript = this._unique(params.importscript); - } - - // We've already concatenated importscriptLate, so don't include it in the serialized URL. - delete params.importscriptLate; - - params.clientsClaim = this.clientsClaim; - params.skipWaiting = this.skipWaiting; - - var serviceWorkerUrl = new URL(this.href, window.location); - // It's very important to ensure that the serialization is stable. - // Serializing the same settings should always produce the same URL. - // Serializing different settings should always produce a different URL. - // This ensures that the service worker upgrade flow is triggered when settings change. - serviceWorkerUrl.search = this._serializeUrlParams(params); - - return serviceWorkerUrl; - }.bind(this)); - }, - - _unique: function(arr) { - return arr.filter(function(item, index) { - return arr.indexOf(item) === index; - }); - }, - - _serializeUrlParams: function(params) { - return Object.keys(params).sort().map(function(key) { - // encodeURIComponent(['a', 'b']) => 'a%2Cb', - // so this will still work when the values are Arrays. - // TODO: It won't work if the values in the Arrays have ',' characters in them. - return encodeURIComponent(key) + "=" + encodeURIComponent(params[key]); - }).join('&'); - }, - - _registerServiceWorker: function(serviceWorkerUrl) { - navigator.serviceWorker.register(serviceWorkerUrl, {scope: this.scope}).then(function(registration) { - if (registration.active) { - this._setState('installed'); - } - - registration.onupdatefound = function() { - var installingWorker = registration.installing; - installingWorker.onstatechange = function() { - switch (installingWorker.state) { - case 'installed': - if (navigator.serviceWorker.controller) { - this._setState('updated'); - this.fire('service-worker-updated', - 'A new service worker was installed, replacing the old service worker.'); - } else { - if (this.reloadOnInstall) { - window.location.reload(); - } else { - this._setState('installed'); - this.fire('service-worker-installed', 'A new service worker was installed.'); - } - } - break; - - case 'redundant': - this._setState('error'); - this.fire('service-worker-error', 'The installing service worker became redundant.'); - break; - } - }.bind(this); - }.bind(this); - }.bind(this)).catch(function(error) { - this._setState('error'); - this.fire('service-worker-error', error.toString()); - if (error.name === 'NetworkError') { - var location = serviceWorkerUrl.origin + serviceWorkerUrl.pathname; - console.error('A valid service worker script was not found at ' + location + '\n' + - 'To learn how to fix this, please see\n' + - 'https://github.com/PolymerElements/platinum-sw#top-level-sw-importjs'); - } - }.bind(this)); - }, - - attached: function() { - if (this.autoRegister) { - this.async(this.register); - } - } - }); -</script><script> - -/** -Polymer.IronFitBehavior fits an element in another element using `max-height` and `max-width`, and -optionally centers it in the window or another element. - -The element will only be sized and/or positioned if it has not already been sized and/or positioned -by CSS. - -CSS properties | Action ------------------------------|------------------------------------------- -`position` set | Element is not centered horizontally or vertically -`top` or `bottom` set | Element is not vertically centered -`left` or `right` set | Element is not horizontally centered -`max-height` or `height` set | Element respects `max-height` or `height` -`max-width` or `width` set | Element respects `max-width` or `width` - -@demo demo/index.html -@polymerBehavior -*/ - - Polymer.IronFitBehavior = { - - properties: { - - /** - * The element that will receive a `max-height`/`width`. By default it is the same as `this`, - * but it can be set to a child element. This is useful, for example, for implementing a - * scrolling region inside the element. - * @type {!Element} - */ - sizingTarget: { - type: Object, - value: function() { - return this; - } - }, - - /** - * The element to fit `this` into. - */ - fitInto: { - type: Object, - value: window - }, - - /** - * Set to true to auto-fit on attach. - */ - autoFitOnAttach: { - type: Boolean, - value: false - }, - - /** @type {?Object} */ - _fitInfo: { - type: Object - } - - }, - - get _fitWidth() { - var fitWidth; - if (this.fitInto === window) { - fitWidth = this.fitInto.innerWidth; - } else { - fitWidth = this.fitInto.getBoundingClientRect().width; - } - return fitWidth; - }, - - get _fitHeight() { - var fitHeight; - if (this.fitInto === window) { - fitHeight = this.fitInto.innerHeight; - } else { - fitHeight = this.fitInto.getBoundingClientRect().height; - } - return fitHeight; - }, - - get _fitLeft() { - var fitLeft; - if (this.fitInto === window) { - fitLeft = 0; - } else { - fitLeft = this.fitInto.getBoundingClientRect().left; - } - return fitLeft; - }, - - get _fitTop() { - var fitTop; - if (this.fitInto === window) { - fitTop = 0; - } else { - fitTop = this.fitInto.getBoundingClientRect().top; - } - return fitTop; - }, - - attached: function() { - if (this.autoFitOnAttach) { - if (window.getComputedStyle(this).display === 'none') { - setTimeout(function() { - this.fit(); - }.bind(this)); - } else { - this.fit(); - } - } - }, - - /** - * Fits and optionally centers the element into the window, or `fitInfo` if specified. - */ - fit: function() { - this._discoverInfo(); - this.constrain(); - this.center(); - }, - - /** - * Memoize information needed to position and size the target element. - */ - _discoverInfo: function() { - if (this._fitInfo) { - return; - } - var target = window.getComputedStyle(this); - var sizer = window.getComputedStyle(this.sizingTarget); - this._fitInfo = { - inlineStyle: { - top: this.style.top || '', - left: this.style.left || '' - }, - positionedBy: { - vertically: target.top !== 'auto' ? 'top' : (target.bottom !== 'auto' ? - 'bottom' : null), - horizontally: target.left !== 'auto' ? 'left' : (target.right !== 'auto' ? - 'right' : null), - css: target.position - }, - sizedBy: { - height: sizer.maxHeight !== 'none', - width: sizer.maxWidth !== 'none' - }, - margin: { - top: parseInt(target.marginTop, 10) || 0, - right: parseInt(target.marginRight, 10) || 0, - bottom: parseInt(target.marginBottom, 10) || 0, - left: parseInt(target.marginLeft, 10) || 0 - } - }; - }, - - /** - * Resets the target element's position and size constraints, and clear - * the memoized data. - */ - resetFit: function() { - if (!this._fitInfo || !this._fitInfo.sizedBy.height) { - this.sizingTarget.style.maxHeight = ''; - this.style.top = this._fitInfo ? this._fitInfo.inlineStyle.top : ''; - } - if (!this._fitInfo || !this._fitInfo.sizedBy.width) { - this.sizingTarget.style.maxWidth = ''; - this.style.left = this._fitInfo ? this._fitInfo.inlineStyle.left : ''; - } - if (this._fitInfo) { - this.style.position = this._fitInfo.positionedBy.css; - } - this._fitInfo = null; - }, - - /** - * Equivalent to calling `resetFit()` and `fit()`. Useful to call this after the element, - * the window, or the `fitInfo` element has been resized. - */ - refit: function() { - this.resetFit(); - this.fit(); - }, - - /** - * Constrains the size of the element to the window or `fitInfo` by setting `max-height` - * and/or `max-width`. - */ - constrain: function() { - var info = this._fitInfo; - // position at (0px, 0px) if not already positioned, so we can measure the natural size. - if (!this._fitInfo.positionedBy.vertically) { - this.style.top = '0px'; - } - if (!this._fitInfo.positionedBy.horizontally) { - this.style.left = '0px'; - } - if (!this._fitInfo.positionedBy.vertically || !this._fitInfo.positionedBy.horizontally) { - // need position:fixed to properly size the element - this.style.position = 'fixed'; - } - // need border-box for margin/padding - this.sizingTarget.style.boxSizing = 'border-box'; - // constrain the width and height if not already set - var rect = this.getBoundingClientRect(); - if (!info.sizedBy.height) { - this._sizeDimension(rect, info.positionedBy.vertically, 'top', 'bottom', 'Height'); - } - if (!info.sizedBy.width) { - this._sizeDimension(rect, info.positionedBy.horizontally, 'left', 'right', 'Width'); - } - }, - - _sizeDimension: function(rect, positionedBy, start, end, extent) { - var info = this._fitInfo; - var max = extent === 'Width' ? this._fitWidth : this._fitHeight; - var flip = (positionedBy === end); - var offset = flip ? max - rect[end] : rect[start]; - var margin = info.margin[flip ? start : end]; - var offsetExtent = 'offset' + extent; - var sizingOffset = this[offsetExtent] - this.sizingTarget[offsetExtent]; - this.sizingTarget.style['max' + extent] = (max - margin - offset - sizingOffset) + 'px'; - }, - - /** - * Centers horizontally and vertically if not already positioned. This also sets - * `position:fixed`. - */ - center: function() { - if (!this._fitInfo.positionedBy.vertically || !this._fitInfo.positionedBy.horizontally) { - // need position:fixed to center - this.style.position = 'fixed'; - } - if (!this._fitInfo.positionedBy.vertically) { - var top = (this._fitHeight - this.offsetHeight) / 2 + this._fitTop; - top -= this._fitInfo.margin.top; - this.style.top = top + 'px'; - } - if (!this._fitInfo.positionedBy.horizontally) { - var left = (this._fitWidth - this.offsetWidth) / 2 + this._fitLeft; - left -= this._fitInfo.margin.left; - this.style.left = left + 'px'; - } - } - - }; - -</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><script> - - Polymer.IronOverlayManager = { - - _overlays: [], - - // iframes have a default z-index of 100, so this default should be at least - // that. - _minimumZ: 101, - - _backdrops: [], - - _applyOverlayZ: function(overlay, aboveZ) { - this._setZ(overlay, aboveZ + 2); - }, - - _setZ: function(element, z) { - element.style.zIndex = z; - }, - - // track overlays for z-index and focus managemant - addOverlay: function(overlay) { - var minimumZ = Math.max(this.currentOverlayZ(), this._minimumZ); - this._overlays.push(overlay); - var newZ = this.currentOverlayZ(); - if (newZ <= minimumZ) { - this._applyOverlayZ(overlay, minimumZ); - } - }, - - removeOverlay: function(overlay) { - var i = this._overlays.indexOf(overlay); - if (i >= 0) { - this._overlays.splice(i, 1); - this._setZ(overlay, ''); - } - }, - - currentOverlay: function() { - var i = this._overlays.length - 1; - while (this._overlays[i] && !this._overlays[i].opened) { - --i; - } - return this._overlays[i]; - }, - - currentOverlayZ: function() { - var z = this._minimumZ; - var current = this.currentOverlay(); - if (current) { - var z1 = window.getComputedStyle(current).zIndex; - if (!isNaN(z1)) { - z = Number(z1); - } - } - return z; - }, - - /** - * Ensures that the minimum z-index of new overlays is at least `minimumZ`. - * This does not effect the z-index of any existing overlays. - * - * @param {number} minimumZ - */ - ensureMinimumZ: function(minimumZ) { - this._minimumZ = Math.max(this._minimumZ, minimumZ); - }, - - focusOverlay: function() { - var current = this.currentOverlay(); - // We have to be careful to focus the next overlay _after_ any current - // transitions are complete (due to the state being toggled prior to the - // transition). Otherwise, we risk infinite recursion when a transitioning - // (closed) overlay becomes the current overlay. - // - // NOTE: We make the assumption that any overlay that completes a transition - // will call into focusOverlay to kick the process back off. Currently: - // transitionend -> _applyFocus -> focusOverlay. - if (current && !current.transitioning) { - current._applyFocus(); - } - }, - - trackBackdrop: function(element) { - // backdrops contains the overlays with a backdrop that are currently - // visible - if (element.opened) { - this._backdrops.push(element); - } else { - var index = this._backdrops.indexOf(element); - if (index >= 0) { - this._backdrops.splice(index, 1); - } - } - }, - - getBackdrops: function() { - return this._backdrops; - } - - }; - -</script><script> - -/** -Use `Polymer.IronOverlayBehavior` to implement an element that can be hidden or shown, and displays -on top of other content. It includes an optional backdrop, and can be used to implement a variety -of UI controls including dialogs and drop downs. Multiple overlays may be displayed at once. - -### Closing and canceling - -A dialog may be hidden by closing or canceling. The difference between close and cancel is user -intent. Closing generally implies that the user acknowledged the content on the overlay. By default, -it will cancel whenever the user taps outside it or presses the escape key. This behavior is -configurable with the `no-cancel-on-esc-key` and the `no-cancel-on-outside-click` properties. -`close()` should be called explicitly by the implementer when the user interacts with a control -in the overlay element. When the dialog is canceled, the overlay fires an 'iron-overlay-canceled' -event. Call `preventDefault` on this event to prevent the overlay from closing. - -### Positioning - -By default the element is sized and positioned to fit and centered inside the window. You can -position and size it manually using CSS. See `Polymer.IronFitBehavior`. - -### Backdrop - -Set the `with-backdrop` attribute to display a backdrop behind the overlay. The backdrop is -appended to `<body>` and is of type `<iron-overlay-backdrop>`. See its doc page for styling -options. - -### Limitations - -The element is styled to appear on top of other content by setting its `z-index` property. You -must ensure no element has a stacking context with a higher `z-index` than its parent stacking -context. You should place this element as a child of `<body>` whenever possible. - -@demo demo/index.html -@polymerBehavior Polymer.IronOverlayBehavior -*/ - - Polymer.IronOverlayBehaviorImpl = { - - properties: { - - /** - * True if the overlay is currently displayed. - */ - opened: { - observer: '_openedChanged', - type: Boolean, - value: false, - notify: true - }, - - /** - * True if the overlay was canceled when it was last closed. - */ - canceled: { - observer: '_canceledChanged', - readOnly: true, - type: Boolean, - value: false - }, - - /** - * Set to true to display a backdrop behind the overlay. - */ - withBackdrop: { - type: Boolean, - value: false - }, - - /** - * Set to true to disable auto-focusing the overlay or child nodes with - * the `autofocus` attribute` when the overlay is opened. - */ - noAutoFocus: { - type: Boolean, - value: false - }, - - /** - * Set to true to disable canceling the overlay with the ESC key. - */ - noCancelOnEscKey: { - type: Boolean, - value: false - }, - - /** - * Set to true to disable canceling the overlay by clicking outside it. - */ - noCancelOnOutsideClick: { - type: Boolean, - value: false - }, - - /** - * Returns the reason this dialog was last closed. - */ - closingReason: { - // was a getter before, but needs to be a property so other - // behaviors can override this. - type: Object - }, - - _manager: { - type: Object, - value: Polymer.IronOverlayManager - }, - - _boundOnCaptureClick: { - type: Function, - value: function() { - return this._onCaptureClick.bind(this); - } - }, - - _boundOnCaptureKeydown: { - type: Function, - value: function() { - return this._onCaptureKeydown.bind(this); - } - } - - }, - - listeners: { - 'iron-resize': '_onIronResize' - }, - - /** - * The backdrop element. - * @type Node - */ - get backdropElement() { - return this._backdrop; - }, - - get _focusNode() { - return Polymer.dom(this).querySelector('[autofocus]') || this; - }, - - registered: function() { - this._backdrop = document.createElement('iron-overlay-backdrop'); - }, - - ready: function() { - this._ensureSetup(); - }, - - attached: function() { - // Call _openedChanged here so that position can be computed correctly. - if (this._callOpenedWhenReady) { - this._openedChanged(); - } - }, - - detached: function() { - this.opened = false; - this._completeBackdrop(); - this._manager.removeOverlay(this); - }, - - /** - * Toggle the opened state of the overlay. - */ - toggle: function() { - this.opened = !this.opened; - }, - - /** - * Open the overlay. - */ - open: function() { - this.opened = true; - this.closingReason = {canceled: false}; - }, - - /** - * Close the overlay. - */ - close: function() { - this.opened = false; - this._setCanceled(false); - }, - - /** - * Cancels the overlay. - */ - cancel: function() { - var cancelEvent = this.fire('iron-overlay-canceled', undefined, {cancelable: true}); - if (cancelEvent.defaultPrevented) { - return; - } - - this.opened = false; - this._setCanceled(true); - }, - - _ensureSetup: function() { - if (this._overlaySetup) { - return; - } - this._overlaySetup = true; - this.style.outline = 'none'; - this.style.display = 'none'; - }, - - _openedChanged: function() { - if (this.opened) { - this.removeAttribute('aria-hidden'); - } else { - this.setAttribute('aria-hidden', 'true'); - } - - // wait to call after ready only if we're initially open - if (!this._overlaySetup) { - this._callOpenedWhenReady = this.opened; - return; - } - if (this._openChangedAsync) { - this.cancelAsync(this._openChangedAsync); - } - - this._toggleListeners(); - - if (this.opened) { - this._prepareRenderOpened(); - } - - // async here to allow overlay layer to become visible. - this._openChangedAsync = this.async(function() { - // overlay becomes visible here - this.style.display = ''; - // force layout to ensure transitions will go - /** @suppress {suspiciousCode} */ this.offsetWidth; - if (this.opened) { - this._renderOpened(); - } else { - this._renderClosed(); - } - this._openChangedAsync = null; - }); - - }, - - _canceledChanged: function() { - this.closingReason = this.closingReason || {}; - this.closingReason.canceled = this.canceled; - }, - - _toggleListener: function(enable, node, event, boundListener, capture) { - if (enable) { - // enable document-wide tap recognizer - if (event === 'tap') { - Polymer.Gestures.add(document, 'tap', null); - } - node.addEventListener(event, boundListener, capture); - } else { - // disable document-wide tap recognizer - if (event === 'tap') { - Polymer.Gestures.remove(document, 'tap', null); - } - node.removeEventListener(event, boundListener, capture); - } - }, - - _toggleListeners: function() { - if (this._toggleListenersAsync) { - this.cancelAsync(this._toggleListenersAsync); - } - // async so we don't auto-close immediately via a click. - this._toggleListenersAsync = this.async(function() { - this._toggleListener(this.opened, document, 'tap', this._boundOnCaptureClick, true); - this._toggleListener(this.opened, document, 'keydown', this._boundOnCaptureKeydown, true); - this._toggleListenersAsync = null; - }, 1); - }, - - // tasks which must occur before opening; e.g. making the element visible - _prepareRenderOpened: function() { - this._manager.addOverlay(this); - - if (this.withBackdrop) { - this.backdropElement.prepare(); - this._manager.trackBackdrop(this); - } - - this._preparePositioning(); - this.fit(); - this._finishPositioning(); - }, - - // tasks which cause the overlay to actually open; typically play an - // animation - _renderOpened: function() { - if (this.withBackdrop) { - this.backdropElement.open(); - } - this._finishRenderOpened(); - }, - - _renderClosed: function() { - if (this.withBackdrop) { - this.backdropElement.close(); - } - this._finishRenderClosed(); - }, - - _onTransitionend: function(event) { - // make sure this is our transition event. - if (event && event.target !== this) { - return; - } - if (this.opened) { - this._finishRenderOpened(); - } else { - this._finishRenderClosed(); - } - }, - - _finishRenderOpened: function() { - // focus the child node with [autofocus] - if (!this.noAutoFocus) { - this._focusNode.focus(); - } - - this.fire('iron-overlay-opened'); - - this._squelchNextResize = true; - this.async(this.notifyResize); - }, - - _finishRenderClosed: function() { - // hide the overlay and remove the backdrop - this.resetFit(); - this.style.display = 'none'; - this._completeBackdrop(); - this._manager.removeOverlay(this); - - this._focusNode.blur(); - // focus the next overlay, if there is one - this._manager.focusOverlay(); - - this.fire('iron-overlay-closed', this.closingReason); - - this._squelchNextResize = true; - this.async(this.notifyResize); - }, - - _completeBackdrop: function() { - if (this.withBackdrop) { - this._manager.trackBackdrop(this); - this.backdropElement.complete(); - } - }, - - _preparePositioning: function() { - this.style.transition = this.style.webkitTransition = 'none'; - this.style.transform = this.style.webkitTransform = 'none'; - this.style.display = ''; - }, - - _finishPositioning: function() { - this.style.display = 'none'; - this.style.transform = this.style.webkitTransform = ''; - // force layout to avoid application of transform - /** @suppress {suspiciousCode} */ this.offsetWidth; - this.style.transition = this.style.webkitTransition = ''; - }, - - _applyFocus: function() { - if (this.opened) { - if (!this.noAutoFocus) { - this._focusNode.focus(); - } - } else { - this._focusNode.blur(); - this._manager.focusOverlay(); - } - }, - - _onCaptureClick: function(event) { - if (!this.noCancelOnOutsideClick && - this._manager.currentOverlay() === this && - Polymer.dom(event).path.indexOf(this) === -1) { - this.cancel(); - } - }, - - _onCaptureKeydown: function(event) { - var ESC = 27; - if (!this.noCancelOnEscKey && (event.keyCode === ESC)) { - this.cancel(); - event.stopPropagation(); - event.stopImmediatePropagation(); - } - }, - - _onIronResize: function() { - if (this._squelchNextResize) { - this._squelchNextResize = false; - return; - } - if (this.opened) { - this.refit(); - } - } - -/** - * Fired after the `iron-overlay` opens. - * @event iron-overlay-opened - */ - -/** - * Fired when the `iron-overlay` is canceled, but before it is closed. - * Cancel the event to prevent the `iron-overlay` from closing. - * @event iron-overlay-canceled - */ - -/** - * Fired after the `iron-overlay` closes. - * @event iron-overlay-closed - * @param {{canceled: (boolean|undefined)}} set to the `closingReason` attribute - */ - }; - - /** @polymerBehavior */ - Polymer.IronOverlayBehavior = [Polymer.IronFitBehavior, Polymer.IronResizableBehavior, Polymer.IronOverlayBehaviorImpl]; - - -</script><style is=custom-style> - - :root { - - /* Shared Styles */ - --paper-font-common-base: { - font-family: 'Roboto', 'Noto', sans-serif; - -webkit-font-smoothing: antialiased; - }; - - --paper-font-common-code: { - font-family: 'Roboto Mono', 'Consolas', 'Menlo', monospace; - -webkit-font-smoothing: antialiased; - }; - - --paper-font-common-expensive-kerning: { - text-rendering: optimizeLegibility; - }; - - --paper-font-common-nowrap: { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - }; - - /* Material Font Styles */ - - --paper-font-display4: { - @apply(--paper-font-common-base); - @apply(--paper-font-common-nowrap); - - font-size: 112px; - font-weight: 300; - letter-spacing: -.044em; - line-height: 120px; - }; - - --paper-font-display3: { - @apply(--paper-font-common-base); - @apply(--paper-font-common-nowrap); - - font-size: 56px; - font-weight: 400; - letter-spacing: -.026em; - line-height: 60px; - }; - - --paper-font-display2: { - @apply(--paper-font-common-base); - - font-size: 45px; - font-weight: 400; - letter-spacing: -.018em; - line-height: 48px; - }; - - --paper-font-display1: { - @apply(--paper-font-common-base); - - font-size: 34px; - font-weight: 400; - letter-spacing: -.01em; - line-height: 40px; - }; - - --paper-font-headline: { - @apply(--paper-font-common-base); - - font-size: 24px; - font-weight: 400; - letter-spacing: -.012em; - line-height: 32px; - }; - - --paper-font-title: { - @apply(--paper-font-common-base); - @apply(--paper-font-common-nowrap); - - font-size: 20px; - font-weight: 500; - line-height: 28px; - }; - - --paper-font-subhead: { - @apply(--paper-font-common-base); - - font-size: 16px; - font-weight: 400; - line-height: 24px; - }; - - --paper-font-body2: { - @apply(--paper-font-common-base); - - font-size: 14px; - font-weight: 500; - line-height: 24px; - }; - - --paper-font-body1: { - @apply(--paper-font-common-base); - - font-size: 14px; - font-weight: 400; - line-height: 20px; - }; - - --paper-font-caption: { - @apply(--paper-font-common-base); - @apply(--paper-font-common-nowrap); - - font-size: 12px; - font-weight: 400; - letter-spacing: 0.011em; - line-height: 20px; - }; - - --paper-font-menu: { - @apply(--paper-font-common-base); - @apply(--paper-font-common-nowrap); - - font-size: 13px; - font-weight: 500; - line-height: 24px; - }; - - --paper-font-button: { - @apply(--paper-font-common-base); - @apply(--paper-font-common-nowrap); - - font-size: 14px; - font-weight: 500; - letter-spacing: 0.018em; - line-height: 24px; - text-transform: uppercase; - }; - - --paper-font-code2: { - @apply(--paper-font-common-code); - - font-size: 14px; - font-weight: 700; - line-height: 20px; - }; - - --paper-font-code1: { - @apply(--paper-font-common-code); - - font-size: 14px; - font-weight: 500; - line-height: 20px; - }; - - } - -</style><script> - - /** - * `iron-range-behavior` provides the behavior for something with a minimum to maximum range. - * - * @demo demo/index.html - * @polymerBehavior - */ - Polymer.IronRangeBehavior = { - - properties: { - - /** - * The number that represents the current value. - */ - value: { - type: Number, - value: 0, - notify: true, - reflectToAttribute: true - }, - - /** - * The number that indicates the minimum value of the range. - */ - min: { - type: Number, - value: 0, - notify: true - }, - - /** - * The number that indicates the maximum value of the range. - */ - max: { - type: Number, - value: 100, - notify: true - }, - - /** - * Specifies the value granularity of the range's value. - */ - step: { - type: Number, - value: 1, - notify: true - }, - - /** - * Returns the ratio of the value. - */ - ratio: { - type: Number, - value: 0, - readOnly: true, - notify: true - }, - }, - - observers: [ - '_update(value, min, max, step)' - ], - - _calcRatio: function(value) { - return (this._clampValue(value) - this.min) / (this.max - this.min); - }, - - _clampValue: function(value) { - return Math.min(this.max, Math.max(this.min, this._calcStep(value))); - }, - - _calcStep: function(value) { - /** - * if we calculate the step using - * `Math.round(value / step) * step` we may hit a precision point issue - * eg. 0.1 * 0.2 = 0.020000000000000004 - * http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html - * - * as a work around we can divide by the reciprocal of `step` - */ - // polymer/issues/2493 - value = parseFloat(value); - return this.step ? (Math.round((value + this.min) / this.step) - - (this.min / this.step)) / (1 / this.step) : value; - }, - - _validateValue: function() { - var v = this._clampValue(this.value); - this.value = this.oldValue = isNaN(v) ? this.oldValue : v; - return this.value !== v; - }, - - _update: function() { - this._validateValue(); - this._setRatio(this._calcRatio(this.value) * 100); - } - -}; -</script><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><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; - }; - - /* 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: around; - -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; - }; - - /******************************* - 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><script> - - /** - * @param {!Function} selectCallback - * @constructor - */ - Polymer.IronSelection = function(selectCallback) { - this.selection = []; - this.selectCallback = selectCallback; - }; - - Polymer.IronSelection.prototype = { - - /** - * Retrieves the selected item(s). - * - * @method get - * @returns Returns the selected item(s). If the multi property is true, - * `get` will return an array, otherwise it will return - * the selected item or undefined if there is no selection. - */ - get: function() { - return this.multi ? this.selection.slice() : this.selection[0]; - }, - - /** - * Clears all the selection except the ones indicated. - * - * @method clear - * @param {Array} excludes items to be excluded. - */ - clear: function(excludes) { - this.selection.slice().forEach(function(item) { - if (!excludes || excludes.indexOf(item) < 0) { - this.setItemSelected(item, false); - } - }, this); - }, - - /** - * Indicates if a given item is selected. - * - * @method isSelected - * @param {*} item The item whose selection state should be checked. - * @returns Returns true if `item` is selected. - */ - isSelected: function(item) { - return this.selection.indexOf(item) >= 0; - }, - - /** - * Sets the selection state for a given item to either selected or deselected. - * - * @method setItemSelected - * @param {*} item The item to select. - * @param {boolean} isSelected True for selected, false for deselected. - */ - setItemSelected: function(item, isSelected) { - if (item != null) { - if (isSelected) { - this.selection.push(item); - } else { - var i = this.selection.indexOf(item); - if (i >= 0) { - this.selection.splice(i, 1); - } - } - if (this.selectCallback) { - this.selectCallback(item, isSelected); - } - } - }, - - /** - * Sets the selection state for a given item. If the `multi` property - * is true, then the selected state of `item` will be toggled; otherwise - * the `item` will be selected. - * - * @method select - * @param {*} item The item to select. - */ - select: function(item) { - if (this.multi) { - this.toggle(item); - } else if (this.get() !== item) { - this.setItemSelected(this.get(), false); - this.setItemSelected(item, true); - } - }, - - /** - * Toggles the selection state for `item`. - * - * @method toggle - * @param {*} item The item to toggle. - */ - toggle: function(item) { - this.setItemSelected(item, !this.isSelected(item)); - } - - }; - -</script><script> - - /** @polymerBehavior */ - Polymer.IronSelectableBehavior = { - - /** - * Fired when iron-selector is activated (selected or deselected). - * It is fired before the selected items are changed. - * Cancel the event to abort selection. - * - * @event iron-activate - */ - - /** - * Fired when an item is selected - * - * @event iron-select - */ - - /** - * Fired when an item is deselected - * - * @event iron-deselect - */ - - /** - * Fired when the list of selectable items changes (e.g., items are - * added or removed). The detail of the event is a list of mutation - * records that describe what changed. - * - * @event iron-items-changed - */ - - properties: { - - /** - * If you want to use the attribute value of an element for `selected` instead of the index, - * set this to the name of the attribute. - */ - attrForSelected: { - type: String, - value: null - }, - - /** - * Gets or sets the selected element. The default is to use the index of the item. - */ - selected: { - type: String, - notify: true - }, - - /** - * Returns the currently selected item. - * - * @type {?Object} - */ - selectedItem: { - type: Object, - readOnly: true, - notify: true - }, - - /** - * The event that fires from items when they are selected. Selectable - * will listen for this event from items and update the selection state. - * Set to empty string to listen to no events. - */ - activateEvent: { - type: String, - value: 'tap', - observer: '_activateEventChanged' - }, - - /** - * This is a CSS selector string. If this is set, only items that match the CSS selector - * are selectable. - */ - selectable: String, - - /** - * The class to set on elements when selected. - */ - selectedClass: { - type: String, - value: 'iron-selected' - }, - - /** - * The attribute to set on elements when selected. - */ - selectedAttribute: { - type: String, - value: null - }, - - /** - * The list of items from which a selection can be made. - */ - items: { - type: Array, - readOnly: true, - value: function() { - return []; - } - }, - - /** - * The set of excluded elements where the key is the `localName` - * of the element that will be ignored from the item list. - * - * @default {template: 1} - */ - _excludedLocalNames: { - type: Object, - value: function() { - return { - 'template': 1 - }; - } - } - }, - - observers: [ - '_updateSelected(attrForSelected, selected)' - ], - - created: function() { - this._bindFilterItem = this._filterItem.bind(this); - this._selection = new Polymer.IronSelection(this._applySelection.bind(this)); - }, - - attached: function() { - this._observer = this._observeItems(this); - this._updateItems(); - if (!this._shouldUpdateSelection) { - this._updateSelected(this.attrForSelected,this.selected) - } - this._addListener(this.activateEvent); - }, - - detached: function() { - if (this._observer) { - Polymer.dom(this).unobserveNodes(this._observer); - } - this._removeListener(this.activateEvent); - }, - - /** - * Returns the index of the given item. - * - * @method indexOf - * @param {Object} item - * @returns Returns the index of the item - */ - indexOf: function(item) { - return this.items.indexOf(item); - }, - - /** - * Selects the given value. - * - * @method select - * @param {string} value the value to select. - */ - select: function(value) { - this.selected = value; - }, - - /** - * Selects the previous item. - * - * @method selectPrevious - */ - selectPrevious: function() { - var length = this.items.length; - var index = (Number(this._valueToIndex(this.selected)) - 1 + length) % length; - this.selected = this._indexToValue(index); - }, - - /** - * Selects the next item. - * - * @method selectNext - */ - selectNext: function() { - var index = (Number(this._valueToIndex(this.selected)) + 1) % this.items.length; - this.selected = this._indexToValue(index); - }, - - get _shouldUpdateSelection() { - return this.selected != null; - }, - - _addListener: function(eventName) { - this.listen(this, eventName, '_activateHandler'); - }, - - _removeListener: function(eventName) { - this.unlisten(this, eventName, '_activateHandler'); - }, - - _activateEventChanged: function(eventName, old) { - this._removeListener(old); - this._addListener(eventName); - }, - - _updateItems: function() { - var nodes = Polymer.dom(this).queryDistributedElements(this.selectable || '*'); - nodes = Array.prototype.filter.call(nodes, this._bindFilterItem); - this._setItems(nodes); - }, - - _updateSelected: function() { - this._selectSelected(this.selected); - }, - - _selectSelected: function(selected) { - this._selection.select(this._valueToItem(this.selected)); - }, - - _filterItem: function(node) { - return !this._excludedLocalNames[node.localName]; - }, - - _valueToItem: function(value) { - return (value == null) ? null : this.items[this._valueToIndex(value)]; - }, - - _valueToIndex: function(value) { - if (this.attrForSelected) { - for (var i = 0, item; item = this.items[i]; i++) { - if (this._valueForItem(item) == value) { - return i; - } - } - } else { - return Number(value); - } - }, - - _indexToValue: function(index) { - if (this.attrForSelected) { - var item = this.items[index]; - if (item) { - return this._valueForItem(item); - } - } else { - return index; - } - }, - - _valueForItem: function(item) { - return item[this.attrForSelected] || item.getAttribute(this.attrForSelected); - }, - - _applySelection: function(item, isSelected) { - if (this.selectedClass) { - this.toggleClass(this.selectedClass, isSelected, item); - } - if (this.selectedAttribute) { - this.toggleAttribute(this.selectedAttribute, isSelected, item); - } - this._selectionChange(); - this.fire('iron-' + (isSelected ? 'select' : 'deselect'), {item: item}); - }, - - _selectionChange: function() { - this._setSelectedItem(this._selection.get()); - }, - - // observe items change under the given node. - _observeItems: function(node) { - return Polymer.dom(node).observeNodes(function(mutations) { - // Let other interested parties know about the change so that - // we don't have to recreate mutation observers everywher. - this.fire('iron-items-changed', mutations, { - bubbles: false, - cancelable: false - }); - - this._updateItems(); - - if (this._shouldUpdateSelection) { - this._updateSelected(); - } - }); - }, - - _activateHandler: function(e) { - var t = e.target; - var items = this.items; - while (t && t != this) { - var i = items.indexOf(t); - if (i >= 0) { - var value = this._indexToValue(i); - this._itemActivate(value, t); - return; - } - t = t.parentNode; - } - }, - - _itemActivate: function(value, item) { - if (!this.fire('iron-activate', - {selected: value, item: item}, {cancelable: true}).defaultPrevented) { - this.select(value); - } - } - - }; - -</script><script> - - (function() { - - // monostate data - var metaDatas = {}; - var metaArrays = {}; - var singleton = null; - - Polymer.IronMeta = Polymer({ - - is: 'iron-meta', - - properties: { - - /** - * The type of meta-data. All meta-data of the same type is stored - * together. - */ - type: { - type: String, - value: 'default', - observer: '_typeChanged' - }, - - /** - * The key used to store `value` under the `type` namespace. - */ - key: { - type: String, - observer: '_keyChanged' - }, - - /** - * The meta-data to store or retrieve. - */ - value: { - type: Object, - notify: true, - observer: '_valueChanged' - }, - - /** - * If true, `value` is set to the iron-meta instance itself. - */ - self: { - type: Boolean, - observer: '_selfChanged' - }, - - /** - * Array of all meta-data values for the given type. - */ - list: { - type: Array, - notify: true - } - - }, - - hostAttributes: { - hidden: true - }, - - /** - * Only runs if someone invokes the factory/constructor directly - * e.g. `new Polymer.IronMeta()` - * - * @param {{type: (string|undefined), key: (string|undefined), value}=} config - */ - factoryImpl: function(config) { - if (config) { - for (var n in config) { - switch(n) { - case 'type': - case 'key': - case 'value': - this[n] = config[n]; - break; - } - } - } - }, - - created: function() { - // TODO(sjmiles): good for debugging? - this._metaDatas = metaDatas; - this._metaArrays = metaArrays; - }, - - _keyChanged: function(key, old) { - this._resetRegistration(old); - }, - - _valueChanged: function(value) { - this._resetRegistration(this.key); - }, - - _selfChanged: function(self) { - if (self) { - this.value = this; - } - }, - - _typeChanged: function(type) { - this._unregisterKey(this.key); - if (!metaDatas[type]) { - metaDatas[type] = {}; - } - this._metaData = metaDatas[type]; - if (!metaArrays[type]) { - metaArrays[type] = []; - } - this.list = metaArrays[type]; - this._registerKeyValue(this.key, this.value); - }, - - /** - * Retrieves meta data value by key. - * - * @method byKey - * @param {string} key The key of the meta-data to be returned. - * @return {*} - */ - byKey: function(key) { - return this._metaData && this._metaData[key]; - }, - - _resetRegistration: function(oldKey) { - this._unregisterKey(oldKey); - this._registerKeyValue(this.key, this.value); - }, - - _unregisterKey: function(key) { - this._unregister(key, this._metaData, this.list); - }, - - _registerKeyValue: function(key, value) { - this._register(key, value, this._metaData, this.list); - }, - - _register: function(key, value, data, list) { - if (key && data && value !== undefined) { - data[key] = value; - list.push(value); - } - }, - - _unregister: function(key, data, list) { - if (key && data) { - if (key in data) { - var value = data[key]; - delete data[key]; - this.arrayDelete(list, value); - } - } - } - - }); - - Polymer.IronMeta.getIronMeta = function getIronMeta() { - if (singleton === null) { - singleton = new Polymer.IronMeta(); - } - return singleton; - }; - - /** - `iron-meta-query` can be used to access infomation stored in `iron-meta`. - - Examples: - - If I create an instance like this: - - <iron-meta key="info" value="foo/bar"></iron-meta> - - Note that value="foo/bar" is the metadata I've defined. I could define more - attributes or use child nodes to define additional metadata. - - Now I can access that element (and it's metadata) from any `iron-meta-query` instance: - - var value = new Polymer.IronMetaQuery({key: 'info'}).value; - - @group Polymer Iron Elements - @element iron-meta-query - */ - Polymer.IronMetaQuery = Polymer({ - - is: 'iron-meta-query', - - properties: { - - /** - * The type of meta-data. All meta-data of the same type is stored - * together. - */ - type: { - type: String, - value: 'default', - observer: '_typeChanged' - }, - - /** - * Specifies a key to use for retrieving `value` from the `type` - * namespace. - */ - key: { - type: String, - observer: '_keyChanged' - }, - - /** - * The meta-data to store or retrieve. - */ - value: { - type: Object, - notify: true, - readOnly: true - }, - - /** - * Array of all meta-data values for the given type. - */ - list: { - type: Array, - notify: true - } - - }, - - /** - * Actually a factory method, not a true constructor. Only runs if - * someone invokes it directly (via `new Polymer.IronMeta()`); - * - * @param {{type: (string|undefined), key: (string|undefined)}=} config - */ - factoryImpl: function(config) { - if (config) { - for (var n in config) { - switch(n) { - case 'type': - case 'key': - this[n] = config[n]; - break; - } - } - } - }, - - created: function() { - // TODO(sjmiles): good for debugging? - this._metaDatas = metaDatas; - this._metaArrays = metaArrays; - }, - - _keyChanged: function(key) { - this._setValue(this._metaData && this._metaData[key]); - }, - - _typeChanged: function(type) { - this._metaData = metaDatas[type]; - this.list = metaArrays[type]; - if (this.key) { - this._keyChanged(this.key); - } - }, - - /** - * Retrieves meta data value by key. - * @param {string} key The key of the meta-data to be returned. - * @return {*} - */ - byKey: function(key) { - return this._metaData && this._metaData[key]; - } - - }); - - })(); -</script><script> - - /** - * Use `Polymer.NeonAnimationBehavior` to implement an animation. - * @polymerBehavior - */ - Polymer.NeonAnimationBehavior = { - - properties: { - - /** - * Defines the animation timing. - */ - animationTiming: { - type: Object, - value: function() { - return { - duration: 500, - easing: 'cubic-bezier(0.4, 0, 0.2, 1)', - fill: 'both' - } - } - } - - }, - - registered: function() { - new Polymer.IronMeta({type: 'animation', key: this.is, value: this.constructor}); - }, - - /** - * Do any animation configuration here. - */ - // configure: function(config) { - // }, - - /** - * Returns the animation timing by mixing in properties from `config` to the defaults defined - * by the animation. - */ - timingFromConfig: function(config) { - if (config.timing) { - for (var property in config.timing) { - this.animationTiming[property] = config.timing[property]; - } - } - return this.animationTiming; - }, - - /** - * Sets `transform` and `transformOrigin` properties along with the prefixed versions. - */ - setPrefixedProperty: function(node, property, value) { - var map = { - 'transform': ['webkitTransform'], - 'transformOrigin': ['mozTransformOrigin', 'webkitTransformOrigin'] - }; - var prefixes = map[property]; - for (var prefix, index = 0; prefix = prefixes[index]; index++) { - node.style[prefix] = value; - } - node.style[property] = value; - }, - - /** - * Called when the animation finishes. - */ - complete: function() {} - - }; - -</script><script>// Copyright 2014 Google Inc. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -!function(a,b){var c={},d={},e={},f=null;!function(a,b){function c(a){if("number"==typeof a)return a;var b={};for(var c in a)b[c]=a[c];return b}function d(){this._delay=0,this._endDelay=0,this._fill="none",this._iterationStart=0,this._iterations=1,this._duration=0,this._playbackRate=1,this._direction="normal",this._easing="linear"}function e(b,c,e){var f=new d;return c&&(f.fill="both",f.duration="auto"),"number"!=typeof b||isNaN(b)?void 0!==b&&Object.getOwnPropertyNames(b).forEach(function(c){if("auto"!=b[c]){if(("number"==typeof f[c]||"duration"==c)&&("number"!=typeof b[c]||isNaN(b[c])))return;if("fill"==c&&-1==t.indexOf(b[c]))return;if("direction"==c&&-1==u.indexOf(b[c]))return;if("playbackRate"==c&&1!==b[c]&&a.isDeprecated("AnimationEffectTiming.playbackRate","2014-11-28","Use Animation.playbackRate instead."))return;f[c]=b[c]}}):f.duration=b,f}function f(a){return"number"==typeof a&&(a=isNaN(a)?{duration:0}:{duration:a}),a}function g(b,c){b=a.numericTimingToObject(b);var d=e(b,c);return d._easingFunction=j(d.easing),d}function h(a,b,c,d){return 0>a||a>1||0>c||c>1?D:function(e){function f(a,b,c){return 3*a*(1-c)*(1-c)*c+3*b*(1-c)*c*c+c*c*c}if(0==e||1==e)return e;for(var g=0,h=1;;){var i=(g+h)/2,j=f(a,c,i);if(Math.abs(e-j)<.001)return f(b,d,i);e>j?g=i:h=i}}}function i(a,b){return function(c){if(c>=1)return 1;var d=1/a;return c+=b*d,c-c%d}}function j(a){z||(z=document.createElement("div").style),z.animationTimingFunction="",z.animationTimingFunction=a,a=z.animationTimingFunction;var b=B.exec(a);if(b)return h.apply(this,b.slice(1).map(Number));var c=C.exec(a);if(c)return i(Number(c[1]),{start:v,middle:w,end:x}[c[2]]);var d=y[a];return d?d:D}function k(a){return Math.abs(l(a)/a.playbackRate)}function l(a){return a.duration*a.iterations}function m(a,b,c){return null==b?E:b<c.delay?F:b>=c.delay+a?G:H}function n(a,b,c,d,e){switch(d){case F:return"backwards"==b||"both"==b?0:null;case H:return c-e;case G:return"forwards"==b||"both"==b?a:null;case E:return null}}function o(a,b,c,d){return(d.playbackRate<0?b-a:b)*d.playbackRate+c}function p(a,b,c,d,e){return c===1/0||c===-(1/0)||c-d==b&&e.iterations&&(e.iterations+e.iterationStart)%1==0?a:c%a}function q(a,b,c,d){return 0===c?0:b==a?d.iterationStart+d.iterations-1:Math.floor(c/a)}function r(a,b,c,d){var e=a%2>=1,f="normal"==d.direction||d.direction==(e?"alternate-reverse":"alternate"),g=f?c:b-c,h=g/b;return b*d._easingFunction(h)}function s(a,b,c){var d=m(a,b,c),e=n(a,c.fill,b,d,c.delay);if(null===e)return null;if(0===a)return d===F?0:1;var f=c.iterationStart*c.duration,g=o(a,e,f,c),h=p(c.duration,l(c),g,f,c),i=q(c.duration,h,g,c);return r(i,c.duration,h,c)/c.duration}var t="backwards|forwards|both|none".split("|"),u="reverse|alternate|alternate-reverse".split("|");d.prototype={_setMember:function(b,c){this["_"+b]=c,this._effect&&(this._effect._timingInput[b]=c,this._effect._timing=a.normalizeTimingInput(a.normalizeTimingInput(this._effect._timingInput)),this._effect.activeDuration=a.calculateActiveDuration(this._effect._timing),this._effect._animation&&this._effect._animation._rebuildUnderlyingAnimation())},get playbackRate(){return this._playbackRate},set delay(a){this._setMember("delay",a)},get delay(){return this._delay},set endDelay(a){this._setMember("endDelay",a)},get endDelay(){return this._endDelay},set fill(a){this._setMember("fill",a)},get fill(){return this._fill},set iterationStart(a){this._setMember("iterationStart",a)},get iterationStart(){return this._iterationStart},set duration(a){this._setMember("duration",a)},get duration(){return this._duration},set direction(a){this._setMember("direction",a)},get direction(){return this._direction},set easing(a){this._setMember("easing",a)},get easing(){return this._easing},set iterations(a){this._setMember("iterations",a)},get iterations(){return this._iterations}};var v=1,w=.5,x=0,y={ease:h(.25,.1,.25,1),"ease-in":h(.42,0,1,1),"ease-out":h(0,0,.58,1),"ease-in-out":h(.42,0,.58,1),"step-start":i(1,v),"step-middle":i(1,w),"step-end":i(1,x)},z=null,A="\\s*(-?\\d+\\.?\\d*|-?\\.\\d+)\\s*",B=new RegExp("cubic-bezier\\("+A+","+A+","+A+","+A+"\\)"),C=/steps\(\s*(\d+)\s*,\s*(start|middle|end)\s*\)/,D=function(a){return a},E=0,F=1,G=2,H=3;a.cloneTimingInput=c,a.makeTiming=e,a.numericTimingToObject=f,a.normalizeTimingInput=g,a.calculateActiveDuration=k,a.calculateTimeFraction=s,a.calculatePhase=m,a.toTimingFunction=j}(c,f),function(a,b){function c(a,b){return a in j?j[a][b]||b:b}function d(a,b,d){var e=g[a];if(e){h.style[a]=b;for(var f in e){var i=e[f],j=h.style[i];d[i]=c(i,j)}}else d[a]=c(a,b)}function e(a){var b=[];for(var c in a)if(!(c in["easing","offset","composite"])){var d=a[c];Array.isArray(d)||(d=[d]);for(var e,f=d.length,g=0;f>g;g++)e={},"offset"in a?e.offset=a.offset:1==f?e.offset=1:e.offset=g/(f-1),"easing"in a&&(e.easing=a.easing),"composite"in a&&(e.composite=a.composite),e[c]=d[g],b.push(e)}return b.sort(function(a,b){return a.offset-b.offset}),b}function f(a){function b(){var a=c.length;null==c[a-1].offset&&(c[a-1].offset=1),a>1&&null==c[0].offset&&(c[0].offset=0);for(var b=0,d=c[0].offset,e=1;a>e;e++){var f=c[e].offset;if(null!=f){for(var g=1;e-b>g;g++)c[b+g].offset=d+(f-d)*g/(e-b);b=e,d=f}}}if(null==a)return[];window.Symbol&&Symbol.iterator&&Array.prototype.from&&a[Symbol.iterator]&&(a=Array.from(a)),Array.isArray(a)||(a=e(a));for(var c=a.map(function(a){var b={};for(var c in a){var e=a[c];if("offset"==c){if(null!=e&&(e=Number(e),!isFinite(e)))throw new TypeError("keyframe offsets must be numbers.")}else{if("composite"==c)throw{type:DOMException.NOT_SUPPORTED_ERR,name:"NotSupportedError",message:"add compositing is not supported"};e=""+e}d(c,e,b)}return void 0==b.offset&&(b.offset=null),b}),f=!0,g=-(1/0),h=0;h<c.length;h++){var i=c[h].offset;if(null!=i){if(g>i)throw{code:DOMException.INVALID_MODIFICATION_ERR,name:"InvalidModificationError",message:"Keyframes are not loosely sorted by offset. Sort or specify offsets."};g=i}else f=!1}return c=c.filter(function(a){return a.offset>=0&&a.offset<=1}),f||b(),c}var g={background:["backgroundImage","backgroundPosition","backgroundSize","backgroundRepeat","backgroundAttachment","backgroundOrigin","backgroundClip","backgroundColor"],border:["borderTopColor","borderTopStyle","borderTopWidth","borderRightColor","borderRightStyle","borderRightWidth","borderBottomColor","borderBottomStyle","borderBottomWidth","borderLeftColor","borderLeftStyle","borderLeftWidth"],borderBottom:["borderBottomWidth","borderBottomStyle","borderBottomColor"],borderColor:["borderTopColor","borderRightColor","borderBottomColor","borderLeftColor"],borderLeft:["borderLeftWidth","borderLeftStyle","borderLeftColor"],borderRadius:["borderTopLeftRadius","borderTopRightRadius","borderBottomRightRadius","borderBottomLeftRadius"],borderRight:["borderRightWidth","borderRightStyle","borderRightColor"],borderTop:["borderTopWidth","borderTopStyle","borderTopColor"],borderWidth:["borderTopWidth","borderRightWidth","borderBottomWidth","borderLeftWidth"],flex:["flexGrow","flexShrink","flexBasis"],font:["fontFamily","fontSize","fontStyle","fontVariant","fontWeight","lineHeight"],margin:["marginTop","marginRight","marginBottom","marginLeft"],outline:["outlineColor","outlineStyle","outlineWidth"],padding:["paddingTop","paddingRight","paddingBottom","paddingLeft"]},h=document.createElementNS("http://www.w3.org/1999/xhtml","div"),i={thin:"1px",medium:"3px",thick:"5px"},j={borderBottomWidth:i,borderLeftWidth:i,borderRightWidth:i,borderTopWidth:i,fontSize:{"xx-small":"60%","x-small":"75%",small:"89%",medium:"100%",large:"120%","x-large":"150%","xx-large":"200%"},fontWeight:{normal:"400",bold:"700"},outlineWidth:i,textShadow:{none:"0px 0px 0px transparent"},boxShadow:{none:"0px 0px 0px 0px transparent"}};a.convertToArrayForm=e,a.normalizeKeyframes=f}(c,f),function(a){var b={};a.isDeprecated=function(a,c,d,e){var f=e?"are":"is",g=new Date,h=new Date(c);return h.setMonth(h.getMonth()+3),h>g?(a in b||console.warn("Web Animations: "+a+" "+f+" deprecated and will stop working on "+h.toDateString()+". "+d),b[a]=!0,!1):!0},a.deprecated=function(b,c,d,e){var f=e?"are":"is";if(a.isDeprecated(b,c,d,e))throw new Error(b+" "+f+" no longer supported. "+d)}}(c),function(){if(document.documentElement.animate){var a=document.documentElement.animate([],0),b=!0;if(a&&(b=!1,"play|currentTime|pause|reverse|playbackRate|cancel|finish|startTime|playState".split("|").forEach(function(c){void 0===a[c]&&(b=!0)})),!b)return}!function(a,b,c){function d(a){for(var b={},c=0;c<a.length;c++)for(var d in a[c])if("offset"!=d&&"easing"!=d&&"composite"!=d){var e={offset:a[c].offset,easing:a[c].easing,value:a[c][d]};b[d]=b[d]||[],b[d].push(e)}for(var f in b){var g=b[f];if(0!=g[0].offset||1!=g[g.length-1].offset)throw{type:DOMException.NOT_SUPPORTED_ERR,name:"NotSupportedError",message:"Partial keyframes are not supported"}}return b}function e(c){var d=[];for(var e in c)for(var f=c[e],g=0;g<f.length-1;g++){var h=f[g].offset,i=f[g+1].offset,j=f[g].value,k=f[g+1].value,l=f[g].easing;h==i&&(1==i?j=k:k=j),d.push({startTime:h,endTime:i,easing:a.toTimingFunction(l?l:"linear"),property:e,interpolation:b.propertyInterpolation(e,j,k)})}return d.sort(function(a,b){return a.startTime-b.startTime}),d}b.convertEffectInput=function(c){var f=a.normalizeKeyframes(c),g=d(f),h=e(g);return function(a,c){if(null!=c)h.filter(function(a){return 0>=c&&0==a.startTime||c>=1&&1==a.endTime||c>=a.startTime&&c<=a.endTime}).forEach(function(d){var e=c-d.startTime,f=d.endTime-d.startTime,g=0==f?0:d.easing(e/f);b.apply(a,d.property,d.interpolation(g))});else for(var d in g)"offset"!=d&&"easing"!=d&&"composite"!=d&&b.clear(a,d)}}}(c,d,f),function(a,b,c){function d(a){return a.replace(/-(.)/g,function(a,b){return b.toUpperCase()})}function e(a,b,c){h[c]=h[c]||[],h[c].push([a,b])}function f(a,b,c){for(var f=0;f<c.length;f++){var g=c[f];e(a,b,d(g))}}function g(c,e,f){var g=c;/-/.test(c)&&!a.isDeprecated("Hyphenated property names","2016-03-22","Use camelCase instead.",!0)&&(g=d(c)),"initial"!=e&&"initial"!=f||("initial"==e&&(e=i[g]),"initial"==f&&(f=i[g]));for(var j=e==f?[]:h[g],k=0;j&&k<j.length;k++){var l=j[k][0](e),m=j[k][0](f);if(void 0!==l&&void 0!==m){var n=j[k][1](l,m);if(n){var o=b.Interpolation.apply(null,n);return function(a){return 0==a?e:1==a?f:o(a)}}}}return b.Interpolation(!1,!0,function(a){return a?f:e})}var h={};b.addPropertiesHandler=f;var i={backgroundColor:"transparent",backgroundPosition:"0% 0%",borderBottomColor:"currentColor",borderBottomLeftRadius:"0px",borderBottomRightRadius:"0px",borderBottomWidth:"3px",borderLeftColor:"currentColor",borderLeftWidth:"3px",borderRightColor:"currentColor",borderRightWidth:"3px",borderSpacing:"2px",borderTopColor:"currentColor",borderTopLeftRadius:"0px",borderTopRightRadius:"0px",borderTopWidth:"3px",bottom:"auto",clip:"rect(0px, 0px, 0px, 0px)",color:"black",fontSize:"100%",fontWeight:"400",height:"auto",left:"auto",letterSpacing:"normal",lineHeight:"120%",marginBottom:"0px",marginLeft:"0px",marginRight:"0px",marginTop:"0px",maxHeight:"none",maxWidth:"none",minHeight:"0px",minWidth:"0px",opacity:"1.0",outlineColor:"invert",outlineOffset:"0px",outlineWidth:"3px",paddingBottom:"0px",paddingLeft:"0px",paddingRight:"0px",paddingTop:"0px",right:"auto",textIndent:"0px",textShadow:"0px 0px 0px transparent",top:"auto",transform:"",verticalAlign:"0px",visibility:"visible",width:"auto",wordSpacing:"normal",zIndex:"auto"};b.propertyInterpolation=g}(c,d,f),function(a,b,c){function d(b){var c=a.calculateActiveDuration(b),d=function(d){return a.calculateTimeFraction(c,d,b)};return d._totalDuration=b.delay+c+b.endDelay,d._isCurrent=function(d){var e=a.calculatePhase(c,d,b);return e===PhaseActive||e===PhaseBefore},d}b.KeyframeEffect=function(c,e,f,g){var h,i=d(a.normalizeTimingInput(f)),j=b.convertEffectInput(e),k=function(){j(c,h)};return k._update=function(a){return h=i(a),null!==h},k._clear=function(){j(c,null)},k._hasSameTarget=function(a){return c===a},k._isCurrent=i._isCurrent,k._totalDuration=i._totalDuration,k._id=g,k},b.NullEffect=function(a){var b=function(){a&&(a(),a=null)};return b._update=function(){return null},b._totalDuration=0,b._isCurrent=function(){return!1},b._hasSameTarget=function(){return!1},b}}(c,d,f),function(a,b){a.apply=function(b,c,d){b.style[a.propertyName(c)]=d},a.clear=function(b,c){b.style[a.propertyName(c)]=""}}(d,f),function(a){window.Element.prototype.animate=function(b,c){var d="";return c&&c.id&&(d=c.id),a.timeline._play(a.KeyframeEffect(this,b,c,d))}}(d),function(a,b){function c(a,b,d){if("number"==typeof a&&"number"==typeof b)return a*(1-d)+b*d;if("boolean"==typeof a&&"boolean"==typeof b)return.5>d?a:b;if(a.length==b.length){for(var e=[],f=0;f<a.length;f++)e.push(c(a[f],b[f],d));return e}throw"Mismatched interpolation arguments "+a+":"+b}a.Interpolation=function(a,b,d){return function(e){return d(c(a,b,e))}}}(d,f),function(a,b,c){a.sequenceNumber=0;var d=function(a,b,c){this.target=a,this.currentTime=b,this.timelineTime=c,this.type="finish",this.bubbles=!1,this.cancelable=!1,this.currentTarget=a,this.defaultPrevented=!1,this.eventPhase=Event.AT_TARGET,this.timeStamp=Date.now()};b.Animation=function(b){this.id="",b&&b._id&&(this.id=b._id),this._sequenceNumber=a.sequenceNumber++,this._currentTime=0,this._startTime=null,this._paused=!1,this._playbackRate=1,this._inTimeline=!0,this._finishedFlag=!0,this.onfinish=null,this._finishHandlers=[],this._effect=b,this._inEffect=this._effect._update(0),this._idle=!0,this._currentTimePending=!1},b.Animation.prototype={_ensureAlive:function(){this.playbackRate<0&&0===this.currentTime?this._inEffect=this._effect._update(-1):this._inEffect=this._effect._update(this.currentTime),this._inTimeline||!this._inEffect&&this._finishedFlag||(this._inTimeline=!0,b.timeline._animations.push(this))},_tickCurrentTime:function(a,b){a!=this._currentTime&&(this._currentTime=a,this._isFinished&&!b&&(this._currentTime=this._playbackRate>0?this._totalDuration:0),this._ensureAlive())},get currentTime(){return this._idle||this._currentTimePending?null:this._currentTime},set currentTime(a){a=+a,isNaN(a)||(b.restart(),this._paused||null==this._startTime||(this._startTime=this._timeline.currentTime-a/this._playbackRate),this._currentTimePending=!1,this._currentTime!=a&&(this._tickCurrentTime(a,!0),b.invalidateEffects()))},get startTime(){return this._startTime},set startTime(a){a=+a,isNaN(a)||this._paused||this._idle||(this._startTime=a,this._tickCurrentTime((this._timeline.currentTime-this._startTime)*this.playbackRate),b.invalidateEffects())},get playbackRate(){return this._playbackRate},set playbackRate(a){if(a!=this._playbackRate){var b=this.currentTime;this._playbackRate=a,this._startTime=null,"paused"!=this.playState&&"idle"!=this.playState&&this.play(),null!=b&&(this.currentTime=b)}},get _isFinished(){return!this._idle&&(this._playbackRate>0&&this._currentTime>=this._totalDuration||this._playbackRate<0&&this._currentTime<=0)},get _totalDuration(){return this._effect._totalDuration},get playState(){return this._idle?"idle":null==this._startTime&&!this._paused&&0!=this.playbackRate||this._currentTimePending?"pending":this._paused?"paused":this._isFinished?"finished":"running"},play:function(){this._paused=!1,(this._isFinished||this._idle)&&(this._currentTime=this._playbackRate>0?0:this._totalDuration,this._startTime=null),this._finishedFlag=!1,this._idle=!1,this._ensureAlive(),b.invalidateEffects()},pause:function(){this._isFinished||this._paused||this._idle||(this._currentTimePending=!0),this._startTime=null,this._paused=!0},finish:function(){this._idle||(this.currentTime=this._playbackRate>0?this._totalDuration:0,this._startTime=this._totalDuration-this.currentTime,this._currentTimePending=!1,b.invalidateEffects())},cancel:function(){this._inEffect&&(this._inEffect=!1,this._idle=!0,this._finishedFlag=!0,this.currentTime=0,this._startTime=null,this._effect._update(null),b.invalidateEffects())},reverse:function(){this.playbackRate*=-1,this.play()},addEventListener:function(a,b){"function"==typeof b&&"finish"==a&&this._finishHandlers.push(b)},removeEventListener:function(a,b){if("finish"==a){var c=this._finishHandlers.indexOf(b);c>=0&&this._finishHandlers.splice(c,1)}},_fireEvents:function(a){if(this._isFinished){if(!this._finishedFlag){var b=new d(this,this._currentTime,a),c=this._finishHandlers.concat(this.onfinish?[this.onfinish]:[]);setTimeout(function(){c.forEach(function(a){a.call(b.target,b)})},0),this._finishedFlag=!0}}else this._finishedFlag=!1},_tick:function(a,b){this._idle||this._paused||(null==this._startTime?b&&(this.startTime=a-this._currentTime/this.playbackRate):this._isFinished||this._tickCurrentTime((a-this._startTime)*this.playbackRate)),b&&(this._currentTimePending=!1,this._fireEvents(a))},get _needsTick(){return this.playState in{pending:1,running:1}||!this._finishedFlag}}}(c,d,f),function(a,b,c){function d(a){var b=j;j=[],a<p.currentTime&&(a=p.currentTime),h(a,!0),b.forEach(function(b){b[1](a)}),g(),l=void 0}function e(a,b){return a._sequenceNumber-b._sequenceNumber}function f(){this._animations=[],this.currentTime=window.performance&&performance.now?performance.now():0}function g(){o.forEach(function(a){a()}),o.length=0}function h(a,c){n=!1;var d=b.timeline;d.currentTime=a,d._animations.sort(e),m=!1;var f=d._animations;d._animations=[];var g=[],h=[];f=f.filter(function(b){b._tick(a,c),b._inEffect?h.push(b._effect):g.push(b._effect),b._needsTick&&(m=!0);var d=b._inEffect||b._needsTick;return b._inTimeline=d,d}),o.push.apply(o,g),o.push.apply(o,h),d._animations.push.apply(d._animations,f),m&&requestAnimationFrame(function(){})}var i=window.requestAnimationFrame,j=[],k=0;window.requestAnimationFrame=function(a){var b=k++;return 0==j.length&&i(d),j.push([b,a]),b},window.cancelAnimationFrame=function(a){j.forEach(function(b){b[0]==a&&(b[1]=function(){})})},f.prototype={_play:function(c){c._timing=a.normalizeTimingInput(c.timing);var d=new b.Animation(c);return d._idle=!1,d._timeline=this,this._animations.push(d),b.restart(),b.invalidateEffects(),d}};var l=void 0,m=!1,n=!1;b.restart=function(){return m||(m=!0,requestAnimationFrame(function(){}),n=!0),n},b.invalidateEffects=function(){h(b.timeline.currentTime,!1),g()};var o=[],p=new f;b.timeline=p}(c,d,f),function(a){function b(a,b){var c=a.exec(b);return c?(c=a.ignoreCase?c[0].toLowerCase():c[0],[c,b.substr(c.length)]):void 0}function c(a,b){b=b.replace(/^\s*/,"");var c=a(b);return c?[c[0],c[1].replace(/^\s*/,"")]:void 0}function d(a,d,e){a=c.bind(null,a);for(var f=[];;){var g=a(e);if(!g)return[f,e];if(f.push(g[0]),e=g[1],g=b(d,e),!g||""==g[1])return[f,e];e=g[1]}}function e(a,b){for(var c=0,d=0;d<b.length&&(!/\s|,/.test(b[d])||0!=c);d++)if("("==b[d])c++;else if(")"==b[d]&&(c--,0==c&&d++,0>=c))break;var e=a(b.substr(0,d));return void 0==e?void 0:[e,b.substr(d)]}function f(a,b){for(var c=a,d=b;c&&d;)c>d?c%=d:d%=c;return c=a*b/(c+d)}function g(a){return function(b){var c=a(b);return c&&(c[0]=void 0),c}}function h(a,b){return function(c){var d=a(c);return d?d:[b,c]}}function i(b,c){for(var d=[],e=0;e<b.length;e++){var f=a.consumeTrimmed(b[e],c);if(!f||""==f[0])return;void 0!==f[0]&&d.push(f[0]),c=f[1]}return""==c?d:void 0}function j(a,b,c,d,e){for(var g=[],h=[],i=[],j=f(d.length,e.length),k=0;j>k;k++){var l=b(d[k%d.length],e[k%e.length]);if(!l)return;g.push(l[0]),h.push(l[1]),i.push(l[2])}return[g,h,function(b){var d=b.map(function(a,b){return i[b](a)}).join(c);return a?a(d):d}]}function k(a,b,c){for(var d=[],e=[],f=[],g=0,h=0;h<c.length;h++)if("function"==typeof c[h]){var i=c[h](a[g],b[g++]);d.push(i[0]),e.push(i[1]),f.push(i[2])}else!function(a){d.push(!1),e.push(!1),f.push(function(){return c[a]})}(h);return[d,e,function(a){for(var b="",c=0;c<a.length;c++)b+=f[c](a[c]);return b}]}a.consumeToken=b,a.consumeTrimmed=c,a.consumeRepeated=d,a.consumeParenthesised=e,a.ignore=g,a.optional=h,a.consumeList=i,a.mergeNestedRepeated=j.bind(null,null),a.mergeWrappedNestedRepeated=j,a.mergeList=k}(d),function(a){function b(b){function c(b){var c=a.consumeToken(/^inset/i,b);if(c)return d.inset=!0,c;var c=a.consumeLengthOrPercent(b);if(c)return d.lengths.push(c[0]),c;var c=a.consumeColor(b);return c?(d.color=c[0],c):void 0}var d={inset:!1,lengths:[],color:null},e=a.consumeRepeated(c,/^/,b);return e&&e[0].length?[d,e[1]]:void 0}function c(c){var d=a.consumeRepeated(b,/^,/,c);return d&&""==d[1]?d[0]:void 0}function d(b,c){for(;b.lengths.length<Math.max(b.lengths.length,c.lengths.length);)b.lengths.push({px:0});for(;c.lengths.length<Math.max(b.lengths.length,c.lengths.length);)c.lengths.push({px:0});if(b.inset==c.inset&&!!b.color==!!c.color){for(var d,e=[],f=[[],0],g=[[],0],h=0;h<b.lengths.length;h++){var i=a.mergeDimensions(b.lengths[h],c.lengths[h],2==h);f[0].push(i[0]),g[0].push(i[1]),e.push(i[2])}if(b.color&&c.color){var j=a.mergeColors(b.color,c.color);f[1]=j[0],g[1]=j[1],d=j[2]}return[f,g,function(a){for(var c=b.inset?"inset ":" ",f=0;f<e.length;f++)c+=e[f](a[0][f])+" ";return d&&(c+=d(a[1])),c}]}}function e(b,c,d,e){function f(a){return{inset:a,color:[0,0,0,0],lengths:[{px:0},{px:0},{px:0},{px:0}]}}for(var g=[],h=[],i=0;i<d.length||i<e.length;i++){var j=d[i]||f(e[i].inset),k=e[i]||f(d[i].inset);g.push(j),h.push(k)}return a.mergeNestedRepeated(b,c,g,h)}var f=e.bind(null,d,", ");a.addPropertiesHandler(c,f,["box-shadow","text-shadow"])}(d),function(a,b){function c(a){return a.toFixed(3).replace(".000","")}function d(a,b,c){return Math.min(b,Math.max(a,c))}function e(a){return/^\s*[-+]?(\d*\.)?\d+\s*$/.test(a)?Number(a):void 0}function f(a,b){return[a,b,c]}function g(a,b){return 0!=a?i(0,1/0)(a,b):void 0}function h(a,b){return[a,b,function(a){return Math.round(d(1,1/0,a))}]}function i(a,b){return function(e,f){return[e,f,function(e){return c(d(a,b,e))}]}}function j(a,b){return[a,b,Math.round]}a.clamp=d,a.addPropertiesHandler(e,i(0,1/0),["border-image-width","line-height"]),a.addPropertiesHandler(e,i(0,1),["opacity","shape-image-threshold"]),a.addPropertiesHandler(e,g,["flex-grow","flex-shrink"]),a.addPropertiesHandler(e,h,["orphans","widows"]),a.addPropertiesHandler(e,j,["z-index"]),a.parseNumber=e,a.mergeNumbers=f,a.numberToString=c}(d,f),function(a,b){function c(a,b){return"visible"==a||"visible"==b?[0,1,function(c){return 0>=c?a:c>=1?b:"visible"}]:void 0}a.addPropertiesHandler(String,c,["visibility"])}(d),function(a,b){function c(a){a=a.trim(),f.fillStyle="#000",f.fillStyle=a;var b=f.fillStyle;if(f.fillStyle="#fff",f.fillStyle=a,b==f.fillStyle){f.fillRect(0,0,1,1);var c=f.getImageData(0,0,1,1).data;f.clearRect(0,0,1,1);var d=c[3]/255;return[c[0]*d,c[1]*d,c[2]*d,d]}}function d(b,c){return[b,c,function(b){function c(a){return Math.max(0,Math.min(255,a))}if(b[3])for(var d=0;3>d;d++)b[d]=Math.round(c(b[d]/b[3]));return b[3]=a.numberToString(a.clamp(0,1,b[3])),"rgba("+b.join(",")+")"}]}var e=document.createElementNS("http://www.w3.org/1999/xhtml","canvas");e.width=e.height=1;var f=e.getContext("2d");a.addPropertiesHandler(c,d,["background-color","border-bottom-color","border-left-color","border-right-color","border-top-color","color","outline-color","text-decoration-color"]),a.consumeColor=a.consumeParenthesised.bind(null,c),a.mergeColors=d}(d,f),function(a,b){function c(a,b){if(b=b.trim().toLowerCase(),"0"==b&&"px".search(a)>=0)return{px:0};if(/^[^(]*$|^calc/.test(b)){b=b.replace(/calc\(/g,"(");var c={};b=b.replace(a,function(a){return c[a]=null,"U"+a});for(var d="U("+a.source+")",e=b.replace(/[-+]?(\d*\.)?\d+/g,"N").replace(new RegExp("N"+d,"g"),"D").replace(/\s[+-]\s/g,"O").replace(/\s/g,""),f=[/N\*(D)/g,/(N|D)[*\/]N/g,/(N|D)O\1/g,/\((N|D)\)/g],g=0;g<f.length;)f[g].test(e)?(e=e.replace(f[g],"$1"),g=0):g++;if("D"==e){for(var h in c){var i=eval(b.replace(new RegExp("U"+h,"g"),"").replace(new RegExp(d,"g"),"*0"));if(!isFinite(i))return;c[h]=i}return c}}}function d(a,b){return e(a,b,!0)}function e(b,c,d){var e,f=[];for(e in b)f.push(e);for(e in c)f.indexOf(e)<0&&f.push(e);return b=f.map(function(a){return b[a]||0}),c=f.map(function(a){return c[a]||0}),[b,c,function(b){var c=b.map(function(c,e){return 1==b.length&&d&&(c=Math.max(c,0)),a.numberToString(c)+f[e]}).join(" + ");return b.length>1?"calc("+c+")":c}]}var f="px|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc",g=c.bind(null,new RegExp(f,"g")),h=c.bind(null,new RegExp(f+"|%","g")),i=c.bind(null,/deg|rad|grad|turn/g);a.parseLength=g,a.parseLengthOrPercent=h,a.consumeLengthOrPercent=a.consumeParenthesised.bind(null,h),a.parseAngle=i,a.mergeDimensions=e;var j=a.consumeParenthesised.bind(null,g),k=a.consumeRepeated.bind(void 0,j,/^/),l=a.consumeRepeated.bind(void 0,k,/^,/);a.consumeSizePairList=l;var m=function(a){var b=l(a);return b&&""==b[1]?b[0]:void 0},n=a.mergeNestedRepeated.bind(void 0,d," "),o=a.mergeNestedRepeated.bind(void 0,n,",");a.mergeNonNegativeSizePair=n,a.addPropertiesHandler(m,o,["background-size"]),a.addPropertiesHandler(h,d,["border-bottom-width","border-image-width","border-left-width","border-right-width","border-top-width","flex-basis","font-size","height","line-height","max-height","max-width","outline-width","width"]),a.addPropertiesHandler(h,e,["border-bottom-left-radius","border-bottom-right-radius","border-top-left-radius","border-top-right-radius","bottom","left","letter-spacing","margin-bottom","margin-left","margin-right","margin-top","min-height","min-width","outline-offset","padding-bottom","padding-left","padding-right","padding-top","perspective","right","shape-margin","text-indent","top","vertical-align","word-spacing"])}(d,f),function(a,b){function c(b){return a.consumeLengthOrPercent(b)||a.consumeToken(/^auto/,b)}function d(b){var d=a.consumeList([a.ignore(a.consumeToken.bind(null,/^rect/)),a.ignore(a.consumeToken.bind(null,/^\(/)),a.consumeRepeated.bind(null,c,/^,/),a.ignore(a.consumeToken.bind(null,/^\)/))],b);return d&&4==d[0].length?d[0]:void 0}function e(b,c){return"auto"==b||"auto"==c?[!0,!1,function(d){var e=d?b:c;if("auto"==e)return"auto";var f=a.mergeDimensions(e,e);return f[2](f[0])}]:a.mergeDimensions(b,c)}function f(a){return"rect("+a+")"}var g=a.mergeWrappedNestedRepeated.bind(null,f,e,", ");a.parseBox=d,a.mergeBoxes=g,a.addPropertiesHandler(d,g,["clip"])}(d,f),function(a,b){function c(a){return function(b){var c=0;return a.map(function(a){return a===k?b[c++]:a})}}function d(a){return a}function e(b){if(b=b.toLowerCase().trim(),"none"==b)return[];for(var c,d=/\s*(\w+)\(([^)]*)\)/g,e=[],f=0;c=d.exec(b);){if(c.index!=f)return;f=c.index+c[0].length;var g=c[1],h=n[g];if(!h)return;var i=c[2].split(","),j=h[0];if(j.length<i.length)return;for(var k=[],o=0;o<j.length;o++){var p,q=i[o],r=j[o];if(p=q?{A:function(b){return"0"==b.trim()?m:a.parseAngle(b)},N:a.parseNumber,T:a.parseLengthOrPercent,L:a.parseLength}[r.toUpperCase()](q):{a:m,n:k[0],t:l}[r],void 0===p)return;k.push(p)}if(e.push({t:g,d:k}),d.lastIndex==b.length)return e}}function f(a){return a.toFixed(6).replace(".000000","")}function g(b,c){if(b.decompositionPair!==c){b.decompositionPair=c;var d=a.makeMatrixDecomposition(b)}if(c.decompositionPair!==b){c.decompositionPair=b;var e=a.makeMatrixDecomposition(c)}return null==d[0]||null==e[0]?[[!1],[!0],function(a){return a?c[0].d:b[0].d}]:(d[0].push(0),e[0].push(1),[d,e,function(b){var c=a.quat(d[0][3],e[0][3],b[5]),g=a.composeMatrix(b[0],b[1],b[2],c,b[4]),h=g.map(f).join(",");return h}])}function h(a){return a.replace(/[xy]/,"")}function i(a){return a.replace(/(x|y|z|3d)?$/,"3d")}function j(b,c){var d=a.makeMatrixDecomposition&&!0,e=!1;if(!b.length||!c.length){b.length||(e=!0,b=c,c=[]);for(var f=0;f<b.length;f++){var j=b[f].t,k=b[f].d,l="scale"==j.substr(0,5)?1:0;c.push({t:j,d:k.map(function(a){if("number"==typeof a)return l;var b={};for(var c in a)b[c]=l;return b})})}}var m=function(a,b){return"perspective"==a&&"perspective"==b||("matrix"==a||"matrix3d"==a)&&("matrix"==b||"matrix3d"==b)},o=[],p=[],q=[];if(b.length!=c.length){if(!d)return;var r=g(b,c);o=[r[0]],p=[r[1]],q=[["matrix",[r[2]]]]}else for(var f=0;f<b.length;f++){var j,s=b[f].t,t=c[f].t,u=b[f].d,v=c[f].d,w=n[s],x=n[t];if(m(s,t)){if(!d)return;var r=g([b[f]],[c[f]]);o.push(r[0]),p.push(r[1]),q.push(["matrix",[r[2]]])}else{if(s==t)j=s;else if(w[2]&&x[2]&&h(s)==h(t))j=h(s),u=w[2](u),v=x[2](v);else{if(!w[1]||!x[1]||i(s)!=i(t)){if(!d)return;var r=g(b,c);o=[r[0]],p=[r[1]],q=[["matrix",[r[2]]]];break}j=i(s),u=w[1](u),v=x[1](v)}for(var y=[],z=[],A=[],B=0;B<u.length;B++){var C="number"==typeof u[B]?a.mergeNumbers:a.mergeDimensions,r=C(u[B],v[B]);y[B]=r[0],z[B]=r[1],A.push(r[2])}o.push(y),p.push(z),q.push([j,A])}}if(e){var D=o;o=p,p=D}return[o,p,function(a){return a.map(function(a,b){var c=a.map(function(a,c){return q[b][1][c](a)}).join(",");return"matrix"==q[b][0]&&16==c.split(",").length&&(q[b][0]="matrix3d"),q[b][0]+"("+c+")"}).join(" ")}]}var k=null,l={px:0},m={deg:0},n={matrix:["NNNNNN",[k,k,0,0,k,k,0,0,0,0,1,0,k,k,0,1],d],matrix3d:["NNNNNNNNNNNNNNNN",d],rotate:["A"],rotatex:["A"],rotatey:["A"],rotatez:["A"],rotate3d:["NNNA"],perspective:["L"],scale:["Nn",c([k,k,1]),d],scalex:["N",c([k,1,1]),c([k,1])],scaley:["N",c([1,k,1]),c([1,k])],scalez:["N",c([1,1,k])],scale3d:["NNN",d],skew:["Aa",null,d],skewx:["A",null,c([k,m])],skewy:["A",null,c([m,k])],translate:["Tt",c([k,k,l]),d],translatex:["T",c([k,l,l]),c([k,l])],translatey:["T",c([l,k,l]),c([l,k])],translatez:["L",c([l,l,k])],translate3d:["TTL",d]};a.addPropertiesHandler(e,j,["transform"])}(d,f),function(a,b){function c(a,b){b.concat([a]).forEach(function(b){b in document.documentElement.style&&(d[a]=b)})}var d={};c("transform",["webkitTransform","msTransform"]),c("transformOrigin",["webkitTransformOrigin"]),c("perspective",["webkitPerspective"]),c("perspectiveOrigin",["webkitPerspectiveOrigin"]),a.propertyName=function(a){return d[a]||a}}(d,f)}(),!function(){if(void 0===document.createElement("div").animate([]).oncancel){var a;if(window.performance&&performance.now)var a=function(){return performance.now()};else var a=function(){return Date.now()};var b=function(a,b,c){this.target=a,this.currentTime=b,this.timelineTime=c,this.type="cancel",this.bubbles=!1,this.cancelable=!1,this.currentTarget=a,this.defaultPrevented=!1,this.eventPhase=Event.AT_TARGET,this.timeStamp=Date.now()},c=window.Element.prototype.animate;window.Element.prototype.animate=function(d,e){var f=c.call(this,d,e);f._cancelHandlers=[],f.oncancel=null;var g=f.cancel;f.cancel=function(){g.call(this);var c=new b(this,null,a()),d=this._cancelHandlers.concat(this.oncancel?[this.oncancel]:[]);setTimeout(function(){d.forEach(function(a){a.call(c.target,c)})},0)};var h=f.addEventListener;f.addEventListener=function(a,b){"function"==typeof b&&"cancel"==a?this._cancelHandlers.push(b):h.call(this,a,b)};var i=f.removeEventListener;return f.removeEventListener=function(a,b){if("cancel"==a){var c=this._cancelHandlers.indexOf(b);c>=0&&this._cancelHandlers.splice(c,1)}else i.call(this,a,b)},f}}}(),function(a){var b=document.documentElement,c=b.animate({opacity:["1","0"]},{duration:1,fill:"forwards"});c.finish();var d="0"==getComputedStyle(b).getPropertyValue("opacity");if(c.cancel(),!d){var e=window.Element.prototype.animate;window.Element.prototype.animate=function(b,c){return window.Symbol&&Symbol.iterator&&Array.prototype.from&&b[Symbol.iterator]&&(b=Array.from(b)),Array.isArray(b)||null===b||(b=a.convertToArrayForm(b)),e.call(this,b,c)}}}(c),!function(a,b,c){function d(a){var b=window.document.timeline;b.currentTime=a,b._discardAnimations(),0==b._animations.length?f=!1:requestAnimationFrame(d)}var e=window.requestAnimationFrame;window.requestAnimationFrame=function(a){return e(function(b){window.document.timeline._updateAnimationsPromises(),a(b),window.document.timeline._updateAnimationsPromises()})},b.AnimationTimeline=function(){this._animations=[],this.currentTime=void 0},b.AnimationTimeline.prototype={getAnimations:function(){return this._discardAnimations(),this._animations.slice()},_updateAnimationsPromises:function(){b.animationsWithPromises=b.animationsWithPromises.filter(function(a){return a._updatePromises()})},_discardAnimations:function(){this._updateAnimationsPromises(), -this._animations=this._animations.filter(function(a){return"finished"!=a.playState&&"idle"!=a.playState})},_play:function(a){var c=new b.Animation(a,this);return this._animations.push(c),b.restartWebAnimationsNextTick(),c._updatePromises(),c._animation.play(),c._updatePromises(),c},play:function(a){return a&&a.remove(),this._play(a)}};var f=!1;b.restartWebAnimationsNextTick=function(){f||(f=!0,requestAnimationFrame(d))};var g=new b.AnimationTimeline;b.timeline=g;try{Object.defineProperty(window.document,"timeline",{configurable:!0,get:function(){return g}})}catch(h){}try{window.document.timeline=g}catch(h){}}(c,e,f),function(a,b,c){b.animationsWithPromises=[],b.Animation=function(b,c){if(this.id="",b&&b._id&&(this.id=b._id),this.effect=b,b&&(b._animation=this),!c)throw new Error("Animation with null timeline is not supported");this._timeline=c,this._sequenceNumber=a.sequenceNumber++,this._holdTime=0,this._paused=!1,this._isGroup=!1,this._animation=null,this._childAnimations=[],this._callback=null,this._oldPlayState="idle",this._rebuildUnderlyingAnimation(),this._animation.cancel(),this._updatePromises()},b.Animation.prototype={_updatePromises:function(){var a=this._oldPlayState,b=this.playState;return this._readyPromise&&b!==a&&("idle"==b?(this._rejectReadyPromise(),this._readyPromise=void 0):"pending"==a?this._resolveReadyPromise():"pending"==b&&(this._readyPromise=void 0)),this._finishedPromise&&b!==a&&("idle"==b?(this._rejectFinishedPromise(),this._finishedPromise=void 0):"finished"==b?this._resolveFinishedPromise():"finished"==a&&(this._finishedPromise=void 0)),this._oldPlayState=this.playState,this._readyPromise||this._finishedPromise},_rebuildUnderlyingAnimation:function(){this._updatePromises();var a,c,d,e,f=!!this._animation;f&&(a=this.playbackRate,c=this._paused,d=this.startTime,e=this.currentTime,this._animation.cancel(),this._animation._wrapper=null,this._animation=null),(!this.effect||this.effect instanceof window.KeyframeEffect)&&(this._animation=b.newUnderlyingAnimationForKeyframeEffect(this.effect),b.bindAnimationForKeyframeEffect(this)),(this.effect instanceof window.SequenceEffect||this.effect instanceof window.GroupEffect)&&(this._animation=b.newUnderlyingAnimationForGroup(this.effect),b.bindAnimationForGroup(this)),this.effect&&this.effect._onsample&&b.bindAnimationForCustomEffect(this),f&&(1!=a&&(this.playbackRate=a),null!==d?this.startTime=d:null!==e?this.currentTime=e:null!==this._holdTime&&(this.currentTime=this._holdTime),c&&this.pause()),this._updatePromises()},_updateChildren:function(){if(this.effect&&"idle"!=this.playState){var a=this.effect._timing.delay;this._childAnimations.forEach(function(c){this._arrangeChildren(c,a),this.effect instanceof window.SequenceEffect&&(a+=b.groupChildDuration(c.effect))}.bind(this))}},_setExternalAnimation:function(a){if(this.effect&&this._isGroup)for(var b=0;b<this.effect.children.length;b++)this.effect.children[b]._animation=a,this._childAnimations[b]._setExternalAnimation(a)},_constructChildAnimations:function(){if(this.effect&&this._isGroup){var a=this.effect._timing.delay;this._removeChildAnimations(),this.effect.children.forEach(function(c){var d=window.document.timeline._play(c);this._childAnimations.push(d),d.playbackRate=this.playbackRate,this._paused&&d.pause(),c._animation=this.effect._animation,this._arrangeChildren(d,a),this.effect instanceof window.SequenceEffect&&(a+=b.groupChildDuration(c))}.bind(this))}},_arrangeChildren:function(a,b){null===this.startTime?a.currentTime=this.currentTime-b/this.playbackRate:a.startTime!==this.startTime+b/this.playbackRate&&(a.startTime=this.startTime+b/this.playbackRate)},get timeline(){return this._timeline},get playState(){return this._animation?this._animation.playState:"idle"},get finished(){return window.Promise?(this._finishedPromise||(-1==b.animationsWithPromises.indexOf(this)&&b.animationsWithPromises.push(this),this._finishedPromise=new Promise(function(a,b){this._resolveFinishedPromise=function(){a(this)},this._rejectFinishedPromise=function(){b({type:DOMException.ABORT_ERR,name:"AbortError"})}}.bind(this)),"finished"==this.playState&&this._resolveFinishedPromise()),this._finishedPromise):(console.warn("Animation Promises require JavaScript Promise constructor"),null)},get ready(){return window.Promise?(this._readyPromise||(-1==b.animationsWithPromises.indexOf(this)&&b.animationsWithPromises.push(this),this._readyPromise=new Promise(function(a,b){this._resolveReadyPromise=function(){a(this)},this._rejectReadyPromise=function(){b({type:DOMException.ABORT_ERR,name:"AbortError"})}}.bind(this)),"pending"!==this.playState&&this._resolveReadyPromise()),this._readyPromise):(console.warn("Animation Promises require JavaScript Promise constructor"),null)},get onfinish(){return this._animation.onfinish},set onfinish(a){"function"==typeof a?this._animation.onfinish=function(b){b.target=this,a.call(this,b)}.bind(this):this._animation.onfinish=a},get oncancel(){return this._animation.oncancel},set oncancel(a){"function"==typeof a?this._animation.oncancel=function(b){b.target=this,a.call(this,b)}.bind(this):this._animation.oncancel=a},get currentTime(){this._updatePromises();var a=this._animation.currentTime;return this._updatePromises(),a},set currentTime(a){this._updatePromises(),this._animation.currentTime=isFinite(a)?a:Math.sign(a)*Number.MAX_VALUE,this._register(),this._forEachChild(function(b,c){b.currentTime=a-c}),this._updatePromises()},get startTime(){return this._animation.startTime},set startTime(a){this._updatePromises(),this._animation.startTime=isFinite(a)?a:Math.sign(a)*Number.MAX_VALUE,this._register(),this._forEachChild(function(b,c){b.startTime=a+c}),this._updatePromises()},get playbackRate(){return this._animation.playbackRate},set playbackRate(a){this._updatePromises();var b=this.currentTime;this._animation.playbackRate=a,this._forEachChild(function(b){b.playbackRate=a}),"paused"!=this.playState&&"idle"!=this.playState&&this.play(),null!==b&&(this.currentTime=b),this._updatePromises()},play:function(){this._updatePromises(),this._paused=!1,this._animation.play(),-1==this._timeline._animations.indexOf(this)&&this._timeline._animations.push(this),this._register(),b.awaitStartTime(this),this._forEachChild(function(a){var b=a.currentTime;a.play(),a.currentTime=b}),this._updatePromises()},pause:function(){this._updatePromises(),this.currentTime&&(this._holdTime=this.currentTime),this._animation.pause(),this._register(),this._forEachChild(function(a){a.pause()}),this._paused=!0,this._updatePromises()},finish:function(){this._updatePromises(),this._animation.finish(),this._register(),this._updatePromises()},cancel:function(){this._updatePromises(),this._animation.cancel(),this._register(),this._removeChildAnimations(),this._updatePromises()},reverse:function(){this._updatePromises();var a=this.currentTime;this._animation.reverse(),this._forEachChild(function(a){a.reverse()}),null!==a&&(this.currentTime=a),this._updatePromises()},addEventListener:function(a,b){var c=b;"function"==typeof b&&(c=function(a){a.target=this,b.call(this,a)}.bind(this),b._wrapper=c),this._animation.addEventListener(a,c)},removeEventListener:function(a,b){this._animation.removeEventListener(a,b&&b._wrapper||b)},_removeChildAnimations:function(){for(;this._childAnimations.length;)this._childAnimations.pop().cancel()},_forEachChild:function(b){var c=0;if(this.effect.children&&this._childAnimations.length<this.effect.children.length&&this._constructChildAnimations(),this._childAnimations.forEach(function(a){b.call(this,a,c),this.effect instanceof window.SequenceEffect&&(c+=a.effect.activeDuration)}.bind(this)),"pending"!=this.playState){var d=this.effect._timing,e=this.currentTime;null!==e&&(e=a.calculateTimeFraction(a.calculateActiveDuration(d),e,d)),(null==e||isNaN(e))&&this._removeChildAnimations()}}},window.Animation=b.Animation}(c,e,f),function(a,b,c){function d(b){this._frames=a.normalizeKeyframes(b)}function e(){for(var a=!1;i.length;){var b=i.shift();b._updateChildren(),a=!0}return a}var f=function(a){if(a._animation=void 0,a instanceof window.SequenceEffect||a instanceof window.GroupEffect)for(var b=0;b<a.children.length;b++)f(a.children[b])};b.removeMulti=function(a){for(var b=[],c=0;c<a.length;c++){var d=a[c];d._parent?(-1==b.indexOf(d._parent)&&b.push(d._parent),d._parent.children.splice(d._parent.children.indexOf(d),1),d._parent=null,f(d)):d._animation&&d._animation.effect==d&&(d._animation.cancel(),d._animation.effect=new KeyframeEffect(null,[]),d._animation._callback&&(d._animation._callback._animation=null),d._animation._rebuildUnderlyingAnimation(),f(d))}for(c=0;c<b.length;c++)b[c]._rebuild()},b.KeyframeEffect=function(b,c,e,f){return this.target=b,this._parent=null,e=a.numericTimingToObject(e),this._timingInput=a.cloneTimingInput(e),this._timing=a.normalizeTimingInput(e),this.timing=a.makeTiming(e,!1,this),this.timing._effect=this,"function"==typeof c?(a.deprecated("Custom KeyframeEffect","2015-06-22","Use KeyframeEffect.onsample instead."),this._normalizedKeyframes=c):this._normalizedKeyframes=new d(c),this._keyframes=c,this.activeDuration=a.calculateActiveDuration(this._timing),this._id=f,this},b.KeyframeEffect.prototype={getFrames:function(){return"function"==typeof this._normalizedKeyframes?this._normalizedKeyframes:this._normalizedKeyframes._frames},set onsample(a){if("function"==typeof this.getFrames())throw new Error("Setting onsample on custom effect KeyframeEffect is not supported.");this._onsample=a,this._animation&&this._animation._rebuildUnderlyingAnimation()},get parent(){return this._parent},clone:function(){if("function"==typeof this.getFrames())throw new Error("Cloning custom effects is not supported.");var b=new KeyframeEffect(this.target,[],a.cloneTimingInput(this._timingInput),this._id);return b._normalizedKeyframes=this._normalizedKeyframes,b._keyframes=this._keyframes,b},remove:function(){b.removeMulti([this])}};var g=Element.prototype.animate;Element.prototype.animate=function(a,c){var d="";return c&&c.id&&(d=c.id),b.timeline._play(new b.KeyframeEffect(this,a,c,d))};var h=document.createElementNS("http://www.w3.org/1999/xhtml","div");b.newUnderlyingAnimationForKeyframeEffect=function(a){if(a){var b=a.target||h,c=a._keyframes;"function"==typeof c&&(c=[]);var d=a._timingInput;d.id=a._id}else var b=h,c=[],d=0;return g.apply(b,[c,d])},b.bindAnimationForKeyframeEffect=function(a){a.effect&&"function"==typeof a.effect._normalizedKeyframes&&b.bindAnimationForCustomEffect(a)};var i=[];b.awaitStartTime=function(a){null===a.startTime&&a._isGroup&&(0==i.length&&requestAnimationFrame(e),i.push(a))};var j=window.getComputedStyle;Object.defineProperty(window,"getComputedStyle",{configurable:!0,enumerable:!0,value:function(){window.document.timeline._updateAnimationsPromises();var a=j.apply(this,arguments);return e()&&(a=j.apply(this,arguments)),window.document.timeline._updateAnimationsPromises(),a}}),window.KeyframeEffect=b.KeyframeEffect,window.Element.prototype.getAnimations=function(){return document.timeline.getAnimations().filter(function(a){return null!==a.effect&&a.effect.target==this}.bind(this))}}(c,e,f),function(a,b,c){function d(a){a._registered||(a._registered=!0,g.push(a),h||(h=!0,requestAnimationFrame(e)))}function e(a){var b=g;g=[],b.sort(function(a,b){return a._sequenceNumber-b._sequenceNumber}),b=b.filter(function(a){a();var b=a._animation?a._animation.playState:"idle";return"running"!=b&&"pending"!=b&&(a._registered=!1),a._registered}),g.push.apply(g,b),g.length?(h=!0,requestAnimationFrame(e)):h=!1}var f=(document.createElementNS("http://www.w3.org/1999/xhtml","div"),0);b.bindAnimationForCustomEffect=function(b){var c,e=b.effect.target,g="function"==typeof b.effect.getFrames();c=g?b.effect.getFrames():b.effect._onsample;var h=b.effect.timing,i=null;h=a.normalizeTimingInput(h);var j=function(){var d=j._animation?j._animation.currentTime:null;null!==d&&(d=a.calculateTimeFraction(a.calculateActiveDuration(h),d,h),isNaN(d)&&(d=null)),d!==i&&(g?c(d,e,b.effect):c(d,b.effect,b.effect._animation)),i=d};j._animation=b,j._registered=!1,j._sequenceNumber=f++,b._callback=j,d(j)};var g=[],h=!1;b.Animation.prototype._register=function(){this._callback&&d(this._callback)}}(c,e,f),function(a,b,c){function d(a){return a._timing.delay+a.activeDuration+a._timing.endDelay}function e(b,c,d){this._id=d,this._parent=null,this.children=b||[],this._reparent(this.children),c=a.numericTimingToObject(c),this._timingInput=a.cloneTimingInput(c),this._timing=a.normalizeTimingInput(c,!0),this.timing=a.makeTiming(c,!0,this),this.timing._effect=this,"auto"===this._timing.duration&&(this._timing.duration=this.activeDuration)}window.SequenceEffect=function(){e.apply(this,arguments)},window.GroupEffect=function(){e.apply(this,arguments)},e.prototype={_isAncestor:function(a){for(var b=this;null!==b;){if(b==a)return!0;b=b._parent}return!1},_rebuild:function(){for(var a=this;a;)"auto"===a.timing.duration&&(a._timing.duration=a.activeDuration),a=a._parent;this._animation&&this._animation._rebuildUnderlyingAnimation()},_reparent:function(a){b.removeMulti(a);for(var c=0;c<a.length;c++)a[c]._parent=this},_putChild:function(a,b){for(var c=b?"Cannot append an ancestor or self":"Cannot prepend an ancestor or self",d=0;d<a.length;d++)if(this._isAncestor(a[d]))throw{type:DOMException.HIERARCHY_REQUEST_ERR,name:"HierarchyRequestError",message:c};for(var d=0;d<a.length;d++)b?this.children.push(a[d]):this.children.unshift(a[d]);this._reparent(a),this._rebuild()},append:function(){this._putChild(arguments,!0)},prepend:function(){this._putChild(arguments,!1)},get parent(){return this._parent},get firstChild(){return this.children.length?this.children[0]:null},get lastChild(){return this.children.length?this.children[this.children.length-1]:null},clone:function(){for(var b=a.cloneTimingInput(this._timingInput),c=[],d=0;d<this.children.length;d++)c.push(this.children[d].clone());return this instanceof GroupEffect?new GroupEffect(c,b):new SequenceEffect(c,b)},remove:function(){b.removeMulti([this])}},window.SequenceEffect.prototype=Object.create(e.prototype),Object.defineProperty(window.SequenceEffect.prototype,"activeDuration",{get:function(){var a=0;return this.children.forEach(function(b){a+=d(b)}),Math.max(a,0)}}),window.GroupEffect.prototype=Object.create(e.prototype),Object.defineProperty(window.GroupEffect.prototype,"activeDuration",{get:function(){var a=0;return this.children.forEach(function(b){a=Math.max(a,d(b))}),a}}),b.newUnderlyingAnimationForGroup=function(c){var d,e=null,f=function(b){var c=d._wrapper;return c&&"pending"!=c.playState&&c.effect?null==b?void c._removeChildAnimations():0==b&&c.playbackRate<0&&(e||(e=a.normalizeTimingInput(c.effect.timing)),b=a.calculateTimeFraction(a.calculateActiveDuration(e),-1,e),isNaN(b)||null==b)?(c._forEachChild(function(a){a.currentTime=-1}),void c._removeChildAnimations()):void 0:void 0},g=new KeyframeEffect(null,[],c._timing,c._id);return g.onsample=f,d=b.timeline._play(g)},b.bindAnimationForGroup=function(a){a._animation._wrapper=a,a._isGroup=!0,b.awaitStartTime(a),a._constructChildAnimations(),a._setExternalAnimation(a)},b.groupChildDuration=d}(c,e,f),b["true"]=a}({},function(){return this}()); -</script><script> - - Polymer({ - - is: 'opaque-animation', - - behaviors: [ - Polymer.NeonAnimationBehavior - ], - - configure: function(config) { - var node = config.node; - node.style.opacity = '0'; - this._effect = new KeyframeEffect(node, [ - {'opacity': '1'}, - {'opacity': '1'} - ], this.timingFromConfig(config)); - return this._effect; - }, - - complete: function(config) { - config.node.style.opacity = ''; - } - - }); - -</script><script> - - /** - * `Polymer.NeonAnimatableBehavior` is implemented by elements containing animations for use with - * elements implementing `Polymer.NeonAnimationRunnerBehavior`. - * @polymerBehavior - */ - Polymer.NeonAnimatableBehavior = { - - properties: { - - /** - * Animation configuration. See README for more info. - */ - animationConfig: { - type: Object - }, - - /** - * Convenience property for setting an 'entry' animation. Do not set `animationConfig.entry` - * manually if using this. The animated node is set to `this` if using this property. - */ - entryAnimation: { - observer: '_entryAnimationChanged', - type: String - }, - - /** - * Convenience property for setting an 'exit' animation. Do not set `animationConfig.exit` - * manually if using this. The animated node is set to `this` if using this property. - */ - exitAnimation: { - observer: '_exitAnimationChanged', - type: String - } - - }, - - _entryAnimationChanged: function() { - this.animationConfig = this.animationConfig || {}; - if (this.entryAnimation !== 'fade-in-animation') { - // insert polyfill hack - this.animationConfig['entry'] = [{ - name: 'opaque-animation', - node: this - }, { - name: this.entryAnimation, - node: this - }]; - } else { - this.animationConfig['entry'] = [{ - name: this.entryAnimation, - node: this - }]; - } - }, - - _exitAnimationChanged: function() { - this.animationConfig = this.animationConfig || {}; - this.animationConfig['exit'] = [{ - name: this.exitAnimation, - node: this - }]; - }, - - _copyProperties: function(config1, config2) { - // shallowly copy properties from config2 to config1 - for (var property in config2) { - config1[property] = config2[property]; - } - }, - - _cloneConfig: function(config) { - var clone = { - isClone: true - }; - this._copyProperties(clone, config); - return clone; - }, - - _getAnimationConfigRecursive: function(type, map, allConfigs) { - if (!this.animationConfig) { - return; - } - - // type is optional - var thisConfig; - if (type) { - thisConfig = this.animationConfig[type]; - } else { - thisConfig = this.animationConfig; - } - - if (!Array.isArray(thisConfig)) { - thisConfig = [thisConfig]; - } - - // iterate animations and recurse to process configurations from child nodes - if (thisConfig) { - for (var config, index = 0; config = thisConfig[index]; index++) { - if (config.animatable) { - config.animatable._getAnimationConfigRecursive(config.type || type, map, allConfigs); - } else { - if (config.id) { - var cachedConfig = map[config.id]; - if (cachedConfig) { - // merge configurations with the same id, making a clone lazily - if (!cachedConfig.isClone) { - map[config.id] = this._cloneConfig(cachedConfig) - cachedConfig = map[config.id]; - } - this._copyProperties(cachedConfig, config); - } else { - // put any configs with an id into a map - map[config.id] = config; - } - } else { - allConfigs.push(config); - } - } - } - } - }, - - /** - * An element implementing `Polymer.NeonAnimationRunnerBehavior` calls this method to configure - * an animation with an optional type. Elements implementing `Polymer.NeonAnimatableBehavior` - * should define the property `animationConfig`, which is either a configuration object - * or a map of animation type to array of configuration objects. - */ - getAnimationConfig: function(type) { - var map = []; - var allConfigs = []; - this._getAnimationConfigRecursive(type, map, allConfigs); - // append the configurations saved in the map to the array - for (var key in map) { - allConfigs.push(map[key]); - } - return allConfigs; - } - - }; - -</script><script> - - /** - * `Polymer.NeonAnimationRunnerBehavior` adds a method to run animations. - * - * @polymerBehavior Polymer.NeonAnimationRunnerBehavior - */ - Polymer.NeonAnimationRunnerBehaviorImpl = { - - properties: { - - _animationMeta: { - type: Object, - value: function() { - return new Polymer.IronMeta({type: 'animation'}); - } - }, - - /** @type {?Object} */ - _player: { - type: Object - } - - }, - - _configureAnimationEffects: function(allConfigs) { - var allAnimations = []; - if (allConfigs.length > 0) { - for (var config, index = 0; config = allConfigs[index]; index++) { - var animationConstructor = this._animationMeta.byKey(config.name); - if (animationConstructor) { - var animation = animationConstructor && new animationConstructor(); - var effect = animation.configure(config); - if (effect) { - allAnimations.push({ - animation: animation, - config: config, - effect: effect - }); - } - } else { - console.warn(this.is + ':', config.name, 'not found!'); - } - } - } - return allAnimations; - }, - - _runAnimationEffects: function(allEffects) { - return document.timeline.play(new GroupEffect(allEffects)); - }, - - _completeAnimations: function(allAnimations) { - for (var animation, index = 0; animation = allAnimations[index]; index++) { - animation.animation.complete(animation.config); - } - }, - - /** - * Plays an animation with an optional `type`. - * @param {string=} type - * @param {!Object=} cookie - */ - playAnimation: function(type, cookie) { - var allConfigs = this.getAnimationConfig(type); - if (!allConfigs) { - return; - } - var allAnimations = this._configureAnimationEffects(allConfigs); - var allEffects = allAnimations.map(function(animation) { - return animation.effect; - }); - - if (allEffects.length > 0) { - this._player = this._runAnimationEffects(allEffects); - this._player.onfinish = function() { - this._completeAnimations(allAnimations); - - if (this._player) { - this._player.cancel(); - this._player = null; - } - - this.fire('neon-animation-finish', cookie, {bubbles: false}); - }.bind(this); - - } else { - this.fire('neon-animation-finish', cookie, {bubbles: false}); - } - }, - - /** - * Cancels the currently running animation. - */ - cancelAnimation: function() { - if (this._player) { - this._player.cancel(); - } - } - }; - - /** @polymerBehavior Polymer.NeonAnimationRunnerBehavior */ - Polymer.NeonAnimationRunnerBehavior = [ - Polymer.NeonAnimatableBehavior, - Polymer.NeonAnimationRunnerBehaviorImpl - ]; -</script><link rel=stylesheet href="https://fonts.googleapis.com/css?family=Roboto:400,300,500,700"><style is=custom-style> -:root { - --dark-primary-color: #303F9F; - --default-primary-color: #3F51B5; - --light-primary-color: #C5CAE9; - --text-primary-color: #ffffff; - /*text/icons*/ - --accent-color: #FF4081; - --primary-background-color: #c5cae9; - --primary-text-color: #212121; - --secondary-text-color: #727272; - --disabled-text-color: #bdbdbd; - --divider-color: #B6B6B6; - /* Components */ - /* paper-drawer-panel */ - --drawer-menu-color: #ffffff; - --drawer-border-color: 1px solid #ccc; - --drawer-toolbar-border-color: 1px solid rgba(0, 0, 0, 0.22); - /* paper-menu */ - --paper-menu-background-color: #fff; - --menu-link-color: #111111; -} - -paper-progress { - width: 100%; - z-index: 10000; - position: absolute; - top: 0; -} -neon-animated-pages{ - height: 100vh; -} -</style><script> - - /** - * Use `Polymer.NeonSharedElementAnimatableBehavior` to implement elements containing shared element - * animations. - * @polymerBehavior Polymer.NeonSharedElementAnimatableBehavior - */ - Polymer.NeonSharedElementAnimatableBehaviorImpl = { - - properties: { - - /** - * A map of shared element id to node. - */ - sharedElements: { - type: Object, - value: {} - } - - } - - }; - - /** @polymerBehavior Polymer.NeonSharedElementAnimatableBehavior */ - Polymer.NeonSharedElementAnimatableBehavior = [ - Polymer.NeonAnimatableBehavior, - Polymer.NeonSharedElementAnimatableBehaviorImpl - ]; - -</script><script> - - Polymer({ - - is: 'cascaded-animation', - - behaviors: [ - Polymer.NeonAnimationBehavior - ], - - properties: { - - /** @type {!Polymer.IronMeta} */ - _animationMeta: { - type: Object, - value: function() { - return new Polymer.IronMeta({type: 'animation'}); - } - } - - }, - - /** - * @param {{ - * animation: string, - * nodes: !Array<!Element>, - * nodeDelay: (number|undefined), - * timing: (Object|undefined) - * }} config - */ - configure: function(config) { - var animationConstructor = /** @type {Function} */ ( - this._animationMeta.byKey(config.animation)); - if (!animationConstructor) { - console.warn(this.is + ':', 'constructor for', config.animation, 'not found!'); - return; - } - - this._animations = []; - var nodes = config.nodes; - var effects = []; - var nodeDelay = config.nodeDelay || 50; - - config.timing = config.timing || {}; - config.timing.delay = config.timing.delay || 0; - - var oldDelay = config.timing.delay; - for (var node, index = 0; node = nodes[index]; index++) { - config.timing.delay += nodeDelay; - config.node = node; - - var animation = new animationConstructor(); - var effect = animation.configure(config); - - this._animations.push(animation); - effects.push(effect); - } - config.timing.delay = oldDelay; - - this._effect = new GroupEffect(effects); - return this._effect; - }, - - complete: function() { - for (var animation, index = 0; animation = this._animations[index]; index++) { - animation.complete(animation.config); - } - } - - }); - -</script><script> - - Polymer({ - - is: 'fade-in-animation', - - behaviors: [ - Polymer.NeonAnimationBehavior - ], - - configure: function(config) { - var node = config.node; - this._effect = new KeyframeEffect(node, [ - {'opacity': '0'}, - {'opacity': '1'} - ], this.timingFromConfig(config)); - return this._effect; - } - - }); - -</script><script> - - Polymer({ - - is: 'fade-out-animation', - - behaviors: [ - Polymer.NeonAnimationBehavior - ], - - configure: function(config) { - var node = config.node; - this._effect = new KeyframeEffect(node, [ - {'opacity': '1'}, - {'opacity': '0'} - ], this.timingFromConfig(config)); - return this._effect; - } - - }); - -</script><script> - - /** - * Use `Polymer.NeonSharedElementAnimationBehavior` to implement shared element animations. - * @polymerBehavior Polymer.NeonSharedElementAnimationBehavior - */ - Polymer.NeonSharedElementAnimationBehaviorImpl = { - - properties: { - - /** - * Cached copy of shared elements. - */ - sharedElements: { - type: Object - } - - }, - - /** - * Finds shared elements based on `config`. - */ - findSharedElements: function(config) { - var fromPage = config.fromPage; - var toPage = config.toPage; - if (!fromPage || !toPage) { - console.warn(this.is + ':', !fromPage ? 'fromPage' : 'toPage', 'is undefined!'); - return null; - }; - - if (!fromPage.sharedElements || !toPage.sharedElements) { - console.warn(this.is + ':', 'sharedElements are undefined for', !fromPage.sharedElements ? fromPage : toPage); - return null; - }; - - var from = fromPage.sharedElements[config.id] - var to = toPage.sharedElements[config.id]; - - if (!from || !to) { - console.warn(this.is + ':', 'sharedElement with id', config.id, 'not found in', !from ? fromPage : toPage); - return null; - } - - this.sharedElements = { - from: from, - to: to - }; - return this.sharedElements; - } - - }; - - /** @polymerBehavior Polymer.NeonSharedElementAnimationBehavior */ - Polymer.NeonSharedElementAnimationBehavior = [ - Polymer.NeonAnimationBehavior, - Polymer.NeonSharedElementAnimationBehaviorImpl - ]; - -</script><script> - - Polymer({ - - is: 'hero-animation', - - behaviors: [ - Polymer.NeonSharedElementAnimationBehavior - ], - - configure: function(config) { - var shared = this.findSharedElements(config); - if (!shared) { - return; - } - - var fromRect = shared.from.getBoundingClientRect(); - var toRect = shared.to.getBoundingClientRect(); - - var deltaLeft = fromRect.left - toRect.left; - var deltaTop = fromRect.top - toRect.top; - var deltaWidth = fromRect.width / toRect.width; - var deltaHeight = fromRect.height / toRect.height; - - this.setPrefixedProperty(shared.to, 'transformOrigin', '0 0'); - shared.to.style.zIndex = 10000; - shared.from.style.visibility = 'hidden'; - - this._effect = new KeyframeEffect(shared.to, [ - {'transform': 'translate(' + deltaLeft + 'px,' + deltaTop + 'px) scale(' + deltaWidth + ',' + deltaHeight + ')'}, - {'transform': 'none'} - ], this.timingFromConfig(config)); - - return this._effect; - }, - - complete: function(config) { - var shared = this.findSharedElements(config); - if (!shared) { - return null; - } - shared.to.style.zIndex = ''; - shared.from.style.visibility = ''; - } - - }); - -</script><script> - - Polymer({ - - is: 'ripple-animation', - - behaviors: [ - Polymer.NeonSharedElementAnimationBehavior - ], - - configure: function(config) { - var shared = this.findSharedElements(config); - if (!shared) { - return null; - } - - var translateX, translateY; - var toRect = shared.to.getBoundingClientRect(); - if (config.gesture) { - translateX = config.gesture.x - (toRect.left + (toRect.width / 2)); - translateY = config.gesture.y - (toRect.top + (toRect.height / 2)); - } else { - var fromRect = shared.from.getBoundingClientRect(); - translateX = (fromRect.left + (fromRect.width / 2)) - (toRect.left + (toRect.width / 2)); - translateY = (fromRect.top + (fromRect.height / 2)) - (toRect.top + (toRect.height / 2)); - } - var translate = 'translate(' + translateX + 'px,' + translateY + 'px)'; - - var size = Math.max(toRect.width + Math.abs(translateX) * 2, toRect.height + Math.abs(translateY) * 2); - var diameter = Math.sqrt(2 * size * size); - var scaleX = diameter / toRect.width; - var scaleY = diameter / toRect.height; - var scale = 'scale(' + scaleX + ',' + scaleY + ')'; - - this.setPrefixedProperty(shared.to, 'transformOrigin', '50% 50%'); - shared.to.style.borderRadius = '50%'; - - this._effect = new KeyframeEffect(shared.to, [ - {'transform': translate + ' scale(0)'}, - {'transform': translate + ' ' + scale} - ], this.timingFromConfig(config)); - return this._effect; - }, - - complete: function() { - if (this.sharedElements) { - this.setPrefixedProperty(this.sharedElements.to, 'transformOrigin', ''); - this.sharedElements.to.style.borderRadius = ''; - } - } - - }); - -</script><script> - Polymer({ - is: 'reverse-ripple-animation', - - behaviors: [ - Polymer.NeonSharedElementAnimationBehavior - ], - - configure: function(config) { - var shared = this.findSharedElements(config); - if (!shared) { - return null; - } - - var translateX, translateY; - var fromRect = shared.from.getBoundingClientRect(); - if (config.gesture) { - translateX = config.gesture.x - (fromRect.left + (fromRect.width / 2)); - translateY = config.gesture.y - (fromRect.top + (fromRect.height / 2)); - } else { - var toRect = shared.to.getBoundingClientRect(); - translateX = (toRect.left + (toRect.width / 2)) - (fromRect.left + (fromRect.width / 2)); - translateY = (toRect.top + (toRect.height / 2)) - (fromRect.top + (fromRect.height / 2)); - } - var translate = 'translate(' + translateX + 'px,' + translateY + 'px)'; - - var size = Math.max(fromRect.width + Math.abs(translateX) * 2, fromRect.height + Math.abs(translateY) * 2); - var diameter = Math.sqrt(2 * size * size); - var scaleX = diameter / fromRect.width; - var scaleY = diameter / fromRect.height; - var scale = 'scale(' + scaleX + ',' + scaleY + ')'; - - this.setPrefixedProperty(shared.from, 'transformOrigin', '50% 50%'); - shared.from.style.borderRadius = '50%'; - - this._effect = new KeyframeEffect(shared.from, [ - {'transform': translate + ' ' + scale}, - {'transform': translate + ' scale(0)'} - ], this.timingFromConfig(config)); - return this._effect; - }, - - complete: function() { - if (this.sharedElements) { - this.setPrefixedProperty(this.sharedElements.from, 'transformOrigin', ''); - this.sharedElements.from.style.borderRadius = ''; - } - } - }); -</script><script> - - Polymer({ - - is: 'scale-down-animation', - - behaviors: [ - Polymer.NeonAnimationBehavior - ], - - configure: function(config) { - var node = config.node; - - if (config.transformOrigin) { - this.setPrefixedProperty(node, 'transformOrigin', config.transformOrigin); - } - - var scaleProperty = 'scale(0, 0)'; - if (config.axis === 'x') { - scaleProperty = 'scale(0, 1)'; - } else if (config.axis === 'y') { - scaleProperty = 'scale(1, 0)'; - } - - this._effect = new KeyframeEffect(node, [ - {'transform': 'scale(1,1)'}, - {'transform': scaleProperty} - ], this.timingFromConfig(config)); - - return this._effect; - } - - }); - -</script><script> - - Polymer({ - - is: 'scale-up-animation', - - behaviors: [ - Polymer.NeonAnimationBehavior - ], - - configure: function(config) { - var node = config.node; - - if (config.transformOrigin) { - this.setPrefixedProperty(node, 'transformOrigin', config.transformOrigin); - } - - var scaleProperty = 'scale(0)'; - if (config.axis === 'x') { - scaleProperty = 'scale(0, 1)'; - } else if (config.axis === 'y') { - scaleProperty = 'scale(1, 0)'; - } - - this._effect = new KeyframeEffect(node, [ - {'transform': scaleProperty}, - {'transform': 'scale(1, 1)'} - ], this.timingFromConfig(config)); - - return this._effect; - } - - }); - -</script><script> - - Polymer({ - - is: 'slide-from-left-animation', - - behaviors: [ - Polymer.NeonAnimationBehavior - ], - - configure: function(config) { - var node = config.node; - - if (config.transformOrigin) { - this.setPrefixedProperty(node, 'transformOrigin', config.transformOrigin); - } else { - this.setPrefixedProperty(node, 'transformOrigin', '0 50%'); - } - - this._effect = new KeyframeEffect(node, [ - {'transform': 'translateX(-100%)'}, - {'transform': 'none'} - ], this.timingFromConfig(config)); - - return this._effect; - } - - }); - -</script><script> - - Polymer({ - - is: 'slide-from-right-animation', - - behaviors: [ - Polymer.NeonAnimationBehavior - ], - - configure: function(config) { - var node = config.node; - - if (config.transformOrigin) { - this.setPrefixedProperty(node, 'transformOrigin', config.transformOrigin); - } else { - this.setPrefixedProperty(node, 'transformOrigin', '0 50%'); - } - - this._effect = new KeyframeEffect(node, [ - {'transform': 'translateX(100%)'}, - {'transform': 'none'} - ], this.timingFromConfig(config)); - - return this._effect; - } - - }); - -</script><script> - - Polymer({ - - is: 'slide-left-animation', - - behaviors: [ - Polymer.NeonAnimationBehavior - ], - - configure: function(config) { - var node = config.node; - - if (config.transformOrigin) { - this.setPrefixedProperty(node, 'transformOrigin', config.transformOrigin); - } else { - this.setPrefixedProperty(node, 'transformOrigin', '0 50%'); - } - - this._effect = new KeyframeEffect(node, [ - {'transform': 'none'}, - {'transform': 'translateX(-100%)'} - ], this.timingFromConfig(config)); - - return this._effect; - } - - }); - -</script><script> - - Polymer({ - - is: 'slide-right-animation', - - behaviors: [ - Polymer.NeonAnimationBehavior - ], - - configure: function(config) { - var node = config.node; - - if (config.transformOrigin) { - this.setPrefixedProperty(node, 'transformOrigin', config.transformOrigin); - } else { - this.setPrefixedProperty(node, 'transformOrigin', '0 50%'); - } - - this._effect = new KeyframeEffect(node, [ - {'transform': 'none'}, - {'transform': 'translateX(100%)'} - ], this.timingFromConfig(config)); - - return this._effect; - } - - }); - -</script><script> - - Polymer({ - - is: 'slide-up-animation', - - behaviors: [ - Polymer.NeonAnimationBehavior - ], - - configure: function(config) { - var node = config.node; - - if (config.transformOrigin) { - this.setPrefixedProperty(node, 'transformOrigin', config.transformOrigin); - } else { - this.setPrefixedProperty(node, 'transformOrigin', '50% 0'); - } - - this._effect = new KeyframeEffect(node, [ - {'transform': 'translate(0)'}, - {'transform': 'translateY(-100%)'} - ], this.timingFromConfig(config)); - - return this._effect; - } - - }); - -</script><script> - - Polymer({ - - is: 'slide-down-animation', - - behaviors: [ - Polymer.NeonAnimationBehavior - ], - - configure: function(config) { - var node = config.node; - - if (config.transformOrigin) { - this.setPrefixedProperty(node, 'transformOrigin', config.transformOrigin); - } else { - this.setPrefixedProperty(node, 'transformOrigin', '50% 0'); - } - - this._effect = new KeyframeEffect(node, [ - {'transform': 'translateY(-100%)'}, - {'transform': 'none'} - ], this.timingFromConfig(config)); - - return this._effect; - } - - }); - -</script><script> - - Polymer({ - - is: 'transform-animation', - - behaviors: [ - Polymer.NeonAnimationBehavior - ], - - /** - * @param {{ - * node: !Element, - * transformOrigin: (string|undefined), - * transformFrom: (string|undefined), - * transformTo: (string|undefined), - * timing: (Object|undefined) - * }} config - */ - configure: function(config) { - var node = config.node; - var transformFrom = config.transformFrom || 'none'; - var transformTo = config.transformTo || 'none'; - - if (config.transformOrigin) { - this.setPrefixedProperty(node, 'transformOrigin', config.transformOrigin); - } - - this._effect = new KeyframeEffect(node, [ - {'transform': transformFrom}, - {'transform': transformTo} - ], this.timingFromConfig(config)); - - return this._effect; - } - - }); - -</script><style> - - /******************************* - Flex Layout - *******************************/ - - html /deep/ .layout.horizontal, - html /deep/ .layout.horizontal-reverse, - html /deep/ .layout.vertical, - html /deep/ .layout.vertical-reverse { - display: -ms-flexbox; - display: -webkit-flex; - display: flex; - } - - html /deep/ .layout.inline { - display: -ms-inline-flexbox; - display: -webkit-inline-flex; - display: inline-flex; - } - - html /deep/ .layout.horizontal { - -ms-flex-direction: row; - -webkit-flex-direction: row; - flex-direction: row; - } - - html /deep/ .layout.horizontal-reverse { - -ms-flex-direction: row-reverse; - -webkit-flex-direction: row-reverse; - flex-direction: row-reverse; - } - - html /deep/ .layout.vertical { - -ms-flex-direction: column; - -webkit-flex-direction: column; - flex-direction: column; - } - - html /deep/ .layout.vertical-reverse { - -ms-flex-direction: column-reverse; - -webkit-flex-direction: column-reverse; - flex-direction: column-reverse; - } - - html /deep/ .layout.wrap { - -ms-flex-wrap: wrap; - -webkit-flex-wrap: wrap; - flex-wrap: wrap; - } - - html /deep/ .layout.wrap-reverse { - -ms-flex-wrap: wrap-reverse; - -webkit-flex-wrap: wrap-reverse; - flex-wrap: wrap-reverse; - } - - html /deep/ .flex-auto { - -ms-flex: 1 1 auto; - -webkit-flex: 1 1 auto; - flex: 1 1 auto; - } - - html /deep/ .flex-none { - -ms-flex: none; - -webkit-flex: none; - flex: none; - } - - html /deep/ .flex, - html /deep/ .flex-1 { - -ms-flex: 1; - -webkit-flex: 1; - flex: 1; - } - - html /deep/ .flex-2 { - -ms-flex: 2; - -webkit-flex: 2; - flex: 2; - } - - html /deep/ .flex-3 { - -ms-flex: 3; - -webkit-flex: 3; - flex: 3; - } - - html /deep/ .flex-4 { - -ms-flex: 4; - -webkit-flex: 4; - flex: 4; - } - - html /deep/ .flex-5 { - -ms-flex: 5; - -webkit-flex: 5; - flex: 5; - } - - html /deep/ .flex-6 { - -ms-flex: 6; - -webkit-flex: 6; - flex: 6; - } - - html /deep/ .flex-7 { - -ms-flex: 7; - -webkit-flex: 7; - flex: 7; - } - - html /deep/ .flex-8 { - -ms-flex: 8; - -webkit-flex: 8; - flex: 8; - } - - html /deep/ .flex-9 { - -ms-flex: 9; - -webkit-flex: 9; - flex: 9; - } - - html /deep/ .flex-10 { - -ms-flex: 10; - -webkit-flex: 10; - flex: 10; - } - - html /deep/ .flex-11 { - -ms-flex: 11; - -webkit-flex: 11; - flex: 11; - } - - html /deep/ .flex-12 { - -ms-flex: 12; - -webkit-flex: 12; - flex: 12; - } - - /* alignment in cross axis */ - - html /deep/ .layout.start { - -ms-flex-align: start; - -webkit-align-items: flex-start; - align-items: flex-start; - } - - html /deep/ .layout.center, - html /deep/ .layout.center-center { - -ms-flex-align: center; - -webkit-align-items: center; - align-items: center; - } - - html /deep/ .layout.end { - -ms-flex-align: end; - -webkit-align-items: flex-end; - align-items: flex-end; - } - - /* alignment in main axis */ - - html /deep/ .layout.start-justified { - -ms-flex-pack: start; - -webkit-justify-content: flex-start; - justify-content: flex-start; - } - - html /deep/ .layout.center-justified, - html /deep/ .layout.center-center { - -ms-flex-pack: center; - -webkit-justify-content: center; - justify-content: center; - } - - html /deep/ .layout.end-justified { - -ms-flex-pack: end; - -webkit-justify-content: flex-end; - justify-content: flex-end; - } - - html /deep/ .layout.around-justified { - -ms-flex-pack: around; - -webkit-justify-content: space-around; - justify-content: space-around; - } - - html /deep/ .layout.justified { - -ms-flex-pack: justify; - -webkit-justify-content: space-between; - justify-content: space-between; - } - - /* self alignment */ - - html /deep/ .self-start { - -ms-align-self: flex-start; - -webkit-align-self: flex-start; - align-self: flex-start; - } - - html /deep/ .self-center { - -ms-align-self: center; - -webkit-align-self: center; - align-self: center; - } - - html /deep/ .self-end { - -ms-align-self: flex-end; - -webkit-align-self: flex-end; - align-self: flex-end; - } - - html /deep/ .self-stretch { - -ms-align-self: stretch; - -webkit-align-self: stretch; - align-self: stretch; - } - - /******************************* - Other Layout - *******************************/ - - html /deep/ .block { - display: block; - } - - /* IE 10 support for HTML5 hidden attr */ - html /deep/ [hidden] { - display: none !important; - } - - html /deep/ .invisible { - visibility: hidden !important; - } - - html /deep/ .relative { - position: relative; - } - - html /deep/ .fit { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - } - - body.fullbleed { - margin: 0; - height: 100vh; - } - - html /deep/ .scroll { - -webkit-overflow-scrolling: touch; - overflow: auto; - } - - .fixed-bottom, - .fixed-left, - .fixed-right, - .fixed-top { - position: fixed; - } - - html /deep/ .fixed-top { - top: 0; - left: 0; - right: 0; - } - - html /deep/ .fixed-right { - top: 0; - right: 0; - bottom: 0; - } - - html /deep/ .fixed-bottom { - right: 0; - bottom: 0; - left: 0; - } - - html /deep/ .fixed-left { - top: 0; - bottom: 0; - left: 0; - } - -</style><style> - - /******************************* - Flex Layout - *******************************/ - - .layout.horizontal, - .layout.horizontal-reverse, - .layout.vertical, - .layout.vertical-reverse { - 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.horizontal-reverse { - -ms-flex-direction: row-reverse; - -webkit-flex-direction: row-reverse; - flex-direction: row-reverse; - } - - .layout.vertical { - -ms-flex-direction: column; - -webkit-flex-direction: column; - flex-direction: column; - } - - .layout.vertical-reverse { - -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; - } - - .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; - } - - .flex, - .flex-1 { - -ms-flex: 1; - -webkit-flex: 1; - flex: 1; - } - - .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; - } - - /* 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; - } - - /* 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: around; - -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; - } - - /******************************* - Other Layout - *******************************/ - - .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><style> - -.paper-font-display4, -.paper-font-display3, -.paper-font-display2, -.paper-font-display1, -.paper-font-headline, -.paper-font-title, -.paper-font-subhead, -.paper-font-body2, -.paper-font-body1, -.paper-font-caption, -.paper-font-menu, -.paper-font-button { - font-family: 'Roboto', 'Noto', sans-serif; - -webkit-font-smoothing: antialiased; /* OS X subpixel AA bleed bug */ -} - -.paper-font-code2, -.paper-font-code1 { - font-family: 'Roboto Mono', 'Consolas', 'Menlo', monospace; - -webkit-font-smoothing: antialiased; /* OS X subpixel AA bleed bug */ -} - -/* Opt for better kerning for headers & other short labels. */ -.paper-font-display4, -.paper-font-display3, -.paper-font-display2, -.paper-font-display1, -.paper-font-headline, -.paper-font-title, -.paper-font-subhead, -.paper-font-menu, -.paper-font-button { - text-rendering: optimizeLegibility; -} - -/* -"Line wrapping only applies to Body, Subhead, Headline, and the smaller Display -styles. All other styles should exist as single lines." -*/ -.paper-font-display4, -.paper-font-display3, -.paper-font-title, -.paper-font-caption, -.paper-font-menu, -.paper-font-button { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.paper-font-display4 { - font-size: 112px; - font-weight: 300; - letter-spacing: -.044em; - line-height: 120px; -} - -.paper-font-display3 { - font-size: 56px; - font-weight: 400; - letter-spacing: -.026em; - line-height: 60px; -} - -.paper-font-display2 { - font-size: 45px; - font-weight: 400; - letter-spacing: -.018em; - line-height: 48px; -} - -.paper-font-display1 { - font-size: 34px; - font-weight: 400; - letter-spacing: -.01em; - line-height: 40px; -} - -.paper-font-headline { - font-size: 24px; - font-weight: 400; - letter-spacing: -.012em; - line-height: 32px; -} - -.paper-font-title { - font-size: 20px; - font-weight: 500; - line-height: 28px; -} - -.paper-font-subhead { - font-size: 16px; - font-weight: 400; - line-height: 24px; -} - -.paper-font-body2 { - font-size: 14px; - font-weight: 500; - line-height: 24px; -} - -.paper-font-body1 { - font-size: 14px; - font-weight: 400; - line-height: 20px; -} - -.paper-font-caption { - font-size: 12px; - font-weight: 400; - letter-spacing: 0.011em; - line-height: 20px; -} - -.paper-font-menu { - font-size: 13px; - font-weight: 500; - line-height: 24px; -} - -.paper-font-button { - font-size: 14px; - font-weight: 500; - letter-spacing: 0.018em; - line-height: 24px; - text-transform: uppercase; -} - -.paper-font-code2 { - font-size: 14px; - font-weight: 700; - line-height: 20px; -} - -.paper-font-code1 { - font-size: 14px; - font-weight: 500; - line-height: 20px; -} - -</style><style> -.shadow-transition { - transition: box-shadow 0.28s cubic-bezier(0.4, 0, 0.2, 1); -} - -.shadow-elevation-2dp { - box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), - 0 1px 5px 0 rgba(0, 0, 0, 0.12), - 0 3px 1px -2px rgba(0, 0, 0, 0.2); -} - -.shadow-elevation-3dp { - box-shadow: 0 3px 4px 0 rgba(0, 0, 0, 0.14), - 0 1px 8px 0 rgba(0, 0, 0, 0.12), - 0 3px 3px -2px rgba(0, 0, 0, 0.4); -} - -.shadow-elevation-4dp { - box-shadow: 0 4px 5px 0 rgba(0, 0, 0, 0.14), - 0 1px 10px 0 rgba(0, 0, 0, 0.12), - 0 2px 4px -1px rgba(0, 0, 0, 0.4); -} - -.shadow-elevation-6dp { - box-shadow: 0 6px 10px 0 rgba(0, 0, 0, 0.14), - 0 1px 18px 0 rgba(0, 0, 0, 0.12), - 0 3px 5px -1px rgba(0, 0, 0, 0.4); -} - -.shadow-elevation-8dp { - box-shadow: 0 8px 10px 1px rgba(0, 0, 0, 0.14), - 0 3px 14px 2px rgba(0, 0, 0, 0.12), - 0 5px 5px -3px rgba(0, 0, 0, 0.4); -} - -.shadow-elevation-16dp { - box-shadow: 0 16px 24px 2px rgba(0, 0, 0, 0.14), - 0 6px 30px 5px rgba(0, 0, 0, 0.12), - 0 8px 10px -5px rgba(0, 0, 0, 0.4); -} - -</style><style is=custom-style> - - :root { - /* - * Light background theme - */ - --light-theme-background-color: #ffffff; - --light-theme-base-color: #000000; - --light-theme-text-color: #212121; - --light-theme-secondary-color: #737373; /* for secondary text and icons */ - --light-theme-disabled-color: #9b9b9b; /* disabled/hint text */ - --light-theme-divider-color: #dbdbdb; - - /* - * Dark background theme - */ - --dark-theme-background-color: #212121; - --dark-theme-base-color: #ffffff; - --dark-theme-text-color: #ffffff; - --dark-theme-secondary-color: #bcbcbc; /* for secondary text and icons */ - --dark-theme-disabled-color: #646464; /* disabled/hint text */ - --dark-theme-divider-color: #3c3c3c; - - /* - * Primary and accent colors. Also see color.html for more colors. - */ - --primary-color: #3f51b5; /* --paper-indigo-500 */ - --light-primary-color: #c5cae9; /* --paper-indigo-100 */ - --dark-primary-color: #303f9f; /* --paper-indigo-700 */ - - --accent-color: #ff4081; /* --paper-pink-a200 */ - --light-accent-color: #ff80ab; /* --paper-pink-a100 */ - --dark-accent-color: #f50057; /* --paper-pink-a400 */ - - /* - * Deprecated values because of their confusing names. - */ - --primary-text-color: var(--light-theme-text-color); - --text-primary-color: var(--dark-theme-text-color); - --primary-background-color: var(--light-theme-background-color); - --secondary-text-color: var(--light-theme-secondary-color); - --disabled-text-color:var(--light-theme-disabled-color); - --divider-color: var(--light-theme-divider-color); - --default-primary-color: var(--primary-color); - } - -</style><script> - (function() { - 'use strict'; - - /** - * Chrome uses an older version of DOM Level 3 Keyboard Events - * - * Most keys are labeled as text, but some are Unicode codepoints. - * Values taken from: http://www.w3.org/TR/2007/WD-DOM-Level-3-Events-20071221/keyset.html#KeySet-Set - */ - var KEY_IDENTIFIER = { - 'U+0008': 'backspace', - 'U+0009': 'tab', - 'U+001B': 'esc', - 'U+0020': 'space', - 'U+007F': 'del' - }; - - /** - * Special table for KeyboardEvent.keyCode. - * KeyboardEvent.keyIdentifier is better, and KeyBoardEvent.key is even better - * than that. - * - * Values from: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent.keyCode#Value_of_keyCode - */ - var KEY_CODE = { - 8: 'backspace', - 9: 'tab', - 13: 'enter', - 27: 'esc', - 33: 'pageup', - 34: 'pagedown', - 35: 'end', - 36: 'home', - 32: 'space', - 37: 'left', - 38: 'up', - 39: 'right', - 40: 'down', - 46: 'del', - 106: '*' - }; - - /** - * MODIFIER_KEYS maps the short name for modifier keys used in a key - * combo string to the property name that references those same keys - * in a KeyboardEvent instance. - */ - var MODIFIER_KEYS = { - 'shift': 'shiftKey', - 'ctrl': 'ctrlKey', - 'alt': 'altKey', - 'meta': 'metaKey' - }; - - /** - * Matches a keyIdentifier string. - */ - var IDENT_CHAR = /U\+/; - - /** - * Matches arrow keys in Gecko 27.0+ - */ - var ARROW_KEY = /^arrow/; - - /** - * Matches space keys everywhere (notably including IE10's exceptional name - * `spacebar`). - */ - var SPACE_KEY = /^space(bar)?/; - - function transformKey(key) { - var validKey = ''; - if (key) { - var lKey = key.toLowerCase(); - if (lKey === ' ' || SPACE_KEY.test(lKey)) { - validKey = 'space'; - } else if (lKey.length == 1) { - validKey = lKey; - } else if (ARROW_KEY.test(lKey)) { - validKey = lKey.replace('arrow', ''); - } else if (lKey == 'multiply') { - // numpad '*' can map to Multiply on IE/Windows - validKey = '*'; - } else { - validKey = lKey; - } - } - return validKey; - } - - function transformKeyIdentifier(keyIdent) { - var validKey = ''; - if (keyIdent) { - if (keyIdent in KEY_IDENTIFIER) { - validKey = KEY_IDENTIFIER[keyIdent]; - } else if (IDENT_CHAR.test(keyIdent)) { - keyIdent = parseInt(keyIdent.replace('U+', '0x'), 16); - validKey = String.fromCharCode(keyIdent).toLowerCase(); - } else { - validKey = keyIdent.toLowerCase(); - } - } - return validKey; - } - - function transformKeyCode(keyCode) { - var validKey = ''; - if (Number(keyCode)) { - if (keyCode >= 65 && keyCode <= 90) { - // ascii a-z - // lowercase is 32 offset from uppercase - validKey = String.fromCharCode(32 + keyCode); - } else if (keyCode >= 112 && keyCode <= 123) { - // function keys f1-f12 - validKey = 'f' + (keyCode - 112); - } else if (keyCode >= 48 && keyCode <= 57) { - // top 0-9 keys - validKey = String(48 - keyCode); - } else if (keyCode >= 96 && keyCode <= 105) { - // num pad 0-9 - validKey = String(96 - keyCode); - } else { - validKey = KEY_CODE[keyCode]; - } - } - return validKey; - } - - function normalizedKeyForEvent(keyEvent) { - // fall back from .key, to .keyIdentifier, to .keyCode, and then to - // .detail.key to support artificial keyboard events - return transformKey(keyEvent.key) || - transformKeyIdentifier(keyEvent.keyIdentifier) || - transformKeyCode(keyEvent.keyCode) || - transformKey(keyEvent.detail.key) || ''; - } - - function keyComboMatchesEvent(keyCombo, event, eventKey) { - return eventKey === keyCombo.key && - (!keyCombo.hasModifiers || ( - !!event.shiftKey === !!keyCombo.shiftKey && - !!event.ctrlKey === !!keyCombo.ctrlKey && - !!event.altKey === !!keyCombo.altKey && - !!event.metaKey === !!keyCombo.metaKey) - ); - } - - function parseKeyComboString(keyComboString) { - if (keyComboString.length === 1) { - return { - combo: keyComboString, - key: keyComboString, - event: 'keydown' - }; - } - return keyComboString.split('+').reduce(function(parsedKeyCombo, keyComboPart) { - var eventParts = keyComboPart.split(':'); - var keyName = eventParts[0]; - var event = eventParts[1]; - - if (keyName in MODIFIER_KEYS) { - parsedKeyCombo[MODIFIER_KEYS[keyName]] = true; - parsedKeyCombo.hasModifiers = true; - } else { - parsedKeyCombo.key = keyName; - parsedKeyCombo.event = event || 'keydown'; - } - - return parsedKeyCombo; - }, { - combo: keyComboString.split(':').shift() - }); - } - - function parseEventString(eventString) { - return eventString.trim().split(' ').map(function(keyComboString) { - return parseKeyComboString(keyComboString); - }); - } - - /** - * `Polymer.IronA11yKeysBehavior` provides a normalized interface for processing - * keyboard commands that pertain to [WAI-ARIA best practices](http://www.w3.org/TR/wai-aria-practices/#kbd_general_binding). - * The element takes care of browser differences with respect to Keyboard events - * and uses an expressive syntax to filter key presses. - * - * Use the `keyBindings` prototype property to express what combination of keys - * will trigger the event to fire. - * - * Use the `key-event-target` attribute to set up event handlers on a specific - * node. - * The `keys-pressed` event will fire when one of the key combinations set with the - * `keys` property is pressed. - * - * @demo demo/index.html - * @polymerBehavior - */ - Polymer.IronA11yKeysBehavior = { - properties: { - /** - * The HTMLElement that will be firing relevant KeyboardEvents. - */ - keyEventTarget: { - type: Object, - value: function() { - return this; - } - }, - - /** - * If true, this property will cause the implementing element to - * automatically stop propagation on any handled KeyboardEvents. - */ - stopKeyboardEventPropagation: { - type: Boolean, - value: false - }, - - _boundKeyHandlers: { - type: Array, - value: function() { - return []; - } - }, - - // We use this due to a limitation in IE10 where instances will have - // own properties of everything on the "prototype". - _imperativeKeyBindings: { - type: Object, - value: function() { - return {}; - } - } - }, - - observers: [ - '_resetKeyEventListeners(keyEventTarget, _boundKeyHandlers)' - ], - - keyBindings: {}, - - registered: function() { - this._prepKeyBindings(); - }, - - attached: function() { - this._listenKeyEventListeners(); - }, - - detached: function() { - this._unlistenKeyEventListeners(); - }, - - /** - * Can be used to imperatively add a key binding to the implementing - * element. This is the imperative equivalent of declaring a keybinding - * in the `keyBindings` prototype property. - */ - addOwnKeyBinding: function(eventString, handlerName) { - this._imperativeKeyBindings[eventString] = handlerName; - this._prepKeyBindings(); - this._resetKeyEventListeners(); - }, - - /** - * When called, will remove all imperatively-added key bindings. - */ - removeOwnKeyBindings: function() { - this._imperativeKeyBindings = {}; - this._prepKeyBindings(); - this._resetKeyEventListeners(); - }, - - keyboardEventMatchesKeys: function(event, eventString) { - var keyCombos = parseEventString(eventString); - var eventKey = normalizedKeyForEvent(event); - for (var i = 0; i < keyCombos.length; ++i) { - if (keyComboMatchesEvent(keyCombos[i], event, eventKey)) { - return true; - } - } - return false; - }, - - _collectKeyBindings: function() { - var keyBindings = this.behaviors.map(function(behavior) { - return behavior.keyBindings; - }); - - if (keyBindings.indexOf(this.keyBindings) === -1) { - keyBindings.push(this.keyBindings); - } - - return keyBindings; - }, - - _prepKeyBindings: function() { - this._keyBindings = {}; - - this._collectKeyBindings().forEach(function(keyBindings) { - for (var eventString in keyBindings) { - this._addKeyBinding(eventString, keyBindings[eventString]); - } - }, this); - - for (var eventString in this._imperativeKeyBindings) { - this._addKeyBinding(eventString, this._imperativeKeyBindings[eventString]); - } - - // Give precedence to combos with modifiers to be checked first. - for (var eventName in this._keyBindings) { - this._keyBindings[eventName].sort(function (kb1, kb2) { - var b1 = kb1[0].hasModifiers; - var b2 = kb2[0].hasModifiers; - return (b1 === b2) ? 0 : b1 ? -1 : 1; - }) - } - }, - - _addKeyBinding: function(eventString, handlerName) { - parseEventString(eventString).forEach(function(keyCombo) { - this._keyBindings[keyCombo.event] = - this._keyBindings[keyCombo.event] || []; - - this._keyBindings[keyCombo.event].push([ - keyCombo, - handlerName - ]); - }, this); - }, - - _resetKeyEventListeners: function() { - this._unlistenKeyEventListeners(); - - if (this.isAttached) { - this._listenKeyEventListeners(); - } - }, - - _listenKeyEventListeners: function() { - Object.keys(this._keyBindings).forEach(function(eventName) { - var keyBindings = this._keyBindings[eventName]; - var boundKeyHandler = this._onKeyBindingEvent.bind(this, keyBindings); - - this._boundKeyHandlers.push([this.keyEventTarget, eventName, boundKeyHandler]); - - this.keyEventTarget.addEventListener(eventName, boundKeyHandler); - }, this); - }, - - _unlistenKeyEventListeners: function() { - var keyHandlerTuple; - var keyEventTarget; - var eventName; - var boundKeyHandler; - - while (this._boundKeyHandlers.length) { - // My kingdom for block-scope binding and destructuring assignment.. - keyHandlerTuple = this._boundKeyHandlers.pop(); - keyEventTarget = keyHandlerTuple[0]; - eventName = keyHandlerTuple[1]; - boundKeyHandler = keyHandlerTuple[2]; - - keyEventTarget.removeEventListener(eventName, boundKeyHandler); - } - }, - - _onKeyBindingEvent: function(keyBindings, event) { - if (this.stopKeyboardEventPropagation) { - event.stopPropagation(); - } - - // if event has been already prevented, don't do anything - if (event.defaultPrevented) { - return; - } - - var eventKey = normalizedKeyForEvent(event); - for (var i = 0; i < keyBindings.length; i++) { - var keyCombo = keyBindings[i][0]; - var handlerName = keyBindings[i][1]; - if (keyComboMatchesEvent(keyCombo, event, eventKey)) { - this._triggerKeyHandler(keyCombo, handlerName, event); - // exit the loop if eventDefault was prevented - if (event.defaultPrevented) { - return; - } - } - } - }, - - _triggerKeyHandler: function(keyCombo, handlerName, keyboardEvent) { - var detail = Object.create(keyCombo); - detail.keyboardEvent = keyboardEvent; - var event = new CustomEvent(keyCombo.event, { - detail: detail, - cancelable: true - }); - this[handlerName].call(this, event); - if (event.defaultPrevented) { - keyboardEvent.preventDefault(); - } - } - }; - })(); -</script><script> - - /** - * @demo demo/index.html - * @polymerBehavior - */ - Polymer.IronControlState = { - - properties: { - - /** - * If true, the element currently has focus. - */ - focused: { - type: Boolean, - value: false, - notify: true, - readOnly: true, - reflectToAttribute: true - }, - - /** - * If true, the user cannot interact with this element. - */ - disabled: { - type: Boolean, - value: false, - notify: true, - observer: '_disabledChanged', - reflectToAttribute: true - }, - - _oldTabIndex: { - type: Number - }, - - _boundFocusBlurHandler: { - type: Function, - value: function() { - return this._focusBlurHandler.bind(this); - } - } - - }, - - observers: [ - '_changedControlState(focused, disabled)' - ], - - ready: function() { - this.addEventListener('focus', this._boundFocusBlurHandler, true); - this.addEventListener('blur', this._boundFocusBlurHandler, true); - }, - - _focusBlurHandler: function(event) { - // NOTE(cdata): if we are in ShadowDOM land, `event.target` will - // eventually become `this` due to retargeting; if we are not in - // ShadowDOM land, `event.target` will eventually become `this` due - // to the second conditional which fires a synthetic event (that is also - // handled). In either case, we can disregard `event.path`. - - if (event.target === this) { - this._setFocused(event.type === 'focus'); - } else if (!this.shadowRoot && !this.isLightDescendant(event.target)) { - this.fire(event.type, {sourceEvent: event}, { - node: this, - bubbles: event.bubbles, - cancelable: event.cancelable - }); - } - }, - - _disabledChanged: function(disabled, old) { - this.setAttribute('aria-disabled', disabled ? 'true' : 'false'); - this.style.pointerEvents = disabled ? 'none' : ''; - if (disabled) { - this._oldTabIndex = this.tabIndex; - this.focused = false; - this.tabIndex = -1; - } else if (this._oldTabIndex !== undefined) { - this.tabIndex = this._oldTabIndex; - } - }, - - _changedControlState: function() { - // _controlStateChanged is abstract, follow-on behaviors may implement it - if (this._controlStateChanged) { - this._controlStateChanged(); - } - } - - }; - -</script><script> - - /** - * @demo demo/index.html - * @polymerBehavior Polymer.IronButtonState - */ - Polymer.IronButtonStateImpl = { - - properties: { - - /** - * If true, the user is currently holding down the button. - */ - pressed: { - type: Boolean, - readOnly: true, - value: false, - reflectToAttribute: true, - observer: '_pressedChanged' - }, - - /** - * If true, the button toggles the active state with each tap or press - * of the spacebar. - */ - toggles: { - type: Boolean, - value: false, - reflectToAttribute: true - }, - - /** - * If true, the button is a toggle and is currently in the active state. - */ - active: { - type: Boolean, - value: false, - notify: true, - reflectToAttribute: true - }, - - /** - * True if the element is currently being pressed by a "pointer," which - * is loosely defined as mouse or touch input (but specifically excluding - * keyboard input). - */ - pointerDown: { - type: Boolean, - readOnly: true, - value: false - }, - - /** - * True if the input device that caused the element to receive focus - * was a keyboard. - */ - receivedFocusFromKeyboard: { - type: Boolean, - readOnly: true - }, - - /** - * The aria attribute to be set if the button is a toggle and in the - * active state. - */ - ariaActiveAttribute: { - type: String, - value: 'aria-pressed', - observer: '_ariaActiveAttributeChanged' - } - }, - - listeners: { - down: '_downHandler', - up: '_upHandler', - tap: '_tapHandler' - }, - - observers: [ - '_detectKeyboardFocus(focused)', - '_activeChanged(active, ariaActiveAttribute)' - ], - - keyBindings: { - 'enter:keydown': '_asyncClick', - 'space:keydown': '_spaceKeyDownHandler', - 'space:keyup': '_spaceKeyUpHandler', - }, - - _mouseEventRe: /^mouse/, - - _tapHandler: function() { - if (this.toggles) { - // a tap is needed to toggle the active state - this._userActivate(!this.active); - } else { - this.active = false; - } - }, - - _detectKeyboardFocus: function(focused) { - this._setReceivedFocusFromKeyboard(!this.pointerDown && focused); - }, - - // to emulate native checkbox, (de-)activations from a user interaction fire - // 'change' events - _userActivate: function(active) { - if (this.active !== active) { - this.active = active; - this.fire('change'); - } - }, - - _downHandler: function(event) { - this._setPointerDown(true); - this._setPressed(true); - this._setReceivedFocusFromKeyboard(false); - }, - - _upHandler: function() { - this._setPointerDown(false); - this._setPressed(false); - }, - - /** - * @param {!KeyboardEvent} event . - */ - _spaceKeyDownHandler: function(event) { - var keyboardEvent = event.detail.keyboardEvent; - var target = Polymer.dom(keyboardEvent).localTarget; - - // Ignore the event if this is coming from a focused light child, since that - // element will deal with it. - if (this.isLightDescendant(/** @type {Node} */(target))) - return; - - keyboardEvent.preventDefault(); - keyboardEvent.stopImmediatePropagation(); - this._setPressed(true); - }, - - /** - * @param {!KeyboardEvent} event . - */ - _spaceKeyUpHandler: function(event) { - var keyboardEvent = event.detail.keyboardEvent; - var target = Polymer.dom(keyboardEvent).localTarget; - - // Ignore the event if this is coming from a focused light child, since that - // element will deal with it. - if (this.isLightDescendant(/** @type {Node} */(target))) - return; - - if (this.pressed) { - this._asyncClick(); - } - this._setPressed(false); - }, - - // trigger click asynchronously, the asynchrony is useful to allow one - // event handler to unwind before triggering another event - _asyncClick: function() { - this.async(function() { - this.click(); - }, 1); - }, - - // any of these changes are considered a change to button state - - _pressedChanged: function(pressed) { - this._changedButtonState(); - }, - - _ariaActiveAttributeChanged: function(value, oldValue) { - if (oldValue && oldValue != value && this.hasAttribute(oldValue)) { - this.removeAttribute(oldValue); - } - }, - - _activeChanged: function(active, ariaActiveAttribute) { - if (this.toggles) { - this.setAttribute(this.ariaActiveAttribute, - active ? 'true' : 'false'); - } else { - this.removeAttribute(this.ariaActiveAttribute); - } - this._changedButtonState(); - }, - - _controlStateChanged: function() { - if (this.disabled) { - this._setPressed(false); - } else { - this._changedButtonState(); - } - }, - - // provide hook for follow-on behaviors to react to button-state - - _changedButtonState: function() { - if (this._buttonStateChanged) { - this._buttonStateChanged(); // abstract - } - } - - }; - - /** @polymerBehavior */ - Polymer.IronButtonState = [ - Polymer.IronA11yKeysBehavior, - Polymer.IronButtonStateImpl - ]; - -</script><script> - - /** - * `Polymer.PaperRippleBehavior` dynamically implements a ripple - * when the element has focus via pointer or keyboard. - * - * NOTE: This behavior is intended to be used in conjunction with and after - * `Polymer.IronButtonState` and `Polymer.IronControlState`. - * - * @polymerBehavior Polymer.PaperRippleBehavior - */ - Polymer.PaperRippleBehavior = { - - properties: { - /** - * If true, the element will not produce a ripple effect when interacted - * with via the pointer. - */ - noink: { - type: Boolean, - observer: '_noinkChanged' - }, - - /** - * @type {Element|undefined} - */ - _rippleContainer: { - type: Object, - } - }, - - /** - * Ensures a `<paper-ripple>` element is available when the element is - * focused. - */ - _buttonStateChanged: function() { - if (this.focused) { - this.ensureRipple(); - } - }, - - /** - * In addition to the functionality provided in `IronButtonState`, ensures - * a ripple effect is created when the element is in a `pressed` state. - */ - _downHandler: function(event) { - Polymer.IronButtonStateImpl._downHandler.call(this, event); - if (this.pressed) { - this.ensureRipple(event); - } - }, - - /** - * Ensures this element contains a ripple effect. For startup efficiency - * the ripple effect is dynamically on demand when needed. - * @param {!Event=} optTriggeringEvent (optional) event that triggered the - * ripple. - */ - ensureRipple: function(optTriggeringEvent) { - if (!this.hasRipple()) { - this._ripple = this._createRipple(); - this._ripple.noink = this.noink; - var rippleContainer = this._rippleContainer || this.root; - if (rippleContainer) { - Polymer.dom(rippleContainer).appendChild(this._ripple); - } - if (optTriggeringEvent) { - // Check if the event happened inside of the ripple container - // Fall back to host instead of the root because distributed text - // nodes are not valid event targets - var domContainer = Polymer.dom(this._rippleContainer || this); - var target = Polymer.dom(optTriggeringEvent).rootTarget; - if (domContainer.deepContains( /** @type {Node} */(target))) { - this._ripple.uiDownAction(optTriggeringEvent); - } - } - } - }, - - /** - * Returns the `<paper-ripple>` element used by this element to create - * ripple effects. The element's ripple is created on demand, when - * necessary, and calling this method will force the - * ripple to be created. - */ - getRipple: function() { - this.ensureRipple(); - return this._ripple; - }, - - /** - * Returns true if this element currently contains a ripple effect. - * @return {boolean} - */ - hasRipple: function() { - return Boolean(this._ripple); - }, - - /** - * Create the element's ripple effect via creating a `<paper-ripple>`. - * Override this method to customize the ripple element. - * @return {!PaperRippleElement} Returns a `<paper-ripple>` element. - */ - _createRipple: function() { - return /** @type {!PaperRippleElement} */ ( - document.createElement('paper-ripple')); - }, - - _noinkChanged: function(noink) { - if (this.hasRipple()) { - this._ripple.noink = noink; - } - } - - }; - -</script><script> - - /** @polymerBehavior Polymer.PaperButtonBehavior */ - Polymer.PaperButtonBehaviorImpl = { - - properties: { - - /** - * The z-depth of this element, from 0-5. Setting to 0 will remove the - * shadow, and each increasing number greater than 0 will be "deeper" - * than the last. - * - * @attribute elevation - * @type number - * @default 1 - */ - elevation: { - type: Number, - reflectToAttribute: true, - readOnly: true - } - - }, - - observers: [ - '_calculateElevation(focused, disabled, active, pressed, receivedFocusFromKeyboard)', - '_computeKeyboardClass(receivedFocusFromKeyboard)' - ], - - hostAttributes: { - role: 'button', - tabindex: '0', - animated: true - }, - - _calculateElevation: function() { - var e = 1; - if (this.disabled) { - e = 0; - } else if (this.active || this.pressed) { - e = 4; - } else if (this.receivedFocusFromKeyboard) { - e = 3; - } - this._setElevation(e); - }, - - _computeKeyboardClass: function(receivedFocusFromKeyboard) { - this.classList.toggle('keyboard-focus', receivedFocusFromKeyboard); - }, - - /** - * In addition to `IronButtonState` behavior, when space key goes down, - * create a ripple down effect. - * - * @param {!KeyboardEvent} event . - */ - _spaceKeyDownHandler: function(event) { - Polymer.IronButtonStateImpl._spaceKeyDownHandler.call(this, event); - if (this.hasRipple()) { - this._ripple.uiDownAction(); - } - }, - - /** - * In addition to `IronButtonState` behavior, when space key goes up, - * create a ripple up effect. - * - * @param {!KeyboardEvent} event . - */ - _spaceKeyUpHandler: function(event) { - Polymer.IronButtonStateImpl._spaceKeyUpHandler.call(this, event); - if (this.hasRipple()) { - this._ripple.uiUpAction(); - } - } - - }; - - /** @polymerBehavior */ - Polymer.PaperButtonBehavior = [ - Polymer.IronButtonState, - Polymer.IronControlState, - Polymer.PaperRippleBehavior, - Polymer.PaperButtonBehaviorImpl - ]; - -</script><script> - - /** - * `Polymer.PaperInkyFocusBehavior` implements a ripple when the element has keyboard focus. - * - * @polymerBehavior Polymer.PaperInkyFocusBehavior - */ - Polymer.PaperInkyFocusBehaviorImpl = { - - observers: [ - '_focusedChanged(receivedFocusFromKeyboard)' - ], - - _focusedChanged: function(receivedFocusFromKeyboard) { - if (receivedFocusFromKeyboard) { - this.ensureRipple(); - } - if (this.hasRipple()) { - this._ripple.holdDown = receivedFocusFromKeyboard; - } - }, - - _createRipple: function() { - var ripple = Polymer.PaperRippleBehavior._createRipple(); - ripple.id = 'ink'; - ripple.setAttribute('center', ''); - ripple.classList.add('circle'); - return ripple; - } - - }; - - /** @polymerBehavior Polymer.PaperInkyFocusBehavior */ - Polymer.PaperInkyFocusBehavior = [ - Polymer.IronButtonState, - Polymer.IronControlState, - Polymer.PaperRippleBehavior, - Polymer.PaperInkyFocusBehaviorImpl - ]; - -</script><script> - /** - * The `iron-iconset-svg` element allows users to define their own icon sets - * that contain svg icons. The svg icon elements should be children of the - * `iron-iconset-svg` element. Multiple icons should be given distinct id's. - * - * Using svg elements to create icons has a few advantages over traditional - * bitmap graphics like jpg or png. Icons that use svg are vector based so - * they are resolution independent and should look good on any device. They - * are stylable via css. Icons can be themed, colorized, and even animated. - * - * Example: - * - * <iron-iconset-svg name="my-svg-icons" size="24"> - * <svg> - * <defs> - * <g id="shape"> - * <rect x="12" y="0" width="12" height="24" /> - * <circle cx="12" cy="12" r="12" /> - * </g> - * </defs> - * </svg> - * </iron-iconset-svg> - * - * This will automatically register the icon set "my-svg-icons" to the iconset - * database. To use these icons from within another element, make a - * `iron-iconset` element and call the `byId` method - * to retrieve a given iconset. To apply a particular icon inside an - * element use the `applyIcon` method. For example: - * - * iconset.applyIcon(iconNode, 'car'); - * - * @element iron-iconset-svg - * @demo demo/index.html - * @implements {Polymer.Iconset} - */ - Polymer({ - is: 'iron-iconset-svg', - - properties: { - - /** - * The name of the iconset. - */ - name: { - type: String, - observer: '_nameChanged' - }, - - /** - * The size of an individual icon. Note that icons must be square. - */ - size: { - type: Number, - value: 24 - } - - }, - - attached: function() { - this.style.display = 'none'; - }, - - /** - * Construct an array of all icon names in this iconset. - * - * @return {!Array} Array of icon names. - */ - getIconNames: function() { - this._icons = this._createIconMap(); - return Object.keys(this._icons).map(function(n) { - return this.name + ':' + n; - }, this); - }, - - /** - * Applies an icon to the given element. - * - * An svg icon is prepended to the element's shadowRoot if it exists, - * otherwise to the element itself. - * - * @method applyIcon - * @param {Element} element Element to which the icon is applied. - * @param {string} iconName Name of the icon to apply. - * @return {?Element} The svg element which renders the icon. - */ - applyIcon: function(element, iconName) { - // insert svg element into shadow root, if it exists - element = element.root || element; - // Remove old svg element - this.removeIcon(element); - // install new svg element - var svg = this._cloneIcon(iconName); - if (svg) { - var pde = Polymer.dom(element); - pde.insertBefore(svg, pde.childNodes[0]); - return element._svgIcon = svg; - } - return null; - }, - - /** - * Remove an icon from the given element by undoing the changes effected - * by `applyIcon`. - * - * @param {Element} element The element from which the icon is removed. - */ - removeIcon: function(element) { - // Remove old svg element - if (element._svgIcon) { - Polymer.dom(element).removeChild(element._svgIcon); - element._svgIcon = null; - } - }, - - /** - * - * When name is changed, register iconset metadata - * - */ - _nameChanged: function() { - new Polymer.IronMeta({type: 'iconset', key: this.name, value: this}); - this.async(function() { - this.fire('iron-iconset-added', this, {node: window}); - }); - }, - - /** - * Create a map of child SVG elements by id. - * - * @return {!Object} Map of id's to SVG elements. - */ - _createIconMap: function() { - // Objects chained to Object.prototype (`{}`) have members. Specifically, - // on FF there is a `watch` method that confuses the icon map, so we - // need to use a null-based object here. - var icons = Object.create(null); - Polymer.dom(this).querySelectorAll('[id]') - .forEach(function(icon) { - icons[icon.id] = icon; - }); - return icons; - }, - - /** - * Produce installable clone of the SVG element matching `id` in this - * iconset, or `undefined` if there is no matching element. - * - * @return {Element} Returns an installable clone of the SVG element - * matching `id`. - */ - _cloneIcon: function(id) { - // create the icon map on-demand, since the iconset itself has no discrete - // signal to know when it's children are fully parsed - this._icons = this._icons || this._createIconMap(); - return this._prepareSvgClone(this._icons[id], this.size); - }, - - /** - * @param {Element} sourceSvg - * @param {number} size - * @return {Element} - */ - _prepareSvgClone: function(sourceSvg, size) { - if (sourceSvg) { - var content = sourceSvg.cloneNode(true), - svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'), - viewBox = content.getAttribute('viewBox') || '0 0 ' + size + ' ' + size; - svg.setAttribute('viewBox', viewBox); - svg.setAttribute('preserveAspectRatio', 'xMidYMid meet'); - // TODO(dfreedm): `pointer-events: none` works around https://crbug.com/370136 - // TODO(sjmiles): inline style may not be ideal, but avoids requiring a shadow-root - svg.style.cssText = 'pointer-events: none; display: block; width: 100%; height: 100%;'; - svg.appendChild(content).removeAttribute('id'); - return svg; - } - return null; - } - - }); -</script><script> -'use strict'; -window.Chat = window.Chat || {}; -Chat.InvitationLinkBehavior = { - properties: { - contact: { - type: String - } - }, - _copy: function(e) { - if (e) { - e.preventDefault(); - e.stopPropagation(); - } - Polymer.Base.create('textarea'); - var copyTextarea = this.textarea; - copyTextarea.value = this.link; - copyTextarea.select(); - try { - var successful = document.execCommand('copy'); - if (successful) { - app.displayToast('Copied invitation link to clipboard. Share it to send files to friends!'); - } - } catch (err) { - console.log('Oops, unable to copy', err); - } - copyTextarea.blur(); - }, - get link() { - return 'http://' + window.location.host + '/' + this.contact; - }, - get textarea() { - var textarea = document.querySelector('#copytextarea'); - if (!textarea) { - textarea = Polymer.Base.create('textarea'); - textarea.id = 'copytextarea'; - var style = textarea.style; - style.position = 'absolute'; - style.top = '-10000px'; - document.body.appendChild(textarea); - } - return textarea; - } -}; -</script><script>function MakePromise (asap) { - function Promise(fn) { - if (typeof this !== 'object' || typeof fn !== 'function') throw new TypeError(); - this._state = null; - this._value = null; - this._deferreds = [] - - doResolve(fn, resolve.bind(this), reject.bind(this)); - } - - function handle(deferred) { - var me = this; - if (this._state === null) { - this._deferreds.push(deferred); - return - } - asap(function() { - var cb = me._state ? deferred.onFulfilled : deferred.onRejected - if (typeof cb !== 'function') { - (me._state ? deferred.resolve : deferred.reject)(me._value); - return; - } - var ret; - try { - ret = cb(me._value); - } - catch (e) { - deferred.reject(e); - return; - } - deferred.resolve(ret); - }) - } - - function resolve(newValue) { - try { //Promise Resolution Procedure: https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure - if (newValue === this) throw new TypeError(); - if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) { - var then = newValue.then; - if (typeof then === 'function') { - doResolve(then.bind(newValue), resolve.bind(this), reject.bind(this)); - return; - } - } - this._state = true; - this._value = newValue; - finale.call(this); - } catch (e) { reject.call(this, e); } - } - - function reject(newValue) { - this._state = false; - this._value = newValue; - finale.call(this); - } - - function finale() { - for (var i = 0, len = this._deferreds.length; i < len; i++) { - handle.call(this, this._deferreds[i]); - } - this._deferreds = null; - } - - /** - * Take a potentially misbehaving resolver function and make sure - * onFulfilled and onRejected are only called once. - * - * Makes no guarantees about asynchrony. - */ - function doResolve(fn, onFulfilled, onRejected) { - var done = false; - try { - fn(function (value) { - if (done) return; - done = true; - onFulfilled(value); - }, function (reason) { - if (done) return; - done = true; - onRejected(reason); - }) - } catch (ex) { - if (done) return; - done = true; - onRejected(ex); - } - } - - Promise.prototype['catch'] = function (onRejected) { - return this.then(null, onRejected); - }; - - Promise.prototype.then = function(onFulfilled, onRejected) { - var me = this; - return new Promise(function(resolve, reject) { - handle.call(me, { - onFulfilled: onFulfilled, - onRejected: onRejected, - resolve: resolve, - reject: reject - }); - }) - }; - - Promise.resolve = function (value) { - if (value && typeof value === 'object' && value.constructor === Promise) { - return value; - } - - return new Promise(function (resolve) { - resolve(value); - }); - }; - - Promise.reject = function (value) { - return new Promise(function (resolve, reject) { - reject(value); - }); - }; - - - return Promise; -} - -if (typeof module !== 'undefined') { - module.exports = MakePromise; -} - -</script><script> -if (!window.Promise) { - window.Promise = MakePromise(Polymer.Base.async); -} -</script><script> - 'use strict' - - Polymer({ - is: 'iron-request', - - hostAttributes: { - hidden: true - }, - - properties: { - - /** - * A reference to the XMLHttpRequest instance used to generate the - * network request. - * - * @type {XMLHttpRequest} - */ - xhr: { - type: Object, - notify: true, - readOnly: true, - value: function() { - return new XMLHttpRequest(); - } - }, - - /** - * A reference to the parsed response body, if the `xhr` has completely - * resolved. - * - * @type {*} - * @default null - */ - response: { - type: Object, - notify: true, - readOnly: true, - value: function() { - return null; - } - }, - - /** - * A reference to the status code, if the `xhr` has completely resolved. - */ - status: { - type: Number, - notify: true, - readOnly: true, - value: 0 - }, - - /** - * A reference to the status text, if the `xhr` has completely resolved. - */ - statusText: { - type: String, - notify: true, - readOnly: true, - value: '' - }, - - /** - * A promise that resolves when the `xhr` response comes back, or rejects - * if there is an error before the `xhr` completes. - * - * @type {Promise} - */ - completes: { - type: Object, - readOnly: true, - notify: true, - value: function() { - return new Promise(function (resolve, reject) { - this.resolveCompletes = resolve; - this.rejectCompletes = reject; - }.bind(this)); - } - }, - - /** - * An object that contains progress information emitted by the XHR if - * available. - * - * @default {} - */ - progress: { - type: Object, - notify: true, - readOnly: true, - value: function() { - return {}; - } - }, - - /** - * Aborted will be true if an abort of the request is attempted. - */ - aborted: { - type: Boolean, - notify: true, - readOnly: true, - value: false, - }, - - /** - * Errored will be true if the browser fired an error event from the - * XHR object (mainly network errors). - */ - errored: { - type: Boolean, - notify: true, - readOnly: true, - value: false - }, - - /** - * TimedOut will be true if the XHR threw a timeout event. - */ - timedOut: { - type: Boolean, - notify: true, - readOnly: true, - value: false - } - }, - - /** - * Succeeded is true if the request succeeded. The request succeeded if it - * loaded without error, wasn't aborted, and the status code is ≥ 200, and - * < 300, or if the status code is 0. - * - * The status code 0 is accepted as a success because some schemes - e.g. - * file:// - don't provide status codes. - * - * @return {boolean} - */ - get succeeded() { - if (this.errored || this.aborted || this.timedOut) { - return false; - } - var status = this.xhr.status || 0; - - // Note: if we are using the file:// protocol, the status code will be 0 - // for all outcomes (successful or otherwise). - return status === 0 || - (status >= 200 && status < 300); - }, - - /** - * Sends an HTTP request to the server and returns the XHR object. - * - * @param {{ - * url: string, - * method: (string|undefined), - * async: (boolean|undefined), - * body: (ArrayBuffer|ArrayBufferView|Blob|Document|FormData|null|string|undefined|Object), - * headers: (Object|undefined), - * handleAs: (string|undefined), - * jsonPrefix: (string|undefined), - * withCredentials: (boolean|undefined)}} options - - * url The url to which the request is sent. - * method The HTTP method to use, default is GET. - * async By default, all requests are sent asynchronously. To send synchronous requests, - * set to true. - * body The content for the request body for POST method. - * headers HTTP request headers. - * handleAs The response type. Default is 'text'. - * withCredentials Whether or not to send credentials on the request. Default is false. - * timeout: (Number|undefined) - * @return {Promise} - */ - send: function (options) { - var xhr = this.xhr; - - if (xhr.readyState > 0) { - return null; - } - - xhr.addEventListener('progress', function (progress) { - this._setProgress({ - lengthComputable: progress.lengthComputable, - loaded: progress.loaded, - total: progress.total - }); - }.bind(this)) - - xhr.addEventListener('error', function (error) { - this._setErrored(true); - this._updateStatus(); - this.rejectCompletes(error); - }.bind(this)); - - xhr.addEventListener('timeout', function (error) { - this._setTimedOut(true); - this._updateStatus(); - this.rejectCompletes(error); - }.bind(this)); - - xhr.addEventListener('abort', function () { - this._updateStatus(); - this.rejectCompletes(new Error('Request aborted.')); - }.bind(this)); - - - // Called after all of the above. - xhr.addEventListener('loadend', function () { - this._updateStatus(); - - if (!this.succeeded) { - this.rejectCompletes(new Error('The request failed with status code: ' + this.xhr.status)); - return; - } - - this._setResponse(this.parseResponse()); - this.resolveCompletes(this); - }.bind(this)); - - this.url = options.url; - xhr.open( - options.method || 'GET', - options.url, - options.async !== false - ); - - var acceptType = { - 'json': 'application/json', - 'text': 'text/plain', - 'html': 'text/html', - 'xml': 'application/xml', - 'arraybuffer': 'application/octet-stream' - }[options.handleAs]; - var headers = options.headers || Object.create(null); - var newHeaders = Object.create(null); - for (var key in headers) { - newHeaders[key.toLowerCase()] = headers[key]; - } - headers = newHeaders; - - if (acceptType && !headers['accept']) { - headers['accept'] = acceptType; - } - Object.keys(headers).forEach(function (requestHeader) { - if (/[A-Z]/.test(requestHeader)) { - console.error('Headers must be lower case, got', requestHeader); - } - xhr.setRequestHeader( - requestHeader, - headers[requestHeader] - ); - }, this); - - if (options.async !== false) { - var handleAs = options.handleAs; - - // If a JSON prefix is present, the responseType must be 'text' or the - // browser won’t be able to parse the response. - if (!!options.jsonPrefix || !handleAs) { - handleAs = 'text'; - } - - // In IE, `xhr.responseType` is an empty string when the response - // returns. Hence, caching it as `xhr._responseType`. - xhr.responseType = xhr._responseType = handleAs; - - // Cache the JSON prefix, if it exists. - if (!!options.jsonPrefix) { - xhr._jsonPrefix = options.jsonPrefix; - } - } - - xhr.withCredentials = !!options.withCredentials; - xhr.timeout = options.timeout; - - var body = this._encodeBodyObject(options.body, headers['content-type']); - - xhr.send( - /** @type {ArrayBuffer|ArrayBufferView|Blob|Document|FormData| - null|string|undefined} */ - (body)); - - return this.completes; - }, - - /** - * Attempts to parse the response body of the XHR. If parsing succeeds, - * the value returned will be deserialized based on the `responseType` - * set on the XHR. - * - * @return {*} The parsed response, - * or undefined if there was an empty response or parsing failed. - */ - parseResponse: function () { - var xhr = this.xhr; - var responseType = xhr.responseType || xhr._responseType; - var preferResponseText = !this.xhr.responseType; - var prefixLen = (xhr._jsonPrefix && xhr._jsonPrefix.length) || 0; - - try { - switch (responseType) { - case 'json': - // If the xhr object doesn't have a natural `xhr.responseType`, - // we can assume that the browser hasn't parsed the response for us, - // and so parsing is our responsibility. Likewise if response is - // undefined, as there's no way to encode undefined in JSON. - if (preferResponseText || xhr.response === undefined) { - // Try to emulate the JSON section of the response body section of - // the spec: https://xhr.spec.whatwg.org/#response-body - // That is to say, we try to parse as JSON, but if anything goes - // wrong return null. - try { - return JSON.parse(xhr.responseText); - } catch (_) { - return null; - } - } - - return xhr.response; - case 'xml': - return xhr.responseXML; - case 'blob': - case 'document': - case 'arraybuffer': - return xhr.response; - case 'text': - default: { - // If `prefixLen` is set, it implies the response should be parsed - // as JSON once the prefix of length `prefixLen` is stripped from - // it. Emulate the behavior above where null is returned on failure - // to parse. - if (prefixLen) { - try { - return JSON.parse(xhr.responseText.substring(prefixLen)); - } catch (_) { - return null; - } - } - return xhr.responseText; - } - } - } catch (e) { - this.rejectCompletes(new Error('Could not parse response. ' + e.message)); - } - }, - - /** - * Aborts the request. - */ - abort: function () { - this._setAborted(true); - this.xhr.abort(); - }, - - /** - * @param {*} body The given body of the request to try and encode. - * @param {?string} contentType The given content type, to infer an encoding - * from. - * @return {*} Either the encoded body as a string, if successful, - * or the unaltered body object if no encoding could be inferred. - */ - _encodeBodyObject: function(body, contentType) { - if (typeof body == 'string') { - return body; // Already encoded. - } - var bodyObj = /** @type {Object} */ (body); - switch(contentType) { - case('application/json'): - return JSON.stringify(bodyObj); - case('application/x-www-form-urlencoded'): - return this._wwwFormUrlEncode(bodyObj); - } - return body; - }, - - /** - * @param {Object} object The object to encode as x-www-form-urlencoded. - * @return {string} . - */ - _wwwFormUrlEncode: function(object) { - if (!object) { - return ''; - } - var pieces = []; - Object.keys(object).forEach(function(key) { - // TODO(rictic): handle array values here, in a consistent way with - // iron-ajax params. - pieces.push( - this._wwwFormUrlEncodePiece(key) + '=' + - this._wwwFormUrlEncodePiece(object[key])); - }, this); - return pieces.join('&'); - }, - - /** - * @param {*} str A key or value to encode as x-www-form-urlencoded. - * @return {string} . - */ - _wwwFormUrlEncodePiece: function(str) { - // Spec says to normalize newlines to \r\n and replace %20 spaces with +. - // jQuery does this as well, so this is likely to be widely compatible. - return encodeURIComponent(str.toString().replace(/\r?\n/g, '\r\n')) - .replace(/%20/g, '+'); - }, - - /** - * Updates the status code and status text. - */ - _updateStatus: function() { - this._setStatus(this.xhr.status); - this._setStatusText((this.xhr.statusText === undefined) ? '' : this.xhr.statusText); - } - }); -</script><script> - 'use strict'; - - Polymer({ - - is: 'iron-ajax', - - /** - * Fired when a request is sent. - * - * @event request - */ - - /** - * Fired when a response is received. - * - * @event response - */ - - /** - * Fired when an error is received. - * - * @event error - */ - - hostAttributes: { - hidden: true - }, - - properties: { - /** - * The URL target of the request. - */ - url: { - type: String - }, - - /** - * An object that contains query parameters to be appended to the - * specified `url` when generating a request. If you wish to set the body - * content when making a POST request, you should use the `body` property - * instead. - */ - params: { - type: Object, - value: function() { - return {}; - } - }, - - /** - * The HTTP method to use such as 'GET', 'POST', 'PUT', or 'DELETE'. - * Default is 'GET'. - */ - method: { - type: String, - value: 'GET' - }, - - /** - * HTTP request headers to send. - * - * Example: - * - * <iron-ajax - * auto - * url="http://somesite.com" - * headers='{"X-Requested-With": "XMLHttpRequest"}' - * handle-as="json"></iron-ajax> - * - * Note: setting a `Content-Type` header here will override the value - * specified by the `contentType` property of this element. - */ - headers: { - type: Object, - value: function() { - return {}; - } - }, - - /** - * Content type to use when sending data. If the `contentType` property - * is set and a `Content-Type` header is specified in the `headers` - * property, the `headers` property value will take precedence. - */ - contentType: { - type: String, - value: null - }, - - /** - * Body content to send with the request, typically used with "POST" - * requests. - * - * If body is a string it will be sent unmodified. - * - * If Content-Type is set to a value listed below, then - * the body will be encoded accordingly. - * - * * `content-type="application/json"` - * * body is encoded like `{"foo":"bar baz","x":1}` - * * `content-type="application/x-www-form-urlencoded"` - * * body is encoded like `foo=bar+baz&x=1` - * - * Otherwise the body will be passed to the browser unmodified, and it - * will handle any encoding (e.g. for FormData, Blob, ArrayBuffer). - * - * @type (ArrayBuffer|ArrayBufferView|Blob|Document|FormData|null|string|undefined|Object) - */ - body: { - type: Object, - value: null - }, - - /** - * Toggle whether XHR is synchronous or asynchronous. Don't change this - * to true unless You Know What You Are Doing™. - */ - sync: { - type: Boolean, - value: false - }, - - /** - * Specifies what data to store in the `response` property, and - * to deliver as `event.detail.response` in `response` events. - * - * One of: - * - * `text`: uses `XHR.responseText`. - * - * `xml`: uses `XHR.responseXML`. - * - * `json`: uses `XHR.responseText` parsed as JSON. - * - * `arraybuffer`: uses `XHR.response`. - * - * `blob`: uses `XHR.response`. - * - * `document`: uses `XHR.response`. - */ - handleAs: { - type: String, - value: 'json' - }, - - /** - * Set the withCredentials flag on the request. - */ - withCredentials: { - type: Boolean, - value: false - }, - - /** - * Set the timeout flag on the request. - */ - timeout: { - type: Number, - value: 0 - }, - - /** - * If true, automatically performs an Ajax request when either `url` or - * `params` changes. - */ - auto: { - type: Boolean, - value: false - }, - - /** - * If true, error messages will automatically be logged to the console. - */ - verbose: { - type: Boolean, - value: false - }, - - /** - * The most recent request made by this iron-ajax element. - */ - lastRequest: { - type: Object, - notify: true, - readOnly: true - }, - - /** - * True while lastRequest is in flight. - */ - loading: { - type: Boolean, - notify: true, - readOnly: true - }, - - /** - * lastRequest's response. - * - * Note that lastResponse and lastError are set when lastRequest finishes, - * so if loading is true, then lastResponse and lastError will correspond - * to the result of the previous request. - * - * The type of the response is determined by the value of `handleAs` at - * the time that the request was generated. - * - * @type {Object} - */ - lastResponse: { - type: Object, - notify: true, - readOnly: true - }, - - /** - * lastRequest's error, if any. - * - * @type {Object} - */ - lastError: { - type: Object, - notify: true, - readOnly: true - }, - - /** - * An Array of all in-flight requests originating from this iron-ajax - * element. - */ - activeRequests: { - type: Array, - notify: true, - readOnly: true, - value: function() { - return []; - } - }, - - /** - * Length of time in milliseconds to debounce multiple automatically generated requests. - */ - debounceDuration: { - type: Number, - value: 0, - notify: true - }, - - /** - * Prefix to be stripped from a JSON response before parsing it. - * - * In order to prevent an attack using CSRF with Array responses - * (http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx/) - * many backends will mitigate this by prefixing all JSON response bodies - * with a string that would be nonsensical to a JavaScript parser. - * - */ - jsonPrefix: { - type: String, - value: '' - }, - - _boundHandleResponse: { - type: Function, - value: function() { - return this._handleResponse.bind(this); - } - } - }, - - observers: [ - '_requestOptionsChanged(url, method, params.*, headers, contentType, ' + - 'body, sync, handleAs, jsonPrefix, withCredentials, timeout, auto)' - ], - - /** - * The query string that should be appended to the `url`, serialized from - * the current value of `params`. - * - * @return {string} - */ - get queryString () { - var queryParts = []; - var param; - var value; - - for (param in this.params) { - value = this.params[param]; - param = window.encodeURIComponent(param); - - if (Array.isArray(value)) { - for (var i = 0; i < value.length; i++) { - queryParts.push(param + '=' + window.encodeURIComponent(value[i])); - } - } else if (value !== null) { - queryParts.push(param + '=' + window.encodeURIComponent(value)); - } else { - queryParts.push(param); - } - } - - return queryParts.join('&'); - }, - - /** - * The `url` with query string (if `params` are specified), suitable for - * providing to an `iron-request` instance. - * - * @return {string} - */ - get requestUrl() { - var queryString = this.queryString; - - if (queryString) { - var bindingChar = this.url.indexOf('?') >= 0 ? '&' : '?'; - return this.url + bindingChar + queryString; - } - - return this.url; - }, - - /** - * An object that maps header names to header values, first applying the - * the value of `Content-Type` and then overlaying the headers specified - * in the `headers` property. - * - * @return {Object} - */ - get requestHeaders() { - var headers = {}; - var contentType = this.contentType; - if (contentType == null && (typeof this.body === 'string')) { - contentType = 'application/x-www-form-urlencoded'; - } - if (contentType) { - headers['content-type'] = contentType; - } - var header; - - if (this.headers instanceof Object) { - for (header in this.headers) { - headers[header] = this.headers[header].toString(); - } - } - - return headers; - }, - - /** - * Request options suitable for generating an `iron-request` instance based - * on the current state of the `iron-ajax` instance's properties. - * - * @return {{ - * url: string, - * method: (string|undefined), - * async: (boolean|undefined), - * body: (ArrayBuffer|ArrayBufferView|Blob|Document|FormData|null|string|undefined|Object), - * headers: (Object|undefined), - * handleAs: (string|undefined), - * jsonPrefix: (string|undefined), - * withCredentials: (boolean|undefined)}} - */ - toRequestOptions: function() { - return { - url: this.requestUrl || '', - method: this.method, - headers: this.requestHeaders, - body: this.body, - async: !this.sync, - handleAs: this.handleAs, - jsonPrefix: this.jsonPrefix, - withCredentials: this.withCredentials, - timeout: this.timeout - }; - }, - - /** - * Performs an AJAX request to the specified URL. - * - * @return {!IronRequestElement} - */ - generateRequest: function() { - var request = /** @type {!IronRequestElement} */ (document.createElement('iron-request')); - var requestOptions = this.toRequestOptions(); - - this.activeRequests.push(request); - - request.completes.then( - this._boundHandleResponse - ).catch( - this._handleError.bind(this, request) - ).then( - this._discardRequest.bind(this, request) - ); - - request.send(requestOptions); - - this._setLastRequest(request); - this._setLoading(true); - - this.fire('request', { - request: request, - options: requestOptions - }, {bubbles: false}); - - return request; - }, - - _handleResponse: function(request) { - if (request === this.lastRequest) { - this._setLastResponse(request.response); - this._setLastError(null); - this._setLoading(false); - } - this.fire('response', request, {bubbles: false}); - }, - - _handleError: function(request, error) { - if (this.verbose) { - console.error(error); - } - - if (request === this.lastRequest) { - this._setLastError({ - request: request, - error: error - }); - this._setLastResponse(null); - this._setLoading(false); - } - this.fire('error', { - request: request, - error: error - }, {bubbles: false}); - }, - - _discardRequest: function(request) { - var requestIndex = this.activeRequests.indexOf(request); - - if (requestIndex > -1) { - this.activeRequests.splice(requestIndex, 1); - } - }, - - _requestOptionsChanged: function() { - this.debounce('generate-request', function() { - if (this.url == null) { - return; - } - - if (this.auto) { - this.generateRequest(); - } - }, this.debounceDuration); - }, - - }); -</script><style is=custom-style> - - :root { - - --shadow-transition: { - transition: box-shadow 0.28s cubic-bezier(0.4, 0, 0.2, 1); - }; - - --shadow-none: { - box-shadow: none; - }; - - /* from http://codepen.io/shyndman/pen/c5394ddf2e8b2a5c9185904b57421cdb */ - - --shadow-elevation-2dp: { - box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), - 0 1px 5px 0 rgba(0, 0, 0, 0.12), - 0 3px 1px -2px rgba(0, 0, 0, 0.2); - }; - - --shadow-elevation-3dp: { - box-shadow: 0 3px 4px 0 rgba(0, 0, 0, 0.14), - 0 1px 8px 0 rgba(0, 0, 0, 0.12), - 0 3px 3px -2px rgba(0, 0, 0, 0.4); - }; - - --shadow-elevation-4dp: { - box-shadow: 0 4px 5px 0 rgba(0, 0, 0, 0.14), - 0 1px 10px 0 rgba(0, 0, 0, 0.12), - 0 2px 4px -1px rgba(0, 0, 0, 0.4); - }; - - --shadow-elevation-6dp: { - box-shadow: 0 6px 10px 0 rgba(0, 0, 0, 0.14), - 0 1px 18px 0 rgba(0, 0, 0, 0.12), - 0 3px 5px -1px rgba(0, 0, 0, 0.4); - }; - - --shadow-elevation-8dp: { - box-shadow: 0 8px 10px 1px rgba(0, 0, 0, 0.14), - 0 3px 14px 2px rgba(0, 0, 0, 0.12), - 0 5px 5px -3px rgba(0, 0, 0, 0.4); - }; - - --shadow-elevation-16dp: { - box-shadow: 0 16px 24px 2px rgba(0, 0, 0, 0.14), - 0 6px 30px 5px rgba(0, 0, 0, 0.12), - 0 8px 10px -5px rgba(0, 0, 0, 0.4); - }; - - } - -</style><script> -'use strict'; -window.Chat = window.Chat || {}; -Chat.FileSelectionBehavior = { - notifyFilesSelection: function(files) { - if (!files) { - console.log('no files selected...'); - return; - } - this._fileSelected(files[0]); //single select - //files.forEach(this._fileSelected.bind(this)); //multi-select - }, - _fileSelected: function(file) { - if (file) { - this.fire('file-selected', { - file: file, - name: file.name - }); - } - } -}; -</script><script> -'use strict'; -window.Chat = window.Chat || {}; -Chat.FileDropBehaviorImpl = { - attached: function() { - var dropZone = this; - - dropZone.addEventListener('dragover', function(e) { - e.stopPropagation(); - e.preventDefault(); - e.dataTransfer.dropEffect = 'copy'; - dropZone.style.transform = 'scale(1.2)'; - }, false); - - var dragEnd = function() { - dropZone.style.transform = 'scale(1)'; - }; - - dropZone.addEventListener('dragleave', dragEnd, false); - dropZone.addEventListener('dragexit', dragEnd, false); - dropZone.addEventListener('dragend', dragEnd, false); - - // Get file data on drop - dropZone.addEventListener('drop', function(event) { - event.stopPropagation(); - event.preventDefault(); - - //drop is a dragend - dragEnd(); - - // Get files - var files = event.dataTransfer.files; - // Notify Selection - this.notifyFilesSelection(files); - }); - } -}; -document.body.addEventListener('dragover', function(e) { - e.stopPropagation(); - e.preventDefault(); -}, false); -document.body.addEventListener('drop', function(event) { - event.stopPropagation(); - event.preventDefault(); -}); -Chat.FileDropBehavior = [Chat.FileDropBehaviorImpl, Chat.FileSelectionBehavior]; -</script><script> -'use strict'; -window.Chat = window.Chat || {}; -Chat.FileButtonBehaviorImpl = { - get fileInput() { - var fileInput = Polymer.dom(this).querySelector('.fileInput'); - if (!fileInput) { - fileInput = document.createElement('input'); - fileInput.type = 'file'; - fileInput.multiple = 'false'; - fileInput.className = 'fileInput'; - fileInput.style.position = 'fixed'; - fileInput.style.top = '-10000px'; - fileInput.style.left = '-10000px'; - fileInput.style.opacity = 0; - Polymer.dom(this).appendChild(fileInput); - } - return fileInput; - }, - attached: function() { - this.fileInput.onchange = function() { - var files = this.fileInput.files; - this.notifyFilesSelection(files); - }.bind(this); - this.addEventListener('click', function(e) { - var button = e.which || e.button; - if (button !== 1) { - return; - } - this.fileInput.value = null; - this.fileInput.click(); - }.bind(this), false); - } -}; -Chat.FileButtonBehavior = [Chat.FileButtonBehaviorImpl, Chat.FileSelectionBehavior]; -</script><script> - 'use strict'; - Chat.FileInputBehavior = [Chat.FileDropBehavior,Chat.FileButtonBehavior]; -</script><script> - -/** -Use `Polymer.PaperDialogBehavior` and `paper-dialog-shared-styles.html` to implement a Material Design -dialog. - -For example, if `<paper-dialog-impl>` implements this behavior: - - <paper-dialog-impl> - <h2>Header</h2> - <div>Dialog body</div> - <div class="buttons"> - <paper-button dialog-dismiss>Cancel</paper-button> - <paper-button dialog-confirm>Accept</paper-button> - </div> - </paper-dialog-impl> - -`paper-dialog-shared-styles.html` provide styles for a header, content area, and an action area for buttons. -Use the `<h2>` tag for the header and the `buttons` class for the action area. You can use the -`paper-dialog-scrollable` element (in its own repository) if you need a scrolling content area. - -Use the `dialog-dismiss` and `dialog-confirm` attributes on interactive controls to close the -dialog. If the user dismisses the dialog with `dialog-confirm`, the `closingReason` will update -to include `confirmed: true`. - -### Styling - -The following custom properties and mixins are available for styling. - -Custom property | Description | Default -----------------|-------------|---------- -`--paper-dialog-background-color` | Dialog background color | `--primary-background-color` -`--paper-dialog-color` | Dialog foreground color | `--primary-text-color` -`--paper-dialog` | Mixin applied to the dialog | `{}` -`--paper-dialog-title` | Mixin applied to the title (`<h2>`) element | `{}` -`--paper-dialog-button-color` | Button area foreground color | `--default-primary-color` - -### Accessibility - -This element has `role="dialog"` by default. Depending on the context, it may be more appropriate -to override this attribute with `role="alertdialog"`. - -If `modal` is set, the element will set `aria-modal` and prevent the focus from exiting the element. -It will also ensure that focus remains in the dialog. - -The `aria-labelledby` attribute will be set to the header element, if one exists. - -@hero hero.svg -@demo demo/index.html -@polymerBehavior Polymer.PaperDialogBehavior -*/ - - Polymer.PaperDialogBehaviorImpl = { - - hostAttributes: { - 'role': 'dialog', - 'tabindex': '-1' - }, - - properties: { - - /** - * If `modal` is true, this implies `no-cancel-on-outside-click` and `with-backdrop`. - */ - modal: { - observer: '_modalChanged', - type: Boolean, - value: false - }, - - /** @type {?Node} */ - _lastFocusedElement: { - type: Object - }, - - _boundOnFocus: { - type: Function, - value: function() { - return this._onFocus.bind(this); - } - }, - - _boundOnBackdropClick: { - type: Function, - value: function() { - return this._onBackdropClick.bind(this); - } - } - - }, - - listeners: { - 'tap': '_onDialogClick', - 'iron-overlay-opened': '_onIronOverlayOpened', - 'iron-overlay-closed': '_onIronOverlayClosed' - }, - - attached: function() { - this._observer = this._observe(this); - this._updateAriaLabelledBy(); - }, - - detached: function() { - if (this._observer) { - this._observer.disconnect(); - } - }, - - _observe: function(node) { - var observer = new MutationObserver(function() { - this._updateAriaLabelledBy(); - }.bind(this)); - observer.observe(node, { - childList: true, - subtree: true - }); - return observer; - }, - - _modalChanged: function() { - if (this.modal) { - this.setAttribute('aria-modal', 'true'); - } else { - this.setAttribute('aria-modal', 'false'); - } - // modal implies noCancelOnOutsideClick and withBackdrop if true, don't overwrite - // those properties otherwise. - if (this.modal) { - this.noCancelOnOutsideClick = true; - this.withBackdrop = true; - } - }, - - _updateAriaLabelledBy: function() { - var header = Polymer.dom(this).querySelector('h2'); - if (!header) { - this.removeAttribute('aria-labelledby'); - return; - } - var headerId = header.getAttribute('id'); - if (headerId && this.getAttribute('aria-labelledby') === headerId) { - return; - } - // set aria-describedBy to the header element - var labelledById; - if (headerId) { - labelledById = headerId; - } else { - labelledById = 'paper-dialog-header-' + new Date().getUTCMilliseconds(); - header.setAttribute('id', labelledById); - } - this.setAttribute('aria-labelledby', labelledById); - }, - - _updateClosingReasonConfirmed: function(confirmed) { - this.closingReason = this.closingReason || {}; - this.closingReason.confirmed = confirmed; - }, - - _onDialogClick: function(event) { - var target = event.target; - while (target && target !== this) { - if (target.hasAttribute) { - if (target.hasAttribute('dialog-dismiss')) { - this._updateClosingReasonConfirmed(false); - this.close(); - break; - } else if (target.hasAttribute('dialog-confirm')) { - this._updateClosingReasonConfirmed(true); - this.close(); - break; - } - } - target = target.parentNode; - } - }, - - _onIronOverlayOpened: function() { - if (this.modal) { - document.body.addEventListener('focus', this._boundOnFocus, true); - this.backdropElement.addEventListener('click', this._boundOnBackdropClick); - } - }, - - _onIronOverlayClosed: function() { - document.body.removeEventListener('focus', this._boundOnFocus, true); - this.backdropElement.removeEventListener('click', this._boundOnBackdropClick); - }, - - _onFocus: function(event) { - if (this.modal) { - var target = event.target; - while (target && target !== this && target !== document.body) { - target = target.parentNode; - } - if (target) { - if (target === document.body) { - if (this._lastFocusedElement) { - this._lastFocusedElement.focus(); - } else { - this._focusNode.focus(); - } - } else { - this._lastFocusedElement = event.target; - } - } - } - }, - - _onBackdropClick: function() { - if (this.modal) { - if (this._lastFocusedElement) { - this._lastFocusedElement.focus(); - } else { - this._focusNode.focus(); - } - } - } - - }; - - /** @polymerBehavior */ - Polymer.PaperDialogBehavior = [Polymer.IronOverlayBehavior, Polymer.PaperDialogBehaviorImpl]; - -</script><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><script> - - /** - * `Use Polymer.IronValidatableBehavior` to implement an element that validates user input. - * Use the related `Polymer.IronValidatorBehavior` to add custom validation logic to an iron-input. - * - * By default, an `<iron-form>` element validates its fields when the user presses the submit button. - * To validate a form imperatively, call the form's `validate()` method, which in turn will - * call `validate()` on all its children. By using `Polymer.IronValidatableBehavior`, your - * custom element will get a public `validate()`, which - * will return the validity of the element, and a corresponding `invalid` attribute, - * which can be used for styling. - * - * To implement the custom validation logic of your element, you must override - * the protected `_getValidity()` method of this behaviour, rather than `validate()`. - * See [this](https://github.com/PolymerElements/iron-form/blob/master/demo/simple-element.html) - * for an example. - * - * ### Accessibility - * - * Changing the `invalid` property, either manually or by calling `validate()` will update the - * `aria-invalid` attribute. - * - * @demo demo/index.html - * @polymerBehavior - */ - Polymer.IronValidatableBehavior = { - - properties: { - - /** - * Namespace for this validator. - */ - validatorType: { - type: String, - value: 'validator' - }, - - /** - * Name of the validator to use. - */ - validator: { - type: String - }, - - /** - * True if the last call to `validate` is invalid. - */ - invalid: { - notify: true, - reflectToAttribute: true, - type: Boolean, - value: false - }, - - _validatorMeta: { - type: Object - } - - }, - - observers: [ - '_invalidChanged(invalid)' - ], - - get _validator() { - return this._validatorMeta && this._validatorMeta.byKey(this.validator); - }, - - ready: function() { - this._validatorMeta = new Polymer.IronMeta({type: this.validatorType}); - }, - - _invalidChanged: function() { - if (this.invalid) { - this.setAttribute('aria-invalid', 'true'); - } else { - this.removeAttribute('aria-invalid'); - } - }, - - /** - * @return {boolean} True if the validator `validator` exists. - */ - hasValidator: function() { - return this._validator != null; - }, - - /** - * Returns true if the `value` is valid, and updates `invalid`. If you want - * your element to have custom validation logic, do not override this method; - * override `_getValidity(value)` instead. - - * @param {Object} value The value to be validated. By default, it is passed - * to the validator's `validate()` function, if a validator is set. - * @return {boolean} True if `value` is valid. - */ - validate: function(value) { - this.invalid = !this._getValidity(value); - return !this.invalid; - }, - - /** - * Returns true if `value` is valid. By default, it is passed - * to the validator's `validate()` function, if a validator is set. You - * should override this method if you want to implement custom validity - * logic for your element. - * - * @param {Object} value The value to be validated. - * @return {boolean} True if `value` is valid. - */ - - _getValidity: function(value) { - if (this.hasValidator()) { - return this._validator.validate(value); - } - return true; - } - }; - -</script><script> - /** - Polymer.IronFormElementBehavior enables a custom element to be included - in an `iron-form`. - - @demo demo/index.html - @polymerBehavior - */ - Polymer.IronFormElementBehavior = { - - properties: { - /** - * Fired when the element is added to an `iron-form`. - * - * @event iron-form-element-register - */ - - /** - * Fired when the element is removed from an `iron-form`. - * - * @event iron-form-element-unregister - */ - - /** - * The name of this element. - */ - name: { - type: String - }, - - /** - * The value for this element. - */ - value: { - notify: true, - type: String - }, - - /** - * Set to true to mark the input as required. If used in a form, a - * custom element that uses this behavior should also use - * Polymer.IronValidatableBehavior and define a custom validation method. - * Otherwise, a `required` element will always be considered valid. - * It's also strongly recommended to provide a visual style for the element - * when its value is invalid. - */ - required: { - type: Boolean, - value: false - }, - - /** - * The form that the element is registered to. - */ - _parentForm: { - type: Object - } - }, - - attached: function() { - // Note: the iron-form that this element belongs to will set this - // element's _parentForm property when handling this event. - this.fire('iron-form-element-register'); - }, - - detached: function() { - if (this._parentForm) { - this._parentForm.fire('iron-form-element-unregister', {target: this}); - } - } - - }; - -</script><script> - /** - * Use `Polymer.PaperInputBehavior` to implement inputs with `<paper-input-container>`. This - * behavior is implemented by `<paper-input>`. It exposes a number of properties from - * `<paper-input-container>` and `<input is="iron-input">` and they should be bound in your - * template. - * - * The input element can be accessed by the `inputElement` property if you need to access - * properties or methods that are not exposed. - * @polymerBehavior Polymer.PaperInputBehavior - */ - Polymer.PaperInputBehaviorImpl = { - properties: { - /** - * Fired when the input changes due to user interaction. - * - * @event change - */ - - /** - * The label for this input. Bind this to `<label>`'s content and `hidden` property, e.g. - * `<label hidden$="[[!label]]">[[label]]</label>` in your `template` - */ - label: { - type: String - }, - - /** - * The value for this input. Bind this to the `<input is="iron-input">`'s `bindValue` - * property, or the value property of your input that is `notify:true`. - */ - value: { - notify: true, - type: String - }, - - /** - * Set to true to disable this input. Bind this to both the `<paper-input-container>`'s - * and the input's `disabled` property. - */ - disabled: { - type: Boolean, - value: false - }, - - /** - * Returns true if the value is invalid. Bind this to both the `<paper-input-container>`'s - * and the input's `invalid` property. - */ - invalid: { - type: Boolean, - value: false, - notify: true - }, - - /** - * Set to true to prevent the user from entering invalid input. Bind this to the - * `<input is="iron-input">`'s `preventInvalidInput` property. - */ - preventInvalidInput: { - type: Boolean - }, - - /** - * Set this to specify the pattern allowed by `preventInvalidInput`. Bind this to the - * `<input is="iron-input">`'s `allowedPattern` property. - */ - allowedPattern: { - type: String - }, - - /** - * The type of the input. The supported types are `text`, `number` and `password`. Bind this - * to the `<input is="iron-input">`'s `type` property. - */ - type: { - type: String - }, - - /** - * The datalist of the input (if any). This should match the id of an existing `<datalist>`. Bind this - * to the `<input is="iron-input">`'s `list` property. - */ - list: { - type: String - }, - - /** - * A pattern to validate the `input` with. Bind this to the `<input is="iron-input">`'s - * `pattern` property. - */ - pattern: { - type: String - }, - - /** - * Set to true to mark the input as required. Bind this to the `<input is="iron-input">`'s - * `required` property. - */ - required: { - type: Boolean, - value: false - }, - - /** - * The error message to display when the input is invalid. Bind this to the - * `<paper-input-error>`'s content, if using. - */ - errorMessage: { - type: String - }, - - /** - * Set to true to show a character counter. - */ - charCounter: { - type: Boolean, - value: false - }, - - /** - * Set to true to disable the floating label. Bind this to the `<paper-input-container>`'s - * `noLabelFloat` property. - */ - noLabelFloat: { - type: Boolean, - value: false - }, - - /** - * Set to true to always float the label. Bind this to the `<paper-input-container>`'s - * `alwaysFloatLabel` property. - */ - alwaysFloatLabel: { - type: Boolean, - value: false - }, - - /** - * Set to true to auto-validate the input value. Bind this to the `<paper-input-container>`'s - * `autoValidate` property. - */ - autoValidate: { - type: Boolean, - value: false - }, - - /** - * Name of the validator to use. Bind this to the `<input is="iron-input">`'s `validator` - * property. - */ - validator: { - type: String - }, - - // HTMLInputElement attributes for binding if needed - - /** - * Bind this to the `<input is="iron-input">`'s `autocomplete` property. - */ - autocomplete: { - type: String, - value: 'off' - }, - - /** - * Bind this to the `<input is="iron-input">`'s `autofocus` property. - */ - autofocus: { - type: Boolean - }, - - /** - * Bind this to the `<input is="iron-input">`'s `inputmode` property. - */ - inputmode: { - type: String - }, - - /** - * Bind this to the `<input is="iron-input">`'s `minlength` property. - */ - minlength: { - type: Number - }, - - /** - * The maximum length of the input value. Bind this to the `<input is="iron-input">`'s - * `maxlength` property. - */ - maxlength: { - type: Number - }, - - /** - * The minimum (numeric or date-time) input value. - * Bind this to the `<input is="iron-input">`'s `min` property. - */ - min: { - type: String - }, - - /** - * The maximum (numeric or date-time) input value. - * Can be a String (e.g. `"2000-1-1"`) or a Number (e.g. `2`). - * Bind this to the `<input is="iron-input">`'s `max` property. - */ - max: { - type: String - }, - - /** - * Limits the numeric or date-time increments. - * Bind this to the `<input is="iron-input">`'s `step` property. - */ - step: { - type: String - }, - - /** - * Bind this to the `<input is="iron-input">`'s `name` property. - */ - name: { - type: String - }, - - /** - * A placeholder string in addition to the label. If this is set, the label will always float. - */ - placeholder: { - type: String, - // need to set a default so _computeAlwaysFloatLabel is run - value: '' - }, - - /** - * Bind this to the `<input is="iron-input">`'s `readonly` property. - */ - readonly: { - type: Boolean, - value: false - }, - - /** - * Bind this to the `<input is="iron-input">`'s `size` property. - */ - size: { - type: Number - }, - - // Nonstandard attributes for binding if needed - - /** - * Bind this to the `<input is="iron-input">`'s `autocapitalize` property. - */ - autocapitalize: { - type: String, - value: 'none' - }, - - /** - * Bind this to the `<input is="iron-input">`'s `autocorrect` property. - */ - autocorrect: { - type: String, - value: 'off' - }, - - /** - * Bind this to the `<input is="iron-input">`'s `autosave` property, used with type=search. - */ - autosave: { - type: String - }, - - /** - * Bind this to the `<input is="iron-input">`'s `results` property, used with type=search. - */ - results: { - type: Number - }, - - /** - * Bind this to the `<input is="iron-input">`'s `accept` property, used with type=file. - */ - accept: { - type: String - }, - - /** - * Bind this to the `<input is="iron-input">`'s `multiple` property, used with type=file. - */ - multiple: { - type: Boolean - }, - - _ariaDescribedBy: { - type: String, - value: '' - }, - - _ariaLabelledBy: { - type: String, - value: '' - } - - }, - - listeners: { - 'addon-attached': '_onAddonAttached', - 'focus': '_onFocus' - }, - - observers: [ - '_focusedControlStateChanged(focused)' - ], - - keyBindings: { - 'shift+tab:keydown': '_onShiftTabDown' - }, - - hostAttributes: { - tabindex: 0 - }, - - /** - * Returns a reference to the input element. - */ - get inputElement() { - return this.$.input; - }, - - /** - * Returns a reference to the focusable element. - */ - get _focusableElement() { - return this.inputElement; - }, - - attached: function() { - this._updateAriaLabelledBy(); - }, - - _appendStringWithSpace: function(str, more) { - if (str) { - str = str + ' ' + more; - } else { - str = more; - } - return str; - }, - - _onAddonAttached: function(event) { - var target = event.path ? event.path[0] : event.target; - if (target.id) { - this._ariaDescribedBy = this._appendStringWithSpace(this._ariaDescribedBy, target.id); - } else { - var id = 'paper-input-add-on-' + Math.floor((Math.random() * 100000)); - target.id = id; - this._ariaDescribedBy = this._appendStringWithSpace(this._ariaDescribedBy, id); - } - }, - - /** - * Validates the input element and sets an error style if needed. - * - * @return {boolean} - */ - validate: function() { - return this.inputElement.validate(); - }, - - /** - * Forward focus to inputElement - */ - _onFocus: function() { - if (!this._shiftTabPressed) { - this._focusableElement.focus(); - } - }, - - /** - * Handler that is called when a shift+tab keypress is detected by the menu. - * - * @param {CustomEvent} event A key combination event. - */ - _onShiftTabDown: function(event) { - var oldTabIndex = this.getAttribute('tabindex'); - this._shiftTabPressed = true; - this.setAttribute('tabindex', '-1'); - this.async(function() { - this.setAttribute('tabindex', oldTabIndex); - this._shiftTabPressed = false; - }, 1); - }, - - /** - * If `autoValidate` is true, then validates the element. - */ - _handleAutoValidate: function() { - if (this.autoValidate) - this.validate(); - }, - - /** - * Restores the cursor to its original position after updating the value. - * @param {string} newValue The value that should be saved. - */ - updateValueAndPreserveCaret: function(newValue) { - // Not all elements might have selection, and even if they have the - // right properties, accessing them might throw an exception (like for - // <input type=number>) - try { - var start = this.inputElement.selectionStart; - this.value = newValue; - - // The cursor automatically jumps to the end after re-setting the value, - // so restore it to its original position. - this.inputElement.selectionStart = start; - this.inputElement.selectionEnd = start; - } catch (e) { - // Just set the value and give up on the caret. - this.value = newValue; - } - }, - - _computeAlwaysFloatLabel: function(alwaysFloatLabel, placeholder) { - return placeholder || alwaysFloatLabel; - }, - - _focusedControlStateChanged: function(focused) { - // IronControlState stops the focus and blur events in order to redispatch them on the host - // element, but paper-input-container listens to those events. Since there are more - // pending work on focus/blur in IronControlState, I'm putting in this hack to get the - // input focus state working for now. - if (!this.$.container) { - this.$.container = Polymer.dom(this.root).querySelector('paper-input-container'); - if (!this.$.container) { - return; - } - } - if (focused) { - this.$.container._onFocus(); - } else { - this.$.container._onBlur(); - } - }, - - _updateAriaLabelledBy: function() { - var label = Polymer.dom(this.root).querySelector('label'); - if (!label) { - this._ariaLabelledBy = ''; - return; - } - var labelledBy; - if (label.id) { - labelledBy = label.id; - } else { - labelledBy = 'paper-input-label-' + new Date().getUTCMilliseconds(); - label.id = labelledBy; - } - this._ariaLabelledBy = labelledBy; - }, - - _onChange:function(event) { - // In the Shadow DOM, the `change` event is not leaked into the - // ancestor tree, so we must do this manually. - // See https://w3c.github.io/webcomponents/spec/shadow/#events-that-are-not-leaked-into-ancestor-trees. - if (this.shadowRoot) { - this.fire(event.type, {sourceEvent: event}, { - node: this, - bubbles: event.bubbles, - cancelable: event.cancelable - }); - } - } - }; - - /** @polymerBehavior */ - Polymer.PaperInputBehavior = [ - Polymer.IronControlState, - Polymer.IronA11yKeysBehavior, - Polymer.PaperInputBehaviorImpl - ]; -</script><script> - - /** - * Use `Polymer.PaperInputAddonBehavior` to implement an add-on for `<paper-input-container>`. A - * add-on appears below the input, and may display information based on the input value and - * validity such as a character counter or an error message. - * @polymerBehavior - */ - Polymer.PaperInputAddonBehavior = { - - hostAttributes: { - 'add-on': '' - }, - - attached: function() { - this.fire('addon-attached'); - }, - - /** - * The function called by `<paper-input-container>` when the input value or validity changes. - * @param {{ - * inputElement: (Node|undefined), - * value: (string|undefined), - * invalid: (boolean|undefined) - * }} state All properties are optional - - * inputElement: The input element. - * value: The input value. - * invalid: True if the input value is invalid. - */ - update: function(state) { - } - - }; - -</script><script> -'use strict'; -!function(){"use strict";function t(t,n){if("function"!=typeof n&&null!==n)throw new TypeError("Super expression must either be null or a function, not "+typeof n);t.prototype=Object.create(n&&n.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),n&&(Object.setPrototypeOf?Object.setPrototypeOf(t,n):t.__proto__=n)}function n(t,n){if(!(t instanceof n))throw new TypeError("Cannot call a class as a function")}function e(t,n,e,o){for(var r=0,i=t.length,a=[],s=void 0;i>r&&(s=n.next(t[r]));)n=s,r++;if(r>=i)return[];for(;i-1>r;)s=new h(o),a.push(s),n.on(t[r],s),n=s,r++;return s=new h(e),a.push(s),n.on(t[i-1],s),a}function o(t,n){if("function"!=typeof n&&null!==n)throw new TypeError("Super expression must either be null or a function, not "+typeof n);t.prototype=Object.create(n&&n.prototype,{constructor:{value:t,enumerable:!1,writable:!0,configurable:!0}}),n&&(Object.setPrototypeOf?Object.setPrototypeOf(t,n):t.__proto__=n)}function r(t,n){if(!(t instanceof n))throw new TypeError("Cannot call a class as a function")}function i(t){return t instanceof d||t instanceof P}function a(t){return t}function s(t,n){return"url"===n?"_blank":null}function u(t){return t=t||{},{attributes:t.linkAttributes||null,defaultProtocol:t.defaultProtocol||"http",events:t.events||null,format:t.format||a,formatHref:t.formatHref||a,newLine:t.newLine||!1,nl2br:!!t.newLine||t.nl2br||!1,tagName:t.tagName||"a",target:t.target||s,linkClass:t.linkClass||"linkified"}}function c(t){for(var n=arguments.length,e=Array(n>1?n-1:0),o=1;n>o;o++)e[o-1]=arguments[o];return"function"==typeof t?t.apply(void 0,e):t}function l(t){if(t&&t.__esModule)return t;var n={};if(null!=t)for(var e in t)Object.prototype.hasOwnProperty.call(t,e)&&(n[e]=t[e]);return n["default"]=t,n}var p={__esModule:!0},f=function(){function t(e){n(this,t),this.j=[],this.T=e||null}return t.prototype.on=function(t,n){if(t instanceof Array)for(var e=0;e<t.length;e++)this.j.push([t[e],n]);else this.j.push([t,n])},t.prototype.next=function(t){for(var n=0;n<this.j.length;n++){var e=this.j[n],o=e[1];if(this.test(t,e[0]))return o}return!1},t.prototype.accepts=function(){return!!this.T},t.prototype.test=function(t,n){return t===n},t.prototype.emit=function(){return this.T},t}(),h=function(e){function o(){n(this,o),e.apply(this,arguments)}return t(o,e),o.prototype.test=function(t,n){return t===n||n instanceof RegExp&&n.test(t)},o}(f),g=function(e){function o(){n(this,o),e.apply(this,arguments)}return t(o,e),o.prototype.test=function(t,n){return t instanceof n},o}(f);p.CharacterState=h,p.TokenState=g,p.stateify=e;var m={__esModule:!0},y=function(){function t(n){r(this,t),this.v=n}return t.prototype.toString=function(){return this.v+""},t}(),d=function(t){function n(){r(this,n),t.apply(this,arguments)}return o(n,t),n}(y),b=function(t){function n(){r(this,n),t.call(this,"@")}return o(n,t),n}(y),v=function(t){function n(){r(this,n),t.call(this,":")}return o(n,t),n}(y),x=function(t){function n(){r(this,n),t.call(this,".")}return o(n,t),n}(y),k=function(t){function n(){r(this,n),t.apply(this,arguments)}return o(n,t),n}(y),w=function(t){function n(){r(this,n),t.apply(this,arguments)}return o(n,t),n}(y),O=function(t){function n(){r(this,n),t.call(this,"\n")}return o(n,t),n}(y),E=function(t){function n(){r(this,n),t.apply(this,arguments)}return o(n,t),n}(y),L=function(t){function n(){r(this,n),t.call(this,"+")}return o(n,t),n}(y),S=function(t){function n(){r(this,n),t.call(this,"#")}return o(n,t),n}(y),A=function(t){function n(){r(this,n),t.apply(this,arguments)}return o(n,t),n}(y),C=function(t){function n(){r(this,n),t.call(this,"?")}return o(n,t),n}(y),T=function(t){function n(){r(this,n),t.call(this,"/")}return o(n,t),n}(y),N=function(t){function n(){r(this,n),t.apply(this,arguments)}return o(n,t),n}(y),P=function(t){function n(){r(this,n),t.apply(this,arguments)}return o(n,t),n}(y),j=function(t){function n(){r(this,n),t.apply(this,arguments)}return o(n,t),n}(y),z=function(t){function n(){r(this,n),t.call(this,"{")}return o(n,t),n}(y),R=function(t){function n(){r(this,n),t.call(this,"[")}return o(n,t),n}(y),_=function(t){function n(){r(this,n),t.call(this,"(")}return o(n,t),n}(y),U=function(t){function n(){r(this,n),t.call(this,"}")}return o(n,t),n}(y),M=function(t){function n(){r(this,n),t.call(this,"]")}return o(n,t),n}(y),B=function(t){function n(){r(this,n),t.call(this,")")}return o(n,t),n}(y),D={Base:y,DOMAIN:d,AT:b,COLON:v,DOT:x,PUNCTUATION:k,LOCALHOST:w,NL:O,NUM:E,PLUS:L,POUND:S,QUERY:C,PROTOCOL:A,SLASH:T,SYM:N,TLD:P,WS:j,OPENBRACE:z,OPENBRACKET:R,OPENPAREN:_,CLOSEBRACE:U,CLOSEBRACKET:M,CLOSEPAREN:B},H=function(){function t(n){r(this,t),this.v=n,this.type="token",this.isLink=!1}return t.prototype.toString=function(){for(var t=[],n=0;n<this.v.length;n++)t.push(this.v[n].toString());return t.join("")},t.prototype.toHref=function(){return this.toString()},t.prototype.toObject=function(){return{type:this.type,value:this.toString(),href:this.toHref(0>=arguments.length||void 0===arguments[0]?"http":arguments[0])}},t}(),q=function(t){function n(e){r(this,n),t.call(this,e),this.type="email",this.isLink=!0}return o(n,t),n.prototype.toHref=function(){return"mailto:"+this.toString()},n}(H),I=function(t){function n(e){r(this,n),t.call(this,e),this.type="text"}return o(n,t),n}(H),K=function(t){function n(e){r(this,n),t.call(this,e),this.type="nl"}return o(n,t),n}(H),Y=function(t){function n(e){r(this,n),t.call(this,e),this.type="url",this.isLink=!0}return o(n,t),n.prototype.toHref=function(){for(var t=0>=arguments.length||void 0===arguments[0]?"http":arguments[0],n=!1,e=!1,o=this.v,r=[],a=0;o[a]instanceof A;)n=!0,r.push(o[a].toString().toLowerCase()),a++;for(;o[a]instanceof T;)e=!0,r.push(o[a].toString()),a++;for(;i(o[a]);)r.push(o[a].toString().toLowerCase()),a++;for(;a<o.length;a++)r.push(o[a].toString());return r=r.join(""),n||e||(r=t+"://"+r),r},n.prototype.hasProtocol=function(){return this.v[0]instanceof A},n}(H),Q={Base:H,EMAIL:q,NL:K,TEXT:I,URL:Y};m.text=D,m.multi=Q;var W={__esModule:!0},X=m,Z=p,F=function(t){return new Z.TokenState(t)},G=X.text.DOMAIN,J=X.text.AT,V=X.text.COLON,$=X.text.DOT,tt=X.text.PUNCTUATION,nt=X.text.LOCALHOST,et=X.text.NL,ot=X.text.NUM,rt=X.text.PLUS,it=X.text.POUND,at=X.text.PROTOCOL,st=X.text.QUERY,ut=X.text.SLASH,ct=X.text.SYM,lt=X.text.TLD,pt=X.text.OPENBRACE,ft=X.text.OPENBRACKET,ht=X.text.OPENPAREN,gt=X.text.CLOSEBRACE,mt=X.text.CLOSEBRACKET,yt=X.text.CLOSEPAREN,dt=X.multi.EMAIL,bt=X.multi.NL,vt=X.multi.TEXT,xt=X.multi.URL,kt=F(),wt=F(),Ot=F(),Et=F(),Lt=F(),St=F(),At=F(xt),Ct=F(),Tt=F(xt),Nt=F(),Pt=F(),jt=F(xt),zt=F(),Rt=F(xt),_t=F(xt),Ut=F(),Mt=F(),Bt=F(),Dt=F(),Ht=F(xt),qt=F(xt),It=F(xt),Kt=F(),Yt=F(),Qt=F(),Wt=F(),Xt=F(),Zt=F(dt),Ft=F(),Gt=F(dt),Jt=F(),Vt=F(),$t=F(),tn=F(bt);kt.on(et,tn),kt.on(at,wt),kt.on(ut,Ot),wt.on(ut,Ot),Ot.on(ut,Et),kt.on(lt,Lt),kt.on(G,Lt),kt.on(nt,At),kt.on(ot,Lt),Et.on(lt,Nt),Et.on(G,Nt),Et.on(ot,Nt),Et.on(nt,jt),Lt.on($,St),Nt.on($,Pt),Wt.on($,Xt),St.on(lt,At),St.on(G,Lt),St.on(ot,Lt),St.on(nt,Lt),Pt.on(lt,jt),Pt.on(G,Nt),Pt.on(ot,Nt),Pt.on(nt,Nt),Xt.on(lt,Zt),Xt.on(G,Wt),Xt.on(ot,Wt),Xt.on(nt,Wt),At.on($,St),jt.on($,Pt),Zt.on($,Xt),At.on(V,Ct),At.on(ut,_t),Ct.on(ot,Tt),Tt.on(ut,_t),jt.on(V,zt),jt.on(ut,_t),zt.on(ot,Rt),Rt.on(ut,_t),Zt.on(V,Ft),Ft.on(ot,Gt);var nn=[G,J,nt,ot,rt,it,at,ut,lt],en=[V,$,st,tt,gt,mt,yt,pt,ft,ht,ct];_t.on(pt,Mt),_t.on(ft,Bt),_t.on(ht,Dt),Ut.on(pt,Mt),Ut.on(ft,Bt),Ut.on(ht,Dt),Mt.on(gt,_t),Bt.on(mt,_t),Dt.on(yt,_t),Ht.on(gt,_t),qt.on(mt,_t),It.on(yt,_t),Kt.on(gt,_t),Yt.on(mt,_t),Qt.on(yt,_t),Mt.on(nn,Ht),Bt.on(nn,qt),Dt.on(nn,It),Mt.on(en,Kt),Bt.on(en,Yt),Dt.on(en,Qt),Ht.on(nn,Ht),qt.on(nn,qt),It.on(nn,It),Ht.on(en,Ht),qt.on(en,qt),It.on(en,It),Kt.on(nn,Ht),Yt.on(nn,qt),Qt.on(nn,It),Kt.on(en,Kt),Yt.on(en,Yt),Qt.on(en,Qt),_t.on(nn,_t),Ut.on(nn,_t),_t.on(en,Ut),Ut.on(en,Ut);var on=[G,ot,rt,it,st,ct,lt];Lt.on(on,Jt),Lt.on(J,Vt),St.on(on,Jt),At.on(on,Jt),At.on(J,Vt),Jt.on(on,Jt),Jt.on(J,Vt),Jt.on($,$t),$t.on(on,Jt),Vt.on(lt,Wt),Vt.on(G,Wt),Vt.on(nt,Zt);var rn=function(t){for(var n=t.length,e=0,o=[],r=[];n>e;){for(var i=kt,a=null,s=null,u=0,c=null,l=-1;n>e&&!(a=i.next(t[e]));)r.push(t[e++]);for(;n>e&&(s=a||i.next(t[e]));)a=null,i=s,i.accepts()?(l=0,c=i):l>=0&&l++,e++,u++;if(0>l)for(u=e-u;e>u;u++)r.push(t[u]);else 0<r.length&&(o.push(new vt(r)),r=[]),e-=l,u-=l,i=c.emit(),o.push(new i(t.slice(e-u,e)))}return 0<r.length&&o.push(new vt(r)),o},an=X.multi,sn=kt;W.State=Z.TokenState,W.TOKENS=an,W.run=rn,W.start=sn;var un={__esModule:!0},cn=m,ln=p,pn="abogado ac academy accountants active actor ad adult ae aero af ag agency ai airforce al allfinanz alsace am an android ao aq aquarelle ar archi army arpa as asia associates at attorney au auction audio autos aw ax axa az ba band bar bargains bayern bb bd be beer berlin best bf bg bh bi bid bike bio biz bj black blackfriday bloomberg blue bm bmw bn bnpparibas bo boo boutique br brussels bs bt budapest build builders business buzz bv bw by bz bzh ca cab cal camera camp cancerresearch capetown capital caravan cards care career careers casa cash cat catering cc cd center ceo cern cf cg ch channel cheap christmas chrome church ci citic city ck cl claims cleaning click clinic clothing club cm cn co coach codes coffee college cologne com community company computer condos construction consulting contractors cooking cool coop country cr credit creditcard cricket crs cruises cu cuisinella cv cw cx cy cymru cz dad dance dating day de deals degree delivery democrat dental dentist desi diamonds diet digital direct directory discount dj dk dm dnp do domains durban dvag dz eat ec edu education ee eg email emerck energy engineer engineering enterprises equipment er es esq estate et eu eurovision eus events everbank exchange expert exposed fail farm fashion feedback fi finance financial firmdale fish fishing fitness fj fk flights florist flsmidth fly fm fo foo forsale foundation fr frl frogans fund furniture futbol ga gal gallery gb gbiz gd ge gent gf gg gh gi gift gifts gives gl glass gle global globo gm gmail gmo gmx gn google gop gov gp gq gr graphics gratis green gripe gs gt gu guide guitars guru gw gy hamburg haus healthcare help here hiphop hiv hk hm hn holdings holiday homes horse host hosting house how hr ht hu ibm id ie il im immo immobilien in industries info ing ink institute insure int international investments io iq ir irish is it je jetzt jm jo jobs joburg jp juegos kaufen ke kg kh ki kim kitchen kiwi km kn koeln kp kr krd kred kw ky kz la lacaixa land latrobe lawyer lb lc lds lease legal lgbt li life lighting limited limo link lk loans london lotto lr ls lt ltda lu luxe luxury lv ly ma madrid maison management mango market marketing mc md me media meet melbourne meme memorial menu mg mh miami mil mini mk ml mm mn mo mobi moda moe monash money mormon mortgage moscow motorcycles mov mp mq mr ms mt mu museum mv mw mx my mz na nagoya name navy nc ne net network neustar new nexus nf ng ngo nhk ni ninja nl no np nr nra nrw nu nyc nz okinawa om ong onl ooo org organic otsuka ovh pa paris partners parts party pe pf pg ph pharmacy photo photography photos physio pics pictures pink pizza pk pl place plumbing pm pn pohl poker porn post pr praxi press pro prod productions prof properties property ps pt pub pw py qa qpon quebec re realtor recipes red rehab reise reisen reit ren rentals repair report republican rest restaurant reviews rich rio rip ro rocks rodeo rs rsvp ru ruhr rw ryukyu sa saarland sarl sb sc sca scb schmidt schule science scot sd se services sexy sg sh shiksha shoes si singles sj sk sl sm sn so social software sohu solar solutions soy space spiegel sr st su supplies supply support surf surgery suzuki sv sx sy sydney systems sz taipei tatar tattoo tax tc td technology tel tf tg th tienda tips tirol tj tk tl tm tn to today tokyo tools top town toys tp tr trade training travel trust tt tui tv tw tz ua ug uk university uno uol us uy uz va vacations vc ve vegas ventures versicherung vet vg vi viajes villas vision vlaanderen vn vodka vote voting voto voyage vu wales wang watch webcam website wed wedding wf whoswho wien wiki williamhill wme work works world ws wtc wtf xxx xyz yachts yandex ye yoga yokohama youtube yt za zip zm zone zw".split(" "),fn=/[0-9]/,hn=/[a-z0-9]/,gn=":",mn=[],yn=function(t){return new ln.CharacterState(t)},dn=cn.text.DOMAIN,bn=cn.text.LOCALHOST,vn=cn.text.NUM,xn=cn.text.PROTOCOL,kn=cn.text.TLD,wn=cn.text.WS,On=yn(),En=yn(vn),Ln=yn(dn),Sn=yn(),An=yn(wn);On.on("@",yn(cn.text.AT)),On.on(".",yn(cn.text.DOT)),On.on("+",yn(cn.text.PLUS)),On.on("#",yn(cn.text.POUND)),On.on("?",yn(cn.text.QUERY)),On.on("/",yn(cn.text.SLASH)),On.on(gn,yn(cn.text.COLON)),On.on("{",yn(cn.text.OPENBRACE)),On.on("[",yn(cn.text.OPENBRACKET)),On.on("(",yn(cn.text.OPENPAREN)),On.on("}",yn(cn.text.CLOSEBRACE)),On.on("]",yn(cn.text.CLOSEBRACKET)),On.on(")",yn(cn.text.CLOSEPAREN)),On.on(/[,;!]/,yn(cn.text.PUNCTUATION)),On.on(/\n/,yn(cn.text.NL)),On.on(/\s/,An),An.on(/[^\S\n]/,An);for(var Cn=0;Cn<pn.length;Cn++){var Tn=ln.stateify(pn[Cn],On,kn,dn);mn.push.apply(mn,Tn)}var Nn=ln.stateify("file",On,dn,dn),Pn=ln.stateify("ftp",On,dn,dn),jn=ln.stateify("http",On,dn,dn);mn.push.apply(mn,Nn),mn.push.apply(mn,Pn),mn.push.apply(mn,jn);var zn=Nn.pop(),Rn=Pn.pop(),_n=jn.pop(),Un=yn(dn),Mn=yn(xn);Rn.on("s",Un),_n.on("s",Un),mn.push(Un),zn.on(gn,Mn),Rn.on(gn,Mn),_n.on(gn,Mn),Un.on(gn,Mn);var Bn=ln.stateify("localhost",On,bn,dn);for(mn.push.apply(mn,Bn),On.on(fn,En),En.on("-",Sn),En.on(fn,En),En.on(hn,Ln),Ln.on("-",Sn),Ln.on(hn,Ln),Cn=0;Cn<mn.length;Cn++)mn[Cn].on("-",Sn),mn[Cn].on(hn,Ln);Sn.on("-",Sn),Sn.on(fn,Ln),Sn.on(hn,Ln),On.on(/./,yn(cn.text.SYM));var Dn=function(t){for(var n=t.replace(/[A-Z]/g,function(t){return t.toLowerCase()}),e=t.length,o=[],r=0;e>r;){for(var i=On,a=null,s=0,u=null,c=-1;e>r&&(a=i.next(n[r]));)i=a,i.accepts()?(c=0,u=i):c>=0&&c++,s++,r++;0>c||(r-=c,s-=c,i=u.emit(),o.push(new i(t.substr(r-s,s))))}return o},Hn=On;un.State=ln.CharacterState,un.TOKENS=cn.text,un.run=Dn,un.start=Hn;var qn={__esModule:!0};qn.normalize=u,qn.resolve=c;var In={__esModule:!0},Kn=qn,Yn=l(Kn),Qn=un,Wn=l(Qn),Xn=W,Zn=l(Xn);Array.isArray||(Array.isArray=function(t){return"[object Array]"===Object.prototype.toString.call(t)});var Fn=function(t){return Zn.run(Wn.run(t))},Gn=function(t){for(var n=1>=arguments.length||void 0===arguments[1]?null:arguments[1],e=Fn(t),o=[],r=0;r<e.length;r++)!e[r].isLink||n&&e[r].type!==n||o.push(e[r].toObject());return o},Jn=function(t){var n=1>=arguments.length||void 0===arguments[1]?null:arguments[1],e=Fn(t);return 1===e.length&&e[0].isLink&&(!n||e[0].type===n)};In.find=Gn,In.options=Yn,In.parser=Zn,In.scanner=Wn,In.test=Jn,In.tokenize=Fn,window.linkify=In}(); -!function(e){"use strict";function t(e,t,n){var r=n[n.length-1];e.replaceChild(r,t);for(var o=n.length-2;o>=0;o--)e.insertBefore(n[o],r),r=n[o]}function n(e,t,n){for(var r=[],o=0;o<e.length;o++){var i=e[o];if(i.isLink){var l=i.toHref(t.defaultProtocol),s=a.resolve(t.format,i.toString(),i.type),f=a.resolve(t.formatHref,l,i.type),u=a.resolve(t.attributes,l,i.type),d=a.resolve(t.tagName,l,i.type),v=a.resolve(t.linkClass,l,i.type),c=a.resolve(t.target,l,i.type),m=a.resolve(t.events,l,i.type),p=n.createElement(d);if(p.setAttribute("href",f),p.setAttribute("class",v),c&&p.setAttribute("target",c),u)for(var h in u)p.setAttribute(h,u[h]);if(m)for(var g in m)p.addEventListener?p.addEventListener(g,m[g]):p.attachEvent&&p.attachEvent("on"+g,m[g]);p.appendChild(n.createTextNode(s)),r.push(p)}else"nl"===i.type&&t.nl2br?r.push(n.createElement("br")):r.push(n.createTextNode(i.toString()))}return r}function r(e,o,a){if(!e||"object"!=typeof e||e.nodeType!==l)throw new Error("Cannot linkify "+e+" - Invalid DOM Node type");if("A"===e.tagName)return e;for(var f=e.firstChild;f;){switch(f.nodeType){case l:r(f,o,a);break;case s:var u=f.nodeValue,d=i(u),v=n(d,o,a);t(e,f,v),f=v[v.length-1]}f=f.nextSibling}return e}function o(e,t){var n=arguments.length<=2||void 0===arguments[2]?null:arguments[2];try{n=n||window&&window.document||global&&global.document}catch(o){}if(!n)throw new Error("Cannot find document implementation. If you are in a non-browser environment like Node.js, pass the document implementation as the third argument to linkifyElement.");return t=a.normalize(t),r(e,t,n)}var i=e.tokenize,a=e.options,l=1,s=3;o.helper=r,o.normalize=a.normalize,window.linkifyElement=o}(window.linkify); -</script><script> -'use strict'; -(function(document) { - var copyTextarea = document.createElement('textarea'); - copyTextarea.setAttribute('id', 'clipboard-textarea'); - var style = copyTextarea.style; - style.position = 'absolute'; - style.top = '-10000px'; - document.body.appendChild(copyTextarea); - - window.Chat.ClipboardBehavior = { - copyToClipboard: function(content) { - copyTextarea.value = content; - var range = document.createRange(); - range.selectNode(copyTextarea); - window.getSelection().addRange(range); - - try { - // Now that we've selected the anchor text, execute the copy command - var successful = document.execCommand('copy'); - if (successful) { - app.displayToast('Copied text to clipboard. Paste it where you want!'); - } else { - console.log('failed to copy to clipboard', successful); - } - } catch (err) { - console.log('Oops, unable to copy', err); - } - - // Remove the selections - NOTE: Should use - // removeRange(range) when it is supported - window.getSelection().removeAllRanges(); - } - }; -}(document)); -</script><script> -'use strict'; -Chat = window.Chat || {}; -Chat.SoundNotificationBehavior = { - sounds: function() { - var sounds = document.querySelector('sound-notification'); - if (!sounds) { - sounds = Polymer.Base.create('sound-notification'); - document.body.appendChild(sounds); - } - return sounds; - }, - attached: function() { - //lazy load sound files - setTimeout(function() { - this.sounds(); - }.bind(this), 1000); - }, - playSound: function(e) { - this.sounds().play(); - } -}; -</script><script> -'use strict'; -window.Chat = window.Chat || {}; -(function() { - var textInput = Polymer.Base.create('text-input-dialog'); - textInput.className = 'textInput'; - document.body.appendChild(textInput); - Chat.TextInputBehavior = { - properties: { - contact: Object, - }, - get textInput() { - var textInput = Polymer.dom(document).querySelector('.textInput'); - return textInput; - }, - openTextDialog: function() { - this.textInput.open(this.contact); - }, - - listeners: { - 'contextmenu': '_handleContextMenu', - 'down': '_handleDown', - 'up': '_handleUp', - }, - _handleContextMenu: function(ev) { - ev.preventDefault(); - ev.stopPropagation(); - ev.cancelBubble = true; - this.cancelAsync(this.pressTimer); - this.openTextDialog(); - return false; - }, - _handleUp: function(e) { - this.cancelAsync(this.pressTimer); - }, - _handleDown: function(ev) { - this.pressTimer = this.async(function() { - this.openTextDialog(); - ev.preventDefault(); - ev.stopPropagation(); - ev.cancelBubble = true; - return false; - }, 800); - }, - }; -}()); -</script><script> - -/* -`<iron-input>` adds two-way binding and custom validators using `Polymer.IronValidatorBehavior` -to `<input>`. - -### Two-way binding - -By default you can only get notified of changes to an `input`'s `value` due to user input: - - <input value="{{myValue::input}}"> - -`iron-input` adds the `bind-value` property that mirrors the `value` property, and can be used -for two-way data binding. `bind-value` will notify if it is changed either by user input or by script. - - <input is="iron-input" bind-value="{{myValue}}"> - -### Custom validators - -You can use custom validators that implement `Polymer.IronValidatorBehavior` with `<iron-input>`. - - <input is="iron-input" validator="my-custom-validator"> - -### Stopping invalid input - -It may be desirable to only allow users to enter certain characters. You can use the -`prevent-invalid-input` and `allowed-pattern` attributes together to accomplish this. This feature -is separate from validation, and `allowed-pattern` does not affect how the input is validated. - - <!-- only allow characters that match [0-9] --> - <input is="iron-input" prevent-invalid-input allowed-pattern="[0-9]"> - -@hero hero.svg -@demo demo/index.html -*/ - - Polymer({ - - is: 'iron-input', - - extends: 'input', - - behaviors: [ - Polymer.IronValidatableBehavior - ], - - properties: { - - /** - * Use this property instead of `value` for two-way data binding. - */ - bindValue: { - observer: '_bindValueChanged', - type: String - }, - - /** - * Set to true to prevent the user from entering invalid input. The new input characters are - * matched with `allowedPattern` if it is set, otherwise it will use the `pattern` attribute if - * set, or the `type` attribute (only supported for `type=number`). - */ - preventInvalidInput: { - type: Boolean - }, - - /** - * Regular expression to match valid input characters. - */ - allowedPattern: { - type: String, - observer: "_allowedPatternChanged" - }, - - _previousValidInput: { - type: String, - value: '' - }, - - _patternAlreadyChecked: { - type: Boolean, - value: false - } - - }, - - listeners: { - 'input': '_onInput', - 'keypress': '_onKeypress' - }, - - get _patternRegExp() { - var pattern; - if (this.allowedPattern) { - pattern = new RegExp(this.allowedPattern); - } else if (this.pattern) { - pattern = new RegExp(this.pattern); - } else { - switch (this.type) { - case 'number': - pattern = /[0-9.,e-]/; - break; - } - } - return pattern; - }, - - ready: function() { - this.bindValue = this.value; - }, - - /** - * @suppress {checkTypes} - */ - _bindValueChanged: function() { - if (this.value !== this.bindValue) { - this.value = !(this.bindValue || this.bindValue === 0) ? '' : this.bindValue; - } - // manually notify because we don't want to notify until after setting value - this.fire('bind-value-changed', {value: this.bindValue}); - }, - - _allowedPatternChanged: function() { - // Force to prevent invalid input when an `allowed-pattern` is set - this.preventInvalidInput = this.allowedPattern ? true : false; - }, - - _onInput: function() { - // Need to validate each of the characters pasted if they haven't - // been validated inside `_onKeypress` already. - if (this.preventInvalidInput && !this._patternAlreadyChecked) { - var valid = this._checkPatternValidity(); - if (!valid) { - this.value = this._previousValidInput; - } - } - - this.bindValue = this.value; - this._previousValidInput = this.value; - this._patternAlreadyChecked = false; - }, - - _isPrintable: function(event) { - // What a control/printable character is varies wildly based on the browser. - // - most control characters (arrows, backspace) do not send a `keypress` event - // in Chrome, but the *do* on Firefox - // - in Firefox, when they do send a `keypress` event, control chars have - // a charCode = 0, keyCode = xx (for ex. 40 for down arrow) - // - printable characters always send a keypress event. - // - in Firefox, printable chars always have a keyCode = 0. In Chrome, the keyCode - // always matches the charCode. - // None of this makes any sense. - - // For these keys, ASCII code == browser keycode. - var anyNonPrintable = - (event.keyCode == 8) || // backspace - (event.keyCode == 9) || // tab - (event.keyCode == 13) || // enter - (event.keyCode == 27); // escape - - // For these keys, make sure it's a browser keycode and not an ASCII code. - var mozNonPrintable = - (event.keyCode == 19) || // pause - (event.keyCode == 20) || // caps lock - (event.keyCode == 45) || // insert - (event.keyCode == 46) || // delete - (event.keyCode == 144) || // num lock - (event.keyCode == 145) || // scroll lock - (event.keyCode > 32 && event.keyCode < 41) || // page up/down, end, home, arrows - (event.keyCode > 111 && event.keyCode < 124); // fn keys - - return !anyNonPrintable && !(event.charCode == 0 && mozNonPrintable); - }, - - _onKeypress: function(event) { - if (!this.preventInvalidInput && this.type !== 'number') { - return; - } - var regexp = this._patternRegExp; - if (!regexp) { - return; - } - - // Handle special keys and backspace - if (event.metaKey || event.ctrlKey || event.altKey) - return; - - // Check the pattern either here or in `_onInput`, but not in both. - this._patternAlreadyChecked = true; - - var thisChar = String.fromCharCode(event.charCode); - if (this._isPrintable(event) && !regexp.test(thisChar)) { - event.preventDefault(); - } - }, - - _checkPatternValidity: function() { - var regexp = this._patternRegExp; - if (!regexp) { - return true; - } - for (var i = 0; i < this.value.length; i++) { - if (!regexp.test(this.value[i])) { - return false; - } - } - return true; - }, - - /** - * Returns true if `value` is valid. The validator provided in `validator` will be used first, - * then any constraints. - * @return {boolean} True if the value is valid. - */ - validate: function() { - // Empty, non-required input is valid. - if (!this.required && this.value == '') { - this.invalid = false; - return true; - } - - var valid; - if (this.hasValidator()) { - valid = Polymer.IronValidatableBehavior.validate.call(this, this.value); - } else { - this.invalid = !this.validity.valid; - valid = this.validity.valid; - } - this.fire('iron-input-validate'); - return valid; - } - - }); - - /* - The `iron-input-validate` event is fired whenever `validate()` is called. - @event iron-input-validate - */ - -</script><script>/*! peerjs build:0.3.14, production. Copyright(c) 2013 Michelle Bu <michelle@michellebu.com> */!function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);var j=new Error("Cannot find module '"+g+"'");throw j.code="MODULE_NOT_FOUND",j}var k=c[g]={exports:{}};b[g][0].call(k.exports,function(a){var c=b[g][1][a];return e(c?c:a)},k,k.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g<d.length;g++)e(d[g]);return e}({1:[function(a,b){b.exports.RTCSessionDescription=window.RTCSessionDescription||window.mozRTCSessionDescription,b.exports.RTCPeerConnection=window.RTCPeerConnection||window.mozRTCPeerConnection||window.webkitRTCPeerConnection,b.exports.RTCIceCandidate=window.RTCIceCandidate||window.mozRTCIceCandidate},{}],2:[function(a,b){function c(a,b,g){return this instanceof c?(e.call(this),this.options=d.extend({serialization:"binary",reliable:!1},g),this.open=!1,this.type="data",this.peer=a,this.provider=b,this.id=this.options.connectionId||c._idPrefix+d.randomToken(),this.label=this.options.label||this.id,this.metadata=this.options.metadata,this.serialization=this.options.serialization,this.reliable=this.options.reliable,this._buffer=[],this._buffering=!1,this.bufferSize=0,this._chunkedData={},this.options._payload&&(this._peerBrowser=this.options._payload.browser),void f.startConnection(this,this.options._payload||{originator:!0})):new c(a,b,g)}var d=a("./util"),e=a("eventemitter3"),f=a("./negotiator"),g=a("reliable");d.inherits(c,e),c._idPrefix="dc_",c.prototype.initialize=function(a){this._dc=this.dataChannel=a,this._configureDataChannel()},c.prototype._configureDataChannel=function(){var a=this;d.supports.sctp&&(this._dc.binaryType="arraybuffer"),this._dc.onopen=function(){d.log("Data channel connection success"),a.open=!0,a.emit("open")},!d.supports.sctp&&this.reliable&&(this._reliable=new g(this._dc,d.debug)),this._reliable?this._reliable.onmessage=function(b){a.emit("data",b)}:this._dc.onmessage=function(b){a._handleDataMessage(b)},this._dc.onclose=function(){d.log("DataChannel closed for:",a.peer),a.close()}},c.prototype._handleDataMessage=function(a){var b=this,c=a.data,e=c.constructor;if("binary"===this.serialization||"binary-utf8"===this.serialization){if(e===Blob)return void d.blobToArrayBuffer(c,function(a){c=d.unpack(a),b.emit("data",c)});if(e===ArrayBuffer)c=d.unpack(c);else if(e===String){var f=d.binaryStringToArrayBuffer(c);c=d.unpack(f)}}else"json"===this.serialization&&(c=JSON.parse(c));if(c.__peerData){var g=c.__peerData,h=this._chunkedData[g]||{data:[],count:0,total:c.total};return h.data[c.n]=c.data,h.count+=1,h.total===h.count&&(delete this._chunkedData[g],c=new Blob(h.data),this._handleDataMessage({data:c})),void(this._chunkedData[g]=h)}this.emit("data",c)},c.prototype.close=function(){this.open&&(this.open=!1,f.cleanup(this),this.emit("close"))},c.prototype.send=function(a,b){if(!this.open)return void this.emit("error",new Error("Connection is not open. You should listen for the `open` event before sending messages."));if(this._reliable)return void this._reliable.send(a);var c=this;if("json"===this.serialization)this._bufferedSend(JSON.stringify(a));else if("binary"===this.serialization||"binary-utf8"===this.serialization){var e=d.pack(a),f=d.chunkedBrowsers[this._peerBrowser]||d.chunkedBrowsers[d.browser];if(f&&!b&&e.size>d.chunkedMTU)return void this._sendChunks(e);d.supports.sctp?d.supports.binaryBlob?this._bufferedSend(e):d.blobToArrayBuffer(e,function(a){c._bufferedSend(a)}):d.blobToBinaryString(e,function(a){c._bufferedSend(a)})}else this._bufferedSend(a)},c.prototype._bufferedSend=function(a){(this._buffering||!this._trySend(a))&&(this._buffer.push(a),this.bufferSize=this._buffer.length)},c.prototype._trySend=function(a){try{this._dc.send(a)}catch(b){this._buffering=!0;var c=this;return setTimeout(function(){c._buffering=!1,c._tryBuffer()},100),!1}return!0},c.prototype._tryBuffer=function(){if(0!==this._buffer.length){var a=this._buffer[0];this._trySend(a)&&(this._buffer.shift(),this.bufferSize=this._buffer.length,this._tryBuffer())}},c.prototype._sendChunks=function(a){for(var b=d.chunk(a),c=0,e=b.length;e>c;c+=1){var a=b[c];this.send(a,!0)}},c.prototype.handleMessage=function(a){var b=a.payload;switch(a.type){case"ANSWER":this._peerBrowser=b.browser,f.handleSDP(a.type,this,b.sdp);break;case"CANDIDATE":f.handleCandidate(this,b.candidate);break;default:d.warn("Unrecognized message type:",a.type,"from peer:",this.peer)}},b.exports=c},{"./negotiator":5,"./util":8,eventemitter3:9,reliable:12}],3:[function(a){window.Socket=a("./socket"),window.MediaConnection=a("./mediaconnection"),window.DataConnection=a("./dataconnection"),window.Peer=a("./peer"),window.RTCPeerConnection=a("./adapter").RTCPeerConnection,window.RTCSessionDescription=a("./adapter").RTCSessionDescription,window.RTCIceCandidate=a("./adapter").RTCIceCandidate,window.Negotiator=a("./negotiator"),window.util=a("./util"),window.BinaryPack=a("js-binarypack")},{"./adapter":1,"./dataconnection":2,"./mediaconnection":4,"./negotiator":5,"./peer":6,"./socket":7,"./util":8,"js-binarypack":10}],4:[function(a,b){function c(a,b,g){return this instanceof c?(e.call(this),this.options=d.extend({},g),this.open=!1,this.type="media",this.peer=a,this.provider=b,this.metadata=this.options.metadata,this.localStream=this.options._stream,this.id=this.options.connectionId||c._idPrefix+d.randomToken(),void(this.localStream&&f.startConnection(this,{_stream:this.localStream,originator:!0}))):new c(a,b,g)}var d=a("./util"),e=a("eventemitter3"),f=a("./negotiator");d.inherits(c,e),c._idPrefix="mc_",c.prototype.addStream=function(a){d.log("Receiving stream",a),this.remoteStream=a,this.emit("stream",a)},c.prototype.handleMessage=function(a){var b=a.payload;switch(a.type){case"ANSWER":f.handleSDP(a.type,this,b.sdp),this.open=!0;break;case"CANDIDATE":f.handleCandidate(this,b.candidate);break;default:d.warn("Unrecognized message type:",a.type,"from peer:",this.peer)}},c.prototype.answer=function(a){if(this.localStream)return void d.warn("Local stream already exists on this MediaConnection. Are you answering a call twice?");this.options._payload._stream=a,this.localStream=a,f.startConnection(this,this.options._payload);for(var b=this.provider._getMessages(this.id),c=0,e=b.length;e>c;c+=1)this.handleMessage(b[c]);this.open=!0},c.prototype.close=function(){this.open&&(this.open=!1,f.cleanup(this),this.emit("close"))},b.exports=c},{"./negotiator":5,"./util":8,eventemitter3:9}],5:[function(a,b){var c=a("./util"),d=a("./adapter").RTCPeerConnection,e=a("./adapter").RTCSessionDescription,f=a("./adapter").RTCIceCandidate,g={pcs:{data:{},media:{}},queue:[]};g._idPrefix="pc_",g.startConnection=function(a,b){var d=g._getPeerConnection(a,b);if("media"===a.type&&b._stream&&d.addStream(b._stream),a.pc=a.peerConnection=d,b.originator){if("data"===a.type){var e={};c.supports.sctp||(e={reliable:b.reliable});var f=d.createDataChannel(a.label,e);a.initialize(f)}c.supports.onnegotiationneeded||g._makeOffer(a)}else g.handleSDP("OFFER",a,b.sdp)},g._getPeerConnection=function(a,b){g.pcs[a.type]||c.error(a.type+" is not a valid connection type. Maybe you overrode the `type` property somewhere."),g.pcs[a.type][a.peer]||(g.pcs[a.type][a.peer]={});{var d;g.pcs[a.type][a.peer]}return b.pc&&(d=g.pcs[a.type][a.peer][b.pc]),d&&"stable"===d.signalingState||(d=g._startPeerConnection(a)),d},g._startPeerConnection=function(a){c.log("Creating RTCPeerConnection.");var b=g._idPrefix+c.randomToken(),e={};"data"!==a.type||c.supports.sctp?"media"===a.type&&(e={optional:[{DtlsSrtpKeyAgreement:!0}]}):e={optional:[{RtpDataChannels:!0}]};var f=new d(a.provider.options.config,e);return g.pcs[a.type][a.peer][b]=f,g._setupListeners(a,f,b),f},g._setupListeners=function(a,b){var d=a.peer,e=a.id,f=a.provider;c.log("Listening for ICE candidates."),b.onicecandidate=function(b){b.candidate&&(c.log("Received ICE candidates for:",a.peer),f.socket.send({type:"CANDIDATE",payload:{candidate:b.candidate,type:a.type,connectionId:a.id},dst:d}))},b.oniceconnectionstatechange=function(){switch(b.iceConnectionState){case"disconnected":case"failed":c.log("iceConnectionState is disconnected, closing connections to "+d),a.close();break;case"completed":b.onicecandidate=c.noop}},b.onicechange=b.oniceconnectionstatechange,c.log("Listening for `negotiationneeded`"),b.onnegotiationneeded=function(){c.log("`negotiationneeded` triggered"),"stable"==b.signalingState?g._makeOffer(a):c.log("onnegotiationneeded triggered when not stable. Is another connection being established?")},c.log("Listening for data channel"),b.ondatachannel=function(a){c.log("Received data channel");var b=a.channel,g=f.getConnection(d,e);g.initialize(b)},c.log("Listening for remote stream"),b.onaddstream=function(a){c.log("Received remote stream");var b=a.stream,g=f.getConnection(d,e);"media"===g.type&&g.addStream(b)}},g.cleanup=function(a){c.log("Cleaning up PeerConnection to "+a.peer);var b=a.pc;!b||"closed"===b.readyState&&"closed"===b.signalingState||(b.close(),a.pc=null)},g._makeOffer=function(a){var b=a.pc;b.createOffer(function(d){c.log("Created offer."),!c.supports.sctp&&"data"===a.type&&a.reliable&&(d.sdp=Reliable.higherBandwidthSDP(d.sdp)),b.setLocalDescription(d,function(){c.log("Set localDescription: offer","for:",a.peer),a.provider.socket.send({type:"OFFER",payload:{sdp:d,type:a.type,label:a.label,connectionId:a.id,reliable:a.reliable,serialization:a.serialization,metadata:a.metadata,browser:c.browser},dst:a.peer})},function(b){a.provider.emitError("webrtc",b),c.log("Failed to setLocalDescription, ",b)})},function(b){a.provider.emitError("webrtc",b),c.log("Failed to createOffer, ",b)},a.options.constraints)},g._makeAnswer=function(a){var b=a.pc;b.createAnswer(function(d){c.log("Created answer."),!c.supports.sctp&&"data"===a.type&&a.reliable&&(d.sdp=Reliable.higherBandwidthSDP(d.sdp)),b.setLocalDescription(d,function(){c.log("Set localDescription: answer","for:",a.peer),a.provider.socket.send({type:"ANSWER",payload:{sdp:d,type:a.type,connectionId:a.id,browser:c.browser},dst:a.peer})},function(b){a.provider.emitError("webrtc",b),c.log("Failed to setLocalDescription, ",b)})},function(b){a.provider.emitError("webrtc",b),c.log("Failed to create answer, ",b)})},g.handleSDP=function(a,b,d){d=new e(d);var f=b.pc;c.log("Setting remote description",d),f.setRemoteDescription(d,function(){c.log("Set remoteDescription:",a,"for:",b.peer),"OFFER"===a&&g._makeAnswer(b)},function(a){b.provider.emitError("webrtc",a),c.log("Failed to setRemoteDescription, ",a)})},g.handleCandidate=function(a,b){var d=b.candidate,e=b.sdpMLineIndex;a.pc.addIceCandidate(new f({sdpMLineIndex:e,candidate:d})),c.log("Added ICE candidate for:",a.peer)},b.exports=g},{"./adapter":1,"./util":8}],6:[function(a,b){function c(a,b){return this instanceof c?(e.call(this),a&&a.constructor==Object?(b=a,a=void 0):a&&(a=a.toString()),b=d.extend({debug:0,host:d.CLOUD_HOST,port:d.CLOUD_PORT,key:"peerjs",path:"/",token:d.randomToken(),config:d.defaultConfig},b),this.options=b,"/"===b.host&&(b.host=window.location.hostname),"/"!==b.path[0]&&(b.path="/"+b.path),"/"!==b.path[b.path.length-1]&&(b.path+="/"),void 0===b.secure&&b.host!==d.CLOUD_HOST&&(b.secure=d.isSecure()),b.logFunction&&d.setLogFunction(b.logFunction),d.setLogLevel(b.debug),d.supports.audioVideo||d.supports.data?d.validateId(a)?d.validateKey(b.key)?b.secure&&"0.peerjs.com"===b.host?void this._delayedAbort("ssl-unavailable","The cloud server currently does not support HTTPS. Please run your own PeerServer to use HTTPS."):(this.destroyed=!1,this.disconnected=!1,this.open=!1,this.connections={},this._lostMessages={},this._initializeServerConnection(),void(a?this._initialize(a):this._retrieveId())):void this._delayedAbort("invalid-key",'API KEY "'+b.key+'" is invalid'):void this._delayedAbort("invalid-id",'ID "'+a+'" is invalid'):void this._delayedAbort("browser-incompatible","The current browser does not support WebRTC")):new c(a,b)}var d=a("./util"),e=a("eventemitter3"),f=a("./socket"),g=a("./mediaconnection"),h=a("./dataconnection");d.inherits(c,e),c.prototype._initializeServerConnection=function(){var a=this;this.socket=new f(this.options.secure,this.options.host,this.options.port,this.options.path,this.options.key),this.socket.on("message",function(b){a._handleMessage(b)}),this.socket.on("error",function(b){a._abort("socket-error",b)}),this.socket.on("disconnected",function(){a.disconnected||(a.emitError("network","Lost connection to server."),a.disconnect())}),this.socket.on("close",function(){a.disconnected||a._abort("socket-closed","Underlying socket is already closed.")})},c.prototype._retrieveId=function(){var a=this,b=new XMLHttpRequest,c=this.options.secure?"https://":"http://",e=c+this.options.host+":"+this.options.port+this.options.path+this.options.key+"/id",f="?ts="+(new Date).getTime()+Math.random();e+=f,b.open("get",e,!0),b.onerror=function(b){d.error("Error retrieving ID",b);var c="";"/"===a.options.path&&a.options.host!==d.CLOUD_HOST&&(c=" If you passed in a `path` to your self-hosted PeerServer, you'll also need to pass in that same path when creating a new Peer."),a._abort("server-error","Could not get an ID from the server."+c)},b.onreadystatechange=function(){return 4===b.readyState?200!==b.status?void b.onerror():void a._initialize(b.responseText):void 0},b.send(null)},c.prototype._initialize=function(a){this.id=a,this.socket.start(this.id,this.options.token)},c.prototype._handleMessage=function(a){var b,c=a.type,e=a.payload,f=a.src;switch(c){case"OPEN":this.emit("open",this.id),this.open=!0;break;case"ERROR":this._abort("server-error",e.msg);break;case"ID-TAKEN":this._abort("unavailable-id","ID `"+this.id+"` is taken");break;case"INVALID-KEY":this._abort("invalid-key",'API KEY "'+this.options.key+'" is invalid');break;case"LEAVE":d.log("Received leave message from",f),this._cleanupPeer(f);break;case"EXPIRE":this.emitError("peer-unavailable","Could not connect to peer "+f);break;case"OFFER":var i=e.connectionId;if(b=this.getConnection(f,i))d.warn("Offer received for existing Connection ID:",i);else{if("media"===e.type)b=new g(f,this,{connectionId:i,_payload:e,metadata:e.metadata}),this._addConnection(f,b),this.emit("call",b);else{if("data"!==e.type)return void d.warn("Received malformed connection type:",e.type);b=new h(f,this,{connectionId:i,_payload:e,metadata:e.metadata,label:e.label,serialization:e.serialization,reliable:e.reliable}),this._addConnection(f,b),this.emit("connection",b)}for(var j=this._getMessages(i),k=0,l=j.length;l>k;k+=1)b.handleMessage(j[k])}break;default:if(!e)return void d.warn("You received a malformed message from "+f+" of type "+c);var m=e.connectionId;b=this.getConnection(f,m),b&&b.pc?b.handleMessage(a):m?this._storeMessage(m,a):d.warn("You received an unrecognized message:",a)}},c.prototype._storeMessage=function(a,b){this._lostMessages[a]||(this._lostMessages[a]=[]),this._lostMessages[a].push(b)},c.prototype._getMessages=function(a){var b=this._lostMessages[a];return b?(delete this._lostMessages[a],b):[]},c.prototype.connect=function(a,b){if(this.disconnected)return d.warn("You cannot connect to a new Peer because you called .disconnect() on this Peer and ended your connection with the server. You can create a new Peer to reconnect, or call reconnect on this peer if you believe its ID to still be available."),void this.emitError("disconnected","Cannot connect to new Peer after disconnecting from server.");var c=new h(a,this,b);return this._addConnection(a,c),c},c.prototype.call=function(a,b,c){if(this.disconnected)return d.warn("You cannot connect to a new Peer because you called .disconnect() on this Peer and ended your connection with the server. You can create a new Peer to reconnect."),void this.emitError("disconnected","Cannot connect to new Peer after disconnecting from server.");if(!b)return void d.error("To call a peer, you must provide a stream from your browser's `getUserMedia`.");c=c||{},c._stream=b;var e=new g(a,this,c);return this._addConnection(a,e),e},c.prototype._addConnection=function(a,b){this.connections[a]||(this.connections[a]=[]),this.connections[a].push(b)},c.prototype.getConnection=function(a,b){var c=this.connections[a];if(!c)return null;for(var d=0,e=c.length;e>d;d++)if(c[d].id===b)return c[d];return null},c.prototype._delayedAbort=function(a,b){var c=this;d.setZeroTimeout(function(){c._abort(a,b)})},c.prototype._abort=function(a,b){d.error("Aborting!"),this._lastServerId?this.disconnect():this.destroy(),this.emitError(a,b)},c.prototype.emitError=function(a,b){d.error("Error:",b),"string"==typeof b&&(b=new Error(b)),b.type=a,this.emit("error",b)},c.prototype.destroy=function(){this.destroyed||(this._cleanup(),this.disconnect(),this.destroyed=!0)},c.prototype._cleanup=function(){if(this.connections)for(var a=Object.keys(this.connections),b=0,c=a.length;c>b;b++)this._cleanupPeer(a[b]);this.emit("close")},c.prototype._cleanupPeer=function(a){for(var b=this.connections[a],c=0,d=b.length;d>c;c+=1)b[c].close()},c.prototype.disconnect=function(){var a=this;d.setZeroTimeout(function(){a.disconnected||(a.disconnected=!0,a.open=!1,a.socket&&a.socket.close(),a.emit("disconnected",a.id),a._lastServerId=a.id,a.id=null)})},c.prototype.reconnect=function(){if(this.disconnected&&!this.destroyed)d.log("Attempting reconnection to server with ID "+this._lastServerId),this.disconnected=!1,this._initializeServerConnection(),this._initialize(this._lastServerId);else{if(this.destroyed)throw new Error("This peer cannot reconnect to the server. It has already been destroyed.");if(this.disconnected||this.open)throw new Error("Peer "+this.id+" cannot reconnect because it is not disconnected from the server!");d.error("In a hurry? We're still trying to make the initial connection!")}},c.prototype.listAllPeers=function(a){a=a||function(){};var b=this,c=new XMLHttpRequest,e=this.options.secure?"https://":"http://",f=e+this.options.host+":"+this.options.port+this.options.path+this.options.key+"/peers",g="?ts="+(new Date).getTime()+Math.random();f+=g,c.open("get",f,!0),c.onerror=function(){b._abort("server-error","Could not get peers from the server."),a([])},c.onreadystatechange=function(){if(4===c.readyState){if(401===c.status){var e="";throw e=b.options.host!==d.CLOUD_HOST?"It looks like you're using the cloud server. You can email team@peerjs.com to enable peer listing for your API key.":"You need to enable `allow_discovery` on your self-hosted PeerServer to use this feature.",a([]),new Error("It doesn't look like you have permission to list peers IDs. "+e)}a(200!==c.status?[]:JSON.parse(c.responseText))}},c.send(null)},b.exports=c},{"./dataconnection":2,"./mediaconnection":4,"./socket":7,"./util":8,eventemitter3:9}],7:[function(a,b){function c(a,b,d,f,g){if(!(this instanceof c))return new c(a,b,d,f,g);e.call(this),this.disconnected=!1,this._queue=[];var h=a?"https://":"http://",i=a?"wss://":"ws://";this._httpUrl=h+b+":"+d+f+g,this._wsUrl=i+b+":"+d+f+"peerjs?key="+g}var d=a("./util"),e=a("eventemitter3");d.inherits(c,e),c.prototype.start=function(a,b){this.id=a,this._httpUrl+="/"+a+"/"+b,this._wsUrl+="&id="+a+"&token="+b,this._startXhrStream(),this._startWebSocket()},c.prototype._startWebSocket=function(){var a=this;this._socket||(this._socket=new WebSocket(this._wsUrl),this._socket.onmessage=function(b){try{var c=JSON.parse(b.data)}catch(e){return void d.log("Invalid server message",b.data)}a.emit("message",c)},this._socket.onclose=function(){d.log("Socket closed."),a.disconnected=!0,a.emit("disconnected")},this._socket.onopen=function(){a._timeout&&(clearTimeout(a._timeout),setTimeout(function(){a._http.abort(),a._http=null},5e3)),a._sendQueuedMessages(),d.log("Socket open")})},c.prototype._startXhrStream=function(a){try{var b=this;this._http=new XMLHttpRequest,this._http._index=1,this._http._streamIndex=a||0,this._http.open("post",this._httpUrl+"/id?i="+this._http._streamIndex,!0),this._http.onerror=function(){clearTimeout(b._timeout),b.emit("disconnected")},this._http.onreadystatechange=function(){2==this.readyState&&this.old?(this.old.abort(),delete this.old):this.readyState>2&&200===this.status&&this.responseText&&b._handleStream(this)},this._http.send(null),this._setHTTPTimeout()}catch(c){d.log("XMLHttpRequest not available; defaulting to WebSockets")}},c.prototype._handleStream=function(a){var b=a.responseText.split("\n");if(a._buffer)for(;a._buffer.length>0;){var c=a._buffer.shift(),e=b[c];try{e=JSON.parse(e)}catch(f){a._buffer.shift(c);break}this.emit("message",e)}var g=b[a._index];if(g)if(a._index+=1,a._index===b.length)a._buffer||(a._buffer=[]),a._buffer.push(a._index-1);else{try{g=JSON.parse(g)}catch(f){return void d.log("Invalid server message",g)}this.emit("message",g)}},c.prototype._setHTTPTimeout=function(){var a=this;this._timeout=setTimeout(function(){var b=a._http;a._wsOpen()?b.abort():(a._startXhrStream(b._streamIndex+1),a._http.old=b)},25e3)},c.prototype._wsOpen=function(){return this._socket&&1==this._socket.readyState},c.prototype._sendQueuedMessages=function(){for(var a=0,b=this._queue.length;b>a;a+=1)this.send(this._queue[a])},c.prototype.send=function(a){if(!this.disconnected){if(!this.id)return void this._queue.push(a);if(!a.type)return void this.emit("error","Invalid message");var b=JSON.stringify(a);if(this._wsOpen())this._socket.send(b);else{var c=new XMLHttpRequest,d=this._httpUrl+"/"+a.type.toLowerCase();c.open("post",d,!0),c.setRequestHeader("Content-Type","application/json"),c.send(b)}}},c.prototype.close=function(){!this.disconnected&&this._wsOpen()&&(this._socket.close(),this.disconnected=!0)},b.exports=c},{"./util":8,eventemitter3:9}],8:[function(a,b){var c={iceServers:[{url:"stun:stun.l.google.com:19302"}]},d=1,e=a("js-binarypack"),f=a("./adapter").RTCPeerConnection,g={noop:function(){},CLOUD_HOST:"0.peerjs.com",CLOUD_PORT:9e3,chunkedBrowsers:{Chrome:1},chunkedMTU:16300,logLevel:0,setLogLevel:function(a){var b=parseInt(a,10);g.logLevel=isNaN(parseInt(a,10))?a?3:0:b,g.log=g.warn=g.error=g.noop,g.logLevel>0&&(g.error=g._printWith("ERROR")),g.logLevel>1&&(g.warn=g._printWith("WARNING")),g.logLevel>2&&(g.log=g._print)},setLogFunction:function(a){a.constructor!==Function?g.warn("The log function you passed in is not a function. Defaulting to regular logs."):g._print=a},_printWith:function(a){return function(){var b=Array.prototype.slice.call(arguments);b.unshift(a),g._print.apply(g,b)}},_print:function(){var a=!1,b=Array.prototype.slice.call(arguments);b.unshift("PeerJS: ");for(var c=0,d=b.length;d>c;c++)b[c]instanceof Error&&(b[c]="("+b[c].name+") "+b[c].message,a=!0);a?console.error.apply(console,b):console.log.apply(console,b)},defaultConfig:c,browser:function(){return window.mozRTCPeerConnection?"Firefox":window.webkitRTCPeerConnection?"Chrome":window.RTCPeerConnection?"Supported":"Unsupported"}(),supports:function(){if("undefined"==typeof f)return{};var a,b,d=!0,e=!0,h=!1,i=!1,j=!!window.webkitRTCPeerConnection;try{a=new f(c,{optional:[{RtpDataChannels:!0}]})}catch(k){d=!1,e=!1}if(d)try{b=a.createDataChannel("_PEERJSTEST")}catch(k){d=!1}if(d){try{b.binaryType="blob",h=!0}catch(k){}var l=new f(c,{});try{var m=l.createDataChannel("_PEERJSRELIABLETEST",{});i=m.reliable}catch(k){}l.close()}if(e&&(e=!!a.addStream),!j&&d){var n=new f(c,{optional:[{RtpDataChannels:!0}]});n.onnegotiationneeded=function(){j=!0,g&&g.supports&&(g.supports.onnegotiationneeded=!0)},n.createDataChannel("_PEERJSNEGOTIATIONTEST"),setTimeout(function(){n.close()},1e3)}return a&&a.close(),{audioVideo:e,data:d,binaryBlob:h,binary:i,reliable:i,sctp:i,onnegotiationneeded:j}}(),validateId:function(a){return!a||/^[A-Za-z0-9]+(?:[ _-][A-Za-z0-9]+)*$/.exec(a)},validateKey:function(a){return!a||/^[A-Za-z0-9]+(?:[ _-][A-Za-z0-9]+)*$/.exec(a)},debug:!1,inherits:function(a,b){a.super_=b,a.prototype=Object.create(b.prototype,{constructor:{value:a,enumerable:!1,writable:!0,configurable:!0}})},extend:function(a,b){for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c]);return a},pack:e.pack,unpack:e.unpack,log:function(){if(g.debug){var a=!1,b=Array.prototype.slice.call(arguments);b.unshift("PeerJS: ");for(var c=0,d=b.length;d>c;c++)b[c]instanceof Error&&(b[c]="("+b[c].name+") "+b[c].message,a=!0);a?console.error.apply(console,b):console.log.apply(console,b)}},setZeroTimeout:function(a){function b(b){d.push(b),a.postMessage(e,"*")}function c(b){b.source==a&&b.data==e&&(b.stopPropagation&&b.stopPropagation(),d.length&&d.shift()())}var d=[],e="zero-timeout-message";return a.addEventListener?a.addEventListener("message",c,!0):a.attachEvent&&a.attachEvent("onmessage",c),b}(window),chunk:function(a){for(var b=[],c=a.size,e=index=0,f=Math.ceil(c/g.chunkedMTU);c>e;){var h=Math.min(c,e+g.chunkedMTU),i=a.slice(e,h),j={__peerData:d,n:index,data:i,total:f};b.push(j),e=h,index+=1}return d+=1,b},blobToArrayBuffer:function(a,b){var c=new FileReader;c.onload=function(a){b(a.target.result)},c.readAsArrayBuffer(a)},blobToBinaryString:function(a,b){var c=new FileReader;c.onload=function(a){b(a.target.result)},c.readAsBinaryString(a)},binaryStringToArrayBuffer:function(a){for(var b=new Uint8Array(a.length),c=0;c<a.length;c++)b[c]=255&a.charCodeAt(c);return b.buffer},randomToken:function(){return Math.random().toString(36).substr(2)},isSecure:function(){return"https:"===location.protocol}};b.exports=g},{"./adapter":1,"js-binarypack":10}],9:[function(a,b){"use strict";function c(a,b,c){this.fn=a,this.context=b,this.once=c||!1}function d(){}d.prototype._events=void 0,d.prototype.listeners=function(a){if(!this._events||!this._events[a])return[];for(var b=0,c=this._events[a].length,d=[];c>b;b++)d.push(this._events[a][b].fn);return d},d.prototype.emit=function(a,b,c,d,e,f){if(!this._events||!this._events[a])return!1;var g,h,i,j=this._events[a],k=j.length,l=arguments.length,m=j[0];if(1===k){switch(m.once&&this.removeListener(a,m.fn,!0),l){case 1:return m.fn.call(m.context),!0;case 2:return m.fn.call(m.context,b),!0;case 3:return m.fn.call(m.context,b,c),!0;case 4:return m.fn.call(m.context,b,c,d),!0;case 5:return m.fn.call(m.context,b,c,d,e),!0;case 6:return m.fn.call(m.context,b,c,d,e,f),!0}for(h=1,g=new Array(l-1);l>h;h++)g[h-1]=arguments[h];m.fn.apply(m.context,g)}else for(h=0;k>h;h++)switch(j[h].once&&this.removeListener(a,j[h].fn,!0),l){case 1:j[h].fn.call(j[h].context);break;case 2:j[h].fn.call(j[h].context,b);break;case 3:j[h].fn.call(j[h].context,b,c);break;default:if(!g)for(i=1,g=new Array(l-1);l>i;i++)g[i-1]=arguments[i];j[h].fn.apply(j[h].context,g)}return!0},d.prototype.on=function(a,b,d){return this._events||(this._events={}),this._events[a]||(this._events[a]=[]),this._events[a].push(new c(b,d||this)),this},d.prototype.once=function(a,b,d){return this._events||(this._events={}),this._events[a]||(this._events[a]=[]),this._events[a].push(new c(b,d||this,!0)),this},d.prototype.removeListener=function(a,b,c){if(!this._events||!this._events[a])return this;var d=this._events[a],e=[];if(b)for(var f=0,g=d.length;g>f;f++)d[f].fn!==b&&d[f].once!==c&&e.push(d[f]);return this._events[a]=e.length?e:null,this},d.prototype.removeAllListeners=function(a){return this._events?(a?this._events[a]=null:this._events={},this):this},d.prototype.off=d.prototype.removeListener,d.prototype.addListener=d.prototype.on,d.prototype.setMaxListeners=function(){return this},d.EventEmitter=d,d.EventEmitter2=d,d.EventEmitter3=d,"object"==typeof b&&b.exports&&(b.exports=d)},{}],10:[function(a,b){function c(a){this.index=0,this.dataBuffer=a,this.dataView=new Uint8Array(this.dataBuffer),this.length=this.dataBuffer.byteLength}function d(){this.bufferBuilder=new g}function e(a){var b=a.charCodeAt(0);return 2047>=b?"00":65535>=b?"000":2097151>=b?"0000":67108863>=b?"00000":"000000"}function f(a){return a.length>600?new Blob([a]).size:a.replace(/[^\u0000-\u007F]/g,e).length}var g=a("./bufferbuilder").BufferBuilder,h=a("./bufferbuilder").binaryFeatures,i={unpack:function(a){var b=new c(a);return b.unpack()},pack:function(a){var b=new d;b.pack(a);var c=b.getBuffer();return c}};b.exports=i,c.prototype.unpack=function(){var a=this.unpack_uint8();if(128>a){var b=a;return b}if(32>(224^a)){var c=(224^a)-32;return c}var d;if((d=160^a)<=15)return this.unpack_raw(d);if((d=176^a)<=15)return this.unpack_string(d);if((d=144^a)<=15)return this.unpack_array(d);if((d=128^a)<=15)return this.unpack_map(d);switch(a){case 192:return null;case 193:return void 0;case 194:return!1;case 195:return!0;case 202:return this.unpack_float();case 203:return this.unpack_double();case 204:return this.unpack_uint8();case 205:return this.unpack_uint16();case 206:return this.unpack_uint32();case 207:return this.unpack_uint64();case 208:return this.unpack_int8();case 209:return this.unpack_int16();case 210:return this.unpack_int32();case 211:return this.unpack_int64();case 212:return void 0;case 213:return void 0;case 214:return void 0;case 215:return void 0;case 216:return d=this.unpack_uint16(),this.unpack_string(d);case 217:return d=this.unpack_uint32(),this.unpack_string(d);case 218:return d=this.unpack_uint16(),this.unpack_raw(d);case 219:return d=this.unpack_uint32(),this.unpack_raw(d);case 220:return d=this.unpack_uint16(),this.unpack_array(d);case 221:return d=this.unpack_uint32(),this.unpack_array(d);case 222:return d=this.unpack_uint16(),this.unpack_map(d);case 223:return d=this.unpack_uint32(),this.unpack_map(d)}},c.prototype.unpack_uint8=function(){var a=255&this.dataView[this.index];return this.index++,a},c.prototype.unpack_uint16=function(){var a=this.read(2),b=256*(255&a[0])+(255&a[1]);return this.index+=2,b},c.prototype.unpack_uint32=function(){var a=this.read(4),b=256*(256*(256*a[0]+a[1])+a[2])+a[3];return this.index+=4,b},c.prototype.unpack_uint64=function(){var a=this.read(8),b=256*(256*(256*(256*(256*(256*(256*a[0]+a[1])+a[2])+a[3])+a[4])+a[5])+a[6])+a[7];return this.index+=8,b},c.prototype.unpack_int8=function(){var a=this.unpack_uint8();return 128>a?a:a-256},c.prototype.unpack_int16=function(){var a=this.unpack_uint16();return 32768>a?a:a-65536},c.prototype.unpack_int32=function(){var a=this.unpack_uint32();return a<Math.pow(2,31)?a:a-Math.pow(2,32)},c.prototype.unpack_int64=function(){var a=this.unpack_uint64();return a<Math.pow(2,63)?a:a-Math.pow(2,64)},c.prototype.unpack_raw=function(a){if(this.length<this.index+a)throw new Error("BinaryPackFailure: index is out of range "+this.index+" "+a+" "+this.length);var b=this.dataBuffer.slice(this.index,this.index+a);return this.index+=a,b},c.prototype.unpack_string=function(a){for(var b,c,d=this.read(a),e=0,f="";a>e;)b=d[e],128>b?(f+=String.fromCharCode(b),e++):32>(192^b)?(c=(192^b)<<6|63&d[e+1],f+=String.fromCharCode(c),e+=2):(c=(15&b)<<12|(63&d[e+1])<<6|63&d[e+2],f+=String.fromCharCode(c),e+=3);return this.index+=a,f},c.prototype.unpack_array=function(a){for(var b=new Array(a),c=0;a>c;c++)b[c]=this.unpack();return b},c.prototype.unpack_map=function(a){for(var b={},c=0;a>c;c++){var d=this.unpack(),e=this.unpack();b[d]=e}return b},c.prototype.unpack_float=function(){var a=this.unpack_uint32(),b=a>>31,c=(a>>23&255)-127,d=8388607&a|8388608;return(0==b?1:-1)*d*Math.pow(2,c-23)},c.prototype.unpack_double=function(){var a=this.unpack_uint32(),b=this.unpack_uint32(),c=a>>31,d=(a>>20&2047)-1023,e=1048575&a|1048576,f=e*Math.pow(2,d-20)+b*Math.pow(2,d-52);return(0==c?1:-1)*f},c.prototype.read=function(a){var b=this.index;if(b+a<=this.length)return this.dataView.subarray(b,b+a);throw new Error("BinaryPackFailure: read index out of range")},d.prototype.getBuffer=function(){return this.bufferBuilder.getBuffer()},d.prototype.pack=function(a){var b=typeof a;if("string"==b)this.pack_string(a);else if("number"==b)Math.floor(a)===a?this.pack_integer(a):this.pack_double(a);else if("boolean"==b)a===!0?this.bufferBuilder.append(195):a===!1&&this.bufferBuilder.append(194);else if("undefined"==b)this.bufferBuilder.append(192);else{if("object"!=b)throw new Error('Type "'+b+'" not yet supported');if(null===a)this.bufferBuilder.append(192);else{var c=a.constructor;if(c==Array)this.pack_array(a);else if(c==Blob||c==File)this.pack_bin(a); -else if(c==ArrayBuffer)this.pack_bin(h.useArrayBufferView?new Uint8Array(a):a);else if("BYTES_PER_ELEMENT"in a)this.pack_bin(h.useArrayBufferView?new Uint8Array(a.buffer):a.buffer);else if(c==Object)this.pack_object(a);else if(c==Date)this.pack_string(a.toString());else{if("function"!=typeof a.toBinaryPack)throw new Error('Type "'+c.toString()+'" not yet supported');this.bufferBuilder.append(a.toBinaryPack())}}}this.bufferBuilder.flush()},d.prototype.pack_bin=function(a){var b=a.length||a.byteLength||a.size;if(15>=b)this.pack_uint8(160+b);else if(65535>=b)this.bufferBuilder.append(218),this.pack_uint16(b);else{if(!(4294967295>=b))throw new Error("Invalid length");this.bufferBuilder.append(219),this.pack_uint32(b)}this.bufferBuilder.append(a)},d.prototype.pack_string=function(a){var b=f(a);if(15>=b)this.pack_uint8(176+b);else if(65535>=b)this.bufferBuilder.append(216),this.pack_uint16(b);else{if(!(4294967295>=b))throw new Error("Invalid length");this.bufferBuilder.append(217),this.pack_uint32(b)}this.bufferBuilder.append(a)},d.prototype.pack_array=function(a){var b=a.length;if(15>=b)this.pack_uint8(144+b);else if(65535>=b)this.bufferBuilder.append(220),this.pack_uint16(b);else{if(!(4294967295>=b))throw new Error("Invalid length");this.bufferBuilder.append(221),this.pack_uint32(b)}for(var c=0;b>c;c++)this.pack(a[c])},d.prototype.pack_integer=function(a){if(a>=-32&&127>=a)this.bufferBuilder.append(255&a);else if(a>=0&&255>=a)this.bufferBuilder.append(204),this.pack_uint8(a);else if(a>=-128&&127>=a)this.bufferBuilder.append(208),this.pack_int8(a);else if(a>=0&&65535>=a)this.bufferBuilder.append(205),this.pack_uint16(a);else if(a>=-32768&&32767>=a)this.bufferBuilder.append(209),this.pack_int16(a);else if(a>=0&&4294967295>=a)this.bufferBuilder.append(206),this.pack_uint32(a);else if(a>=-2147483648&&2147483647>=a)this.bufferBuilder.append(210),this.pack_int32(a);else if(a>=-0x8000000000000000&&0x8000000000000000>=a)this.bufferBuilder.append(211),this.pack_int64(a);else{if(!(a>=0&&0x10000000000000000>=a))throw new Error("Invalid integer");this.bufferBuilder.append(207),this.pack_uint64(a)}},d.prototype.pack_double=function(a){var b=0;0>a&&(b=1,a=-a);var c=Math.floor(Math.log(a)/Math.LN2),d=a/Math.pow(2,c)-1,e=Math.floor(d*Math.pow(2,52)),f=Math.pow(2,32),g=b<<31|c+1023<<20|e/f&1048575,h=e%f;this.bufferBuilder.append(203),this.pack_int32(g),this.pack_int32(h)},d.prototype.pack_object=function(a){var b=Object.keys(a),c=b.length;if(15>=c)this.pack_uint8(128+c);else if(65535>=c)this.bufferBuilder.append(222),this.pack_uint16(c);else{if(!(4294967295>=c))throw new Error("Invalid length");this.bufferBuilder.append(223),this.pack_uint32(c)}for(var d in a)a.hasOwnProperty(d)&&(this.pack(d),this.pack(a[d]))},d.prototype.pack_uint8=function(a){this.bufferBuilder.append(a)},d.prototype.pack_uint16=function(a){this.bufferBuilder.append(a>>8),this.bufferBuilder.append(255&a)},d.prototype.pack_uint32=function(a){var b=4294967295&a;this.bufferBuilder.append((4278190080&b)>>>24),this.bufferBuilder.append((16711680&b)>>>16),this.bufferBuilder.append((65280&b)>>>8),this.bufferBuilder.append(255&b)},d.prototype.pack_uint64=function(a){var b=a/Math.pow(2,32),c=a%Math.pow(2,32);this.bufferBuilder.append((4278190080&b)>>>24),this.bufferBuilder.append((16711680&b)>>>16),this.bufferBuilder.append((65280&b)>>>8),this.bufferBuilder.append(255&b),this.bufferBuilder.append((4278190080&c)>>>24),this.bufferBuilder.append((16711680&c)>>>16),this.bufferBuilder.append((65280&c)>>>8),this.bufferBuilder.append(255&c)},d.prototype.pack_int8=function(a){this.bufferBuilder.append(255&a)},d.prototype.pack_int16=function(a){this.bufferBuilder.append((65280&a)>>8),this.bufferBuilder.append(255&a)},d.prototype.pack_int32=function(a){this.bufferBuilder.append(a>>>24&255),this.bufferBuilder.append((16711680&a)>>>16),this.bufferBuilder.append((65280&a)>>>8),this.bufferBuilder.append(255&a)},d.prototype.pack_int64=function(a){var b=Math.floor(a/Math.pow(2,32)),c=a%Math.pow(2,32);this.bufferBuilder.append((4278190080&b)>>>24),this.bufferBuilder.append((16711680&b)>>>16),this.bufferBuilder.append((65280&b)>>>8),this.bufferBuilder.append(255&b),this.bufferBuilder.append((4278190080&c)>>>24),this.bufferBuilder.append((16711680&c)>>>16),this.bufferBuilder.append((65280&c)>>>8),this.bufferBuilder.append(255&c)}},{"./bufferbuilder":11}],11:[function(a,b){function c(){this._pieces=[],this._parts=[]}var d={};d.useBlobBuilder=function(){try{return new Blob([]),!1}catch(a){return!0}}(),d.useArrayBufferView=!d.useBlobBuilder&&function(){try{return 0===new Blob([new Uint8Array([])]).size}catch(a){return!0}}(),b.exports.binaryFeatures=d;var e=b.exports.BlobBuilder;"undefined"!=typeof window&&(e=b.exports.BlobBuilder=window.WebKitBlobBuilder||window.MozBlobBuilder||window.MSBlobBuilder||window.BlobBuilder),c.prototype.append=function(a){"number"==typeof a?this._pieces.push(a):(this.flush(),this._parts.push(a))},c.prototype.flush=function(){if(this._pieces.length>0){var a=new Uint8Array(this._pieces);d.useArrayBufferView||(a=a.buffer),this._parts.push(a),this._pieces=[]}},c.prototype.getBuffer=function(){if(this.flush(),d.useBlobBuilder){for(var a=new e,b=0,c=this._parts.length;c>b;b++)a.append(this._parts[b]);return a.getBlob()}return new Blob(this._parts)},b.exports.BufferBuilder=c},{}],12:[function(a,b){function c(a,b){return this instanceof c?(this._dc=a,d.debug=b,this._outgoing={},this._incoming={},this._received={},this._window=1e3,this._mtu=500,this._interval=0,this._count=0,this._queue=[],void this._setupDC()):new c(a)}var d=a("./util");c.prototype.send=function(a){var b=d.pack(a);return b.size<this._mtu?void this._handleSend(["no",b]):(this._outgoing[this._count]={ack:0,chunks:this._chunk(b)},d.debug&&(this._outgoing[this._count].timer=new Date),this._sendWindowedChunks(this._count),void(this._count+=1))},c.prototype._setupInterval=function(){var a=this;this._timeout=setInterval(function(){var b=a._queue.shift();if(b._multiple)for(var c=0,d=b.length;d>c;c+=1)a._intervalSend(b[c]);else a._intervalSend(b)},this._interval)},c.prototype._intervalSend=function(a){var b=this;a=d.pack(a),d.blobToBinaryString(a,function(a){b._dc.send(a)}),0===b._queue.length&&(clearTimeout(b._timeout),b._timeout=null)},c.prototype._processAcks=function(){for(var a in this._outgoing)this._outgoing.hasOwnProperty(a)&&this._sendWindowedChunks(a)},c.prototype._handleSend=function(a){for(var b=!0,c=0,d=this._queue.length;d>c;c+=1){var e=this._queue[c];e===a?b=!1:e._multiple&&-1!==e.indexOf(a)&&(b=!1)}b&&(this._queue.push(a),this._timeout||this._setupInterval())},c.prototype._setupDC=function(){var a=this;this._dc.onmessage=function(b){var c=b.data,e=c.constructor;if(e===String){var f=d.binaryStringToArrayBuffer(c);c=d.unpack(f),a._handleMessage(c)}}},c.prototype._handleMessage=function(a){var b,c=a[1],e=this._incoming[c],f=this._outgoing[c];switch(a[0]){case"no":var g=c;g&&this.onmessage(d.unpack(g));break;case"end":if(b=e,this._received[c]=a[2],!b)break;this._ack(c);break;case"ack":if(b=f){var h=a[2];b.ack=Math.max(h,b.ack),b.ack>=b.chunks.length?(d.log("Time: ",new Date-b.timer),delete this._outgoing[c]):this._processAcks()}break;case"chunk":if(b=e,!b){var i=this._received[c];if(i===!0)break;b={ack:["ack",c,0],chunks:[]},this._incoming[c]=b}var j=a[2],k=a[3];b.chunks[j]=new Uint8Array(k),j===b.ack[2]&&this._calculateNextAck(c),this._ack(c);break;default:this._handleSend(a)}},c.prototype._chunk=function(a){for(var b=[],c=a.size,e=0;c>e;){var f=Math.min(c,e+this._mtu),g=a.slice(e,f),h={payload:g};b.push(h),e=f}return d.log("Created",b.length,"chunks."),b},c.prototype._ack=function(a){var b=this._incoming[a].ack;this._received[a]===b[2]&&(this._complete(a),this._received[a]=!0),this._handleSend(b)},c.prototype._calculateNextAck=function(a){for(var b=this._incoming[a],c=b.chunks,d=0,e=c.length;e>d;d+=1)if(void 0===c[d])return void(b.ack[2]=d);b.ack[2]=c.length},c.prototype._sendWindowedChunks=function(a){d.log("sendWindowedChunks for: ",a);for(var b=this._outgoing[a],c=b.chunks,e=[],f=Math.min(b.ack+this._window,c.length),g=b.ack;f>g;g+=1)c[g].sent&&g!==b.ack||(c[g].sent=!0,e.push(["chunk",a,g,c[g].payload]));b.ack+this._window>=c.length&&e.push(["end",a,c.length]),e._multiple=!0,this._handleSend(e)},c.prototype._complete=function(a){d.log("Completed called for",a);var b=this,c=this._incoming[a].chunks,e=new Blob(c);d.blobToArrayBuffer(e,function(a){b.onmessage(d.unpack(a))}),delete this._incoming[a]},c.higherBandwidthSDP=function(a){var b=navigator.appVersion.match(/Chrome\/(.*?) /);if(b&&(b=parseInt(b[1].split(".").shift()),31>b)){var c=a.split("b=AS:30"),d="b=AS:102400";if(c.length>1)return c[0]+d+c[1]}return a},c.prototype.onmessage=function(){},b.exports.Reliable=c},{"./util":13}],13:[function(a,b){var c=a("js-binarypack"),d={debug:!1,inherits:function(a,b){a.super_=b,a.prototype=Object.create(b.prototype,{constructor:{value:a,enumerable:!1,writable:!0,configurable:!0}})},extend:function(a,b){for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c]);return a},pack:c.pack,unpack:c.unpack,log:function(){if(d.debug){for(var a=[],b=0;b<arguments.length;b++)a[b]=arguments[b];a.unshift("Reliable: "),console.log.apply(console,a)}},setZeroTimeout:function(a){function b(b){d.push(b),a.postMessage(e,"*")}function c(b){b.source==a&&b.data==e&&(b.stopPropagation&&b.stopPropagation(),d.length&&d.shift()())}var d=[],e="zero-timeout-message";return a.addEventListener?a.addEventListener("message",c,!0):a.attachEvent&&a.attachEvent("onmessage",c),b}(this),blobToArrayBuffer:function(a,b){var c=new FileReader;c.onload=function(a){b(a.target.result)},c.readAsArrayBuffer(a)},blobToBinaryString:function(a,b){var c=new FileReader;c.onload=function(a){b(a.target.result)},c.readAsBinaryString(a)},binaryStringToArrayBuffer:function(a){for(var b=new Uint8Array(a.length),c=0;c<a.length;c++)b[c]=255&a.charCodeAt(c);return b.buffer},randomToken:function(){return Math.random().toString(36).substr(2)}};b.exports=d},{"js-binarypack":10}]},{},[3]);</script><script> -'use strict'; -window.Chat = window.Chat || {}; -Chat.FileTransferProtocol = { - properties: { - loading: { - type: Boolean, - notify: true, - value: false, - observer: '_loadingChanged' - }, - buddies: { - notify: true - } - }, - listeners: { - 'system-event': '_onSystemMsg', - 'file-received': '_onFileReceived', - }, - _onSystemMsg: function(event) { - var msg = event.detail; - console.log('FTP received sysMsg:', msg); - - switch (msg.type) { - case 'handshake': - this._onHandshake(msg); - break; - case 'offer': - this._onOffered(msg); - break; - case 'decline': - this._onDeclined(msg); - break; - case 'accept': - this._onAccepted(msg); - break; - case 'transfer': - this._onTransfer(msg); - break; - case 'received': - this._onReceived(msg); - break; - case 'buddies': - this._onBuddies(msg); - break; - case 'text': - this._onTextReceived(msg); - break; - } - }, - sendFile: function(peerId, file) { - this.set('loading', true); - this.fileToSend = file; - this.fire('file-offered', { - to: peerId - }); - this.connectToPeer(peerId, function() { - this._offer(peerId, file); - }.bind(this)); - - //set 15sec timeout - this._timeoutTimer = this.async(function() { - this._onError(); - }, 15000); - }, - _offer: function(toPeer, file) { - console.log('FTP offer file:', file, 'To:', toPeer); - - this._sendSystemEvent(toPeer, { - type: 'offer', - name: file.name - }); - }, - _onOffered: function(offer) { - console.log('FTP offered file:', offer.name, 'From:', offer.from); - this.fire('file-offer', { - from: offer.from, - name: offer.name - }); - }, - decline: function(offer) { - this._sendSystemEvent(offer.from, { - type: 'decline', - name: offer.name - }); - }, - _onDeclined: function(offer) { - this.cancelAsync(this._timeoutTimer); - delete this.fileToSend; - this.set('loading', false); - this.fire('file-declined', offer); - }, - accept: function(offer) { - this._sendSystemEvent(offer.from, { - type: 'accept', - name: offer.name - }); - this.fire('download-started', { - from: offer.from - }); - }, - _onAccepted: function(offer) { - this.cancelAsync(this._timeoutTimer); - this._sendSystemEvent(offer.from, { - type: 'transfer', - name: offer.name - }); - this.fire('upload-started', { - to: offer.from - }); - this._sendFile(offer.from, this.fileToSend); - }, - _onTransfer: function() { - this.loading = true; - }, - _onFileReceived: function(event) { - var file = event.detail; - this.loading = false; - this._sendSystemEvent(file.from, { - type: 'received', - name: file.name - }); - this.fire('download-complete', { - from: file.from - }); - console.log('FTP received:', file); - }, - _onReceived: function(offer) { - this.loading = false; - this.fire('upload-complete', offer); - if(window.donateDialog){ - window.donateDialog.open(); - } - }, - _onError: function() { - this.loading = false; - this.fire('upload-error'); - }, - _loadingChanged: function(loading) { - window.anim(loading); - }, - _onBuddies: function(msg) { - this.set('buddies', msg.buddies); - }, - sendText: function(toPeer, text) { - console.log('FTP send text:', text, 'To:', toPeer); - this.connectToPeer(toPeer, function() { - this._sendSystemEvent(toPeer, { - type: 'text', - text: text - }); - }.bind(this)); - - }, - _onTextReceived: function(msg) { - this.fire('text-received', msg); - } -}; -</script><script> - /*! binary.js build:0.2.2, development. Copyright(c) 2012 Eric Zhang <eric@ericzhang.com> MIT Licensed */ -(function(exports){ -/*! binarypack.js build:0.0.9, production. Copyright(c) 2012 Eric Zhang <eric@ericzhang.com> MIT Licensed */(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ -var BufferBuilder = require('./bufferbuilder').BufferBuilder; -var binaryFeatures = require('./bufferbuilder').binaryFeatures; - -var BinaryPack = { - unpack: function(data){ - var unpacker = new Unpacker(data); - return unpacker.unpack(); - }, - pack: function(data){ - var packer = new Packer(); - packer.pack(data); - var buffer = packer.getBuffer(); - return buffer; - } -}; - -module.exports = BinaryPack; - -function Unpacker (data){ - // Data is ArrayBuffer - this.index = 0; - this.dataBuffer = data; - this.dataView = new Uint8Array(this.dataBuffer); - this.length = this.dataBuffer.byteLength; -} - -Unpacker.prototype.unpack = function(){ - var type = this.unpack_uint8(); - if (type < 0x80){ - var positive_fixnum = type; - return positive_fixnum; - } else if ((type ^ 0xe0) < 0x20){ - var negative_fixnum = (type ^ 0xe0) - 0x20; - return negative_fixnum; - } - var size; - if ((size = type ^ 0xa0) <= 0x0f){ - return this.unpack_raw(size); - } else if ((size = type ^ 0xb0) <= 0x0f){ - return this.unpack_string(size); - } else if ((size = type ^ 0x90) <= 0x0f){ - return this.unpack_array(size); - } else if ((size = type ^ 0x80) <= 0x0f){ - return this.unpack_map(size); - } - switch(type){ - case 0xc0: - return null; - case 0xc1: - return undefined; - case 0xc2: - return false; - case 0xc3: - return true; - case 0xca: - return this.unpack_float(); - case 0xcb: - return this.unpack_double(); - case 0xcc: - return this.unpack_uint8(); - case 0xcd: - return this.unpack_uint16(); - case 0xce: - return this.unpack_uint32(); - case 0xcf: - return this.unpack_uint64(); - case 0xd0: - return this.unpack_int8(); - case 0xd1: - return this.unpack_int16(); - case 0xd2: - return this.unpack_int32(); - case 0xd3: - return this.unpack_int64(); - case 0xd4: - return undefined; - case 0xd5: - return undefined; - case 0xd6: - return undefined; - case 0xd7: - return undefined; - case 0xd8: - size = this.unpack_uint16(); - return this.unpack_string(size); - case 0xd9: - size = this.unpack_uint32(); - return this.unpack_string(size); - case 0xda: - size = this.unpack_uint16(); - return this.unpack_raw(size); - case 0xdb: - size = this.unpack_uint32(); - return this.unpack_raw(size); - case 0xdc: - size = this.unpack_uint16(); - return this.unpack_array(size); - case 0xdd: - size = this.unpack_uint32(); - return this.unpack_array(size); - case 0xde: - size = this.unpack_uint16(); - return this.unpack_map(size); - case 0xdf: - size = this.unpack_uint32(); - return this.unpack_map(size); - } -} - -Unpacker.prototype.unpack_uint8 = function(){ - var byte = this.dataView[this.index] & 0xff; - this.index++; - return byte; -}; - -Unpacker.prototype.unpack_uint16 = function(){ - var bytes = this.read(2); - var uint16 = - ((bytes[0] & 0xff) * 256) + (bytes[1] & 0xff); - this.index += 2; - return uint16; -} - -Unpacker.prototype.unpack_uint32 = function(){ - var bytes = this.read(4); - var uint32 = - ((bytes[0] * 256 + - bytes[1]) * 256 + - bytes[2]) * 256 + - bytes[3]; - this.index += 4; - return uint32; -} - -Unpacker.prototype.unpack_uint64 = function(){ - var bytes = this.read(8); - var uint64 = - ((((((bytes[0] * 256 + - bytes[1]) * 256 + - bytes[2]) * 256 + - bytes[3]) * 256 + - bytes[4]) * 256 + - bytes[5]) * 256 + - bytes[6]) * 256 + - bytes[7]; - this.index += 8; - return uint64; -} - - -Unpacker.prototype.unpack_int8 = function(){ - var uint8 = this.unpack_uint8(); - return (uint8 < 0x80 ) ? uint8 : uint8 - (1 << 8); -}; - -Unpacker.prototype.unpack_int16 = function(){ - var uint16 = this.unpack_uint16(); - return (uint16 < 0x8000 ) ? uint16 : uint16 - (1 << 16); -} - -Unpacker.prototype.unpack_int32 = function(){ - var uint32 = this.unpack_uint32(); - return (uint32 < Math.pow(2, 31) ) ? uint32 : - uint32 - Math.pow(2, 32); -} - -Unpacker.prototype.unpack_int64 = function(){ - var uint64 = this.unpack_uint64(); - return (uint64 < Math.pow(2, 63) ) ? uint64 : - uint64 - Math.pow(2, 64); -} - -Unpacker.prototype.unpack_raw = function(size){ - if ( this.length < this.index + size){ - throw new Error('BinaryPackFailure: index is out of range' - + ' ' + this.index + ' ' + size + ' ' + this.length); - } - var buf = this.dataBuffer.slice(this.index, this.index + size); - this.index += size; - - //buf = util.bufferToString(buf); - - return buf; -} - -Unpacker.prototype.unpack_string = function(size){ - var bytes = this.read(size); - var i = 0, str = '', c, code; - while(i < size){ - c = bytes[i]; - if ( c < 128){ - str += String.fromCharCode(c); - i++; - } else if ((c ^ 0xc0) < 32){ - code = ((c ^ 0xc0) << 6) | (bytes[i+1] & 63); - str += String.fromCharCode(code); - i += 2; - } else { - code = ((c & 15) << 12) | ((bytes[i+1] & 63) << 6) | - (bytes[i+2] & 63); - str += String.fromCharCode(code); - i += 3; - } - } - this.index += size; - return str; -} - -Unpacker.prototype.unpack_array = function(size){ - var objects = new Array(size); - for(var i = 0; i < size ; i++){ - objects[i] = this.unpack(); - } - return objects; -} - -Unpacker.prototype.unpack_map = function(size){ - var map = {}; - for(var i = 0; i < size ; i++){ - var key = this.unpack(); - var value = this.unpack(); - map[key] = value; - } - return map; -} - -Unpacker.prototype.unpack_float = function(){ - var uint32 = this.unpack_uint32(); - var sign = uint32 >> 31; - var exp = ((uint32 >> 23) & 0xff) - 127; - var fraction = ( uint32 & 0x7fffff ) | 0x800000; - return (sign == 0 ? 1 : -1) * - fraction * Math.pow(2, exp - 23); -} - -Unpacker.prototype.unpack_double = function(){ - var h32 = this.unpack_uint32(); - var l32 = this.unpack_uint32(); - var sign = h32 >> 31; - var exp = ((h32 >> 20) & 0x7ff) - 1023; - var hfrac = ( h32 & 0xfffff ) | 0x100000; - var frac = hfrac * Math.pow(2, exp - 20) + - l32 * Math.pow(2, exp - 52); - return (sign == 0 ? 1 : -1) * frac; -} - -Unpacker.prototype.read = function(length){ - var j = this.index; - if (j + length <= this.length) { - return this.dataView.subarray(j, j + length); - } else { - throw new Error('BinaryPackFailure: read index out of range'); - } -} - -function Packer(){ - this.bufferBuilder = new BufferBuilder(); -} - -Packer.prototype.getBuffer = function(){ - return this.bufferBuilder.getBuffer(); -} - -Packer.prototype.pack = function(value){ - var type = typeof(value); - if (type == 'string'){ - this.pack_string(value); - } else if (type == 'number'){ - if (Math.floor(value) === value){ - this.pack_integer(value); - } else{ - this.pack_double(value); - } - } else if (type == 'boolean'){ - if (value === true){ - this.bufferBuilder.append(0xc3); - } else if (value === false){ - this.bufferBuilder.append(0xc2); - } - } else if (type == 'undefined'){ - this.bufferBuilder.append(0xc0); - } else if (type == 'object'){ - if (value === null){ - this.bufferBuilder.append(0xc0); - } else { - var constructor = value.constructor; - if (constructor == Array){ - this.pack_array(value); - } else if (constructor == Blob || constructor == File) { - this.pack_bin(value); - } else if (constructor == ArrayBuffer) { - if(binaryFeatures.useArrayBufferView) { - this.pack_bin(new Uint8Array(value)); - } else { - this.pack_bin(value); - } - } else if ('BYTES_PER_ELEMENT' in value){ - if(binaryFeatures.useArrayBufferView) { - this.pack_bin(new Uint8Array(value.buffer)); - } else { - this.pack_bin(value.buffer); - } - } else if (constructor == Object){ - this.pack_object(value); - } else if (constructor == Date){ - this.pack_string(value.toString()); - } else if (typeof value.toBinaryPack == 'function'){ - this.bufferBuilder.append(value.toBinaryPack()); - } else { - throw new Error('Type "' + constructor.toString() + '" not yet supported'); - } - } - } else { - throw new Error('Type "' + type + '" not yet supported'); - } - this.bufferBuilder.flush(); -} - - -Packer.prototype.pack_bin = function(blob){ - var length = blob.length || blob.byteLength || blob.size; - if (length <= 0x0f){ - this.pack_uint8(0xa0 + length); - } else if (length <= 0xffff){ - this.bufferBuilder.append(0xda) ; - this.pack_uint16(length); - } else if (length <= 0xffffffff){ - this.bufferBuilder.append(0xdb); - this.pack_uint32(length); - } else{ - throw new Error('Invalid length'); - } - this.bufferBuilder.append(blob); -} - -Packer.prototype.pack_string = function(str){ - var length = utf8Length(str); - - if (length <= 0x0f){ - this.pack_uint8(0xb0 + length); - } else if (length <= 0xffff){ - this.bufferBuilder.append(0xd8) ; - this.pack_uint16(length); - } else if (length <= 0xffffffff){ - this.bufferBuilder.append(0xd9); - this.pack_uint32(length); - } else{ - throw new Error('Invalid length'); - } - this.bufferBuilder.append(str); -} - -Packer.prototype.pack_array = function(ary){ - var length = ary.length; - if (length <= 0x0f){ - this.pack_uint8(0x90 + length); - } else if (length <= 0xffff){ - this.bufferBuilder.append(0xdc) - this.pack_uint16(length); - } else if (length <= 0xffffffff){ - this.bufferBuilder.append(0xdd); - this.pack_uint32(length); - } else{ - throw new Error('Invalid length'); - } - for(var i = 0; i < length ; i++){ - this.pack(ary[i]); - } -} - -Packer.prototype.pack_integer = function(num){ - if ( -0x20 <= num && num <= 0x7f){ - this.bufferBuilder.append(num & 0xff); - } else if (0x00 <= num && num <= 0xff){ - this.bufferBuilder.append(0xcc); - this.pack_uint8(num); - } else if (-0x80 <= num && num <= 0x7f){ - this.bufferBuilder.append(0xd0); - this.pack_int8(num); - } else if ( 0x0000 <= num && num <= 0xffff){ - this.bufferBuilder.append(0xcd); - this.pack_uint16(num); - } else if (-0x8000 <= num && num <= 0x7fff){ - this.bufferBuilder.append(0xd1); - this.pack_int16(num); - } else if ( 0x00000000 <= num && num <= 0xffffffff){ - this.bufferBuilder.append(0xce); - this.pack_uint32(num); - } else if (-0x80000000 <= num && num <= 0x7fffffff){ - this.bufferBuilder.append(0xd2); - this.pack_int32(num); - } else if (-0x8000000000000000 <= num && num <= 0x7FFFFFFFFFFFFFFF){ - this.bufferBuilder.append(0xd3); - this.pack_int64(num); - } else if (0x0000000000000000 <= num && num <= 0xFFFFFFFFFFFFFFFF){ - this.bufferBuilder.append(0xcf); - this.pack_uint64(num); - } else{ - throw new Error('Invalid integer'); - } -} - -Packer.prototype.pack_double = function(num){ - var sign = 0; - if (num < 0){ - sign = 1; - num = -num; - } - var exp = Math.floor(Math.log(num) / Math.LN2); - var frac0 = num / Math.pow(2, exp) - 1; - var frac1 = Math.floor(frac0 * Math.pow(2, 52)); - var b32 = Math.pow(2, 32); - var h32 = (sign << 31) | ((exp+1023) << 20) | - (frac1 / b32) & 0x0fffff; - var l32 = frac1 % b32; - this.bufferBuilder.append(0xcb); - this.pack_int32(h32); - this.pack_int32(l32); -} - -Packer.prototype.pack_object = function(obj){ - var keys = Object.keys(obj); - var length = keys.length; - if (length <= 0x0f){ - this.pack_uint8(0x80 + length); - } else if (length <= 0xffff){ - this.bufferBuilder.append(0xde); - this.pack_uint16(length); - } else if (length <= 0xffffffff){ - this.bufferBuilder.append(0xdf); - this.pack_uint32(length); - } else{ - throw new Error('Invalid length'); - } - for(var prop in obj){ - if (obj.hasOwnProperty(prop)){ - this.pack(prop); - this.pack(obj[prop]); - } - } -} - -Packer.prototype.pack_uint8 = function(num){ - this.bufferBuilder.append(num); -} - -Packer.prototype.pack_uint16 = function(num){ - this.bufferBuilder.append(num >> 8); - this.bufferBuilder.append(num & 0xff); -} - -Packer.prototype.pack_uint32 = function(num){ - var n = num & 0xffffffff; - this.bufferBuilder.append((n & 0xff000000) >>> 24); - this.bufferBuilder.append((n & 0x00ff0000) >>> 16); - this.bufferBuilder.append((n & 0x0000ff00) >>> 8); - this.bufferBuilder.append((n & 0x000000ff)); -} - -Packer.prototype.pack_uint64 = function(num){ - var high = num / Math.pow(2, 32); - var low = num % Math.pow(2, 32); - this.bufferBuilder.append((high & 0xff000000) >>> 24); - this.bufferBuilder.append((high & 0x00ff0000) >>> 16); - this.bufferBuilder.append((high & 0x0000ff00) >>> 8); - this.bufferBuilder.append((high & 0x000000ff)); - this.bufferBuilder.append((low & 0xff000000) >>> 24); - this.bufferBuilder.append((low & 0x00ff0000) >>> 16); - this.bufferBuilder.append((low & 0x0000ff00) >>> 8); - this.bufferBuilder.append((low & 0x000000ff)); -} - -Packer.prototype.pack_int8 = function(num){ - this.bufferBuilder.append(num & 0xff); -} - -Packer.prototype.pack_int16 = function(num){ - this.bufferBuilder.append((num & 0xff00) >> 8); - this.bufferBuilder.append(num & 0xff); -} - -Packer.prototype.pack_int32 = function(num){ - this.bufferBuilder.append((num >>> 24) & 0xff); - this.bufferBuilder.append((num & 0x00ff0000) >>> 16); - this.bufferBuilder.append((num & 0x0000ff00) >>> 8); - this.bufferBuilder.append((num & 0x000000ff)); -} - -Packer.prototype.pack_int64 = function(num){ - var high = Math.floor(num / Math.pow(2, 32)); - var low = num % Math.pow(2, 32); - this.bufferBuilder.append((high & 0xff000000) >>> 24); - this.bufferBuilder.append((high & 0x00ff0000) >>> 16); - this.bufferBuilder.append((high & 0x0000ff00) >>> 8); - this.bufferBuilder.append((high & 0x000000ff)); - this.bufferBuilder.append((low & 0xff000000) >>> 24); - this.bufferBuilder.append((low & 0x00ff0000) >>> 16); - this.bufferBuilder.append((low & 0x0000ff00) >>> 8); - this.bufferBuilder.append((low & 0x000000ff)); -} - -function _utf8Replace(m){ - var code = m.charCodeAt(0); - - if(code <= 0x7ff) return '00'; - if(code <= 0xffff) return '000'; - if(code <= 0x1fffff) return '0000'; - if(code <= 0x3ffffff) return '00000'; - return '000000'; -} - -function utf8Length(str){ - if (str.length > 600) { - // Blob method faster for large strings - return (new Blob([str])).size; - } else { - return str.replace(/[^\u0000-\u007F]/g, _utf8Replace).length; - } -} - -},{"./bufferbuilder":2}],2:[function(require,module,exports){ -var binaryFeatures = {}; -binaryFeatures.useBlobBuilder = (function(){ - try { - new Blob([]); - return false; - } catch (e) { - return true; - } -})(); - -binaryFeatures.useArrayBufferView = !binaryFeatures.useBlobBuilder && (function(){ - try { - return (new Blob([new Uint8Array([])])).size === 0; - } catch (e) { - return true; - } -})(); - -module.exports.binaryFeatures = binaryFeatures; -var BlobBuilder = module.exports.BlobBuilder; -if (typeof window != 'undefined') { - BlobBuilder = module.exports.BlobBuilder = window.WebKitBlobBuilder || - window.MozBlobBuilder || window.MSBlobBuilder || window.BlobBuilder; -} - -function BufferBuilder(){ - this._pieces = []; - this._parts = []; -} - -BufferBuilder.prototype.append = function(data) { - if(typeof data === 'number') { - this._pieces.push(data); - } else { - this.flush(); - this._parts.push(data); - } -}; - -BufferBuilder.prototype.flush = function() { - if (this._pieces.length > 0) { - var buf = new Uint8Array(this._pieces); - if(!binaryFeatures.useArrayBufferView) { - buf = buf.buffer; - } - this._parts.push(buf); - this._pieces = []; - } -}; - -BufferBuilder.prototype.getBuffer = function() { - this.flush(); - if(binaryFeatures.useBlobBuilder) { - var builder = new BlobBuilder(); - for(var i = 0, ii = this._parts.length; i < ii; i++) { - builder.append(this._parts[i]); - } - return builder.getBlob(); - } else { - return new Blob(this._parts); - } -}; - -module.exports.BufferBuilder = BufferBuilder; - -},{}],3:[function(require,module,exports){ -var BufferBuilderExports = require('./bufferbuilder'); - -window.BufferBuilder = BufferBuilderExports.BufferBuilder; -window.binaryFeatures = BufferBuilderExports.binaryFeatures; -window.BlobBuilder = BufferBuilderExports.BlobBuilder; -window.BinaryPack = require('./binarypack'); - -},{"./binarypack":1,"./bufferbuilder":2}]},{},[3]); -/** - * Light EventEmitter. Ported from Node.js/events.js - * Eric Zhang - */ - -/** - * EventEmitter class - * Creates an object with event registering and firing methods - */ -function EventEmitter() { - // Initialise required storage variables - this._events = {}; -} - -var isArray = Array.isArray; - - -EventEmitter.prototype.addListener = function(type, listener, scope, once) { - if ('function' !== typeof listener) { - throw new Error('addListener only takes instances of Function'); - } - - // To avoid recursion in the case that type == "newListeners"! Before - // adding it to the listeners, first emit "newListeners". - this.emit('newListener', type, typeof listener.listener === 'function' ? - listener.listener : listener); - - if (!this._events[type]) { - // Optimize the case of one listener. Don't need the extra array object. - this._events[type] = listener; - } else if (isArray(this._events[type])) { - - // If we've already got an array, just append. - this._events[type].push(listener); - - } else { - // Adding the second element, need to change to array. - this._events[type] = [this._events[type], listener]; - } - -}; - -EventEmitter.prototype.on = EventEmitter.prototype.addListener; - -EventEmitter.prototype.once = function(type, listener, scope) { - if ('function' !== typeof listener) { - throw new Error('.once only takes instances of Function'); - } - - var self = this; - function g() { - self.removeListener(type, g); - listener.apply(this, arguments); - }; - - g.listener = listener; - self.on(type, g); - - return this; -}; - -EventEmitter.prototype.removeListener = function(type, listener, scope) { - if ('function' !== typeof listener) { - throw new Error('removeListener only takes instances of Function'); - } - - // does not use listeners(), so no side effect of creating _events[type] - if (!this._events[type]) return this; - - var list = this._events[type]; - - if (isArray(list)) { - var position = -1; - for (var i = 0, length = list.length; i < length; i++) { - if (list[i] === listener || - (list[i].listener && list[i].listener === listener)) - { - position = i; - break; - } - } - - if (position < 0) return this; - list.splice(position, 1); - if (list.length == 0) - delete this._events[type]; - } else if (list === listener || - (list.listener && list.listener === listener)) - { - delete this._events[type]; - } - - return this; -}; - - -EventEmitter.prototype.off = EventEmitter.prototype.removeListener; - - -EventEmitter.prototype.removeAllListeners = function(type) { - if (arguments.length === 0) { - this._events = {}; - return this; - } - - // does not use listeners(), so no side effect of creating _events[type] - if (type && this._events && this._events[type]) this._events[type] = null; - return this; -}; - -EventEmitter.prototype.listeners = function(type) { - if (!this._events[type]) this._events[type] = []; - if (!isArray(this._events[type])) { - this._events[type] = [this._events[type]]; - } - return this._events[type]; -}; - -EventEmitter.prototype.emit = function(type) { - var type = arguments[0]; - var handler = this._events[type]; - if (!handler) return false; - - if (typeof handler == 'function') { - switch (arguments.length) { - // fast cases - case 1: - handler.call(this); - break; - case 2: - handler.call(this, arguments[1]); - break; - case 3: - handler.call(this, arguments[1], arguments[2]); - break; - // slower - default: - var l = arguments.length; - var args = new Array(l - 1); - for (var i = 1; i < l; i++) args[i - 1] = arguments[i]; - handler.apply(this, args); - } - return true; - - } else if (isArray(handler)) { - var l = arguments.length; - var args = new Array(l - 1); - for (var i = 1; i < l; i++) args[i - 1] = arguments[i]; - - var listeners = handler.slice(); - for (var i = 0, l = listeners.length; i < l; i++) { - listeners[i].apply(this, args); - } - return true; - } else { - return false; - } -}; - - - - -var util = { - inherits: function(ctor, superCtor) { - ctor.super_ = superCtor; - ctor.prototype = Object.create(superCtor.prototype, { - constructor: { - value: ctor, - enumerable: false, - writable: true, - configurable: true - } - }); - }, - extend: function(dest, source) { - for(var key in source) { - if(source.hasOwnProperty(key)) { - dest[key] = source[key]; - } - } - return dest; - }, - pack: BinaryPack.pack, - unpack: BinaryPack.unpack, - setZeroTimeout: (function(global) { - var timeouts = []; - var messageName = 'zero-timeout-message'; - - // Like setTimeout, but only takes a function argument. There's - // no time argument (always zero) and no arguments (you have to - // use a closure). - function setZeroTimeoutPostMessage(fn) { - timeouts.push(fn); - global.postMessage(messageName, '*'); - } - - function handleMessage(event) { - if (event.source == global && event.data == messageName) { - if (event.stopPropagation) { - event.stopPropagation(); - } - if (timeouts.length) { - timeouts.shift()(); - } - } - } - if (global.addEventListener) { - global.addEventListener('message', handleMessage, true); - } else if (global.attachEvent) { - global.attachEvent('onmessage', handleMessage); - } - return setZeroTimeoutPostMessage; - }(this)) -}; - -exports.util = util; - - -function Stream() { - EventEmitter.call(this); -} - -util.inherits(Stream, EventEmitter); - -Stream.prototype.pipe = function(dest, options) { - var source = this; - - function ondata(chunk) { - if (dest.writable) { - if (false === dest.write(chunk) && source.pause) { - source.pause(); - } - } - } - - source.on('data', ondata); - - function ondrain() { - if (source.readable && source.resume) { - source.resume(); - } - } - - dest.on('drain', ondrain); - - // If the 'end' option is not supplied, dest.end() will be called when - // source gets the 'end' or 'close' events. Only dest.end() once. - if (!dest._isStdio && (!options || options.end !== false)) { - source.on('end', onend); - source.on('close', onclose); - } - - var didOnEnd = false; - function onend() { - if (didOnEnd) return; - didOnEnd = true; - - dest.end(); - } - - - function onclose() { - if (didOnEnd) return; - didOnEnd = true; - - dest.destroy(); - } - - // don't leave dangling pipes when there are errors. - function onerror(er) { - cleanup(); - if (this.listeners('error').length === 0) { - throw er; // Unhandled stream error in pipe. - } - } - - source.on('error', onerror); - dest.on('error', onerror); - - // remove all the event listeners that were added. - function cleanup() { - source.removeListener('data', ondata); - dest.removeListener('drain', ondrain); - - source.removeListener('end', onend); - source.removeListener('close', onclose); - - source.removeListener('error', onerror); - dest.removeListener('error', onerror); - - source.removeListener('end', cleanup); - source.removeListener('close', cleanup); - - dest.removeListener('end', cleanup); - dest.removeListener('close', cleanup); - } - - source.on('end', cleanup); - source.on('close', cleanup); - - dest.on('end', cleanup); - dest.on('close', cleanup); - - dest.emit('pipe', source); - - // Allow for unix-like usage: A.pipe(B).pipe(C) - return dest; -}; - -exports.Stream = Stream; -function BlobReadStream(source, options){ - Stream.call(this); - - options = util.extend({ - readDelay: 0, - paused: false - }, options); - - this._source = source; - this._start = 0; - this._readChunkSize = options.chunkSize || source.size; - this._readDelay = options.readDelay; - - this.readable = true; - this.paused = options.paused; - - this._read(); -} - -util.inherits(BlobReadStream, Stream); - - -BlobReadStream.prototype.pause = function(){ - this.paused = true; -}; - -BlobReadStream.prototype.resume = function(){ - this.paused = false; - this._read(); -}; - -BlobReadStream.prototype.destroy = function(){ - this.readable = false; - clearTimeout(this._timeoutId); -}; - -BlobReadStream.prototype._read = function(){ - - var self = this; - - function emitReadChunk(){ - self._emitReadChunk(); - } - - var readDelay = this._readDelay; - if (readDelay !== 0){ - this._timeoutId = setTimeout(emitReadChunk, readDelay); - } else { - util.setZeroTimeout(emitReadChunk); - } - -}; - -BlobReadStream.prototype._emitReadChunk = function(){ - - if(this.paused || !this.readable) return; - - var chunkSize = Math.min(this._source.size - this._start, this._readChunkSize); - - if(chunkSize === 0){ - this.readable = false; - this.emit("end"); - return; - } - - var sourceEnd = this._start + chunkSize; - var chunk = (this._source.slice || this._source.webkitSlice || this._source.mozSlice).call(this._source, this._start, sourceEnd); - - this._start = sourceEnd; - this._read(); - - this.emit("data", chunk); - -}; - -/* - - - - -function BlobWriteStream(options){ - - stream.Stream.call(this); - - options = _.extend({ - onFull: onFull, - onEnd: function(){}, - minBlockAllocSize: 0, - drainDelay:0 - }, options); - - this._onFull = options.onFull; - this._onEnd = options.onEnd; - this._onWrite = options.onWrite; - - this._minBlockAllocSize = options.minBlockAllocSize; - this._maxBlockAllocSize = options.maxBlockAllocSize; - this._drainDelay = options.drainDelay; - - this._buffer = new Buffer(options.minBlockAllocSize); - this._destination = this._buffer; - this._destinationPos = 0; - - this._writeQueue = []; - this._pendingOnFull = false; - this._pendingQueueDrain = false; - - this.writable = true; - this.bytesWritten = 0; -} - -util.inherits(BlobWriteStream, stream.Stream); - -BlobWriteStream.prototype.getBuffer = function(){ - return this._buffer; -}; - -BlobWriteStream.prototype.write = function(data, encoding){ - - if(!this.writable){ - throw new Error("stream is not writable"); - } - - if(!Buffer.isBuffer(data)){ - data = new Buffer(data, encoding); - } - - if(data.length){ - this._writeQueue.push(data); - } - - this._commit(); - - return this._writeQueue.length === 0; -}; - -BlobWriteStream.prototype._commit = function(){ - - var self = this; - - var destination = this._destination; - var writeQueue = this._writeQueue; - - var startDestinationPos = this._destinationPos; - - while(writeQueue.length && destination.length){ - - var head = writeQueue[0]; - - var copySize = Math.min(destination.length, head.length); - - head.copy(destination, 0, 0, copySize); - - head = head.slice(copySize); - destination = destination.slice(copySize); - - this.bytesWritten += copySize; - this._destinationPos += copySize; - - if(head.length === 0){ - writeQueue.shift(); - } - else{ - writeQueue[0] = head; - } - } - - this._destination = destination; - - bytesCommitted = this._destinationPos - startDestinationPos; - if(bytesCommitted){ - if(this._onWrite){ - - if(writeQueue.length){ - this._pendingQueueDrain = true; - } - - // By locking destination the buffer is frozen and the onWrite - // callback cannot miss any write commits - this._destination = emptyBuffer; - - var consumer = this._onWrite; - this._onWrite = null; - - consumer.call(this, function(nextCallback){ - util.setZeroTimeout(function(){ - self._destination = destination; - self._onWrite = nextCallback; - self._commit(); - }); - }, consumer); - - return; - } - } - - if(writeQueue.length){ - - this._pendingQueueDrain = true; - this._growBuffer(); - } - else if(this._pendingQueueDrain){ - - this._pendingQueueDrain = false; - - if(this._drainDelay !== 0){ - setTimeout(function(){ - self.emit("drain"); - }, this._drainDelay); - } - else{ - util.setZeroTimeout(function(){ - self.emit("drain"); - }); - } - } -}; - -BlobWriteStream.prototype._growBuffer = function(){ - - var self = this; - var writeQueue = this._writeQueue; - - var requestSize = this._minBlockAllocSize; - - var maxBlockAllocSize = this._maxBlockAllocSize; - var add = (maxBlockAllocSize === undefined ? function(a, b){return a + b;} : function(a, b){return Math.min(a + b, maxBlockAllocSize);}); - - for(var i = 0, queueLength = writeQueue.length; i < queueLength; i++){ - requestSize = add(requestSize, writeQueue[i].length); - } - - // Prevent concurrent onFull callbacks - if(this._pendingOnFull){ - return; - } - this._pendingOnFull = true; - - this._onFull(this._buffer, requestSize, function(buffer, destination){ - util.setZeroTimeout(function(){ - - self._pendingOnFull = false; - - if(!destination){ - if(self.writable){ - self.emit("error", new Error("buffer is full")); - } - self.destroy(); - return; - } - - self._buffer = buffer; - self._destination = destination; - - self._commit(); - }); - }); -}; - -BlobWriteStream.prototype.end = function(data, encoding){ - - var self = this; - - function _end(){ - self.writable = false; - self._onEnd(); - } - - if(data){ - if(this.write(data, encoding)){ - _end(); - }else{ - self.writable = false; - this.once("drain", _end); - } - } - else{ - _end(); - } -}; - -BlobWriteStream.prototype.destroy = function(){ - this.writable = false; - this._pendingQueueDrain = false; - this._writeQueue = []; -}; - -BlobWriteStream.prototype.consume = function(consume){ - - this._buffer = this._buffer.slice(consume); - this._destinationPos -= consume; -}; - -BlobWriteStream.prototype.getCommittedSlice = function(){ - return this._buffer.slice(0, this._destinationPos); -}; - -function onFull(buffer, extraSize, callback){ - var newBuffer = new Buffer(buffer.length + extraSize); - buffer.copy(newBuffer); - callback(newBuffer, newBuffer.slice(buffer.length)); -} -*/ -exports.BlobReadStream = BlobReadStream; - -function BinaryStream(socket, id, create, meta) { - if (!(this instanceof BinaryStream)) return new BinaryStream(options); - - var self = this; - - Stream.call(this); - - - this.id = id; - this._socket = socket; - - this.writable = true; - this.readable = true; - this.paused = false; - - this._closed = false; - this._ended = false; - - if(create) { - // This is a stream we are creating - this._write(1, meta, this.id); - } -} - -util.inherits(BinaryStream, Stream); - - -BinaryStream.prototype._onDrain = function() { - if(!this.paused) { - this.emit('drain'); - } -}; - -BinaryStream.prototype._onClose = function() { - // Emit close event - if (this._closed) { - return; - } - this.readable = false; - this.writable = false; - this._closed = true; - this.emit('close'); -}; - -BinaryStream.prototype._onError = function(error){ - this.readable = false; - this.writable = false; - this.emit('error', error); -}; - -// Write stream - -BinaryStream.prototype._onPause = function() { - // Emit pause event - this.paused = true; - this.emit('pause'); -}; - -BinaryStream.prototype._onResume = function() { - // Emit resume event - this.paused = false; - this.emit('resume'); - this.emit('drain'); -}; - -BinaryStream.prototype._write = function(code, data, bonus) { - if (this._socket.readyState !== this._socket.constructor.OPEN) { - return false; - } - var message = util.pack([code, data, bonus]); - return this._socket.send(message) !== false; -}; - -BinaryStream.prototype.write = function(data) { - if(this.writable) { - var out = this._write(2, data, this.id); - return !this.paused && out; - } else { - this.emit('error', new Error('Stream is not writable')); - return false; - } -}; - -BinaryStream.prototype.end = function() { - this._ended = true; - this.readable = false; - this._write(5, null, this.id); -}; - -BinaryStream.prototype.destroy = BinaryStream.prototype.destroySoon = function() { - this._onClose(); - this._write(6, null, this.id); -}; - - -// Read stream - -BinaryStream.prototype._onEnd = function() { - if(this._ended) { - return; - } - this._ended = true; - this.readable = false; - this.emit('end'); -}; - -BinaryStream.prototype._onData = function(data) { - // Dispatch - this.emit('data', data); -}; - -BinaryStream.prototype.pause = function() { - this._onPause(); - this._write(3, null, this.id); -}; - -BinaryStream.prototype.resume = function() { - this._onResume(); - this._write(4, null, this.id); -}; - - -function BinaryClient(socket, options) { - if (!(this instanceof BinaryClient)) return new BinaryClient(socket, options); - - EventEmitter.call(this); - - var self = this; - - this._options = util.extend({ - chunkSize: 40960 - }, options); - - this.streams = {}; - - if(typeof socket === 'string') { - this._nextId = 0; - this._socket = new WebSocket(socket); - } else { - // Use odd numbered ids for server originated streams - this._nextId = 1; - this._socket = socket; - } - - this._socket.binaryType = 'arraybuffer'; - - this._socket.addEventListener('open', function(){ - self.emit('open'); - }); - this._socket.addEventListener('error', function(error){ - var ids = Object.keys(self.streams); - for (var i = 0, ii = ids.length; i < ii; i++) { - self.streams[ids[i]]._onError(error); - } - self.emit('error', error); - }); - this._socket.addEventListener('close', function(code, message){ - var ids = Object.keys(self.streams); - for (var i = 0, ii = ids.length; i < ii; i++) { - self.streams[ids[i]]._onClose(); - } - self.emit('close', code, message); - }); - this._socket.addEventListener('message', function(data, flags){ - util.setZeroTimeout(function(){ - - // Message format - // [type, payload, bonus ] - // - // Reserved - // [ 0 , X , X ] - // - // - // New stream - // [ 1 , Meta , new streamId ] - // - // - // Data - // [ 2 , Data , streamId ] - // - // - // Pause - // [ 3 , null , streamId ] - // - // - // Resume - // [ 4 , null , streamId ] - // - // - // End - // [ 5 , null , streamId ] - // - // - // Close - // [ 6 , null , streamId ] - // - - data = data.data; - - try { - data = util.unpack(data); - } catch (ex) { - return self.emit('error', new Error('Received unparsable message: ' + ex)); - } - if (!(data instanceof Array)) - return self.emit('error', new Error('Received non-array message')); - if (data.length != 3) - return self.emit('error', new Error('Received message with wrong part count: ' + data.length)); - if ('number' != typeof data[0]) - return self.emit('error', new Error('Received message with non-number type: ' + data[0])); - - switch(data[0]) { - case 0: - // Reserved - break; - case 1: - var meta = data[1]; - var streamId = data[2]; - var binaryStream = self._receiveStream(streamId); - self.emit('stream', binaryStream, meta); - break; - case 2: - var payload = data[1]; - var streamId = data[2]; - var binaryStream = self.streams[streamId]; - if(binaryStream) { - binaryStream._onData(payload); - } else { - self.emit('error', new Error('Received `data` message for unknown stream: ' + streamId)); - } - break; - case 3: - var streamId = data[2]; - var binaryStream = self.streams[streamId]; - if(binaryStream) { - binaryStream._onPause(); - } else { - self.emit('error', new Error('Received `pause` message for unknown stream: ' + streamId)); - } - break; - case 4: - var streamId = data[2]; - var binaryStream = self.streams[streamId]; - if(binaryStream) { - binaryStream._onResume(); - } else { - self.emit('error', new Error('Received `resume` message for unknown stream: ' + streamId)); - } - break; - case 5: - var streamId = data[2]; - var binaryStream = self.streams[streamId]; - if(binaryStream) { - binaryStream._onEnd(); - } else { - self.emit('error', new Error('Received `end` message for unknown stream: ' + streamId)); - } - break; - case 6: - var streamId = data[2]; - var binaryStream = self.streams[streamId]; - if(binaryStream) { - binaryStream._onClose(); - } else { - self.emit('error', new Error('Received `close` message for unknown stream: ' + streamId)); - } - break; - default: - self.emit('error', new Error('Unrecognized message type received: ' + data[0])); - } - }); - }); -} - -util.inherits(BinaryClient, EventEmitter); - -BinaryClient.prototype.send = function(data, meta){ - var stream = this.createStream(meta); - if(data instanceof Stream) { - data.pipe(stream); - } else if (util.isNode === true) { - if(Buffer.isBuffer(data)) { - (new BufferReadStream(data, {chunkSize: this._options.chunkSize})).pipe(stream); - } else { - stream.write(data); - } - } else if (util.isNode !== true) { - if(data.constructor == Blob || data.constructor == File) { - (new BlobReadStream(data, {chunkSize: this._options.chunkSize})).pipe(stream); - } else if (data.constructor == ArrayBuffer) { - var blob; - if(binaryFeatures.useArrayBufferView) { - data = new Uint8Array(data); - } - if(binaryFeatures.useBlobBuilder) { - var builder = new BlobBuilder(); - builder.append(data); - blob = builder.getBlob() - } else { - blob = new Blob([data]); - } - (new BlobReadStream(blob, {chunkSize: this._options.chunkSize})).pipe(stream); - } else if (typeof data === 'object' && 'BYTES_PER_ELEMENT' in data) { - var blob; - if(!binaryFeatures.useArrayBufferView) { - // Warn - data = data.buffer; - } - if(binaryFeatures.useBlobBuilder) { - var builder = new BlobBuilder(); - builder.append(data); - blob = builder.getBlob() - } else { - blob = new Blob([data]); - } - (new BlobReadStream(blob, {chunkSize: this._options.chunkSize})).pipe(stream); - } else { - stream.write(data); - } - } - return stream; -}; - -BinaryClient.prototype._receiveStream = function(streamId){ - var self = this; - var binaryStream = new BinaryStream(this._socket, streamId, false); - binaryStream.on('close', function(){ - delete self.streams[streamId]; - }); - this.streams[streamId] = binaryStream; - return binaryStream; -}; - -BinaryClient.prototype.createStream = function(meta){ - if(this._socket.readyState !== WebSocket.OPEN) { - throw new Error('Client is not yet connected or has closed'); - return; - } - var self = this; - var streamId = this._nextId; - this._nextId += 2; - var binaryStream = new BinaryStream(this._socket, streamId, true, meta); - binaryStream.on('close', function(){ - delete self.streams[streamId]; - }); - this.streams[streamId] = binaryStream; - return binaryStream; -}; - -BinaryClient.prototype.close = BinaryClient.prototype.destroy = function() { - this._socket.close(); -}; - -exports.BinaryClient = BinaryClient; - -})(this); - -</script></head><body> - - -<div hidden by-vulcanize><dom-module id=iron-a11y-announcer assetpath="../bower_components/iron-a11y-announcer/"><style> - :host { - display: inline-block; - position: fixed; - clip: rect(0px,0px,0px,0px); - } - </style><template><div aria-live$=[[mode]]>[[_text]]</div></template><script> - - (function() { - 'use strict'; - - Polymer.IronA11yAnnouncer = Polymer({ - is: 'iron-a11y-announcer', - - properties: { - - /** - * The value of mode is used to set the `aria-live` attribute - * for the element that will be announced. Valid values are: `off`, - * `polite` and `assertive`. - */ - mode: { - type: String, - value: 'polite' - }, - - _text: { - type: String, - value: '' - } - }, - - created: function() { - if (!Polymer.IronA11yAnnouncer.instance) { - Polymer.IronA11yAnnouncer.instance = this; - } - - document.body.addEventListener('iron-announce', this._onIronAnnounce.bind(this)); - }, - - /** - * Cause a text string to be announced by screen readers. - * - * @param {string} text The text that should be announced. - */ - announce: function(text) { - this._text = ''; - this.async(function() { - this._text = text; - }, 100); - }, - - _onIronAnnounce: function(event) { - if (event.detail && event.detail.text) { - this.announce(event.detail.text); - } - } - }); - - Polymer.IronA11yAnnouncer.instance = null; - - Polymer.IronA11yAnnouncer.requestAvailability = function() { - if (!Polymer.IronA11yAnnouncer.instance) { - Polymer.IronA11yAnnouncer.instance = document.createElement('iron-a11y-announcer'); - } - - document.body.appendChild(Polymer.IronA11yAnnouncer.instance); - }; - })(); - - </script></dom-module><dom-module id=iron-overlay-backdrop assetpath="../bower_components/iron-overlay-behavior/"><style> - - :host { - position: fixed; - top: 0; - left: 0; - width: 100vw; - height: 100vh; - background-color: var(--iron-overlay-backdrop-background-color, #000); - opacity: 0; - transition: opacity 0.2s; - - @apply(--iron-overlay-backdrop); - } - - :host([opened]) { - opacity: var(--iron-overlay-backdrop-opacity, 0.6); - - @apply(--iron-overlay-backdrop-opened); - } - - </style><template><content></content></template></dom-module><script> - -(function() { - - Polymer({ - - is: 'iron-overlay-backdrop', - - properties: { - - /** - * Returns true if the backdrop is opened. - */ - opened: { - readOnly: true, - reflectToAttribute: true, - type: Boolean, - value: false - }, - - _manager: { - type: Object, - value: Polymer.IronOverlayManager - } - - }, - - /** - * Appends the backdrop to document body and sets its `z-index` to be below the latest overlay. - */ - prepare: function() { - if (!this.parentNode) { - Polymer.dom(document.body).appendChild(this); - this.style.zIndex = this._manager.currentOverlayZ() - 1; - } - }, - - /** - * Shows the backdrop if needed. - */ - open: function() { - // only need to make the backdrop visible if this is called by the first overlay with a backdrop - if (this._manager.getBackdrops().length < 2) { - this._setOpened(true); - } - }, - - /** - * Hides the backdrop if needed. - */ - close: function() { - // only need to make the backdrop invisible if this is called by the last overlay with a backdrop - if (this._manager.getBackdrops().length < 2) { - this._setOpened(false); - } - }, - - /** - * Removes the backdrop from document body if needed. - */ - complete: function() { - // only remove the backdrop if there are no more overlays with backdrops - if (this._manager.getBackdrops().length === 0 && this.parentNode) { - Polymer.dom(this.parentNode).removeChild(this); - } - } - - }); - -})(); - -</script><dom-module id=paper-toast assetpath="../bower_components/paper-toast/"><template><style> - :host { - display: block; - position: fixed; - background-color: var(--paper-toast-background-color, #323232); - color: var(--paper-toast-color, #f1f1f1); - min-height: 48px; - min-width: 288px; - padding: 16px 24px 12px; - box-sizing: border-box; - box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.26); - border-radius: 2px; - left: 0; - bottom: 0; - margin: 12px; - font-size: 14px; - cursor: default; - -webkit-transition: visibility 0.3s, -webkit-transform 0.3s, opacity 0.3s; - transition: visibility 0.3s, transform 0.3s, opacity 0.3s; - visibility: hidden; - opacity: 0; - -webkit-transform: translateY(100px); - transform: translateY(100px); - } - - :host(.capsule) { - border-radius: 24px; - } - - :host(.fit-bottom) { - width: 100%; - min-width: 0; - border-radius: 0; - margin: 0; - } - - :host(.paper-toast-open) { - visibility: visible; - opacity: 1; - -webkit-transform: translateY(0px); - transform: translateY(0px); - } - </style><span id=label>{{text}}</span><content></content></template><script> - (function() { - // Keeps track of the toast currently opened. - var currentToast = null; - - Polymer({ - is: 'paper-toast', - - behaviors: [ - Polymer.IronOverlayBehavior - ], - - properties: { - /** - * The duration in milliseconds to show the toast. - * Set to `0`, a negative number, or `Infinity`, to disable the - * toast auto-closing. - */ - duration: { - type: Number, - value: 3000 - }, - - /** - * The text to display in the toast. - */ - text: { - type: String, - value: '' - }, - - /** - * Overridden from `IronOverlayBehavior`. - * Set to false to enable closing of the toast by clicking outside it. - */ - noCancelOnOutsideClick: { - type: Boolean, - value: true - }, - }, - - /** - * Read-only. Deprecated. Use `opened` from `IronOverlayBehavior`. - * @property visible - * @deprecated - */ - get visible() { - console.warn('`visible` is deprecated, use `opened` instead'); - return this.opened; - }, - - /** - * Read-only. Can auto-close if duration is a positive finite number. - * @property _canAutoClose - */ - get _canAutoClose() { - return this.duration > 0 && this.duration !== Infinity; - }, - - created: function() { - Polymer.IronA11yAnnouncer.requestAvailability(); - }, - - /** - * Show the toast. Same as `open()` from `IronOverlayBehavior`. - */ - show: function() { - this.open(); - }, - - /** - * Hide the toast. Same as `close()` from `IronOverlayBehavior`. - */ - hide: function() { - this.close(); - }, - - /** - * Overridden from `IronOverlayBehavior`. - * Called when the value of `opened` changes. - */ - _openedChanged: function() { - if (this.opened) { - if (currentToast && currentToast !== this) { - currentToast.close(); - } - currentToast = this; - this.fire('iron-announce', { - text: this.text - }); - if (this._canAutoClose) { - this.debounce('close', this.close, this.duration); - } - } else if (currentToast === this) { - currentToast = null; - } - Polymer.IronOverlayBehaviorImpl._openedChanged.apply(this, arguments); - }, - - /** - * Overridden from `IronOverlayBehavior`. - */ - _renderOpened: function() { - this.classList.add('paper-toast-open'); - }, - - /** - * Overridden from `IronOverlayBehavior`. - */ - _renderClosed: function() { - this.classList.remove('paper-toast-open'); - }, - - /** - * Overridden from `IronOverlayBehavior`. - * iron-fit-behavior will set the inline style position: static, which - * causes the toast to be rendered incorrectly when opened by default. - */ - _onIronResize: function() { - Polymer.IronOverlayBehaviorImpl._onIronResize.apply(this, arguments); - if (this.opened) { - // Make sure there is no inline style for position. - this.style.position = ''; - } - } - - /** - * Fired when `paper-toast` is opened. - * - * @event 'iron-announce' - * @param {Object} detail - * @param {String} detail.text The text that will be announced. - */ - }); - })(); - </script></dom-module><dom-module id=paper-progress assetpath="../bower_components/paper-progress/"><style> - :host { - display: block; - width: 200px; - position: relative; - overflow: hidden; - } - - #progressContainer { - position: relative; - } - - #progressContainer, - /* the stripe for the indeterminate animation*/ - .indeterminate:after { - height: var(--paper-progress-height, 4px); - } - - #primaryProgress, - #secondaryProgress, - .indeterminate:after { - @apply(--layout-fit); - } - - #progressContainer, - .indeterminate:after { - background-color: var(--paper-progress-container-color, --google-grey-300); - } - - :host(.transiting) #primaryProgress, - :host(.transiting) #secondaryProgress { - -webkit-transition-property: -webkit-transform; - transition-property: transform; - - /* Duration */ - -webkit-transition-duration: var(--paper-progress-transition-duration, 0.08s); - transition-duration: var(--paper-progress-transition-duration, 0.08s); - - /* Timing function */ - -webkit-transition-timing-function: var(--paper-progress-transition-timing-function, ease); - transition-timing-function: var(--paper-progress-transition-timing-function, ease); - - /* Delay */ - -webkit-transition-delay: var(--paper-progress-transition-delay, 0s); - transition-delay: var(--paper-progress-transition-delay, 0s); - } - - #primaryProgress, - #secondaryProgress { - @apply(--layout-fit); - -webkit-transform-origin: left center; - transform-origin: left center; - -webkit-transform: scaleX(0); - transform: scaleX(0); - will-change: transform; - } - - #primaryProgress { - background-color: var(--paper-progress-active-color, --google-green-500); - } - - #secondaryProgress { - position: relative; - background-color: var(--paper-progress-secondary-color, --google-green-100); - } - - :host([disabled]) #primaryProgress { - background-color: var(--paper-progress-disabled-active-color, --google-grey-500); - } - - :host([disabled]) #secondaryProgress { - background-color: var(--paper-progress-disabled-active-color, --google-grey-300); - } - - :host(:not([disabled])) #primaryProgress.indeterminate { - -webkit-transform-origin: right center; - transform-origin: right center; - -webkit-animation: indeterminate-bar 2s linear infinite; - animation: indeterminate-bar 2s linear infinite; - } - - :host(:not([disabled])) #primaryProgress.indeterminate:after { - content: ""; - -webkit-transform-origin: center center; - transform-origin: center center; - - -webkit-animation: indeterminate-splitter 2s linear infinite; - animation: indeterminate-splitter 2s linear infinite; - } - - @-webkit-keyframes indeterminate-bar { - 0% { - -webkit-transform: scaleX(1) translateX(-100%); - } - 50% { - -webkit-transform: scaleX(1) translateX(0%); - } - 75% { - -webkit-transform: scaleX(1) translateX(0%); - -webkit-animation-timing-function: cubic-bezier(.28,.62,.37,.91); - } - 100% { - -webkit-transform: scaleX(0) translateX(0%); - } - } - - @-webkit-keyframes indeterminate-splitter { - 0% { - -webkit-transform: scaleX(.75) translateX(-125%); - } - 30% { - -webkit-transform: scaleX(.75) translateX(-125%); - -webkit-animation-timing-function: cubic-bezier(.42,0,.6,.8); - } - 90% { - -webkit-transform: scaleX(.75) translateX(125%); - } - 100% { - -webkit-transform: scaleX(.75) translateX(125%); - } - } - - @keyframes indeterminate-bar { - 0% { - transform: scaleX(1) translateX(-100%); - } - 50% { - transform: scaleX(1) translateX(0%); - } - 75% { - transform: scaleX(1) translateX(0%); - animation-timing-function: cubic-bezier(.28,.62,.37,.91); - } - 100% { - transform: scaleX(0) translateX(0%); - } - } - - @keyframes indeterminate-splitter { - 0% { - transform: scaleX(.75) translateX(-125%); - } - 30% { - transform: scaleX(.75) translateX(-125%); - animation-timing-function: cubic-bezier(.42,0,.6,.8); - } - 90% { - transform: scaleX(.75) translateX(125%); - } - 100% { - transform: scaleX(.75) translateX(125%); - } - } - </style><template><div id=progressContainer><div id=secondaryProgress hidden$=[[_hideSecondaryProgress(secondaryRatio)]]></div><div id=primaryProgress></div></div></template></dom-module><script> - Polymer({ - - is: 'paper-progress', - - behaviors: [ - Polymer.IronRangeBehavior - ], - - properties: { - - /** - * The number that represents the current secondary progress. - */ - secondaryProgress: { - type: Number, - value: 0 - }, - - /** - * The secondary ratio - */ - secondaryRatio: { - type: Number, - value: 0, - readOnly: true - }, - - /** - * Use an indeterminate progress indicator. - */ - indeterminate: { - type: Boolean, - value: false, - observer: '_toggleIndeterminate' - }, - - /** - * True if the progress is disabled. - */ - disabled: { - type: Boolean, - value: false, - reflectToAttribute: true, - observer: '_disabledChanged' - } - }, - - observers: [ - '_progressChanged(secondaryProgress, value, min, max)' - ], - - hostAttributes: { - role: 'progressbar' - }, - - _toggleIndeterminate: function(indeterminate) { - // If we use attribute/class binding, the animation sometimes doesn't translate properly - // on Safari 7.1. So instead, we toggle the class here in the update method. - this.toggleClass('indeterminate', indeterminate, this.$.primaryProgress); - }, - - _transformProgress: function(progress, ratio) { - var transform = 'scaleX(' + (ratio / 100) + ')'; - progress.style.transform = progress.style.webkitTransform = transform; - }, - - _mainRatioChanged: function(ratio) { - this._transformProgress(this.$.primaryProgress, ratio); - }, - - _progressChanged: function(secondaryProgress, value, min, max) { - secondaryProgress = this._clampValue(secondaryProgress); - value = this._clampValue(value); - - var secondaryRatio = this._calcRatio(secondaryProgress) * 100; - var mainRatio = this._calcRatio(value) * 100; - - this._setSecondaryRatio(secondaryRatio); - this._transformProgress(this.$.secondaryProgress, secondaryRatio); - this._transformProgress(this.$.primaryProgress, mainRatio); - - this.secondaryProgress = secondaryProgress; - - this.setAttribute('aria-valuenow', value); - this.setAttribute('aria-valuemin', min); - this.setAttribute('aria-valuemax', max); - }, - - _disabledChanged: function(disabled) { - this.setAttribute('aria-disabled', disabled ? 'true' : 'false'); - }, - - _hideSecondaryProgress: function(secondaryRatio) { - return secondaryRatio === 0; - } - - }); - -</script><dom-module id=neon-animated-pages assetpath="../bower_components/neon-animation/"><style> - - :host { - display: block; - position: relative; - } - - :host > ::content > * { - position: absolute; - top: 0; - left: 0; - bottom: 0; - right: 0; - height: 100%; - } - - :host > ::content > :not(.iron-selected):not(.neon-animating) { - display: none !important; - } - - :host > ::content > .neon-animating { - pointer-events: none; - } - - </style><template><content id=content></content></template></dom-module><script> -(function() { - - Polymer({ - - is: 'neon-animated-pages', - - behaviors: [ - Polymer.IronResizableBehavior, - Polymer.IronSelectableBehavior, - Polymer.NeonAnimationRunnerBehavior - ], - - properties: { - - activateEvent: { - type: String, - value: '' - }, - - // if true, the initial page selection will also be animated according to its animation config. - animateInitialSelection: { - type: Boolean, - value: false - } - - }, - - observers: [ - '_selectedChanged(selected)' - ], - - listeners: { - 'neon-animation-finish': '_onNeonAnimationFinish' - }, - - _selectedChanged: function(selected) { - - var selectedPage = this.selectedItem; - var oldPage = this._valueToItem(this._prevSelected) || false; - this._prevSelected = selected; - - // on initial load and if animateInitialSelection is negated, simply display selectedPage. - if (!oldPage && !this.animateInitialSelection) { - this._completeSelectedChanged(); - return; - } - - // insert safari fix. - this.animationConfig = [{ - name: 'opaque-animation', - node: selectedPage - }]; - - // configure selectedPage animations. - if (this.entryAnimation) { - this.animationConfig.push({ - name: this.entryAnimation, - node: selectedPage - }); - } else { - if (selectedPage.getAnimationConfig) { - this.animationConfig.push({ - animatable: selectedPage, - type: 'entry' - }); - } - } - - // configure oldPage animations iff exists. - if (oldPage) { - - // cancel the currently running animation if one is ongoing. - if (oldPage.classList.contains('neon-animating')) { - this._squelchNextFinishEvent = true; - this.cancelAnimation(); - this._completeSelectedChanged(); - this._squelchNextFinishEvent = false; - } - - // configure the animation. - if (this.exitAnimation) { - this.animationConfig.push({ - name: this.exitAnimation, - node: oldPage - }); - } else { - if (oldPage.getAnimationConfig) { - this.animationConfig.push({ - animatable: oldPage, - type: 'exit' - }); - } - } - - // display the oldPage during the transition. - oldPage.classList.add('neon-animating'); - } - - // display the selectedPage during the transition. - selectedPage.classList.add('neon-animating'); - - // actually run the animations. - if (this.animationConfig.length > 1) { - - // on first load, ensure we run animations only after element is attached. - if (!this.isAttached) { - this.async(function () { - this.playAnimation(undefined, { - fromPage: null, - toPage: selectedPage - }); - }); - - } else { - this.playAnimation(undefined, { - fromPage: oldPage, - toPage: selectedPage - }); - } - - } else { - this._completeSelectedChanged(oldPage, selectedPage); - } - }, - - /** - * @param {Object=} oldPage - * @param {Object=} selectedPage - */ - _completeSelectedChanged: function(oldPage, selectedPage) { - if (selectedPage) { - selectedPage.classList.remove('neon-animating'); - } - if (oldPage) { - oldPage.classList.remove('neon-animating'); - } - if (!selectedPage || !oldPage) { - var nodes = Polymer.dom(this.$.content).getDistributedNodes(); - for (var node, index = 0; node = nodes[index]; index++) { - node.classList && node.classList.remove('neon-animating'); - } - } - this.async(this._notifyPageResize); - }, - - _onNeonAnimationFinish: function(event) { - if (this._squelchNextFinishEvent) { - this._squelchNextFinishEvent = false; - return; - } - this._completeSelectedChanged(event.detail.fromPage, event.detail.toPage); - }, - - _notifyPageResize: function() { - var selectedPage = this.selectedItem; - this.resizerShouldNotify = function(element) { - return element == selectedPage; - } - this.notifyResize(); - } - - }) - -})(); -</script><dom-module id=iron-icon assetpath="../bower_components/iron-icon/"><style> - :host { - @apply(--layout-inline); - @apply(--layout-center-center); - position: relative; - - vertical-align: middle; - - fill: var(--iron-icon-fill-color, currentcolor); - stroke: var(--iron-icon-stroke-color, none); - - width: var(--iron-icon-width, 24px); - height: var(--iron-icon-height, 24px); - } - </style><template></template><script> - - Polymer({ - - is: 'iron-icon', - - properties: { - - /** - * The name of the icon to use. The name should be of the form: - * `iconset_name:icon_name`. - */ - icon: { - type: String, - observer: '_iconChanged' - }, - - /** - * The name of the theme to used, if one is specified by the - * iconset. - */ - theme: { - type: String, - observer: '_updateIcon' - }, - - /** - * If using iron-icon without an iconset, you can set the src to be - * the URL of an individual icon image file. Note that this will take - * precedence over a given icon attribute. - */ - src: { - type: String, - observer: '_srcChanged' - }, - - /** - * @type {!Polymer.IronMeta} - */ - _meta: { - value: Polymer.Base.create('iron-meta', {type: 'iconset'}) - } - - }, - - _DEFAULT_ICONSET: 'icons', - - _iconChanged: function(icon) { - var parts = (icon || '').split(':'); - this._iconName = parts.pop(); - this._iconsetName = parts.pop() || this._DEFAULT_ICONSET; - this._updateIcon(); - }, - - _srcChanged: function(src) { - this._updateIcon(); - }, - - _usesIconset: function() { - return this.icon || !this.src; - }, - - /** @suppress {visibility} */ - _updateIcon: function() { - if (this._usesIconset()) { - if (this._iconsetName) { - this._iconset = /** @type {?Polymer.Iconset} */ ( - this._meta.byKey(this._iconsetName)); - if (this._iconset) { - this._iconset.applyIcon(this, this._iconName, this.theme); - this.unlisten(window, 'iron-iconset-added', '_updateIcon'); - } else { - this.listen(window, 'iron-iconset-added', '_updateIcon'); - } - } - } else { - if (!this._img) { - this._img = document.createElement('img'); - this._img.style.width = '100%'; - this._img.style.height = '100%'; - this._img.draggable = false; - } - this._img.src = this.src; - Polymer.dom(this.root).appendChild(this._img); - } - } - - }); - - </script></dom-module><dom-module id=about-page assetpath="x-cards/"><template><style> - :host { - display: block; - overflow: hidden; - color: white; - z-index: 3; - --paper-tooltip: { - font-size: 14px; - background-color: white; - color: #4285f4; - } - --paper-tooltip-opacity:0.95; - } - - #placeholder { - opacity: 0; - background-color: #4285f4; - @apply(--layout-fit); - } - - #container { - @apply(--layout-fit); - @apply(--layout-vertical); - @apply(--layout-center-center); - background-color: #4285f4; - padding: 64px 32px 64px 32px; - box-sizing: border-box; - } - - .logo { - width: 96px; - height: 96px; - } - - .slogan { - text-align: center; - } - - .paper-font-headline { - margin-bottom: 8px; - font-size: 32px; - } - - a { - text-decoration: none; - color: white; - } - - .center { - @apply(--layout-vertical); - @apply(--layout-center-center); - } - - #footer { - position: absolute; - left: 50%; - margin-left: -160px; - width: 320px; - bottom: 24px; - text-align: center; - } - - @media all and (max-height: 370px) { - #footer { - width: 320px; - bottom: 8px; - } - } - - a { - display: inline-block; - text-align: center; - } - - .donate-icon { - padding-top: 32px; - width: 32px; - height: 32px; - } - - .paper-font-subhead { - font-size: 20px; - } - - a.paper-font-subhead { - padding-top: 32px; - } - - .share { - position: absolute; - top: 16px; - right: 16px; - z-index: 10; - } - - .social { - margin: 16px; - width: 228px; - } - - #btn, - .social a { - color: white; - } - - .social a { - text-decoration: none; - padding: 2px 9px 0 9px; - } - - .share a:hover, - #btn:hover { - color: white; - } - </style><div id=placeholder></div><div id=container><div class=share><paper-icon-button id=btn icon=chat:close on-tap=_switch></paper-icon-button></div><div class=center><iron-icon icon=chat:wifi-tethering class=logo></iron-icon><div class=paper-font-headline>Snapdrop</div><div class=slogan>The easiest way to transfer files across devices.</div><div class=social><a href="https://twitter.com/intent/tweet?text=Instantly%20share%20files%20with%20devices%20nearby.%20No%20Setup,%20no%20Signup.%0D%0Ahttps://snapdrop.net%20by%20@robin_linus%20&amp;" target=_blank><iron-icon icon=chat:twitter></iron-icon><paper-tooltip position=bottom offset=14>Tweet about Snapdrop!</paper-tooltip></a> <a href=https://www.facebook.com/RobinLinus target=_blank><iron-icon icon=chat:facebook></iron-icon><paper-tooltip position=bottom offset=14>Like my Facebook Page!</paper-tooltip></a> <a href="https://paypal.me/RobinLinus" target=_blank><iron-icon icon=chat:local-cafe></iron-icon><paper-tooltip position=bottom offset=14>You like Snapdrop?<br>Buy me a cup of coffee!</paper-tooltip></a> <a href=https://github.com/yougrow/snapdrop target=_blank class=github><iron-icon icon=chat:github></iron-icon><paper-tooltip position=bottom offset=14>Get involved!</paper-tooltip></a> <a href=https://github.com/yougrow/snapdrop#frequently-asked-questions target=_blank class=github><iron-icon icon=chat:help-outline></iron-icon><paper-tooltip position=bottom offset=14>Frequently Asked Questions</paper-tooltip></a></div></div><span id=footer><a href=https://twitter.com/robin_linus target=_blank>Built with ♥ by Robin Linus</a></span></div></template></dom-module><script> -(function() { - Polymer({ - is: 'about-page', - behaviors: [ - Polymer.NeonSharedElementAnimatableBehavior - ], - properties: { - animationConfig: { - value: function() { - return { - 'entry': [{ - name: 'ripple-animation', - id: 'ripple', - toPage: this - }, { - name: 'fade-out-animation', - node: this.$.placeholder, - timing: { - delay: 250 - } - }, { - name: 'fade-in-animation', - node: this.$.container, - timing: { - delay: 50 - } - }], - 'exit': [{ - name: 'opaque-animation', - node: this.$.placeholder - }, { - name: 'fade-out-animation', - node: this.$.container, - timing: { - duration: 0 - } - }, { - name: 'reverse-ripple-animation', - id: 'reverse-ripple', - fromPage: this - }] - }; - } - }, - sharedElements: { - value: function() { - return { - 'ripple': this.$.placeholder, - 'reverse-ripple': this.$.placeholder - }; - } - } - }, - _switch: function() { - document.querySelector('#pages').select(0); - } - }); -})(); -</script><dom-module id=paper-ripple assetpath="../bower_components/paper-ripple/"><template><style> - :host { - display: block; - position: absolute; - border-radius: inherit; - overflow: hidden; - top: 0; - left: 0; - right: 0; - bottom: 0; - - /* See PolymerElements/paper-behaviors/issues/34. On non-Chrome browsers, - * creating a node (with a position:absolute) in the middle of an event - * handler "interrupts" that event handler (which happens when the - * ripple is created on demand) */ - pointer-events: none; - } - - :host([animating]) { - /* This resolves a rendering issue in Chrome (as of 40) where the - ripple is not properly clipped by its parent (which may have - rounded corners). See: http://jsbin.com/temexa/4 - - Note: We only apply this style conditionally. Otherwise, the browser - will create a new compositing layer for every ripple element on the - page, and that would be bad. */ - -webkit-transform: translate(0, 0); - transform: translate3d(0, 0, 0); - } - - #background, - #waves, - .wave-container, - .wave { - pointer-events: none; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - } - - #background, - .wave { - opacity: 0; - } - - #waves, - .wave { - overflow: hidden; - } - - .wave-container, - .wave { - border-radius: 50%; - } - - :host(.circle) #background, - :host(.circle) #waves { - border-radius: 50%; - } - - :host(.circle) .wave-container { - overflow: hidden; - } - </style><div id=background></div><div id=waves></div></template></dom-module><script> - (function() { - var Utility = { - distance: function(x1, y1, x2, y2) { - var xDelta = (x1 - x2); - var yDelta = (y1 - y2); - - return Math.sqrt(xDelta * xDelta + yDelta * yDelta); - }, - - now: window.performance && window.performance.now ? - window.performance.now.bind(window.performance) : Date.now - }; - - /** - * @param {HTMLElement} element - * @constructor - */ - function ElementMetrics(element) { - this.element = element; - this.width = this.boundingRect.width; - this.height = this.boundingRect.height; - - this.size = Math.max(this.width, this.height); - } - - ElementMetrics.prototype = { - get boundingRect () { - return this.element.getBoundingClientRect(); - }, - - furthestCornerDistanceFrom: function(x, y) { - var topLeft = Utility.distance(x, y, 0, 0); - var topRight = Utility.distance(x, y, this.width, 0); - var bottomLeft = Utility.distance(x, y, 0, this.height); - var bottomRight = Utility.distance(x, y, this.width, this.height); - - return Math.max(topLeft, topRight, bottomLeft, bottomRight); - } - }; - - /** - * @param {HTMLElement} element - * @constructor - */ - function Ripple(element) { - this.element = element; - this.color = window.getComputedStyle(element).color; - - this.wave = document.createElement('div'); - this.waveContainer = document.createElement('div'); - this.wave.style.backgroundColor = this.color; - this.wave.classList.add('wave'); - this.waveContainer.classList.add('wave-container'); - Polymer.dom(this.waveContainer).appendChild(this.wave); - - this.resetInteractionState(); - } - - Ripple.MAX_RADIUS = 300; - - Ripple.prototype = { - get recenters() { - return this.element.recenters; - }, - - get center() { - return this.element.center; - }, - - get mouseDownElapsed() { - var elapsed; - - if (!this.mouseDownStart) { - return 0; - } - - elapsed = Utility.now() - this.mouseDownStart; - - if (this.mouseUpStart) { - elapsed -= this.mouseUpElapsed; - } - - return elapsed; - }, - - get mouseUpElapsed() { - return this.mouseUpStart ? - Utility.now () - this.mouseUpStart : 0; - }, - - get mouseDownElapsedSeconds() { - return this.mouseDownElapsed / 1000; - }, - - get mouseUpElapsedSeconds() { - return this.mouseUpElapsed / 1000; - }, - - get mouseInteractionSeconds() { - return this.mouseDownElapsedSeconds + this.mouseUpElapsedSeconds; - }, - - get initialOpacity() { - return this.element.initialOpacity; - }, - - get opacityDecayVelocity() { - return this.element.opacityDecayVelocity; - }, - - get radius() { - var width2 = this.containerMetrics.width * this.containerMetrics.width; - var height2 = this.containerMetrics.height * this.containerMetrics.height; - var waveRadius = Math.min( - Math.sqrt(width2 + height2), - Ripple.MAX_RADIUS - ) * 1.1 + 5; - - var duration = 1.1 - 0.2 * (waveRadius / Ripple.MAX_RADIUS); - var timeNow = this.mouseInteractionSeconds / duration; - var size = waveRadius * (1 - Math.pow(80, -timeNow)); - - return Math.abs(size); - }, - - get opacity() { - if (!this.mouseUpStart) { - return this.initialOpacity; - } - - return Math.max( - 0, - this.initialOpacity - this.mouseUpElapsedSeconds * this.opacityDecayVelocity - ); - }, - - get outerOpacity() { - // Linear increase in background opacity, capped at the opacity - // of the wavefront (waveOpacity). - var outerOpacity = this.mouseUpElapsedSeconds * 0.3; - var waveOpacity = this.opacity; - - return Math.max( - 0, - Math.min(outerOpacity, waveOpacity) - ); - }, - - get isOpacityFullyDecayed() { - return this.opacity < 0.01 && - this.radius >= Math.min(this.maxRadius, Ripple.MAX_RADIUS); - }, - - get isRestingAtMaxRadius() { - return this.opacity >= this.initialOpacity && - this.radius >= Math.min(this.maxRadius, Ripple.MAX_RADIUS); - }, - - get isAnimationComplete() { - return this.mouseUpStart ? - this.isOpacityFullyDecayed : this.isRestingAtMaxRadius; - }, - - get translationFraction() { - return Math.min( - 1, - this.radius / this.containerMetrics.size * 2 / Math.sqrt(2) - ); - }, - - get xNow() { - if (this.xEnd) { - return this.xStart + this.translationFraction * (this.xEnd - this.xStart); - } - - return this.xStart; - }, - - get yNow() { - if (this.yEnd) { - return this.yStart + this.translationFraction * (this.yEnd - this.yStart); - } - - return this.yStart; - }, - - get isMouseDown() { - return this.mouseDownStart && !this.mouseUpStart; - }, - - resetInteractionState: function() { - this.maxRadius = 0; - this.mouseDownStart = 0; - this.mouseUpStart = 0; - - this.xStart = 0; - this.yStart = 0; - this.xEnd = 0; - this.yEnd = 0; - this.slideDistance = 0; - - this.containerMetrics = new ElementMetrics(this.element); - }, - - draw: function() { - var scale; - var translateString; - var dx; - var dy; - - this.wave.style.opacity = this.opacity; - - scale = this.radius / (this.containerMetrics.size / 2); - dx = this.xNow - (this.containerMetrics.width / 2); - dy = this.yNow - (this.containerMetrics.height / 2); - - - // 2d transform for safari because of border-radius and overflow:hidden clipping bug. - // https://bugs.webkit.org/show_bug.cgi?id=98538 - this.waveContainer.style.webkitTransform = 'translate(' + dx + 'px, ' + dy + 'px)'; - this.waveContainer.style.transform = 'translate3d(' + dx + 'px, ' + dy + 'px, 0)'; - this.wave.style.webkitTransform = 'scale(' + scale + ',' + scale + ')'; - this.wave.style.transform = 'scale3d(' + scale + ',' + scale + ',1)'; - }, - - /** @param {Event=} event */ - downAction: function(event) { - var xCenter = this.containerMetrics.width / 2; - var yCenter = this.containerMetrics.height / 2; - - this.resetInteractionState(); - this.mouseDownStart = Utility.now(); - - if (this.center) { - this.xStart = xCenter; - this.yStart = yCenter; - this.slideDistance = Utility.distance( - this.xStart, this.yStart, this.xEnd, this.yEnd - ); - } else { - this.xStart = event ? - event.detail.x - this.containerMetrics.boundingRect.left : - this.containerMetrics.width / 2; - this.yStart = event ? - event.detail.y - this.containerMetrics.boundingRect.top : - this.containerMetrics.height / 2; - } - - if (this.recenters) { - this.xEnd = xCenter; - this.yEnd = yCenter; - this.slideDistance = Utility.distance( - this.xStart, this.yStart, this.xEnd, this.yEnd - ); - } - - this.maxRadius = this.containerMetrics.furthestCornerDistanceFrom( - this.xStart, - this.yStart - ); - - this.waveContainer.style.top = - (this.containerMetrics.height - this.containerMetrics.size) / 2 + 'px'; - this.waveContainer.style.left = - (this.containerMetrics.width - this.containerMetrics.size) / 2 + 'px'; - - this.waveContainer.style.width = this.containerMetrics.size + 'px'; - this.waveContainer.style.height = this.containerMetrics.size + 'px'; - }, - - /** @param {Event=} event */ - upAction: function(event) { - if (!this.isMouseDown) { - return; - } - - this.mouseUpStart = Utility.now(); - }, - - remove: function() { - Polymer.dom(this.waveContainer.parentNode).removeChild( - this.waveContainer - ); - } - }; - - Polymer({ - is: 'paper-ripple', - - behaviors: [ - Polymer.IronA11yKeysBehavior - ], - - properties: { - /** - * The initial opacity set on the wave. - * - * @attribute initialOpacity - * @type number - * @default 0.25 - */ - initialOpacity: { - type: Number, - value: 0.25 - }, - - /** - * How fast (opacity per second) the wave fades out. - * - * @attribute opacityDecayVelocity - * @type number - * @default 0.8 - */ - opacityDecayVelocity: { - type: Number, - value: 0.8 - }, - - /** - * If true, ripples will exhibit a gravitational pull towards - * the center of their container as they fade away. - * - * @attribute recenters - * @type boolean - * @default false - */ - recenters: { - type: Boolean, - value: false - }, - - /** - * If true, ripples will center inside its container - * - * @attribute recenters - * @type boolean - * @default false - */ - center: { - type: Boolean, - value: false - }, - - /** - * A list of the visual ripples. - * - * @attribute ripples - * @type Array - * @default [] - */ - ripples: { - type: Array, - value: function() { - return []; - } - }, - - /** - * True when there are visible ripples animating within the - * element. - */ - animating: { - type: Boolean, - readOnly: true, - reflectToAttribute: true, - value: false - }, - - /** - * If true, the ripple will remain in the "down" state until `holdDown` - * is set to false again. - */ - holdDown: { - type: Boolean, - value: false, - observer: '_holdDownChanged' - }, - - /** - * If true, the ripple will not generate a ripple effect - * via pointer interaction. - * Calling ripple's imperative api like `simulatedRipple` will - * still generate the ripple effect. - */ - noink: { - type: Boolean, - value: false - }, - - _animating: { - type: Boolean - }, - - _boundAnimate: { - type: Function, - value: function() { - return this.animate.bind(this); - } - } - }, - - get target () { - var ownerRoot = Polymer.dom(this).getOwnerRoot(); - var target; - - if (this.parentNode.nodeType == 11) { // DOCUMENT_FRAGMENT_NODE - target = ownerRoot.host; - } else { - target = this.parentNode; - } - - return target; - }, - - keyBindings: { - 'enter:keydown': '_onEnterKeydown', - 'space:keydown': '_onSpaceKeydown', - 'space:keyup': '_onSpaceKeyup' - }, - - attached: function() { - // Set up a11yKeysBehavior to listen to key events on the target, - // so that space and enter activate the ripple even if the target doesn't - // handle key events. The key handlers deal with `noink` themselves. - this.keyEventTarget = this.target; - this.listen(this.target, 'up', 'uiUpAction'); - this.listen(this.target, 'down', 'uiDownAction'); - }, - - detached: function() { - this.unlisten(this.target, 'up', 'uiUpAction'); - this.unlisten(this.target, 'down', 'uiDownAction'); - }, - - get shouldKeepAnimating () { - for (var index = 0; index < this.ripples.length; ++index) { - if (!this.ripples[index].isAnimationComplete) { - return true; - } - } - - return false; - }, - - simulatedRipple: function() { - this.downAction(null); - - // Please see polymer/polymer#1305 - this.async(function() { - this.upAction(); - }, 1); - }, - - /** - * Provokes a ripple down effect via a UI event, - * respecting the `noink` property. - * @param {Event=} event - */ - uiDownAction: function(event) { - if (!this.noink) { - this.downAction(event); - } - }, - - /** - * Provokes a ripple down effect via a UI event, - * *not* respecting the `noink` property. - * @param {Event=} event - */ - downAction: function(event) { - if (this.holdDown && this.ripples.length > 0) { - return; - } - - var ripple = this.addRipple(); - - ripple.downAction(event); - - if (!this._animating) { - this.animate(); - } - }, - - /** - * Provokes a ripple up effect via a UI event, - * respecting the `noink` property. - * @param {Event=} event - */ - uiUpAction: function(event) { - if (!this.noink) { - this.upAction(event); - } - }, - - /** - * Provokes a ripple up effect via a UI event, - * *not* respecting the `noink` property. - * @param {Event=} event - */ - upAction: function(event) { - if (this.holdDown) { - return; - } - - this.ripples.forEach(function(ripple) { - ripple.upAction(event); - }); - - this.animate(); - }, - - onAnimationComplete: function() { - this._animating = false; - this.$.background.style.backgroundColor = null; - this.fire('transitionend'); - }, - - addRipple: function() { - var ripple = new Ripple(this); - - Polymer.dom(this.$.waves).appendChild(ripple.waveContainer); - this.$.background.style.backgroundColor = ripple.color; - this.ripples.push(ripple); - - this._setAnimating(true); - - return ripple; - }, - - removeRipple: function(ripple) { - var rippleIndex = this.ripples.indexOf(ripple); - - if (rippleIndex < 0) { - return; - } - - this.ripples.splice(rippleIndex, 1); - - ripple.remove(); - - if (!this.ripples.length) { - this._setAnimating(false); - } - }, - - animate: function() { - var index; - var ripple; - - this._animating = true; - - for (index = 0; index < this.ripples.length; ++index) { - ripple = this.ripples[index]; - - ripple.draw(); - - this.$.background.style.opacity = ripple.outerOpacity; - - if (ripple.isOpacityFullyDecayed && !ripple.isRestingAtMaxRadius) { - this.removeRipple(ripple); - } - } - - if (!this.shouldKeepAnimating && this.ripples.length === 0) { - this.onAnimationComplete(); - } else { - window.requestAnimationFrame(this._boundAnimate); - } - }, - - _onEnterKeydown: function() { - this.uiDownAction(); - this.async(this.uiUpAction, 1); - }, - - _onSpaceKeydown: function() { - this.uiDownAction(); - }, - - _onSpaceKeyup: function() { - this.uiUpAction(); - }, - - // note: holdDown does not respect noink since it can be a focus based - // effect. - _holdDownChanged: function(newVal, oldVal) { - if (oldVal === undefined) { - return; - } - if (newVal) { - this.downAction(); - } else { - this.upAction(); - } - } - }); - })(); -</script><dom-module id=paper-icon-button assetpath="../bower_components/paper-icon-button/"><template strip-whitespace><style> - :host { - display: inline-block; - position: relative; - padding: 8px; - outline: none; - -webkit-tap-highlight-color: rgba(0,0,0,0); - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - cursor: pointer; - z-index: 0; - line-height: 1; - - width: 40px; - height: 40px; - - /* Because of polymer/2558, this style has lower specificity than * */ - box-sizing: border-box !important; - @apply(--paper-icon-button); - } - - :host #ink { - color: var(--paper-icon-button-ink-color, --primary-text-color); - opacity: 0.6; - } - - :host([disabled]) { - color: var(--paper-icon-button-disabled-text, --disabled-text-color); - pointer-events: none; - cursor: auto; - @apply(--paper-icon-button-disabled); - } - - :host(:hover) { - @apply(--paper-icon-button-hover); - } - - iron-icon { - --iron-icon-width: 100%; - --iron-icon-height: 100%; - } - </style><iron-icon id=icon src=[[src]] icon=[[icon]] alt$=[[alt]]></iron-icon></template><script> - Polymer({ - is: 'paper-icon-button', - - hostAttributes: { - role: 'button', - tabindex: '0' - }, - - behaviors: [ - Polymer.PaperInkyFocusBehavior - ], - - properties: { - /** - * The URL of an image for the icon. If the src property is specified, - * the icon property should not be. - */ - src: { - type: String - }, - - /** - * Specifies the icon name or index in the set of icons available in - * the icon's icon set. If the icon property is specified, - * the src property should not be. - */ - icon: { - type: String - }, - - /** - * Specifies the alternate text for the button, for accessibility. - */ - alt: { - type: String, - observer: "_altChanged" - } - }, - - _altChanged: function(newValue, oldValue) { - var label = this.getAttribute('aria-label'); - - // Don't stomp over a user-set aria-label. - if (!label || oldValue == label) { - this.setAttribute('aria-label', newValue); - } - } - }); - </script></dom-module><dom-module id=paper-tooltip assetpath="../bower_components/paper-tooltip/"><template><style> - :host { - display: block; - position: absolute; - outline: none; - z-index: 1002; - -moz-user-select: none; - -ms-user-select: none; - -webkit-user-select: none; - user-select: none; - cursor: default; - } - - #tooltip { - display: block; - outline: none; - @apply(--paper-font-common-base); - font-size: 10px; - line-height: 1; - - background-color: var(--paper-tooltip-background, #616161); - opacity: var(--paper-tooltip-opacity, 0.9); - color: var(--paper-tooltip-text-color, white); - - padding: 8px; - border-radius: 2px; - - @apply(--paper-tooltip); - } - - /* Thanks IE 10. */ - .hidden { - display: none !important; - } - </style><div id=tooltip class=hidden><content></content></div></template><script> - Polymer({ - is: 'paper-tooltip', - - hostAttributes: { - role: 'tooltip', - tabindex: -1 - }, - - behaviors: [ - Polymer.NeonAnimationRunnerBehavior - ], - - properties: { - /** - * The id of the element that the tooltip is anchored to. This element - * must be a sibling of the tooltip. - */ - for: { - type: String, - observer: '_forChanged' - }, - - /** - * Positions the tooltip to the top, right, bottom, left of its content. - */ - position: { - type: String, - value: 'bottom' - }, - - /** - * If true, no parts of the tooltip will ever be shown offscreen. - */ - fitToVisibleBounds: { - type: Boolean, - value: false - }, - - /** - * The spacing between the top of the tooltip and the element it is - * anchored to. - */ - offset: { - type: Number, - value: 14 - }, - - /** - * This property is deprecated, but left over so that it doesn't - * break exiting code. Please use `offset` instead. If both `offset` and - * `marginTop` are provided, `marginTop` will be ignored. - * @deprecated since version 1.0.3 - */ - marginTop: { - type: Number, - value: 14 - }, - - /** - * The delay that will be applied before the `entry` animation is - * played when showing the tooltip. - */ - animationDelay: { - type: Number, - value: 500 - }, - - /** - * The entry and exit animations that will be played when showing and - * hiding the tooltip. If you want to override this, you must ensure - * that your animationConfig has the exact format below. - */ - animationConfig: { - type: Object, - value: function() { - return { - 'entry': [{ - name: 'fade-in-animation', - node: this, - timing: {delay: 0} - }], - 'exit': [{ - name: 'fade-out-animation', - node: this - }] - } - } - }, - - _showing: { - type: Boolean, - value: false - } - }, - - listeners: { - 'neon-animation-finish': '_onAnimationFinish', - 'mouseenter': 'hide' - }, - - /** - * Returns the target element that this tooltip is anchored to. It is - * either the element given by the `for` attribute, or the immediate parent - * of the tooltip. - */ - get target () { - var parentNode = Polymer.dom(this).parentNode; - // If the parentNode is a document fragment, then we need to use the host. - var ownerRoot = Polymer.dom(this).getOwnerRoot(); - - var target; - if (this.for) { - target = Polymer.dom(ownerRoot).querySelector('#' + this.for); - } else { - target = parentNode.nodeType == Node.DOCUMENT_FRAGMENT_NODE ? - ownerRoot.host : parentNode; - } - - return target; - }, - - attached: function() { - this._target = this.target; - - this.listen(this._target, 'mouseenter', 'show'); - this.listen(this._target, 'focus', 'show'); - this.listen(this._target, 'mouseleave', 'hide'); - this.listen(this._target, 'blur', 'hide'); - this.listen(this._target, 'tap', 'hide'); - }, - - detached: function() { - if (this._target) { - this.unlisten(this._target, 'mouseenter', 'show'); - this.unlisten(this._target, 'focus', 'show'); - this.unlisten(this._target, 'mouseleave', 'hide'); - this.unlisten(this._target, 'blur', 'hide'); - this.unlisten(this._target, 'tap', 'hide'); - } - }, - - show: function() { - // If the tooltip is already showing, there's nothing to do. - if (this._showing) - return; - - if (Polymer.dom(this).textContent.trim() === '') - return; - - - this.cancelAnimation(); - this._showing = true; - this.toggleClass('hidden', false, this.$.tooltip); - this.updatePosition(); - - this.animationConfig.entry[0].timing.delay = this.animationDelay; - this._animationPlaying = true; - this.playAnimation('entry'); - }, - - hide: function() { - // If the tooltip is already hidden, there's nothing to do. - if (!this._showing) { - return; - } - - // If the entry animation is still playing, don't try to play the exit - // animation since this will reset the opacity to 1. Just end the animation. - if (this._animationPlaying) { - this.cancelAnimation(); - this._showing = false; - this._onAnimationFinish(); - return; - } - - this._showing = false; - this._animationPlaying = true; - this.playAnimation('exit'); - }, - - _forChanged: function() { - this._target = this.target; - }, - - updatePosition: function() { - if (!this._target) - return; - - var offset = this.offset; - // If a marginTop has been provided by the user (pre 1.0.3), use it. - if (this.marginTop != 14 && this.offset == 14) - offset = this.marginTop; - - var parentRect = this.offsetParent.getBoundingClientRect(); - var targetRect = this._target.getBoundingClientRect(); - var thisRect = this.getBoundingClientRect(); - - var horizontalCenterOffset = (targetRect.width - thisRect.width) / 2; - var verticalCenterOffset = (targetRect.height - thisRect.height) / 2; - - var targetLeft = targetRect.left - parentRect.left; - var targetTop = targetRect.top - parentRect.top; - - var tooltipLeft, tooltipTop; - - switch (this.position) { - case 'top': - tooltipLeft = targetLeft + horizontalCenterOffset; - tooltipTop = targetTop - thisRect.height - offset; - break; - case 'bottom': - tooltipLeft = targetLeft + horizontalCenterOffset; - tooltipTop = targetTop + targetRect.height + offset; - break; - case 'left': - tooltipLeft = targetLeft - thisRect.width - offset; - tooltipTop = targetTop + verticalCenterOffset; - break; - case 'right': - tooltipLeft = targetLeft + targetRect.width + offset; - tooltipTop = targetTop + verticalCenterOffset; - break; - } - - // TODO(noms): This should use IronFitBehavior if possible. - if (this.fitToVisibleBounds) { - // Clip the left/right side. - if (tooltipLeft + thisRect.width > window.innerWidth) { - this.style.right = '0px'; - this.style.left = 'auto'; - } else { - this.style.left = Math.max(0, tooltipLeft) + 'px'; - this.style.right = 'auto'; - } - - // Clip the top/bottom side. - if (tooltipTop + thisRect.height > window.innerHeight) { - this.style.bottom = '0px'; - this.style.top = 'auto'; - } else { - this.style.top = Math.max(0, tooltipTop) + 'px'; - this.style.bottom = 'auto'; - } - } else { - this.style.left = tooltipLeft + 'px'; - this.style.top = tooltipTop + 'px'; - } - - }, - - _onAnimationFinish: function() { - this._animationPlaying = false; - if (!this._showing) { - this.toggleClass('hidden', true, this.$.tooltip); - } - }, - }); - </script></dom-module><iron-iconset-svg name=chat size=24><svg><defs><g id=notifications-off><path d="M11.5 22c1.1 0 2-.9 2-2h-4c0 1.1.9 2 2 2zM18 10.5c0-3.07-2.13-5.64-5-6.32V3.5c0-.83-.67-1.5-1.5-1.5S10 2.67 10 3.5v.68c-.51.12-.99.32-1.45.56L18 14.18V10.5zm-.27 8.5l2 2L21 19.73 4.27 3 3 4.27l2.92 2.92C5.34 8.16 5 9.29 5 10.5V16l-2 2v1h14.73z"></path></g><g id=share><path d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81 1.66 0 3-1.34 3-3s-1.34-3-3-3-3 1.34-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9c-1.66 0-3 1.34-3 3s1.34 3 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.16c-.05.21-.08.43-.08.65 0 1.61 1.31 2.92 2.92 2.92 1.61 0 2.92-1.31 2.92-2.92s-1.31-2.92-2.92-2.92z"></path></g><g id=call><path d="M6.62 10.79c1.44 2.83 3.76 5.14 6.59 6.59l2.2-2.2c.27-.27.67-.36 1.02-.24 1.12.37 2.33.57 3.57.57.55 0 1 .45 1 1V20c0 .55-.45 1-1 1-9.39 0-17-7.61-17-17 0-.55.45-1 1-1h3.5c.55 0 1 .45 1 1 0 1.25.2 2.45.57 3.57.11.35.03.74-.25 1.02l-2.2 2.2z"></path></g><g id=wifi-tethering><path d="M12 11c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 2c0-3.31-2.69-6-6-6s-6 2.69-6 6c0 2.22 1.21 4.15 3 5.19l1-1.74c-1.19-.7-2-1.97-2-3.45 0-2.21 1.79-4 4-4s4 1.79 4 4c0 1.48-.81 2.75-2 3.45l1 1.74c1.79-1.04 3-2.97 3-5.19zM12 3C6.48 3 2 7.48 2 13c0 3.7 2.01 6.92 4.99 8.65l1-1.73C5.61 18.53 4 15.96 4 13c0-4.42 3.58-8 8-8s8 3.58 8 8c0 2.96-1.61 5.53-4 6.92l1 1.73c2.99-1.73 5-4.95 5-8.65 0-5.52-4.48-10-10-10z"></path></g><g id=attach-file><path d="M16.5 6v11.5c0 2.21-1.79 4-4 4s-4-1.79-4-4V5c0-1.38 1.12-2.5 2.5-2.5s2.5 1.12 2.5 2.5v10.5c0 .55-.45 1-1 1s-1-.45-1-1V6H10v9.5c0 1.38 1.12 2.5 2.5 2.5s2.5-1.12 2.5-2.5V5c0-2.21-1.79-4-4-4S7 2.79 7 5v12.5c0 3.04 2.46 5.5 5.5 5.5s5.5-2.46 5.5-5.5V6h-1.5z"></path></g><g id=desktop-mac><path d="M21 2H3c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h7l-2 3v1h8v-1l-2-3h7c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 12H3V4h18v10z"></path></g><g id=desktop-windows><path d="M21 2H3c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h7v2H8v2h8v-2h-2v-2h7c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H3V4h18v12z"></path></g><g id=smartphone><path d="M17 1.01L7 1c-1.1 0-2 .9-2 2v18c0 1.1.9 2 2 2h10c1.1 0 2-.9 2-2V3c0-1.1-.9-1.99-2-1.99zM17 19H7V5h10v14z"></path></g><g id=phone-iphone><path d="M15.5 1h-8C6.12 1 5 2.12 5 3.5v17C5 21.88 6.12 23 7.5 23h8c1.38 0 2.5-1.12 2.5-2.5v-17C18 2.12 16.88 1 15.5 1zm-4 21c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zm4.5-4H7V4h9v14z"></path></g><g id=tablet-mac><path d="M18.5 0h-14C3.12 0 2 1.12 2 2.5v19C2 22.88 3.12 24 4.5 24h14c1.38 0 2.5-1.12 2.5-2.5v-19C21 1.12 19.88 0 18.5 0zm-7 23c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zm7.5-4H4V3h15v16z"></path></g><g id=info-outline><path d="M11 17h2v-6h-2v6zm1-15C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zM11 9h2V7h-2v2z"></path></g><g id=close><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"></path></g><g id=local-cafe><path d="M20 3H4v10c0 2.21 1.79 4 4 4h6c2.21 0 4-1.79 4-4v-3h2c1.11 0 2-.89 2-2V5c0-1.11-.89-2-2-2zm0 5h-2V5h2v3zM2 21h18v-2H2v2z"></path></g><g id=menu><path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"></path></g><g id=help-outline><path d="M11 18h2v-2h-2v2zm1-16C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm0-14c-2.21 0-4 1.79-4 4h2c0-1.1.9-2 2-2s2 .9 2 2c0 2-3 1.75-3 5h2c0-2.25 3-2.5 3-5 0-2.21-1.79-4-4-4z"></path></g><svg xmlns=http://www.w3.org/2000/svg xmlns:xlink=http://www.w3.org/1999/xlink version=1.1 id=twitter x=0px y=0px width=430.117px height=430.117px viewbox="0 0 430.117 430.117" style="enable-background:new 0 0 430.117 430.117;" xml:space=preserve><g><path id=twitter1 d="M381.384,198.639c24.157-1.993,40.543-12.975,46.849-27.876 c-8.714,5.353-35.764,11.189-50.703,5.631c-0.732-3.51-1.55-6.844-2.353-9.854c-11.383-41.798-50.357-75.472-91.194-71.404 c3.304-1.334,6.655-2.576,9.996-3.691c4.495-1.61,30.868-5.901,26.715-15.21c-3.5-8.188-35.722,6.188-41.789,8.067 c8.009-3.012,21.254-8.193,22.673-17.396c-12.27,1.683-24.315,7.484-33.622,15.919c3.36-3.617,5.909-8.025,6.45-12.769 C241.68,90.963,222.563,133.113,207.092,174c-12.148-11.773-22.915-21.044-32.574-26.192 c-27.097-14.531-59.496-29.692-110.355-48.572c-1.561,16.827,8.322,39.201,36.8,54.08c-6.17-0.826-17.453,1.017-26.477,3.178 c3.675,19.277,15.677,35.159,48.169,42.839c-14.849,0.98-22.523,4.359-29.478,11.642c6.763,13.407,23.266,29.186,52.953,25.947 c-33.006,14.226-13.458,40.571,13.399,36.642C113.713,320.887,41.479,317.409,0,277.828 c108.299,147.572,343.716,87.274,378.799-54.866c26.285,0.224,41.737-9.105,51.318-19.39 C414.973,206.142,393.023,203.486,381.384,198.639z"></path></g></svg><svg xmlns=http://www.w3.org/2000/svg xmlns:xlink=http://www.w3.org/1999/xlink version=1.1 id=facebook x=0px y=0px width=90px height=90px viewbox="0 0 90 90" style="enable-background:new 0 0 90 90;" xml:space=preserve><path id=Facebook__x28_alt_x29_ d="M90,15.001C90,7.119,82.884,0,75,0H15C7.116,0,0,7.119,0,15.001v59.998 C0,82.881,7.116,90,15.001,90H45V56H34V41h11v-5.844C45,25.077,52.568,16,61.875,16H74v15H61.875C60.548,31,59,32.611,59,35.024V41 h15v15H59v34h16c7.884,0,15-7.119,15-15.001V15.001z"></path></svg><svg xmlns=http://www.w3.org/2000/svg xmlns:xlink=http://www.w3.org/1999/xlink version=1.1 id=github x=0px y=0px width=438.549px height=438.549px viewbox="0 0 438.549 438.549" style="enable-background:new 0 0 438.549 438.549;" xml:space=preserve><g><path d="M409.132,114.573c-19.608-33.596-46.205-60.194-79.798-79.8C295.736,15.166,259.057,5.365,219.271,5.365 c-39.781,0-76.472,9.804-110.063,29.408c-33.596,19.605-60.192,46.204-79.8,79.8C9.803,148.168,0,184.854,0,224.63 c0,47.78,13.94,90.745,41.827,128.906c27.884,38.164,63.906,64.572,108.063,79.227c5.14,0.954,8.945,0.283,11.419-1.996 c2.475-2.282,3.711-5.14,3.711-8.562c0-0.571-0.049-5.708-0.144-15.417c-0.098-9.709-0.144-18.179-0.144-25.406l-6.567,1.136 c-4.187,0.767-9.469,1.092-15.846,1c-6.374-0.089-12.991-0.757-19.842-1.999c-6.854-1.231-13.229-4.086-19.13-8.559 c-5.898-4.473-10.085-10.328-12.56-17.556l-2.855-6.57c-1.903-4.374-4.899-9.233-8.992-14.559 c-4.093-5.331-8.232-8.945-12.419-10.848l-1.999-1.431c-1.332-0.951-2.568-2.098-3.711-3.429c-1.142-1.331-1.997-2.663-2.568-3.997 c-0.572-1.335-0.098-2.43,1.427-3.289c1.525-0.859,4.281-1.276,8.28-1.276l5.708,0.853c3.807,0.763,8.516,3.042,14.133,6.851 c5.614,3.806,10.229,8.754,13.846,14.842c4.38,7.806,9.657,13.754,15.846,17.847c6.184,4.093,12.419,6.136,18.699,6.136 c6.28,0,11.704-0.476,16.274-1.423c4.565-0.952,8.848-2.383,12.847-4.285c1.713-12.758,6.377-22.559,13.988-29.41 c-10.848-1.14-20.601-2.857-29.264-5.14c-8.658-2.286-17.605-5.996-26.835-11.14c-9.235-5.137-16.896-11.516-22.985-19.126 c-6.09-7.614-11.088-17.61-14.987-29.979c-3.901-12.374-5.852-26.648-5.852-42.826c0-23.035,7.52-42.637,22.557-58.817 c-7.044-17.318-6.379-36.732,1.997-58.24c5.52-1.715,13.706-0.428,24.554,3.853c10.85,4.283,18.794,7.952,23.84,10.994 c5.046,3.041,9.089,5.618,12.135,7.708c17.705-4.947,35.976-7.421,54.818-7.421s37.117,2.474,54.823,7.421l10.849-6.849 c7.419-4.57,16.18-8.758,26.262-12.565c10.088-3.805,17.802-4.853,23.134-3.138c8.562,21.509,9.325,40.922,2.279,58.24 c15.036,16.18,22.559,35.787,22.559,58.817c0,16.178-1.958,30.497-5.853,42.966c-3.9,12.471-8.941,22.457-15.125,29.979 c-6.191,7.521-13.901,13.85-23.131,18.986c-9.232,5.14-18.182,8.85-26.84,11.136c-8.662,2.286-18.415,4.004-29.263,5.146 c9.894,8.562,14.842,22.077,14.842,40.539v60.237c0,3.422,1.19,6.279,3.572,8.562c2.379,2.279,6.136,2.95,11.276,1.995 c44.163-14.653,80.185-41.062,108.068-79.226c27.88-38.161,41.825-81.126,41.825-128.906 C438.536,184.851,428.728,148.168,409.132,114.573z"></path></g></svg></defs></svg></iron-iconset-svg><dom-module id=invitation-link assetpath="invitation-link/"><template><style> - :host { - display: block; - position: absolute; - top: 16px; - left: 16px; - z-index: 3; - } - </style><paper-icon-button icon=chat:share on-tap=_copy id=btn></paper-icon-button><paper-tooltip for=btn position=bottom offset=14>Get an Invitation Link to send files accross different networks.</paper-tooltip></template><script> - 'use strict'; - Polymer({ - is: 'invitation-link', - behaviors: [Chat.InvitationLinkBehavior] - }); - </script></dom-module><dom-module id=x-cards assetpath="x-cards/"><template><style> - :host { - display: block; - overflow: hidden; - } - - #placeholder { - opacity: 0; - background-color: grey; - @apply(--layout-fit); - } - - paper-icon-button { - color: #52524F; - } - - paper-icon-button:hover { - color: #4285f4; - } - - .share { - position: absolute; - top: 16px; - right: 16px; - z-index: 10; - } - - .share a { - color: #52524F; - text-decoration: none; - padding-left: 8px; - padding-right: 8px; - } - - .share a:hover, - #btn:hover { - color: #4285f4; - } - - </style><div id=placeholder></div><div id=container><div class=share><paper-icon-button id=btn icon=chat:info-outline on-tap=_switch></paper-icon-button></div><content select=div></content></div></template></dom-module><script> -(function() { - Polymer({ - is: 'x-cards', - behaviors: [ - Polymer.NeonSharedElementAnimatableBehavior - ], - properties: { - animationConfig: { - value: function() { - return { - 'entry': [{ - name: 'reverse-ripple-animation', - id: 'reverse-ripple', - toPage: this - }], - 'exit': [{ - name: 'fade-out-animation', - node: this.$.container, - timing: { - delay: 150, - duration: 0 - } - }, { - name: 'ripple-animation', - id: 'ripple', - fromPage: this - }] - }; - } - }, - sharedElements: { - value: function() { - return { - 'ripple': this.$.btn, - 'reverse-ripple': this.$.btn - }; - } - } - }, - _switch: function() { - document.querySelector('#pages').select(1); - } - }); -})(); -</script><dom-module id=paper-dialog-shared-styles assetpath="../bower_components/paper-dialog-behavior/"><template><style> - :host { - display: block; - margin: 24px 40px; - -webkit-overflow-scrolling: touch; - - background: var(--paper-dialog-background-color, --primary-background-color); - color: var(--paper-dialog-color, --primary-text-color); - - @apply(--paper-font-body1); - @apply(--shadow-elevation-16dp); - @apply(--paper-dialog); - } - - :host > ::content > * { - margin-top: 20px; - padding: 0 24px; - } - - :host > ::content > .no-padding { - padding: 0; - } - - :host > ::content > *:first-child { - margin-top: 24px; - } - - :host > ::content > *:last-child { - margin-bottom: 24px; - } - - :host > ::content h2 { - position: relative; - margin: 0; - @apply(--paper-font-title); - - @apply(--paper-dialog-title); - } - - :host > ::content .buttons { - position: relative; - padding: 8px 8px 8px 24px; - margin: 0; - - color: var(--paper-dialog-button-color, --default-primary-color); - - @apply(--layout-horizontal); - @apply(--layout-end-justified); - } - </style></template></dom-module><dom-module id=paper-dialog assetpath="../bower_components/paper-dialog/"><template><style include=paper-dialog-shared-styles></style><content></content></template></dom-module><script> - -(function() { - - Polymer({ - - is: 'paper-dialog', - - behaviors: [ - Polymer.PaperDialogBehavior, - Polymer.NeonAnimationRunnerBehavior - ], - - listeners: { - 'neon-animation-finish': '_onNeonAnimationFinish' - }, - - _renderOpened: function() { - if (this.withBackdrop) { - this.backdropElement.open(); - } - this.playAnimation('entry'); - }, - - _renderClosed: function() { - if (this.withBackdrop) { - this.backdropElement.close(); - } - this.playAnimation('exit'); - }, - - _onNeonAnimationFinish: function() { - if (this.opened) { - this._finishRenderOpened(); - } else { - this._finishRenderClosed(); - } - } - - }); - -})(); - -</script><dom-module id=paper-material-shared-styles assetpath="../bower_components/paper-material/"><template><style> - :host { - display: block; - position: relative; - } - - :host([elevation="1"]) { - @apply(--shadow-elevation-2dp); - } - - :host([elevation="2"]) { - @apply(--shadow-elevation-4dp); - } - - :host([elevation="3"]) { - @apply(--shadow-elevation-6dp); - } - - :host([elevation="4"]) { - @apply(--shadow-elevation-8dp); - } - - :host([elevation="5"]) { - @apply(--shadow-elevation-16dp); - } - </style></template></dom-module><dom-module id=paper-material assetpath="../bower_components/paper-material/"><template><style include=paper-material-shared-styles></style><style> - :host([animated]) { - @apply(--shadow-transition); - } - </style><content></content></template></dom-module><script> - Polymer({ - is: 'paper-material', - - properties: { - /** - * The z-depth of this element, from 0-5. Setting to 0 will remove the - * shadow, and each increasing number greater than 0 will be "deeper" - * than the last. - * - * @attribute elevation - * @type number - * @default 1 - */ - elevation: { - type: Number, - reflectToAttribute: true, - value: 1 - }, - - /** - * Set this to true to animate the shadow when setting a new - * `elevation` value. - * - * @attribute animated - * @type boolean - * @default false - */ - animated: { - type: Boolean, - reflectToAttribute: true, - value: false - } - } - }); -</script><dom-module id=paper-button assetpath="../bower_components/paper-button/"><template strip-whitespace><style include=paper-material> - :host { - display: inline-block; - position: relative; - box-sizing: border-box; - min-width: 5.14em; - margin: 0 0.29em; - background: transparent; - text-align: center; - font: inherit; - text-transform: uppercase; - outline-width: 0; - border-radius: 3px; - -moz-user-select: none; - -ms-user-select: none; - -webkit-user-select: none; - user-select: none; - cursor: pointer; - z-index: 0; - padding: 0.7em 0.57em; - - @apply(--paper-button); - } - - :host([raised].keyboard-focus) { - font-weight: bold; - @apply(--paper-button-raised-keyboard-focus); - } - - :host(:not([raised]).keyboard-focus) { - font-weight: bold; - @apply(--paper-button-flat-keyboard-focus); - } - - :host([disabled]) { - background: #eaeaea; - color: #a8a8a8; - cursor: auto; - pointer-events: none; - - @apply(--paper-button-disabled); - } - - paper-ripple { - color: var(--paper-button-ink-color); - } - - :host > ::content * { - text-transform: inherit; - } - </style><content></content></template></dom-module><script> - Polymer({ - is: 'paper-button', - - behaviors: [ - Polymer.PaperButtonBehavior - ], - - properties: { - /** - * If true, the button should be styled with a shadow. - */ - raised: { - type: Boolean, - reflectToAttribute: true, - value: false, - observer: '_calculateElevation' - } - }, - - _calculateElevation: function() { - if (!this.raised) { - this._setElevation(0); - } else { - Polymer.PaperButtonBehaviorImpl._calculateElevation.apply(this); - } - } - /** - - Fired when the animation finishes. - This is useful if you want to wait until - the ripple animation finishes to perform some action. - - @event transitionend - @param {{node: Object}} detail Contains the animated node. - */ - }); -</script><dom-module id=iron-pages assetpath="../bower_components/iron-pages/"><style> - - :host { - display: block; - } - - :host > ::content > :not(.iron-selected) { - display: none !important; - } - - </style><template><content></content></template></dom-module><script> - - Polymer({ - - is: 'iron-pages', - - behaviors: [ - Polymer.IronResizableBehavior, - Polymer.IronSelectableBehavior - ], - - properties: { - - // as the selected page is the only one visible, activateEvent - // is both non-sensical and problematic; e.g. in cases where a user - // handler attempts to change the page and the activateEvent - // handler immediately changes it back - activateEvent: { - type: String, - value: null - } - - }, - - observers: [ - '_selectedPageChanged(selected)' - ], - - _selectedPageChanged: function(selected, old) { - this.async(this.notifyResize); - } - }); - -</script><dom-module id=paper-spinner-styles assetpath="../bower_components/paper-spinner/"><template><style> - /* - /**************************/ - /* STYLES FOR THE SPINNER */ - /**************************/ - - /* - * Constants: - * STROKEWIDTH = 3px - * 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) - * CONTAINERWIDTH = 28px - * SHRINK_TIME = 400ms - */ - - :host { - display: inline-block; - position: relative; - width: 28px; /* CONTAINERWIDTH */ - height: 28px; /* CONTAINERWIDTH */ - } - - #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 { - /* duration: 360 * ARCTIME / (ARCSTARTROT + (360-ARCSIZE)) */ - -webkit-animation: container-rotate 1568ms linear infinite; - animation: container-rotate 1568ms 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). - * - * We write out each animation in full (instead of separating animation-name, - * animation-duration, etc.) because under the polyfill, Safari does not recognize those - * specific properties properly, treats them as -webkit-animation, and overrides the - * other animation rules. See https://github.com/Polymer/platform/issues/53. - */ - .active .spinner-layer { - /* durations: 4 * ARCTIME */ - -webkit-animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both; - animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both; - opacity: 1; - } - - .active .spinner-layer.layer-1 { - /* durations: 4 * ARCTIME */ - -webkit-animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both, layer-1-fade-in-out 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both; - animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both, layer-1-fade-in-out 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both; - } - - .active .spinner-layer.layer-2 { - /* durations: 4 * ARCTIME */ - -webkit-animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both, layer-2-fade-in-out 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both; - animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both, layer-2-fade-in-out 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both; - } - - .active .spinner-layer.layer-3 { - /* durations: 4 * ARCTIME */ - -webkit-animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both, layer-3-fade-in-out 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both; - animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both, layer-3-fade-in-out 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both; - } - - .active .spinner-layer.layer-4 { - /* durations: 4 * ARCTIME */ - -webkit-animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both, layer-4-fade-in-out 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both; - animation: fill-unfill-rotate 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both, layer-4-fade-in-out 5332ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both; - } - - @-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 */ - } - - /** - * HACK: Even though the intention is to have the current .spinner-layer at - * `opacity: 1`, we set it to `opacity: 0.99` instead since this forces Chrome - * to do proper subpixel rendering for the elements being animated. This is - * especially visible in Chrome 39 on Ubuntu 14.04. See: - * - * - https://github.com/Polymer/paper-spinner/issues/9 - * - https://code.google.com/p/chromium/issues/detail?id=436255 - */ - @-webkit-keyframes layer-1-fade-in-out { - from { opacity: 0.99; } - 25% { opacity: 0.99; } - 26% { opacity: 0; } - 89% { opacity: 0; } - 90% { opacity: 0.99; } - 100% { opacity: 0.99; } - } - - @keyframes layer-1-fade-in-out { - from { opacity: 0.99; } - 25% { opacity: 0.99; } - 26% { opacity: 0; } - 89% { opacity: 0; } - 90% { opacity: 0.99; } - 100% { opacity: 0.99; } - } - - @-webkit-keyframes layer-2-fade-in-out { - from { opacity: 0; } - 15% { opacity: 0; } - 25% { opacity: 0.99; } - 50% { opacity: 0.99; } - 51% { opacity: 0; } - } - - @keyframes layer-2-fade-in-out { - from { opacity: 0; } - 15% { opacity: 0; } - 25% { opacity: 0.99; } - 50% { opacity: 0.99; } - 51% { opacity: 0; } - } - - @-webkit-keyframes layer-3-fade-in-out { - from { opacity: 0; } - 40% { opacity: 0; } - 50% { opacity: 0.99; } - 75% { opacity: 0.99; } - 76% { opacity: 0; } - } - - @keyframes layer-3-fade-in-out { - from { opacity: 0; } - 40% { opacity: 0; } - 50% { opacity: 0.99; } - 75% { opacity: 0.99; } - 76% { opacity: 0; } - } - - @-webkit-keyframes layer-4-fade-in-out { - from { opacity: 0; } - 65% { opacity: 0; } - 75% { opacity: 0.99; } - 90% { opacity: 0.99; } - 100% { opacity: 0; } - } - - @keyframes layer-4-fade-in-out { - from { opacity: 0; } - 65% { opacity: 0; } - 75% { opacity: 0.99; } - 90% { opacity: 0.99; } - 100% { opacity: 0; } - } - - /** - * Patch the gap that appear between the two adjacent div.circle-clipper while the - * spinner is rotating (appears on Chrome 38, Safari 7.1, and IE 11). - * - * Update: the gap no longer appears on Chrome when .spinner-layer's opacity is 0.99, - * but still does on Safari and IE. - */ - .gap-patch { - position: absolute; - box-sizing: border-box; - top: 0; - left: 45%; - width: 10%; - height: 100%; - overflow: hidden; - border-color: inherit; - } - - .gap-patch .circle { - width: 1000%; - left: -450%; - } - - .circle-clipper { - display: inline-block; - position: relative; - width: 50%; - height: 100%; - overflow: hidden; - border-color: inherit; - } - - .circle-clipper .circle { - width: 200%; - } - - .circle { - box-sizing: border-box; - height: 100%; - border-width: 3px; /* STROKEWIDTH */ - border-style: solid; - border-color: inherit; - border-bottom-color: transparent !important; - border-radius: 50%; - -webkit-animation: none; - animation: none; - - @apply(--layout-fit); - } - - .circle-clipper.left .circle { - border-right-color: transparent !important; - -webkit-transform: rotate(129deg); - transform: rotate(129deg); - } - - .circle-clipper.right .circle { - left: -100%; - border-left-color: transparent !important; - -webkit-transform: rotate(-129deg); - transform: rotate(-129deg); - } - - .active .circle-clipper.left .circle { - /* duration: ARCTIME */ - -webkit-animation: left-spin 1333ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both; - animation: left-spin 1333ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both; - } - - .active .circle-clipper.right .circle { - /* duration: ARCTIME */ - -webkit-animation: right-spin 1333ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both; - animation: right-spin 1333ms cubic-bezier(0.4, 0.0, 0.2, 1) infinite both; - } - - @-webkit-keyframes left-spin { - from { -webkit-transform: rotate(130deg); } - 50% { -webkit-transform: rotate(-5deg); } - to { -webkit-transform: rotate(130deg); } - } - - @keyframes left-spin { - from { transform: rotate(130deg); } - 50% { transform: rotate(-5deg); } - to { transform: rotate(130deg); } - } - - @-webkit-keyframes right-spin { - from { -webkit-transform: rotate(-130deg); } - 50% { -webkit-transform: rotate(5deg); } - to { -webkit-transform: rotate(-130deg); } - } - - @keyframes right-spin { - from { transform: rotate(-130deg); } - 50% { transform: rotate(5deg); } - to { transform: rotate(-130deg); } - } - - #spinnerContainer.cooldown { - /* duration: SHRINK_TIME */ - -webkit-animation: container-rotate 1568ms linear infinite, fade-out 400ms cubic-bezier(0.4, 0.0, 0.2, 1); - animation: container-rotate 1568ms linear infinite, fade-out 400ms cubic-bezier(0.4, 0.0, 0.2, 1); - } - - @-webkit-keyframes fade-out { - from { opacity: 0.99; } - to { opacity: 0; } - } - - @keyframes fade-out { - from { opacity: 0.99; } - to { opacity: 0; } - } - </style></template></dom-module><dom-module id=paper-spinner assetpath="../bower_components/paper-spinner/"><template><style include=paper-spinner-styles></style><div id=spinnerContainer class-name="[[__computeContainerClasses(active, __coolingDown)]]"><div class="spinner-layer layer-1"><div class="circle-clipper left"><div class=circle></div></div><div class=gap-patch><div class=circle></div></div><div class="circle-clipper right"><div class=circle></div></div></div><div class="spinner-layer layer-2"><div class="circle-clipper left"><div class=circle></div></div><div class=gap-patch><div class=circle></div></div><div class="circle-clipper right"><div class=circle></div></div></div><div class="spinner-layer layer-3"><div class="circle-clipper left"><div class=circle></div></div><div class=gap-patch><div class=circle></div></div><div class="circle-clipper right"><div class=circle></div></div></div><div class="spinner-layer layer-4"><div class="circle-clipper left"><div class=circle></div></div><div class=gap-patch><div class=circle></div></div><div class="circle-clipper right"><div class=circle></div></div></div></div></template><script> - Polymer({ - is: 'paper-spinner', - - behaviors: [ - Polymer.PaperSpinnerBehavior - ] - }); - </script></dom-module><dom-module id=iron-autogrow-textarea assetpath="../bower_components/iron-autogrow-textarea/"><style> - :host { - display: inline-block; - position: relative; - width: 400px; - border: 1px solid; - padding: 2px; - -moz-appearance: textarea; - -webkit-appearance: textarea; - } - - .mirror-text { - visibility: hidden; - word-wrap: break-word; - } - - .fit { - @apply(--layout-fit); - } - - textarea { - position: relative; - outline: none; - border: none; - resize: none; - background: inherit; - color: inherit; - /* see comments in template */ - width: 100%; - height: 100%; - font-size: inherit; - font-family: inherit; - line-height: inherit; - text-align: inherit; - @apply(--iron-autogrow-textarea); - } - - ::content textarea:invalid { - box-shadow: none; - } - - </style><template><div id=mirror class=mirror-text aria-hidden=true>&nbsp;</div><div class="textarea-container fit"><textarea id=textarea autocomplete$=[[autocomplete]] autofocus$=[[autofocus]] inputmode$=[[inputmode]] placeholder$=[[placeholder]] readonly$=[[readonly]] required$=[[required]] disabled$=[[disabled]] rows$=[[rows]] maxlength$=[[maxlength]]></textarea></div></template></dom-module><script> - - Polymer({ - - is: 'iron-autogrow-textarea', - - behaviors: [ - Polymer.IronFormElementBehavior, - Polymer.IronValidatableBehavior, - Polymer.IronControlState - ], - - properties: { - - /** - * Use this property instead of `value` for two-way data binding. - * - * @type {string|number|undefined|null} - */ - bindValue: { - observer: '_bindValueChanged', - type: String - }, - - /** - * The initial number of rows. - * - * @attribute rows - * @type number - * @default 1 - */ - rows: { - type: Number, - value: 1, - observer: '_updateCached' - }, - - /** - * The maximum number of rows this element can grow to until it - * scrolls. 0 means no maximum. - * - * @attribute maxRows - * @type number - * @default 0 - */ - maxRows: { - type: Number, - value: 0, - observer: '_updateCached' - }, - - /** - * Bound to the textarea's `autocomplete` attribute. - */ - autocomplete: { - type: String, - value: 'off' - }, - - /** - * Bound to the textarea's `autofocus` attribute. - */ - autofocus: { - type: Boolean, - value: false - }, - - /** - * Bound to the textarea's `inputmode` attribute. - */ - inputmode: { - type: String - }, - - /** - * Bound to the textarea's `name` attribute. - */ - name: { - type: String - }, - - /** - * The value for this input, same as `bindValue` - */ - value: { - notify: true, - type: String, - computed: '_computeValue(bindValue)' - }, - - /** - * Bound to the textarea's `placeholder` attribute. - */ - placeholder: { - type: String - }, - - /** - * Bound to the textarea's `readonly` attribute. - */ - readonly: { - type: String - }, - - /** - * Set to true to mark the textarea as required. - */ - required: { - type: Boolean - }, - - /** - * The maximum length of the input value. - */ - maxlength: { - type: Number - } - - }, - - listeners: { - 'input': '_onInput' - }, - - /** - * Returns the underlying textarea. - * @type HTMLTextAreaElement - */ - get textarea() { - return this.$.textarea; - }, - - /** - * Returns textarea's selection start. - * @type Number - */ - get selectionStart() { - return this.$.textarea.selectionStart; - }, - - /** - * Returns textarea's selection end. - * @type Number - */ - get selectionEnd() { - return this.$.textarea.selectionEnd; - }, - - /** - * Sets the textarea's selection start. - */ - set selectionStart(value) { - this.$.textarea.selectionStart = value; - }, - - /** - * Sets the textarea's selection end. - */ - set selectionEnd(value) { - this.$.textarea.selectionEnd = value; - }, - - /** - * Returns true if `value` is valid. The validator provided in `validator` - * will be used first, if it exists; otherwise, the `textarea`'s validity - * is used. - * @return {boolean} True if the value is valid. - */ - validate: function() { - // Empty, non-required input is valid. - if (!this.required && this.value == '') { - this.invalid = false; - return true; - } - - var valid; - if (this.hasValidator()) { - valid = Polymer.IronValidatableBehavior.validate.call(this, this.value); - } else { - valid = this.$.textarea.validity.valid; - this.invalid = !valid; - } - this.fire('iron-input-validate'); - return valid; - }, - - _bindValueChanged: function() { - var textarea = this.textarea; - if (!textarea) { - return; - } - - // If the bindValue changed manually, then we need to also update - // the underlying textarea's value. Otherwise this change was probably - // generated from the _onInput handler, and the two values are already - // the same. - if (textarea.value !== this.bindValue) { - textarea.value = !(this.bindValue || this.bindValue === 0) ? '' : this.bindValue; - } - - this.$.mirror.innerHTML = this._valueForMirror(); - // manually notify because we don't want to notify until after setting value - this.fire('bind-value-changed', {value: this.bindValue}); - }, - - _onInput: function(event) { - this.bindValue = event.path ? event.path[0].value : event.target.value; - }, - - _constrain: function(tokens) { - var _tokens; - tokens = tokens || ['']; - // Enforce the min and max heights for a multiline input to avoid measurement - if (this.maxRows > 0 && tokens.length > this.maxRows) { - _tokens = tokens.slice(0, this.maxRows); - } else { - _tokens = tokens.slice(0); - } - while (this.rows > 0 && _tokens.length < this.rows) { - _tokens.push(''); - } - // Use &#160; instead &nbsp; of to allow this element to be used in XHTML. - return _tokens.join('<br/>') + '&#160;'; - }, - - _valueForMirror: function() { - var input = this.textarea; - if (!input) { - return; - } - this.tokens = (input && input.value) ? input.value.replace(/&/gm, '&amp;').replace(/"/gm, '&quot;').replace(/'/gm, '&#39;').replace(/</gm, '&lt;').replace(/>/gm, '&gt;').split('\n') : ['']; - return this._constrain(this.tokens); - }, - - _updateCached: function() { - this.$.mirror.innerHTML = this._constrain(this.tokens); - }, - - _computeValue: function() { - return this.bindValue; - } - }); -</script><dom-module id=paper-input-char-counter assetpath="../bower_components/paper-input/"><template><style> - :host { - display: inline-block; - float: right; - - @apply(--paper-font-caption); - @apply(--paper-input-char-counter); - } - - :host-context([dir="rtl"]) { - float: left; - } - </style><span>[[_charCounterStr]]</span></template></dom-module><script> - Polymer({ - is: 'paper-input-char-counter', - - behaviors: [ - Polymer.PaperInputAddonBehavior - ], - - properties: { - _charCounterStr: { - type: String, - value: '0' - } - }, - - update: function(state) { - if (!state.inputElement) { - return; - } - - state.value = state.value || ''; - - // Account for the textarea's new lines. - var str = state.value.replace(/(\r\n|\n|\r)/g, '--').length; - - if (state.inputElement.hasAttribute('maxlength')) { - str += '/' + state.inputElement.getAttribute('maxlength'); - } - this._charCounterStr = str; - } - }); -</script><dom-module id=paper-input-container assetpath="../bower_components/paper-input/"><template><style> - :host { - display: block; - padding: 8px 0; - - @apply(--paper-input-container); - } - - :host[inline] { - display: inline-block; - } - - :host([disabled]) { - pointer-events: none; - opacity: 0.33; - - @apply(--paper-input-container-disabled); - } - - .floated-label-placeholder { - @apply(--paper-font-caption); - } - - .underline { - position: relative; - } - - .focused-line { - @apply(--layout-fit); - - background: var(--paper-input-container-focus-color, --default-primary-color); - height: 2px; - -webkit-transform-origin: center center; - transform-origin: center center; - -webkit-transform: scale3d(0,1,1); - transform: scale3d(0,1,1); - - @apply(--paper-input-container-underline-focus); - } - - .underline.is-highlighted .focused-line { - -webkit-transform: none; - transform: none; - -webkit-transition: -webkit-transform 0.25s; - transition: transform 0.25s; - - @apply(--paper-transition-easing); - } - - .underline.is-invalid .focused-line { - background: var(--paper-input-container-invalid-color, --google-red-500); - -webkit-transform: none; - transform: none; - -webkit-transition: -webkit-transform 0.25s; - transition: transform 0.25s; - - @apply(--paper-transition-easing); - } - - .unfocused-line { - @apply(--layout-fit); - - height: 1px; - background: var(--paper-input-container-color, --secondary-text-color); - - @apply(--paper-input-container-underline); - } - - :host([disabled]) .unfocused-line { - border-bottom: 1px dashed; - border-color: var(--paper-input-container-color, --secondary-text-color); - background: transparent; - - @apply(--paper-input-container-underline-disabled); - } - - .label-and-input-container { - @apply(--layout-flex); - @apply(--layout-relative); - } - - .input-content { - @apply(--layout-horizontal); - @apply(--layout-center); - - position: relative; - } - - .input-content ::content label, - .input-content ::content .paper-input-label { - position: absolute; - top: 0; - right: 0; - left: 0; - font: inherit; - color: var(--paper-input-container-color, --secondary-text-color); - - @apply(--paper-font-common-nowrap); - @apply(--paper-font-subhead); - @apply(--paper-input-container-label); - } - - .input-content.label-is-floating ::content label, - .input-content.label-is-floating ::content .paper-input-label { - -webkit-transform: translateY(-75%) scale(0.75); - transform: translateY(-75%) scale(0.75); - -webkit-transition: -webkit-transform 0.25s; - transition: transform 0.25s; - -webkit-transform-origin: left top; - transform-origin: left top; - - /* Since we scale to 75/100 of the size, we actually have 100/75 of the - original space now available */ - width: 133%; - - @apply(--paper-transition-easing); - @apply(--paper-input-container-label-floating); - } - - :host-context([dir="rtl"]) .input-content.label-is-floating ::content label, - :host-context([dir="rtl"]) .input-content.label-is-floating ::content .paper-input-label { - /* TODO(noms): Figure out why leaving the width at 133% before the animation - * actually makes - * it wider on the right side, not left side, as you would expect in RTL */ - width: 100%; - -webkit-transform-origin: right top; - transform-origin: right top; - } - - .input-content.label-is-highlighted ::content label, - .input-content.label-is-highlighted ::content .paper-input-label { - color: var(--paper-input-container-focus-color, --default-primary-color); - - @apply(--paper-input-container-label-focus); - } - - .input-content.is-invalid ::content label, - .input-content.is-invalid ::content .paper-input-label { - color: var(--paper-input-container-invalid-color, --google-red-500); - } - - .input-content.label-is-hidden ::content label, - .input-content.label-is-hidden ::content .paper-input-label { - visibility: hidden; - } - - .input-content ::content input, - .input-content ::content textarea, - .input-content ::content iron-autogrow-textarea, - .input-content ::content .paper-input-input { - position: relative; /* to make a stacking context */ - outline: none; - box-shadow: none; - padding: 0; - width: 100%; - background: transparent; - border: none; - color: var(--paper-input-container-input-color, --primary-text-color); - -webkit-appearance: none; - text-align: inherit; - - @apply(--paper-font-subhead); - @apply(--paper-input-container-input); - } - - ::content [prefix] { - @apply(--paper-font-subhead); - @apply(--paper-input-prefix); - } - - ::content [suffix] { - @apply(--paper-font-subhead); - @apply(--paper-input-suffix); - } - - /* Firefox sets a min-width on the input, which can cause layout issues */ - .input-content ::content input { - min-width: 0; - } - - .input-content ::content textarea { - resize: none; - } - - .add-on-content { - position: relative; - } - - .add-on-content.is-invalid ::content * { - color: var(--paper-input-container-invalid-color, --google-red-500); - } - - .add-on-content.is-highlighted ::content * { - color: var(--paper-input-container-focus-color, --default-primary-color); - } - </style><template is=dom-if if=[[!noLabelFloat]]><div class=floated-label-placeholder>&nbsp;</div></template><div class$=[[_computeInputContentClass(noLabelFloat,alwaysFloatLabel,focused,invalid,_inputHasContent)]]><content select=[prefix] id=prefix></content><div class=label-and-input-container id=labelAndInputContainer><content select=:not([add-on]):not([prefix]):not([suffix])></content></div><content select=[suffix]></content></div><div class$=[[_computeUnderlineClass(focused,invalid)]]><div class=unfocused-line></div><div class=focused-line></div></div><div class$=[[_computeAddOnContentClass(focused,invalid)]]><content id=addOnContent select=[add-on]></content></div></template></dom-module><script> - Polymer({ - is: 'paper-input-container', - - properties: { - /** - * Set to true to disable the floating label. The label disappears when the input value is - * not null. - */ - noLabelFloat: { - type: Boolean, - value: false - }, - - /** - * Set to true to always float the floating label. - */ - alwaysFloatLabel: { - type: Boolean, - value: false - }, - - /** - * The attribute to listen for value changes on. - */ - attrForValue: { - type: String, - value: 'bind-value' - }, - - /** - * Set to true to auto-validate the input value when it changes. - */ - autoValidate: { - type: Boolean, - value: false - }, - - /** - * True if the input is invalid. This property is set automatically when the input value - * changes if auto-validating, or when the `iron-input-validate` event is heard from a child. - */ - invalid: { - observer: '_invalidChanged', - type: Boolean, - value: false - }, - - /** - * True if the input has focus. - */ - focused: { - readOnly: true, - type: Boolean, - value: false, - notify: true - }, - - _addons: { - type: Array - // do not set a default value here intentionally - it will be initialized lazily when a - // distributed child is attached, which may occur before configuration for this element - // in polyfill. - }, - - _inputHasContent: { - type: Boolean, - value: false - }, - - _inputSelector: { - type: String, - value: 'input,textarea,.paper-input-input' - }, - - _boundOnFocus: { - type: Function, - value: function() { - return this._onFocus.bind(this); - } - }, - - _boundOnBlur: { - type: Function, - value: function() { - return this._onBlur.bind(this); - } - }, - - _boundOnInput: { - type: Function, - value: function() { - return this._onInput.bind(this); - } - }, - - _boundValueChanged: { - type: Function, - value: function() { - return this._onValueChanged.bind(this); - } - } - }, - - listeners: { - 'addon-attached': '_onAddonAttached', - 'iron-input-validate': '_onIronInputValidate' - }, - - get _valueChangedEvent() { - return this.attrForValue + '-changed'; - }, - - get _propertyForValue() { - return Polymer.CaseMap.dashToCamelCase(this.attrForValue); - }, - - get _inputElement() { - return Polymer.dom(this).querySelector(this._inputSelector); - }, - - get _inputElementValue() { - return this._inputElement[this._propertyForValue] || this._inputElement.value; - }, - - ready: function() { - if (!this._addons) { - this._addons = []; - } - this.addEventListener('focus', this._boundOnFocus, true); - this.addEventListener('blur', this._boundOnBlur, true); - if (this.attrForValue) { - this._inputElement.addEventListener(this._valueChangedEvent, this._boundValueChanged); - } else { - this.addEventListener('input', this._onInput); - } - }, - - attached: function() { - // Only validate when attached if the input already has a value. - if (this._inputElementValue != '') { - this._handleValueAndAutoValidate(this._inputElement); - } else { - this._handleValue(this._inputElement); - } - }, - - _onAddonAttached: function(event) { - if (!this._addons) { - this._addons = []; - } - var target = event.target; - if (this._addons.indexOf(target) === -1) { - this._addons.push(target); - if (this.isAttached) { - this._handleValue(this._inputElement); - } - } - }, - - _onFocus: function() { - this._setFocused(true); - }, - - _onBlur: function() { - this._setFocused(false); - this._handleValueAndAutoValidate(this._inputElement); - }, - - _onInput: function(event) { - this._handleValueAndAutoValidate(event.target); - }, - - _onValueChanged: function(event) { - this._handleValueAndAutoValidate(event.target); - }, - - _handleValue: function(inputElement) { - var value = this._inputElementValue; - - // type="number" hack needed because this.value is empty until it's valid - if (value || value === 0 || (inputElement.type === 'number' && !inputElement.checkValidity())) { - this._inputHasContent = true; - } else { - this._inputHasContent = false; - } - - this.updateAddons({ - inputElement: inputElement, - value: value, - invalid: this.invalid - }); - }, - - _handleValueAndAutoValidate: function(inputElement) { - if (this.autoValidate) { - var valid; - if (inputElement.validate) { - valid = inputElement.validate(this._inputElementValue); - } else { - valid = inputElement.checkValidity(); - } - this.invalid = !valid; - } - - // Call this last to notify the add-ons. - this._handleValue(inputElement); - }, - - _onIronInputValidate: function(event) { - this.invalid = this._inputElement.invalid; - }, - - _invalidChanged: function() { - if (this._addons) { - this.updateAddons({invalid: this.invalid}); - } - }, - - /** - * Call this to update the state of add-ons. - * @param {Object} state Add-on state. - */ - updateAddons: function(state) { - for (var addon, index = 0; addon = this._addons[index]; index++) { - addon.update(state); - } - }, - - _computeInputContentClass: function(noLabelFloat, alwaysFloatLabel, focused, invalid, _inputHasContent) { - var cls = 'input-content'; - if (!noLabelFloat) { - var label = this.querySelector('label'); - - if (alwaysFloatLabel || _inputHasContent) { - cls += ' label-is-floating'; - // If the label is floating, ignore any offsets that may have been - // applied from a prefix element. - this.$.labelAndInputContainer.style.position = 'static'; - - if (invalid) { - cls += ' is-invalid'; - } else if (focused) { - cls += " label-is-highlighted"; - } - } else { - // When the label is not floating, it should overlap the input element. - if (label) { - this.$.labelAndInputContainer.style.position = 'relative'; - } - } - } else { - if (_inputHasContent) { - cls += ' label-is-hidden'; - } - } - return cls; - }, - - _computeUnderlineClass: function(focused, invalid) { - var cls = 'underline'; - if (invalid) { - cls += ' is-invalid'; - } else if (focused) { - cls += ' is-highlighted' - } - return cls; - }, - - _computeAddOnContentClass: function(focused, invalid) { - var cls = 'add-on-content'; - if (invalid) { - cls += ' is-invalid'; - } else if (focused) { - cls += ' is-highlighted' - } - return cls; - } - }); -</script><dom-module id=paper-input-error assetpath="../bower_components/paper-input/"><template><style> - :host { - display: inline-block; - visibility: hidden; - - color: var(--paper-input-container-invalid-color, --google-red-500); - - @apply(--paper-font-caption); - @apply(--paper-input-error); - position: absolute; - left:0; - right:0; - } - - :host([invalid]) { - visibility: visible; - }; - </style><content></content></template></dom-module><script> - Polymer({ - is: 'paper-input-error', - - behaviors: [ - Polymer.PaperInputAddonBehavior - ], - - properties: { - /** - * True if the error is showing. - */ - invalid: { - readOnly: true, - reflectToAttribute: true, - type: Boolean - } - }, - - update: function(state) { - this._setInvalid(state.invalid); - } - }); -</script><dom-module id=paper-textarea assetpath="../bower_components/paper-input/"><template><style> - :host { - display: block; - } - </style><paper-input-container no-label-float$=[[noLabelFloat]] always-float-label=[[_computeAlwaysFloatLabel(alwaysFloatLabel,placeholder)]] auto-validate$=[[autoValidate]] disabled$=[[disabled]] invalid=[[invalid]]><label hidden$=[[!label]]>[[label]]</label><iron-autogrow-textarea id=input class=paper-input-input bind-value={{value}} disabled$=[[disabled]] autocomplete$=[[autocomplete]] autofocus$=[[autofocus]] inputmode$=[[inputmode]] name$=[[name]] placeholder$=[[placeholder]] readonly$=[[readonly]] required$=[[required]] maxlength$=[[maxlength]] autocapitalize$=[[autocapitalize]] rows$=[[rows]] max-rows$=[[maxRows]] on-change=_onChange></iron-autogrow-textarea><template is=dom-if if=[[errorMessage]]><paper-input-error>[[errorMessage]]</paper-input-error></template><template is=dom-if if=[[charCounter]]><paper-input-char-counter></paper-input-char-counter></template></paper-input-container></template></dom-module><script> - Polymer({ - is: 'paper-textarea', - - behaviors: [ - Polymer.PaperInputBehavior - ], - - properties: { - _ariaLabelledBy: { - observer: '_ariaLabelledByChanged', - type: String - }, - - _ariaDescribedBy: { - observer: '_ariaDescribedByChanged', - type: String - }, - - /** - * The initial number of rows. - * - * @attribute rows - * @type number - * @default 1 - */ - rows: { - type: Number, - value: 1 - }, - - /** - * The maximum number of rows this element can grow to until it - * scrolls. 0 means no maximum. - * - * @attribute maxRows - * @type number - * @default 0 - */ - maxRows: { - type: Number, - value: 0 - } - }, - - _ariaLabelledByChanged: function(ariaLabelledBy) { - this.$.input.textarea.setAttribute('aria-labelledby', ariaLabelledBy); - }, - - _ariaDescribedByChanged: function(ariaDescribedBy) { - this.$.input.textarea.setAttribute('aria-describedby', ariaDescribedBy); - }, - - get _focusableElement() { - return this.$.input.textarea; - }, - }); -</script><dom-module id=sound-notification assetpath="sound-notification/"><template><audio id=blop preload=auto autobuffer=true><source src=/sounds/blop.mp3 id=mp3Source type=audio/mpeg><source src=/sounds/blop.ogg id=oggSource type=audio/ogg></audio></template></dom-module><script> -'use strict'; -Polymer({ - is: 'sound-notification', - properties: { - volumes: { - value: { - 'blop': 0.8, - } - } - }, - attached: function() { - // mobiles don't like autoplay - the first play must be triggert by user interaction - var that = this; - var hackListener = function() { - that.volumes.blop = 0.1; - that.play(); - document.body.removeEventListener('touchstart', hackListener, false); - that.volumes.blop = 0.8; - }; - document.body.addEventListener('touchstart', hackListener, false); - }, - play: function() { - this._play('blop'); - }, - _play: function(sound) { - var audio = this.$[sound]; - if (!audio) { - console.warn('audio ', sound, ' doesn\'t exist.'); - return; - } - - if (audio.readyState > 0) { - audio.volume = this.volumes[sound]; - audio.pause(); - audio.currentTime = 0; - audio.play(); - } else { - console.warn('audio not ready yet...'); - //play when ready - //TODO: play only if ready within next ~500ms - audio.addEventListener('loadedmetadata', function() { - this._play(sound); - }.bind(this), false); - } - } -}); -</script><dom-module id=text-input-dialog assetpath="text-sharing/"><template><style> - :host { - display: block; - } - - #sendDialog, - #receiveDialog { - width: 324px; - z-index: 101; - max-height: 320px; - overflow: hidden; - margin: 16px; - } - - @media all and (max-height: 600px) { - #sendDialog { - padding-top: 24px; - top: 0px !important; - } - } - - #receivedText { - word-break: break-all; - word-break: break-word; - } - - paper-textarea { - max-height: 200px; - width: calc(100% - 48px); - overflow-x: hidden; - overflow-y: auto; - } - - #receivedText { - max-height: 200px; - overflow: hidden; - width: calc(100% - 48px); - text-overflow: ellipsis; - -webkit-line-clamp: 9; - clamp: 9; - } - </style><paper-dialog id=sendDialog entry-animation=scale-up-animation exit-animation=fade-out-animation with-backdrop modal><h2>Send Text</h2><paper-textarea id=textInput label="Enter Text" value={{textToSend}} autofocus></paper-textarea><div class=buttons><paper-button dialog-dismiss>Discard</paper-button><paper-button dialog-dismiss on-tap=_send>Send</paper-button></div></paper-dialog><paper-dialog id=receiveDialog entry-animation=scale-up-animation exit-animation=fade-out-animation with-backdrop modal><h2>Text Received</h2><div><div id=receivedText></div></div><div class=buttons><paper-button dialog-dismiss>Close</paper-button><paper-button on-tap=_copy autofocus hidden$={{!clipboardSupported}}>Copy</paper-button><a href=tel:{{tel}} hidden$={{!tel}}><paper-button autofocus dialog-dismiss>Call</paper-button></a> <a href={{url}} hidden$={{!url}} target=_blank><paper-button autofocus dialog-dismiss>Open</paper-button></a></div></paper-dialog></template><script> - 'use strict'; - (function() { - /* - * - * /^\+?[0-9x]*$/ is the first usuful Text sent via Snapdrop 2015/1/2 5:30 - * - */ - var phoneNumbers = /^\+?[0-9x/ ]*$/; - var urls = /((([A-Za-z]{3,9}:(?:\/\/)?)(?:[\-;:&=\+\$,\w]+@)?[A-Za-z0-9\.\-]+|(?:www\.|[\-;:&=\+\$,\w]+@)[A-Za-z0-9\.\-]+)((?:\/[\+~%\/\.\w\-_]*)?\??(?:[\-\+=&;%@\.\w_]*)#?(?:[\.\!\/\\\w]*))?)/; - - Polymer({ - is: 'text-input-dialog', - behaviors: [Chat.ClipboardBehavior, Chat.SoundNotificationBehavior], - properties: { - textToSend: { - type: String - }, - receivedText: { - type: String - }, - contact: { - type: Object - }, - tel: { - computed: '_isPhoneNumber(receivedText)', - value: false - }, - url: { - computed: '_isUrl(receivedText)', - value: false - }, - clipboardSupported: { - value: false - }, - fallback: { - computed: '_isFallback(url,tel,clipboardSupported)', - value: false - } - }, - open: function(contact) { - this.contact = contact; - this.$.sendDialog.open(); - }, - attached: function() { - // clipboard must be initalized by user interaction - var that = this; - var hackListener = function() { - document.body.removeEventListener('touchstart', hackListener, false); - document.body.removeEventListener('click', hackListener, false); - // wait 1s to tell the ui that copy is supported - that.async(function() { - that.clipboardSupported = document.queryCommandSupported && document.queryCommandSupported('copy'); - }, 1000); - }; - document.body.addEventListener('touchstart', hackListener, false); - document.body.addEventListener('click', hackListener, false); - - - this.async(function() { - app.conn.addEventListener('text-received', function(e) { - var receivedText = e.detail.text; - if (!receivedText || receivedText.trim() === '') { - this.playSound(); - return; - } - this.receivedText = receivedText; - this.$.receivedText.textContent = receivedText; - window.linkifyElement(this.$.receivedText, {}, document); - this.$.receiveDialog.open(); - this.playSound(); - }.bind(this), false); - }, 200); - - this.$.textInput.addEventListener('keypress', function(e) { - if (e.which === 13 || e.charCode === 13) { - var key; - var isShift; - if (window.event) { - key = window.event.keyCode; - isShift = !!window.event.shiftKey; // typecast to boolean - } else { - key = e.which; - isShift = !!e.shiftKey; - } - if (!isShift) { - e.preventDefault(); - e.stopPropagation(); - this._send(); - } - } - }.bind(this), false); - - - }, - _send: function() { - this.$.sendDialog.close(); - app.conn.sendText(this.contact.peerId, this.textToSend); - }, - _copy: function() { - this.copyToClipboard(this.receivedText); - - this.$.receiveDialog.close(); - console.log('text copied', this.receivedText); - }, - _isPhoneNumber: function(text) { - if (!text || text.length < 5 || text.length > 100) { - return false; - } - if (phoneNumbers.test(text)) { - return text; - } - - }, - _isUrl: function(text) { - if (!text) { - return false; - } - if (urls.test(text)) { - return text; - } - - }, - _isFallback: function(url, tel, clipboardSupported) { - return (!url && !tel && !clipboardSupported); - } - }); - }()); - </script></dom-module><dom-module id=buddy-avatar assetpath="buddy-finder/"><template><style> - :host { - display: block; - @apply(--layout-vertical); - @apply(--layout-center-center); - width: 120px; - height: 124px; - cursor: pointer; - } - - paper-icon-button { - display: inline-block; - width: 64px !important; - height: 64px !important; - border-radius: 50%; - overflow: hidden; - padding: 12px; - margin-bottom: 6px; - background-color: #4285f4; - color: white; - } - - :host:hover paper-icon-button { - transform: scale(1.05); - } - - .paper-font-subhead { - text-align: center; - margin-top: 0px; - line-height: 18px; - } - - .paper-font-body1 { - text-align: center; - width: 100%; - font-size: 13px; - color: grey; - margin-top: 0px !important; - line-height: 16px; - } - - :host, - .paper-font-subhead, - .paper-font-body1 { - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - width: 120px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - - @media all and (min-height: 440px) { - :host([only]) { - padding: 20vh; - } - } - - .container { - @apply(--layout-vertical); - @apply(--layout-center); - height: 112px; - padding-top: 16px; - display: block; - } - </style><div class=container><paper-icon-button icon={{_displayIcon}}></paper-icon-button><div class=paper-font-subhead>{{_displayName}}</div><div class=paper-font-body1>{{status}}</div></div></template><script> - 'use strict'; - Polymer({ - is: 'buddy-avatar', - behaviors: [Chat.FileInputBehavior, Chat.TextInputBehavior], - properties: { - contact: Object, - _displayName: { - computed: '_computeDisplayName(contact)' - }, - _displayIcon: { - computed: '_computeDisplayIcon(contact)' - }, - status: { - type: String, - value: '', - }, - defaultStatus: { - computed: '_computeDefaultStatus(contact)' - } - }, - _computeDisplayName: function(contact) { - if (!contact.name.os) { - return contact.name; - } - return this._computeDeviceName(contact.name); - }, - _computeDeviceName: function(contact) { - if (contact.model) { - return contact.os + ' ' + contact.model; - } - contact.os = contact.os.replace('Mac OS', 'Mac'); - return contact.os + ' ' + contact.browser; - }, - _computeDisplayIcon: function(contact) { - contact = contact.device || contact.name; - if (contact.type === 'mobile') { - return 'chat:phone-iphone'; - } - if (contact.type === 'tablet') { - return 'chat:tablet-mac'; - } - return 'chat:desktop-mac'; - }, - _computeDefaultStatus: function(contact) { - var status = contact.device ? this._computeDeviceName(contact.device) : ''; - this.status = status; - return status; - }, - attached: function() { - // var adsDialog = document.querySelector('#ads'); - this.async(function() { - app.conn.addEventListener('file-offered', function(e) { - if (e.detail.to === this.contact.peerId) { - this.status = 'Waiting to accept...'; - //adsDialog.open(); - } - }.bind(this), false); - app.conn.addEventListener('upload-started', function(e) { - if (e.detail.to === this.contact.peerId) { - this.status = 'Uploading...'; - // adsDialog.setState('Uploading...'); - } - }.bind(this), false); - app.conn.addEventListener('download-started', function(e) { - if (e.detail.from === this.contact.peerId) { - this.status = 'Downloading...'; - //adsDialog.open(); - // adsDialog.setState('Downloading...'); - } - }.bind(this), false); - app.conn.addEventListener('upload-complete', function(e) { - if (e.detail.from === this.contact.peerId) { - this.status = this.defaultStatus; - adsDialog.setState('Transfer Complete'); - // adsDialog.close(); - } - }.bind(this), false); - app.conn.addEventListener('download-complete', function(e) { - if (e.detail.from === this.contact.peerId) { - this.status = this.defaultStatus; - adsDialog.setState('Transfer Complete'); - // adsDialog.close(); - } - }.bind(this), false); - app.conn.addEventListener('file-declined', function(e) { - if (e.detail.from === this.contact.peerId) { - this.status = this.defaultStatus; - // adsDialog.close(true); - } - }.bind(this), false); - app.conn.addEventListener('upload-error', function(e) { - this.status = this.defaultStatus; - // adsDialog.close(); - }.bind(this), false); - }, 200); - } - }); - </script></dom-module><dom-module id=paper-input assetpath="../bower_components/paper-input/"><template><style> - :host { - display: block; - } - - input::-webkit-input-placeholder { - color: var(--paper-input-container-color, --secondary-text-color); - } - - input:-moz-placeholder { - color: var(--paper-input-container-color, --secondary-text-color); - } - - input::-moz-placeholder { - color: var(--paper-input-container-color, --secondary-text-color); - } - - input:-ms-input-placeholder { - color: var(--paper-input-container-color, --secondary-text-color); - } - </style><paper-input-container no-label-float=[[noLabelFloat]] always-float-label=[[_computeAlwaysFloatLabel(alwaysFloatLabel,placeholder)]] auto-validate$=[[autoValidate]] disabled$=[[disabled]] invalid=[[invalid]]><content select=[prefix]></content><label hidden$=[[!label]]>[[label]]</label> <input is=iron-input id=input aria-labelledby$=[[_ariaLabelledBy]] aria-describedby$=[[_ariaDescribedBy]] disabled$=[[disabled]] bind-value={{value}} invalid={{invalid}} prevent-invalid-input=[[preventInvalidInput]] allowed-pattern=[[allowedPattern]] validator=[[validator]] type$=[[type]] pattern$=[[pattern]] required$=[[required]] autocomplete$=[[autocomplete]] autofocus$=[[autofocus]] inputmode$=[[inputmode]] minlength$=[[minlength]] maxlength$=[[maxlength]] min$=[[min]] max$=[[max]] step$=[[step]] name$=[[name]] placeholder$=[[placeholder]] readonly$=[[readonly]] list$=[[list]] size$=[[size]] autocapitalize$=[[autocapitalize]] autocorrect$=[[autocorrect]] on-change=_onChange tabindex$=[[tabindex]] autosave$=[[autosave]] results$=[[results]] accept$=[[accept]] multiple$=[[multiple]]><content select=[suffix]></content><template is=dom-if if=[[errorMessage]]><paper-input-error>[[errorMessage]]</paper-input-error></template><template is=dom-if if=[[charCounter]]><paper-input-char-counter></paper-input-char-counter></template></paper-input-container></template></dom-module><script> - Polymer({ - is: 'paper-input', - - behaviors: [ - Polymer.IronFormElementBehavior, - Polymer.PaperInputBehavior - ] - }); -</script><dom-module id=iron-localstorage assetpath="../bower_components/iron-localstorage/"></dom-module><script> - - Polymer({ - is: 'iron-localstorage', - - properties: { - /** - * localStorage item key - */ - name: { - type: String, - value: '' - }, - /** - * The data associated with this storage. - * If set to null item will be deleted. - * @type {*} - */ - value: { - type: Object, - notify: true - }, - - /** - * If true: do not convert value to JSON on save/load - */ - useRaw: { - type: Boolean, - value: false - }, - - /** - * Value will not be saved automatically if true. You'll have to do it manually with `save()` - */ - autoSaveDisabled: { - type: Boolean, - value: false - }, - /** - * Last error encountered while saving/loading items - */ - errorMessage: { - type: String, - notify: true - }, - - /** True if value has been loaded */ - _loaded: { - type: Boolean, - value: false - } - }, - - observers: [ - '_debounceReload(name,useRaw)', - '_trySaveValue(autoSaveDisabled)', - '_trySaveValue(value.*)' - ], - - ready: function() { - this._boundHandleStorage = this._handleStorage.bind(this); - }, - - attached: function() { - window.addEventListener('storage', this._boundHandleStorage); - }, - - detached: function() { - window.removeEventListener('storage', this._boundHandleStorage); - }, - - _handleStorage: function(ev) { - if (ev.key == this.name) { - this._load(true); - } - }, - - _trySaveValue: function() { - if (this._doNotSave) { - return; - } - if (this._loaded && !this.autoSaveDisabled) { - this.debounce('save', this.save); - } - }, - - _debounceReload: function() { - this.debounce('reload', this.reload); - }, - - /** - * Loads the value again. Use if you modify - * localStorage using DOM calls, and want to - * keep this element in sync. - */ - reload: function() { - this._loaded = false; - this._load(); - }, - - /** - * loads value from local storage - * @param {boolean=} externalChange true if loading changes from a different window - */ - _load: function(externalChange) { - var v = window.localStorage.getItem(this.name); - - if (v === null) { - this._loaded = true; - this._doNotSave = true; // guard for save watchers - this.value = null; - this._doNotSave = false; - this.fire('iron-localstorage-load-empty', { externalChange: externalChange}); - } else { - if (!this.useRaw) { - try { // parse value as JSON - v = JSON.parse(v); - } catch(x) { - this.errorMessage = "Could not parse local storage value"; - console.error("could not parse local storage value", v); - v = null; - } - } - this._loaded = true; - this._doNotSave = true; - this.value = v; - this._doNotSave = false; - this.fire('iron-localstorage-load', { externalChange: externalChange}); - } - }, - - /** - * Saves the value to localStorage. Call to save if autoSaveDisabled is set. - * If `value` is null or undefined, deletes localStorage. - */ - save: function() { - var v = this.useRaw ? this.value : JSON.stringify(this.value); - try { - if (this.value === null || this.value === undefined) { - window.localStorage.removeItem(this.name); - } else { - window.localStorage.setItem(this.name, /** @type {string} */ (v)); - } - } - catch(ex) { - // Happens in Safari incognito mode, - this.errorMessage = ex.message; - console.error("localStorage could not be saved. Safari incoginito mode?", ex); - } - } - - /** - * Fired when value loads from localStorage. - * - * @event iron-localstorage-load - * @param {{externalChange:boolean}} detail - - * externalChange: true if change occured in different window. - */ - - /** - * Fired when loaded value does not exist. - * Event handler can be used to initialize default value. - * - * @event iron-localstorage-load-empty - * @param {{externalChange:boolean}} detail - - * externalChange: true if change occured in different window. - */ - }); - -</script><dom-module id=device-name-dialog assetpath="buddy-finder/"><template><style> - :host { - display: block; - } - - paper-dialog { - width: 400px; - max-width: 90% - } - </style><paper-dialog id=dialog entry-animation=scale-up-animation exit-animation=fade-out-animation with-backdrop><h2>Name this Device</h2><p><paper-input id=input value={{deviceName}} label="Name this Device" char-counter maxlength=18 on-keypress=_keyPressed autofocus></paper-input></p><div class=buttons><paper-button dialog-dismiss>Cancel</paper-button><paper-button on-tap=_save>Rename</paper-button></div></paper-dialog></template><script> - 'use strict'; - Polymer({ - is: 'device-name-dialog', - properties: { - deviceName: { - notify: true - } - }, - open: function() { - this.$.dialog.open(); - }, - _keyPressed: function(e) { - if (e.which === 13 || e.charCode === 13) { - this.$.input.inputElement.blur(); - this._save(); - } - }, - _save: function() { - this.$.dialog.close(); - this.fire('save-device-name', this.deviceName); - } - - }); - </script></dom-module><dom-module id=device-name assetpath="buddy-finder/"><template><style> - :host { - display: inline-block; - cursor: pointer; - } - - .name-label { - @apply(--paper-font-subhead); - text-align: center; - cursor: pointer; - width: 160px; - line-height: 12px !important; - } - - :root { - /* Label and underline color when the input is not focused */ - --paper-input-container-color: #333; - /* Label and underline color when the input is focused */ - --paper-input-container-focus-color: #4285f4; - /* Label and underline color when the input is invalid */ - --paper-input-container-invalid-color: red; - /* Input foreground color */ - --paper-input-container-input-color: #333; - } - - @media all and (max-height: 370px) { - :host {} - } - - paper-dialog { - width: 300px; - text-align: left; - } - </style><div class=name-label hidden$={{name}}>My Device</div><div class=name-label hidden$={{!name}}>{{name}}</div><iron-localstorage name=device-name value={{name}} iron-localstorage-load=_nameChanged></iron-localstorage></template><script> - 'use strict'; - Polymer({ - is: 'device-name', - properties: { - name: { - observer: '_nameChanged' - } - }, - open: function() { - this.deviceNameDialog.open(); - }, - _nameChanged: function(name) { - if (!name) { - return; - } - this.cancelAsync(this.timer); - this.timer = this.async(function() { - if (!app.conn.notifyServer) { - this._nameChanged(name); - return; - } - try { - this._sendNameToServer(name); - } catch (e) { - this._nameChanged(name); - } - - }, 300); - - }, - _sendNameToServer: function(name) { - app.conn.notifyServer({ - serverMsg: 'device-name', - name: name - }); - }, - _initialize: function() { - console.log('initialize name'); - }, - get deviceNameDialog() { - var deviceNameDialog = document.querySelector('device-name-dialog'); - if (!deviceNameDialog) { - deviceNameDialog = Polymer.Base.create('device-name-dialog'); - deviceNameDialog.addEventListener('save-device-name', function(e) { - this.name = e.detail; - console.log(this.name); - }.bind(this)); - document.body.appendChild(deviceNameDialog); - }; - deviceNameDialog.deviceName = this.name; - return deviceNameDialog; - } - }); - </script></dom-module><dom-module id=personal-avatar assetpath="buddy-finder/"><template><style> - :host { - @apply(--layout-vertical); - @apply(--layout-center); - width: 360px; - position: absolute; - bottom: 16px; - left: 50%; - margin-left: -180px; - z-index: 12; - cursor: pointer; - } - - :host:hover iron-icon, - :host:hover device-name { - color: #3367d6; - } - - :host:hover iron-icon { - transform: scale(1.05); - } - - iron-icon { - width: 80px; - height: 80px; - color: #4285f4; - margin-bottom: 6px; - } - - .paper-font-body1 { - font-size: 13px; - } - - .discover { - color: #4285f4; - line-height: 18px; - } - - @media all and (max-width: 370px) { - .discover { - font-size: 12px; - } - } - - @media all and (max-height: 370px) { - :host { - bottom: 8px; - } - iron-icon { - width: 32px; - height: 32px; - margin-bottom: 2px; - } - .slogan { - display: none; - } - } - </style><device-name id=deviceName></device-name><iron-icon icon=chat:wifi-tethering></iron-icon><div class="paper-font-body1 slogan">The easiest way to transfer data across devices.</div><div class="paper-font-body1 discover">Allow me to be discovered by: Everyone in this network.</div></template><script> - 'use strict'; - Polymer({ - is: 'personal-avatar', - listeners: { - 'tap': '_openDeviceNameDialog' - }, - _openDeviceNameDialog: function() { - this.$.deviceName.open(); - } - }); - </script></dom-module><dom-module id=buddy-finder assetpath="buddy-finder/"><template><style> - :host { - background-color: transparent; - @apply(--layout-fit); - @apply(--layout-horizontal); - @apply(--layout-center-center); - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - margin: 0; - --paper-tooltip: { - font-size: 14px; - background-color: #4285f4; - } - } - - .buddies { - z-index: 1; - @apply(--layout-horizontal); - @apply(--layout-center-center); - @apply(--layout-wrap); - } - - .explanation { - @apply(--paper-font-headline); - color: #4285f4; - text-align: center; - cursor: default; - } - - .short { - font-size: 14px; - line-height: 20px; - color: #727272; - } - - .short a { - color: #4285f4; - text-decoration: none; - } - - .explanation:hover a { - transform: scale(1.1); - } - - @media all and (max-width: 600px) { - .explanation { - width: 340px; - } - } - - @media all and (max-height: 440px) { - .buddies { - padding-top: 56px; - @apply(--layout-self-start); - } - } - - @media all and (max-height: 600px) { - .buddies[two-lines] { - padding-top: 56px; - @apply(--layout-self-start); - } - } - - .explanation2 { - display: none; - position: absolute; - width: 296px; - margin-left: -148px; - left: 50%; - @apply(--paper-font-title); - color: rgba(66, 133, 244, 0.65); - text-align: center; - opacity: 0; - -webkit-transition: opacity 500ms ease-out; - -moz-transition: opacity 500ms ease-out; - -o-transition: opacity 500ms ease-out; - transition: opacity 500ms ease-out; - z-index: 0; - } - - @media all and (min-height: 441px) and (max-height: 559px) { - .explanation2 { - display: block; - top: 64px; - opacity: 1; - } - } - - @media all and (min-height: 560px) { - .explanation2 { - display: block; - top: 128px; - opacity: 1; - } - } - </style><div class=explanation2 hidden$={{!_showExplanation}}>{{_clickExplanation1}} Device to send File.<br>{{_clickExplanation2}} to send a Text.</div><div class=buddies two-lines$={{twoLinesOfBuddies}}><template is=dom-repeat items={{buddies}}><buddy-avatar on-file-selected=_fileSelected only$={{!buddies.1}} contact={{item}}></buddy-avatar></template></div><div hidden$={{buddies.0}} class=explanation>Open Snapdrop on other devices to send files.<div class=short>Short link: <a href=http://yg.gl/ target=_blank>yg.gl</a></div></div><personal-avatar class=me></personal-avatar></template><script> - 'use strict'; - (function() { - var isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); - Polymer({ - is: 'buddy-finder', - properties: { - buddies: { - type: Array, - notify: true - }, - me: { - type: String, - }, - _showExplanation: { - computed: '_computeShowExplanation(buddies.length)', - value: false - }, - twoLinesOfBuddies: { - value: false - }, - _clickExplanation1: { - value: (function() { - if (isMobile) { - return 'Tap'; - } else { - return 'Click'; - } - }) - }, - _clickExplanation2: { - value: (function() { - if (isMobile) { - return 'Long Press'; - } else { - return 'Right Click'; - } - }) - } - }, - observers: [ - '_buddiesChanged(buddies.splices)' - ], - _fileSelected: function(e) { - var peerId = e.model.item.peerId; - var file = e.detail; - app.conn.sendFile(peerId, file); - //document.querySelector('#ads').open(); - }, - _computeShowExplanation: function(nBuddies) { - if (!nBuddies || nBuddies === 0) { - return false; - } - if (nBuddies < 3) { - return true; - } - }, - _buddiesChanged: function() { - var length = window.innerWidth / 120; - this.set('twoLinesOfBuddies', (this.buddies.length > length)); - } - }); - }()); - </script></dom-module><dom-module id=p2p-network assetpath="p2p-network/"><template></template><script> - 'use strict'; - Polymer({ - is: 'p2p-network', - properties: { - me: { - type: String - } - }, - attached: function() { - this._connectedPeers = {}; - this._initCallbacks = []; - this._unsendMsgs = {}; - window.onunload = window.onbeforeunload = function() { - if (!!this._peer && !this._peer.destroyed) { - this._peer.destroy(); - } - }.bind(this); - }, - initialize: function() { - clearInterval(this.reconnectTimer); - this.reconnectTimer = undefined; - var options; - if (window.debug) { - options = { - host: window.location.hostname, - port: 3002, - path: 'peerjs' - }; - } else { - options = { - host: 'snapdrop.net', - port: 443, - path: 'peerjs', - secure: true - }; - } - this._peer = new Peer(this.me, options); - this._peer.on('open', function(id) { - console.log('My peer ID is: ' + id); - this.set('me', id); - this._peerOpen = true; - this._initCallbacks.forEach(function(cb) { - cb(); - }); - }.bind(this)); - - this._peer.on('connection', this.connect.bind(this)); - this._peer.on('error', function(err) { - console.error(err); - //ugly hack to find out error type - if (err.message.indexOf('Could not connect to peer') > -1) { - delete this._connectedPeers[this.peer]; - return; - } - if (err.message.indexOf('Lost connection to server') > -1) { - this._peer.destroy(); - this._reconnect(); - return; - } - }.bind(this)); - }, - - connect: function(c) { - var peer = c.peer; - - if (c.label === 'file') { - c.on('data', function(data) { - console.log(data); - var dataView = new Uint8Array(data.file); - var dataBlob = new Blob([dataView]); - this.fire('file-received', { - from: peer, - blob: dataBlob, - name: data.name, - }); - - }.bind(this)); - } - - if (c.label === 'system') { - c.on('data', function(data) { - data.from = peer; - this.fire('system-event', data); - }.bind(this)); - } - }, - connectToPeer: (function() { - function request(requestedPeer, callback) { - return function() { - - //system messages channel - var s = this._peer.connect(requestedPeer, { - label: 'system' - }); - - s.on('open', function() { - this.connect(s); - if (callback) { - callback(); - } - }.bind(this)); - s.on('error', function(err) { - console.log(err); - if (err.message.indexOf('Connection is not open') > -1) { - console.error('Handle this error!!', err); - //this.connectToPeer(requestedPeer, callback); - } - }.bind(this)); - - //files channel - var f = this._peer.connect(requestedPeer, { - label: 'file', - reliable: true - }); - f.on('open', function() { - this.connect(f); - - }.bind(this)); - f.on('error', function(err) { - console.log(err); - }); - }; - } - return function(requestedPeer, callback) { - if (this._peer.connections[requestedPeer]) { - callback(); - return; - } - if (this._peerOpen) { - request(requestedPeer, callback).bind(this)(); - } else { - this._initCallbacks.push(request(requestedPeer, callback).bind(this)); - } - - }; - }()), - _sendFile: function(peerId, file) { - var conns = this._peer.connections[peerId]; - if (conns) { - conns.forEach(function(conn) { - if (conn.label === 'file') { - conn.send(file); - console.log('send file via WebRTC'); - } - }.bind(this)); - } - }, - _sendSystemEvent: function(peerId, msg) { - var conns = this._peer.connections[peerId]; - if (conns) { - conns.forEach(function(conn) { - if (conn.label === 'system') { - conn.send(msg); - } - }.bind(this)); - } - }, - _reconnect: function(e) { - //try to reconnect after 3s - if (!this.reconnectTimer) { - this.reconnectTimer = setInterval(this.initialize.bind(this), 3000); - } - } - }); - </script></dom-module><dom-module id=web-socket assetpath="p2p-network/"><template><style> - :host { - display: block; - } - </style></template><script> - 'use strict'; - Polymer({ - is: 'web-socket', - attached: function() { - this.init(); - }, - init: function() { - clearInterval(this.reconnectTimer); - this.reconnectTimer = undefined; - var protocol = window.location.protocol === 'https:' ? 'wss://' : 'ws://'; - var host = window.location.host; - var websocketUrl = protocol + host + '/binary'; - this.client = new BinaryClient(websocketUrl); - this.client.on('stream', function(stream, meta) { - // collect stream data - var parts = []; - stream.on('data', function(data) { - //console.log('part received', meta, data); - if (data.isSystemEvent) { - if (meta) { - data.from = meta.from; - } - this.fire('system-event', data); - } else { - parts.push(data); - } - }.bind(this)); - // when finished, set it as the background image - stream.on('end', function() { - var blob = new Blob(parts, { - type: meta.type - }); - console.log('file received', blob, meta); - this.fire('file-received', { - blob: blob, - name: meta.name, - from: meta.from - }); - }.bind(this)); - }.bind(this)); - this.client.on('open', function(e) { - console.log(e); - this.client.send({}, { - serverMsg: 'rtc-support', - rtc: window.webRTCSupported - }); - }.bind(this)); - this.client.on('error', function(e) { - this._reconnect(e); - }.bind(this)); - this.client.on('close', function(e) { - this._reconnect(e); - }.bind(this)); - }, - _sendFile: function(toPeer, file) { - console.log('send file via WebSocket', file); - this.client.send(file.file, { - name: file.file.name, - type: file.file.type, - toPeer: toPeer - }); - }, - connectToPeer: function(peer, callback) { - callback(); - }, - _sendSystemEvent: function(toPeer, event) { - console.log('system event', toPeer, event); - event.isSystemEvent = true; - this.client.send(event, { - toPeer: toPeer - }); - }, - _reconnect: function(e) { - console.log('disconnected', e); - //try to reconnect after 3s - if (!this.reconnectTimer) { - this.reconnectTimer = setInterval(this.init.bind(this), 3000); - } - } - }); - </script></dom-module><dom-module id=connection-wrapper assetpath="p2p-network/"><template><p2p-network id=p2p me={{me}}></p2p-network><web-socket id=ws me={{me}}></web-socket></template><script> - 'use strict'; - (function() { - window.webRTCSupported = !!(window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection || window.webkitRTCPeerConnection); - - function rtcConnectionSupported(peerId) { - return window.webRTCSupported && (peerId.indexOf('rtc_') === 0); - } - Polymer({ - is: 'connection-wrapper', - properties: { - me: { - notify: true - }, - }, - behaviors: [Chat.FileTransferProtocol], - _sendFile: function(toPeer, file) { - if (!rtcConnectionSupported(toPeer)) { - this.$.ws._sendFile(toPeer, file); - } else { - this.$.p2p._sendFile(toPeer, file); - } - }, - _sendSystemEvent: function(toPeer, event) { - console.log('system event', toPeer, event); - if (!rtcConnectionSupported(toPeer)) { - this.$.ws._sendSystemEvent(toPeer, event); - } else { - this.$.p2p._sendSystemEvent(toPeer, event); - } - }, - connectToPeer: function(toPeer, callback) { - if (!rtcConnectionSupported(toPeer)) { - callback(); - } else { - this.$.p2p.connectToPeer(toPeer, callback); - } - }, - _onHandshake: function(event) { - var me = event.uuid; - this.set('me', me); - if (window.webRTCSupported) { - this.$.p2p.initialize(); - } - }, - notifyServer: function(msg) { - this.$.ws.client.send({}, msg); - } - }); - })(); - </script></dom-module><dom-module id=file-receiver assetpath="file-sharing/"><template><style> - :host { - display: block; - } - - #dialog, - #download { - width: 324px; - z-index: 101; - margin: 16px; - } - - .filename { - word-break: break-all; - word-break: break-word; - } - </style><paper-dialog id=dialog entry-animation=scale-up-animation exit-animation=fade-out-animation with-backdrop modal><h2>Download File</h2><p><b class=filename>{{file.name}}</b></p><div class=buttons><paper-button dialog-dismiss on-tap=_decline>Ignore</paper-button><paper-button dialog-confirm on-tap=_accept autofocus>Download</paper-button></div></paper-dialog><paper-dialog id=download entry-animation=scale-up-animation exit-animation=fade-out-animation with-backdrop modal><h2>File Received</h2><p>Open File or Right Click and "Save as"...</p><div class=buttons><paper-button dialog-dismiss>Discard</paper-button><a href={{dataUri}} target=_blank><paper-button dialog-confirm autofocus>Open File</paper-button></a></div></paper-dialog></template><script> - 'use strict'; - (function() { - Polymer({ - is: 'file-receiver', - behaviors: [Chat.SoundNotificationBehavior], - attached: function() { - this.async(function() { - app.conn.addEventListener('file-offer', function(e) { - this.file = e.detail; - this.$.dialog.open(); - this.playSound(); - }.bind(this), false); - app.conn.addEventListener('file-received', function(e) { - this._fileReceived(e.detail); - }.bind(this), false); - app.conn.addEventListener('file-declined', function(e) { - app.displayToast('User declined file ' + e.detail.name); - }.bind(this), false); - app.conn.addEventListener('upload-complete', function(e) { - app.displayToast('User received file ' + e.detail.name); - }.bind(this), false); - app.conn.addEventListener('upload-error', function(e) { - app.displayToast('The other device did not respond. Please try again.'); - }.bind(this), false); - }, 200); - }, - _fileReceived: function(file) { - this.downloadURI(file); - }, - _decline: function() { - app.conn.decline(this.file); - }, - _accept: function() { - app.conn.accept(this.file); - }, - downloadURI: function(file) { - var link = document.createElement('a'); - var uri = (window.URL || window.webkitURL).createObjectURL(file.blob); - if (typeof link.download !== 'undefined') { - //download attribute is supported - link.href = uri; - link.download = file.name || 'blank'; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - } else { - this.dataUri = uri; - this.$.download.open(); - } - } - }); - }()); - </script></dom-module><dom-module id=donate-dialog-impl assetpath="donate-dialog/"><template><style> - :host { - display: block; - } - - paper-dialog { - max-width: 324px; - } - - iron-icon { - width: 72px; - height: 72px; - display: block; - margin: 0 auto; - margin-bottom: 4px; - color: #4285f4; - } - - p { - padding: 0 48px; - margin: 4px; - @apply(--paper-font-subhead); - } - - paper-dialog > a { - text-decoration: none; - color: #52524F; - text-align: center; - } - - @media screen and (max-width: 500px) { - paper-dialog { - margin: 24px - } - } - </style><paper-dialog id=dialog with-backdrop entry-animation=scale-up-animation exit-animation=fade-out-animation modal><h2>Do you like Snapdrop?</h2><a href="https://paypal.me/RobinLinus" target=_blank dialog-confirm><p><iron-icon icon=chat:local-cafe></iron-icon><span>Donate a cup of Coffee<br>to the Developers!</span></p></a><div class=buttons><paper-button dialog-dismiss>No, thanks.</paper-button><a href="https://paypal.me/RobinLinus" target=_blank><paper-button dialog-confirm>Donate</paper-button></a></div></paper-dialog></template><script> - 'use strict'; - Polymer({ - is: 'donate-dialog-impl', - open: function() { - this.$.dialog.open(); - } - }); - </script></dom-module><dom-module id=donate-dialog assetpath="donate-dialog/"><script> - 'use strict'; - Polymer({ - is: 'donate-dialog', - properties: { - chance: { - value: 1 / 6 - } - }, - attached: function() { - window.donateDialog = this; - }, - get dialog() { - var dialog = document.querySelector('donate-dialog-impl'); - if (!dialog) { - dialog = Polymer.Base.create('donate-dialog-impl'); - document.body.appendChild(dialog); - } - return dialog; - }, - open: function() { - var chance = Math.random() <= this.chance; - if (chance) { - this.dialog.open(); - } - } - }); - </script></dom-module></div></body></html> diff --git a/dist/public/index.html b/dist/public/index.html @@ -1,18 +0,0 @@ -<!doctype html><html lang="en"><head> -<meta charset="utf-8"><meta name="viewport" content="width=device-width initial-scale=1.0, maximum-scale=1.0, user-scalable=no"><meta name="generator" content="Snapdrop"><title>Snapdrop</title><link rel="shortcut icon" href="favicon.ico?v=3"><meta name="theme-color" content="#3367d6"><link rel="manifest" href="manifest.json"><meta name="msapplication-TileColor" content="#3372DF"><meta name="mobile-web-app-capable" content="yes"><meta name="application-name" content="PSK"><link rel="icon" sizes="192x192" href="images/touch/chrome-touch-icon-192x192.png"><link rel="fluid-icon" type="image/png" href="images/touch/chrome-touch-icon-192x192.png"><meta name="description" content="Snapdrop is an easy way to transfer files. Instantly share images, video, PDF, and links across devices. Peer2Peer, Private, Secure and Open Source. No Setup, No Signup."><meta property="og:image" content="https://snapdrop.net/images/touch/chrome-splashscreen-icon-384x384.png"><meta property="og:url" content="https://snapdrop.net/"><meta name="twitter:image" content="https://snapdrop.net/images/touch/chrome-splashscreen-icon-384x384.png"><meta name="twitter:author" content="@RobinLinus"><meta property="og:type" content="article"><meta property="og:author" content="https://facebook.com/RobinLinus"><meta property="fb:pages" content="451189218422617"><meta property="fb:profile_id" content="451189218422617"><meta name="twitter:description" content="Snapdrop is an easy way to transfer files. Instantly share images, video, PDF, and links across devices. Peer2Peer, Private, Secure and Open Source. No Setup, No Signup."><meta name="apple-mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-status-bar-style" content="black"><meta name="apple-mobile-web-app-title" content="Snapdrop"><link rel="apple-touch-icon" href="images/touch/apple-touch-icon.png"><meta name="msapplication-TileImage" content="images/touch/ms-touch-icon-144x144-precomposed.png"><link rel="stylesheet" href="styles/main.css"><script src="bower_components/webcomponentsjs/webcomponents-lite.min.js" async="" foo="1"></script><link rel="import" href="elements/elements.html" async="true"></head><body class="layout vertical"> -<script>"use strict";!function(){function n(){u=window.innerWidth,m=window.innerHeight,a.width=u,a.height=m;var n=m>370?100:65;w=u/2,d=m-n,c=Math.max(u,m,1e3)/13,i()}function t(n){s.beginPath();var t=Math.round(255*(1-n/Math.max(u,m)));s.strokeStyle="rgba("+t+","+t+","+t+",0.1)",s.arc(w,d,n,0,2*Math.PI),s.stroke(),s.lineWidth=2}function i(){s.clearRect(0,0,u,m);for(var n=0;8>n;n++)t(c*n+h%c);h+=1}function e(){(f||c-5>h%c)&&o(function(){i(),e()})}var o=function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(n){window.setTimeout(n,1e3/60)}}(),a=document.createElement("canvas");document.body.appendChild(a);var r=a.style;r.width="100%",r.position="absolute";var w,d,u,m,c,s=a.getContext("2d");window.onresize=n;var h=0,f=!0;window.anim=function(n){f=n,e()},n(),e()}();</script><span id="browser-sync-binding"></span><template is="dom-bind" id="app"><connection-wrapper me="{{me}}" loading="{{loading}}" buddies="{{buddies}}"></connection-wrapper><neon-animated-pages id="pages" selected="0"><x-cards on-switch="_showAbout"><div><paper-progress indeterminate="" hidden$="{{!loading}}"></paper-progress><buddy-finder me="{{me}}" active$="{{loading}}" buddies="{{buddies}}"></buddy-finder></div></x-cards><about-page on-switch="_showApp"></about-page></neon-animated-pages><file-receiver></file-receiver><paper-toast id="toast" duration="6000"></paper-toast><paper-toast id="caching-complete" duration="6000" text="Caching complete! This app will work offline."></paper-toast><donate-dialog></donate-dialog><platinum-sw-register auto-register="" clients-claim="" skip-waiting="" base-uri="bower_components/platinum-sw/bootstrap" on-service-worker-installed="displayInstalledToast"><platinum-sw-cache default-cache-strategy="fastest" cache-config-file="cache-config.json"></platinum-sw-cache></platinum-sw-register></template><script src="scripts/app.js"></script><script> - (function(i, s, o, g, r, a, m) { - i['GoogleAnalyticsObject'] = r; - i[r] = i[r] || function() { - (i[r].q = i[r].q || []).push(arguments) - }, i[r].l = 1 * new Date(); - a = s.createElement(o), - m = s.getElementsByTagName(o)[0]; - a.async = 1; - a.src = g; - m.parentNode.insertBefore(a, m) - })(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga'); - - ga('create', 'UA-71686975-1', 'auto'); - ga('send', 'pageview'); - </script></body></html> diff --git a/dist/public/manifest.json b/dist/public/manifest.json @@ -1,29 +0,0 @@ -{ - "name": "Snapdrop", - "short_name": "Snapdrop", - "icons": [{ - "src": "images/touch/icon-128x128.png", - "sizes": "128x128", - "type": "image/png" - }, { - "src": "images/touch/apple-touch-icon.png", - "sizes": "152x152", - "type": "image/png" - }, { - "src": "images/touch/ms-touch-icon-144x144-precomposed.png", - "sizes": "144x144", - "type": "image/png" - }, { - "src": "images/touch/chrome-touch-icon-192x192.png", - "sizes": "192x192", - "type": "image/png" - }, { - "src": "images/touch/chrome-splashscreen-icon-384x384.png", - "sizes": "384x384", - "type": "image/png" - }], - "background_color": "#3367d6", - "start_url": "index.html", - "display": "standalone", - "theme_color": "#3367d6" -} diff --git a/dist/public/scripts/app.js b/dist/public/scripts/app.js @@ -1 +0,0 @@ -!function(e){"use strict";var o=e.querySelector("#app");o.baseUrl="/",""===window.location.port,o.displayInstalledToast=function(){Polymer.dom(e).querySelector("platinum-sw-cache").disabled||Polymer.dom(e).querySelector("#caching-complete").show()},o.displayToast=function(o){var t=Polymer.dom(e).querySelector("#toast");t.text=o,t.show()},o.addEventListener("dom-change",function(){console.log("Our app is ready to rock!"),o.conn=e.querySelector("connection-wrapper")}),window.addEventListener("WebComponentsReady",function(){}),o._showAbout=function(){e.querySelector("#pages").select(1)},o._showAbout=function(){e.querySelector("#pages").select(0)}}(document); -\ No newline at end of file diff --git a/dist/public/sounds/blop.mp3 b/dist/public/sounds/blop.mp3 Binary files differ. diff --git a/dist/public/sounds/blop.ogg b/dist/public/sounds/blop.ogg Binary files differ. diff --git a/dist/public/styles/main.css b/dist/public/styles/main.css @@ -1 +0,0 @@ -body,html{height:100%;width:100%;padding:0;margin:0}body{background:#fafafa;font-family:Roboto,'Helvetica Neue',Helvetica,Arial,sans-serif;color:#333;-webkit-font-smoothing:antialiased;overflow-x:hidden}#ads,#ads2{display:none}@media screen and (min-width:520px){#ads{display:block;position:absolute;top:8px;left:50%;margin-left:-150px}}@media screen and (min-width:720px){#ads{display:none}#ads2{display:block;position:absolute;bottom:4px;left:4px}} -\ No newline at end of file diff --git a/dist/readme.md b/dist/readme.md @@ -1,5 +0,0 @@ -# Run a Snapdrop Server -- `npm install` -- `node index.js` -- TODO: SSL connection (i.e nginx) - - ( Please do a PR if you've build an alternative index.js with a self-signed cert ) -\ No newline at end of file diff --git a/dist/server/ws-server.js b/dist/server/ws-server.js @@ -1,154 +0,0 @@ -'use strict'; -var parser = require('ua-parser-js'); - -// Start Binary.js server -var BinaryServer = require('binaryjs').BinaryServer; - -exports.create = function(server) { - - // link it to express - var bs = BinaryServer({ - server: server, - path: '/binary' - }); - - function guid() { - function s4() { - return Math.floor((1 + Math.random()) * 0x10000) - .toString(16) - .substring(1); - } - return s4() + s4() + '-' + s4() + '-' + s4() + '-' + - s4() + '-' + s4() + s4() + s4(); - } - - function getDeviceName(req) { - var ua = parser(req.headers['user-agent']); - return { - model: ua.device.model, - os: ua.os.name, - browser: ua.browser.name, - type: ua.device.type - }; - } - - function hash(text) { - // A string hashing function based on Daniel J. Bernstein's popular 'times 33' hash algorithm. - var h = 5381, - index = text.length; - while (index) { - h = (h * 33) ^ text.charCodeAt(--index); - } - return h >>> 0; - } - - function getIP(socket) { - return socket.upgradeReq.headers['x-forwarded-for'] || socket.upgradeReq.connection.remoteAddress; - } - // Wait for new user connections - bs.on('connection', function(client) { - - client.uuidRaw = guid(); - //ip is hashed to prevent injections by spoofing the 'x-forwarded-for' header - // client.hashedIp = 1; //use this to test locally - client.hashedIp = hash(getIP(client._socket)); - - client.deviceName = getDeviceName(client._socket.upgradeReq); - - // Incoming stream from browsers - client.on('stream', function(stream, meta) { - if (meta && meta.serverMsg === 'rtc-support') { - client.uuid = (meta.rtc ? 'rtc_' : '') + client.uuidRaw; - client.send({ - isSystemEvent: true, - type: 'handshake', - name: client.deviceName, - uuid: client.uuid - }); - return; - } - if (meta && meta.serverMsg === 'device-name') { - //max name length = 40 - if (meta.name && meta.name.length > 40) { - return; - } - client.name = meta.name; - return; - } - - meta.from = client.uuid; - - // broadcast to the other client - for (var id in bs.clients) { - if (bs.clients.hasOwnProperty(id)) { - var otherClient = bs.clients[id]; - if (otherClient !== client && meta.toPeer === otherClient.uuid) { - var send = otherClient.createStream(meta); - stream.pipe(send, meta); - } - } - } - }); - }); - - function forEachClient(fn) { - for (var id in bs.clients) { - if (bs.clients.hasOwnProperty(id)) { - var client = bs.clients[id]; - fn(client); - } - } - } - - - - - - function notifyBuddies() { - var locations = {}; - //group all clients by location (by public ip address) - forEachClient(function(client) { - var ip = client.hashedIp; - locations[ip] = locations[ip] || []; - locations[ip].push({ - socket: client, - contact: { - peerId: client.uuid, - name: client.name || client.deviceName, - device: client.name ? client.deviceName : undefined - } - }); - }); - //notify every location - Object.keys(locations).forEach(function(locationKey) { - //notify every client of all other clients in this location - var location = locations[locationKey]; - location.forEach(function(client) { - //all other clients - var buddies = location.reduce(function(result, otherClient) { - if (otherClient !== client) { - result.push(otherClient.contact); - } - return result; - }, []); - var currState = hash(JSON.stringify(buddies)); - console.log(currState); - var socket = client.socket; - //protocol - var msg = { - buddies: buddies, - isSystemEvent: true, - type: 'buddies' - }; - //send only if state changed - if (currState !== socket.lastState) { - socket.send(msg); - socket.lastState = currState; - return; - } - }); - }); - } - - setInterval(notifyBuddies, 3000); -}; diff --git a/gulpfile.js b/gulpfile.js @@ -1,359 +0,0 @@ -'use strict'; - -// Include Gulp & tools we'll use -var autoprefixer = require('gulp-autoprefixer'); -var useref = require('gulp-useref'); -var vulcanize = require('vulcanize'); -var size = require('gulp-size'); -var gulp = require('gulp'); -var ghPages = require('gulp-gh-pages'); -var gulpIf = require('gulp-if'); -var jscs = require('gulp-jscs'); -var jscsStylish = require('gulp-jscs-stylish'); -var htmlExtract = require('gulp-html-extract'); -var imagemin = require('gulp-imagemin'); -var cleanCSS = require('gulp-clean-css'); -var changed = require('gulp-changed'); -var del = require('del'); -var uglify = require('gulp-uglify'); -var jshint = require('gulp-jshint'); -var runSequence = require('run-sequence'); -var browserSync = require('browser-sync'); -var reload = browserSync.reload; -var merge = require('merge-stream'); -var path = require('path'); -var fs = require('fs'); -var glob = require('glob-all'); -var historyApiFallback = require('connect-history-api-fallback'); -var packageJson = require('./package.json'); -var crypto = require('crypto'); -var ensureFiles = require('./tasks/ensure-files.js'); -var inlinesource = require('gulp-inline-source'); -var proxy = require('proxy-middleware'); -var url = require('url'); -var minifyHTML = require('gulp-htmlmin'); -var replace = require('gulp-replace'); - -// var ghPages = require('gulp-gh-pages'); - -var AUTOPREFIXER_BROWSERS = [ - 'ie >= 10', - 'ie_mob >= 10', - 'ff >= 30', - 'chrome >= 34', - 'safari >= 7', - 'opera >= 23', - 'ios >= 7', - 'android >= 4.4', - 'bb >= 10' -]; - -var DIST = 'dist'; - -var dist = function(subpath) { - return !subpath ? DIST : path.join(DIST, subpath); -}; - -var styleTask = function(stylesPath, srcs) { - return gulp.src(srcs.map(function(src) { - return path.join('app', stylesPath, src); - })) - .pipe(changed(stylesPath, { - extension: '.css' - })) - .pipe(autoprefixer(AUTOPREFIXER_BROWSERS)) - .pipe(gulp.dest('.tmp/' + stylesPath)) - .pipe(cleanCSS()) - .pipe(gulp.dest(dist(stylesPath))) - .pipe(size({ - title: stylesPath - })); -}; - -var imageOptimizeTask = function(src, dest) { - return gulp.src(src) - .pipe(imagemin({ - progressive: true, - interlaced: true - })) - .pipe(gulp.dest(dest)) - .pipe(size({ - title: 'images' - })); -}; - -var optimizeHtmlTask = function(src, dest) { - var assets = useref.assets({ - searchPath: ['.tmp', 'app'] - }); - - return gulp.src(src) - .pipe(assets) - // Concatenate and minify JavaScript - .pipe(gulpIf('*.js', uglify({ - preserveComments: 'some' - }))) - // Concatenate and minify styles - // In case you are still using useref build blocks - .pipe(gulpIf('*.css', cleanCSS())) - .pipe(assets.restore()) - .pipe(useref()) - // Minify any HTML - .pipe(gulpIf('*.html', minifyHTML({ - quotes: true, - empty: true, - spare: true - }))) - .pipe(gulpIf('*.html', inlinesource())) - .pipe(replace('window.debug = true;', '')) - // Output files - .pipe(gulp.dest(dest)) - .pipe(size({ - title: 'html' - })); -}; - -// Compile and automatically prefix stylesheets -gulp.task('styles', function() { - return styleTask('styles', ['**/*.css']); -}); - -gulp.task('elements', function() { - return styleTask('elements', ['**/*.css']); -}); - -// Ensure that we are not missing required files for the project -// "dot" files are specifically tricky due to them being hidden on -// some systems. -gulp.task('ensureFiles', function(cb) { - var requiredFiles = ['.jscsrc', '.jshintrc', '.bowerrc']; - - ensureFiles(requiredFiles.map(function(p) { - return path.join(__dirname, p); - }), cb); -}); - -// Lint JavaScript -gulp.task('lint', ['ensureFiles'], function() { - return gulp.src([ - 'app/scripts/**/*.js', - 'app/elements/**/*.js', - 'app/elements/**/*.html', - 'gulpfile.js' - ]) - .pipe(reload({ - stream: true, - once: true - })) - - // JSCS has not yet a extract option - .pipe(gulpIf('*.html', htmlExtract())) - .pipe(jshint()) - .pipe(jscs()) - .pipe(jscsStylish.combineWithHintResults()) - .pipe(jshint.reporter('jshint-stylish')) - .pipe(gulpIf(!browserSync.active, jshint.reporter('fail'))); -}); - -// Optimize images -gulp.task('images', function() { - return imageOptimizeTask('app/images/**/*', dist('images')); -}); - -// Copy all files at the root level (app) -gulp.task('copy', function() { - var app = gulp.src([ - 'app/*', - '!app/test', - '!app/elements', - '!app/bower_components', - '!app/cache-config.json' - ], { - dot: true - }).pipe(gulp.dest(dist())); - - // Copy over only the bower_components we need - // These are things which cannot be vulcanized - var bower = gulp.src([ - 'app/bower_components/{webcomponentsjs,platinum-sw,sw-toolbox,promise-polyfill}/**/*' - ]).pipe(gulp.dest(dist('bower_components'))); - - return merge(app, bower) - .pipe(size({ - title: 'copy' - })); -}); - -// Copy web fonts to dist -gulp.task('fonts', function() { - return gulp.src(['app/fonts/**']) - .pipe(gulp.dest(dist('fonts'))) - .pipe(size({ - title: 'fonts' - })); -}); - -// Scan your HTML for assets & optimize them -gulp.task('html', function() { - return optimizeHtmlTask( - ['app/**/*.html', '!app/{elements,test,bower_components}/**/*.html'], - dist()); -}); - -// Vulcanize granular configuration -gulp.task('vulcanize', function() { - return gulp.src('app/elements/elements.html') - .pipe(vulcanize({ - stripComments: true, - stripExclude:['app/bower_components/font-roboto/roboto.html'], - inlineCss: true, - inlineScripts: true - })) - .pipe(minifyHTML({ - empty: true - })) - .pipe(gulp.dest(dist('elements'))) - .pipe(size({ - title: 'vulcanize' - })); -}); - -// Generate config data for the <sw-precache-cache> element. -// This include a list of files that should be precached, as well as a (hopefully unique) cache -// id that ensure that multiple PSK projects don't share the same Cache Storage. -// This task does not run by default, but if you are interested in using service worker caching -// in your project, please enable it within the 'default' task. -// See https://github.com/PolymerElements/polymer-starter-kit#enable-service-worker-support -// for more context. -gulp.task('cache-config', function(callback) { - var dir = dist(); - var config = { - cacheId: packageJson.name || path.basename(__dirname), - disabled: false - }; - - glob([ - 'index.html', - './', - 'bower_components/webcomponentsjs/webcomponents-lite.min.js', - '{elements,scripts,styles}/**/*.*' - ], { - cwd: dir - }, function(error, files) { - if (error) { - callback(error); - } else { - config.precache = files; - - var md5 = crypto.createHash('md5'); - md5.update(JSON.stringify(config.precache)); - config.precacheFingerprint = md5.digest('hex'); - - var configPath = path.join(dir, 'cache-config.json'); - fs.writeFile(configPath, JSON.stringify(config), callback); - } - }); -}); - -// Clean output directory -gulp.task('clean', function() { - return del(['.tmp', dist()]); -}); - -// Watch files for changes & reload -gulp.task('serve', ['styles', 'elements', 'images'], function() { - var peerjsProxy = url.parse('http://localhost:3002/peerjs'); - peerjsProxy.route = '/peerjs'; - var websocketProxy = url.parse('http://localhost:3002/binary'); - websocketProxy.route = '/binary'; - browserSync({ - port: 5000, - notify: false, - logPrefix: 'PSK', - ghostMode: false, - snippetOptions: { - rule: { - match: '<span id="browser-sync-binding"></span>', - fn: function(snippet) { - return snippet; - } - } - }, - // Run as an https by uncommenting 'https: true' - // Note: this uses an unsigned certificate which on first access - // will present a certificate warning in the browser. - // https: true, - server: { - baseDir: ['.tmp', 'app'], - middleware: [proxy(peerjsProxy), proxy(websocketProxy), historyApiFallback()] - } - }); - - gulp.watch(['app/**/*.html'], reload); - gulp.watch(['app/styles/**/*.css'], ['styles', reload]); - gulp.watch(['app/elements/**/*.css'], ['elements', reload]); - gulp.watch(['app/{scripts,elements}/**/{*.js,*.html}'], ['lint']); - gulp.watch(['app/images/**/*'], reload); -}); - -// Build and serve the output from the dist build -gulp.task('serve:dist', ['default'], function() { - browserSync({ - port: 5001, - notify: false, - logPrefix: 'PSK', - snippetOptions: { - rule: { - match: '<span id="browser-sync-binding"></span>', - fn: function(snippet) { - return snippet; - } - } - }, - // Run as an https by uncommenting 'https: true' - // Note: this uses an unsigned certificate which on first access - // will present a certificate warning in the browser. - // https: true, - server: dist(), - middleware: [historyApiFallback()] - }); -}); - -// Build production files, the default task -gulp.task('default', ['clean'], function(cb) { - // Uncomment 'cache-config' if you are going to use service workers. - runSequence( - ['copy', 'styles'], - 'elements', ['images', 'fonts', 'html'], //'lint', - 'vulcanize', 'cache-config', - cb); -}); - -// Build then deploy to GitHub pages gh-pages branch -gulp.task('build-deploy-gh-pages', function(cb) { - runSequence( - 'default', - 'deploy-gh-pages', - cb); -}); - -// Deploy to GitHub pages gh-pages branch -gulp.task('deploy-gh-pages', function() { - return gulp.src(dist('**/*')) - // Check if running task from Travis CI, if so run using GH_TOKEN - // otherwise run using ghPages defaults. - .pipe(gulpIf(process.env.TRAVIS === 'true', ghPages({ - remoteUrl: 'https://$GH_TOKEN@github.com/polymerelements/polymer-starter-kit.git', - silent: true, - branch: 'gh-pages' - }), ghPages())); -}); - -// Load tasks for web-component-tester -// Adds tasks for `gulp test:local` and `gulp test:remote` -require('web-component-tester').gulp.init(gulp); - -// Load custom tasks from the `tasks` directory -try { - require('require-dir')('tasks'); -} catch (err) {} diff --git a/index.js b/index.js @@ -1,23 +0,0 @@ -'use strict'; -var express = require('express'); -var compression = require('compression'); -var app = express(); -var http = require('http'); -var ExpressPeerServer = require('peer').ExpressPeerServer; -var wsServer = require('./server/ws-server.js'); - -var server = http.createServer(app); - -// Serve up content from public directory -app.use(compression()); -app.use(express.static(__dirname + '/public')); - -var port = process.env.PORT || 3002; -server.listen(port); -wsServer.create(server); -app.use('/peerjs', ExpressPeerServer(server, { - debug: true -})); - - -console.log('listening on port ' + port); diff --git a/nginx.conf.example b/nginx.conf.example @@ -0,0 +1,60 @@ +# This is an configuration example. Please adjust to fit your environment (especially root location and server_name). +# The nginx user requires read permissions to the root location. + +user nginx; +worker_processes auto; +error_log /var/log/nginx/error.log; +pid /run/nginx.pid; + +# Load dynamic modules. See /usr/share/nginx/README.dynamic. +include /usr/share/nginx/modules/*.conf; + +events { + worker_connections 1024; +} + +http { + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # Load modular configuration files from the /etc/nginx/conf.d directory. + # See http://nginx.org/en/docs/ngx_core_module.html#include + # for more information. + include /etc/nginx/conf.d/*.conf; + + server { + server_name your.domain; + root /path/to/secret-snapdrop/client; + + # Load configuration files for the default server block. + include /etc/nginx/default.d/*.conf; + + location /server { + proxy_pass http://localhost:3000/; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header X-Forwarded-For $remote_addr; + } + + location / { + proxy_http_version 1.1; + } + + + listen [::]:80 ; + listen 80 ; +} +} diff --git a/package.json b/package.json @@ -1,54 +0,0 @@ -{ - "private": true, - "devDependencies": { - "browser-sync": "^2.10.1", - "connect-history-api-fallback": "^1.1.0", - "del": "^2.0.2", - "glob-all": "^3.0.1", - "gulp": "^3.9.0", - "gulp-autoprefixer": "^3.1.0", - "gulp-cache": "^0.4.0", - "gulp-changed": "^1.0.0", - "gulp-clean-css": "^2.0.13", - "gulp-gh-pages": "^0.5.4", - "gulp-html-extract": "^0.0.3", - "gulp-htmlmin": "^3.0.0", - "gulp-if": "^2.0.0", - "gulp-imagemin": "^2.2.1", - "gulp-inline-source": "^2.1.0", - "gulp-jscs": "^3.0.0", - "gulp-jscs-stylish": "^1.1.2", - "gulp-jshint": "^1.6.3", - "gulp-load-plugins": "^1.1.0", - "gulp-rename": "^1.2.0", - "gulp-replace": "^0.5.4", - "gulp-size": "^2.0.0", - "gulp-uglify": "^1.2.0", - "gulp-useref": "^2.1.0", - "gulp-vulcanize": "^6.0.0", - "jshint-stylish": "^2.0.0", - "merge-stream": "^1.0.0", - "proxy-middleware": "^0.15.0", - "require-dir": "^0.3.0", - "run-sequence": "^1.0.2", - "url": "^0.11.0", - "vulcanize": ">= 1.4.2", - "web-component-tester": "^4.0.0" - }, - "scripts": { - "test": "gulp test:local", - "start": "gulp serve", - "lint": "gulp lint" - }, - "engines": { - "node": ">=0.10.0" - }, - "dependencies": { - "binaryjs": "^0.2.1", - "compression": "^1.6.0", - "express": "^4.13.3", - "peer": "^0.2.8", - "ua-parser-js": "^0.7.10", - "ws": "^1.1.1" - } -} diff --git a/server/index.js b/server/index.js @@ -0,0 +1,217 @@ +const parser = require('ua-parser-js'); + +class SnapdropServer { + + constructor(port) { + const WebSocket = require('ws'); + this._wss = new WebSocket.Server({ + port: port + }); + this._wss.on('connection', (socket, request) => this._onConnection(new Peer(socket, request))); + this._wss.on('headers', (headers, response) => this._onHeaders(headers, response)); + + this._rooms = {}; + this._timerID = 0; + + console.log('Snapdrop is running on port', port); + } + + _onConnection(peer) { + this._joinRoom(peer); + peer.socket.on('message', message => this._onMessage(peer, message)); + this._keepAlive(peer); + } + + _onHeaders(headers, response) { + if (response.headers.cookie && response.headers.cookie.indexOf('peerid=') > -1) return; + response.peerId = Peer.uuid(); + headers.push('Set-Cookie: peerid=' + response.peerId); + } + + _onMessage(sender, message) { + message = JSON.parse(message); + + switch (message.type) { + case 'disconnect': + this._leaveRoom(sender); + case 'pong': + sender.lastBeat = Date.now(); + } + + // relay message to recipient + if (message.to) { + const recipientId = message.to; // TODO: sanitize + const recipient = this._rooms[sender.ip][recipientId]; + delete message.to; + // add sender id + message.sender = sender.id; + this._send(recipient, message); + return; + } + } + + _joinRoom(peer) { + // if room doesn't exist, create it + if (!this._rooms[peer.ip]) { + this._rooms[peer.ip] = {}; + } + + // console.log(peer.id, ' joined the room', peer.ip); + // notify all other peers + for (const otherPeerId in this._rooms[peer.ip]) { + const otherPeer = this._rooms[peer.ip][otherPeerId]; + this._send(otherPeer, { + type: 'peer-joined', + peer: peer.getInfo() + }); + } + + // notify peer about the other peers + const otherPeers = []; + for (const otherPeerId in this._rooms[peer.ip]) { + otherPeers.push(this._rooms[peer.ip][otherPeerId].getInfo()); + } + + this._send(peer, { + type: 'peers', + peers: otherPeers + }); + + // add peer to room + this._rooms[peer.ip][peer.id] = peer; + } + + _leaveRoom(peer) { + // delete the peer + this._cancelKeepAlive(peer); + if (!this._rooms[peer.ip]) return; + + delete this._rooms[peer.ip][peer.id]; + + peer.socket.terminate(); + //if room is empty, delete the room + if (!Object.keys(this._rooms[peer.ip]).length) { + delete this._rooms[peer.ip]; + } else { + // notify all other peers + for (const otherPeerId in this._rooms[peer.ip]) { + const otherPeer = this._rooms[peer.ip][otherPeerId]; + this._send(otherPeer, { + type: 'peer-left', + peerId: peer.id + }); + } + } + } + + _send(peer, message) { + if (!peer) return console.error('undefined peer'); + message = JSON.stringify(message); + peer.socket.send(message, error => { + if (error) this._leaveRoom(peer); + }); + } + + _keepAlive(peer) { + var timeout = 10000; + // console.log(Date.now() - peer.lastBeat); + if (Date.now() - peer.lastBeat > 2 * timeout) { + this._leaveRoom(peer); + return; + } + + if (this._wss.readyState == this._wss.OPEN) { + this._send(peer, { + type: 'ping' + }); + } + peer.timerId = setTimeout(() => this._keepAlive(peer), timeout); + } + + _cancelKeepAlive(peer) { + if (peer.timerId) { + clearTimeout(peer.timerId); + } + } +} + + + +class Peer { + + constructor(socket, request) { + // set socket + this.socket = socket; + + + // set remote ip + if (request.headers['x-forwarded-for']) + this.ip = request.headers['x-forwarded-for'].split(/\s*,\s*/)[0]; + else + this.ip = request.connection.remoteAddress; + + if (request.peerId) { + this.id = request.peerId; + } else { + this.id = request.headers.cookie.replace('peerid=', ''); + } + // set peer id + // is WebRTC supported ? + this.rtcSupported = request.url.indexOf('webrtc') > -1; + // set name + this.setName(request); + // for keepalive + this.timerId = 0; + this.lastBeat = Date.now(); + } + + // return uuid of form xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx + static uuid() { + let uuid = '', + ii; + for (ii = 0; ii < 32; ii += 1) { + switch (ii) { + case 8: + case 20: + uuid += '-'; + uuid += (Math.random() * 16 | 0).toString(16); + break; + case 12: + uuid += '-'; + uuid += '4'; + break; + case 16: + uuid += '-'; + uuid += (Math.random() * 4 | 8).toString(16); + break; + default: + uuid += (Math.random() * 16 | 0).toString(16); + } + } + return uuid; + }; + + toString() { + return `<Peer id=${this.id} ip=${this.ip} rtcSupported=${this.rtcSupported}>` + } + + setName(req) { + var ua = parser(req.headers['user-agent']); + this.name = { + model: ua.device.model, + os: ua.os.name, + browser: ua.browser.name, + type: ua.device.type + }; + } + + getInfo() { + return { + id: this.id, + name: this.name, + rtcSupported: this.rtcSupported + } + } +} + +const server = new SnapdropServer(process.env.PORT || 3000); +\ No newline at end of file diff --git a/server/package-lock.json b/server/package-lock.json @@ -0,0 +1,26 @@ +{ + "name": "snapdrop", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "async-limiter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", + "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" + }, + "ua-parser-js": { + "version": "0.7.18", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.18.tgz", + "integrity": "sha512-LtzwHlVHwFGTptfNSgezHp7WUlwiqb0gA9AALRbKaERfxwJoiX0A73QbTToxteIAuIaFshhgIZfqK8s7clqgnA==" + }, + "ws": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.0.0.tgz", + "integrity": "sha512-c2UlYcAZp1VS8AORtpq6y4RJIkJ9dQz18W32SpR/qXGfLDZ2jU4y4wKvvZwqbi7U6gxFQTeE+urMbXU/tsDy4w==", + "requires": { + "async-limiter": "~1.0.0" + } + } + } +} diff --git a/server/package.json b/server/package.json @@ -0,0 +1,15 @@ +{ + "name": "snapdrop", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + "ua-parser-js": "^0.7.18", + "ws": "^6.0.0" + } +} diff --git a/server/ws-server.js b/server/ws-server.js @@ -1,154 +0,0 @@ -'use strict'; -var parser = require('ua-parser-js'); - -// Start Binary.js server -var BinaryServer = require('binaryjs').BinaryServer; - -exports.create = function(server) { - - // link it to express - var bs = BinaryServer({ - server: server, - path: '/binary' - }); - - function guid() { - function s4() { - return Math.floor((1 + Math.random()) * 0x10000) - .toString(16) - .substring(1); - } - return s4() + s4() + '-' + s4() + '-' + s4() + '-' + - s4() + '-' + s4() + s4() + s4(); - } - - function getDeviceName(req) { - var ua = parser(req.headers['user-agent']); - return { - model: ua.device.model, - os: ua.os.name, - browser: ua.browser.name, - type: ua.device.type - }; - } - - function hash(text) { - // A string hashing function based on Daniel J. Bernstein's popular 'times 33' hash algorithm. - var h = 5381, - index = text.length; - while (index) { - h = (h * 33) ^ text.charCodeAt(--index); - } - return h >>> 0; - } - - function getIP(socket) { - return socket.upgradeReq.headers['x-forwarded-for'] || socket.upgradeReq.connection.remoteAddress; - } - // Wait for new user connections - bs.on('connection', function(client) { - - client.uuidRaw = guid(); - //ip is hashed to prevent injections by spoofing the 'x-forwarded-for' header - // client.hashedIp = 1; //use this to test locally - client.hashedIp = hash(getIP(client._socket)); - - client.deviceName = getDeviceName(client._socket.upgradeReq); - - // Incoming stream from browsers - client.on('stream', function(stream, meta) { - if (meta && meta.serverMsg === 'rtc-support') { - client.uuid = (meta.rtc ? 'rtc_' : '') + client.uuidRaw; - client.send({ - isSystemEvent: true, - type: 'handshake', - name: client.deviceName, - uuid: client.uuid - }); - return; - } - if (meta && meta.serverMsg === 'device-name') { - //max name length = 40 - if (meta.name && meta.name.length > 40) { - return; - } - client.name = meta.name; - return; - } - - meta.from = client.uuid; - - // broadcast to the other client - for (var id in bs.clients) { - if (bs.clients.hasOwnProperty(id)) { - var otherClient = bs.clients[id]; - if (otherClient !== client && meta.toPeer === otherClient.uuid) { - var send = otherClient.createStream(meta); - stream.pipe(send, meta); - } - } - } - }); - }); - - function forEachClient(fn) { - for (var id in bs.clients) { - if (bs.clients.hasOwnProperty(id)) { - var client = bs.clients[id]; - fn(client); - } - } - } - - - - - - function notifyBuddies() { - var locations = {}; - //group all clients by location (by public ip address) - forEachClient(function(client) { - var ip = client.hashedIp; - locations[ip] = locations[ip] || []; - locations[ip].push({ - socket: client, - contact: { - peerId: client.uuid, - name: client.name || client.deviceName, - device: client.name ? client.deviceName : undefined - } - }); - }); - //notify every location - Object.keys(locations).forEach(function(locationKey) { - //notify every client of all other clients in this location - var location = locations[locationKey]; - location.forEach(function(client) { - //all other clients - var buddies = location.reduce(function(result, otherClient) { - if (otherClient !== client) { - result.push(otherClient.contact); - } - return result; - }, []); - var currState = hash(JSON.stringify(buddies)); - console.log(currState); - var socket = client.socket; - //protocol - var msg = { - buddies: buddies, - isSystemEvent: true, - type: 'buddies' - }; - //send only if state changed - if (currState !== socket.lastState) { - socket.send(msg); - socket.lastState = currState; - return; - } - }); - }); - } - - setInterval(notifyBuddies, 3000); -}; diff --git a/tasks/ensure-files.js b/tasks/ensure-files.js @@ -1,34 +0,0 @@ -var fs = require('fs'); - -/** - * @param {Array<string>} files - * @param {Function} cb - */ - -function ensureFiles(files, cb) { - var missingFiles = files.reduce(function(prev, filePath) { - var fileFound = false; - - try { - fileFound = fs.statSync(filePath).isFile(); - } catch (e) { } - - if (!fileFound) { - prev.push(filePath + ' Not Found'); - } - - return prev; - }, []); - - if (missingFiles.length) { - var err = new Error('Missing Required Files\n' + missingFiles.join('\n')); - } - - if (cb) { - cb(err); - } else if (err) { - throw err; - } -} - -module.exports = ensureFiles; diff --git a/wct.conf.js b/wct.conf.js @@ -1,18 +0,0 @@ -var path = require('path'); - -var ret = { - 'suites': ['app/test'], - 'webserver': { - 'pathMappings': [] - } -}; - -var mapping = {}; -var rootPath = (__dirname).split(path.sep).slice(-1)[0]; - -mapping['/components/' + rootPath + -'/app/bower_components'] = 'bower_components'; - -ret.webserver.pathMappings.push(mapping); - -module.exports = ret;