snapdrop

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

commit 0551df6c401aad6e6cbf6f697b37e43a00269890
parent dce88a309d7cd2fee3d90a15ff4ce73d3220dc2f
Author: RobinLinus <robin_woll@capira.de>
Date:   Wed,  8 Feb 2017 01:41:04 +0100

Commit a dist folder

Diffstat:
M.gitignore | 1-
Adist/index.js | 23+++++++++++++++++++++++
Adist/package.json | 14++++++++++++++
Adist/public/elements/elements.html | 22672+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adist/public/index.html | 18++++++++++++++++++
Adist/public/manifest.json | 29+++++++++++++++++++++++++++++
Adist/public/scripts/app.js | 2++
Adist/public/sounds/blop.mp3 | 0
Adist/public/sounds/blop.ogg | 0
Adist/public/styles/main.css | 2++
Adist/readme.md | 6++++++
Adist/server/ws-server.js | 154+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
12 files changed, 22920 insertions(+), 1 deletion(-)

diff --git a/.gitignore b/.gitignore @@ -1,5 +1,4 @@ node_modules -dist bower_components .tmp .publish/ diff --git a/dist/index.js b/dist/index.js @@ -0,0 +1,23 @@ +'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 @@ -0,0 +1,14 @@ +{ + "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 @@ -0,0 +1,22671 @@ +<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){b["true"]=a;var c={},d={},e={},f=null;!function(a){function b(a){if("number"==typeof a)return a;var b={};for(var c in a)b[c]=a[c];return b}function c(){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 d(b,d){var e=new c;return d&&(e.fill="both",e.duration="auto"),"number"!=typeof b||isNaN(b)?void 0!==b&&Object.getOwnPropertyNames(b).forEach(function(c){if("auto"!=b[c]){if(("number"==typeof e[c]||"duration"==c)&&("number"!=typeof b[c]||isNaN(b[c])))return;if("fill"==c&&-1==s.indexOf(b[c]))return;if("direction"==c&&-1==t.indexOf(b[c]))return;if("playbackRate"==c&&1!==b[c]&&a.isDeprecated("AnimationEffectTiming.playbackRate","2014-11-28","Use Animation.playbackRate instead."))return;e[c]=b[c]}}):e.duration=b,e}function e(a){return"number"==typeof a&&(a=isNaN(a)?{duration:0}:{duration:a}),a}function f(b,c){b=a.numericTimingToObject(b);var e=d(b,c);return e._easing=i(e.easing),e}function g(a,b,c,d){return 0>a||a>1||0>c||c>1?B: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 h(a,b){return function(c){if(c>=1)return 1;var d=1/a;return c+=b*d,c-c%d}}function i(a){var b=z.exec(a);if(b)return g.apply(this,b.slice(1).map(Number));var c=A.exec(a);if(c)return h(Number(c[1]),{start:u,middle:v,end:w}[c[2]]);var d=x[a];return d?d:B}function j(a){return Math.abs(k(a)/a.playbackRate)}function k(a){return a.duration*a.iterations}function l(a,b,c){return null==b?C:b<c.delay?D:b>=c.delay+a?E:F}function m(a,b,c,d,e){switch(d){case D:return"backwards"==b||"both"==b?0:null;case F:return c-e;case E:return"forwards"==b||"both"==b?a:null;case C:return null}}function n(a,b,c,d){return(d.playbackRate<0?b-a:b)*d.playbackRate+c}function o(a,b,c,d,e){return 1/0===c||c===-1/0||c-d==b&&e.iterations&&(e.iterations+e.iterationStart)%1==0?a:c%a}function p(a,b,c,d){return 0===c?0:b==a?d.iterationStart+d.iterations-1:Math.floor(c/a)}function q(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.easing(h)}function r(a,b,c){var d=l(a,b,c),e=m(a,c.fill,b,d,c.delay);if(null===e)return null;if(0===a)return d===D?0:1;var f=c.iterationStart*c.duration,g=n(a,e,f,c),h=o(c.duration,k(c),g,f,c),i=p(c.duration,h,g,c);return q(i,c.duration,h,c)/c.duration}var s="backwards|forwards|both|none".split("|"),t="reverse|alternate|alternate-reverse".split("|");c.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 u=1,v=.5,w=0,x={ease:g(.25,.1,.25,1),"ease-in":g(.42,0,1,1),"ease-out":g(0,0,.58,1),"ease-in-out":g(.42,0,.58,1),"step-start":h(1,u),"step-middle":h(1,v),"step-end":h(1,w)},y="\\s*(-?\\d+\\.?\\d*|-?\\.\\d+)\\s*",z=new RegExp("cubic-bezier\\("+y+","+y+","+y+","+y+"\\)"),A=/steps\(\s*(\d+)\s*,\s*(start|middle|end)\s*\)/,B=function(a){return a},C=0,D=1,E=2,F=3;a.cloneTimingInput=b,a.makeTiming=d,a.numericTimingToObject=e,a.normalizeTimingInput=f,a.calculateActiveDuration=j,a.calculateTimeFraction=r,a.calculatePhase=l,a.toTimingFunction=i}(c,f),function(a){function b(a,b){return a in h?h[a][b]||b:b}function c(a,c,d){var g=e[a];if(g){f.style[a]=c;for(var h in g){var i=g[h],j=f.style[i];d[i]=b(i,j)}}else d[a]=b(a,c)}function d(b){function d(){var a=e.length;null==e[a-1].offset&&(e[a-1].offset=1),a>1&&null==e[0].offset&&(e[0].offset=0);for(var b=0,c=e[0].offset,d=1;a>d;d++){var f=e[d].offset;if(null!=f){for(var g=1;d-b>g;g++)e[b+g].offset=c+(f-c)*g/(d-b);b=d,c=f}}}if(!Array.isArray(b)&&null!==b)throw new TypeError("Keyframes must be null or an array of keyframes");if(null==b)return[];for(var e=b.map(function(b){var d={};for(var e in b){var f=b[e];if("offset"==e){if(null!=f&&(f=Number(f),!isFinite(f)))throw new TypeError("keyframe offsets must be numbers.")}else{if("composite"==e)throw{type:DOMException.NOT_SUPPORTED_ERR,name:"NotSupportedError",message:"add compositing is not supported"};f="easing"==e?a.toTimingFunction(f):""+f}c(e,f,d)}return void 0==d.offset&&(d.offset=null),void 0==d.easing&&(d.easing=a.toTimingFunction("linear")),d}),f=!0,g=-1/0,h=0;h<e.length;h++){var i=e[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 e=e.filter(function(a){return a.offset>=0&&a.offset<=1}),f||d(),e}var e={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"]},f=document.createElementNS("http://www.w3.org/1999/xhtml","div"),g={thin:"1px",medium:"3px",thick:"5px"},h={borderBottomWidth:g,borderLeftWidth:g,borderRightWidth:g,borderTopWidth:g,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:g,textShadow:{none:"0px 0px 0px transparent"},boxShadow:{none:"0px 0px 0px 0px transparent"}};a.normalizeKeyframes=d}(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){function c(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 d(a){var c=[];for(var d in a)for(var e=a[d],f=0;f<e.length-1;f++){var g=e[f].offset,h=e[f+1].offset,i=e[f].value,j=e[f+1].value;g==h&&(1==h?i=j:j=i),c.push({startTime:g,endTime:h,easing:e[f].easing,property:d,interpolation:b.propertyInterpolation(d,i,j)})}return c.sort(function(a,b){return a.startTime-b.startTime}),c}b.convertEffectInput=function(e){var f=a.normalizeKeyframes(e),g=c(f),h=d(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){function b(a,b,c){e[c]=e[c]||[],e[c].push([a,b])}function c(a,c,d){for(var e=0;e<d.length;e++){var f=d[e];b(a,c,f),/-/.test(f)&&b(a,c,f.replace(/-(.)/g,function(a,b){return b.toUpperCase()}))}}function d(b,c,d){if("initial"==c||"initial"==d){var g=b.replace(/-(.)/g,function(a,b){return b.toUpperCase()});"initial"==c&&(c=f[g]),"initial"==d&&(d=f[g])}for(var h=c==d?[]:e[b],i=0;h&&i<h.length;i++){var j=h[i][0](c),k=h[i][0](d);if(void 0!==j&&void 0!==k){var l=h[i][1](j,k);if(l){var m=a.Interpolation.apply(null,l);return function(a){return 0==a?c:1==a?d:m(a)}}}}return a.Interpolation(!1,!0,function(a){return a?d:c})}var e={};a.addPropertiesHandler=c;var f={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"};a.propertyInterpolation=d}(d,f),function(a,b){function c(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(d,e,f){var g,h=c(a.normalizeTimingInput(f)),i=b.convertEffectInput(e),j=function(){i(d,g)};return j._update=function(a){return g=h(a),null!==g},j._clear=function(){i(d,null)},j._hasSameTarget=function(a){return d===a},j._isCurrent=h._isCurrent,j._totalDuration=h._totalDuration,j},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){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){return a.timeline._play(a.KeyframeEffect(this,b,c))}}(d),function(a){function b(a,c,d){if("number"==typeof a&&"number"==typeof c)return a*(1-d)+c*d;if("boolean"==typeof a&&"boolean"==typeof c)return.5>d?a:c;if(a.length==c.length){for(var e=[],f=0;f<a.length;f++)e.push(b(a[f],c[f],d));return e}throw"Mismatched interpolation arguments "+a+":"+c}a.Interpolation=function(a,c,d){return function(e){return d(b(a,c,e))}}}(d,f),function(a,b){a.sequenceNumber=0;var c=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._sequenceNumber=a.sequenceNumber++,this._currentTime=0,this._startTime=null,this._paused=!1,this._playbackRate=1,this._inTimeline=!0,this._finishedFlag=!1,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._inEffect=this._effect._update(this.playbackRate<0&&0===this.currentTime?-1: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,b.invalidateEffects()),this._finishedFlag=!1,b.restart(),this._idle=!1,this._ensureAlive()},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)},cancel:function(){this._inEffect&&(this._inEffect=!1,this._idle=!0,this.currentTime=0,this._startTime=null,this._effect._update(null),b.invalidateEffects(),b.restart())},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){var b=this._isFinished;if((b||this._idle)&&!this._finishedFlag){var d=new c(this,this._currentTime,a),e=this._finishHandlers.concat(this.onfinish?[this.onfinish]:[]);setTimeout(function(){e.forEach(function(a){a.call(d.target,d)})},0)}this._finishedFlag=b},_tick:function(a){return this._idle||this._paused||(null==this._startTime?this.startTime=a-this._currentTime/this.playbackRate:this._isFinished||this._tickCurrentTime((a-this._startTime)*this.playbackRate)),this._currentTimePending=!1,this._fireEvents(a),!this._idle&&(this._inEffect||!this._finishedFlag)}}}(c,d,f),function(a,b){function c(a){var b=i;i=[],a<s.currentTime&&(a=s.currentTime),g(a),b.forEach(function(b){b[1](a)}),o&&g(a),f(),l=void 0}function d(a,b){return a._sequenceNumber-b._sequenceNumber}function e(){this._animations=[],this.currentTime=window.performance&&performance.now?performance.now():0}function f(){p.forEach(function(a){a()}),p.length=0}function g(a){n=!1;var c=b.timeline;c.currentTime=a,c._animations.sort(d),m=!1;var e=c._animations;c._animations=[];var f=[],g=[];e=e.filter(function(b){return b._inTimeline=b._tick(a),b._inEffect?g.push(b._effect):f.push(b._effect),b._isFinished||b._paused||b._idle||(m=!0),b._inTimeline}),p.push.apply(p,f),p.push.apply(p,g),c._animations.push.apply(c._animations,e),o=!1,m&&requestAnimationFrame(function(){})}var h=window.requestAnimationFrame,i=[],j=0;window.requestAnimationFrame=function(a){var b=j++;return 0==i.length&&h(c),i.push([b,a]),b},window.cancelAnimationFrame=function(a){i.forEach(function(b){b[0]==a&&(b[1]=function(){})})},e.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 k,l=void 0,k=function(){return void 0==l&&(l=performance.now()),l},m=!1,n=!1;b.restart=function(){return m||(m=!0,requestAnimationFrame(function(){}),n=!0),n};var o=!1;b.invalidateEffects=function(){o=!0};var p=[],q=1e3/60,r=window.getComputedStyle;Object.defineProperty(window,"getComputedStyle",{configurable:!0,enumerable:!0,value:function(){if(o){var a=k();a-s.currentTime>0&&(s.currentTime+=q*(Math.floor((a-s.currentTime)/q)+1)),g(s.currentTime)}return f(),r.apply(this,arguments)}});var s=new e;b.timeline=s}(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){function b(a){return a.toFixed(3).replace(".000","")}function c(a,b,c){return Math.min(b,Math.max(a,c))}function d(a){return/^\s*[-+]?(\d*\.)?\d+\s*$/.test(a)?Number(a):void 0}function e(a,c){return[a,c,b]}function f(a,b){return 0!=a?h(0,1/0)(a,b):void 0}function g(a,b){return[a,b,function(a){return Math.round(c(1,1/0,a))}]}function h(a,d){return function(e,f){return[e,f,function(e){return b(c(a,d,e))}]}}function i(a,b){return[a,b,Math.round]}a.clamp=c,a.addPropertiesHandler(d,h(0,1/0),["border-image-width","line-height"]),a.addPropertiesHandler(d,h(0,1),["opacity","shape-image-threshold"]),a.addPropertiesHandler(d,f,["flex-grow","flex-shrink"]),a.addPropertiesHandler(d,g,["orphans","widows"]),a.addPropertiesHandler(d,i,["z-index"]),a.parseNumber=d,a.mergeNumbers=e,a.numberToString=b}(d,f),function(a){function b(a,b){return"visible"==a||"visible"==b?[0,1,function(c){return 0>=c?a:c>=1?b:"visible"}]:void 0}a.addPropertiesHandler(String,b,["visibility"])}(d),function(a){function b(a){a=a.trim(),e.fillStyle="#000",e.fillStyle=a;var b=e.fillStyle;if(e.fillStyle="#fff",e.fillStyle=a,b==e.fillStyle){e.fillRect(0,0,1,1);var c=e.getImageData(0,0,1,1).data;e.clearRect(0,0,1,1);var d=c[3]/255;return[c[0]*d,c[1]*d,c[2]*d,d]}}function c(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 d=document.createElementNS("http://www.w3.org/1999/xhtml","canvas");d.width=d.height=1;var e=d.getContext("2d");a.addPropertiesHandler(b,c,["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,b),a.mergeColors=c}(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){function b(b){return a.consumeLengthOrPercent(b)||a.consumeToken(/^auto/,b)}function c(c){var d=a.consumeList([a.ignore(a.consumeToken.bind(null,/^rect/)),a.ignore(a.consumeToken.bind(null,/^\(/)),a.consumeRepeated.bind(null,b,/^,/),a.ignore(a.consumeToken.bind(null,/^\)/))],c);return d&&4==d[0].length?d[0]:void 0}function d(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 e(a){return"rect("+a+")"}var f=a.mergeWrappedNestedRepeated.bind(null,e,d,", ");a.parseBox=c,a.mergeBoxes=f,a.addPropertiesHandler(c,f,["clip"])}(d,f),function(a){function b(a){return function(b){var c=0;return a.map(function(a){return a===j?b[c++]:a})}}function c(a){return a}function d(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=m[g];if(!h)return;var i=c[2].split(","),j=h[0];if(j.length<i.length)return;for(var n=[],o=0;o<j.length;o++){var p,q=i[o],r=j[o];if(p=q?{A:function(b){return"0"==b.trim()?l:a.parseAngle(b)},N:a.parseNumber,T:a.parseLengthOrPercent,L:a.parseLength}[r.toUpperCase()](q):{a:l,n:n[0],t:k}[r],void 0===p)return;n.push(p)}if(e.push({t:g,d:n}),d.lastIndex==b.length)return e}}function e(a){return a.toFixed(6).replace(".000000","")}function f(b,c){if(b.decompositionPair!==c){b.decompositionPair=c;var d=a.makeMatrixDecomposition(b)}if(c.decompositionPair!==b){c.decompositionPair=b;var f=a.makeMatrixDecomposition(c)}return null==d[0]||null==f[0]?[[!1],[!0],function(a){return a?c[0].d:b[0].d}]:(d[0].push(0),f[0].push(1),[d,f,function(b){var c=a.quat(d[0][3],f[0][3],b[5]),g=a.composeMatrix(b[0],b[1],b[2],c,b[4]),h=g.map(e).join(",");return h}])}function g(a){return a.replace(/[xy]/,"")}function h(a){return a.replace(/(x|y|z|3d)?$/,"3d")}function i(b,c){var d=a.makeMatrixDecomposition&&!0,e=!1;if(!b.length||!c.length){b.length||(e=!0,b=c,c=[]);for(var i=0;i<b.length;i++){var j=b[i].t,k=b[i].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 n=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=f(b,c);o=[r[0]],p=[r[1]],q=[["matrix",[r[2]]]]}else for(var i=0;i<b.length;i++){var j,s=b[i].t,t=c[i].t,u=b[i].d,v=c[i].d,w=m[s],x=m[t];if(n(s,t)){if(!d)return;var r=f([b[i]],[c[i]]);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]&&g(s)==g(t))j=g(s),u=w[2](u),v=x[2](v);else{if(!w[1]||!x[1]||h(s)!=h(t)){if(!d)return;var r=f(b,c);o=[r[0]],p=[r[1]],q=[["matrix",[r[2]]]];break}j=h(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 j=null,k={px:0},l={deg:0},m={matrix:["NNNNNN",[j,j,0,0,j,j,0,0,0,0,1,0,j,j,0,1],c],matrix3d:["NNNNNNNNNNNNNNNN",c],rotate:["A"],rotatex:["A"],rotatey:["A"],rotatez:["A"],rotate3d:["NNNA"],perspective:["L"],scale:["Nn",b([j,j,1]),c],scalex:["N",b([j,1,1]),b([j,1])],scaley:["N",b([1,j,1]),b([1,j])],scalez:["N",b([1,1,j])],scale3d:["NNN",c],skew:["Aa",null,c],skewx:["A",null,b([j,l])],skewy:["A",null,b([l,j])],translate:["Tt",b([j,j,k]),c],translatex:["T",b([j,k,k]),b([j,k])],translatey:["T",b([k,j,k]),b([k,j])],translatez:["L",b([k,k,j])],translate3d:["TTL",c]};a.addPropertiesHandler(d,i,["transform"])}(d,f),function(a){function b(a,b){b.concat([a]).forEach(function(b){b in document.documentElement.style&&(c[a]=b)})}var c={};b("transform",["webkitTransform","msTransform"]),b("transformOrigin",["webkitTransformOrigin"]),b("perspective",["webkitPerspective"]),b("perspectiveOrigin",["webkitPerspectiveOrigin"]),a.propertyName=function(a){return c[a]||a}}(d,f)}(),!function(a,b){function c(a){var b=window.document.timeline;b.currentTime=a,b._discardAnimations(),0==b._animations.length?e=!1:requestAnimationFrame(c)}var d=window.requestAnimationFrame;window.requestAnimationFrame=function(a){return d(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 e=!1;b.restartWebAnimationsNextTick=function(){e||(e=!0,requestAnimationFrame(c))};var f=new b.AnimationTimeline;b.timeline=f;try{Object.defineProperty(window.document,"timeline",{configurable:!0,get:function(){return f}})}catch(g){}try{window.document.timeline=f}catch(g){}}(c,e,f),function(a,b){b.animationsWithPromises=[],b.Animation=function(b,c){if(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?!0:!1;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._onfinish},set onfinish(a){"function"==typeof a?(this._onfinish=a,this._animation.onfinish=function(b){b.target=this,a.call(this,b)}.bind(this)):(this._animation.onfinish=a,this.onfinish=this._animation.onfinish)},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){function c(b){this._frames=a.normalizeKeyframes(b)}function d(){for(var a=!1;h.length;){var b=h.shift();b._updateChildren(),a=!0}return a}var e=function(a){if(a._animation=void 0,a instanceof window.SequenceEffect||a instanceof window.GroupEffect)for(var b=0;b<a.children.length;b++)e(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,e(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(),e(d))}for(c=0;c<b.length;c++)b[c]._rebuild()},b.KeyframeEffect=function(b,d,e){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 d?(a.deprecated("Custom KeyframeEffect","2015-06-22","Use KeyframeEffect.onsample instead."),this._normalizedKeyframes=d):this._normalizedKeyframes=new c(d),this._keyframes=d,this.activeDuration=a.calculateActiveDuration(this._timing),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));return b._normalizedKeyframes=this._normalizedKeyframes,b._keyframes=this._keyframes,b},remove:function(){b.removeMulti([this])}};var f=Element.prototype.animate;Element.prototype.animate=function(a,c){return b.timeline._play(new b.KeyframeEffect(this,a,c))};var g=document.createElementNS("http://www.w3.org/1999/xhtml","div");b.newUnderlyingAnimationForKeyframeEffect=function(a){if(a){var b=a.target||g,c=a._keyframes;"function"==typeof c&&(c=[]);var d=a._timingInput}else var b=g,c=[],d=0;return f.apply(b,[c,d])},b.bindAnimationForKeyframeEffect=function(a){a.effect&&"function"==typeof a.effect._normalizedKeyframes&&b.bindAnimationForCustomEffect(a)};var h=[];b.awaitStartTime=function(a){null===a.startTime&&a._isGroup&&(0==h.length&&requestAnimationFrame(d),h.push(a))};var i=window.getComputedStyle;Object.defineProperty(window,"getComputedStyle",{configurable:!0,enumerable:!0,value:function(){window.document.timeline._updateAnimationsPromises();var a=i.apply(this,arguments);return d()&&(a=i.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){function c(a){a._registered||(a._registered=!0,f.push(a),g||(g=!0,requestAnimationFrame(d)))}function d(){var a=f;f=[],a.sort(function(a,b){return a._sequenceNumber-b._sequenceNumber}),a=a.filter(function(a){a();var b=a._animation?a._animation.playState:"idle";return"running"!=b&&"pending"!=b&&(a._registered=!1),a._registered}),f.push.apply(f,a),f.length?(g=!0,requestAnimationFrame(d)):g=!1}var e=(document.createElementNS("http://www.w3.org/1999/xhtml","div"),0);b.bindAnimationForCustomEffect=function(b){var d,f=b.effect.target,g="function"==typeof b.effect.getFrames();d=g?b.effect.getFrames():b.effect._onsample;var h=b.effect.timing,i=null;h=a.normalizeTimingInput(h);var j=function(){var c=j._animation?j._animation.currentTime:null;null!==c&&(c=a.calculateTimeFraction(a.calculateActiveDuration(h),c,h),isNaN(c)&&(c=null)),c!==i&&(g?d(c,f,b.effect):d(c,b.effect,b.effect._animation)),i=c};j._animation=b,j._registered=!1,j._sequenceNumber=e++,b._callback=j,c(j)};var f=[],g=!1;b.Animation.prototype._register=function(){this._callback&&c(this._callback)}}(c,e,f),function(a,b){function c(a){return a._timing.delay+a.activeDuration+a._timing.endDelay}function d(b,c){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(){d.apply(this,arguments)},window.GroupEffect=function(){d.apply(this,arguments)},d.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(d.prototype),Object.defineProperty(window.SequenceEffect.prototype,"activeDuration",{get:function(){var a=0;return this.children.forEach(function(b){a+=c(b)}),Math.max(a,0)}}),window.GroupEffect.prototype=Object.create(d.prototype),Object.defineProperty(window.GroupEffect.prototype,"activeDuration",{get:function(){var a=0;return this.children.forEach(function(b){a=Math.max(a,c(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);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=c}(c,e,f)}({},function(){return this}()); +//# sourceMappingURL=web-animations-next-lite.min.js.map</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> +\ No newline at end of file diff --git a/dist/public/index.html b/dist/public/index.html @@ -0,0 +1,18 @@ +<!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 @@ -0,0 +1,29 @@ +{ + "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 @@ -0,0 +1 @@ +!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 @@ -0,0 +1 @@ +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 @@ -0,0 +1,5 @@ +# 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 @@ -0,0 +1,154 @@ +'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); +};