Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
28
gaia/apps/music/components/bridge/.bower.json
Executable file
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"name": "bridge",
|
||||
"main": "bridge.js",
|
||||
"authors": [
|
||||
"Wilson Page <wilsonpage@me.com>"
|
||||
],
|
||||
"license": "MIT",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"README.md",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"test",
|
||||
"tests",
|
||||
"docs",
|
||||
"examples"
|
||||
],
|
||||
"homepage": "https://github.com/gaia-components/bridge",
|
||||
"_release": "807683a641",
|
||||
"_resolution": {
|
||||
"type": "branch",
|
||||
"branch": "master",
|
||||
"commit": "807683a641b316f21af7dce39cb678f8e1d12760"
|
||||
},
|
||||
"_source": "git://github.com/gaia-components/bridge.git",
|
||||
"_target": "master",
|
||||
"_originalSource": "gaia-components/bridge"
|
||||
}
|
||||
22
gaia/apps/music/components/bridge/LICENSE
Executable file
|
|
@ -0,0 +1,22 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
19
gaia/apps/music/components/bridge/bower.json
Executable file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "bridge",
|
||||
"version": "1.0.1",
|
||||
"main": "bridge.js",
|
||||
"authors": [
|
||||
"Wilson Page <wilsonpage@me.com>"
|
||||
],
|
||||
"license": "MIT",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"README.md",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"test",
|
||||
"tests",
|
||||
"docs",
|
||||
"examples"
|
||||
]
|
||||
}
|
||||
1787
gaia/apps/music/components/bridge/bridge.js
Executable file
31
gaia/apps/music/components/bridge/bridge.min.js
vendored
Executable file
|
|
@ -0,0 +1,31 @@
|
|||
(function e$$0(l,n,b){function m(h,f){if(!n[h]){if(!l[h]){var e="function"==typeof require&&require;if(!f&&e)return e(h,!0);if(p)return p(h,!0);e=Error("Cannot find module '"+h+"'");throw e.code="MODULE_NOT_FOUND",e;}e=n[h]={exports:{}};l[h][0].call(e.exports,function(c){var a=l[h][1][c];return m(a?a:c)},e,e.exports,e$$0,l,n,b)}return n[h].exports}for(var p="function"==typeof require&&require,k=0;k<b.length;k++)m(b[k]);return m})({1:[function(g,l,n){var b={service:g("../src/service"),client:g("../src/client"),
|
||||
_m:g("../src/message")};"u"!=(typeof define)[0]?define([],function(){return b}):self.bridge=b},{"../src/client":2,"../src/message":4,"../src/service":6}],2:[function(g,l,n){function b(a,d,c){if(!(this instanceof b))return new b(a,d,c);"object"==typeof a&&(d=a.endpoint,c=a.timeout,a=a.service);this.id=f();this.service=a;this.timeout=c;this.endpoint=d||this.endpoint;if(!this.endpoint)throw m(1);this.setPort(this.endpoint);this.pending=new Set;this.receiver=h.receiver(this.id).on("_push",this.onPush.bind(this));
|
||||
e("initialized",a)}function m(a,d){d=[].slice.call(arguments,1);return Error({1:"an endpoint must be defined",2:'Unable to establish a connection with "'+d[0]+'". Either the target endpoint is not alive or the Service is not `.listen()`ing.',3:'Method "'+d[0]+"\" didn't get a response. Either the target endpoint is not alive or the Service is not `.listen()`ing."}[a])}var p=g("./message/port-adaptors"),k=g("./emitter"),h=g("./message"),f=g("./utils").uuid;l.exports=b;var e={0:function(){},1:function(a){return performance.mark("["+
|
||||
self.constructor.name+"][Client] - "+a)},2:function(a,d){d=[].slice.call(arguments,1);console.log.apply(console,[].concat(["[Client]"+("["+self.constructor.name+"]["+location.pathname+"]")+' - "'+a+'"'],d))}}[0],c=constructor.name;b.prototype={connect:function(){var a=this;e("connect");if(this.connected)return this.connected;e("connecting...",this.service);var d=new MessageChannel;this.channel=d.port1;this.channel.start();var f={clientId:this.id,service:this.service,originEnv:c};return this.connected=
|
||||
this.message("_connect").set("transfer",[d.port2]).set("data",f).listen(d.port1).send().then(function(d){e("connected",d);d.event.target===a.channel?a.setPort(a.channel):(a.channel.close(),delete a.channel);a.receiver.listen(a.port)}).catch(function(d){"timeout"==(d&&d.message)&&(d=m(2,a.service),console.error(d.message));throw d;})},disconnect:function(a){var d=this;if(!this.connected)return Promise.resolve();e("disconnecting ...");a={noRespond:a&&a.noRespond,data:this.id};this.cancelPending();return this.message("_disconnect").set(a).send().then(function(){return d.onDisconnected()})},
|
||||
method:function(a,d){var c=this;d=[].slice.call(arguments,1);return this.connect().then(function(){e("method",a);return c.message("_method").set({recipient:c.service,data:{name:a,args:d}}).send()}).then(function(a){return a.value}).catch(function(d){"timeout"==(d&&d.message)&&(d=m(3,a),console.error(d.message));throw d;})},plugin:function(a){a(this,{Emitter:k,uuid:f});return this},message:function(a){var d=this;e("create message",a);var c=h(a).set("port",this.port).set("timeout",this.timeout).on("response",
|
||||
function(){return d.pending.delete(c)}).on("cancel",function(){return d.pending.delete(c)});this.pending.add(c);return c},cancelPending:function(){e("cancel pending");this.pending.forEach(function(a){a.cancel()});this.pending.clear()},pendingResponded:function(){var a=[];this.pending.forEach(function(d){return a.push(d.responded)});return Promise.all(a)},onPush:function(a){e("on push",a.data);this._emit(a.data.type,a.data.data)},onDisconnected:function(){var a=this;delete this.connected;this.pendingResponded().then(function(){e("disconnected");
|
||||
a.channel&&a.channel.close();a._emit("disconnected")})},setPort:function(a){e("set port");this.port=p(a)},destroy:function(){var a=this;return this.disconnect().then(function(){a.destroyed||(e("destroy"),a.destroyed=!0,a.receiver.destroy(),a._off(),a.port=a.endpoint=a.receiver=null)})},_on:k.prototype.on,_off:k.prototype.off,_emit:k.prototype.emit};b.prototype.on=function(a,d){var c=this;this.connect().then(function(){e("bind on",a);k.prototype.on.call(c,a,d);c.message("_on").set("noRespond",!0).set("data",
|
||||
{name:a,clientId:c.id}).send(c.port)});return this};b.prototype.off=function(a,d){var c=this;this.connect().then(function(){k.prototype.off.call(c,a,d);c.message("_off").set("noRespond",!0).set("data",{name:a,clientId:c.id}).send(c.port)});return this}},{"./emitter":3,"./message":4,"./message/port-adaptors":5,"./utils":7}],3:[function(g,l,n){function b(m){if(m)return Object.assign(m,b.prototype)}l.exports=b;b.prototype={on:function(b,g){this._callbacks||(this._callbacks={});this._callbacks[b]||(this._callbacks[b]=
|
||||
[]);this._callbacks[b].push(g);return this},off:function(b,g){if(this._callbacks)switch(arguments.length){case 0:this._callbacks={};break;case 1:delete this._callbacks[b];break;default:var k=this._callbacks[b];if(!k)return;var h=k.indexOf(g);~h&&k.splice(h,1)}return this},emit:function(b,g){if(this._callbacks)for(var k=this._callbacks[b]||[],k=k.concat(this._callbacks["*"]||[]),h=0;h<k.length;h++)k[h].call(this,g,b);return this}};g=b.prototype;g.off=g.off;g.on=g.on},{}],4:[function(g,l,n){function b(a){this.cancelled=
|
||||
!1;this.listeners=[];this.deferred=f();this.onMessage=this.onMessage.bind(this);this.onTimeout=this.onTimeout.bind(this);"object"===typeof a?this.setupInbound(a):this.setupOutbound(a);c("initialized",a)}function m(a){this.name=a;this.ports=new Set;this.onMessage=this.onMessage.bind(this);this.listen=this.listen.bind(this);this.unlisten=this.unlisten.bind(this);c("receiver initialized",a)}function p(a,c){c=[].slice.call(arguments,1);return Error({1:".send() can only be called once",2:"response already sent for this message",
|
||||
3:"a port must be defined",4:"timeout"}[a])}var k=g("./port-adaptors"),h=g("../emitter");g=g("../utils");var f=g.deferred,e=g.uuid;n=l.exports=function(a){return new b(a)};n.receiver=function(a,c){return new m(a,c)};n.Receiver=m;n.Message=b;var c={0:function(){},1:function(a){return performance.mark("["+self.constructor.name+"][Message] - "+a)},2:function(a,c){c=[].slice.call(arguments,1);console.log.apply(console,[].concat(["[Message]"+("["+self.constructor.name+"]["+location.pathname+"]")+' - "'+
|
||||
a+'"'],c))}}[0];b.prototype={setupOutbound:function(a){this.id=e();this.type=a;this.sent=!1;this.recipient="*"},setupInbound:function(a){c("inbound");this.hasResponded=!1;this.setSourcePort(a.source||a.target);this.event=a;Object.assign(this,a.data)},setSourcePort:function(a){c("set source",a.constructor.name);this.sourcePort=k(a,{ready:!0});return this},set:function(a,d){c("set",a,d);"object"==typeof a?Object.assign(this,a):this[a]=d;return this},serialize:function(){return{id:this.id,type:this.type,
|
||||
data:this.data,recipient:this.recipient,noRespond:this.noRespond}},preventDefault:function(){c("prevent default");this.defaultPrevented=!0},send:function(a){c("send",this.type);if(this.sent)throw p(1);var d=this.serialize(),f=!this.noRespond;this.port=a?k(a):this.port;if(!this.port)throw p(3);f?(this.listen(this.port),this.setResponseTimeout()):this.deferred.resolve();this.port.postMessage(d,this.getTransfer());c("sent",d);return this.deferred.promise},setResponseTimeout:function(){!1!==this.timeout&&
|
||||
(this._timer=setTimeout(this.onTimeout,this.timeout||1E3))},clearResponseTimeout:function(){clearTimeout(this._timer)},getTransfer:function(){return this.transfer||this.event&&this.event.ports},onMessage:function(a){if(a.data.response&&a.data.id===this.id&&!this.cancelled)this.onResponse(a)},onTimeout:function(){c("response timeout",this.type);this.silentTimeout||this.deferred.reject(p(4));this.teardown()},listen:function(a){c("add response listener",a);a=k(a);a.addListener(this.onMessage);this.listeners.push(a);
|
||||
return this},unlisten:function(){var a=this;c("remove response listeners");this.listeners.forEach(function(c){return c.removeListener(a.onMessage)});this.listeners=[]},cancel:function(){this.teardown();this.cancelled=!0;this.emit("cancel")},teardown:function(){this.clearResponseTimeout();this.unlisten()},respond:function(a){function d(a){c("resolve",a);b({type:"resolve",value:a})}function f(a){a=a&&a.message||a;c("reject",a);b({type:"reject",value:a})}function b(a){e.response=a;e.sourcePort.postMessage({id:e.id,
|
||||
response:a},e.transfer);c("responded with:",a)}c("respond",a);if(this.hasResponded)throw p(2);if(this.sourcePort&&!this.noRespond){var e=this;this.hasResponded=!0;a instanceof Error&&f(a);Promise.resolve(a).then(d,f).catch(f)}},forward:function(a){var d=this;c("forward");return this.set("silentTimeout",!0).send(a).then(function(a){return d.respond(a.value)})},onResponse:function(a){c("on response",a.data);var d=a.data.response,f="reject"==d.type?d.value:d;d.event=a;this.response=d;this.teardown();
|
||||
this.deferred[this.response.type](f);this.emit("response",d)}};h(b.prototype);m.prototype={listen:function(a){c("listen");a=k(a||self,{receiver:!0});if(!this.ports.has(a))return a.addListener(this.onMessage,this.listen),this.ports.add(a),this},unlisten:function(){var a=this;c("unlisten");this.ports.forEach(function(c){c.removeListener(a.onMessage,a.unlisten)})},onMessage:function(a){if(a.data.id&&a.data.type&&this.isRecipient(a.data.recipient)&&(c("receiver on message",a.data),a=new b(a),this.emit("message",
|
||||
a),!a.defaultPrevented))try{this.emit(a.type,a)}catch(d){throw a.respond(d),d;}},isRecipient:function(a){return a==this.name||"*"==a||"*"==this.name},destroy:function(){this.unlisten();delete this.name;return this}};h(m.prototype)},{"../emitter":3,"../utils":7,"./port-adaptors":5}],5:[function(g,l,n){function b(c){this.target=c}function m(c,a,d){c.addEventListener(a,d)}function p(c,a,d){a={data:a,source:self};d&&(a.ports=d);c.dispatchEvent(new MessageEvent("message",a))}var k=g("../utils").deferred;
|
||||
l.exports=function(c,a){if(!c)throw Error({1:"target is undefined"}[1]);if(c&&c.addListener)return c;var d=f[c.constructor.name];return d?d(c,a):new b(c,a)};var h=b.prototype={constructor:b,addListener:function(c){this.target.addEventListener("message",c)},removeListener:function(c){this.target.removeEventListener("message",c)},postMessage:function(c,a){this.target.postMessage(c,a)}},f={HTMLIFrameElement:function(c){var a=e(c);return{addListener:function(a,c){window.addEventListener("message",a)},
|
||||
removeListener:function(a,c){window.removeEventListener("message",a)},postMessage:function(d,f){a.then(function(){return p(c.contentWindow,d,f)})}}},BroadcastChannel:function(c,a){function d(){var a=k();c.postMessage("ready?");m(c,"message",function q(d){"ready"==d.data&&(c.removeEventListener("message",q),a.resolve())});return a.promise}var f=a&&a.receiver,b=a&&a.ready,b=b||f?Promise.resolve():d();f&&(c.postMessage("ready"),m(c,"message",function(a){"ready?"==a.data&&c.postMessage("ready")}));return{target:c,
|
||||
addListener:h.addListener,removeListener:h.removeListener,postMessage:function(a,d){b.then(function(){return c.postMessage(a,d)})}}},Window:function(c,a){var d=a&&a.ready||c===parent||c===self,d=d?Promise.resolve():e(c);return{addListener:function(a,c){window.addEventListener("message",a)},removeListener:function(a,c){window.removeEventListener("message",a)},postMessage:function(a,f){d.then(function(){return p(c,a,f)})}}},SharedWorker:function(c){c.port.start();return new b(c.port)},SharedWorkerGlobalScope:function(){var c=
|
||||
[];return{postMessage:function(){},addListener:function(a,d){this.onconnect=function(a){a=a.ports[0];c.push(a);a.start();d(a)};self.addEventListener("connect",this.onconnect)},removeListener:function(a,d){self.removeEventListener("connect",this.onconnect);c.forEach(function(a){a.close();d(a)})}}}},e=function(){if("undefined"!=typeof window){var c=window.opener||window.parent,a=new WeakSet;c!=self&&m(window,"DOMContentLoaded",function r(){window.removeEventListener("DOMContentLoaded",r);p(c,"load")});
|
||||
m(self,"message",function(c){return"load"==c.data&&a.add(c.source)});return function(c){var f=c.contentWindow||c;if(a.has(f)||f==window.parent)return Promise.resolve();var b=k();m(window,"message",function q(a){"load"==a.data&&a.source==f&&(window.removeEventListener("message",q),b.resolve())});return b.promise}}}()},{"../utils":7}],6:[function(g,l,n){function b(f){if(!(this instanceof b))return new b(f);k.Receiver.call(this,f);this.clients={};this.methods={};this.on("_disconnect",this.onDisconnect.bind(this)).on("_connect",
|
||||
this.onConnect.bind(this)).on("_method",this.onMethod.bind(this)).on("_off",this.onOff.bind(this)).on("_on",this.onOn.bind(this));this.destroy=this.destroy.bind(this);h("initialized",f)}function m(f){var b=[].slice.call(arguments,1);return Error({4:'method "'+b[0]+"\" doesn't exist"}[f])}var p=g("./utils").uuid,k=g("./message");g=k.Receiver;l.exports=b;var h={0:function(){},1:function(f){return performance.mark("["+self.constructor.name+"][Service] - "+f)},2:function(f,b){b=[].slice.call(arguments,
|
||||
1);console.log.apply(console,[].concat(["[Service]"+("["+self.constructor.name+"]["+location.pathname+"]")+' - "'+f+'"'],b))}}[0];b.prototype=Object.create(g.prototype);b.prototype.inWindow="Window"===constructor.name;b.prototype.method=function(f,b){this.methods[f]=b;return this};b.prototype.broadcast=function(f,b,c){var a=this;h("broadcast",f,b,c);this.eachClient(function(d){if(!c||~c.indexOf(d.id))h("broadcasting to",d.id),a.push(f,b,d.id,{noRespond:!0})});return this};b.prototype.push=function(b,
|
||||
e,c,a){a=a&&a.noRespond;var d=this.getClient(c);return k("_push").set({recipient:c,noRespond:a,data:{type:b,data:e}}).send(d.port)};b.prototype.eachClient=function(b){for(var e in this.clients)b(this.clients[e])};b.prototype.getClient=function(b){return this.clients[b]};b.prototype.onConnect=function(b){h("connection attempt",b.data,this.name);var e=b.data,c=e.clientId;c&&e.service===this.name&&!this.clients[c]&&(this.emit("before-connect",b),b.defaultPrevented||(this.upgradeChannel(b),this.addClient(c,
|
||||
b.sourcePort),b.respond(),this.emit("connected",c),h("connected",c)))};b.prototype.upgradeChannel=function(b){if(!this.inWindow||"Window"!==b.data.originEnv){var e=b.event.ports;if(e=e&&e[0])b.setSourcePort(e),this.listen(e),e.start();h("channel upgraded")}};b.prototype.onDisconnect=function(b){var e=this.clients[b.data];e&&(this.emit("before-disconnect",b),b.defaultPrevented||(this.removeClient(e.id),b.respond(),this.emit("disconnected",e.id),h("disconnected",e.id)))};b.prototype.onMethod=function(b){h("on method",
|
||||
b.data);this.emit("before-method",b);if(!b.defaultPrevented){var e=b.data,c=e.name,a,d=this.methods[c];if(!d)throw m(4,c);try{a=d.apply(this,e.args)}catch(g){a=g}b.respond(a)}};b.prototype.onOn=function(b){h("on on",b.data);this.emit("on",b.data)};b.prototype.onOff=function(b){h("on off");this.emit("off",b.data)};b.prototype.addClient=function(b,e){this.clients[b]={id:b,port:e}};b.prototype.removeClient=function(b){delete this.clients[b]};b.prototype.plugin=function(b){b(this,{uuid:p});return this};
|
||||
b.prototype.disconnect=function(b){this.removeClient(b.id);k("disconnect").set({recipient:b.id,noRespond:!0}).send(b.port)};b.prototype.destroy=function(){delete this.clients;this.unlisten();this.off()};l=b.prototype;l.broadcast=l.broadcast;l.destroy=l.destroy;l.method=l.method;l.plugin=l.plugin},{"./message":4,"./utils":7}],7:[function(g,l,n){n.uuid=function(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(b){var g=16*Math.random()|0;return("x"==b?g:g&3|8).toString(16)})};
|
||||
n.deferred=function(){var b={};b.promise=new Promise(function(g,l){b.resolve=g;b.reject=l});return b}},{}]},{},[1]);
|
||||
9
gaia/apps/music/components/bridge/build/bridge.js
Executable file
|
|
@ -0,0 +1,9 @@
|
|||
|
||||
var bridge = {
|
||||
'service': require('../src/service'),
|
||||
'client': require('../src/client'),
|
||||
_m: require('../src/message')
|
||||
};
|
||||
|
||||
if ((typeof define)[0] != 'u') define([], () => bridge);
|
||||
else self['bridge'] = bridge;
|
||||
4
gaia/apps/music/components/bridge/build/client.js
Executable file
|
|
@ -0,0 +1,4 @@
|
|||
var bridge = module.exports = self['bridge'] || {};
|
||||
bridge['client'] = require('../src/client');
|
||||
if ((typeof define)[0] != 'u') define([], () => bridge);
|
||||
else self['bridge'] = bridge;
|
||||
4
gaia/apps/music/components/bridge/build/service.js
Executable file
|
|
@ -0,0 +1,4 @@
|
|||
var bridge = module.exports = self['bridge'] || {};
|
||||
bridge['service'] = require('../src/service');
|
||||
if ((typeof define)[0] != 'u') define([], () => bridge);
|
||||
else self['bridge'] = bridge;
|
||||
1319
gaia/apps/music/components/bridge/client.js
Executable file
25
gaia/apps/music/components/bridge/client.min.js
vendored
Executable file
|
|
@ -0,0 +1,25 @@
|
|||
(function(q){"object"===typeof exports&&"undefined"!==typeof module?module.exports=q():"function"===typeof define&&define.amd?define([],q):("undefined"!==typeof window?window:"undefined"!==typeof global?global:"undefined"!==typeof self?self:this).bridge=q()})(function(){return function e(l,h,d){function k(f,p){if(!h[f]){if(!l[f]){var g="function"==typeof require&&require;if(!p&&g)return g(f,!0);if(m)return m(f,!0);g=Error("Cannot find module '"+f+"'");throw g.code="MODULE_NOT_FOUND",g;}g=h[f]={exports:{}};
|
||||
l[f][0].call(g.exports,function(b){var a=l[f][1][b];return k(a?a:b)},g,g.exports,e,l,h,d)}return h[f].exports}for(var m="function"==typeof require&&require,f=0;f<d.length;f++)k(d[f]);return k}({1:[function(e,l,h){var d=l.exports=self.bridge||{};d.client=e("../src/client");"u"!="undefined"[0]?(void 0)([],function(){return d}):self.bridge=d},{"../src/client":2}],2:[function(e,l,h){function d(a,c,b){if(!(this instanceof d))return new d(a,c,b);"object"==typeof a&&(c=a.endpoint,b=a.timeout,a=a.service);
|
||||
this.id=p();this.service=a;this.timeout=b;this.endpoint=c||this.endpoint;if(!this.endpoint)throw k(1);this.setPort(this.endpoint);this.pending=new Set;this.receiver=n.receiver(this.id).on("_push",this.onPush.bind(this));g("initialized",a)}function k(a,c){c=[].slice.call(arguments,1);return Error({1:"an endpoint must be defined",2:'Unable to establish a connection with "'+c[0]+'". Either the target endpoint is not alive or the Service is not `.listen()`ing.',3:'Method "'+c[0]+"\" didn't get a response. Either the target endpoint is not alive or the Service is not `.listen()`ing."}[a])}
|
||||
var m=e("./message/port-adaptors"),f=e("./emitter"),n=e("./message"),p=e("./utils").uuid;l.exports=d;var g={0:function(){},1:function(a){return performance.mark("["+self.constructor.name+"][Client] - "+a)},2:function(a,c){c=[].slice.call(arguments,1);console.log.apply(console,[].concat(["[Client]"+("["+self.constructor.name+"]["+location.pathname+"]")+' - "'+a+'"'],c))}}[0],b=constructor.name;d.prototype={connect:function(){var a=this;g("connect");if(this.connected)return this.connected;g("connecting...",
|
||||
this.service);var c=new MessageChannel;this.channel=c.port1;this.channel.start();var r={clientId:this.id,service:this.service,originEnv:b};return this.connected=this.message("_connect").set("transfer",[c.port2]).set("data",r).listen(c.port1).send().then(function(c){g("connected",c);c.event.target===a.channel?a.setPort(a.channel):(a.channel.close(),delete a.channel);a.receiver.listen(a.port)}).catch(function(c){"timeout"==(c&&c.message)&&(c=k(2,a.service),console.error(c.message));throw c;})},disconnect:function(a){var c=
|
||||
this;if(!this.connected)return Promise.resolve();g("disconnecting ...");a={noRespond:a&&a.noRespond,data:this.id};this.cancelPending();return this.message("_disconnect").set(a).send().then(function(){return c.onDisconnected()})},method:function(a,c){var b=this;c=[].slice.call(arguments,1);return this.connect().then(function(){g("method",a);return b.message("_method").set({recipient:b.service,data:{name:a,args:c}}).send()}).then(function(a){return a.value}).catch(function(c){"timeout"==(c&&c.message)&&
|
||||
(c=k(3,a),console.error(c.message));throw c;})},plugin:function(a){a(this,{Emitter:f,uuid:p});return this},message:function(a){var c=this;g("create message",a);var b=n(a).set("port",this.port).set("timeout",this.timeout).on("response",function(){return c.pending.delete(b)}).on("cancel",function(){return c.pending.delete(b)});this.pending.add(b);return b},cancelPending:function(){g("cancel pending");this.pending.forEach(function(a){a.cancel()});this.pending.clear()},pendingResponded:function(){var a=
|
||||
[];this.pending.forEach(function(c){return a.push(c.responded)});return Promise.all(a)},onPush:function(a){g("on push",a.data);this._emit(a.data.type,a.data.data)},onDisconnected:function(){var a=this;delete this.connected;this.pendingResponded().then(function(){g("disconnected");a.channel&&a.channel.close();a._emit("disconnected")})},setPort:function(a){g("set port");this.port=m(a)},destroy:function(){var a=this;return this.disconnect().then(function(){a.destroyed||(g("destroy"),a.destroyed=!0,a.receiver.destroy(),
|
||||
a._off(),a.port=a.endpoint=a.receiver=null)})},_on:f.prototype.on,_off:f.prototype.off,_emit:f.prototype.emit};d.prototype.on=function(a,c){var b=this;this.connect().then(function(){g("bind on",a);f.prototype.on.call(b,a,c);b.message("_on").set("noRespond",!0).set("data",{name:a,clientId:b.id}).send(b.port)});return this};d.prototype.off=function(a,c){var b=this;this.connect().then(function(){f.prototype.off.call(b,a,c);b.message("_off").set("noRespond",!0).set("data",{name:a,clientId:b.id}).send(b.port)});
|
||||
return this}},{"./emitter":3,"./message":4,"./message/port-adaptors":5,"./utils":6}],3:[function(e,l,h){function d(k){if(k)return Object.assign(k,d.prototype)}l.exports=d;d.prototype={on:function(d,e){this._callbacks||(this._callbacks={});this._callbacks[d]||(this._callbacks[d]=[]);this._callbacks[d].push(e);return this},off:function(d,e){if(this._callbacks)switch(arguments.length){case 0:this._callbacks={};break;case 1:delete this._callbacks[d];break;default:var f=this._callbacks[d];if(!f)return;
|
||||
var h=f.indexOf(e);~h&&f.splice(h,1)}return this},emit:function(d,e){if(this._callbacks)for(var f=this._callbacks[d]||[],f=f.concat(this._callbacks["*"]||[]),h=0;h<f.length;h++)f[h].call(this,e,d);return this}};e=d.prototype;e.off=e.off;e.on=e.on},{}],4:[function(e,l,h){function d(a){this.cancelled=!1;this.listeners=[];this.deferred=p();this.onMessage=this.onMessage.bind(this);this.onTimeout=this.onTimeout.bind(this);"object"===typeof a?this.setupInbound(a):this.setupOutbound(a);b("initialized",a)}
|
||||
function k(a){this.name=a;this.ports=new Set;this.onMessage=this.onMessage.bind(this);this.listen=this.listen.bind(this);this.unlisten=this.unlisten.bind(this);b("receiver initialized",a)}function m(a,c){c=[].slice.call(arguments,1);return Error({1:".send() can only be called once",2:"response already sent for this message",3:"a port must be defined",4:"timeout"}[a])}var f=e("./port-adaptors"),n=e("../emitter");e=e("../utils");var p=e.deferred,g=e.uuid;h=l.exports=function(a){return new d(a)};h.receiver=
|
||||
function(a,c){return new k(a,c)};h.Receiver=k;h.Message=d;var b={0:function(){},1:function(a){return performance.mark("["+self.constructor.name+"][Message] - "+a)},2:function(a,c){c=[].slice.call(arguments,1);console.log.apply(console,[].concat(["[Message]"+("["+self.constructor.name+"]["+location.pathname+"]")+' - "'+a+'"'],c))}}[0];d.prototype={setupOutbound:function(a){this.id=g();this.type=a;this.sent=!1;this.recipient="*"},setupInbound:function(a){b("inbound");this.hasResponded=!1;this.setSourcePort(a.source||
|
||||
a.target);this.event=a;Object.assign(this,a.data)},setSourcePort:function(a){b("set source",a.constructor.name);this.sourcePort=f(a,{ready:!0});return this},set:function(a,c){b("set",a,c);"object"==typeof a?Object.assign(this,a):this[a]=c;return this},serialize:function(){return{id:this.id,type:this.type,data:this.data,recipient:this.recipient,noRespond:this.noRespond}},preventDefault:function(){b("prevent default");this.defaultPrevented=!0},send:function(a){b("send",this.type);if(this.sent)throw m(1);
|
||||
var c=this.serialize(),d=!this.noRespond;this.port=a?f(a):this.port;if(!this.port)throw m(3);d?(this.listen(this.port),this.setResponseTimeout()):this.deferred.resolve();this.port.postMessage(c,this.getTransfer());b("sent",c);return this.deferred.promise},setResponseTimeout:function(){!1!==this.timeout&&(this._timer=setTimeout(this.onTimeout,this.timeout||1E3))},clearResponseTimeout:function(){clearTimeout(this._timer)},getTransfer:function(){return this.transfer||this.event&&this.event.ports},onMessage:function(a){if(a.data.response&&
|
||||
a.data.id===this.id&&!this.cancelled)this.onResponse(a)},onTimeout:function(){b("response timeout",this.type);this.silentTimeout||this.deferred.reject(m(4));this.teardown()},listen:function(a){b("add response listener",a);a=f(a);a.addListener(this.onMessage);this.listeners.push(a);return this},unlisten:function(){var a=this;b("remove response listeners");this.listeners.forEach(function(c){return c.removeListener(a.onMessage)});this.listeners=[]},cancel:function(){this.teardown();this.cancelled=!0;
|
||||
this.emit("cancel")},teardown:function(){this.clearResponseTimeout();this.unlisten()},respond:function(a){function c(a){b("resolve",a);f({type:"resolve",value:a})}function d(a){a=a&&a.message||a;b("reject",a);f({type:"reject",value:a})}function f(a){e.response=a;e.sourcePort.postMessage({id:e.id,response:a},e.transfer);b("responded with:",a)}b("respond",a);if(this.hasResponded)throw m(2);if(this.sourcePort&&!this.noRespond){var e=this;this.hasResponded=!0;a instanceof Error&&d(a);Promise.resolve(a).then(c,
|
||||
d).catch(d)}},forward:function(a){var c=this;b("forward");return this.set("silentTimeout",!0).send(a).then(function(a){return c.respond(a.value)})},onResponse:function(a){b("on response",a.data);var c=a.data.response,d="reject"==c.type?c.value:c;c.event=a;this.response=c;this.teardown();this.deferred[this.response.type](d);this.emit("response",c)}};n(d.prototype);k.prototype={listen:function(a){b("listen");a=f(a||self,{receiver:!0});if(!this.ports.has(a))return a.addListener(this.onMessage,this.listen),
|
||||
this.ports.add(a),this},unlisten:function(){var a=this;b("unlisten");this.ports.forEach(function(c){c.removeListener(a.onMessage,a.unlisten)})},onMessage:function(a){if(a.data.id&&a.data.type&&this.isRecipient(a.data.recipient)&&(b("receiver on message",a.data),a=new d(a),this.emit("message",a),!a.defaultPrevented))try{this.emit(a.type,a)}catch(c){throw a.respond(c),c;}},isRecipient:function(a){return a==this.name||"*"==a||"*"==this.name},destroy:function(){this.unlisten();delete this.name;return this}};
|
||||
n(k.prototype)},{"../emitter":3,"../utils":6,"./port-adaptors":5}],5:[function(e,l,h){function d(b){this.target=b}function k(b,a,c){b.addEventListener(a,c)}function m(b,a,c){a={data:a,source:self};c&&(a.ports=c);b.dispatchEvent(new MessageEvent("message",a))}var f=e("../utils").deferred;l.exports=function(b,a){if(!b)throw Error({1:"target is undefined"}[1]);if(b&&b.addListener)return b;var c=p[b.constructor.name];return c?c(b,a):new d(b,a)};var n=d.prototype={constructor:d,addListener:function(b){this.target.addEventListener("message",
|
||||
b)},removeListener:function(b){this.target.removeEventListener("message",b)},postMessage:function(b,a){this.target.postMessage(b,a)}},p={HTMLIFrameElement:function(b){var a=g(b);return{addListener:function(a,b){window.addEventListener("message",a)},removeListener:function(a,b){window.removeEventListener("message",a)},postMessage:function(c,d){a.then(function(){return m(b.contentWindow,c,d)})}}},BroadcastChannel:function(b,a){function c(){var a=f();b.postMessage("ready?");k(b,"message",function t(c){"ready"==
|
||||
c.data&&(b.removeEventListener("message",t),a.resolve())});return a.promise}var d=a&&a.receiver,e=a&&a.ready,e=e||d?Promise.resolve():c();d&&(b.postMessage("ready"),k(b,"message",function(a){"ready?"==a.data&&b.postMessage("ready")}));return{target:b,addListener:n.addListener,removeListener:n.removeListener,postMessage:function(a,c){e.then(function(){return b.postMessage(a,c)})}}},Window:function(b,a){var c=a&&a.ready||b===parent||b===self,c=c?Promise.resolve():g(b);return{addListener:function(a,
|
||||
c){window.addEventListener("message",a)},removeListener:function(a,c){window.removeEventListener("message",a)},postMessage:function(a,d){c.then(function(){return m(b,a,d)})}}},SharedWorker:function(b){b.port.start();return new d(b.port)},SharedWorkerGlobalScope:function(){var b=[];return{postMessage:function(){},addListener:function(a,c){this.onconnect=function(a){a=a.ports[0];b.push(a);a.start();c(a)};self.addEventListener("connect",this.onconnect)},removeListener:function(a,c){self.removeEventListener("connect",
|
||||
this.onconnect);b.forEach(function(a){a.close();c(a)})}}}},g=function(){if("undefined"!=typeof window){var b=window.opener||window.parent,a=new WeakSet;b!=self&&k(window,"DOMContentLoaded",function r(){window.removeEventListener("DOMContentLoaded",r);m(b,"load")});k(self,"message",function(b){return"load"==b.data&&a.add(b.source)});return function(b){var d=b.contentWindow||b;if(a.has(d)||d==window.parent)return Promise.resolve();var e=f();k(window,"message",function t(a){"load"==a.data&&a.source==
|
||||
d&&(window.removeEventListener("message",t),e.resolve())});return e.promise}}}()},{"../utils":6}],6:[function(e,l,h){h.uuid=function(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(d){var e=16*Math.random()|0;return("x"==d?e:e&3|8).toString(16)})};h.deferred=function(){var d={};d.promise=new Promise(function(e,h){d.resolve=e;d.reject=h});return d}},{}]},{},[1])(1)});
|
||||
1
gaia/apps/music/components/bridge/index.html
Executable file
|
|
@ -0,0 +1 @@
|
|||
<meta http-equiv="refresh" content="0; url=docs/out/index.html" />
|
||||
6
gaia/apps/music/components/bridge/index.js
Executable file
|
|
@ -0,0 +1,6 @@
|
|||
|
||||
module.exports = {
|
||||
service: require('./src/service'),
|
||||
client: require('./src/client'),
|
||||
_message: require('./src/message')
|
||||
};
|
||||
52
gaia/apps/music/components/bridge/package.json
Executable file
|
|
@ -0,0 +1,52 @@
|
|||
{
|
||||
"name": "bridge",
|
||||
"version": "1.0.1",
|
||||
"homepage": "https://github.com/gaia-components/bridge",
|
||||
"license": "MIT",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "./node_modules/karma/bin/karma start test/unit/karma.conf.js --single-run",
|
||||
"test-dev": "./node_modules/karma/bin/karma start test/unit/karma.conf.js",
|
||||
|
||||
"docs": "rm -rf docs/bridge; babel src --out-dir src-es5; jsdoc -c docs/jsdoc.json; rm -rf src-es5",
|
||||
|
||||
"bundle": "browserify build/bridge.js -o bridge.js; npm run bundle-client; npm run bundle-service",
|
||||
"bundle-client": "browserify build/client -s bridge -o client.js",
|
||||
"bundle-service": "browserify build/service -s bridge -o service.js",
|
||||
|
||||
"compress": "ccjs bridge.js --language_in=ECMASCRIPT6 --language_out=ES5 > bridge.min.js; npm run compress-client; npm run compress-service",
|
||||
"compress-client": "ccjs client.js --language_in=ECMASCRIPT6 --language_out=ES5 > client.min.js",
|
||||
"compress-service": "ccjs service.js --language_in=ECMASCRIPT6 --language_out=ES5 > service.min.js",
|
||||
|
||||
"build": "npm run bundle; npm run compress",
|
||||
"watch": "watchify build/bridge.js -o bridge.js",
|
||||
"watch-client": "watchify src/client.js -s client -o client.js",
|
||||
"watch-service": "watchify src/service.js -s service -o service.js",
|
||||
"gh-pages": "git checkout gh-pages; git reset master --hard; git checkout -"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/gaia-components/bridge.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/gaia-components/bridge/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"browserify": "^10.1.0",
|
||||
"karma": "^0.12.31",
|
||||
"karma-firefox-launcher": "^0.1.4",
|
||||
"karma-mocha": "^0.1.10",
|
||||
"karma-sinon-chai": "^0.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"browserify": "^9.0.8",
|
||||
"closurecompiler": "^1.5.2",
|
||||
"jsdoc": "git+https://github.com/jsdoc3/jsdoc.git",
|
||||
"karma": "^0.12.37",
|
||||
"karma-firefox-launcher": "0.1.3",
|
||||
"karma-mocha": "0.1.3",
|
||||
"karma-sinon-chai": "0.1.6",
|
||||
"mozilla-download": "^1.0.4",
|
||||
"watchify": "^3.2.3"
|
||||
}
|
||||
}
|
||||
1332
gaia/apps/music/components/bridge/service.js
Executable file
24
gaia/apps/music/components/bridge/service.min.js
vendored
Executable file
|
|
@ -0,0 +1,24 @@
|
|||
(function(p){"object"===typeof exports&&"undefined"!==typeof module?module.exports=p():"function"===typeof define&&define.amd?define([],p):("undefined"!==typeof window?window:"undefined"!==typeof global?global:"undefined"!==typeof self?self:this).bridge=p()})(function(){return function e(h,m,b){function l(f,c){if(!m[f]){if(!h[f]){var k="function"==typeof require&&require;if(!c&&k)return k(f,!0);if(n)return n(f,!0);k=Error("Cannot find module '"+f+"'");throw k.code="MODULE_NOT_FOUND",k;}k=m[f]={exports:{}};
|
||||
h[f][0].call(k.exports,function(d){var a=h[f][1][d];return l(a?a:d)},k,k.exports,e,h,m,b)}return m[f].exports}for(var n="function"==typeof require&&require,g=0;g<b.length;g++)l(b[g]);return l}({1:[function(e,h,m){var b=h.exports=self.bridge||{};b.service=e("../src/service");"u"!="undefined"[0]?(void 0)([],function(){return b}):self.bridge=b},{"../src/service":5}],2:[function(e,h,m){function b(l){if(l)return Object.assign(l,b.prototype)}h.exports=b;b.prototype={on:function(b,e){this._callbacks||(this._callbacks=
|
||||
{});this._callbacks[b]||(this._callbacks[b]=[]);this._callbacks[b].push(e);return this},off:function(b,e){if(this._callbacks)switch(arguments.length){case 0:this._callbacks={};break;case 1:delete this._callbacks[b];break;default:var g=this._callbacks[b];if(!g)return;var f=g.indexOf(e);~f&&g.splice(f,1)}return this},emit:function(b,e){if(this._callbacks)for(var g=this._callbacks[b]||[],g=g.concat(this._callbacks["*"]||[]),f=0;f<g.length;f++)g[f].call(this,e,b);return this}};e=b.prototype;e.off=e.off;
|
||||
e.on=e.on},{}],3:[function(e,h,m){function b(a){this.cancelled=!1;this.listeners=[];this.deferred=c();this.onMessage=this.onMessage.bind(this);this.onTimeout=this.onTimeout.bind(this);"object"===typeof a?this.setupInbound(a):this.setupOutbound(a);d("initialized",a)}function l(a){this.name=a;this.ports=new Set;this.onMessage=this.onMessage.bind(this);this.listen=this.listen.bind(this);this.unlisten=this.unlisten.bind(this);d("receiver initialized",a)}function n(a,d){d=[].slice.call(arguments,1);return Error({1:".send() can only be called once",
|
||||
2:"response already sent for this message",3:"a port must be defined",4:"timeout"}[a])}var g=e("./port-adaptors"),f=e("../emitter");e=e("../utils");var c=e.deferred,k=e.uuid;m=h.exports=function(a){return new b(a)};m.receiver=function(a,d){return new l(a,d)};m.Receiver=l;m.Message=b;var d={0:function(){},1:function(a){return performance.mark("["+self.constructor.name+"][Message] - "+a)},2:function(a,d){d=[].slice.call(arguments,1);console.log.apply(console,[].concat(["[Message]"+("["+self.constructor.name+
|
||||
"]["+location.pathname+"]")+' - "'+a+'"'],d))}}[0];b.prototype={setupOutbound:function(a){this.id=k();this.type=a;this.sent=!1;this.recipient="*"},setupInbound:function(a){d("inbound");this.hasResponded=!1;this.setSourcePort(a.source||a.target);this.event=a;Object.assign(this,a.data)},setSourcePort:function(a){d("set source",a.constructor.name);this.sourcePort=g(a,{ready:!0});return this},set:function(a,c){d("set",a,c);"object"==typeof a?Object.assign(this,a):this[a]=c;return this},serialize:function(){return{id:this.id,
|
||||
type:this.type,data:this.data,recipient:this.recipient,noRespond:this.noRespond}},preventDefault:function(){d("prevent default");this.defaultPrevented=!0},send:function(a){d("send",this.type);if(this.sent)throw n(1);var c=this.serialize(),b=!this.noRespond;this.port=a?g(a):this.port;if(!this.port)throw n(3);b?(this.listen(this.port),this.setResponseTimeout()):this.deferred.resolve();this.port.postMessage(c,this.getTransfer());d("sent",c);return this.deferred.promise},setResponseTimeout:function(){!1!==
|
||||
this.timeout&&(this._timer=setTimeout(this.onTimeout,this.timeout||1E3))},clearResponseTimeout:function(){clearTimeout(this._timer)},getTransfer:function(){return this.transfer||this.event&&this.event.ports},onMessage:function(a){if(a.data.response&&a.data.id===this.id&&!this.cancelled)this.onResponse(a)},onTimeout:function(){d("response timeout",this.type);this.silentTimeout||this.deferred.reject(n(4));this.teardown()},listen:function(a){d("add response listener",a);a=g(a);a.addListener(this.onMessage);
|
||||
this.listeners.push(a);return this},unlisten:function(){var a=this;d("remove response listeners");this.listeners.forEach(function(d){return d.removeListener(a.onMessage)});this.listeners=[]},cancel:function(){this.teardown();this.cancelled=!0;this.emit("cancel")},teardown:function(){this.clearResponseTimeout();this.unlisten()},respond:function(a){function c(a){d("resolve",a);k({type:"resolve",value:a})}function b(a){a=a&&a.message||a;d("reject",a);k({type:"reject",value:a})}function k(a){f.response=
|
||||
a;f.sourcePort.postMessage({id:f.id,response:a},f.transfer);d("responded with:",a)}d("respond",a);if(this.hasResponded)throw n(2);if(this.sourcePort&&!this.noRespond){var f=this;this.hasResponded=!0;a instanceof Error&&b(a);Promise.resolve(a).then(c,b).catch(b)}},forward:function(a){var c=this;d("forward");return this.set("silentTimeout",!0).send(a).then(function(a){return c.respond(a.value)})},onResponse:function(a){d("on response",a.data);var c=a.data.response,b="reject"==c.type?c.value:c;c.event=
|
||||
a;this.response=c;this.teardown();this.deferred[this.response.type](b);this.emit("response",c)}};f(b.prototype);l.prototype={listen:function(a){d("listen");a=g(a||self,{receiver:!0});if(!this.ports.has(a))return a.addListener(this.onMessage,this.listen),this.ports.add(a),this},unlisten:function(){var a=this;d("unlisten");this.ports.forEach(function(d){d.removeListener(a.onMessage,a.unlisten)})},onMessage:function(a){if(a.data.id&&a.data.type&&this.isRecipient(a.data.recipient)&&(d("receiver on message",
|
||||
a.data),a=new b(a),this.emit("message",a),!a.defaultPrevented))try{this.emit(a.type,a)}catch(c){throw a.respond(c),c;}},isRecipient:function(a){return a==this.name||"*"==a||"*"==this.name},destroy:function(){this.unlisten();delete this.name;return this}};f(l.prototype)},{"../emitter":2,"../utils":6,"./port-adaptors":4}],4:[function(e,h,m){function b(d){this.target=d}function l(d,a,c){d.addEventListener(a,c)}function n(d,a,c){a={data:a,source:self};c&&(a.ports=c);d.dispatchEvent(new MessageEvent("message",
|
||||
a))}var g=e("../utils").deferred;h.exports=function(d,a){if(!d)throw Error({1:"target is undefined"}[1]);if(d&&d.addListener)return d;var k=c[d.constructor.name];return k?k(d,a):new b(d,a)};var f=b.prototype={constructor:b,addListener:function(d){this.target.addEventListener("message",d)},removeListener:function(d){this.target.removeEventListener("message",d)},postMessage:function(d,a){this.target.postMessage(d,a)}},c={HTMLIFrameElement:function(d){var a=k(d);return{addListener:function(a,d){window.addEventListener("message",
|
||||
a)},removeListener:function(a,d){window.removeEventListener("message",a)},postMessage:function(c,b){a.then(function(){return n(d.contentWindow,c,b)})}}},BroadcastChannel:function(d,a){function c(){var a=g();d.postMessage("ready?");l(d,"message",function q(c){"ready"==c.data&&(d.removeEventListener("message",q),a.resolve())});return a.promise}var b=a&&a.receiver,k=a&&a.ready,k=k||b?Promise.resolve():c();b&&(d.postMessage("ready"),l(d,"message",function(a){"ready?"==a.data&&d.postMessage("ready")}));
|
||||
return{target:d,addListener:f.addListener,removeListener:f.removeListener,postMessage:function(a,c){k.then(function(){return d.postMessage(a,c)})}}},Window:function(c,a){var b=a&&a.ready||c===parent||c===self,b=b?Promise.resolve():k(c);return{addListener:function(a,c){window.addEventListener("message",a)},removeListener:function(a,c){window.removeEventListener("message",a)},postMessage:function(a,k){b.then(function(){return n(c,a,k)})}}},SharedWorker:function(c){c.port.start();return new b(c.port)},
|
||||
SharedWorkerGlobalScope:function(){var c=[];return{postMessage:function(){},addListener:function(a,b){this.onconnect=function(a){a=a.ports[0];c.push(a);a.start();b(a)};self.addEventListener("connect",this.onconnect)},removeListener:function(a,b){self.removeEventListener("connect",this.onconnect);c.forEach(function(a){a.close();b(a)})}}}},k=function(){if("undefined"!=typeof window){var c=window.opener||window.parent,a=new WeakSet;c!=self&&l(window,"DOMContentLoaded",function r(){window.removeEventListener("DOMContentLoaded",
|
||||
r);n(c,"load")});l(self,"message",function(c){return"load"==c.data&&a.add(c.source)});return function(c){var b=c.contentWindow||c;if(a.has(b)||b==window.parent)return Promise.resolve();var d=g();l(window,"message",function q(a){"load"==a.data&&a.source==b&&(window.removeEventListener("message",q),d.resolve())});return d.promise}}}()},{"../utils":6}],5:[function(e,h,m){function b(c){if(!(this instanceof b))return new b(c);g.Receiver.call(this,c);this.clients={};this.methods={};this.on("_disconnect",
|
||||
this.onDisconnect.bind(this)).on("_connect",this.onConnect.bind(this)).on("_method",this.onMethod.bind(this)).on("_off",this.onOff.bind(this)).on("_on",this.onOn.bind(this));this.destroy=this.destroy.bind(this);f("initialized",c)}function l(c){var b=[].slice.call(arguments,1);return Error({4:'method "'+b[0]+"\" doesn't exist"}[c])}var n=e("./utils").uuid,g=e("./message");e=g.Receiver;h.exports=b;var f={0:function(){},1:function(c){return performance.mark("["+self.constructor.name+"][Service] - "+
|
||||
c)},2:function(c,b){b=[].slice.call(arguments,1);console.log.apply(console,[].concat(["[Service]"+("["+self.constructor.name+"]["+location.pathname+"]")+' - "'+c+'"'],b))}}[0];b.prototype=Object.create(e.prototype);b.prototype.inWindow="Window"===constructor.name;b.prototype.method=function(c,b){this.methods[c]=b;return this};b.prototype.broadcast=function(c,b,d){var a=this;f("broadcast",c,b,d);this.eachClient(function(e){if(!d||~d.indexOf(e.id))f("broadcasting to",e.id),a.push(c,b,e.id,{noRespond:!0})});
|
||||
return this};b.prototype.push=function(c,b,d,a){a=a&&a.noRespond;var f=this.getClient(d);return g("_push").set({recipient:d,noRespond:a,data:{type:c,data:b}}).send(f.port)};b.prototype.eachClient=function(c){for(var b in this.clients)c(this.clients[b])};b.prototype.getClient=function(c){return this.clients[c]};b.prototype.onConnect=function(c){f("connection attempt",c.data,this.name);var b=c.data,d=b.clientId;d&&b.service===this.name&&!this.clients[d]&&(this.emit("before-connect",c),c.defaultPrevented||
|
||||
(this.upgradeChannel(c),this.addClient(d,c.sourcePort),c.respond(),this.emit("connected",d),f("connected",d)))};b.prototype.upgradeChannel=function(c){if(!this.inWindow||"Window"!==c.data.originEnv){var b=c.event.ports;if(b=b&&b[0])c.setSourcePort(b),this.listen(b),b.start();f("channel upgraded")}};b.prototype.onDisconnect=function(c){var b=this.clients[c.data];b&&(this.emit("before-disconnect",c),c.defaultPrevented||(this.removeClient(b.id),c.respond(),this.emit("disconnected",b.id),f("disconnected",
|
||||
b.id)))};b.prototype.onMethod=function(c){f("on method",c.data);this.emit("before-method",c);if(!c.defaultPrevented){var b=c.data,d=b.name,a,e=this.methods[d];if(!e)throw l(4,d);try{a=e.apply(this,b.args)}catch(g){a=g}c.respond(a)}};b.prototype.onOn=function(b){f("on on",b.data);this.emit("on",b.data)};b.prototype.onOff=function(b){f("on off");this.emit("off",b.data)};b.prototype.addClient=function(b,f){this.clients[b]={id:b,port:f}};b.prototype.removeClient=function(b){delete this.clients[b]};b.prototype.plugin=
|
||||
function(b){b(this,{uuid:n});return this};b.prototype.disconnect=function(b){this.removeClient(b.id);g("disconnect").set({recipient:b.id,noRespond:!0}).send(b.port)};b.prototype.destroy=function(){delete this.clients;this.unlisten();this.off()};h=b.prototype;h.broadcast=h.broadcast;h.destroy=h.destroy;h.method=h.method;h.plugin=h.plugin},{"./message":3,"./utils":6}],6:[function(e,h,m){m.uuid=function(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(b){var e=16*Math.random()|
|
||||
0;return("x"==b?e:e&3|8).toString(16)})};m.deferred=function(){var b={};b.promise=new Promise(function(e,h){b.resolve=e;b.reject=h});return b}},{}]},{},[1])(1)});
|
||||
449
gaia/apps/music/components/bridge/src/client.js
Executable file
|
|
@ -0,0 +1,449 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* Dependencies
|
||||
* @ignore
|
||||
*/
|
||||
|
||||
var createPort = require('./message/port-adaptors');
|
||||
var Emitter = require('./emitter');
|
||||
var message = require('./message');
|
||||
var uuid = require('./utils').uuid;
|
||||
|
||||
/**
|
||||
* Exports
|
||||
* @ignore
|
||||
*/
|
||||
|
||||
module.exports = Client;
|
||||
|
||||
/**
|
||||
* Mini Logger
|
||||
*
|
||||
* 0: off
|
||||
* 1: performance
|
||||
* 2: console.log
|
||||
*
|
||||
* @type {Function}
|
||||
* @private
|
||||
*/
|
||||
var debug = {
|
||||
0: () => {},
|
||||
1: arg => performance.mark(`[${self.constructor.name}][Client] - ${arg}`),
|
||||
2: (arg1, ...args) => {
|
||||
var type = `[${self.constructor.name}][${location.pathname}]`;
|
||||
console.log(`[Client]${type} - "${arg1}"`, ...args);
|
||||
}
|
||||
}[0];
|
||||
|
||||
/**
|
||||
* The type environment.
|
||||
* @type {String}
|
||||
*/
|
||||
var env = constructor.name
|
||||
|
||||
/**
|
||||
* A Client is a remote interface
|
||||
* to a Service within a given endpoint.
|
||||
*
|
||||
* See {@tutorial What's an endpoint?}
|
||||
* for more information on 'endpoints'.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* var endpoint = document.querySelector('iframe');
|
||||
* var client = bridge.client('my-service', endpoint);
|
||||
*
|
||||
* @constructor
|
||||
* @param {String} service The service name to connect to
|
||||
* @param {(Iframe|Worker|MessagePort|BroadcastChannel|Window)} endpoint
|
||||
* @param {Number} [timeout] Override default response timeout
|
||||
* The context/thread this service can be found in.
|
||||
* @public
|
||||
*/
|
||||
function Client(service, endpoint, timeout) {
|
||||
if (!(this instanceof Client)) return new Client(service, endpoint, timeout);
|
||||
|
||||
// Parameters can be passed as single object
|
||||
if (typeof service == 'object') {
|
||||
endpoint = service.endpoint;
|
||||
timeout = service.timeout;
|
||||
service = service.service;
|
||||
}
|
||||
|
||||
this.id = uuid();
|
||||
this.service = service;
|
||||
this.timeout = timeout;
|
||||
|
||||
// Keep a reference to the original endpoint
|
||||
// so that it's not garbage collected (Workers)
|
||||
this.endpoint = endpoint || this.endpoint;
|
||||
if (!this.endpoint) throw error(1);
|
||||
|
||||
this.setPort(this.endpoint);
|
||||
this.pending = new Set();
|
||||
|
||||
this.receiver = message.receiver(this.id)
|
||||
.on('_push', this.onPush.bind(this));
|
||||
|
||||
debug('initialized', service);
|
||||
}
|
||||
|
||||
Client.prototype = {
|
||||
|
||||
/**
|
||||
* Connect with the Service. Called
|
||||
* automatically internally, so
|
||||
* only required if you have
|
||||
* perposely called .disconnect().
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
connect() {
|
||||
debug('connect');
|
||||
if (this.connected) return this.connected;
|
||||
debug('connecting...', this.service);
|
||||
|
||||
var mc = new MessageChannel();
|
||||
this.channel = mc.port1;
|
||||
this.channel.start();
|
||||
|
||||
var data = {
|
||||
clientId: this.id,
|
||||
service: this.service,
|
||||
originEnv: env
|
||||
};
|
||||
|
||||
return this.connected = this.message('_connect')
|
||||
.set('transfer', [mc.port2])
|
||||
.set('data', data)
|
||||
.listen(mc.port1)
|
||||
.send()
|
||||
.then(response => {
|
||||
debug('connected', response);
|
||||
|
||||
// Check if the response came back on
|
||||
// the MessageChannel. If it did then
|
||||
// update the endpoint so that all
|
||||
// subsequent messaging uses this channel.
|
||||
var usingChannel = response.event.target === this.channel;
|
||||
if (usingChannel) this.setPort(this.channel);
|
||||
else {
|
||||
this.channel.close();
|
||||
delete this.channel;
|
||||
}
|
||||
|
||||
// Begin listening so that Clients can
|
||||
// respond to service pushed messages
|
||||
this.receiver.listen(this.port);
|
||||
})
|
||||
|
||||
// In the event of message timeout we
|
||||
// upgrade the message to something more
|
||||
// informative. console.error() is used to
|
||||
// makesure the message is seen even when
|
||||
// the user hasn't registered a .catch() handler.
|
||||
.catch(err => {
|
||||
var msg = err && err.message;
|
||||
if (msg == 'timeout') {
|
||||
err = error(2, this.service);
|
||||
console.error(err.message);
|
||||
}
|
||||
|
||||
throw err;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Disconnect from the `Service`.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
disconnect(options) {
|
||||
if (!this.connected) return Promise.resolve();
|
||||
debug('disconnecting ...');
|
||||
|
||||
var config = {
|
||||
noRespond: options && options.noRespond,
|
||||
data: this.id
|
||||
};
|
||||
|
||||
this.cancelPending();
|
||||
|
||||
return this.message('_disconnect')
|
||||
.set(config)
|
||||
.send()
|
||||
.then(() => this.onDisconnected());
|
||||
},
|
||||
|
||||
/**
|
||||
* Call a method on the connected Service.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* client.method('greet', 'wilson').then(result => {
|
||||
* console.log(result); //=> 'hello wilson'
|
||||
* });
|
||||
*
|
||||
* // my-service.js:
|
||||
*
|
||||
* service.method('greet', name => {
|
||||
* return 'hello ' + name;
|
||||
* });
|
||||
*
|
||||
* @param {String} name The method name
|
||||
* @param {...*} [args] Arguments to send
|
||||
* @return {Promise}
|
||||
*/
|
||||
method(name, ...args) {
|
||||
return this.connect()
|
||||
.then(() => {
|
||||
debug('method', name);
|
||||
return this.message('_method')
|
||||
.set({
|
||||
recipient: this.service,
|
||||
data: {
|
||||
name: name,
|
||||
args: args
|
||||
}
|
||||
})
|
||||
.send();
|
||||
})
|
||||
|
||||
// Only send back the response value
|
||||
.then(response => response.value)
|
||||
|
||||
// In the event of message timeout we
|
||||
// upgrade the message to something more
|
||||
// informative. console.error() is used to
|
||||
// make sure the message is seen even when
|
||||
// the user hasn't registered a .catch() handler.
|
||||
.catch(err => {
|
||||
var msg = err && err.message;
|
||||
if (msg == 'timeout') {
|
||||
err = error(3, name);
|
||||
console.error(err.message);
|
||||
}
|
||||
|
||||
throw err;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Use a plugin with this Client.
|
||||
* See {@tutorial Writing plugins}.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* client.plugin(megaPlugin);
|
||||
*
|
||||
* @param {Function} fn The plugin
|
||||
* @return {this} for chaining
|
||||
* @public
|
||||
*/
|
||||
plugin(fn) {
|
||||
fn(this, {
|
||||
'Emitter': Emitter,
|
||||
'uuid': uuid
|
||||
});
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* A wrapper around Message that
|
||||
* ensures pending messages are
|
||||
* noted and the Client's endpoint
|
||||
* is predefined.
|
||||
*
|
||||
* @param {String} type The message type
|
||||
* @return {Message}
|
||||
* @private
|
||||
*/
|
||||
message(type) {
|
||||
debug('create message', type);
|
||||
|
||||
var msg = message(type)
|
||||
.set('port', this.port)
|
||||
.set('timeout', this.timeout)
|
||||
.on('response', () => this.pending.delete(msg))
|
||||
.on('cancel', () => this.pending.delete(msg));
|
||||
|
||||
this.pending.add(msg);
|
||||
return msg;
|
||||
},
|
||||
|
||||
/**
|
||||
* Cancel any message that we have
|
||||
* not recieved a response from yet.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
cancelPending() {
|
||||
debug('cancel pending');
|
||||
this.pending.forEach(msg => { msg.cancel();});
|
||||
this.pending.clear();
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a Promise that resolves
|
||||
* once all pending messages have
|
||||
* responded.
|
||||
*
|
||||
* @private
|
||||
* @return {Promise}
|
||||
*/
|
||||
pendingResponded() {
|
||||
var responded = [];
|
||||
this.pending.forEach(msg => responded.push(msg.responded));
|
||||
return Promise.all(responded);
|
||||
},
|
||||
|
||||
/**
|
||||
* Emits a event when a 'push' Message
|
||||
* is recieved from the Service.
|
||||
*
|
||||
* @private
|
||||
* @param {Message} message The pushed message
|
||||
*/
|
||||
onPush(message) {
|
||||
debug('on push', message.data);
|
||||
this._emit(message.data.type, message.data.data);
|
||||
},
|
||||
|
||||
// Needs testing!
|
||||
onDisconnected() {
|
||||
delete this.connected;
|
||||
this.pendingResponded().then(() => {
|
||||
debug('disconnected');
|
||||
if (this.channel) this.channel.close();
|
||||
this._emit('disconnected');
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the port which all messages
|
||||
* will be sent over. This can differ
|
||||
* to the endpoint if we successfully
|
||||
* upgrade transport to MessageChannel.
|
||||
*
|
||||
* @param {(Iframe|Worker|MessagePort|BroadcastChannel|Window)} endpoint
|
||||
*/
|
||||
setPort(endpoint) {
|
||||
debug('set port');
|
||||
this.port = createPort(endpoint);
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroy the Client. Waits from all
|
||||
* pending Messages to have responded.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* client.destroy().then(() => ...);
|
||||
*
|
||||
* @public
|
||||
* @return {Promise}
|
||||
*/
|
||||
destroy: function() {
|
||||
return this.disconnect()
|
||||
.then(() => {
|
||||
if (this.destroyed) return;
|
||||
debug('destroy');
|
||||
this.destroyed = true;
|
||||
this.receiver.destroy();
|
||||
this._off();
|
||||
|
||||
// Wipe references
|
||||
this.port
|
||||
= this.endpoint
|
||||
= this.receiver
|
||||
= null;
|
||||
});
|
||||
},
|
||||
|
||||
_on: Emitter.prototype.on,
|
||||
_off: Emitter.prototype.off,
|
||||
_emit: Emitter.prototype.emit
|
||||
};
|
||||
|
||||
/**
|
||||
* Listen to a Service .broadcast() or .push().
|
||||
*
|
||||
* Services get notified whenever a Client
|
||||
* starts listening to a particular event.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* client
|
||||
* .on('importantevent', data => ...)
|
||||
* .on('thingchanged', thing => ...);
|
||||
*
|
||||
* @param {String} name The event name
|
||||
* @param {Function} fn Callback function
|
||||
* @return {this} for chaining
|
||||
* @public
|
||||
*/
|
||||
Client.prototype.on = function(name, fn) {
|
||||
this.connect().then(() => {
|
||||
debug('bind on', name);
|
||||
Emitter.prototype.on.call(this, name, fn);
|
||||
this.message('_on')
|
||||
.set('noRespond', true)
|
||||
.set('data', {
|
||||
name: name,
|
||||
clientId: this.id
|
||||
})
|
||||
.send(this.port);
|
||||
});
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Unlisten to a Service event.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* client
|
||||
* .off('importantevent') // remove all
|
||||
* .off('thingchanged', onThingChanged); // remove one
|
||||
*
|
||||
* @this Client
|
||||
* @param {String} name The event name
|
||||
* @param {Function} fn Callback function
|
||||
* @return {this} for chaining
|
||||
* @public
|
||||
*/
|
||||
Client.prototype.off = function(name, fn) {
|
||||
this.connect().then(() => {
|
||||
Emitter.prototype.off.call(this, name, fn);
|
||||
this.message('_off')
|
||||
.set('noRespond', true)
|
||||
.set('data', {
|
||||
name: name,
|
||||
clientId: this.id
|
||||
})
|
||||
.send(this.port);
|
||||
});
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates new `Error` from registery.
|
||||
*
|
||||
* @param {Number} id Error Id
|
||||
* @return {Error}
|
||||
* @private
|
||||
*/
|
||||
|
||||
function error(id, ...args) {
|
||||
/*jshint maxlen:false*/
|
||||
var help = 'Either the target endpoint is not alive or the Service is not `.listen()`ing.';
|
||||
return new Error({
|
||||
1: 'an endpoint must be defined',
|
||||
2: `Unable to establish a connection with "${args[0]}". ${help}`,
|
||||
3: `Method "${args[0]}" didn't get a response. ${help}`
|
||||
}[id]);
|
||||
}
|
||||
104
gaia/apps/music/components/bridge/src/emitter.js
Executable file
|
|
@ -0,0 +1,104 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* Exports
|
||||
* @ignore
|
||||
*/
|
||||
|
||||
module.exports = Emitter;
|
||||
|
||||
/**
|
||||
* Simple logger
|
||||
*
|
||||
* @type {Function}
|
||||
* @private
|
||||
*/
|
||||
|
||||
var debug = 0 ? console.log.bind(console, '[Emitter]') : () => {};
|
||||
|
||||
/**
|
||||
* Create new `Emitter`
|
||||
*
|
||||
* @class Emitter
|
||||
*/
|
||||
|
||||
function Emitter(host) {
|
||||
if (host) return Object.assign(host, Emitter.prototype);
|
||||
}
|
||||
|
||||
Emitter.prototype = {
|
||||
|
||||
/**
|
||||
* Add an event listener.
|
||||
*
|
||||
* It is possible to subscript to * events.
|
||||
*
|
||||
* @param {String} type
|
||||
* @param {Function} callback
|
||||
* @return {this} for chaining
|
||||
*/
|
||||
|
||||
on: function(type, callback) {
|
||||
debug('on', type, callback);
|
||||
if (!this._callbacks) this._callbacks = {};
|
||||
if (!this._callbacks[type]) this._callbacks[type] = [];
|
||||
this._callbacks[type].push(callback);
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove an event listener.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* emitter.off('name', fn); // remove one callback
|
||||
* emitter.off('name'); // remove all callbacks for 'name'
|
||||
* emitter.off(); // remove all callbacks
|
||||
*
|
||||
* @param {String} [type]
|
||||
* @param {Function} [callback]
|
||||
* @return {this} for chaining
|
||||
*/
|
||||
|
||||
off: function(type, callback) {
|
||||
debug('off', type, callback);
|
||||
if (this._callbacks) {
|
||||
switch (arguments.length) {
|
||||
case 0: this._callbacks = {}; break;
|
||||
case 1: delete this._callbacks[type]; break;
|
||||
default:
|
||||
var typeListeners = this._callbacks[type];
|
||||
if (!typeListeners) return;
|
||||
var i = typeListeners.indexOf(callback);
|
||||
if (~i) typeListeners.splice(i, 1);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Emit an event.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* emitter.emit('name', { some: 'data' });
|
||||
*
|
||||
* @param {String} type
|
||||
* @param {*} [data]
|
||||
* @return {this} for chaining
|
||||
*/
|
||||
|
||||
emit: function(type, data) {
|
||||
debug('emit', type, data);
|
||||
if (this._callbacks) {
|
||||
var fns = this._callbacks[type] || [];
|
||||
fns = fns.concat(this._callbacks['*'] || []);
|
||||
for (var i = 0; i < fns.length; i++) fns[i].call(this, data, type);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
var p = Emitter.prototype;
|
||||
p['off'] = p.off;
|
||||
p['on'] = p.on;
|
||||
441
gaia/apps/music/components/bridge/src/message/index.js
Executable file
|
|
@ -0,0 +1,441 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* Dependencies
|
||||
* @ignore
|
||||
*/
|
||||
|
||||
var createPort = require('./port-adaptors');
|
||||
var Emitter = require('../emitter');
|
||||
var utils = require('../utils');
|
||||
var defer = utils.deferred;
|
||||
var uuid = utils.uuid;
|
||||
|
||||
/**
|
||||
* Exports
|
||||
* @ignore
|
||||
*/
|
||||
|
||||
exports = module.exports = type => new Message(type);
|
||||
exports.receiver = (id, n) => new Receiver(id, n);
|
||||
exports.Receiver = Receiver;
|
||||
exports.Message = Message;
|
||||
|
||||
/**
|
||||
* Mini Logger
|
||||
*
|
||||
* 0: off
|
||||
* 1: performance
|
||||
* 2: console.log
|
||||
*
|
||||
* @type {Function}
|
||||
* @private
|
||||
*/
|
||||
var debug = {
|
||||
0: () => {},
|
||||
1: arg => performance.mark(`[${self.constructor.name}][Message] - ${arg}`),
|
||||
2: (arg1, ...args) => {
|
||||
var type = `[${self.constructor.name}][${location.pathname}]`;
|
||||
console.log(`[Message]${type} - "${arg1}"`, ...args);
|
||||
}
|
||||
}[0];
|
||||
|
||||
/**
|
||||
* Default response timeout.
|
||||
* @type {Number}
|
||||
* @private
|
||||
*/
|
||||
var TIMEOUT = 1000;
|
||||
|
||||
/**
|
||||
* Initialize a new `Message`
|
||||
*
|
||||
* @class Message
|
||||
* @borrows Emitter#on as #on
|
||||
* @borrows Emitter#off as #off
|
||||
* @borrows Emitter#emit as #emit
|
||||
* @param {String} type Message type
|
||||
*/
|
||||
function Message(type) {
|
||||
this.cancelled = false;
|
||||
this.listeners = [];
|
||||
this.deferred = defer();
|
||||
this.onMessage = this.onMessage.bind(this);
|
||||
this.onTimeout = this.onTimeout.bind(this);
|
||||
if (typeof type === 'object') this.setupInbound(type);
|
||||
else this.setupOutbound(type);
|
||||
debug('initialized', type);
|
||||
}
|
||||
|
||||
Message.prototype = {
|
||||
setupOutbound(type) {
|
||||
this.id = uuid();
|
||||
this.type = type;
|
||||
this.sent = false;
|
||||
this.recipient = '*';
|
||||
},
|
||||
|
||||
setupInbound (e) {
|
||||
debug('inbound');
|
||||
this.hasResponded = false;
|
||||
this.setSourcePort(e.source || e.target);
|
||||
|
||||
// Keep a reference to the MessageEvent
|
||||
this.event = e;
|
||||
|
||||
// Mixin the properties of the original message
|
||||
Object.assign(this, e.data);
|
||||
},
|
||||
|
||||
setSourcePort(endpoint) {
|
||||
debug('set source', endpoint.constructor.name);
|
||||
this.sourcePort = createPort(endpoint, { ready: true });
|
||||
return this;
|
||||
},
|
||||
|
||||
set(key, value) {
|
||||
debug('set', key, value);
|
||||
if (typeof key == 'object') Object.assign(this, key);
|
||||
else this[key] = value;
|
||||
return this;
|
||||
},
|
||||
|
||||
serialize() {
|
||||
return {
|
||||
id: this.id,
|
||||
type: this.type,
|
||||
data: this.data,
|
||||
recipient: this.recipient,
|
||||
noRespond: this.noRespond
|
||||
};
|
||||
},
|
||||
|
||||
preventDefault() {
|
||||
debug('prevent default');
|
||||
this.defaultPrevented = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Send the message to an endpoint.
|
||||
*
|
||||
* @param {(Iframe|Window|Worker|MessagePort)} endpoint
|
||||
* @return {Promise}
|
||||
*/
|
||||
send(endpoint) {
|
||||
debug('send', this.type);
|
||||
if (this.sent) throw error(1);
|
||||
var serialized = this.serialize();
|
||||
var expectsResponse = !this.noRespond;
|
||||
|
||||
// A port is resolved from either a predefined
|
||||
// port, or an endpoint given as first argument
|
||||
this.port = endpoint ? createPort(endpoint) : this.port;
|
||||
if (!this.port) throw error(3);
|
||||
|
||||
// If we're expecting a response listen
|
||||
// on the port else resolve promise instantly
|
||||
if (expectsResponse) {
|
||||
this.listen(this.port);
|
||||
this.setResponseTimeout();
|
||||
} else this.deferred.resolve();
|
||||
|
||||
this.port.postMessage(serialized, this.getTransfer());
|
||||
debug('sent', serialized);
|
||||
return this.deferred.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the response timeout.
|
||||
*
|
||||
* When set to `false` no timeout
|
||||
* is installed.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
setResponseTimeout() {
|
||||
if (this.timeout === false) return;
|
||||
var ms = this.timeout || TIMEOUT;
|
||||
this._timer = setTimeout(this.onTimeout, ms);
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear the response timeout.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
clearResponseTimeout() {
|
||||
clearTimeout(this._timer);
|
||||
},
|
||||
|
||||
getTransfer() {
|
||||
return this.transfer || this.event && this.event.ports;
|
||||
},
|
||||
|
||||
onMessage(e) {
|
||||
var valid = !!e.data.response
|
||||
&& e.data.id === this.id
|
||||
&& !this.cancelled;
|
||||
|
||||
if (valid) this.onResponse(e);
|
||||
},
|
||||
|
||||
onTimeout() {
|
||||
debug('response timeout', this.type);
|
||||
if (!this.silentTimeout) this.deferred.reject(error(4));
|
||||
this.teardown();
|
||||
},
|
||||
|
||||
listen(thing) {
|
||||
debug('add response listener', thing);
|
||||
var port = createPort(thing);
|
||||
port.addListener(this.onMessage);
|
||||
this.listeners.push(port);
|
||||
return this;
|
||||
},
|
||||
|
||||
unlisten() {
|
||||
debug('remove response listeners');
|
||||
this.listeners.forEach(port => port.removeListener(this.onMessage));
|
||||
this.listeners = [];
|
||||
},
|
||||
|
||||
/**
|
||||
* Cancel a pending Message.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* var msg = message('foo')
|
||||
*
|
||||
* msg.send(new Worker('my-worker.js'))
|
||||
* .then(response => {
|
||||
* // this will never run
|
||||
* })
|
||||
*
|
||||
* msg.cancel();
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
cancel() {
|
||||
this.teardown();
|
||||
this.cancelled = true;
|
||||
this.emit('cancel');
|
||||
},
|
||||
|
||||
teardown() {
|
||||
this.clearResponseTimeout();
|
||||
this.unlisten();
|
||||
},
|
||||
|
||||
/**
|
||||
* Respond to a message.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* receiver.on('hello', message => {
|
||||
* message.respond('world');
|
||||
* });
|
||||
*
|
||||
* @public
|
||||
* @param {*} [result] Data to send back with the response
|
||||
*/
|
||||
respond(result) {
|
||||
debug('respond', result);
|
||||
|
||||
if (this.hasResponded) throw error(2);
|
||||
if (!this.sourcePort) return;
|
||||
if (this.noRespond) return;
|
||||
|
||||
var self = this;
|
||||
this.hasResponded = true;
|
||||
|
||||
// Repsond with rejection when result is an `Error`
|
||||
if (result instanceof Error) reject(result);
|
||||
|
||||
// Call the handler and make
|
||||
// sure return value is a promise.
|
||||
// If the returned value is unclonable
|
||||
// then the send() method will throw,
|
||||
// the .catch() will reject in this case.
|
||||
Promise.resolve(result)
|
||||
.then(resolve, reject)
|
||||
.catch(reject);
|
||||
|
||||
function resolve(value) {
|
||||
debug('resolve', value);
|
||||
respond({
|
||||
type: 'resolve',
|
||||
value: value
|
||||
});
|
||||
}
|
||||
|
||||
function reject(err) {
|
||||
var msg = err && err.message || err;
|
||||
debug('reject', msg);
|
||||
respond({
|
||||
type: 'reject',
|
||||
value: msg
|
||||
});
|
||||
}
|
||||
|
||||
function respond(response) {
|
||||
self.response = response;
|
||||
self.sourcePort.postMessage({
|
||||
id: self.id,
|
||||
response: response
|
||||
}, self.transfer);
|
||||
|
||||
debug('responded with:', response);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Forward a `Message` onto another endpoint.
|
||||
*
|
||||
* The `silentTrue` option prevents the
|
||||
* message request timing out should
|
||||
* the response come back via an
|
||||
* alternative route.
|
||||
*
|
||||
* @param {(HTMLIframeElement|MessagePort|Window)} endpoint
|
||||
* @public
|
||||
*/
|
||||
forward(endpoint) {
|
||||
debug('forward');
|
||||
return this
|
||||
.set('silentTimeout', true)
|
||||
.send(endpoint)
|
||||
.then(result => this.respond(result.value));
|
||||
},
|
||||
|
||||
onResponse(e) {
|
||||
debug('on response', e.data);
|
||||
var response = e.data.response;
|
||||
var type = response.type;
|
||||
var value = type == 'reject'
|
||||
? response.value
|
||||
: response;
|
||||
|
||||
response.event = e;
|
||||
this.response = response;
|
||||
this.teardown();
|
||||
|
||||
this.deferred[this.response.type](value);
|
||||
this.emit('response', response);
|
||||
}
|
||||
};
|
||||
|
||||
// Mixin Emitter methods
|
||||
Emitter(Message.prototype);
|
||||
|
||||
/**
|
||||
* Initialize a new `Receiver`.
|
||||
*
|
||||
* @class Receiver
|
||||
* @extends Emitter
|
||||
* @param {String} name - corresponds to `Message.recipient`
|
||||
*/
|
||||
function Receiver(name) {
|
||||
this.name = name;
|
||||
this.ports = new Set();
|
||||
this.onMessage = this.onMessage.bind(this);
|
||||
this['listen'] = this['listen'].bind(this);
|
||||
this['unlisten'] = this['unlisten'].bind(this);
|
||||
debug('receiver initialized', name);
|
||||
}
|
||||
|
||||
Receiver.prototype = {
|
||||
|
||||
/**
|
||||
* Begin listening for inbound messages.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* // When no arguments are given
|
||||
* // messages will be listened for
|
||||
* // on the default global scope
|
||||
* .listen();
|
||||
*
|
||||
* // When an endpoint is out of reach
|
||||
* // BroadcastChannel can be used.
|
||||
* .listen(new BroadcastChannel('foo'));
|
||||
*
|
||||
* @param {(HTMLIframeElement|Worker|MessagePort|
|
||||
* BroadcastChannel|Window|Object)} [thing]
|
||||
* @public
|
||||
*/
|
||||
listen(thing) {
|
||||
debug('listen');
|
||||
var _port = createPort(thing || self, { receiver: true });
|
||||
if (this.ports.has(_port)) return;
|
||||
_port.addListener(this.onMessage, this.listen);
|
||||
this.ports.add(_port);
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Stop listening for inbound messages
|
||||
* on all endpoints listened to prior.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
unlisten() {
|
||||
debug('unlisten');
|
||||
this.ports.forEach(port => {
|
||||
port.removeListener(this.onMessage, this.unlisten);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Callback to handle inbound messages.
|
||||
* @param {MessageEvent} e
|
||||
* @private
|
||||
*/
|
||||
onMessage(e) {
|
||||
if (!e.data.id) return;
|
||||
if (!e.data.type) return;
|
||||
if (!this.isRecipient(e.data.recipient)) return;
|
||||
debug('receiver on message', e.data);
|
||||
var message = new Message(e);
|
||||
|
||||
// Before hook
|
||||
this.emit('message', message);
|
||||
if (message.defaultPrevented) return;
|
||||
|
||||
try { this.emit(message.type, message); }
|
||||
catch (e) {
|
||||
message.respond(e);
|
||||
throw e;
|
||||
}
|
||||
},
|
||||
|
||||
isRecipient(recipient) {
|
||||
return recipient == this.name
|
||||
|| recipient == '*'
|
||||
|| this.name == '*';
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
this.unlisten();
|
||||
delete this.name;
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
// Mixin Emitter methods
|
||||
Emitter(Receiver.prototype);
|
||||
|
||||
/**
|
||||
* Creates new `Error` from registry.
|
||||
*
|
||||
* @param {Number} id Error Id
|
||||
* @return {Error}
|
||||
* @private
|
||||
*/
|
||||
function error(id, ...args) {
|
||||
return new Error({
|
||||
1: '.send() can only be called once',
|
||||
2: 'response already sent for this message',
|
||||
3: 'a port must be defined',
|
||||
4: 'timeout'
|
||||
}[id]);
|
||||
}
|
||||
282
gaia/apps/music/components/bridge/src/message/port-adaptors.js
Executable file
|
|
@ -0,0 +1,282 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* Dependencies
|
||||
* @ignore
|
||||
*/
|
||||
|
||||
var deferred = require('../utils').deferred;
|
||||
|
||||
/**
|
||||
* Message event name
|
||||
* @type {String}
|
||||
*/
|
||||
const MSG = 'message';
|
||||
|
||||
/**
|
||||
* Mini Logger
|
||||
* @type {Function}
|
||||
* @private
|
||||
*/
|
||||
var debug = 0 ? function(arg1, ...args) {
|
||||
var type = `[${self.constructor.name}][${location.pathname}]`;
|
||||
console.log(`[PortAdaptor]${type} - "${arg1}"`, ...args);
|
||||
} : () => {};
|
||||
|
||||
/**
|
||||
* Creates a bridge.js port abstraction
|
||||
* with a consistent interface.
|
||||
*
|
||||
* @param {Object} target
|
||||
* @param {Object} options
|
||||
* @return {PortAdaptor}
|
||||
*/
|
||||
module.exports = function create(target, options) {
|
||||
if (!target) throw error(1);
|
||||
if (isEndpoint(target)) return target;
|
||||
var type = target.constructor.name;
|
||||
var CustomAdaptor = adaptors[type];
|
||||
debug('creating port adaptor for', type);
|
||||
if (CustomAdaptor) return CustomAdaptor(target, options);
|
||||
return new PortAdaptor(target, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* The default adaptor.
|
||||
* @private
|
||||
*/
|
||||
function PortAdaptor(target) {
|
||||
debug('PortAdaptor');
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
var PortAdaptorProto = PortAdaptor.prototype = {
|
||||
constructor: PortAdaptor,
|
||||
addListener(callback) { on(this.target, MSG, callback); },
|
||||
removeListener(callback) { off(this.target, MSG, callback); },
|
||||
postMessage(data, transfer) { this.target.postMessage(data, transfer); }
|
||||
};
|
||||
|
||||
/**
|
||||
* A registry of specific adaptors
|
||||
* for when the default PortAdaptor
|
||||
* is not suitable.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
var adaptors = {
|
||||
|
||||
/**
|
||||
* Create an HTMLIframeElement PortAdaptor.
|
||||
*
|
||||
* @param {HTMLIframeElement} iframe
|
||||
*/
|
||||
'HTMLIFrameElement': function(iframe) {
|
||||
debug('HTMLIFrameElement');
|
||||
var ready = windowReady(iframe);
|
||||
return {
|
||||
addListener(callback, listen) { on(window, MSG, callback); },
|
||||
removeListener(callback, listen) { off(window, MSG, callback); },
|
||||
postMessage(data, transfer) {
|
||||
ready.then(() => postMessageSync(iframe.contentWindow, data, transfer));
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a BroadcastChannel port-adaptor.
|
||||
*
|
||||
* @param {Object} channel
|
||||
* @param {[type]} options [description]
|
||||
*/
|
||||
'BroadcastChannel': function(channel, options) {
|
||||
debug('BroadcastChannel', channel.name);
|
||||
var receiver = options && options.receiver;
|
||||
var ready = options && options.ready;
|
||||
var sendReady = () => {
|
||||
channel.postMessage('ready');
|
||||
debug('sent ready');
|
||||
};
|
||||
|
||||
ready = ready || receiver
|
||||
? Promise.resolve()
|
||||
: setupSender();
|
||||
|
||||
if (receiver) {
|
||||
sendReady();
|
||||
on(channel, MSG, e => {
|
||||
if (e.data != 'ready?') return;
|
||||
sendReady();
|
||||
});
|
||||
}
|
||||
|
||||
function setupSender() {
|
||||
debug('setup sender');
|
||||
var promise = deferred();
|
||||
|
||||
channel.postMessage('ready?');
|
||||
on(channel, MSG, function fn(e) {
|
||||
if (e.data != 'ready') return;
|
||||
off(channel, MSG, fn);
|
||||
debug('BroadcastChannel: ready');
|
||||
promise.resolve();
|
||||
});
|
||||
|
||||
return promise.promise;
|
||||
}
|
||||
|
||||
return {
|
||||
target: channel,
|
||||
addListener: PortAdaptorProto.addListener,
|
||||
removeListener: PortAdaptorProto.removeListener,
|
||||
postMessage(data, transfer) {
|
||||
ready.then(() => channel.postMessage(data, transfer));
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
'Window': function(win, options) {
|
||||
debug('Window');
|
||||
var ready = options && options.ready
|
||||
|| win === parent // parent always ready
|
||||
|| win === self; // self always ready
|
||||
|
||||
ready = ready ? Promise.resolve() : windowReady(win);
|
||||
|
||||
return {
|
||||
addListener(callback, listen) { on(window, MSG, callback); },
|
||||
removeListener(callback, listen) { off(window, MSG, callback); },
|
||||
postMessage(data, transfer) {
|
||||
ready.then(() => postMessageSync(win, data, transfer));
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
'SharedWorker': function(worker) {
|
||||
worker.port.start();
|
||||
return new PortAdaptor(worker.port);
|
||||
},
|
||||
|
||||
'SharedWorkerGlobalScope': function() {
|
||||
var ports = [];
|
||||
|
||||
return {
|
||||
postMessage() {}, // noop
|
||||
addListener(callback, listen) {
|
||||
this.onconnect = e => {
|
||||
var port = e.ports[0];
|
||||
ports.push(port);
|
||||
port.start();
|
||||
listen(port);
|
||||
};
|
||||
|
||||
on(self, 'connect', this.onconnect);
|
||||
},
|
||||
|
||||
removeListener(callback, unlisten) {
|
||||
off(self, 'connect', this.onconnect);
|
||||
ports.forEach(port => {
|
||||
port.close();
|
||||
unlisten(port);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Return a Promise that resolves
|
||||
* when a Window is ready to start
|
||||
* recieving messages.
|
||||
*
|
||||
* @param {Window} target
|
||||
* @return {Promise}
|
||||
*/
|
||||
var windowReady = (function() {
|
||||
if (typeof window == 'undefined') return;
|
||||
var parent = window.opener || window.parent;
|
||||
var domReady = 'DOMContentLoaded';
|
||||
var windows = new WeakSet();
|
||||
|
||||
// Side B: Dispatches 'load'
|
||||
// from the child window
|
||||
if (parent != self) {
|
||||
on(window, domReady, function fn() {
|
||||
off(window, domReady, fn);
|
||||
postMessageSync(parent, 'load');
|
||||
});
|
||||
}
|
||||
|
||||
// Side A: Listens for 'ready' in the parent window
|
||||
on(self, 'message', e => e.data == 'load' && windows.add(e.source));
|
||||
|
||||
return target => {
|
||||
var win = target.contentWindow || target;
|
||||
|
||||
// Ready if the target has previously announces itself ready
|
||||
if (windows.has(win)) return Promise.resolve();
|
||||
|
||||
// Ready if the target is the parent window
|
||||
if (win == window.parent) return Promise.resolve();
|
||||
|
||||
var def = deferred();
|
||||
debug('waiting for Window to be ready ...');
|
||||
on(window, 'message', function fn(e) {
|
||||
if (e.data == 'load' && e.source == win) {
|
||||
debug('Window ready');
|
||||
off(window, 'message', fn);
|
||||
def.resolve();
|
||||
}
|
||||
});
|
||||
return def.promise;
|
||||
};
|
||||
})();
|
||||
|
||||
/**
|
||||
* Utils
|
||||
* @ignore
|
||||
*/
|
||||
|
||||
function isEndpoint(thing) {
|
||||
return !!(thing && thing.addListener);
|
||||
}
|
||||
|
||||
// Shorthand
|
||||
function on(target, name, fn) { target.addEventListener(name, fn); }
|
||||
function off(target, name, fn) { target.removeEventListener(name, fn); }
|
||||
|
||||
/**
|
||||
* Dispatches syncronous 'message'
|
||||
* event on a Window.
|
||||
*
|
||||
* We use this because standard
|
||||
* window.postMessage() gets blocked
|
||||
* until the main-thread is free.
|
||||
*
|
||||
* @param {Window} win
|
||||
* @param {*} data
|
||||
* @private
|
||||
*/
|
||||
function postMessageSync(win, data, transfer) { try { void win.document; } catch (_) { win.postMessage(data, location.origin, transfer || []); return; }
|
||||
var event = {
|
||||
data: data,
|
||||
source: self
|
||||
};
|
||||
|
||||
if (transfer) event.ports = transfer;
|
||||
|
||||
win.dispatchEvent(new MessageEvent('message', event));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates new `Error` from registery.
|
||||
*
|
||||
* @param {Number} id Error Id
|
||||
* @return {Error}
|
||||
* @private
|
||||
*/
|
||||
function error(id) {
|
||||
return new Error({
|
||||
1: 'target is undefined'
|
||||
}[id]);
|
||||
}
|
||||
9
gaia/apps/music/components/bridge/src/plugins/contracts/service.js
Executable file
|
|
@ -0,0 +1,9 @@
|
|||
!((define)=>{define((require,exports,module)=>{
|
||||
|
||||
module.exports = function() {
|
||||
|
||||
};
|
||||
|
||||
});})((typeof define)[0]=='f'&&define.amd?define:((n,w)=>{return(typeof
|
||||
module)[0]=='o'?c=>{c(require,exports,module);}:(c)=>{var m={exports:{}};
|
||||
c(n=>w[n],m.exports,m);w[n]=m.exports;};})('contractsPlugin',this));
|
||||
197
gaia/apps/music/components/bridge/src/plugins/stream/client.js
Executable file
|
|
@ -0,0 +1,197 @@
|
|||
!((define)=>{define((require,exports,module)=>{
|
||||
|
||||
/**
|
||||
* Mini Logger
|
||||
*
|
||||
* @type {Function}
|
||||
*/
|
||||
|
||||
var debug = 0 ? function(arg1, ...args) {
|
||||
var type = `[${self.constructor.name}][${location.pathname}]`;
|
||||
console.log(`[ClientStream]${type} - "${arg1}"`, ...args);
|
||||
} : () => {};
|
||||
|
||||
/**
|
||||
* Exports
|
||||
*/
|
||||
|
||||
module.exports = (client, utils) => {
|
||||
client._activeStreams = [];
|
||||
client.on('streamevent', onStreamEvent);
|
||||
|
||||
client.stream = function(name) {
|
||||
debug('stream open', name);
|
||||
var args = [].slice.call(arguments, 1);
|
||||
var stream = new ClientStream(client, utils);
|
||||
var self = this;
|
||||
|
||||
this.method('_stream', name, stream.id, this.id, args)
|
||||
.then(() => {
|
||||
debug('stream connected', name);
|
||||
stream._connected.resolve();
|
||||
})
|
||||
|
||||
.catch(err => {
|
||||
debug('error', err);
|
||||
onStreamEvent({
|
||||
type: 'abort',
|
||||
id: stream.id,
|
||||
data: err
|
||||
});
|
||||
});
|
||||
|
||||
this._activeStreams[stream.id] = stream;
|
||||
stream.emitter.on('cancel', onEnd);
|
||||
stream.emitter.on('abort', onEnd);
|
||||
stream.emitter.on('close', onEnd);
|
||||
|
||||
function onEnd() {
|
||||
debug('end');
|
||||
delete self._activeStreams[stream.id];
|
||||
stream.destroy();
|
||||
}
|
||||
|
||||
return stream;
|
||||
};
|
||||
|
||||
/**
|
||||
* Called every time the service calls
|
||||
* write/abort/close on the ServiceStream
|
||||
*
|
||||
* @param {Object} broadcast
|
||||
* @param {String} broadcast.id Stream ID
|
||||
* @param {String} broadcast.type Event type ('write', 'abort' or 'close')
|
||||
* @private
|
||||
*/
|
||||
|
||||
function onStreamEvent(data) {
|
||||
debug('stream event', data.type);
|
||||
var stream = client._activeStreams[data.streamId];
|
||||
stream.emitter.emit(data.type, data.data);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Readable stream instance returned by
|
||||
* a `client.stream('methodName')` call.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {String} options.id Stream Id, used to match client/service streams
|
||||
* @param {Client} options.client Client instance
|
||||
*/
|
||||
|
||||
function ClientStream(client, utils) {
|
||||
this.id = utils.uuid();
|
||||
this.client = client;
|
||||
this.emitter = new utils.Emitter();
|
||||
this.emitter.on('close', this.onClose.bind(this));
|
||||
this.emitter.on('abort', this.onAbort.bind(this));
|
||||
this._connected = defer();
|
||||
this.connected = this._connected.promise;
|
||||
this._closed = defer();
|
||||
this.closed = this._closed.promise;
|
||||
debug('initialized');
|
||||
}
|
||||
|
||||
/**
|
||||
* Prototype assigned to variable
|
||||
* to improve compression.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
ClientStream.prototype = {
|
||||
|
||||
/**
|
||||
* Add a listener that will be called
|
||||
* every time the service broadcasts
|
||||
* a new chunk of data.
|
||||
*
|
||||
* @param {Function} callback
|
||||
*/
|
||||
|
||||
listen(callback) {
|
||||
debug('listen', callback);
|
||||
this.emitter.on('write', callback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes 'data' listener
|
||||
*
|
||||
* @param {Function} callback
|
||||
*/
|
||||
|
||||
unlisten(callback) {
|
||||
debug('unlisten', callback);
|
||||
this.emitter.off('write', callback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Notify the service that
|
||||
* action should be canceled
|
||||
*
|
||||
* @param {*} [reason] Optional data to be sent to service.
|
||||
*/
|
||||
|
||||
cancel(reason) {
|
||||
debug('cancel', reason);
|
||||
return this.client.method('_streamcancel', this.id, reason)
|
||||
.then(result => {
|
||||
debug('cancelled', result);
|
||||
this.emitter.emit('cancel');
|
||||
return result;
|
||||
})
|
||||
|
||||
.catch(e => {
|
||||
this.emitter.emit('cancel');
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Used internally by Client when
|
||||
* it receives an 'abort' event
|
||||
* from the service.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
|
||||
onAbort(reason) {
|
||||
debug('abort', reason);
|
||||
this._closed.reject(reason);
|
||||
},
|
||||
|
||||
/**
|
||||
* Used internally by Client when
|
||||
* it receives a 'close' event
|
||||
* from the service.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
|
||||
onClose() {
|
||||
debug('close');
|
||||
this._closed.resolve();
|
||||
},
|
||||
|
||||
destroy() {
|
||||
debug('destroy');
|
||||
this.emitter.off();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Utils
|
||||
*/
|
||||
|
||||
function defer() {
|
||||
var result = {};
|
||||
result.promise = new Promise((resolve, reject) => {
|
||||
result.resolve = resolve;
|
||||
result.reject = reject;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
});})((typeof define)[0]=='f'&&define.amd?define:((n,w)=>{return(typeof
|
||||
module)[0]=='o'?c=>{c(require,exports,module);}:(c)=>{var m={exports:{}};
|
||||
c(n=>w[n],m.exports,m);w[n]=m.exports;};})('streamClient',this));
|
||||
206
gaia/apps/music/components/bridge/src/plugins/stream/service.js
Executable file
|
|
@ -0,0 +1,206 @@
|
|||
!((define)=>{define((require,exports,module)=>{
|
||||
|
||||
/**
|
||||
* Mini Logger
|
||||
*
|
||||
* @type {Function}
|
||||
*/
|
||||
|
||||
var debug = 0 ? function(arg1, ...args) {
|
||||
var type = `[${self.constructor.name}][${location.pathname}]`;
|
||||
console.log(`[ServiceStream]${type} - "${arg1}"`, ...args);
|
||||
} : () => {};
|
||||
|
||||
module.exports = function(service) {
|
||||
debug('attaching plugin');
|
||||
|
||||
service
|
||||
.method('_stream', onStream)
|
||||
.method('_streamcancel', onStreamCancel);
|
||||
|
||||
service._streamHandlers = {};
|
||||
service._activeStreams = [];
|
||||
|
||||
service.stream = function(name, fn) {
|
||||
debug('register stream', name);
|
||||
this._streamHandlers[name] = fn;
|
||||
return this;
|
||||
};
|
||||
|
||||
function onStream(name, id, clientId, args) {
|
||||
debug('on stream open', name);
|
||||
var e = {
|
||||
preventDefault: () => e.defaultPrevented = true,
|
||||
arguments: arguments
|
||||
};
|
||||
|
||||
// Allow other plugins to override default behaviour
|
||||
service.emit('before-stream', e);
|
||||
if (e.defaultPrevented) return;
|
||||
|
||||
var fn = service._streamHandlers[name];
|
||||
if (!fn) throw error(1, name);
|
||||
|
||||
var stream = new ServiceStream(id, service, clientId);
|
||||
service._activeStreams[id] = stream;
|
||||
|
||||
// Always pass stream object as first
|
||||
// argument to simplify the process
|
||||
fn.apply(service, [stream].concat(args));
|
||||
}
|
||||
|
||||
function onStreamCancel(id, reason) {
|
||||
debug('stream cancel');
|
||||
var stream = service._activeStreams[id];
|
||||
delete service._activeStreams[id];
|
||||
return stream.cancel(reason);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Writable Stream instance passed to the
|
||||
* `service.stream` implementation
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {String} options.id Stream ID used to sync client and service streams
|
||||
* @param {BroadcastChannel} options.channel Channel used to postMessage
|
||||
* @param {String} options.serviceId ID of the service
|
||||
* @param {String} options.clientId ID of client that should receive message
|
||||
*/
|
||||
|
||||
function ServiceStream(streamId, service, clientId) {
|
||||
this.id = streamId;
|
||||
this.service = service;
|
||||
this.clientId = clientId;
|
||||
this.writable = true;
|
||||
debug('initialized');
|
||||
}
|
||||
|
||||
/**
|
||||
* Prototype assigned to variable
|
||||
* to improve compression.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
var ServiceStreamPrototype = ServiceStream.prototype;
|
||||
|
||||
/**
|
||||
* Services that allows clients to
|
||||
* cancel the operation before it's
|
||||
* complete should override the
|
||||
* `stream.cancel` method.
|
||||
*
|
||||
* @param {*} [reason] Data sent from client about the cancellation
|
||||
* @returns {(Promise|*)}
|
||||
*/
|
||||
|
||||
ServiceStreamPrototype.cancel = function(reason) {
|
||||
var err = new TypeError('service should implement stream.cancel()');
|
||||
return Promise.reject(err);
|
||||
};
|
||||
|
||||
/**
|
||||
* Signal to client that action was
|
||||
* aborted during the process, this
|
||||
* should be used as a way to
|
||||
* communicate errors.
|
||||
*
|
||||
* @param {*} [data] Reason of failure
|
||||
* @returns {Promise}
|
||||
* @public
|
||||
*/
|
||||
|
||||
ServiceStreamPrototype.abort = function(data) {
|
||||
debug('abort', data);
|
||||
this._post('abort', data);
|
||||
this.writable = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sends a chunk of data to the client.
|
||||
*
|
||||
* @param {*} data Chunk of data to be sent to client.
|
||||
* @returns {Promise}
|
||||
* @public
|
||||
*/
|
||||
|
||||
ServiceStreamPrototype.write = function(data) {
|
||||
debug('write', data);
|
||||
this._post('write', data);
|
||||
};
|
||||
|
||||
/**
|
||||
* Closes the stream, signals that
|
||||
* action was completed with success.
|
||||
*
|
||||
* According to whatwg streams spec,
|
||||
* WritableStream#close() doesn't send data.
|
||||
*
|
||||
* @returns {Promise}
|
||||
*/
|
||||
|
||||
ServiceStreamPrototype.close = function() {
|
||||
debug('close');
|
||||
this._post('close');
|
||||
this.writable = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate the current state and
|
||||
* call cancel on the target stream.
|
||||
*
|
||||
* Called by the Service when client
|
||||
* sends a 'streamcancel' message.
|
||||
*
|
||||
* @param {*} [reason] Reason for cancelation sent by the client
|
||||
* @returns {Promise}
|
||||
* @private
|
||||
*/
|
||||
|
||||
ServiceStreamPrototype._cancel = function(reason) {
|
||||
debug('cancel', reason);
|
||||
return this.cancel(reason)
|
||||
.then(result => {
|
||||
this.writable = false;
|
||||
return result;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate the current state and post message to client.
|
||||
*
|
||||
* @param {String} type 'write', 'abort' or 'close'
|
||||
* @param {String} state 'writable', 'aborted' or 'closed'
|
||||
* @param {*} [data] Data to be sent to the client
|
||||
* @returns {Promise}
|
||||
* @private
|
||||
*/
|
||||
|
||||
ServiceStreamPrototype._post = function(type, data) {
|
||||
debug('post', type, data, this.writable);
|
||||
if (!this.writable) return Promise.reject();
|
||||
this.service.broadcast('streamevent', {
|
||||
streamId: this.id,
|
||||
type: type,
|
||||
data: data
|
||||
}, [this.clientId]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create new `Error` from registery.
|
||||
*
|
||||
* @param {Number} id Error Id
|
||||
* @return {Error}
|
||||
* @private
|
||||
*/
|
||||
function error(id) {
|
||||
var args = [].slice.call(arguments, 1);
|
||||
return new Error({
|
||||
1: 'stream "' + args[0] + '" doesn\'t exist'
|
||||
}[id]);
|
||||
}
|
||||
|
||||
});})((typeof define)[0]=='f'&&define.amd?define:((n,w)=>{return(typeof
|
||||
module)[0]=='o'?c=>{c(require,exports,module);}:(c)=>{var m={exports:{}};
|
||||
c(n=>w[n],m.exports,m);w[n]=m.exports;};})('streamService',this));
|
||||
462
gaia/apps/music/components/bridge/src/service.js
Executable file
|
|
@ -0,0 +1,462 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* Dependencies
|
||||
* @ignore
|
||||
*/
|
||||
|
||||
var uuid = require('./utils').uuid;
|
||||
var message = require('./message');
|
||||
var Receiver = message.Receiver;
|
||||
|
||||
/**
|
||||
* Exports
|
||||
* @ignore
|
||||
*/
|
||||
|
||||
module.exports = Service;
|
||||
|
||||
/**
|
||||
* Debug logger
|
||||
*
|
||||
* 0: off
|
||||
* 1: performance
|
||||
* 2: console.log
|
||||
*
|
||||
* @type {Function}
|
||||
* @private
|
||||
*/
|
||||
var debug = {
|
||||
0: () => {},
|
||||
1: arg => performance.mark(`[${self.constructor.name}][Service] - ${arg}`),
|
||||
2: (arg1, ...args) => {
|
||||
var type = `[${self.constructor.name}][${location.pathname}]`;
|
||||
console.log(`[Service]${type} - "${arg1}"`, ...args);
|
||||
}
|
||||
}[0];
|
||||
|
||||
/**
|
||||
* Extends `Receiver`
|
||||
* @ignore
|
||||
*/
|
||||
|
||||
Service.prototype = Object.create(Receiver.prototype);
|
||||
|
||||
/**
|
||||
* A `Service` is a collection of methods
|
||||
* exposed to a `Client`. Methods can be
|
||||
* sync or async (using Promises).
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* bridge.service('my-service')
|
||||
* .method('ping', param => 'pong: ' + param)
|
||||
* .listen();
|
||||
*
|
||||
* @class Service
|
||||
* @extends Receiver
|
||||
* @param {String} name The service name
|
||||
* @public
|
||||
*/
|
||||
function Service(name) {
|
||||
if (!(this instanceof Service)) return new Service(name);
|
||||
message.Receiver.call(this, name); // call super
|
||||
|
||||
this.clients = {};
|
||||
this.methods = {};
|
||||
|
||||
this
|
||||
.on('_disconnect', this.onDisconnect.bind(this))
|
||||
.on('_connect', this.onConnect.bind(this))
|
||||
.on('_method', this.onMethod.bind(this))
|
||||
.on('_off', this.onOff.bind(this))
|
||||
.on('_on', this.onOn.bind(this));
|
||||
|
||||
this.destroy = this.destroy.bind(this);
|
||||
debug('initialized', name);
|
||||
}
|
||||
|
||||
Service.prototype.inWindow = constructor.name === 'Window';
|
||||
|
||||
/**
|
||||
* Define a method to expose to Clients.
|
||||
* The return value of the result of a
|
||||
* returned Promise will be sent back
|
||||
* to the Client.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* bridge.service('my-service')
|
||||
*
|
||||
* // sync return value
|
||||
* .method('myMethod', function(param) {
|
||||
* return 'hello: ' + param;
|
||||
* })
|
||||
*
|
||||
* // or async Promise
|
||||
* .method('myOtherMethod', function() {
|
||||
* return new Promise(resolve => {
|
||||
* setTimeout(() => resolve('result'), 1000);
|
||||
* });
|
||||
* })
|
||||
*
|
||||
* .listen();
|
||||
*
|
||||
* @param {String} name
|
||||
* @param {Function} fn
|
||||
* @return {this} for chaining
|
||||
*/
|
||||
Service.prototype.method = function(name, fn) {
|
||||
this.methods[name] = fn;
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Broadcast's an event from a `Service`
|
||||
* to connected `Client`s.
|
||||
*
|
||||
* The third argument can be used to
|
||||
* target selected clients by their
|
||||
* `client.id`.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* service.broadcast('my-event', { some: data }); // all clients
|
||||
* service.broadcast('my-event', { some: data }, [ clientId ]); // one client
|
||||
*
|
||||
* @memberof Service
|
||||
* @param {String} type The message type/name
|
||||
* @param {*} [data] Data to send with the event
|
||||
* @param {Array} [only] A select list of clients to message
|
||||
* @return {this}
|
||||
*/
|
||||
Service.prototype.broadcast = function(type, data, only) {
|
||||
debug('broadcast', type, data, only);
|
||||
|
||||
this.eachClient(client => {
|
||||
if (only && !~only.indexOf(client.id)) return;
|
||||
debug('broadcasting to', client.id);
|
||||
this.push(type, data, client.id, { noRespond: true });
|
||||
});
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Push message to a single connected Client.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* client.on('my-event', data => ...)
|
||||
*
|
||||
* ...
|
||||
*
|
||||
* service.push('my-event', { some: data}, clientId)
|
||||
* .then(() => console.log('sent'));
|
||||
*
|
||||
* @public
|
||||
* @param {String} type
|
||||
* @param {Object} data
|
||||
* @param {String} clientId The Id of the Client to push to
|
||||
* @param {Object} options Optional parameters
|
||||
* @param {Boolean} options.noResponse Tell the Client not to respond
|
||||
* (Promise resolves instantly)
|
||||
* @return {Promise}
|
||||
*/
|
||||
Service.prototype.push = function(type, data, clientId, options) {
|
||||
var noRespond = options && options.noRespond;
|
||||
var client = this.getClient(clientId);
|
||||
return message('_push')
|
||||
.set({
|
||||
recipient: clientId,
|
||||
noRespond: noRespond,
|
||||
data: {
|
||||
type: type,
|
||||
data: data
|
||||
}
|
||||
}).send(client.port);
|
||||
};
|
||||
|
||||
Service.prototype.eachClient = function(fn) {
|
||||
for (var id in this.clients) fn(this.clients[id]);
|
||||
};
|
||||
|
||||
Service.prototype.getClient = function(id) {
|
||||
return this.clients[id];
|
||||
};
|
||||
|
||||
/**
|
||||
* @fires Service#before-connect
|
||||
* @fires Service#connected
|
||||
* @param {Message} message
|
||||
* @private
|
||||
*/
|
||||
Service.prototype.onConnect = function(message) {
|
||||
debug('connection attempt', message.data, this.name);
|
||||
var data = message.data;
|
||||
var clientId = data.clientId;
|
||||
|
||||
if (!clientId) return;
|
||||
if (data.service !== this.name) return;
|
||||
if (this.clients[clientId]) return;
|
||||
|
||||
// before hook
|
||||
this.emit('before-connect', message);
|
||||
if (message.defaultPrevented) return;
|
||||
|
||||
this.upgradeChannel(message);
|
||||
this.addClient(clientId, message.sourcePort);
|
||||
message.respond();
|
||||
|
||||
this.emit('connected', clientId);
|
||||
debug('connected', clientId);
|
||||
};
|
||||
|
||||
/**
|
||||
* When a Client attempt to connect we
|
||||
* can sometimes upgrade the to a direct
|
||||
* MessageChannel 'pipe' to prevent
|
||||
* hopping threads.
|
||||
*
|
||||
* We only do this if both:
|
||||
*
|
||||
* A. `MessagePort` was supplied with the 'connect' event.
|
||||
* B. The Client and Service are not both in `Window` contexts
|
||||
* (it's faster to use sync messaging window -> window).
|
||||
*
|
||||
* @param {Message} message the 'connect' message
|
||||
* @private
|
||||
*/
|
||||
Service.prototype.upgradeChannel = function(message) {
|
||||
if (this.inWindow && message.data.originEnv === 'Window') return;
|
||||
|
||||
var ports = message.event.ports;
|
||||
var channel = ports && ports[0];
|
||||
|
||||
if (channel) {
|
||||
message.setSourcePort(channel);
|
||||
this.listen(channel);
|
||||
channel.start();
|
||||
}
|
||||
|
||||
debug('channel upgraded');
|
||||
};
|
||||
|
||||
/**
|
||||
* @fires Service#before-disconnect
|
||||
* @fires Service#disconnected
|
||||
* @param {Message} message
|
||||
* @private
|
||||
*/
|
||||
Service.prototype.onDisconnect = function(message) {
|
||||
var client = this.clients[message.data];
|
||||
if (!client) return;
|
||||
|
||||
// before hook
|
||||
this.emit('before-disconnect', message);
|
||||
if (message.defaultPrevented) return;
|
||||
|
||||
this.removeClient(client.id);
|
||||
message.respond();
|
||||
|
||||
this.emit('disconnected', client.id);
|
||||
debug('disconnected', client.id);
|
||||
};
|
||||
|
||||
/**
|
||||
* @fires Service#before-method
|
||||
* @param {Message} message
|
||||
* @private
|
||||
*/
|
||||
Service.prototype.onMethod = function(message) {
|
||||
debug('on method', message.data);
|
||||
this.emit('before-method', message);
|
||||
if (message.defaultPrevented) return;
|
||||
|
||||
var method = message.data;
|
||||
var name = method.name;
|
||||
var result;
|
||||
|
||||
var fn = this.methods[name];
|
||||
if (!fn) throw error(4, name);
|
||||
try { result = fn.apply(this, method.args); }
|
||||
catch (err) { result = err; }
|
||||
message.respond(result);
|
||||
};
|
||||
|
||||
/**
|
||||
* @fires Service#on
|
||||
* @param {Message} message
|
||||
* @private
|
||||
*/
|
||||
Service.prototype.onOn = function(message) {
|
||||
debug('on on', message.data);
|
||||
this.emit('on', message.data);
|
||||
};
|
||||
|
||||
/**
|
||||
* @fires Service#off
|
||||
* @param {Message} message
|
||||
* @private
|
||||
*/
|
||||
Service.prototype.onOff = function(message) {
|
||||
debug('on off');
|
||||
this.emit('off', message.data);
|
||||
};
|
||||
|
||||
Service.prototype.addClient = function(id, port) {
|
||||
this.clients[id] = {
|
||||
id: id,
|
||||
port: port
|
||||
};
|
||||
};
|
||||
|
||||
Service.prototype.removeClient = function(id) {
|
||||
delete this.clients[id];
|
||||
};
|
||||
|
||||
/**
|
||||
* Use a plugin with this Service.
|
||||
* @param {Function} fn Plugin function
|
||||
* @return {this} for chaining
|
||||
* @public
|
||||
*/
|
||||
Service.prototype.plugin = function(fn) {
|
||||
fn(this, { 'uuid': uuid });
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Disconnect a Client from the Service.
|
||||
* @param {Object} client
|
||||
* @private
|
||||
*/
|
||||
Service.prototype.disconnect = function(client) {
|
||||
this.removeClient(client.id);
|
||||
message('disconnect')
|
||||
.set({
|
||||
recipient: client.id,
|
||||
noRespond: true
|
||||
})
|
||||
.send(client.port);
|
||||
};
|
||||
|
||||
/**
|
||||
* Destroy the Service.
|
||||
* @public
|
||||
*/
|
||||
Service.prototype.destroy = function() {
|
||||
delete this.clients;
|
||||
this.unlisten();
|
||||
this.off();
|
||||
};
|
||||
|
||||
var sp = Service.prototype;
|
||||
sp['broadcast'] = sp.broadcast;
|
||||
sp['destroy'] = sp.destroy;
|
||||
sp['method'] = sp.method;
|
||||
sp['plugin'] = sp.plugin;
|
||||
|
||||
/**
|
||||
* Creates new `Error` from registery.
|
||||
*
|
||||
* @param {Number} id Error Id
|
||||
* @return {Error}
|
||||
* @private
|
||||
*/
|
||||
function error(id) {
|
||||
var args = [].slice.call(arguments, 1);
|
||||
return new Error({
|
||||
4: 'method "' + args[0] + '" doesn\'t exist'
|
||||
}[id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires before the default 'connect' logic.
|
||||
* This event acts as a hook for plugin authors
|
||||
* to override default 'connect' behaviour.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* service.on('before-connect', message => {
|
||||
* message.preventDefault();
|
||||
* // alternative connection logic ...
|
||||
* });
|
||||
*
|
||||
* @event Service#before-connect
|
||||
* @param {Message} message - The connect message
|
||||
*/
|
||||
|
||||
/**
|
||||
* Signals that a Client has connected.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* service.on('connected', clientId => {
|
||||
* console.log('client (%s) has connected', clientId);
|
||||
* });
|
||||
*
|
||||
* @event Service#connected
|
||||
* @param {String} clientId - The id of the connected Client
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fires before the default 'disconnect' logic.
|
||||
* This event acts as a hook for plugin authors
|
||||
* to override default 'disconnect' behaviour.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* service.on('before-disconnect', message => {
|
||||
* message.preventDefault();
|
||||
* // alternative disconnection logic ...
|
||||
* });
|
||||
*
|
||||
* @event Service#before-disconnect
|
||||
* @param {Message} message - The disconnect message
|
||||
*/
|
||||
|
||||
/**
|
||||
* Signals that a Client has disconnected.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* service.on('disconnected', clientId => {
|
||||
* console.log('client (%s) has disconnected', clientId);
|
||||
* });
|
||||
*
|
||||
* @event Service#disconnected
|
||||
* @param {String} clientId - The id of the disconnected Client
|
||||
*/
|
||||
|
||||
/**
|
||||
* Signals that a Client has begun
|
||||
* listening to a broadcast event.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* service.on('on', data => {
|
||||
* console.log('client (%s) is listening to %s', data.clientId, data.name);
|
||||
* });
|
||||
*
|
||||
* @event Service#on
|
||||
* @type {Object}
|
||||
* @property {String} name - The broadcast name
|
||||
* @property {String} clientId - The id of the Client that started listening
|
||||
*/
|
||||
|
||||
/**
|
||||
* Signals that a Client has stopped
|
||||
* listening to a broadcast event.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* service.on('off', data => {
|
||||
* console.log('client (%s) stopped listening to %s', data.clientId, data.name);
|
||||
* });
|
||||
*
|
||||
* @event Service#off
|
||||
* @param {Object} data
|
||||
* @param {String} data.name - The broadcast name
|
||||
* @param {String} data.clientId - The id of the Client that stopped listening
|
||||
*/
|
||||
25
gaia/apps/music/components/bridge/src/utils/index.js
Executable file
|
|
@ -0,0 +1,25 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* Create a UUID string.
|
||||
*
|
||||
* http://jsperf.com/guid-generation-stackoverflow
|
||||
*
|
||||
* @return {String}
|
||||
*/
|
||||
|
||||
exports.uuid = function() {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||
var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
|
||||
return v.toString(16);
|
||||
});
|
||||
};
|
||||
|
||||
exports.deferred = function() {
|
||||
var promise = {};
|
||||
promise.promise = new Promise((resolve, reject) => {
|
||||
promise.resolve = resolve;
|
||||
promise.reject = reject;
|
||||
});
|
||||
return promise;
|
||||
};
|
||||
34
gaia/apps/music/components/dom-scheduler/.bower.json
Executable file
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"name": "dom-scheduler",
|
||||
"version": "1.0.1",
|
||||
"homepage": "https://github.com/etiennesegonzac/dom-scheduler",
|
||||
"authors": [
|
||||
"Etienne Segonzac <etienne@segonzac.info>"
|
||||
],
|
||||
"description": "Making the performant thing the easy thing.",
|
||||
"main": "lib/dom-scheduler.js",
|
||||
"keywords": [
|
||||
"web",
|
||||
"performance",
|
||||
"transition",
|
||||
"animation",
|
||||
"promise"
|
||||
],
|
||||
"license": "MPL",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"demo-app/bower_components",
|
||||
"test",
|
||||
"tests"
|
||||
],
|
||||
"_release": "1.0.1",
|
||||
"_resolution": {
|
||||
"type": "version",
|
||||
"tag": "v1.0.1",
|
||||
"commit": "1f3a138fb104d067774315acf7ae23f7d940bc4f"
|
||||
},
|
||||
"_source": "git://github.com/etiennesegonzac/dom-scheduler.git",
|
||||
"_target": "~1.0.1",
|
||||
"_originalSource": "etiennesegonzac/dom-scheduler"
|
||||
}
|
||||
25
gaia/apps/music/components/dom-scheduler/bower.json
Executable file
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"name": "dom-scheduler",
|
||||
"version": "1.0.0",
|
||||
"homepage": "https://github.com/etiennesegonzac/dom-scheduler",
|
||||
"authors": [
|
||||
"Etienne Segonzac <etienne@segonzac.info>"
|
||||
],
|
||||
"description": "Making the performant thing the easy thing.",
|
||||
"main": "lib/dom-scheduler.js",
|
||||
"keywords": [
|
||||
"web",
|
||||
"performance",
|
||||
"transition",
|
||||
"animation",
|
||||
"promise"
|
||||
],
|
||||
"license": "MPL",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"demo-app/bower_components",
|
||||
"test",
|
||||
"tests"
|
||||
]
|
||||
}
|
||||
227
gaia/apps/music/components/dom-scheduler/bower_components/base/base.css
vendored
Executable file
|
|
@ -0,0 +1,227 @@
|
|||
|
||||
/** Variables
|
||||
---------------------------------------------------------*/
|
||||
|
||||
:root {
|
||||
--dim-micro: 6px;
|
||||
--dim-small: 12px;
|
||||
--dim-medium: 24px;
|
||||
--dim-mega: 48px;
|
||||
|
||||
--base-xs: 6px;
|
||||
--base-s: 18px;
|
||||
--base-m: 24px;
|
||||
--base-l: 30px;
|
||||
--base-xl: 42px;
|
||||
--base-xxl: 60px;
|
||||
|
||||
--base-font-xs: 10px;
|
||||
--base-font-s: 14px;
|
||||
--base-font-m: 17px;
|
||||
--base-font-l: 23px;
|
||||
--base-font-xl: 60px;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Apply a natural box layout
|
||||
* model to all elements
|
||||
*/
|
||||
|
||||
*,
|
||||
*:after,
|
||||
*:before {
|
||||
box-sizing: border-box; /* 1 */
|
||||
font: inherit;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/** Document
|
||||
---------------------------------------------------------*/
|
||||
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/** Body
|
||||
---------------------------------------------------------*/
|
||||
|
||||
body {
|
||||
height: 100%;
|
||||
font: 300 17px/21px 'FiraSans';
|
||||
background-color: var(--background);
|
||||
color: var(--text-color);
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/** Tags
|
||||
---------------------------------------------------------*/
|
||||
|
||||
a {
|
||||
color: var(--highlight-color, inherit);
|
||||
text-decoration: none;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
hr {
|
||||
height: 1px;
|
||||
margin: var(--dim-small) 0 var(--dim-small) 0;
|
||||
border: 0;
|
||||
background:
|
||||
var(--border-color,
|
||||
var(--background-plus,
|
||||
rgba(0,0,0,0.2)));
|
||||
}
|
||||
|
||||
em {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5 {
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.alpha {
|
||||
font-size: 23px;
|
||||
font-weight: 200;
|
||||
font-style: italic;
|
||||
line-height: 26px;
|
||||
margin: 6px 0 24px 0;
|
||||
}
|
||||
|
||||
.beta {
|
||||
font-size: 17px;
|
||||
font-weight: 400;
|
||||
margin: 32px 0 16px 0;
|
||||
}
|
||||
|
||||
.gamma {
|
||||
font-size: 14px;
|
||||
text-transform: uppercase;
|
||||
font-weight: normal;
|
||||
margin: 32px 0 16px 0;
|
||||
}
|
||||
|
||||
.milli {
|
||||
font-size: 14px;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.large {
|
||||
font-size: 23px;
|
||||
line-height: 26px;
|
||||
}
|
||||
|
||||
.mega {
|
||||
font-size: 60px;
|
||||
line-height: 60px;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.giga {}
|
||||
.kilo {}
|
||||
|
||||
/* alignment */
|
||||
.l-align-right { float: right; }
|
||||
.l-align-left { float: left; }
|
||||
.l-align-center { text-align: center; }
|
||||
.l-text-align-left { text-align: left; }
|
||||
.l-text-align-right { text-align: right; }
|
||||
.l-text-valign-top {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.l-width-full {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/** Flexbox
|
||||
---------------------------------------------------------*/
|
||||
|
||||
.l-flex { display: flex; }
|
||||
.l-flex-col { display: flex; flex-direction: column; }
|
||||
|
||||
.l-flex-col-v-align {
|
||||
display: flex !important;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.l-flex-v-align { align-items: center; }
|
||||
.l-flex-h-align { justify-content: center; }
|
||||
.l-flex-grow { flex: 1; }
|
||||
|
||||
/** Margin
|
||||
---------------------------------------------------------*/
|
||||
|
||||
/* Top */
|
||||
|
||||
.l-mt-small { margin-top: var(--dim-small); }
|
||||
.l-mt-med { margin-top: var(--dim-medium); }
|
||||
|
||||
/* Bottom */
|
||||
|
||||
.l-mb-small { margin-bottom: var(--dim-small); }
|
||||
.l-mb-med { margin-bottom: var(--dim-medium); }
|
||||
.l-mb-large {}
|
||||
.l-mb-mega { margin-bottom: var(--dim-mega); }
|
||||
|
||||
/* Left */
|
||||
|
||||
.l-ml-small { margin-left: var(--dim-small); }
|
||||
|
||||
/* Right */
|
||||
|
||||
.l-mr-small { margin-right: var(--dim-small); }
|
||||
|
||||
/* Auto */
|
||||
|
||||
.l-mlr-auto { margin-left: auto; margin-right: auto; }
|
||||
.l-m-auto { margin: auto }
|
||||
|
||||
/* Margin */
|
||||
|
||||
.l-m-small { margin: var(--dim-small) }
|
||||
.l-m-med { margin: var(--dim-medium) }
|
||||
.l-m-0 { margin: 0 }
|
||||
|
||||
/** Padding
|
||||
---------------------------------------------------------*/
|
||||
|
||||
.l-p-small { padding: var(--dim-small); }
|
||||
.l-pl-small { padding-left: var(--dim-small); }
|
||||
.l-pr-small { padding-right: var(--dim-small); }
|
||||
.l-plr-small {
|
||||
padding-right: var(--dim-small);
|
||||
padding-left: var(--dim-small);
|
||||
}
|
||||
.l-pb-small { padding-bottom: var(--dim-small); }
|
||||
.l-pt-small { padding-top: var(--dim-small); }
|
||||
.l-pt-mega { padding-top: var(--dim-mega); }
|
||||
.l-pt-micro { padding-top: var(--dim-micro); }
|
||||
.l-pb-micro { padding-bottom: var(--dim-micro); }
|
||||
.l-p-0 { padding: 0 ;}
|
||||
|
||||
/** Helpers
|
||||
---------------------------------------------------------*/
|
||||
|
||||
.l-clearfix:after {
|
||||
content: "";
|
||||
display: table;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.l-inline-block {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.is-hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.is-invisible {
|
||||
visibility: hidden !important;
|
||||
}
|
||||
16
gaia/apps/music/components/dom-scheduler/bower_components/base/bower.json
vendored
Executable file
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"name": "base",
|
||||
"version": "0.0.0",
|
||||
"authors": [
|
||||
"Wilson Page <wilsonpage@me.com>"
|
||||
],
|
||||
"main": "base.css",
|
||||
"license": "MIT",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"test",
|
||||
"tests"
|
||||
]
|
||||
}
|
||||
25
gaia/apps/music/components/dom-scheduler/bower_components/fast-list/bower.json
vendored
Executable file
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"name": "fast-list",
|
||||
"version": "0.1.2",
|
||||
"homepage": "https://github.com/gaia-components/fast-list",
|
||||
"authors": [
|
||||
"Etienne Segonzac <etienne@segonzac.info>",
|
||||
"Wilson Page <wilsonpage@me.com>"
|
||||
],
|
||||
"description": "No, really.",
|
||||
"main": "fast-list.js",
|
||||
"keywords": [
|
||||
"web",
|
||||
"performance",
|
||||
"virtual",
|
||||
"list"
|
||||
],
|
||||
"license": "MPL",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"test",
|
||||
"tests"
|
||||
]
|
||||
}
|
||||
20
gaia/apps/music/components/dom-scheduler/bower_components/fast-list/examples/sections/bower.json
vendored
Executable file
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"name": "fast-list/examples/sections",
|
||||
"version": "0.1.0",
|
||||
"homepage": "https://github.com/gaia-components/fast-list",
|
||||
"authors": [
|
||||
"Wilson Page <wilsonpage@me.com>"
|
||||
],
|
||||
"main": "index.html",
|
||||
"license": "MIT",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"test",
|
||||
"tests"
|
||||
],
|
||||
"dependencies": {
|
||||
"dom-scheduler": "etiennesegonzac/dom-scheduler"
|
||||
}
|
||||
}
|
||||
134
gaia/apps/music/components/dom-scheduler/bower_components/fast-list/examples/sections/css/app.css
vendored
Executable file
|
|
@ -0,0 +1,134 @@
|
|||
html, body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
font-family: sans-serif;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
h1 {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#h1-after {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
left: 0;
|
||||
top: 46px;
|
||||
|
||||
pointer-events: none;
|
||||
background-color: #ff9500;
|
||||
|
||||
opacity: 0;
|
||||
|
||||
animation-duration: 0.25s;
|
||||
animation-timing-function: ease-in;
|
||||
animation-fill-mode: both;
|
||||
|
||||
-webkit-animation-duration: 0.25s;
|
||||
-webkit-animation-timing-function: ease-in;
|
||||
-webkit-animation-fill-mode: both;
|
||||
}
|
||||
|
||||
[data-anim='reveal'] {
|
||||
animation-name: reveal;
|
||||
-webkit-animation-name: reveal;
|
||||
}
|
||||
|
||||
@keyframes reveal {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes reveal {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
[data-anim='hide'] {
|
||||
animation-name: hide;
|
||||
-webkit-animation-name: hide;
|
||||
}
|
||||
|
||||
@keyframes hide {
|
||||
from {
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes hide {
|
||||
from {
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
position: absolute;
|
||||
display: block;
|
||||
top: 0;
|
||||
right: 0;
|
||||
height: 50px;
|
||||
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
outline: 0;
|
||||
background: none;
|
||||
padding: 0 15px;
|
||||
|
||||
text-align: center;
|
||||
text-decoration: underline;
|
||||
font-size: 14px;
|
||||
color: #ffffff;
|
||||
-moz-appearance: none;
|
||||
|
||||
opacity: 1;
|
||||
transition: transform 0.1s linear,
|
||||
opacity 0.1s linear;
|
||||
-webkit-transition: -webkit-transform 0.1s linear,
|
||||
opacity 0.1s linear;
|
||||
|
||||
-webkit-tap-highlight-color: rgba(0,0,0,0);
|
||||
}
|
||||
|
||||
button.transitioning {
|
||||
opacity: 0.5;
|
||||
transform: scale(0.6, 0.6);
|
||||
-webkit-transform: scale(0.6, 0.6);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
ul {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
body > section {
|
||||
position: absolute;
|
||||
top: 50px;
|
||||
width: 100%;
|
||||
height: calc(100% - 50px);
|
||||
|
||||
-webkit-overflow-scrolling: touch;
|
||||
-webkit-tap-highlight-color: rgba(0,0,0,0);
|
||||
}
|
||||
|
||||
21
gaia/apps/music/components/dom-scheduler/bower_components/fast-list/examples/sections/css/h1.css
vendored
Executable file
|
|
@ -0,0 +1,21 @@
|
|||
h1 {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: #27c8c2;
|
||||
|
||||
text-align: center;
|
||||
line-height: 50px;
|
||||
font-weight: 300;
|
||||
font-style: italic;
|
||||
font-size: 24px;
|
||||
color: #ffffff;
|
||||
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
143
gaia/apps/music/components/dom-scheduler/bower_components/fast-list/examples/sections/css/ul.css
vendored
Executable file
|
|
@ -0,0 +1,143 @@
|
|||
ul {
|
||||
list-style: none;
|
||||
padding: 0 0 0 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
ul section {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul h2 {
|
||||
position: sticky;
|
||||
position: -webkit-sticky;
|
||||
top: 0px;
|
||||
height: 32px;
|
||||
|
||||
margin: 0;
|
||||
padding: 0 6px;
|
||||
box-sizing: border-box;
|
||||
|
||||
font-size: 0.9em;
|
||||
line-height: 32px;
|
||||
|
||||
background: #efefef;
|
||||
border-bottom: 1px #bcbcbc solid;
|
||||
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
ul .background {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
|
||||
background: linear-gradient(to bottom, transparent 0px, transparent 45px, #EDEDED 45px, #EDEDED 70px, transparent 20px),
|
||||
linear-gradient(to bottom, transparent 0px, transparent 15px, #EDEDED 15px, #EDEDED 25px, transparent 20px),
|
||||
linear-gradient(to top, #bcbcbc, #bcbcbc 1px, white 1px, white);
|
||||
background-position: 20% 88px, 5% -88px, 0 -88px;
|
||||
background-size: 90% 88px, 60% 88px, 100% 88px;
|
||||
background-repeat: repeat-y;
|
||||
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
ul.reordering .background {
|
||||
background: linear-gradient(to top, #bcbcbc, #bcbcbc 1px, white 1px, white);
|
||||
background-position: 0 -88px;
|
||||
background-size: 100% 88px;
|
||||
}
|
||||
|
||||
ul li {
|
||||
width: 100%;
|
||||
height: 88px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
|
||||
-moz-user-select: none;
|
||||
|
||||
background: white;
|
||||
border-bottom: 1px #bcbcbc solid;
|
||||
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
ul li h3 {
|
||||
margin: 0;
|
||||
padding: 9px 6px 3px 6px;
|
||||
background: white;
|
||||
|
||||
color: #424242;
|
||||
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
ul li p {
|
||||
margin: 0;
|
||||
padding: 3px 6px 10px 6px;
|
||||
background: white;
|
||||
|
||||
color: #797979;
|
||||
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
li.new {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
li .overlay {
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: calc(100% - 2px);
|
||||
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
|
||||
opacity: 0;
|
||||
animation-duration: 0.2s;
|
||||
animation-timing-function: ease-in;
|
||||
animation-fill-mode: both;
|
||||
|
||||
-webkit-animation-duration: 0.2s;
|
||||
-webkit-animation-timing-function: ease-in;
|
||||
-webkit-animation-fill-mode: both;
|
||||
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
li.edit .overlay {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.overlay .cursor {
|
||||
margin: 17px auto;
|
||||
padding: 0;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
|
||||
text-align: center;
|
||||
line-height: 50px;
|
||||
font-size: 25px;
|
||||
color: #0b0b0b;
|
||||
|
||||
border-radius: 8px;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
-moz-user-select: none;
|
||||
}
|
||||
|
||||
li.dragging {
|
||||
transition: transform 0s linear;
|
||||
-webkit-transition: -webkit-transform 0s linear;
|
||||
}
|
||||
|
||||
li.dragging .overlay {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
li[data-populated=false] {
|
||||
opacity: 0;
|
||||
}
|
||||
BIN
gaia/apps/music/components/dom-scheduler/bower_components/fast-list/examples/sections/icons/icon128x128.png
vendored
Executable file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
gaia/apps/music/components/dom-scheduler/bower_components/fast-list/examples/sections/icons/icon16x16.png
vendored
Executable file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
gaia/apps/music/components/dom-scheduler/bower_components/fast-list/examples/sections/icons/icon48x48.png
vendored
Executable file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
gaia/apps/music/components/dom-scheduler/bower_components/fast-list/examples/sections/icons/icon60x60.png
vendored
Executable file
|
After Width: | Height: | Size: 3.6 KiB |
46
gaia/apps/music/components/dom-scheduler/bower_components/fast-list/examples/sections/index.html
vendored
Executable file
|
|
@ -0,0 +1,46 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1">
|
||||
|
||||
<link rel="stylesheet" href="css/h1.css">
|
||||
<link rel="stylesheet" href="css/ul.css">
|
||||
<link rel="stylesheet" href="css/app.css">
|
||||
|
||||
<script src="bower_components/dom-scheduler/lib/dom-scheduler.js" defer></script>
|
||||
<script src="fast-list.js" async></script>
|
||||
|
||||
<script src="js/bacon-source.js" async></script>
|
||||
<script src="js/page.js" async></script>
|
||||
|
||||
<title>Dom Scheduler Demo</title>
|
||||
</head>
|
||||
|
||||
<body class="theme-communications">
|
||||
<h1>
|
||||
Main List
|
||||
</h1>
|
||||
<div id="h1-after"></div>
|
||||
<button>
|
||||
Edit
|
||||
</button>
|
||||
<section>
|
||||
<ul>
|
||||
<section
|
||||
><h2> </h2
|
||||
><div class="background"
|
||||
></div
|
||||
></section>
|
||||
<li
|
||||
><h3> </h3
|
||||
><p> </p
|
||||
><div class="overlay"
|
||||
><div class="cursor">↕︎</div></div
|
||||
></li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
95
gaia/apps/music/components/dom-scheduler/bower_components/fast-list/examples/sections/js/app.js
vendored
Executable file
|
|
@ -0,0 +1,95 @@
|
|||
(function() {
|
||||
var debug = false;
|
||||
var headerHeight = 50;
|
||||
var maxItemCount = 28;
|
||||
|
||||
var endEvent = ('ontouchstart' in window) ? 'touchend' : 'mouseup';
|
||||
|
||||
window.addEventListener('load', function() {
|
||||
var listContainer = document.querySelector('section');
|
||||
|
||||
var source = new BaconSource();
|
||||
var list = new FastList(listContainer, source, scheduler);
|
||||
var dialog = document.querySelector('gaia-dialog-alert');
|
||||
|
||||
function updateHeader() {
|
||||
return scheduler.mutation(function() {
|
||||
var h1 = document.querySelector('h1');
|
||||
h1.textContent = 'Main List (' + source.fullLength() + ')';
|
||||
h1.scrollTop; // flush
|
||||
});
|
||||
}
|
||||
updateHeader();
|
||||
|
||||
function clearNewIndicator() {
|
||||
var h1After = document.querySelector('#h1-after');
|
||||
|
||||
if (h1After.dataset.anim == 'reveal') {
|
||||
scheduler.transition(function() {
|
||||
h1After.dataset.anim = 'hide';
|
||||
}, h1After, 'animationend');
|
||||
}
|
||||
}
|
||||
listContainer.addEventListener('top-reached', clearNewIndicator);
|
||||
|
||||
function updateNewIndicator() {
|
||||
var h1After = document.querySelector('#h1-after');
|
||||
if (h1After.dataset.anim == 'reveal') {
|
||||
return;
|
||||
}
|
||||
|
||||
scheduler.transition(function() {
|
||||
h1After.dataset.anim = 'reveal';
|
||||
}, h1After, 'animationend');
|
||||
}
|
||||
listContainer.addEventListener('hidden-new-content', updateNewIndicator);
|
||||
|
||||
function openGaiaDialog(evt) {
|
||||
scheduler.mutation(function() {
|
||||
var detail = evt.detail;
|
||||
var li = source.getRecordAt(detail.index);
|
||||
dialog.textContent = li.title + ' item clicked!';
|
||||
dialog.open(detail.clickEvt);
|
||||
});
|
||||
}
|
||||
list.list.addEventListener('item-selected', openGaiaDialog);
|
||||
|
||||
function newContentHandler() {
|
||||
var newContent = {
|
||||
title: 'NEW Bacon ' + Date.now().toString().slice(7, -1),
|
||||
body: 'Turkey BLT please.'
|
||||
};
|
||||
|
||||
source.insertAtIndex(0, newContent);
|
||||
list.insertedAtIndex(0);
|
||||
|
||||
updateHeader();
|
||||
}
|
||||
|
||||
setInterval(newContentHandler, 15000);
|
||||
window.addEventListener('new-content', newContentHandler);
|
||||
|
||||
window.pushNewContent = function() {
|
||||
window.dispatchEvent(new CustomEvent('new-content'));
|
||||
};
|
||||
|
||||
var button = document.querySelector('button');
|
||||
button.addEventListener(endEvent, function() {
|
||||
Promise.all([toggleTransitioning(), list.toggleEditMode()])
|
||||
.then(updateText)
|
||||
.then(toggleTransitioning);
|
||||
});
|
||||
|
||||
function updateText(text) {
|
||||
return scheduler.mutation(function() {
|
||||
button.textContent = list.editing ? 'Exit' : 'Edit';
|
||||
});
|
||||
}
|
||||
|
||||
function toggleTransitioning() {
|
||||
return scheduler.feedback(function() {
|
||||
button.classList.toggle('transitioning');
|
||||
}, button, 'transitionend');
|
||||
}
|
||||
});
|
||||
})();
|
||||
175
gaia/apps/music/components/dom-scheduler/bower_components/fast-list/examples/sections/js/bacon-source.js
vendored
Executable file
|
|
@ -0,0 +1,175 @@
|
|||
(function(exports) {
|
||||
var listSize = 1042;
|
||||
var sectionCount = 25;
|
||||
|
||||
var headerHeight = 32;
|
||||
var itemHeight = 88;
|
||||
|
||||
function makeContent(prefix, name) {
|
||||
return {
|
||||
title: prefix + ' Bacon ipsum dolor ' +
|
||||
Date.now().toString().slice(7, -1),
|
||||
body: 'Turkey pig boudin ' + name.toLowerCase() +
|
||||
' de mignon drums and all ' + prefix + '.'
|
||||
}
|
||||
}
|
||||
|
||||
exports.BaconSource = function BaconSource() {
|
||||
this.sections = [];
|
||||
this.contentMap = new Map();
|
||||
this._cachedLength = null;
|
||||
|
||||
for (var i = 0; i < sectionCount; i++) {
|
||||
this.sections.push('Section ' + i);
|
||||
}
|
||||
|
||||
this.sections.forEach(function(name) {
|
||||
var content = [];
|
||||
for (var i = 0; i < (Math.random() * listSize); i++) {
|
||||
content.push(makeContent(i, name));
|
||||
}
|
||||
this.contentMap.set(name, content);
|
||||
}, this);
|
||||
};
|
||||
|
||||
exports.BaconSource.prototype = {
|
||||
populateItem: function(item, i) {
|
||||
// Simulates the data source not being ready to populate
|
||||
/*if (parseInt(Date.now() / 10) % 8 === 0) {*/
|
||||
/*return new Promise(function(resolve, reject) {*/
|
||||
/*setTimeout(resolve, Math.random() * 1000);*/
|
||||
/*});*/
|
||||
/*}*/
|
||||
|
||||
var title = item.firstChild;
|
||||
var body = title.nextSibling;
|
||||
var record = this.getRecordAt(i);
|
||||
|
||||
title.firstChild.data = record.title;
|
||||
body.firstChild.data = record.body;
|
||||
},
|
||||
|
||||
getSections: function() {
|
||||
return this.contentMap.keys();
|
||||
},
|
||||
|
||||
sectionHeaderHeight: function() {
|
||||
return headerHeight;
|
||||
},
|
||||
|
||||
fullSectionHeight: function(key) {
|
||||
return this.contentMap.get(key).length * itemHeight;
|
||||
},
|
||||
|
||||
fullSectionLength: function(key) {
|
||||
return this.contentMap(key).length;
|
||||
},
|
||||
|
||||
getRecordAt: function(index) {
|
||||
for (var section of this.contentMap.values()) {
|
||||
if (index < section.length) {
|
||||
return section[index];
|
||||
}
|
||||
index -= section.length;
|
||||
}
|
||||
},
|
||||
|
||||
getSectionFor: function(index) {
|
||||
for (var entry of this.contentMap.entries()) {
|
||||
if (index < entry[1].length) {
|
||||
return entry[0];
|
||||
}
|
||||
index -= entry[1].length;
|
||||
}
|
||||
},
|
||||
|
||||
indexAtPosition: function(pos) {
|
||||
var index = 0;
|
||||
for (var section of this.contentMap.values()) {
|
||||
pos -= headerHeight;
|
||||
var sectionHeight = section.length * itemHeight;
|
||||
if (pos > sectionHeight) {
|
||||
pos -= sectionHeight;
|
||||
index += section.length;
|
||||
continue;
|
||||
}
|
||||
for (var item of section) {
|
||||
pos -= itemHeight;
|
||||
index++;
|
||||
if (pos <= 0 || index === this.fullLength() - 1) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
positionForIndex: function(index) {
|
||||
var top = 0;
|
||||
for (var section of this.contentMap.values()) {
|
||||
top += headerHeight;
|
||||
if (index < section.length) {
|
||||
top += index * itemHeight;
|
||||
return top;
|
||||
}
|
||||
index -= section.length;
|
||||
top += section.length * itemHeight;
|
||||
}
|
||||
},
|
||||
|
||||
fullLength: function() {
|
||||
if (this._cachedLength) {
|
||||
return this._cachedLength;
|
||||
}
|
||||
|
||||
var length = 0;
|
||||
for (var section of this.contentMap.values()) {
|
||||
length += section.length;
|
||||
}
|
||||
this._cachedLength = length;
|
||||
return length;
|
||||
},
|
||||
|
||||
itemHeight: function() {
|
||||
return itemHeight;
|
||||
},
|
||||
|
||||
fullHeight: function() {
|
||||
var height = 0;
|
||||
for (var section of this.contentMap.values()) {
|
||||
height += headerHeight + section.length * itemHeight;
|
||||
}
|
||||
return height;
|
||||
},
|
||||
|
||||
insertAtIndex: function(index, record, toSection) {
|
||||
this._cachedLength = null;
|
||||
for (var entry of this.contentMap.entries()) {
|
||||
if (index < entry[1].length || entry[0] === toSection) {
|
||||
return entry[1].splice(index, 0, record);
|
||||
}
|
||||
index -= entry[1].length;
|
||||
}
|
||||
},
|
||||
|
||||
replaceAtIndex: function(index, record) {
|
||||
for (var section of this.contentMap.values()) {
|
||||
if (index < section.length) {
|
||||
return section.splice(index, 1, record);
|
||||
}
|
||||
index -= section.length;
|
||||
}
|
||||
},
|
||||
|
||||
removeAtIndex: function(index) {
|
||||
this._cachedLength = null;
|
||||
|
||||
for (var section of this.contentMap.values()) {
|
||||
if (index < section.length) {
|
||||
return section.splice(index, 1)[0];
|
||||
}
|
||||
index -= section.length;
|
||||
}
|
||||
}
|
||||
};
|
||||
})(window);
|
||||
|
||||
94
gaia/apps/music/components/dom-scheduler/bower_components/fast-list/examples/sections/js/page.js
vendored
Executable file
|
|
@ -0,0 +1,94 @@
|
|||
(function() {
|
||||
/*global BaconSource, FastList*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var endEvent = ('ontouchstart' in window) ? 'touchend' : 'mouseup';
|
||||
|
||||
window.addEventListener('load', function() {
|
||||
var listContainer = document.querySelector('section');
|
||||
|
||||
var source = new BaconSource();
|
||||
var list = new FastList(listContainer, source);
|
||||
var scheduler = FastList.scheduler;
|
||||
|
||||
function updateHeader() {
|
||||
return scheduler.mutation(function() {
|
||||
var h1 = document.querySelector('h1');
|
||||
h1.textContent = 'Main List (' + source.fullLength() + ')';
|
||||
h1.scrollTop; // flush
|
||||
});
|
||||
}
|
||||
updateHeader();
|
||||
|
||||
function openAlert(evt) {
|
||||
scheduler.mutation(function() {
|
||||
var detail = evt.detail;
|
||||
var li = source.getRecordAt(detail.index);
|
||||
alert(li.title + ' item clicked!');
|
||||
});
|
||||
}
|
||||
list.list.addEventListener('item-selected', openAlert);
|
||||
|
||||
function clearNewIndicator() {
|
||||
var h1After = document.querySelector('#h1-after');
|
||||
|
||||
if (h1After.dataset.anim == 'reveal') {
|
||||
scheduler.transition(function() {
|
||||
h1After.dataset.anim = 'hide';
|
||||
}, h1After, 'animationend');
|
||||
}
|
||||
}
|
||||
listContainer.addEventListener('top-reached', clearNewIndicator);
|
||||
|
||||
function updateNewIndicator() {
|
||||
var h1After = document.querySelector('#h1-after');
|
||||
if (h1After.dataset.anim == 'reveal') {
|
||||
return;
|
||||
}
|
||||
|
||||
scheduler.transition(function() {
|
||||
h1After.dataset.anim = 'reveal';
|
||||
}, h1After, 'animationend');
|
||||
}
|
||||
listContainer.addEventListener('hidden-new-content', updateNewIndicator);
|
||||
|
||||
function newContentHandler() {
|
||||
var newContent = {
|
||||
title: 'NEW Bacon ' + Date.now().toString().slice(7, -1),
|
||||
body: 'Turkey BLT please.'
|
||||
};
|
||||
|
||||
source.insertAtIndex(0, newContent);
|
||||
list.insertedAtIndex(0);
|
||||
|
||||
updateHeader();
|
||||
}
|
||||
|
||||
setInterval(newContentHandler, 15000);
|
||||
window.addEventListener('new-content', newContentHandler);
|
||||
|
||||
window.pushNewContent = function() {
|
||||
window.dispatchEvent(new CustomEvent('new-content'));
|
||||
};
|
||||
|
||||
var button = document.querySelector('button');
|
||||
button.addEventListener(endEvent, function() {
|
||||
Promise.all([toggleTransitioning(), list.toggleEditMode()])
|
||||
.then(updateText)
|
||||
.then(toggleTransitioning);
|
||||
});
|
||||
|
||||
function updateText(text) {
|
||||
return scheduler.mutation(function() {
|
||||
button.textContent = list.editing ? 'Exit' : 'Edit';
|
||||
});
|
||||
}
|
||||
|
||||
function toggleTransitioning() {
|
||||
return scheduler.feedback(function() {
|
||||
button.classList.toggle('transitioning');
|
||||
}, button, 'transitionend');
|
||||
}
|
||||
});
|
||||
})();
|
||||
14
gaia/apps/music/components/dom-scheduler/bower_components/fast-list/examples/sections/manifest.webapp
vendored
Executable file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"name": "fast-list-demo",
|
||||
"description": "Faaaaasssst",
|
||||
"launch_path": "/index.html",
|
||||
"icons": {
|
||||
"16": "/icons/icon16x16.png",
|
||||
"48": "/icons/icon48x48.png",
|
||||
"60": "/icons/icon60x60.png",
|
||||
"128": "/icons/icon128x128.png"
|
||||
},
|
||||
"type": "certified",
|
||||
"orientation": "default",
|
||||
"permissions": {}
|
||||
}
|
||||
84
gaia/apps/music/components/dom-scheduler/bower_components/fast-list/examples/simple/app.css
vendored
Executable file
|
|
@ -0,0 +1,84 @@
|
|||
|
||||
/* mini reset */
|
||||
* { margin: 0; padding: 0; }
|
||||
|
||||
body,
|
||||
html {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
header,
|
||||
footer {
|
||||
height: 50px;
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
#content {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0 0 0 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
ul section {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul h2 {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.background {
|
||||
position: absolute;
|
||||
z-index: 0;
|
||||
|
||||
width: 100%;
|
||||
|
||||
background: linear-gradient(to top, #bcbcbc, #bcbcbc 1px, white 1px, white);
|
||||
background-position: 0 -88px;
|
||||
background-size: 100% 88px;
|
||||
background-repeat: repeat-y;
|
||||
}
|
||||
|
||||
|
||||
ul li {
|
||||
width: 100%;
|
||||
height: 88px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
|
||||
-moz-user-select: none;
|
||||
|
||||
background: white;
|
||||
border-bottom: 1px #bcbcbc solid;
|
||||
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
ul li h3 {
|
||||
margin: 0;
|
||||
padding: 9px 6px 3px 6px;
|
||||
background: white;
|
||||
|
||||
color: #424242;
|
||||
}
|
||||
|
||||
ul li p {
|
||||
margin: 0;
|
||||
padding: 3px 6px 10px 6px;
|
||||
background: white;
|
||||
|
||||
color: #797979;
|
||||
}
|
||||
22
gaia/apps/music/components/dom-scheduler/bower_components/fast-list/examples/simple/app.js
vendored
Executable file
|
|
@ -0,0 +1,22 @@
|
|||
/*global DataSource, FastList*/
|
||||
|
||||
var content = document.getElementById('content');
|
||||
var data = createDummyData(1000);
|
||||
var dataSource = new DataSource(data);
|
||||
var list = new FastList(content, dataSource);
|
||||
|
||||
function createDummyData(count) {
|
||||
var result = [];
|
||||
|
||||
for (var i = 0; i < count; i++) {
|
||||
result.push({
|
||||
name: 'Song ' + i,
|
||||
metadata: {
|
||||
title: 'Song title ' + i,
|
||||
artist: 'Song artist ' + i
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
79
gaia/apps/music/components/dom-scheduler/bower_components/fast-list/examples/simple/data-source.js
vendored
Executable file
|
|
@ -0,0 +1,79 @@
|
|||
(function(exports) {
|
||||
var itemHeight = 88;
|
||||
|
||||
var debug = 0 ? (...args) => console.log('[DataSource]', ...args) : ()=>{};
|
||||
|
||||
exports.DataSource = function DataSource(items) {
|
||||
this.items = items;
|
||||
debug('initialized', items);
|
||||
};
|
||||
|
||||
exports.DataSource.prototype = {
|
||||
populateItem: function(item, i) {
|
||||
debug('populate item', item);
|
||||
var title = item.firstChild;
|
||||
var body = title.nextSibling;
|
||||
var record = this.getRecordAt(i);
|
||||
|
||||
title.firstChild.data = record.name;
|
||||
body.firstChild.data = record.metadata.artist;
|
||||
},
|
||||
|
||||
getSections() {
|
||||
debug('get sections');
|
||||
return [''];
|
||||
},
|
||||
|
||||
sectionHeaderHeight() {
|
||||
return 0;
|
||||
},
|
||||
|
||||
fullSectionHeight() {
|
||||
var result = this.items.length * itemHeight;
|
||||
debug('full section height', result);
|
||||
return result;
|
||||
},
|
||||
|
||||
fullSectionLength() {
|
||||
var result = this.items.length;
|
||||
debug('full section length', result);
|
||||
return result;
|
||||
},
|
||||
|
||||
getSectionFor() {
|
||||
return '';
|
||||
},
|
||||
|
||||
getRecordAt: function(index) {
|
||||
return this.items[index];
|
||||
},
|
||||
|
||||
indexAtPosition: function(pos) {
|
||||
return Math.floor(pos / itemHeight);
|
||||
},
|
||||
|
||||
positionForIndex: function(index) {
|
||||
return index * itemHeight;
|
||||
},
|
||||
|
||||
fullLength: function() {
|
||||
return this.items.length;
|
||||
},
|
||||
|
||||
itemHeight: function() {
|
||||
return itemHeight;
|
||||
},
|
||||
|
||||
fullHeight: function() {
|
||||
this.items.length * itemHeight;
|
||||
},
|
||||
|
||||
insertAtIndex: function(index, record, toSection) {
|
||||
return this.items.splice(index, 0, record);
|
||||
},
|
||||
|
||||
replaceAtIndex: function(index, record) {
|
||||
return this.items.splice(index, 1)[0];
|
||||
}
|
||||
};
|
||||
})(window);
|
||||
30
gaia/apps/music/components/dom-scheduler/bower_components/fast-list/examples/simple/index.html
vendored
Executable file
|
|
@ -0,0 +1,30 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1">
|
||||
<link rel="stylesheet" href="app.css">
|
||||
<title>fast-list: simple</title>
|
||||
</head>
|
||||
<body>
|
||||
<header>Header</header>
|
||||
<section id="content" style="height:100%;">
|
||||
<ul>
|
||||
<section
|
||||
><h2> </h2
|
||||
><div class="background"
|
||||
></div
|
||||
></section>
|
||||
<li
|
||||
><h3> </h3
|
||||
><p> </p
|
||||
></li>
|
||||
</ul>
|
||||
</section>
|
||||
<footer>Footer</footer>
|
||||
<!-- <script src="bower_components/dom-scheduler/lib/dom-scheduler.js"></script> -->
|
||||
<script src="fast-list.js"></script>
|
||||
<script src="data-source.js"></script>
|
||||
<script src="app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
13
gaia/apps/music/components/dom-scheduler/bower_components/fast-list/examples/simple/manifest.webapp
vendored
Executable file
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"name": "simple-list-demo",
|
||||
"description": "Sweet",
|
||||
"launch_path": "/index.html",
|
||||
"icons": {
|
||||
"16": "/icons/icon16x16.png",
|
||||
"48": "/icons/icon48x48.png",
|
||||
"60": "/icons/icon60x60.png",
|
||||
"128": "/icons/icon128x128.png"
|
||||
},
|
||||
"orientation": "default",
|
||||
"permissions": {}
|
||||
}
|
||||
935
gaia/apps/music/components/dom-scheduler/bower_components/fast-list/fast-list.js
vendored
Executable file
|
|
@ -0,0 +1,935 @@
|
|||
!(function(define){'use strict';define(function(require,exports,module){
|
||||
|
||||
var debug = 0 ? console.log.bind(console, '[FastList]') : function() {};
|
||||
|
||||
var startEvent = ('ontouchstart' in window) ? 'touchstart' : 'mousedown';
|
||||
var moveEvent = ('ontouchstart' in window) ? 'touchmove' : 'mousemove';
|
||||
var endEvent = ('ontouchstart' in window) ? 'touchend' : 'mouseup';
|
||||
|
||||
/**
|
||||
* Use the dom-scheduler if it's around,
|
||||
* else fallback to fake shim.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
var schedule = (window.scheduler && typeof window.scheduler.mutation === "function" ? window.scheduler : schedulerShim());
|
||||
|
||||
/**
|
||||
* Exports
|
||||
*/
|
||||
|
||||
exports = module.exports = FastList;
|
||||
exports.scheduler = schedule;
|
||||
|
||||
function FastList(container, source, scheduler) {
|
||||
this.editing = false;
|
||||
|
||||
container.style.overflowX = 'hidden';
|
||||
container.style.overflowY = 'scroll';
|
||||
this.container = container;
|
||||
this.list = container.querySelector('ul');
|
||||
this.source = source;
|
||||
|
||||
this.geometry = {
|
||||
topPosition: 0,
|
||||
forward: true,
|
||||
|
||||
viewportHeight: 0,
|
||||
itemHeight: 0,
|
||||
headerHeight: 0,
|
||||
maxItemCount: 0,
|
||||
switchWindow: 0
|
||||
};
|
||||
|
||||
this._templateSection = this.list.querySelector('section');
|
||||
this.geometry.headerHeight = source.sectionHeaderHeight();
|
||||
this._templateSection.remove();
|
||||
|
||||
var template = this.list.querySelector('li');
|
||||
template.style.position = 'absolute';
|
||||
template.style.left = template.style.top = 0;
|
||||
template.style.overflow = 'hidden';
|
||||
template.style.willChange = 'transform';
|
||||
|
||||
this.geometry.itemHeight = template.offsetHeight;
|
||||
template.remove();
|
||||
|
||||
this._template = template;
|
||||
|
||||
this.updateContainerGeometry();
|
||||
|
||||
if (debug.name) {
|
||||
if (this.geometry.itemHeight !== source.itemHeight()) {
|
||||
debug('Template height and source height are not the same.');
|
||||
}
|
||||
}
|
||||
|
||||
this._rendered = false;
|
||||
this._previousTop = -1;
|
||||
this._previousFast = false;
|
||||
this._items = [];
|
||||
this._itemsInDOM = [];
|
||||
|
||||
this.reorderingContext = {
|
||||
item: null,
|
||||
initialY: null,
|
||||
identifier: null,
|
||||
moveUp: null,
|
||||
moveDown: null,
|
||||
};
|
||||
|
||||
this.updateListHeight();
|
||||
|
||||
schedule.attachDirect(
|
||||
this.container,
|
||||
'scroll',
|
||||
this.handleScroll.bind(this)
|
||||
);
|
||||
|
||||
on(this.container, 'click', this);
|
||||
on(window, 'resize', this);
|
||||
|
||||
schedule.mutation(function() {
|
||||
this.updateSections();
|
||||
this.render();
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
FastList.prototype = {
|
||||
|
||||
/* Rendering
|
||||
========= */
|
||||
updateContainerGeometry: function() {
|
||||
var geo = this.geometry;
|
||||
|
||||
geo.viewportHeight = this.container.offsetHeight;
|
||||
var itemPerScreen = geo.viewportHeight / geo.itemHeight;
|
||||
// Taking into account the will-change budget multiplier from
|
||||
// layout/base/nsDisplayList.cpp#1193
|
||||
geo.maxItemCount = Math.floor(itemPerScreen * 2.4);
|
||||
geo.switchWindow = Math.floor(itemPerScreen / 2);
|
||||
|
||||
debug('maxItemCount: ' + geo.maxItemCount);
|
||||
},
|
||||
|
||||
// Returns true if we're fast scrolling
|
||||
updateViewportGeometry: function() {
|
||||
var geo = this.geometry;
|
||||
|
||||
var position = this.container.scrollTop;
|
||||
|
||||
// Don't compute velocity on first load
|
||||
if (this._previousTop === -1) {
|
||||
geo.topPosition = position;
|
||||
}
|
||||
|
||||
this._previousTop = geo.topPosition;
|
||||
geo.topPosition = position;
|
||||
|
||||
var distanceSinceLast = geo.topPosition - this._previousTop;
|
||||
|
||||
if ((distanceSinceLast > 0) && !geo.forward) {
|
||||
geo.forward = true;
|
||||
}
|
||||
|
||||
if ((distanceSinceLast < 0) && geo.forward) {
|
||||
geo.forward = false;
|
||||
}
|
||||
|
||||
if (geo.topPosition === 0 && this._previousTop !== 0) {
|
||||
this.container.dispatchEvent(new CustomEvent('top-reached'));
|
||||
}
|
||||
|
||||
var moved = Math.abs(distanceSinceLast);
|
||||
var fastScrolling = this._previousFast;
|
||||
if (!fastScrolling && moved > geo.viewportHeight * 2) {
|
||||
fastScrolling = true;
|
||||
}
|
||||
if (fastScrolling && moved > 0 && moved < geo.viewportHeight * 0.5) {
|
||||
fastScrolling = false;
|
||||
}
|
||||
this._previousFast = fastScrolling;
|
||||
|
||||
var onTop = position === 0;
|
||||
var atBottom = position === this.source.fullHeight() - geo.viewportHeight;
|
||||
|
||||
return fastScrolling && !onTop && !atBottom;
|
||||
},
|
||||
|
||||
render: function(reload, changedIndex) {
|
||||
var source = this.source;
|
||||
var items = this._items;
|
||||
var itemsInDOM = this._itemsInDOM;
|
||||
var geo = this.geometry;
|
||||
var template = this._template;
|
||||
var list = this.list;
|
||||
|
||||
var indices = computeIndices(this.source, this.geometry);
|
||||
var criticalStart = indices.cStart;
|
||||
var criticalEnd = indices.cEnd;
|
||||
var startIndex = indices.start;
|
||||
var endIndex = indices.end;
|
||||
|
||||
// Initial render generating all dom nodes
|
||||
if (!this._rendered) {
|
||||
this._rendered = true;
|
||||
endIndex = Math.min(source.fullLength() - 1,
|
||||
startIndex + this.geometry.maxItemCount - 1);
|
||||
}
|
||||
|
||||
var recyclableItems = recycle(items, criticalStart, criticalEnd,
|
||||
geo.forward ? endIndex : startIndex);
|
||||
|
||||
var findItemFor = function(index) {
|
||||
var item;
|
||||
|
||||
if (recyclableItems.length > 0) {
|
||||
var recycleIndex = recyclableItems.pop();
|
||||
item = items[recycleIndex];
|
||||
delete items[recycleIndex];
|
||||
} else if (itemsInDOM.length < geo.maxItemCount){
|
||||
item = template.cloneNode(true);
|
||||
list.appendChild(item);
|
||||
itemsInDOM.push(item);
|
||||
} else {
|
||||
console.warn('missing a cell');
|
||||
return;
|
||||
}
|
||||
|
||||
items[index] = item;
|
||||
return item;
|
||||
};
|
||||
|
||||
var renderItem = function(i) {
|
||||
var item = items[i];
|
||||
if (!item) {
|
||||
item = findItemFor(i);
|
||||
tryToPopulate(item, i, source, true);
|
||||
item.classList.toggle('new', i === changedIndex);
|
||||
} else if (reload) {
|
||||
source.populateItem(item, i);
|
||||
}
|
||||
|
||||
var section = source.getSectionFor(i);
|
||||
placeItem(item, i, section, geo, source, reload);
|
||||
};
|
||||
|
||||
if (geo.forward) {
|
||||
for (var i = startIndex; i <= endIndex; ++i) {
|
||||
renderItem(i);
|
||||
}
|
||||
} else {
|
||||
for (var j = endIndex; j >= startIndex; --j) {
|
||||
renderItem(j);
|
||||
}
|
||||
}
|
||||
|
||||
debugViewport(items, geo.forward, criticalStart, criticalEnd,
|
||||
startIndex, endIndex);
|
||||
},
|
||||
|
||||
/* Scrolling
|
||||
========= */
|
||||
handleScroll: function(evt) {
|
||||
var fast = this.updateViewportGeometry();
|
||||
if (fast) {
|
||||
debug('[x] ---------- faaaaassssstttt');
|
||||
} else {
|
||||
this.render();
|
||||
}
|
||||
},
|
||||
|
||||
updateListHeight: function() {
|
||||
return schedule.mutation((function() {
|
||||
this.list.style.height = this.source.fullHeight() + 'px';
|
||||
}).bind(this));
|
||||
},
|
||||
|
||||
get scrollTop() {
|
||||
return this.geometry.topPosition;
|
||||
},
|
||||
|
||||
scrollInstantly: function(by) {
|
||||
this.container.scrollTop += by;
|
||||
this.updateViewportGeometry();
|
||||
this.render();
|
||||
},
|
||||
|
||||
reloadData: function() {
|
||||
return schedule.mutation((function() {
|
||||
this.updateSections();
|
||||
this.render(true);
|
||||
this.updateListHeight();
|
||||
}).bind(this));
|
||||
},
|
||||
|
||||
updateSections: function() {
|
||||
var list = this.list;
|
||||
var source = this.source;
|
||||
var template = this._templateSection;
|
||||
|
||||
return schedule.mutation(function() {
|
||||
var nodes = list.querySelectorAll('section');
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
var toRemove = nodes[i];
|
||||
toRemove.remove();
|
||||
}
|
||||
|
||||
var headerHeight = source.sectionHeaderHeight();
|
||||
for (var section of source.getSections()) {
|
||||
var height = source.fullSectionHeight(section);
|
||||
|
||||
var sectionNode = template.cloneNode(true);
|
||||
sectionNode.style.height = headerHeight + height + 'px';
|
||||
|
||||
var title = sectionNode.firstChild;
|
||||
title.firstChild.data = section;
|
||||
|
||||
var background = title.nextSibling;
|
||||
background.style.height = height + 'px';
|
||||
|
||||
list.appendChild(sectionNode);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/* Edit mode
|
||||
========= */
|
||||
toggleEditMode: function() {
|
||||
this.editing = !this.editing;
|
||||
|
||||
if (this.editing) {
|
||||
this._startTouchListeners();
|
||||
} else {
|
||||
this._stopTouchListeners();
|
||||
}
|
||||
|
||||
return toggleEditClass(this.list,
|
||||
this._itemsInDOM, this.editing);
|
||||
},
|
||||
|
||||
_startTouchListeners: function() {
|
||||
on(this.list, startEvent, this);
|
||||
on(this.list, endEvent, this);
|
||||
},
|
||||
|
||||
_stopTouchListeners: function() {
|
||||
off(this.list, startEvent, this);
|
||||
off(this.list, endEvent, this);
|
||||
},
|
||||
|
||||
/* Reordering
|
||||
---------- */
|
||||
_reorderStart: function(evt) {
|
||||
debug('reorder start');
|
||||
var ctx = this.reorderingContext;
|
||||
|
||||
// Already tracking an item, bailing out
|
||||
if (ctx.item) {
|
||||
return;
|
||||
}
|
||||
|
||||
var li = evt.target.parentNode.parentNode;
|
||||
var touch = evt.touches && evt.touches[0] || evt;
|
||||
|
||||
ctx.item = li;
|
||||
ctx.initialY = touch.pageY;
|
||||
ctx.identifier = touch.identifier;
|
||||
ctx.moveUp = new Set();
|
||||
ctx.moveDown = new Set();
|
||||
ctx.moveHandler = this._reorderMove.bind(this);
|
||||
|
||||
var listenToMove = (function() {
|
||||
schedule.attachDirect(this.list, moveEvent, ctx.moveHandler);
|
||||
}).bind(this);
|
||||
|
||||
setupForDragging(this.scheduler, li, true)
|
||||
.then(listenToMove)
|
||||
.then(toggleDraggingClass.bind(null, this.scheduler, li, true));
|
||||
},
|
||||
|
||||
_reorderMove: function(evt) {
|
||||
debug('reorder move');
|
||||
var ctx = this.reorderingContext;
|
||||
if (!ctx.item) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Multi touch
|
||||
if (evt.touches && evt.touches.length > 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
var li = ctx.item;
|
||||
var touch = evt.touches && evt.touches[0] || evt;
|
||||
var position = touch.pageY;
|
||||
|
||||
var delta = position - ctx.initialY;
|
||||
|
||||
this._rearrange(delta);
|
||||
tweakTransform(li, delta);
|
||||
|
||||
// TODO: scroll when close to the beginning/end of the viewport
|
||||
},
|
||||
|
||||
_reorderEnd: function(evt) {
|
||||
debug('reorder end');
|
||||
|
||||
var ctx = this.reorderingContext;
|
||||
if (!ctx.item) {
|
||||
return;
|
||||
}
|
||||
|
||||
var li = ctx.item;
|
||||
schedule.detachDirect(this.list, moveEvent, ctx.moveHandler);
|
||||
|
||||
var touch = evt.touches && evt.touches[0] || evt;
|
||||
if (touch.identifier == ctx.identifier) {
|
||||
var position = touch.pageY;
|
||||
var delta = position - ctx.initialY;
|
||||
computeChanges(ctx, this.geometry, this._itemsInDOM, delta);
|
||||
}
|
||||
|
||||
Promise.all([
|
||||
applyChanges(ctx, this.geometry, this._itemsInDOM),
|
||||
moveInPlace(ctx, this.geometry)
|
||||
]).then(this._reorderFinish.bind(this, li))
|
||||
.then(toggleDraggingClass.bind(null, schedule, li, false));
|
||||
},
|
||||
|
||||
_reorderFinish: function(li) {
|
||||
var scheduler = this.scheduler;
|
||||
return Promise.all([this._commitToDocument(),
|
||||
setupForDragging(scheduler, li, false)]);
|
||||
},
|
||||
|
||||
_rearrange: function(delta) {
|
||||
var ctx = this.reorderingContext;
|
||||
var upCount = ctx.moveUp.size;
|
||||
var downCount = ctx.moveDown.size;
|
||||
|
||||
computeChanges(this.reorderingContext, this.geometry,
|
||||
this._itemsInDOM, delta);
|
||||
|
||||
if (ctx.moveUp.size === upCount &&
|
||||
ctx.moveDown.size === downCount) {
|
||||
return;
|
||||
}
|
||||
|
||||
applyChanges(this.reorderingContext,
|
||||
this.geometry, this._itemsInDOM);
|
||||
},
|
||||
|
||||
_commitToDocument: function() {
|
||||
var ctx = this.reorderingContext;
|
||||
var li = ctx.item;
|
||||
var itemsInDOM = this._itemsInDOM;
|
||||
var items = this._items;
|
||||
var source = this.source;
|
||||
|
||||
return schedule.mutation((function() {
|
||||
// Nothing to do
|
||||
if (!ctx.moveUp && !ctx.moveDown) {
|
||||
return;
|
||||
}
|
||||
|
||||
var index = items.indexOf(li);
|
||||
var section = li.dataset.section;
|
||||
var newIndex = index + ctx.moveUp.size - ctx.moveDown.size;
|
||||
|
||||
items.splice(index, 1);
|
||||
items.splice(newIndex, 0, li);
|
||||
var c = source.removeAtIndex(index);
|
||||
source.insertAtIndex(newIndex, c, section);
|
||||
|
||||
itemsInDOM.forEach(function(item) {
|
||||
resetTransition(item);
|
||||
resetTransform(item);
|
||||
});
|
||||
|
||||
this.updateSections();
|
||||
this.render();
|
||||
|
||||
ctx.item = null;
|
||||
ctx.initialY = null;
|
||||
ctx.identifier = null;
|
||||
ctx.moveUp = null;
|
||||
ctx.moveDown = null;
|
||||
ctx.moveHandler = null;
|
||||
}).bind(this));
|
||||
},
|
||||
|
||||
/* External content changes
|
||||
======================== */
|
||||
insertedAtIndex: function(index) {
|
||||
if (index !== 0) {
|
||||
//TODO: support any point of insertion
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.geometry.topPosition > this.geometry.itemHeight ||
|
||||
this.editing) {
|
||||
// No transition needed, just keep the scroll position
|
||||
this._insertOnTop(true);
|
||||
return;
|
||||
}
|
||||
|
||||
var domItems = this._itemsInDOM;
|
||||
var list = this.list;
|
||||
|
||||
list.classList.add('reordering');
|
||||
pushDown(this.scheduler, domItems, this.geometry)
|
||||
.then(this._insertOnTop.bind(this, false))
|
||||
.then(cleanInlineStyles.bind(null, domItems))
|
||||
.then(reveal.bind(null, this.list))
|
||||
.then(function() {
|
||||
list.classList.remove('reordering');
|
||||
});
|
||||
},
|
||||
|
||||
_insertOnTop: function(keepScrollPosition) {
|
||||
return schedule.mutation((function() {
|
||||
this._items.unshift(null);
|
||||
delete this._items[0]; // keeping it sparse
|
||||
|
||||
this.updateSections();
|
||||
|
||||
if (keepScrollPosition) {
|
||||
this.scrollInstantly(this.geometry.itemHeight);
|
||||
this.container.dispatchEvent(new CustomEvent('hidden-new-content'));
|
||||
} else {
|
||||
this.render(false, 0);
|
||||
}
|
||||
|
||||
this.updateListHeight();
|
||||
}).bind(this));
|
||||
},
|
||||
|
||||
handleEvent: function(evt) {
|
||||
switch (evt.type) {
|
||||
case 'resize':
|
||||
this.geometry.viewportHeight = this.container.offsetHeight;
|
||||
this.updateContainerGeometry();
|
||||
break;
|
||||
case startEvent:
|
||||
if (!evt.target.classList.contains('cursor')) {
|
||||
return;
|
||||
}
|
||||
evt.preventDefault();
|
||||
this._reorderStart(evt);
|
||||
break;
|
||||
case endEvent:
|
||||
if (!evt.target.classList.contains('cursor') &&
|
||||
endEvent === 'touchend') {
|
||||
return;
|
||||
}
|
||||
evt.preventDefault();
|
||||
this._reorderEnd(evt);
|
||||
break;
|
||||
case 'click':
|
||||
if (this.editing) {
|
||||
break;
|
||||
}
|
||||
|
||||
var li = evt.target;
|
||||
var index = this._items.indexOf(li);
|
||||
|
||||
this.list.dispatchEvent(new CustomEvent('item-selected', {detail: {
|
||||
index: index,
|
||||
clickEvt: evt
|
||||
}}));
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/* Internals
|
||||
========= */
|
||||
|
||||
/* ASCII Art viewport debugging
|
||||
---------------------------- */
|
||||
function debugViewport(items, forward, cStart, cEnd, start, end) {
|
||||
if (!debug.name) {
|
||||
return;
|
||||
}
|
||||
|
||||
var str = '[' + (forward ? 'v' : '^') + ']';
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
if (i == start) {
|
||||
str += '|';
|
||||
}
|
||||
if (i == cStart) {
|
||||
str += '[';
|
||||
}
|
||||
if (items[i]) {
|
||||
str += 'x';
|
||||
} else {
|
||||
str += '-';
|
||||
}
|
||||
if (i == cEnd) {
|
||||
str += ']';
|
||||
}
|
||||
if (i == end) {
|
||||
str += '|';
|
||||
}
|
||||
}
|
||||
|
||||
debug(str);
|
||||
}
|
||||
|
||||
function computeIndices(source, geometry) {
|
||||
var criticalStart = source.indexAtPosition(geometry.topPosition);
|
||||
var criticalEnd = source.indexAtPosition(geometry.topPosition +
|
||||
geometry.viewportHeight);
|
||||
|
||||
var canPrerender = geometry.maxItemCount -
|
||||
(criticalEnd - criticalStart) - 1;
|
||||
var before = geometry.switchWindow;
|
||||
var after = canPrerender - before;
|
||||
var lastIndex = source.fullLength() - 1;
|
||||
|
||||
var startIndex;
|
||||
var endIndex;
|
||||
|
||||
if (geometry.forward) {
|
||||
startIndex = Math.max(0, criticalStart - before);
|
||||
endIndex = Math.min(lastIndex,
|
||||
criticalEnd + after);
|
||||
} else {
|
||||
startIndex = Math.max(0, criticalStart - after);
|
||||
endIndex = Math.min(lastIndex,
|
||||
criticalEnd + before);
|
||||
}
|
||||
|
||||
return {
|
||||
cStart: criticalStart,
|
||||
cEnd: criticalEnd,
|
||||
start: startIndex,
|
||||
end: endIndex
|
||||
};
|
||||
}
|
||||
|
||||
function recycle(items, start, end, action) {
|
||||
var recyclableItems = [];
|
||||
for (var i in items) {
|
||||
if ((i < start) || (i > end)) {
|
||||
recyclableItems.push(i);
|
||||
}
|
||||
}
|
||||
|
||||
// Put the items that are furthest away from the displayport edge
|
||||
// at the end of the array.
|
||||
recyclableItems.sort(function(a, b) {
|
||||
return Math.abs(a - action) - Math.abs(b - action);
|
||||
});
|
||||
|
||||
return recyclableItems;
|
||||
}
|
||||
|
||||
function tryToPopulate(item, index, source, first) {
|
||||
if (parseInt(item.dataset.index) !== index && !first) {
|
||||
// The item was probably reused
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: should be in a mutation block when !first
|
||||
var populateResult = source.populateItem(item, index);
|
||||
|
||||
if (populateResult instanceof Promise) {
|
||||
item.dataset.populated = false;
|
||||
populateResult.then(tryToPopulate.bind(null, item, index,
|
||||
source));
|
||||
return;
|
||||
}
|
||||
|
||||
if (first) {
|
||||
item.dataset.populated = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Revealing the populated item
|
||||
item.style.transition = 'opacity 0.2s linear';
|
||||
schedule.transition(function() {
|
||||
item.dataset.populated = true;
|
||||
}, item, 'transitionend').then(function() {
|
||||
item.style.transition = '';
|
||||
});
|
||||
}
|
||||
|
||||
function placeItem(item, index, section, geometry, source, reload) {
|
||||
if (parseInt(item.dataset.index) === index && !reload) {
|
||||
// The item was probably reused
|
||||
return;
|
||||
}
|
||||
|
||||
item.dataset.position = source.positionForIndex(index);
|
||||
item.dataset.index = index;
|
||||
item.dataset.section = section;
|
||||
|
||||
var tweakedBy = parseInt(item.dataset.tweakDelta);
|
||||
if (tweakedBy) {
|
||||
tweakTransform(item, tweakedBy);
|
||||
} else {
|
||||
resetTransform(item);
|
||||
}
|
||||
}
|
||||
|
||||
function resetTransform(item) {
|
||||
var position = parseInt(item.dataset.position);
|
||||
item.style.webkitTransform =
|
||||
item.style.transform = 'translate3d(0, ' + position + 'px, 0)';
|
||||
item.dataset.tweakDelta = '';
|
||||
}
|
||||
|
||||
function tweakTransform(item, delta) {
|
||||
var position = parseInt(item.dataset.position) + delta;
|
||||
item.style.webkitTransform =
|
||||
item.style.transform = 'translate3d(0, ' + position + 'px, 0)';
|
||||
item.dataset.tweakDelta = delta;
|
||||
}
|
||||
|
||||
function resetTransition(item) {
|
||||
item.style.transition = '';
|
||||
item.style.webkitTransition = '';
|
||||
}
|
||||
|
||||
function tweakAndTransition(item, tweak) {
|
||||
return schedule.feedback(function() {
|
||||
item.style.transition = 'transform 0.15s ease';
|
||||
item.style.webkitTransition = '-webkit-transform 0.15s ease';
|
||||
if (tweak === 0) {
|
||||
setTimeout(resetTransform.bind(null, item));
|
||||
return;
|
||||
}
|
||||
setTimeout(tweakTransform.bind(null, item, tweak));
|
||||
}, item, 'transitionend')
|
||||
.then(resetTransition.bind(null, item));
|
||||
}
|
||||
|
||||
function pushDown(domItems, geometry) {
|
||||
if (!domItems.length) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return schedule.transition(function() {
|
||||
for (var i = 0; i < domItems.length; i++) {
|
||||
var item = domItems[i];
|
||||
item.style.transition = 'transform 0.15s ease-in';
|
||||
item.style.webkitTransition = '-webkit-transform 0.15s ease-in';
|
||||
tweakTransform(item, geometry.itemHeight);
|
||||
}
|
||||
}, domItems[0], 'transitionend');
|
||||
}
|
||||
|
||||
function reveal(list) {
|
||||
var newEl = list.querySelector('li.new');
|
||||
|
||||
return schedule.transition(function() {
|
||||
newEl.style.transition = 'opacity 0.25s ease-out';
|
||||
newEl.style.webkitTransition = 'opacity 0.25s ease-out';
|
||||
setTimeout(function() {
|
||||
newEl.classList.remove('new');
|
||||
});
|
||||
}, newEl, 'transitionend').then(function() {
|
||||
newEl.style.transition = '';
|
||||
newEl.style.webkitTransition = '';
|
||||
});
|
||||
}
|
||||
|
||||
function cleanInlineStyles(domItems) {
|
||||
return schedule.mutation(function() {
|
||||
for (var i = 0; i < domItems.length; i++) {
|
||||
var item = domItems[i];
|
||||
item.style.transition = '';
|
||||
item.style.webkitTransition = '';
|
||||
resetTransform(item);
|
||||
}
|
||||
domItems[0] && domItems[0].scrollTop; // flushing
|
||||
});
|
||||
}
|
||||
|
||||
function toggleEditClass(list, domItems, editing) {
|
||||
if (!domItems.length) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return schedule.feedback(function() {
|
||||
for (var i = 0; i < domItems.length; i++) {
|
||||
var item = domItems[i];
|
||||
var overlay = item.querySelector('.overlay');
|
||||
overlay.dataset.anim = editing ? 'reveal' : 'hide';
|
||||
item.classList.toggle('edit', editing);
|
||||
}
|
||||
}, list, 'animationend');
|
||||
}
|
||||
|
||||
function setupForDragging(scheduler, item, on) {
|
||||
return schedule.mutation(function() {
|
||||
item.parentNode.classList.toggle('reordering', on);
|
||||
item.style.zIndex = on ? '1000' : '';
|
||||
item.style.boxShadow = on ? '0 0 3px 1px #bcbcbc' : '';
|
||||
});
|
||||
}
|
||||
|
||||
function toggleDraggingClass(scheduler, item, on) {
|
||||
return schedule.feedback(function() {
|
||||
var overlay = item.querySelector('.overlay');
|
||||
overlay.dataset.anim = on ? 'hide' : 'reveal';
|
||||
}, item, 'animationend');
|
||||
}
|
||||
|
||||
function computeChanges(context, geometry, domItems, delta) {
|
||||
if (!context.item) {
|
||||
return;
|
||||
}
|
||||
|
||||
var draggedItem = context.item;
|
||||
var draggedOriginal = parseInt(draggedItem.dataset.position);
|
||||
var draggedTweaked = draggedOriginal + delta;
|
||||
|
||||
for (var i = 0; i < domItems.length; i++) {
|
||||
var item = domItems[i];
|
||||
if (item === draggedItem) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: support re-ordering accross sections
|
||||
if (item.dataset.section !== draggedItem.dataset.section) {
|
||||
continue;
|
||||
}
|
||||
|
||||
var itemOriginal = parseInt(item.dataset.position);
|
||||
var itemTweaked = itemOriginal + parseInt(item.dataset.tweakDelta);
|
||||
|
||||
if ((itemOriginal < draggedOriginal) && (draggedTweaked < itemOriginal)) {
|
||||
context.moveDown.add(item);
|
||||
} else {
|
||||
if (draggedTweaked > itemTweaked) {
|
||||
context.moveDown.delete(item);
|
||||
}
|
||||
}
|
||||
|
||||
if ((itemOriginal > draggedOriginal) && (draggedTweaked > itemOriginal)) {
|
||||
context.moveUp.add(item);
|
||||
} else {
|
||||
if (draggedTweaked < itemTweaked) {
|
||||
context.moveUp.delete(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function applyChanges(context, geometry, domItems) {
|
||||
if (!context.item) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
var itemHeight = geometry.itemHeight;
|
||||
var draggedItem = context.item;
|
||||
var promises = [];
|
||||
|
||||
for (var i = 0; i < domItems.length; i++) {
|
||||
var item = domItems[i];
|
||||
if (item === draggedItem) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Reset
|
||||
if (item.dataset.tweakDelta &&
|
||||
!context.moveUp.has(item) && !context.moveDown.has(item)) {
|
||||
|
||||
promises.push(tweakAndTransition(item, 0));
|
||||
}
|
||||
|
||||
// Moving down
|
||||
if (context.moveDown.has(item)) {
|
||||
if (parseInt(item.dataset.tweakDelta) !== itemHeight) {
|
||||
promises.push(tweakAndTransition(item, itemHeight));
|
||||
}
|
||||
}
|
||||
|
||||
// Moving up
|
||||
if (context.moveUp.has(item)) {
|
||||
if (parseInt(item.dataset.tweakDelta) !== itemHeight * -1) {
|
||||
promises.push(tweakAndTransition(item, itemHeight * -1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
function moveInPlace(context, geometry) {
|
||||
var li = context.item;
|
||||
|
||||
// We're already in place
|
||||
if (!li.dataset.tweakDelta) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
var position = parseInt(li.dataset.position);
|
||||
var taintedPosition = position + parseInt(li.dataset.tweakDelta);
|
||||
var itemHeight = geometry.itemHeight;
|
||||
var newPosition = position + context.moveUp.size * itemHeight -
|
||||
context.moveDown.size * itemHeight;
|
||||
|
||||
var duration = (Math.abs(taintedPosition - newPosition) / itemHeight) * 150;
|
||||
|
||||
return schedule.feedback(function() {
|
||||
li.style.transition = 'transform ' + duration + 'ms linear';
|
||||
li.style.webkitTransition = '-webkit-transform ' + duration + 'ms linear';
|
||||
setTimeout(tweakTransform.bind(null, li, (newPosition - position)));
|
||||
}, li, 'transitionend');
|
||||
}
|
||||
|
||||
/**
|
||||
* Utils
|
||||
*/
|
||||
|
||||
function schedulerShim() {
|
||||
var raf = window.requestAnimationFrame;
|
||||
|
||||
return {
|
||||
mutation: function(block) { return Promise.resolve(block()); },
|
||||
transition: function(block, el, event, timeout) {
|
||||
block();
|
||||
return after(el, event, timeout || 500);
|
||||
},
|
||||
|
||||
// Not sure if this should be different from .transition()
|
||||
feedback: function(block, el, event, timeout) {
|
||||
block();
|
||||
return after(el, event, timeout || 500);
|
||||
},
|
||||
|
||||
attachDirect: function(el, event, fn) {
|
||||
fn._raffed = function(e) { raf(function() { fn(e); } ); };
|
||||
on(el, event, fn._raffed);
|
||||
},
|
||||
|
||||
detachDirect: function(el, event, fn) {
|
||||
off(el, event, fn._raffed);
|
||||
}
|
||||
};
|
||||
|
||||
function after(target, event, timeout) {
|
||||
return new Promise(function(resolve) {
|
||||
var timer = timeout && setTimeout(cb, timeout);
|
||||
on(target, event, cb);
|
||||
function cb() {
|
||||
off(target, event, cb);
|
||||
clearTimeout(timer);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Shorthand
|
||||
function on(el, name, fn) { el.addEventListener(name, fn); }
|
||||
function off(el, name, fn) { el.removeEventListener(name, fn); }
|
||||
|
||||
});})((typeof define)[0]=='f'&&define.amd?define:(function(n,w){'use strict';
|
||||
return(typeof module)[0]=='o'?function(c){c(require,exports,module);}:
|
||||
function(c){var m={exports:{}};c(function(n){w[n];},m.exports,m);
|
||||
w[n]=m.exports;};})('FastList',this));
|
||||
24
gaia/apps/music/components/dom-scheduler/bower_components/fast-list/package.json
vendored
Executable file
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"name": "fast-list",
|
||||
"main": "fast-list.js",
|
||||
"devDependencies": {
|
||||
"jshint": "^2.7.0",
|
||||
"karma": "^0.12.31",
|
||||
"karma-firefox-launcher": "^0.1.4",
|
||||
"karma-chrome-launcher": "^0.1.4",
|
||||
"karma-mocha": "^0.1.10",
|
||||
"karma-sinon-chai": "^0.3.0"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "./node_modules/jshint/bin/jshint lib/*.js test/*.js",
|
||||
"test": "./node_modules/karma/bin/karma start test/karma.conf --single-run",
|
||||
"test-dev": "./node_modules/karma/bin/karma start test/karma.conf"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/gaia-components/fast-list.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/gaia-components/fast-list/issues"
|
||||
}
|
||||
}
|
||||
18
gaia/apps/music/components/dom-scheduler/bower_components/gaia-component/bower.json
vendored
Executable file
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"name": "gaia-component",
|
||||
"version": "0.3.8",
|
||||
"authors": [
|
||||
"Wilson Page <wilsonpage@me.com>"
|
||||
],
|
||||
"main": "gaia-component.js",
|
||||
"license": "MIT",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"README.md",
|
||||
"package.json",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"test",
|
||||
"tests"
|
||||
]
|
||||
}
|
||||
420
gaia/apps/music/components/dom-scheduler/bower_components/gaia-component/gaia-component.js
vendored
Executable file
|
|
@ -0,0 +1,420 @@
|
|||
/* globals define */
|
||||
;(function(define){'use strict';define(function(require,exports,module){
|
||||
/**
|
||||
* Locals
|
||||
*/
|
||||
var textContent = Object.getOwnPropertyDescriptor(Node.prototype,
|
||||
'textContent');
|
||||
var innerHTML = Object.getOwnPropertyDescriptor(Element.prototype, 'innerHTML');
|
||||
var removeAttribute = Element.prototype.removeAttribute;
|
||||
var setAttribute = Element.prototype.setAttribute;
|
||||
var noop = function() {};
|
||||
|
||||
/**
|
||||
* Register a new component.
|
||||
*
|
||||
* @param {String} name
|
||||
* @param {Object} props
|
||||
* @return {constructor}
|
||||
* @public
|
||||
*/
|
||||
exports.register = function(name, props) {
|
||||
var baseProto = getBaseProto(props.extends);
|
||||
var template = props.template || baseProto.templateString;
|
||||
|
||||
// Components are extensible by default but can be declared
|
||||
// as non extensible as an optimization to avoid
|
||||
// storing the template strings
|
||||
var extensible = props.extensible = props.hasOwnProperty('extensible')?
|
||||
props.extensible : true;
|
||||
|
||||
// Clean up
|
||||
delete props.extends;
|
||||
|
||||
// Pull out CSS that needs to be in the light-dom
|
||||
if (template) {
|
||||
// Stores the string to be reprocessed when
|
||||
// a new component extends this one
|
||||
if (extensible && props.template) {
|
||||
props.templateString = props.template;
|
||||
}
|
||||
|
||||
var output = processCss(template, name);
|
||||
|
||||
props.template = document.createElement('template');
|
||||
props.template.innerHTML = output.template;
|
||||
props.lightCss = output.lightCss;
|
||||
|
||||
props.globalCss = props.globalCss || '';
|
||||
props.globalCss += output.globalCss;
|
||||
}
|
||||
|
||||
// Inject global CSS into the document,
|
||||
// and delete as no longer needed
|
||||
injectGlobalCss(props.globalCss);
|
||||
delete props.globalCss;
|
||||
|
||||
// Merge base getter/setter attributes with the user's,
|
||||
// then define the property descriptors on the prototype.
|
||||
var descriptors = mixin(props.attrs || {}, base.descriptors);
|
||||
|
||||
// Store the orginal descriptors somewhere
|
||||
// a little more private and delete the original
|
||||
props._attrs = props.attrs;
|
||||
delete props.attrs;
|
||||
|
||||
// Create the prototype, extended from base and
|
||||
// define the descriptors directly on the prototype
|
||||
var proto = createProto(baseProto, props);
|
||||
Object.defineProperties(proto, descriptors);
|
||||
|
||||
// Register the custom-element and return the constructor
|
||||
try {
|
||||
return document.registerElement(name, { prototype: proto });
|
||||
} catch (e) {
|
||||
if (e.name !== 'NotSupportedError') {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var base = {
|
||||
properties: {
|
||||
GaiaComponent: true,
|
||||
attributeChanged: noop,
|
||||
attached: noop,
|
||||
detached: noop,
|
||||
created: noop,
|
||||
|
||||
createdCallback: function() {
|
||||
if (this.rtl) { addDirObserver(); }
|
||||
injectLightCss(this);
|
||||
this.created();
|
||||
},
|
||||
|
||||
/**
|
||||
* It is very common to want to keep object
|
||||
* properties in-sync with attributes,
|
||||
* for example:
|
||||
*
|
||||
* el.value = 'foo';
|
||||
* el.setAttribute('value', 'foo');
|
||||
*
|
||||
* So we support an object on the prototype
|
||||
* named 'attrs' to provide a consistent
|
||||
* way for component authors to define
|
||||
* these properties. When an attribute
|
||||
* changes we keep the attr[name]
|
||||
* up-to-date.
|
||||
*
|
||||
* @param {String} name
|
||||
* @param {String||null} from
|
||||
* @param {String||null} to
|
||||
*/
|
||||
attributeChangedCallback: function(name, from, to) {
|
||||
var prop = toCamelCase(name);
|
||||
if (this._attrs && this._attrs[prop]) { this[prop] = to; }
|
||||
this.attributeChanged(name, from, to);
|
||||
},
|
||||
|
||||
attachedCallback: function() { this.attached(); },
|
||||
detachedCallback: function() { this.detached(); },
|
||||
|
||||
/**
|
||||
* A convenient method for setting up
|
||||
* a shadow-root using the defined template.
|
||||
*
|
||||
* @return {ShadowRoot}
|
||||
*/
|
||||
setupShadowRoot: function() {
|
||||
if (!this.template) { return; }
|
||||
var node = document.importNode(this.template.content, true);
|
||||
this.createShadowRoot().appendChild(node);
|
||||
return this.shadowRoot;
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets an attribute internally
|
||||
* and externally. This is so that
|
||||
* we can style internal shadow-dom
|
||||
* content.
|
||||
*
|
||||
* @param {String} name
|
||||
* @param {String} value
|
||||
*/
|
||||
setAttr: function(name, value) {
|
||||
var internal = this.shadowRoot.firstElementChild;
|
||||
setAttribute.call(internal, name, value);
|
||||
setAttribute.call(this, name, value);
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes an attribute internally
|
||||
* and externally. This is so that
|
||||
* we can style internal shadow-dom
|
||||
* content.
|
||||
*
|
||||
* @param {String} name
|
||||
* @param {String} value
|
||||
*/
|
||||
removeAttr: function(name) {
|
||||
var internal = this.shadowRoot.firstElementChild;
|
||||
removeAttribute.call(internal, name);
|
||||
removeAttribute.call(this, name);
|
||||
}
|
||||
},
|
||||
|
||||
descriptors: {
|
||||
textContent: {
|
||||
set: function(value) {
|
||||
textContent.set.call(this, value);
|
||||
if (this.lightStyle) { this.appendChild(this.lightStyle); }
|
||||
},
|
||||
|
||||
get: function() {
|
||||
return textContent.get();
|
||||
}
|
||||
},
|
||||
|
||||
innerHTML: {
|
||||
set: function(value) {
|
||||
innerHTML.set.call(this, value);
|
||||
if (this.lightStyle) { this.appendChild(this.lightStyle); }
|
||||
},
|
||||
|
||||
get: innerHTML.get
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The default base prototype to use
|
||||
* when `extends` is undefined.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
var defaultPrototype = createProto(HTMLElement.prototype, base.properties);
|
||||
|
||||
/**
|
||||
* Returns a suitable prototype based
|
||||
* on the object passed.
|
||||
*
|
||||
* @private
|
||||
* @param {HTMLElementPrototype|undefined} proto
|
||||
* @return {HTMLElementPrototype}
|
||||
*/
|
||||
function getBaseProto(proto) {
|
||||
if (!proto) { return defaultPrototype; }
|
||||
proto = proto.prototype || proto;
|
||||
return !proto.GaiaComponent ?
|
||||
createProto(proto, base.properties) : proto;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extends the given proto and mixes
|
||||
* in the given properties.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} proto
|
||||
* @param {Object} props
|
||||
* @return {Object}
|
||||
*/
|
||||
function createProto(proto, props) {
|
||||
return mixin(Object.create(proto), props);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects presence of shadow-dom
|
||||
* CSS selectors.
|
||||
*
|
||||
* @private
|
||||
* @return {Boolean}
|
||||
*/
|
||||
var hasShadowCSS = (function() {
|
||||
var div = document.createElement('div');
|
||||
try { div.querySelector(':host'); div.querySelector('::content'); return true; }
|
||||
catch (e) { return false; }
|
||||
})();
|
||||
|
||||
/**
|
||||
* Regexs used to extract shadow-css
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
var regex = {
|
||||
shadowCss: /(?:\:host|\:\:content)[^{]*\{[^}]*\}/g,
|
||||
':host': /(?:\:host)/g,
|
||||
':host()': /\:host\((.+)\)(?: \:\:content)?/g,
|
||||
':host-context': /\:host-context\((.+)\)([^{,]+)?/g,
|
||||
'::content': /(?:\:\:content)/g
|
||||
};
|
||||
|
||||
/**
|
||||
* Extracts the :host and ::content rules
|
||||
* from the shadow-dom CSS and rewrites
|
||||
* them to work from the <style scoped>
|
||||
* injected at the root of the component.
|
||||
*
|
||||
* @private
|
||||
* @return {String}
|
||||
*/
|
||||
function processCss(template, name) {
|
||||
var globalCss = '';
|
||||
var lightCss = '';
|
||||
|
||||
if (!hasShadowCSS) {
|
||||
template = template.replace(regex.shadowCss, function(match) {
|
||||
var hostContext = regex[':host-context'].exec(match);
|
||||
|
||||
if (hostContext) {
|
||||
globalCss += match
|
||||
.replace(regex['::content'], '')
|
||||
.replace(regex[':host-context'], '$1 ' + name + '$2')
|
||||
.replace(/ +/g, ' '); // excess whitespace
|
||||
} else {
|
||||
lightCss += match
|
||||
.replace(regex[':host()'], name + '$1')
|
||||
.replace(regex[':host'], name)
|
||||
.replace(regex['::content'], name);
|
||||
}
|
||||
|
||||
return '';
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
template: template,
|
||||
lightCss: lightCss,
|
||||
globalCss: globalCss
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Some CSS rules, such as @keyframes
|
||||
* and @font-face don't work inside
|
||||
* scoped or shadow <style>. So we
|
||||
* have to put them into 'global'
|
||||
* <style> in the head of the
|
||||
* document.
|
||||
*
|
||||
* @private
|
||||
* @param {String} css
|
||||
*/
|
||||
function injectGlobalCss(css) {
|
||||
if (!css) {return;}
|
||||
var style = document.createElement('style');
|
||||
style.innerHTML = css.trim();
|
||||
headReady().then(function() {
|
||||
document.head.appendChild(style);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Resolves a promise once document.head is ready.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function headReady() {
|
||||
return new Promise(function(resolve) {
|
||||
if (document.head) { return resolve(); }
|
||||
window.addEventListener('load', function fn() {
|
||||
window.removeEventListener('load', fn);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The Gecko platform doesn't yet have
|
||||
* `::content` or `:host`, selectors,
|
||||
* without these we are unable to style
|
||||
* user-content in the light-dom from
|
||||
* within our shadow-dom style-sheet.
|
||||
*
|
||||
* To workaround this, we clone the <style>
|
||||
* node into the root of the component,
|
||||
* so our selectors are able to target
|
||||
* light-dom content.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function injectLightCss(el) {
|
||||
if (hasShadowCSS) { return; }
|
||||
el.lightStyle = document.createElement('style');
|
||||
el.lightStyle.setAttribute('scoped', '');
|
||||
el.lightStyle.innerHTML = el.lightCss;
|
||||
el.appendChild(el.lightStyle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert hyphen separated
|
||||
* string to camel-case.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* toCamelCase('foo-bar'); //=> 'fooBar'
|
||||
*
|
||||
* @private
|
||||
* @param {Sring} string
|
||||
* @return {String}
|
||||
*/
|
||||
function toCamelCase(string) {
|
||||
return string.replace(/-(.)/g, function replacer(string, p1) {
|
||||
return p1.toUpperCase();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Observer (singleton)
|
||||
*
|
||||
* @type {MutationObserver|undefined}
|
||||
*/
|
||||
var dirObserver;
|
||||
|
||||
/**
|
||||
* Observes the document `dir` (direction)
|
||||
* attribute and dispatches a global event
|
||||
* when it changes.
|
||||
*
|
||||
* Components can listen to this event and
|
||||
* make internal changes if need be.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function addDirObserver() {
|
||||
if (dirObserver) { return; }
|
||||
|
||||
dirObserver = new MutationObserver(onChanged);
|
||||
dirObserver.observe(document.documentElement, {
|
||||
attributeFilter: ['dir'],
|
||||
attributes: true
|
||||
});
|
||||
|
||||
function onChanged(mutations) {
|
||||
document.dispatchEvent(new Event('dirchanged'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy the values of all properties from
|
||||
* source object `target` to a target object `source`.
|
||||
* It will return the target object.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} target
|
||||
* @param {Object} source
|
||||
* @returns {Object}
|
||||
*/
|
||||
function mixin(target, source) {
|
||||
for (var key in source) {
|
||||
target[key] = source[key];
|
||||
}
|
||||
return target;
|
||||
}
|
||||
|
||||
});})(typeof define=='function'&&define.amd?define
|
||||
:(function(n,w){'use strict';return typeof module=='object'?function(c){
|
||||
c(require,exports,module);}:function(c){var m={exports:{}};c(function(n){
|
||||
return w[n];},m.exports,m);w[n]=m.exports;};})('gaia-component',this));
|
||||
31
gaia/apps/music/components/dom-scheduler/bower_components/gaia-dialog/bower.json
vendored
Executable file
|
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"name": "gaia-dialog",
|
||||
"version": "0.2.3",
|
||||
"homepage": "https://github.com/gaia-components/gaia-dialog",
|
||||
"authors": [
|
||||
"Wilson Page <wilsonpage@me.com>"
|
||||
],
|
||||
"main": "gaia-dialog.js",
|
||||
"license": "MIT",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"examples",
|
||||
"test",
|
||||
"tests",
|
||||
"README.md"
|
||||
],
|
||||
"devDependencies": {
|
||||
"base": "gaia-components/base",
|
||||
"pressed": "gaia-components/pressed",
|
||||
"gaia-fonts": "gaia-components/gaia-fonts",
|
||||
"gaia-theme": "gaia-components/gaia-theme",
|
||||
"gaia-button": "gaia-components/gaia-button"
|
||||
},
|
||||
"dependencies": {
|
||||
"gaia-text-input": "gaia-components/gaia-text-input",
|
||||
"gaia-icons": "gaia-components/gaia-icons#^0.8.0",
|
||||
"gaia-component": "gaia-components/gaia-component#^0.3.7"
|
||||
}
|
||||
}
|
||||
111
gaia/apps/music/components/dom-scheduler/bower_components/gaia-dialog/gaia-dialog-action.js
vendored
Executable file
|
|
@ -0,0 +1,111 @@
|
|||
/* global define */
|
||||
;(function(define){'use strict';define(function(require,exports,module){
|
||||
/**
|
||||
* Dependencies
|
||||
*/
|
||||
|
||||
var GaiaDialogProto = require('gaia-dialog').prototype;
|
||||
var component = require('gaia-component');
|
||||
|
||||
/**
|
||||
* Exports
|
||||
*/
|
||||
module.exports = component.register('gaia-dialog-action', {
|
||||
created: function() {
|
||||
this.setupShadowRoot();
|
||||
|
||||
this.els = {
|
||||
dialog: this.shadowRoot.querySelector('gaia-dialog'),
|
||||
submit: this.shadowRoot.querySelector('.submit'),
|
||||
cancel: this.shadowRoot.querySelector('.cancel')
|
||||
};
|
||||
},
|
||||
|
||||
open: function(e) {
|
||||
return GaiaDialogProto.show.call(this)
|
||||
.then(() => this.els.dialog.open(e));
|
||||
},
|
||||
|
||||
close: function() {
|
||||
return GaiaDialogProto.show.call(this)
|
||||
.then(() => this.els.dialog.close());
|
||||
},
|
||||
|
||||
template: `
|
||||
<gaia-dialog>
|
||||
<section>
|
||||
<content select="h1"></content>
|
||||
</section>
|
||||
<content select="button"></content>
|
||||
<button on-click="close" class="cancel">Cancel</button>
|
||||
</gaia-dialog>
|
||||
|
||||
<style>
|
||||
|
||||
:host {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:host[opened],
|
||||
:host.animating {
|
||||
display: block;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/** Button
|
||||
---------------------------------------------------------*/
|
||||
|
||||
::content section {
|
||||
padding: 33px 16px;
|
||||
}
|
||||
|
||||
::content button {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
padding: 0rem 25px;
|
||||
font: inherit;
|
||||
background: var(--color-beta);
|
||||
color: var(--color-epsilon);
|
||||
transition: all 200ms;
|
||||
transition-delay: 300ms;
|
||||
}
|
||||
|
||||
::content button:after {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
height: 1px;
|
||||
left: 6px;
|
||||
right: 6px;
|
||||
top: 49px;
|
||||
background: #E7E7E7;
|
||||
}
|
||||
|
||||
::content button:last-child:after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
::content button:active {
|
||||
background-color: var(--highlight-color);
|
||||
color: #fff;
|
||||
transition: none;
|
||||
}
|
||||
|
||||
::content button:active:after {
|
||||
background: var(--highlight-color);
|
||||
transition: none;
|
||||
}
|
||||
|
||||
</style>`
|
||||
});
|
||||
|
||||
});})(typeof define=='function'&&define.amd?define
|
||||
:(function(n,w){'use strict';return typeof module=='object'?function(c){
|
||||
c(require,exports,module);}:function(c){var m={exports:{}};c(function(n){
|
||||
return w[n];},m.exports,m);w[n]=m.exports;};})('gaia-dialog-action',this));
|
||||
59
gaia/apps/music/components/dom-scheduler/bower_components/gaia-dialog/gaia-dialog-alert.js
vendored
Executable file
|
|
@ -0,0 +1,59 @@
|
|||
/* global define */
|
||||
;(function(define){'use strict';define(function(require,exports,module){
|
||||
|
||||
/**
|
||||
* Dependencies
|
||||
*/
|
||||
|
||||
var GaiaDialogProto = require('gaia-dialog').prototype;
|
||||
var component = require('gaia-component');
|
||||
|
||||
/**
|
||||
* Exports
|
||||
*/
|
||||
module.exports = component.register('gaia-dialog-alert', {
|
||||
created: function() {
|
||||
this.setupShadowRoot();
|
||||
this.els = {
|
||||
dialog: this.shadowRoot.querySelector('gaia-dialog')
|
||||
};
|
||||
this.els.dialog.addEventListener('closed',
|
||||
GaiaDialogProto.hide.bind(this));
|
||||
},
|
||||
|
||||
open: function(e) {
|
||||
return GaiaDialogProto.show.call(this)
|
||||
.then(() => this.els.dialog.open(e));
|
||||
},
|
||||
|
||||
close: function() {
|
||||
return this.els.dialog.close()
|
||||
.then(GaiaDialogProto.hide.bind(this));
|
||||
},
|
||||
|
||||
template: `
|
||||
<gaia-dialog>
|
||||
<section>
|
||||
<p><content></content></p>
|
||||
</section>
|
||||
<div>
|
||||
<button class="submit primary" on-click="close">Ok</button>
|
||||
</div>
|
||||
</gaia-dialog>
|
||||
|
||||
<style>
|
||||
|
||||
:host {
|
||||
display: none;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
</style>`
|
||||
});
|
||||
|
||||
});})(typeof define=='function'&&define.amd?define
|
||||
:(function(n,w){'use strict';return typeof module=='object'?function(c){
|
||||
c(require,exports,module);}:function(c){var m={exports:{}};c(function(n){
|
||||
return w[n];},m.exports,m);w[n]=m.exports;};})('gaia-dialog-alert',this));
|
||||
69
gaia/apps/music/components/dom-scheduler/bower_components/gaia-dialog/gaia-dialog-confirm.js
vendored
Executable file
|
|
@ -0,0 +1,69 @@
|
|||
/* global define */
|
||||
;(function(define){'use strict';define(function(require,exports,module){
|
||||
|
||||
/**
|
||||
* Dependencies
|
||||
*/
|
||||
|
||||
var GaiaDialogProto = require('gaia-dialog').prototype;
|
||||
var component = require('gaia-component');
|
||||
|
||||
/**
|
||||
* Exports
|
||||
*/
|
||||
module.exports = component.register('gaia-dialog-confirm', {
|
||||
created: function() {
|
||||
this.setupShadowRoot();
|
||||
|
||||
this.els = {
|
||||
dialog: this.shadowRoot.querySelector('gaia-dialog'),
|
||||
submit: this.shadowRoot.querySelector('.submit'),
|
||||
cancel: this.shadowRoot.querySelector('.cancel')
|
||||
};
|
||||
|
||||
this.els.cancel.addEventListener('click', this.close.bind(this));
|
||||
this.els.submit.addEventListener('click', this.close.bind(this));
|
||||
},
|
||||
|
||||
open: function(e) {
|
||||
return GaiaDialogProto.show.call(this)
|
||||
.then(() => this.els.dialog.open(e));
|
||||
},
|
||||
|
||||
close: function() {
|
||||
return GaiaDialogProto.show.call(this)
|
||||
.then(() => this.els.dialog.close());
|
||||
},
|
||||
|
||||
template: `
|
||||
<gaia-dialog>
|
||||
<section>
|
||||
<p><content></content></p>
|
||||
</section>
|
||||
<fieldset>
|
||||
<button class="cancel">Cancel</button>
|
||||
<button class="submit danger">Confirm</button>
|
||||
</fieldset>
|
||||
</gaia-dialog>
|
||||
|
||||
<style>
|
||||
|
||||
:host {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:host[opened],
|
||||
:host.animating {
|
||||
display: block;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
</style>`
|
||||
});
|
||||
|
||||
});})(typeof define=='function'&&define.amd?define
|
||||
:(function(n,w){'use strict';return typeof module=='object'?function(c){
|
||||
c(require,exports,module);}:function(c){var m={exports:{}};c(function(n){
|
||||
return w[n];},m.exports,m);w[n]=m.exports;};})('gaia-dialog-confirm',this));
|
||||
92
gaia/apps/music/components/dom-scheduler/bower_components/gaia-dialog/gaia-dialog-menu.js
vendored
Executable file
|
|
@ -0,0 +1,92 @@
|
|||
/* global define */
|
||||
;(function(define){'use strict';define(function(require,exports,module){
|
||||
|
||||
/**
|
||||
* Dependencies
|
||||
*/
|
||||
|
||||
var GaiaDialogProto = require('gaia-dialog').prototype;
|
||||
var component = require('gaia-component');
|
||||
|
||||
/**
|
||||
* Exports
|
||||
*/
|
||||
module.exports = component.register('gaia-dialog-menu', {
|
||||
created: function() {
|
||||
this.setupShadowRoot();
|
||||
this.els = {
|
||||
dialog: this.shadowRoot.querySelector('gaia-dialog')
|
||||
};
|
||||
},
|
||||
|
||||
open: function(e) {
|
||||
return GaiaDialogProto.show.call(this)
|
||||
.then(() => this.els.dialog.open(e));
|
||||
},
|
||||
|
||||
close: function() {
|
||||
return GaiaDialogProto.show.call(this)
|
||||
.then(() => this.els.dialog.close());
|
||||
},
|
||||
|
||||
template: `
|
||||
<gaia-dialog>
|
||||
<div class="items"><content select="button"></content></div>
|
||||
</gaia-dialog>
|
||||
|
||||
<style>
|
||||
|
||||
:host {
|
||||
display: none;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
::content > button {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
line-height: 51px;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
padding: 0rem 16px;
|
||||
font: inherit;
|
||||
font-style: italic;
|
||||
text-align: start;
|
||||
background: var(--color-beta);
|
||||
color: var(--highlight-color);
|
||||
}
|
||||
|
||||
::content > button[data-icon]:before {
|
||||
width: 50px;
|
||||
font-size: 22px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/** Button Divider Line
|
||||
---------------------------------------------------------*/
|
||||
|
||||
::content > button:after {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
height: 1px;
|
||||
left: 6px;
|
||||
right: 6px;
|
||||
top: 49px;
|
||||
background: #E7E7E7;
|
||||
}
|
||||
|
||||
::content > button:last-of-type:after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
</style>`
|
||||
});
|
||||
|
||||
});})(typeof define=='function'&&define.amd?define
|
||||
:(function(n,w){'use strict';return typeof module=='object'?function(c){
|
||||
c(require,exports,module);}:function(c){var m={exports:{}};c(function(n){
|
||||
return w[n];},m.exports,m);w[n]=m.exports;};})('gaia-dialog-menu',this));
|
||||
83
gaia/apps/music/components/dom-scheduler/bower_components/gaia-dialog/gaia-dialog-prompt.js
vendored
Executable file
|
|
@ -0,0 +1,83 @@
|
|||
/* global define */
|
||||
;(function(define){'use strict';define(function(require,exports,module){
|
||||
|
||||
/**
|
||||
* Dependencies
|
||||
*/
|
||||
|
||||
var GaiaDialogProto = require('gaia-dialog').prototype;
|
||||
var component = require('gaia-component');
|
||||
|
||||
require('gaia-text-input');
|
||||
|
||||
/**
|
||||
* Exports
|
||||
*/
|
||||
module.exports = component.register('gaia-dialog-prompt', {
|
||||
created: function() {
|
||||
this.setupShadowRoot();
|
||||
|
||||
this.els = {
|
||||
dialog: this.shadowRoot.querySelector('gaia-dialog'),
|
||||
input: this.shadowRoot.querySelector('gaia-text-input'),
|
||||
submit: this.shadowRoot.querySelector('.submit'),
|
||||
cancel: this.shadowRoot.querySelector('.cancel')
|
||||
};
|
||||
|
||||
this.els.dialog.addEventListener('opened', () => {
|
||||
this.setAttribute('opened', '');
|
||||
});
|
||||
|
||||
this.els.dialog.addEventListener('closed', () => {
|
||||
this.removeAttribute('opened');
|
||||
});
|
||||
|
||||
this.els.input.placeholder = this.firstChild.textContent;
|
||||
this.els.cancel.addEventListener('click', this.close.bind(this));
|
||||
this.els.submit.addEventListener('click', this.close.bind(this));
|
||||
},
|
||||
|
||||
open: function(e) {
|
||||
return GaiaDialogProto.show.call(this)
|
||||
.then(() => this.els.dialog.open(e));
|
||||
},
|
||||
|
||||
close: function() {
|
||||
return GaiaDialogProto.show.call(this)
|
||||
.then(() => this.els.dialog.close());
|
||||
},
|
||||
|
||||
template: `
|
||||
<gaia-dialog>
|
||||
<div><gaia-text-input></gaia-text-input></div>
|
||||
<fieldset>
|
||||
<button class="cancel">Cancel</button>
|
||||
<button class="submit primary">Ok</button>
|
||||
</fieldset>
|
||||
</gaia-dialog>
|
||||
|
||||
<style>
|
||||
|
||||
:host {
|
||||
display: none;
|
||||
}
|
||||
|
||||
:host[opened],
|
||||
:host.animating {
|
||||
display: block;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
gaia-text-input {
|
||||
margin: 16px !important;
|
||||
}
|
||||
|
||||
</style>`
|
||||
});
|
||||
|
||||
});})(typeof define=='function'&&define.amd?define
|
||||
:(function(n,w){'use strict';return typeof module=='object'?function(c){
|
||||
c(require,exports,module);}:function(c){var m={exports:{}};c(function(n){
|
||||
return w[n];},m.exports,m);w[n]=m.exports;};})('gaia-dialog-prompt',this));
|
||||
230
gaia/apps/music/components/dom-scheduler/bower_components/gaia-dialog/gaia-dialog-select.js
vendored
Executable file
|
|
@ -0,0 +1,230 @@
|
|||
/* global define */
|
||||
;(function(define){'use strict';define(function(require,exports,module){
|
||||
|
||||
/**
|
||||
* Dependencies
|
||||
*/
|
||||
|
||||
var GaiaDialogProto = require('gaia-dialog').prototype;
|
||||
var component = require('gaia-component');
|
||||
|
||||
/**
|
||||
* Exports
|
||||
*/
|
||||
module.exports = component.register('gaia-dialog-select', {
|
||||
created: function() {
|
||||
this.setupShadowRoot();
|
||||
|
||||
this.els = {
|
||||
dialog: this.shadowRoot.querySelector('gaia-dialog'),
|
||||
submit: this.shadowRoot.querySelector('.submit'),
|
||||
cancel: this.shadowRoot.querySelector('.cancel'),
|
||||
list: this.shadowRoot.querySelector('ul')
|
||||
};
|
||||
|
||||
this.multiple = this.hasAttribute('multiple');
|
||||
this.els.list.addEventListener('click', this.onListClick.bind(this));
|
||||
this.els.submit.addEventListener('click', this.close.bind(this));
|
||||
this.els.cancel.addEventListener('click', this.close.bind(this));
|
||||
},
|
||||
|
||||
open: function(e) {
|
||||
return GaiaDialogProto.show.call(this)
|
||||
.then(() => this.els.dialog.open(e));
|
||||
},
|
||||
|
||||
close: function() {
|
||||
return GaiaDialogProto.show.call(this)
|
||||
.then(() => this.els.dialog.close());
|
||||
},
|
||||
|
||||
onListClick: function(e) {
|
||||
var el = e.target.closest('li');
|
||||
var selected = el.getAttribute('aria-selected') === 'true';
|
||||
|
||||
if (this.multiple) { this.onChangeMultiple(el, selected); }
|
||||
else { this.onChange(el, selected); }
|
||||
},
|
||||
|
||||
onChange: function(el, selected) {
|
||||
this.clearSelected();
|
||||
if (!selected) { el.setAttribute('aria-selected', !selected); }
|
||||
this.fireChange();
|
||||
this.close();
|
||||
},
|
||||
|
||||
onChangeMultiple: function(el, selected) {
|
||||
el.setAttribute('aria-selected', !selected);
|
||||
this.fireChange();
|
||||
},
|
||||
|
||||
clearSelected: function() {
|
||||
[].forEach.call(this.selectedOptions, function(option) {
|
||||
option.removeAttribute('aria-selected');
|
||||
});
|
||||
},
|
||||
|
||||
fireChange: function() {
|
||||
var e = new CustomEvent('change', { detail: { value: this.valueString }});
|
||||
this.dispatchEvent(e);
|
||||
},
|
||||
|
||||
attrs: {
|
||||
multiple: {
|
||||
get: function() { return !!this._multiple; },
|
||||
set: function(value) {
|
||||
value = value === '' ? true : value;
|
||||
if (value === this._multiple) { return; }
|
||||
if (!value) {
|
||||
this._multiple = false;
|
||||
this.removeAttribute('multiple');
|
||||
} else {
|
||||
this._multiple = true;
|
||||
this.setAttribute('multiple', '');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
options: {
|
||||
get: function() { return this.querySelectorAll('li'); }
|
||||
},
|
||||
|
||||
selectedOptions: {
|
||||
get: function() {
|
||||
return this.querySelectorAll('li[aria-selected="true"]');
|
||||
}
|
||||
},
|
||||
|
||||
selected: {
|
||||
get: function() { return this.selectedOptions[0]; }
|
||||
},
|
||||
|
||||
value: {
|
||||
get: function() {
|
||||
var selected = this.selectedOptions[0];
|
||||
return selected && selected.getAttribute('value');
|
||||
}
|
||||
},
|
||||
|
||||
valueString: {
|
||||
get: function() {
|
||||
var selected = this.selected;
|
||||
return selected && selected.textContent;
|
||||
}
|
||||
},
|
||||
|
||||
length: {
|
||||
get: function() { return this.options.length; }
|
||||
}
|
||||
},
|
||||
|
||||
template: `
|
||||
<gaia-dialog>
|
||||
<content select="h1"></content>
|
||||
<ul><content select="li"></content></ul>
|
||||
<fieldset>
|
||||
<button class="cancel">Cancel</button>
|
||||
<button class="submit primary">Select</button>
|
||||
</fieldset>
|
||||
</gaia-dialog>
|
||||
|
||||
<style>
|
||||
|
||||
:host {
|
||||
display: none;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/** Title (duplicate from gaia-dialog)
|
||||
---------------------------------------------------------*/
|
||||
|
||||
::content h1 {
|
||||
padding: 16px;
|
||||
font-size: 23px;
|
||||
line-height: 26px;
|
||||
font-weight: 200;
|
||||
font-style: italic;
|
||||
color: #858585;
|
||||
}
|
||||
|
||||
/** List
|
||||
---------------------------------------------------------*/
|
||||
|
||||
ul {
|
||||
display: block;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/** Options
|
||||
---------------------------------------------------------*/
|
||||
|
||||
::content li {
|
||||
position: relative;
|
||||
display: block;
|
||||
padding: 16px;
|
||||
text-align: start;
|
||||
-moz-user-select: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
::content li[aria-selected='true'] {
|
||||
color: #00CAF2;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
::content li:after {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
height: 1px;
|
||||
left: 16px;
|
||||
right: 16px;
|
||||
bottom: 1px;
|
||||
background: #E7E7E7;
|
||||
}
|
||||
|
||||
::content li:last-of-type:after {
|
||||
display: none
|
||||
}
|
||||
|
||||
/** Buttons
|
||||
---------------------------------------------------------*/
|
||||
|
||||
::host:not([multiple]) .submit {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
::host:not([multiple]) .cancel {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
::host:not([multiple]) .cancel:after {
|
||||
display: none% !important;
|
||||
}
|
||||
|
||||
/** Tick Icon
|
||||
---------------------------------------------------------*/
|
||||
|
||||
::content li[aria-selected='true']:before {
|
||||
content: 'tick';
|
||||
position: absolute;
|
||||
top: 50%; right: 20px;
|
||||
display: block;
|
||||
margin-top: -12px;
|
||||
font-family: 'gaia-icons';
|
||||
font-style: normal;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
</style>`
|
||||
});
|
||||
|
||||
});})(typeof define=='function'&&define.amd?define
|
||||
:(function(n,w){'use strict';return typeof module=='object'?function(c){
|
||||
c(require,exports,module);}:function(c){var m={exports:{}};c(function(n){
|
||||
return w[n];},m.exports,m);w[n]=m.exports;};})('gaia-dialog-select',this));
|
||||
475
gaia/apps/music/components/dom-scheduler/bower_components/gaia-dialog/gaia-dialog.js
vendored
Executable file
|
|
@ -0,0 +1,475 @@
|
|||
;(function(define){'use strict';define((require,exports,module) => {
|
||||
|
||||
/**
|
||||
* Dependencies
|
||||
*/
|
||||
var component = require('gaia-component');
|
||||
|
||||
/**
|
||||
* Simple logger (toggle 0)
|
||||
*
|
||||
* @type {Function}
|
||||
*/
|
||||
var debug = 0 ? console.log.bind(console) : () => {};
|
||||
|
||||
/**
|
||||
* Use the dom-scheduler if it's around,
|
||||
* else fallback to fake shim.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
var schedule = (window.scheduler && typeof window.scheduler.mutation === "function" ? window.scheduler : null) || {
|
||||
mutation: block => Promise.resolve(block()),
|
||||
transition: (block, el, event, timeout) => {
|
||||
block();
|
||||
return after(el, event, timeout || 500);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Exports
|
||||
*/
|
||||
|
||||
module.exports = component.register('gaia-dialog', {
|
||||
created() {
|
||||
this.setupShadowRoot();
|
||||
|
||||
this.els = {
|
||||
inner: this.shadowRoot.querySelector('.dialog-inner'),
|
||||
background: this.shadowRoot.querySelector('.background'),
|
||||
window: this.shadowRoot.querySelector('.window')
|
||||
};
|
||||
|
||||
this.shadowRoot.addEventListener('click', e => this.onClick(e));
|
||||
},
|
||||
|
||||
onClick(e) {
|
||||
var el = e.target.closest('[on-click]');
|
||||
if (!el) { return; }
|
||||
debug('onClick');
|
||||
var method = el.getAttribute('on-click');
|
||||
if (typeof this[method] == 'function') { this[method](); }
|
||||
},
|
||||
|
||||
open(options) {
|
||||
if (this.isOpen) { return; }
|
||||
debug('open dialog');
|
||||
this.isOpen = true;
|
||||
|
||||
this.show()
|
||||
.then(() => this.animateBackgroundIn(options))
|
||||
.then(() => this.animateWindowIn())
|
||||
.then(() => this.dispatch('opened'));
|
||||
},
|
||||
|
||||
close(options) {
|
||||
if (!this.isOpen) { return; }
|
||||
debug('close dialog');
|
||||
this.isOpen = false;
|
||||
|
||||
this.animateWindowOut()
|
||||
.then(() => this.animateBackgroundOut())
|
||||
.then(() => this.hide())
|
||||
.then(() => this.dispatch('closed'));
|
||||
},
|
||||
|
||||
animateBackgroundIn(options) {
|
||||
if (options) { return this.animateBackgroundInFrom(options); }
|
||||
|
||||
var el = this.els.background;
|
||||
return schedule.transition(() => {
|
||||
debug('animate background in');
|
||||
el.classList.remove('animate-out');
|
||||
el.classList.add('animate-in');
|
||||
}, el, 'animationend');
|
||||
},
|
||||
|
||||
animateBackgroundOut() {
|
||||
var el = this.els.background;
|
||||
return schedule.transition(() => {
|
||||
debug('animate background out');
|
||||
el.classList.add('animate-out');
|
||||
el.classList.remove('animate-in');
|
||||
}, el, 'animationend')
|
||||
.then(() => el.style = '');
|
||||
},
|
||||
|
||||
animateBackgroundInFrom(pos) {
|
||||
var el = this.els.background;
|
||||
var scale = Math.sqrt(window.innerWidth * window.innerHeight) / 15;
|
||||
var duration = scale * 9;
|
||||
|
||||
return schedule.mutation(() => {
|
||||
el.classList.add('circular');
|
||||
el.classList.remove('animate-out');
|
||||
el.style.transform = `translate(${pos.clientX}px, ${pos.clientY}px)`;
|
||||
el.style.transitionDuration = duration + 'ms';
|
||||
el.offsetTop; // Hack, any ideas?
|
||||
})
|
||||
|
||||
.then(() => {
|
||||
return schedule.transition(() => {
|
||||
debug('animate background in from', pos);
|
||||
el.style.transform += ` scale(${scale})`;
|
||||
el.style.opacity = 1;
|
||||
}, el, 'transitionend', duration * 1.5);
|
||||
});
|
||||
},
|
||||
|
||||
show() {
|
||||
return schedule.mutation(() => {
|
||||
debug('show');
|
||||
this.style.display = 'block';
|
||||
});
|
||||
},
|
||||
|
||||
hide() {
|
||||
return schedule.mutation(() => {
|
||||
debug('hide');
|
||||
this.style.display = 'none';
|
||||
});
|
||||
},
|
||||
|
||||
animateWindowIn() {
|
||||
var el = this.els.window;
|
||||
return schedule.transition(() => {
|
||||
debug('animate window in');
|
||||
el.classList.add('animate-in');
|
||||
el.classList.remove('animate-out');
|
||||
}, el, 'animationend');
|
||||
},
|
||||
|
||||
animateWindowOut() {
|
||||
var el = this.els.window;
|
||||
return schedule.transition(() => {
|
||||
debug('animate window out');
|
||||
el.classList.add('animate-out');
|
||||
el.classList.remove('animate-in');
|
||||
}, el, 'animationend');
|
||||
},
|
||||
|
||||
dispatch(name) {
|
||||
this.dispatchEvent(new CustomEvent(name));
|
||||
},
|
||||
|
||||
attrs: {
|
||||
opened: {
|
||||
get: function() { return !!this.isOpen; },
|
||||
set: function(value) {
|
||||
value = value === '' || value;
|
||||
if (!value) { this.close(); }
|
||||
else { this.open(); }
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
template: `
|
||||
<div class="dialog-inner">
|
||||
<div class="background" on-click="close"></div>
|
||||
<div class="window"><content></content></div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
||||
::content * {
|
||||
box-sizing: border-box;
|
||||
font-weight: inherit;
|
||||
font-size: inherit;
|
||||
}
|
||||
|
||||
::content p,
|
||||
::content h1,
|
||||
::content h2,
|
||||
::content h3,
|
||||
::content h4,
|
||||
::content button,
|
||||
::content fieldset {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
:host {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0px; left: 0px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 200;
|
||||
font-style: italic;
|
||||
text-align: center;
|
||||
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/** Inner
|
||||
---------------------------------------------------------*/
|
||||
|
||||
.dialog-inner {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/** Background
|
||||
---------------------------------------------------------*/
|
||||
|
||||
.background {
|
||||
position: absolute;
|
||||
top: 0; left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0;
|
||||
background: rgba(199,199,199,0.85);
|
||||
}
|
||||
|
||||
/**
|
||||
* .circular
|
||||
*/
|
||||
|
||||
.background.circular {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin: -20px;
|
||||
border-radius: 50%;
|
||||
will-change: transform, opacity;
|
||||
transition-property: opacity, transform;
|
||||
transition-timing-function: linear;
|
||||
}
|
||||
|
||||
/**
|
||||
* .animate-in
|
||||
*/
|
||||
|
||||
.background.animate-in {
|
||||
animation-name: gaia-dialog-fade-in;
|
||||
animation-duration: 260ms;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
/**
|
||||
* .animate-out
|
||||
*/
|
||||
|
||||
.background.animate-out {
|
||||
animation-name: gaia-dialog-fade-out;
|
||||
animation-duration: 260ms;
|
||||
animation-fill-mode: forwards;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/** Window
|
||||
---------------------------------------------------------*/
|
||||
|
||||
.window {
|
||||
position: relative;
|
||||
width: 90%;
|
||||
max-width: 350px;
|
||||
margin: auto;
|
||||
box-shadow: 0 1px 0 0px rgba(0,0,0,0.15);
|
||||
background: var(--color-iota);
|
||||
transition: opacity 300ms;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.window.animate-in {
|
||||
animation-name: gaia-dialog-entrance;
|
||||
animation-duration: 300ms;
|
||||
animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1.275);
|
||||
animation-fill-mode: forwards;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.window.animate-out {
|
||||
animation-name: gaia-dialog-fade-out;
|
||||
animation-duration: 150ms;
|
||||
animation-timing-function: linear;
|
||||
animation-fill-mode: forwards;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/** Title
|
||||
---------------------------------------------------------*/
|
||||
|
||||
::content h1 {
|
||||
padding: 16px;
|
||||
font-size: 23px;
|
||||
line-height: 26px;
|
||||
font-weight: 200;
|
||||
font-style: italic;
|
||||
color: #858585;
|
||||
}
|
||||
|
||||
::content strong {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
::content small {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
/** Section
|
||||
---------------------------------------------------------*/
|
||||
|
||||
::content section {
|
||||
padding: 33px 18px;
|
||||
color: #858585;
|
||||
}
|
||||
|
||||
::content section > *:not(:last-child) {
|
||||
margin-bottom: 13px;
|
||||
}
|
||||
|
||||
/** Paragraphs
|
||||
---------------------------------------------------------*/
|
||||
|
||||
::content p {
|
||||
text-align: -moz-start;
|
||||
}
|
||||
|
||||
/** Buttons
|
||||
---------------------------------------------------------*/
|
||||
|
||||
::content button {
|
||||
position: relative;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
padding: 0rem 16px;
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
background: var(--color-beta);
|
||||
color: var(--color-epsilon);
|
||||
transition: all 200ms;
|
||||
transition-delay: 300ms;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* .primary
|
||||
*/
|
||||
|
||||
::content button.primary {
|
||||
color: var(--highlight-color);
|
||||
}
|
||||
|
||||
/**
|
||||
* .danger
|
||||
*/
|
||||
|
||||
::content button.danger {
|
||||
color: var(--color-destructive);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disabled buttons
|
||||
*/
|
||||
|
||||
::content button[disabled] {
|
||||
color: var(--color-zeta);
|
||||
}
|
||||
|
||||
/** Button Divider Line
|
||||
---------------------------------------------------------*/
|
||||
|
||||
::content button:after {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
height: 1px;
|
||||
left: 6px;
|
||||
right: 6px;
|
||||
top: 49px;
|
||||
background: #E7E7E7;
|
||||
}
|
||||
|
||||
::content button:last-of-type:after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
::content button:active {
|
||||
background-color: var(--highlight-color);
|
||||
color: #fff;
|
||||
transition: none;
|
||||
}
|
||||
|
||||
::content button:active:after {
|
||||
background: var(--highlight-color);
|
||||
transition: none;
|
||||
}
|
||||
|
||||
::content button[data-icon]:before {
|
||||
float: left;
|
||||
}
|
||||
|
||||
/** Fieldset (button group)
|
||||
---------------------------------------------------------*/
|
||||
|
||||
::content fieldset {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
::content fieldset button {
|
||||
position: relative;
|
||||
float: left;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
::content fieldset button:after {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
bottom: 6px;
|
||||
right: 0px;
|
||||
left: auto;
|
||||
width: 1px;
|
||||
height: calc(100% - 12px);
|
||||
background: #e7e7e7;
|
||||
transition: all 200ms;
|
||||
transition-delay: 200ms;
|
||||
}
|
||||
|
||||
</style>`,
|
||||
|
||||
globalCss: `
|
||||
@keyframes gaia-dialog-entrance {
|
||||
0% { transform: translateY(100px); }
|
||||
100% { transform: translateY(0px); }
|
||||
}
|
||||
|
||||
@keyframes gaia-dialog-fade-in {
|
||||
0% { opacity: 0 }
|
||||
100% { opacity: 1 }
|
||||
}
|
||||
|
||||
@keyframes gaia-dialog-fade-out {
|
||||
0% { opacity: 1 }
|
||||
100% { opacity: 0 }
|
||||
}`
|
||||
});
|
||||
|
||||
/**
|
||||
* Utils
|
||||
*/
|
||||
|
||||
function after(target, event, timeout) {
|
||||
return new Promise(resolve => {
|
||||
var timer = timeout && setTimeout(cb, timeout);
|
||||
target.addEventListener(event, cb);
|
||||
function cb() {
|
||||
target.removeEventListener(event, cb);
|
||||
clearTimeout(timer);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
});})(typeof define=='function'&&define.amd?define
|
||||
:(function(n,w){'use strict';return typeof module=='object'?function(c){
|
||||
c(require,exports,module);}:function(c){var m={exports:{}};c(function(n){
|
||||
return w[n];},m.exports,m);w[n]=m.exports;};})('gaia-dialog',this));
|
||||
124
gaia/apps/music/components/dom-scheduler/bower_components/gaia-dialog/index.html
vendored
Executable file
|
|
@ -0,0 +1,124 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title><gaia-dialog></title>
|
||||
<meta name="description" content="gaia-dialog demo" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
|
||||
<link rel="stylesheet" type="text/css" href="bower_components/base/base.css">
|
||||
<link rel="stylesheet" type="text/css" href="bower_components/gaia-theme/gaia-theme.css">
|
||||
<link rel="stylesheet" type="text/css" href="bower_components/gaia-fonts/style.css">
|
||||
<link rel="stylesheet" type="text/css" href="bower_components/gaia-icons/gaia-icons.css">
|
||||
<script src="bower_components/gaia-theme/lib/gaia-theme-selector.js"></script>
|
||||
<script src="bower_components/pressed/pressed.js"></script>
|
||||
<script src="bower_components/gaia-component/gaia-component.js"></script>
|
||||
<script src="bower_components/gaia-button/gaia-button.js"></script>
|
||||
<script src="bower_components/gaia-text-input/gaia-text-input.js"></script>
|
||||
<!-- <script src="bower_components/dom-scheduler/lib/dom-scheduler.js"></script> -->
|
||||
<script src="gaia-dialog.js"></script>
|
||||
<script src="gaia-dialog-alert.js"></script>
|
||||
<script src="gaia-dialog-confirm.js"></script>
|
||||
<script src="gaia-dialog-prompt.js"></script>
|
||||
<script src="gaia-dialog-action.js"></script>
|
||||
<script src="gaia-dialog-select.js"></script>
|
||||
<script src="gaia-dialog-menu.js"></script>
|
||||
<style>
|
||||
body {
|
||||
padding: 48px var(--base-m) 0 var(--base-m);
|
||||
background-color: var(--background);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="l-align-center">
|
||||
<gaia-theme-selector></gaia-theme-selector>
|
||||
|
||||
<div id="buttons">
|
||||
<div class="l-mb-small"><gaia-button data-for="gaia-dialog-alert">Alert</gaia-button></div>
|
||||
<div class="l-mb-small"><gaia-button data-for="gaia-dialog-confirm">Confirm</gaia-button></div>
|
||||
<div class="l-mb-small"><gaia-button data-for="gaia-dialog-prompt">Prompt</gaia-button></div>
|
||||
<div class="l-mb-small"><gaia-button data-for="gaia-dialog-action">Action</gaia-button></div>
|
||||
<div class="l-mb-small"><gaia-button data-for="gaia-dialog-select">Select</gaia-button></div>
|
||||
<div class="l-mb-small"><gaia-button data-for="gaia-dialog-select[multiple]">Select Multiple</gaia-button></div>
|
||||
<div class="l-mb-small"><gaia-button data-for="gaia-dialog-menu">Menu</gaia-button></div>
|
||||
<div class="l-mb-small"><gaia-button data-for="#custom-dialog-1">Custom</gaia-button></div>
|
||||
</div>
|
||||
|
||||
<div id="dialogs">
|
||||
<gaia-dialog-alert>No SIM card is present</gaia-dialog-alert>
|
||||
<gaia-dialog-confirm>Are you sure you want to delete this contact?</gaia-dialog-confirm>
|
||||
<gaia-dialog-prompt>Device name</gaia-dialog-prompt>
|
||||
|
||||
<gaia-dialog-action>
|
||||
<h1>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur eu mollis tortor.</h1>
|
||||
<button on-click="close">Action 1</button>
|
||||
<button on-click="close">Action 2</button>
|
||||
</gaia-dialog-action>
|
||||
|
||||
<gaia-dialog-select>
|
||||
<h1>Ring tone</h1>
|
||||
<li>Classic prism</li>
|
||||
<li>Wallphone</li>
|
||||
<li>Disco drive</li>
|
||||
<li>Touchmatic</li>
|
||||
<li>Classic courier</li>
|
||||
<li>Hacker</li>
|
||||
</gaia-dialog-select>
|
||||
|
||||
<gaia-dialog-select multiple>
|
||||
<h1>Ring tone</h1>
|
||||
<li>Classic prism</li>
|
||||
<li>Wallphone</li>
|
||||
<li>Disco drive</li>
|
||||
<li>Touchmatic</li>
|
||||
<li>Classic courier</li>
|
||||
<li>Hacker</li>
|
||||
</gaia-dialog-select>
|
||||
|
||||
<gaia-dialog-menu>
|
||||
<button on-click="close" data-icon="firefox">Open in new window</button>
|
||||
<button on-click="close" data-icon="firefox">Add to Home Screen</button>
|
||||
<button on-click="close" data-icon="firefox">Share link</button>
|
||||
<button on-click="close" data-icon="firefox">Settings</button>
|
||||
</gaia-dialog-menu>
|
||||
|
||||
<gaia-dialog id="custom-dialog-1">
|
||||
<section>
|
||||
<h1>Application Download</h1>
|
||||
<p>Do you want to download and install this application?</p>
|
||||
</section>
|
||||
<fieldset>
|
||||
<button on-click="close">Cancel</button>
|
||||
<button on-click="close">Action</button>
|
||||
</fieldset>
|
||||
</gaia-dialog>
|
||||
|
||||
<gaia-dialog>
|
||||
<h1>Main Heading</h1>
|
||||
<h2>Sub Heading</h2>
|
||||
<hr></hr>
|
||||
<p>Lorem ipsum dolor sit amet, consecutor adipisicing elit, sed do eiusod tempor incidenti ut la et dolore magna...</p>
|
||||
<button on-click="close">Action</button>
|
||||
<button on-click="close">Cancel</button>
|
||||
</gaia-dialog>
|
||||
|
||||
<gaia-dialog>
|
||||
<h1>Confirmation</h1>
|
||||
<p>Lorem ipsum dolor sit amet, consecutor adipisicing elit, sed do eiusod tempor incidenti ut la et dolore magna...</p>
|
||||
<hr>
|
||||
<p>Lorem ipsum dolor sit amet, consecutor adipisicing elit, sed do eiusod tempor incidenti ut la et dolore magna...</p>
|
||||
<button on-click="close">Action</button>
|
||||
<button on-click="close">Action</button>
|
||||
<button on-click="close">Cancel</button>
|
||||
</gaia-dialog>
|
||||
</div>
|
||||
<script>
|
||||
var buttons = document.querySelectorAll('gaia-button');
|
||||
[].forEach.call(buttons, function(button) {
|
||||
button.addEventListener('click', function(e) {
|
||||
var dialog = document.querySelector(button.dataset.for);
|
||||
dialog.open(e);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
27
gaia/apps/music/components/dom-scheduler/bower_components/gaia-dialog/package.json
vendored
Executable file
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"name": "gaia-dialog",
|
||||
"version": "0.2.3",
|
||||
"homepage": "https://github.com/gaia-components/gaia-dialog",
|
||||
"license": "MIT",
|
||||
"main": "gaia-dialog.js",
|
||||
"devDependencies": {
|
||||
"babel": "^5.2.12",
|
||||
"bower": "^1.4.1",
|
||||
"browserify": "^10.1.0",
|
||||
"jshint": "^2.7.0"
|
||||
},
|
||||
"scripts": {
|
||||
"install": "bower install",
|
||||
"lint": "jshint gaia-dialog*.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/gaia-components/gaia-dialog.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/gaia-components/gaia-dialog/issues"
|
||||
},
|
||||
"browser": {
|
||||
"gaia-component": "./bower_components/gaia-component/gaia-component.js"
|
||||
}
|
||||
}
|
||||
16
gaia/apps/music/components/dom-scheduler/bower_components/gaia-fonts/bower.json
vendored
Executable file
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"name": "gaia-fonts",
|
||||
"version": "0.2.1",
|
||||
"authors": [
|
||||
"Wilson Page <wilsonpage@me.com>"
|
||||
],
|
||||
"main": "style.css",
|
||||
"license": "MIT",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"test",
|
||||
"tests"
|
||||
]
|
||||
}
|
||||
BIN
gaia/apps/music/components/dom-scheduler/bower_components/gaia-fonts/fonts/FiraSans-Bold.woff
vendored
Executable file
BIN
gaia/apps/music/components/dom-scheduler/bower_components/gaia-fonts/fonts/FiraSans-BoldItalic.woff
vendored
Executable file
BIN
gaia/apps/music/components/dom-scheduler/bower_components/gaia-fonts/fonts/FiraSans-Light.woff
vendored
Executable file
BIN
gaia/apps/music/components/dom-scheduler/bower_components/gaia-fonts/fonts/FiraSans-LightItalic.woff
vendored
Executable file
BIN
gaia/apps/music/components/dom-scheduler/bower_components/gaia-fonts/fonts/FiraSans-Medium.woff
vendored
Executable file
BIN
gaia/apps/music/components/dom-scheduler/bower_components/gaia-fonts/fonts/FiraSans-MediumItalic.woff
vendored
Executable file
BIN
gaia/apps/music/components/dom-scheduler/bower_components/gaia-fonts/fonts/FiraSans-Regular.woff
vendored
Executable file
BIN
gaia/apps/music/components/dom-scheduler/bower_components/gaia-fonts/fonts/FiraSans-RegularItalic.woff
vendored
Executable file
54
gaia/apps/music/components/dom-scheduler/bower_components/gaia-fonts/style.css
vendored
Executable file
|
|
@ -0,0 +1,54 @@
|
|||
|
||||
@font-face {
|
||||
font-family: "FiraSans";
|
||||
src: url("fonts/FiraSans-Light.woff") format("woff");
|
||||
font-weight: 300; /* lighter */
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "FiraSans";
|
||||
src: url("fonts/FiraSans-LightItalic.woff") format("woff");
|
||||
font-style: italic;
|
||||
font-weight: 300; /* lighter */
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "FiraSans";
|
||||
src: url("fonts/FiraSans-Regular.woff") format("woff");
|
||||
font-style: normal;
|
||||
font-weight: normal; /* 400 */
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "FiraSans";
|
||||
src: url("fonts/FiraSans-RegularItalic.woff") format("woff");
|
||||
font-style: italic;
|
||||
font-weight: normal; /* 400 */
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "FiraSans";
|
||||
src: url("fonts/FiraSans-Medium.woff") format("woff");
|
||||
font-style: italic;
|
||||
font-weight: bold; /* 700 */
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "FiraSans";
|
||||
src: url("fonts/FiraSans-MediumItalic.woff") format("woff");
|
||||
font-style: italic;
|
||||
font-weight: bold; /* 700 */
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "FiraSans";
|
||||
src: url("fonts/FiraSans-Bold.woff") format("woff");
|
||||
font-weight: 800; /* bolder */
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "FiraSans";
|
||||
src: url("fonts/FiraSans-BoldItalic.woff") format("woff");
|
||||
font-style: italic;
|
||||
font-weight: 800; /* bolder */
|
||||
}
|
||||
118
gaia/apps/music/components/dom-scheduler/bower_components/gaia-icons/bin/report.js
vendored
Executable file
|
|
@ -0,0 +1,118 @@
|
|||
'use strict';
|
||||
|
||||
// Create a report about unused icons in a project.
|
||||
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var exec = require('child_process').exec;
|
||||
|
||||
var optimist = require('optimist');
|
||||
|
||||
var cli = optimist
|
||||
.usage('Create a report about unused icons in a project.' + '\n' +
|
||||
'Usage: $0 path/to/your/project/');
|
||||
var projectDir = cli.argv._[0];
|
||||
|
||||
// No folder passed? Show help message then.
|
||||
if (!projectDir) {
|
||||
cli.showHelp();
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Check if the directory exist.
|
||||
var stats = fs.lstatSync(projectDir);
|
||||
if (!stats.isDirectory()) {
|
||||
console.log('\x1b[31mPlease provide a valid folder.\x1b[0m\n');
|
||||
cli.showHelp();
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of all the icons in the font.
|
||||
* @returns {Array}
|
||||
*/
|
||||
function getIcons() {
|
||||
var icons = fs.readdirSync('./images/');
|
||||
|
||||
icons = icons.map(function(icon) {
|
||||
return path.basename(icon, '.svg');
|
||||
});
|
||||
|
||||
return icons;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use a grep pattern to extract used icons from files.
|
||||
* @param {string} grep
|
||||
* @param {Function} callback
|
||||
*/
|
||||
function getUsedIcons(grep, callback) {
|
||||
exec(grep, function(err, stdout, stderr) {
|
||||
var usedIcons = stdout
|
||||
.trim()
|
||||
.split('\n')
|
||||
.map(function(icon) {
|
||||
// Take only the icon name.
|
||||
return icon.split(/["']/)[1];
|
||||
})
|
||||
.filter(function(icon) {
|
||||
// Remove non string items.
|
||||
return icon && icon.match(/^[a-z0-9-]+$/);
|
||||
});
|
||||
|
||||
callback(usedIcons);
|
||||
|
||||
if (err) {
|
||||
console.log(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of unused icons in a folder. The patterns of extraction are:
|
||||
* * data-icon="[^"]+"
|
||||
* * .dataset.icon ?= ?.+
|
||||
*/
|
||||
function getUnusedIcons() {
|
||||
var usedIcons = [];
|
||||
var length = 2;
|
||||
|
||||
var cb = function(icons) {
|
||||
usedIcons = usedIcons.concat(icons);
|
||||
length--;
|
||||
|
||||
if (length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var allIcons = getIcons();
|
||||
var unusedIcons = [];
|
||||
|
||||
allIcons.forEach(function(icon) {
|
||||
if (usedIcons.indexOf(icon) === -1) {
|
||||
unusedIcons.push(icon);
|
||||
}
|
||||
});
|
||||
|
||||
outputUnusedIcons(allIcons, unusedIcons);
|
||||
};
|
||||
|
||||
getUsedIcons('grep -rhoP \'data-icon="[^"]+"\' ' + projectDir, cb);
|
||||
getUsedIcons('grep -rhoP \'.dataset.icon ?= ?.+\' ' + projectDir, cb);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the list of unused icons to the screen.
|
||||
* @param {Array.<string>} icons
|
||||
* @param {Array.<string>} unusedIcons
|
||||
*/
|
||||
function outputUnusedIcons(icons, unusedIcons) {
|
||||
console.log('\x1b[36mList of unused icons in %s (%d/%d):\x1b[0m',
|
||||
projectDir, unusedIcons.length, icons.length);
|
||||
|
||||
unusedIcons.forEach(function(icon) {
|
||||
console.log('* ' + icon);
|
||||
});
|
||||
}
|
||||
|
||||
getUnusedIcons();
|
||||
30
gaia/apps/music/components/dom-scheduler/bower_components/gaia-icons/bower.json
vendored
Executable file
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"name": "gaia-icons",
|
||||
"version": "0.8.0",
|
||||
"homepage": "https://github.com/wilsonpage/gaia-icons",
|
||||
"authors": [
|
||||
"Wilson Page <wilsonpage@me.com>"
|
||||
],
|
||||
"main": [
|
||||
"gaia-icons.js",
|
||||
"gaia-icons.css",
|
||||
"fonts/gaia-icons.ttf",
|
||||
"gaia-icons-embedded.css"
|
||||
],
|
||||
"license": "MIT",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"!.jshintrc",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"templates",
|
||||
"images",
|
||||
"test",
|
||||
"tests",
|
||||
"index.html",
|
||||
"create-svg-icon-set-file.py",
|
||||
"Gruntfile.js",
|
||||
"README.md",
|
||||
"package.json"
|
||||
]
|
||||
}
|
||||
BIN
gaia/apps/music/components/dom-scheduler/bower_components/gaia-icons/fonts/gaia-icons.ttf
vendored
Executable file
25
gaia/apps/music/components/dom-scheduler/bower_components/gaia-icons/gaia-icons-embedded.css
vendored
Executable file
25
gaia/apps/music/components/dom-scheduler/bower_components/gaia-icons/gaia-icons.css
vendored
Executable file
|
|
@ -0,0 +1,25 @@
|
|||
/* Generated by grunt-webfont */
|
||||
/* Based on https://github.com/endtwist/fontcustom/blob/master/lib/fontcustom/templates/fontcustom.css */
|
||||
|
||||
|
||||
@font-face {
|
||||
font-family: "gaia-icons";
|
||||
src: url("fonts/gaia-icons.ttf") format("truetype");
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
|
||||
[data-icon]:before,
|
||||
.ligature-icons {
|
||||
font-family: "gaia-icons";
|
||||
content: attr(data-icon);
|
||||
display: inline-block;
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
text-decoration: inherit;
|
||||
text-transform: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
font-size: 30px;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
38
gaia/apps/music/components/dom-scheduler/bower_components/gaia-icons/gaia-icons.js
vendored
Executable file
|
|
@ -0,0 +1,38 @@
|
|||
(function(define){define(function(require,exports,module){
|
||||
/*jshint laxbreak:true*/
|
||||
|
||||
/**
|
||||
* Exports
|
||||
*/
|
||||
|
||||
var base = window.GAIA_ICONS_BASE_URL
|
||||
|| window.COMPONENTS_BASE_URL
|
||||
|| 'bower_components/';
|
||||
|
||||
if (!document.documentElement) {
|
||||
window.addEventListener('load', load);
|
||||
} else {
|
||||
load();
|
||||
}
|
||||
|
||||
function load() {
|
||||
if (isLoaded()) { return; }
|
||||
|
||||
var link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.type = 'text/css';
|
||||
link.href = base + 'gaia-icons/gaia-icons.css';
|
||||
document.head.appendChild(link);
|
||||
exports.loaded = true;
|
||||
}
|
||||
|
||||
function isLoaded() {
|
||||
return exports.loaded ||
|
||||
document.querySelector('link[href*=gaia-icons]') ||
|
||||
document.documentElement.classList.contains('gaia-icons-loaded');
|
||||
}
|
||||
|
||||
});})(typeof define=='function'&&define.amd?define
|
||||
:(function(n,w){return typeof module=='object'?function(c){
|
||||
c(require,exports,module);}:function(c){var m={exports:{}};c(function(n){
|
||||
return w[n];},m.exports,m);w[n]=m.exports;};})('gaia-icons',this));
|
||||
27
gaia/apps/music/components/dom-scheduler/bower_components/gaia-text-input/bower.json
vendored
Executable file
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"name": "gaia-text-input",
|
||||
"version": "0.1.2",
|
||||
"authors": [
|
||||
"Wilson Page <wilsonpage@me.com>"
|
||||
],
|
||||
"main": "gaia-text-input.js",
|
||||
"license": "MIT",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"test",
|
||||
"tests",
|
||||
"README.md"
|
||||
],
|
||||
"dependencies": {
|
||||
"gaia-icons": "gaia-components/gaia-icons#~0.8.0",
|
||||
"gaia-component": "gaia-components/gaia-component#~0.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"gaia-fonts": "gaia-components/gaia-fonts#~0.2.1",
|
||||
"gaia-theme": "gaia-components/gaia-theme#~0.4.3",
|
||||
"base": "gaia-components/base",
|
||||
"gaia-icons": "gaia-components/gaia-icons#master"
|
||||
}
|
||||
}
|
||||
216
gaia/apps/music/components/dom-scheduler/bower_components/gaia-text-input/gaia-text-input-multiline.js
vendored
Executable file
|
|
@ -0,0 +1,216 @@
|
|||
(function(define){define(function(require,exports,module){
|
||||
/*jshint esnext:true*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Dependencies
|
||||
*/
|
||||
|
||||
var component = require('gaia-component');
|
||||
|
||||
/**
|
||||
* Exports
|
||||
*/
|
||||
|
||||
module.exports = component.register('gaia-text-input-multiline', {
|
||||
created: function() {
|
||||
this.setupShadowRoot();
|
||||
|
||||
this.els = {
|
||||
inner: this.shadowRoot.querySelector('.inner'),
|
||||
field: this.shadowRoot.querySelector('textarea')
|
||||
};
|
||||
|
||||
this.type = this.getAttribute('type');
|
||||
this.disabled = this.hasAttribute('disabled');
|
||||
this.placeholder = this.getAttribute('placeholder');
|
||||
this.required = this.getAttribute('required');
|
||||
this.value = this.getAttribute('value');
|
||||
},
|
||||
|
||||
clear: function(e) {
|
||||
this.value = '';
|
||||
},
|
||||
|
||||
attrs: {
|
||||
placeholder: {
|
||||
get: function() { return this.els.field.placeholder; },
|
||||
set: function(value) { this.els.field.placeholder = value || ''; }
|
||||
},
|
||||
|
||||
value: {
|
||||
get: function() { return this.els.field.value; },
|
||||
set: function(value) { this.els.field.value = value; }
|
||||
},
|
||||
|
||||
required: {
|
||||
get: function() { return this.els.field.required; },
|
||||
set: function(value) { this.els.field.required = value; }
|
||||
},
|
||||
|
||||
disabled: {
|
||||
get: function() { return this.els.field.disabled; },
|
||||
set: function(value) {
|
||||
value = !!(value === '' || value);
|
||||
this.els.field.disabled = value;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
template: `<div class="inner">
|
||||
<content select="label"></content>
|
||||
<div class="fields">
|
||||
<textarea></textarea>
|
||||
<div class="focus focus-1"></div>
|
||||
<div class="focus focus-2"></div>
|
||||
</div>
|
||||
</div>
|
||||
<style>
|
||||
|
||||
/** Reset
|
||||
---------------------------------------------------------*/
|
||||
|
||||
textarea {
|
||||
box-sizing: border-box;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/** Host
|
||||
---------------------------------------------------------*/
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
margin-top: var(--base-m, 18px);
|
||||
margin-bottom: var(--base-m, 18px);
|
||||
}
|
||||
|
||||
/** Inner
|
||||
---------------------------------------------------------*/
|
||||
|
||||
.inner {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/** Label
|
||||
---------------------------------------------------------*/
|
||||
|
||||
label {
|
||||
font-size: 14px;
|
||||
display: block;
|
||||
margin: 0 0 4px 16px;
|
||||
}
|
||||
|
||||
/**
|
||||
* [disbled]
|
||||
*/
|
||||
|
||||
[disabled] label {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
/** Fields
|
||||
---------------------------------------------------------*/
|
||||
|
||||
.fields {
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
--gi-border-color:
|
||||
var(--input-border-color,
|
||||
var(--border-color,
|
||||
var(--background-plus,
|
||||
#e7e7e7)));
|
||||
|
||||
border-color:
|
||||
var(--gi-border-color);
|
||||
|
||||
border:
|
||||
var(--input-border,
|
||||
var(--border,
|
||||
1px solid var(--gi-border-color)));
|
||||
}
|
||||
|
||||
/** Textarea
|
||||
---------------------------------------------------------*/
|
||||
|
||||
textarea {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 86px;
|
||||
padding: 10px 16px;
|
||||
font-size: inherit;
|
||||
border: none;
|
||||
margin: 0;
|
||||
font: inherit;
|
||||
resize: none;
|
||||
|
||||
/* dynamic */
|
||||
|
||||
color:
|
||||
var(--text-color, #000);
|
||||
|
||||
background:
|
||||
var(--text-input-background,
|
||||
var(--input-background,
|
||||
var(--background-minus,
|
||||
#fff)));
|
||||
}
|
||||
|
||||
/**
|
||||
* [disabled]
|
||||
*/
|
||||
|
||||
textarea[disabled] {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/** Placeholder Text
|
||||
---------------------------------------------------------*/
|
||||
|
||||
::-moz-placeholder {
|
||||
font-style: italic;
|
||||
|
||||
color:
|
||||
var(--input-placeholder-color, #909ca7);
|
||||
}
|
||||
|
||||
/** Focus Bar
|
||||
---------------------------------------------------------*/
|
||||
|
||||
.focus {
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
transition: all 200ms;
|
||||
transform: scaleX(0);
|
||||
visibility: hidden;
|
||||
background: var(--highlight-color, #000);
|
||||
}
|
||||
|
||||
/**
|
||||
* input:focus
|
||||
*/
|
||||
|
||||
:focus ~ .focus {
|
||||
transform: scaleX(1);
|
||||
transition-delay: 200ms;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.focus-2 {
|
||||
top: 0;
|
||||
bottom: auto;
|
||||
}
|
||||
|
||||
</style>`
|
||||
});
|
||||
|
||||
});})((function(n,w){return typeof define=='function'&&define.amd?
|
||||
define:typeof module=='object'?function(c){c(require,exports,module);}:function(c){
|
||||
var m={exports:{}},r=function(n){return w[n];};w[n]=c(r,m.exports,m)||m.exports;};})('gaia-text-input-multiline',this));
|
||||
279
gaia/apps/music/components/dom-scheduler/bower_components/gaia-text-input/gaia-text-input-pin.js
vendored
Executable file
|
|
@ -0,0 +1,279 @@
|
|||
(function(define){define(function(require,exports,module){
|
||||
/*jshint esnext:true*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Dependencies
|
||||
*/
|
||||
|
||||
var component = require('gaia-component');
|
||||
|
||||
/**
|
||||
* Locals
|
||||
*/
|
||||
|
||||
var DEFAULT_LENGTH = 4;
|
||||
|
||||
/**
|
||||
* Exports
|
||||
*/
|
||||
|
||||
module.exports = component.register('gaia-text-input-pin', {
|
||||
created: function() {
|
||||
this.setupShadowRoot();
|
||||
|
||||
this.els = {
|
||||
inner: this.shadowRoot.querySelector('.inner'),
|
||||
fields: this.shadowRoot.querySelector('.fields'),
|
||||
input: this.shadowRoot.querySelector('input')
|
||||
};
|
||||
|
||||
this.disabled = this.hasAttribute('disabled');
|
||||
this.length = this.getAttribute('length') || DEFAULT_LENGTH;
|
||||
|
||||
this.addEventListener('keyup', () => this.updateCells());
|
||||
},
|
||||
|
||||
updateCells: function() {
|
||||
var l = this.els.input.value.length;
|
||||
this.els.cells.forEach((cell, i) => {
|
||||
cell.classList.toggle('populated', i < l);
|
||||
cell.classList.toggle('focused', i == l);
|
||||
});
|
||||
},
|
||||
|
||||
onBackspace: function(e) {
|
||||
var input = e.target;
|
||||
var empty = !input.value;
|
||||
var previous = input.previousElementSibling;
|
||||
|
||||
if (!empty && previous) {
|
||||
previous.clear();
|
||||
previous.focus();
|
||||
} else if (empty && previous) {
|
||||
previous.focus();
|
||||
}
|
||||
},
|
||||
|
||||
setupFields: function() {
|
||||
this.els.fields.innerHTML = '';
|
||||
this.els.cells = [];
|
||||
|
||||
for (var i = 0, l = this.length; i < l; i++) {
|
||||
var el = document.createElement('div');
|
||||
el.className = 'cell';
|
||||
this.els.fields.appendChild(el);
|
||||
this.els.cells.push(el);
|
||||
}
|
||||
},
|
||||
|
||||
attributeChangedCallback: function(attr, from, to) {
|
||||
if (attrs[attr]) { this[attr] = to; }
|
||||
},
|
||||
|
||||
clear: function(e) {
|
||||
this.value = '';
|
||||
},
|
||||
|
||||
/**
|
||||
* Attributes
|
||||
*/
|
||||
|
||||
attrs: {
|
||||
length: {
|
||||
get: function() { return this._length; },
|
||||
set: function(value) {
|
||||
value = Number(value);
|
||||
this._length = value;
|
||||
this.els.input.setAttribute('maxlength', this.length);
|
||||
this.setupFields();
|
||||
}
|
||||
},
|
||||
|
||||
value: {
|
||||
get: function() { return this.els.input.value; },
|
||||
set: function(value) { this.els.input.value = value; }
|
||||
},
|
||||
|
||||
disabled: {
|
||||
get: function() { return this.els.input.disabled; },
|
||||
set: function(value) {
|
||||
value = !!(value === '' || value);
|
||||
this.els.input.disabled = value;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
template: `
|
||||
<div class="inner">
|
||||
<content select="label"></content>
|
||||
<div class="container">
|
||||
<input />
|
||||
<div class="fields"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
||||
/** Host
|
||||
---------------------------------------------------------*/
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
height: 40px;
|
||||
margin-top: var(--base-m, 18px);
|
||||
margin-bottom: var(--base-m, 18px);
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
/** Inner
|
||||
---------------------------------------------------------*/
|
||||
|
||||
.inner {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/** Label
|
||||
---------------------------------------------------------*/
|
||||
|
||||
label {
|
||||
font-size: 14px;
|
||||
display: block;
|
||||
margin: 0 0 4px 16px;
|
||||
}
|
||||
|
||||
/** Container
|
||||
---------------------------------------------------------*/
|
||||
|
||||
.container {
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/** Input (hidden)
|
||||
---------------------------------------------------------*/
|
||||
|
||||
input {
|
||||
opacity: 0;
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/** Fields
|
||||
---------------------------------------------------------*/
|
||||
|
||||
.fields {
|
||||
display: flex;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
margin-left: -1rem;
|
||||
}
|
||||
|
||||
/**
|
||||
* [disbled]
|
||||
*/
|
||||
|
||||
[disabled] + .fields {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/** Cell
|
||||
---------------------------------------------------------*/
|
||||
|
||||
.cell {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
margin-left: 1rem;
|
||||
flex: 1;
|
||||
|
||||
/* dynamic */
|
||||
|
||||
--gi-border-color:
|
||||
var(--input-border-color,
|
||||
var(--border-color,
|
||||
var(--background-plus,
|
||||
#e7e7e7)));
|
||||
|
||||
border-color:
|
||||
var(--gi-border-color);
|
||||
|
||||
border:
|
||||
var(--input-border,
|
||||
var(--border,
|
||||
1px solid var(--gi-border-color)));
|
||||
|
||||
background:
|
||||
var(--text-input-background,
|
||||
var(--input-background,
|
||||
var(--background-minus,
|
||||
#fff)));
|
||||
}
|
||||
|
||||
/**
|
||||
* [disbled]
|
||||
*/
|
||||
|
||||
[disabled] + .fields .cell {
|
||||
background: none;
|
||||
}
|
||||
|
||||
/** Dot
|
||||
---------------------------------------------------------*/
|
||||
|
||||
.cell::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
margin: -7px;
|
||||
border-radius: 50%;
|
||||
opacity: 0;
|
||||
|
||||
/* dynamic */
|
||||
|
||||
background:
|
||||
var(--text-color);
|
||||
}
|
||||
|
||||
/**
|
||||
* .populated
|
||||
*/
|
||||
|
||||
.cell.populated::after {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.cell::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
visibility: hidden;
|
||||
background: var(--highlight-color, #000);
|
||||
}
|
||||
|
||||
/**
|
||||
* input:focus
|
||||
*/
|
||||
|
||||
input:focus + .fields .cell::before {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
</style>
|
||||
`
|
||||
});
|
||||
|
||||
});})((function(n,w){return typeof define=='function'&&define.amd?
|
||||
define:typeof module=='object'?function(c){c(require,exports,module);}:function(c){
|
||||
var m={exports:{}},r=function(n){return w[n];};w[n]=c(r,m.exports,m)||m.exports;};})('gaia-text-input-pin',this));
|
||||
317
gaia/apps/music/components/dom-scheduler/bower_components/gaia-text-input/gaia-text-input.js
vendored
Executable file
|
|
@ -0,0 +1,317 @@
|
|||
(function(define){define(function(require,exports,module){
|
||||
/*jshint esnext:true*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Dependencies
|
||||
*/
|
||||
|
||||
var component = require('gaia-component');
|
||||
require('gaia-icons'); // Load gaia-icons
|
||||
|
||||
/**
|
||||
* Exports
|
||||
*/
|
||||
|
||||
module.exports = component.register('gaia-text-input', {
|
||||
extends: HTMLInputElement,
|
||||
|
||||
created: function() {
|
||||
this.setupShadowRoot();
|
||||
|
||||
this.els = {
|
||||
inner: this.shadowRoot.querySelector('.inner'),
|
||||
input: this.shadowRoot.querySelector('input'),
|
||||
clear: this.shadowRoot.querySelector('.clear')
|
||||
};
|
||||
|
||||
this.type = this.getAttribute('type');
|
||||
this.disabled = this.hasAttribute('disabled');
|
||||
this.clearable = this.hasAttribute('clearable');
|
||||
this.placeholder = this.getAttribute('placeholder');
|
||||
this.required = this.getAttribute('required');
|
||||
this.value = this.getAttribute('value');
|
||||
|
||||
// Don't take focus from the input field
|
||||
this.els.clear.addEventListener('mousedown', (e) => e.preventDefault());
|
||||
this.els.clear.addEventListener('click', e => this.clear(e));
|
||||
},
|
||||
|
||||
clear: function(e) {
|
||||
this.value = '';
|
||||
},
|
||||
|
||||
focus: function() {
|
||||
this.els.input.focus();
|
||||
},
|
||||
|
||||
/**
|
||||
* Attributes
|
||||
*/
|
||||
|
||||
attrs: {
|
||||
type: {
|
||||
get: function() { return this.els.input.getAttribute('type'); },
|
||||
set: function(value) {
|
||||
if (!value) { return; }
|
||||
this.els.inner.setAttribute('type', value);
|
||||
this.els.input.setAttribute('type', value);
|
||||
}
|
||||
},
|
||||
|
||||
placeholder: {
|
||||
get: function() { return this.field.placeholder; },
|
||||
set: function(value) {
|
||||
if (!value && value !== '') { return; }
|
||||
this.els.input.placeholder = value;
|
||||
}
|
||||
},
|
||||
|
||||
clearable: {
|
||||
get: function() { return this._clearable; },
|
||||
set: function(value) {
|
||||
var clearable = !!(value === '' || value);
|
||||
if (clearable === this.clearable) { return; }
|
||||
|
||||
if (clearable) {
|
||||
this.els.inner.setAttribute('clearable', '');
|
||||
this.setAttribute('clearable', '');
|
||||
} else {
|
||||
this.els.inner.removeAttribute('clearable');
|
||||
this.removeAttribute('clearable');
|
||||
}
|
||||
|
||||
this._clearable = clearable;
|
||||
}
|
||||
},
|
||||
|
||||
value: {
|
||||
get: function() { return this.els.input.value; },
|
||||
set: function(value) { this.els.input.value = value; }
|
||||
},
|
||||
|
||||
required: {
|
||||
get: function() { return this.els.input.required; },
|
||||
set: function(value) { this.els.input.required = value; }
|
||||
},
|
||||
|
||||
maxlength: {
|
||||
get: function() { return this.els.input.getAttribute('maxlength'); },
|
||||
set: function(value) { this.els.input.setAttribute('maxlength', value); }
|
||||
},
|
||||
|
||||
disabled: {
|
||||
get: function() { return this.els.input.disabled; },
|
||||
set: function(value) {
|
||||
value = !!(value === '' || value);
|
||||
this.els.input.disabled = value;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
template: `
|
||||
<div class="inner">
|
||||
<content select="label"></content>
|
||||
<div class="fields">
|
||||
<input type="text"/>
|
||||
<button class="clear" tabindex="-1"></button>
|
||||
<div class="focus"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
height: 40px;
|
||||
margin-top: var(--base-m, 18px);
|
||||
margin-bottom: var(--base-m, 18px);
|
||||
}
|
||||
|
||||
/** Reset
|
||||
---------------------------------------------------------*/
|
||||
|
||||
input,
|
||||
button {
|
||||
box-sizing: border-box;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/** Inner
|
||||
---------------------------------------------------------*/
|
||||
|
||||
.inner {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/** Label
|
||||
---------------------------------------------------------*/
|
||||
|
||||
label {
|
||||
font-size: 14px;
|
||||
display: block;
|
||||
margin: 0 0 4px 16px;
|
||||
}
|
||||
|
||||
/**
|
||||
* [disbled]
|
||||
*/
|
||||
|
||||
[disabled] label {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
/** Fields
|
||||
---------------------------------------------------------*/
|
||||
|
||||
.fields {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
--gi-border-color:
|
||||
var(--input-border-color,
|
||||
var(--border-color,
|
||||
var(--background-plus,
|
||||
#e7e7e7)));
|
||||
|
||||
border-color:
|
||||
var(--gi-border-color);
|
||||
|
||||
border:
|
||||
var(--input-border,
|
||||
var(--border,
|
||||
1px solid var(--gi-border-color)));
|
||||
}
|
||||
|
||||
/**
|
||||
* [type='search']
|
||||
*/
|
||||
|
||||
[type='search'] .fields {
|
||||
border-radius: 30px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/** Input Field
|
||||
---------------------------------------------------------*/
|
||||
|
||||
input {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
padding: 0 16px;
|
||||
margin: 0;
|
||||
font: inherit;
|
||||
resize: none;
|
||||
|
||||
/* Dynamic */
|
||||
|
||||
color:
|
||||
var(--text-color, #000);
|
||||
|
||||
background:
|
||||
var(--text-input-background,
|
||||
var(--input-background,
|
||||
var(--background-minus,
|
||||
#fff)));
|
||||
}
|
||||
|
||||
/**
|
||||
* [disabled]
|
||||
*/
|
||||
|
||||
input[disabled] {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/** Placeholder Text
|
||||
---------------------------------------------------------*/
|
||||
|
||||
::-moz-placeholder {
|
||||
font-style: italic;
|
||||
|
||||
color:
|
||||
var(--input-placeholder-color, #909ca7);
|
||||
}
|
||||
|
||||
/** Clear Button
|
||||
---------------------------------------------------------*/
|
||||
|
||||
.clear {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 11px;
|
||||
right: 10px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border-radius: 50%;
|
||||
opacity: 0;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
|
||||
background:
|
||||
var(--input-clear-background, #999);
|
||||
}
|
||||
|
||||
/**
|
||||
* [clearable]
|
||||
*/
|
||||
|
||||
[clearable] .clear {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/**
|
||||
* input:focus
|
||||
*/
|
||||
|
||||
input:focus ~ .clear {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/** Clear Icon
|
||||
---------------------------------------------------------*/
|
||||
|
||||
.clear:before {
|
||||
font: normal 500 19px/16.5px 'gaia-icons';
|
||||
content: 'close';
|
||||
display: block;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
/** Focus Bar
|
||||
---------------------------------------------------------*/
|
||||
|
||||
.focus {
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
transition: all 200ms;
|
||||
transform: scaleX(0);
|
||||
visibility: hidden;
|
||||
background: var(--highlight-color, #000);
|
||||
}
|
||||
|
||||
/**
|
||||
* input:focus
|
||||
*/
|
||||
|
||||
:focus ~ .focus {
|
||||
transform: scaleX(1);
|
||||
transition-delay: 200ms;
|
||||
visibility: visible;
|
||||
}
|
||||
</style>
|
||||
`
|
||||
});
|
||||
|
||||
});})((function(n,w){return typeof define=='function'&&define.amd?
|
||||
define:typeof module=='object'?function(c){c(require,exports,module);}:function(c){
|
||||
var m={exports:{}},r=function(n){return w[n];};w[n]=c(r,m.exports,m)||m.exports;};})('gaia-text-input',this));
|
||||
50
gaia/apps/music/components/dom-scheduler/bower_components/gaia-text-input/index.html
vendored
Executable file
|
|
@ -0,0 +1,50 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title><gaia-text-input></title>
|
||||
<meta name="description" content="gaia-text-input demo">
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1">
|
||||
<link rel="stylesheet" type="text/css" href="bower_components/base/base.css">
|
||||
<link rel="stylesheet" type="text/css" href="bower_components/gaia-icons/gaia-icons.css">
|
||||
<link rel="stylesheet" type="text/css" href="bower_components/gaia-theme/gaia-theme.css">
|
||||
<link rel="stylesheet" type="text/css" href="bower_components/gaia-fonts/style.css">
|
||||
<script src="bower_components/gaia-theme/lib/gaia-theme-selector.js"></script>
|
||||
<script src="bower_components/gaia-component/gaia-component.js"></script>
|
||||
<script src="gaia-text-input.js"></script>
|
||||
<script src="gaia-text-input-multiline.js"></script>
|
||||
<script src="gaia-text-input-pin.js"></script>
|
||||
<style type="text/css">
|
||||
|
||||
body {
|
||||
padding: 48px 24px 24px;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<gaia-theme-selector></gaia-theme-selector>
|
||||
|
||||
<button id="btn">Focus</button>
|
||||
|
||||
|
||||
<gaia-text-input id="field" placeholder="Default"></gaia-text-input>
|
||||
<gaia-text-input placeholder="Search" type="search"></gaia-text-input>
|
||||
<gaia-text-input placeholder="Email" type="email" clearable></gaia-text-input>
|
||||
<gaia-text-input-multiline placeholder="Multiline"></gaia-text-input-multiline>
|
||||
<gaia-text-input-pin length="4"></gaia-text-input-pin>
|
||||
|
||||
<gaia-text-input placeholder="Disabled" disabled></gaia-text-input>
|
||||
<gaia-text-input placeholder="Search" type="search" disabled></gaia-text-input>
|
||||
<gaia-text-input placeholder="Email" type="email" clearable disabled></gaia-text-input>
|
||||
<gaia-text-input-multiline placeholder="Multiline" disabled></gaia-text-input-multiline>
|
||||
<gaia-text-input-pin length="4" disabled></gaia-text-input-pin>
|
||||
|
||||
<!-- <gaia-text-input-password></gaia-text-input-password> -->
|
||||
|
||||
|
||||
<script>
|
||||
btn.onclick = e => field.focus();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
28
gaia/apps/music/components/dom-scheduler/bower_components/gaia-text-input/package.json
vendored
Executable file
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"name": "gaia-text-input",
|
||||
"homepage": "https://github.com/gaia-components/gaia-text-input",
|
||||
"license": "ISC",
|
||||
"main": "gaia-text-input.js",
|
||||
"directories": {
|
||||
"test": "test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"bower": "^1.3.5",
|
||||
"karma": "^0.12.16",
|
||||
"karma-firefox-launcher": "^0.1.3",
|
||||
"karma-mocha": "^0.1.3",
|
||||
"karma-sinon-chai": "^0.1.6",
|
||||
"mozilla-download": "0.4.4"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "./node_modules/karma/bin/karma start test/karma.conf.js --single-run",
|
||||
"test-dev": "./node_modules/karma/bin/karma start test/karma.conf.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/gaia-components/gaia-text-input.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/gaia-components/gaia-text-input/issues"
|
||||
}
|
||||
}
|
||||
26
gaia/apps/music/components/dom-scheduler/bower_components/gaia-theme/bower.json
vendored
Executable file
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"name": "gaia-theme",
|
||||
"version": "0.5.0",
|
||||
"homepage": "https://github.com/gaia-components/gaia-theme",
|
||||
"authors": [
|
||||
"Wilson Page <wilsonpage@me.com>"
|
||||
],
|
||||
"license": "MIT",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"!.jshintrc",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"test",
|
||||
"tests",
|
||||
"index.html",
|
||||
"README.md"
|
||||
],
|
||||
"devDependencies": {
|
||||
"gaia-icons": "gaia-components/gaia-icons",
|
||||
"gaia-header": "gaia-components/gaia-header",
|
||||
"gaia-button": "gaia-components/gaia-button",
|
||||
"gaia-switch": "gaia-components/gaia-switch",
|
||||
"gaia-checkbox": "gaia-components/gaia-checkbox"
|
||||
}
|
||||
}
|
||||
244
gaia/apps/music/components/dom-scheduler/bower_components/gaia-theme/gaia-theme.css
vendored
Executable file
|
|
@ -0,0 +1,244 @@
|
|||
/*
|
||||
Gaia theme variables
|
||||
*/
|
||||
|
||||
|
||||
:root {
|
||||
|
||||
/** Grey Colors
|
||||
---------------------------------------------------------*/
|
||||
|
||||
--color-alpha: #333333;
|
||||
--color-beta: #ffffff;
|
||||
--color-gamma: #4d4d4d;
|
||||
--color-delta: #5f5f5f;
|
||||
--color-epsilon: #858585;
|
||||
--color-zeta: #a6a6a6;
|
||||
--color-eta: #c7c7c7;
|
||||
--color-theta: #e7e7e7;
|
||||
--color-iota: #f4f4f4;
|
||||
|
||||
/** Brand Colors
|
||||
---------------------------------------------------------*/
|
||||
|
||||
--color-darkblue: #00539f;
|
||||
--color-blue: #00caf2;
|
||||
--color-turquoise: #27c8c2;
|
||||
--color-darkorange: #e66000;
|
||||
--color-orange: #ff9500;
|
||||
--color-yellow: #ffcb00;
|
||||
--color-violet: #c40c84;
|
||||
|
||||
--color-warning: #fbbd3c;
|
||||
--color-destructive: #e2443a;
|
||||
--color-preffered: #00ba91;
|
||||
|
||||
/** Background
|
||||
---------------------------------------------------------*/
|
||||
|
||||
--background: var(--color-beta);
|
||||
--background-plus: var(--color-theta);
|
||||
--background-minus: var(--color-zeta);
|
||||
|
||||
/** Borders
|
||||
---------------------------------------------------------*/
|
||||
|
||||
--border-color: var(--color-theta);
|
||||
|
||||
/** Text Color
|
||||
---------------------------------------------------------*/
|
||||
|
||||
--text-color: var(--color-gamma);
|
||||
--text-color-minus: var(--color-eta);
|
||||
|
||||
/** Button
|
||||
---------------------------------------------------------*/
|
||||
|
||||
--button-background: var(--color-iota);
|
||||
|
||||
/** Links
|
||||
---------------------------------------------------------*/
|
||||
|
||||
--link-color: var(--highlight-color);
|
||||
|
||||
/** Inputs
|
||||
---------------------------------------------------------*/
|
||||
|
||||
--input-background: var(--color-beta);
|
||||
--input-color: var(--color-alpha);
|
||||
--input-clear-background: #909ca7;
|
||||
|
||||
/** Buttons
|
||||
---------------------------------------------------------*/
|
||||
|
||||
--button-box-shadow:
|
||||
inset 0 -1px 0 #d8d8d8,
|
||||
inset 0 1px 0 #eeeeee;
|
||||
--button-box-shadow-active: var(--button-box-shadow);
|
||||
|
||||
/** Header
|
||||
---------------------------------------------------------*/
|
||||
|
||||
--header-disabled-button-color: rgba(255,255,255,0.3);
|
||||
|
||||
/** Transitions
|
||||
---------------------------------------------------------*/
|
||||
|
||||
--transition-duration: 0.2s;
|
||||
}
|
||||
|
||||
|
||||
.theme-productivity {
|
||||
|
||||
/** Brand Color
|
||||
---------------------------------------------------------*/
|
||||
|
||||
--highlight-color: var(--color-orange);
|
||||
|
||||
/** Header
|
||||
---------------------------------------------------------*/
|
||||
|
||||
--header-background: var(--highlight-color);
|
||||
--header-color: var(--color-beta);
|
||||
}
|
||||
|
||||
.theme-communications {
|
||||
|
||||
/** Brand Color
|
||||
---------------------------------------------------------*/
|
||||
|
||||
--highlight-color: var(--color-turquoise);
|
||||
|
||||
/** Links
|
||||
---------------------------------------------------------*/
|
||||
|
||||
--link-color: #177874;
|
||||
|
||||
/** Header
|
||||
---------------------------------------------------------*/
|
||||
|
||||
--header-background: var(--highlight-color);
|
||||
--header-color: var(--color-beta);
|
||||
--header-icon-color: var(--color-beta);
|
||||
--header-button-color: var(--color-beta);
|
||||
}
|
||||
|
||||
.theme-settings {
|
||||
|
||||
/** Background
|
||||
---------------------------------------------------------*/
|
||||
|
||||
--background: var(--color-iota);
|
||||
--background-plus: var(--color-beta);
|
||||
--background-minus: var(--color-epsilon);
|
||||
--background-minus-minus: var(--color-zeta);
|
||||
|
||||
/** Highlight
|
||||
---------------------------------------------------------*/
|
||||
|
||||
--highlight-color: var(--color-blue);
|
||||
|
||||
/** Borders
|
||||
---------------------------------------------------------*/
|
||||
|
||||
--border-color: var(--color-theta);
|
||||
|
||||
/** Text Color
|
||||
---------------------------------------------------------*/
|
||||
|
||||
--text-color: var(--color-gamma);
|
||||
|
||||
/** Titles
|
||||
---------------------------------------------------------*/
|
||||
|
||||
--title-color: var(--background-minus);
|
||||
|
||||
/** Buttons
|
||||
---------------------------------------------------------*/
|
||||
|
||||
--button-background: var(--background-plus);
|
||||
|
||||
/** Switch
|
||||
---------------------------------------------------------*/
|
||||
|
||||
--switch-background: var(--background-minus-minus);
|
||||
|
||||
/** Checkbox
|
||||
---------------------------------------------------------*/
|
||||
|
||||
--checkbox-border-color: var(--background-minus-minus);
|
||||
|
||||
/** Header
|
||||
---------------------------------------------------------*/
|
||||
|
||||
--header-color: var(--title-color);
|
||||
--header-background: var(--background);
|
||||
--header-button-color: var(--highlight-color);
|
||||
--header-disabled-button-color: var(--color-theta);
|
||||
--header-action-button-color: var(--title-color);
|
||||
}
|
||||
|
||||
.theme-media {
|
||||
|
||||
/** Highlight Color
|
||||
---------------------------------------------------------*/
|
||||
|
||||
--highlight-color: var(--color-blue);
|
||||
|
||||
/** Background
|
||||
---------------------------------------------------------*/
|
||||
|
||||
--background: var(--color-alpha);
|
||||
--background-plus: var(--color-gamma);
|
||||
--background-minus: #2B2B2B;
|
||||
--background-minus-minus: #1a1a1a;
|
||||
|
||||
/** Text Color
|
||||
---------------------------------------------------------*/
|
||||
|
||||
--text-color: var(--color-beta);
|
||||
|
||||
/** Button
|
||||
---------------------------------------------------------*/
|
||||
|
||||
--button-background: var(--background-plus);
|
||||
|
||||
/** Header
|
||||
---------------------------------------------------------*/
|
||||
|
||||
--header-background: var(--background);
|
||||
--header-icon-color: var(--text-color);
|
||||
--header-button-color: var(--highlight-color);
|
||||
|
||||
/** Borders
|
||||
---------------------------------------------------------*/
|
||||
|
||||
--border-color: var(--color-gamma);
|
||||
|
||||
/** Inputs
|
||||
---------------------------------------------------------*/
|
||||
|
||||
--input-background: var(--background-plus);
|
||||
|
||||
/** Text Input
|
||||
---------------------------------------------------------*/
|
||||
|
||||
--text-input-background: var(--background-minus);
|
||||
|
||||
/** Switch
|
||||
---------------------------------------------------------*/
|
||||
|
||||
--switch-head-border-color: var(--background-minus-minus);
|
||||
--switch-background: var(--background-minus-minus);
|
||||
|
||||
/** Checkbox
|
||||
---------------------------------------------------------*/
|
||||
|
||||
--checkbox-border-color: var(--background-minus-minus);
|
||||
|
||||
/** Buttons
|
||||
---------------------------------------------------------*/
|
||||
|
||||
--button-box-shadow: none;
|
||||
--button-box-shadow-active: none;
|
||||
}
|
||||
111
gaia/apps/music/components/dom-scheduler/bower_components/gaia-theme/lib/gaia-theme-selector.js
vendored
Executable file
|
|
@ -0,0 +1,111 @@
|
|||
(function(define){'use strict';define(function(require,exports,module){
|
||||
/*jshint esnext:true*/
|
||||
|
||||
/**
|
||||
* Extend from the `HTMLElement` prototype
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
var proto = Object.create(HTMLElement.prototype);
|
||||
|
||||
proto.createdCallback = function() {
|
||||
this.createShadowRoot().innerHTML = template;
|
||||
|
||||
this.els = {
|
||||
themes: this.shadowRoot.querySelector('.themes'),
|
||||
rtl: this.shadowRoot.querySelector('.toggle-rtl')
|
||||
};
|
||||
|
||||
this.els.rtl.addEventListener('click', this.onRtlClick.bind(this));
|
||||
this.els.themes.addEventListener('click', this.onThemeClick.bind(this));
|
||||
this.styleHack();
|
||||
this.set(localStorage.theme || 'settings');
|
||||
};
|
||||
|
||||
proto.styleHack = function() {
|
||||
var style = this.shadowRoot.querySelector('style');
|
||||
style.setAttribute('scoped', '');
|
||||
this.appendChild(style.cloneNode(true));
|
||||
};
|
||||
|
||||
proto.onThemeClick = function(e) {
|
||||
var theme = e.target.value;
|
||||
if (theme) { this.set(theme); }
|
||||
};
|
||||
|
||||
proto.onRtlClick = function() {
|
||||
document.dir = this.els.rtl.checked ? 'rtl' : 'ltr';
|
||||
};
|
||||
|
||||
proto.set = function(theme) {
|
||||
var radio = this.shadowRoot.querySelector('[value="' + theme + '"]');
|
||||
document.body.classList.remove('theme-' + this.theme);
|
||||
document.body.classList.add('theme-' + theme);
|
||||
radio.checked = true;
|
||||
this.theme = theme;
|
||||
localStorage.theme = theme;
|
||||
};
|
||||
|
||||
var template = `
|
||||
<style>
|
||||
|
||||
* { margin: 0; padding: 0; }
|
||||
|
||||
gaia-theme-selector {
|
||||
position: fixed;
|
||||
top: 0; left: 0;
|
||||
z-index: 101;
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
background: rgba(255,255,255,0.8);
|
||||
color: #000;
|
||||
font-size: 9px;
|
||||
text-align: center;
|
||||
box-shadow: 0 1px 1px rgba(0,0,0,0.1);
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
form {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
form label {
|
||||
-moz-margin-start: 9px;
|
||||
}
|
||||
|
||||
form input {
|
||||
-moz-margin-end: 6px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.rtl {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<form>
|
||||
<label class="rtl"><input type="checkbox" class="toggle-rtl" />RTL</label>
|
||||
<span class="themes">
|
||||
<label><input type="radio" name="theme" value="productivity"/>Prod</label>
|
||||
<label><input type="radio" name="theme" value="communications"/>Comms</label>
|
||||
<label><input type="radio" name="theme" value="media"/>Media</label>
|
||||
<label><input type="radio" name="theme" value="settings"/>Settings</label>
|
||||
</span>
|
||||
</form>`;
|
||||
|
||||
document.registerElement('gaia-theme-selector', { prototype: proto });
|
||||
|
||||
});})((function(n,w){'use strict';return typeof define=='function'&&define.amd?
|
||||
define:typeof module=='object'?function(c){c(require,exports,module);}:
|
||||
function(c){var m={exports:{}},r=function(n){return w[n];};
|
||||
w[n]=c(r,m.exports,m)||m.exports;};})('gaia-theme-selector',this));
|
||||
134
gaia/apps/music/components/dom-scheduler/demo-app/css/app.css
Executable file
|
|
@ -0,0 +1,134 @@
|
|||
html, body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
font-family: sans-serif;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
h1 {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#h1-after {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
left: 0;
|
||||
top: 46px;
|
||||
|
||||
pointer-events: none;
|
||||
background-color: #ff9500;
|
||||
|
||||
opacity: 0;
|
||||
|
||||
animation-duration: 0.25s;
|
||||
animation-timing-function: ease-in;
|
||||
animation-fill-mode: both;
|
||||
|
||||
-webkit-animation-duration: 0.25s;
|
||||
-webkit-animation-timing-function: ease-in;
|
||||
-webkit-animation-fill-mode: both;
|
||||
}
|
||||
|
||||
[data-anim='reveal'] {
|
||||
animation-name: reveal;
|
||||
-webkit-animation-name: reveal;
|
||||
}
|
||||
|
||||
@keyframes reveal {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes reveal {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
[data-anim='hide'] {
|
||||
animation-name: hide;
|
||||
-webkit-animation-name: hide;
|
||||
}
|
||||
|
||||
@keyframes hide {
|
||||
from {
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes hide {
|
||||
from {
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
position: absolute;
|
||||
display: block;
|
||||
top: 0;
|
||||
right: 0;
|
||||
height: 50px;
|
||||
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
outline: 0;
|
||||
background: none;
|
||||
padding: 0 15px;
|
||||
|
||||
text-align: center;
|
||||
text-decoration: underline;
|
||||
font-size: 14px;
|
||||
color: #ffffff;
|
||||
-moz-appearance: none;
|
||||
|
||||
opacity: 1;
|
||||
transition: transform 0.1s linear,
|
||||
opacity 0.1s linear;
|
||||
-webkit-transition: -webkit-transform 0.1s linear,
|
||||
opacity 0.1s linear;
|
||||
|
||||
-webkit-tap-highlight-color: rgba(0,0,0,0);
|
||||
}
|
||||
|
||||
button.transitioning {
|
||||
opacity: 0.5;
|
||||
transform: scale(0.6, 0.6);
|
||||
-webkit-transform: scale(0.6, 0.6);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
ul {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
body > section {
|
||||
position: absolute;
|
||||
top: 50px;
|
||||
width: 100%;
|
||||
height: calc(100% - 50px);
|
||||
|
||||
-webkit-overflow-scrolling: touch;
|
||||
-webkit-tap-highlight-color: rgba(0,0,0,0);
|
||||
}
|
||||
|
||||
21
gaia/apps/music/components/dom-scheduler/demo-app/css/h1.css
Executable file
|
|
@ -0,0 +1,21 @@
|
|||
h1 {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: #27c8c2;
|
||||
|
||||
text-align: center;
|
||||
line-height: 50px;
|
||||
font-weight: 300;
|
||||
font-style: italic;
|
||||
font-size: 24px;
|
||||
color: #ffffff;
|
||||
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
143
gaia/apps/music/components/dom-scheduler/demo-app/css/ul.css
Executable file
|
|
@ -0,0 +1,143 @@
|
|||
ul {
|
||||
list-style: none;
|
||||
padding: 0 0 0 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
ul section {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul h2 {
|
||||
position: sticky;
|
||||
position: -webkit-sticky;
|
||||
top: 0px;
|
||||
height: 32px;
|
||||
|
||||
margin: 0;
|
||||
padding: 0 6px;
|
||||
box-sizing: border-box;
|
||||
|
||||
font-size: 0.9em;
|
||||
line-height: 32px;
|
||||
|
||||
background: #efefef;
|
||||
border-bottom: 1px #bcbcbc solid;
|
||||
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
ul .background {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
|
||||
background: linear-gradient(to bottom, transparent 0px, transparent 45px, #EDEDED 45px, #EDEDED 70px, transparent 20px),
|
||||
linear-gradient(to bottom, transparent 0px, transparent 15px, #EDEDED 15px, #EDEDED 25px, transparent 20px),
|
||||
linear-gradient(to top, #bcbcbc, #bcbcbc 1px, white 1px, white);
|
||||
background-position: 20% 88px, 5% -88px, 0 -88px;
|
||||
background-size: 90% 88px, 60% 88px, 100% 88px;
|
||||
background-repeat: repeat-y;
|
||||
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
ul.reordering .background {
|
||||
background: linear-gradient(to top, #bcbcbc, #bcbcbc 1px, white 1px, white);
|
||||
background-position: 0 -88px;
|
||||
background-size: 100% 88px;
|
||||
}
|
||||
|
||||
ul li {
|
||||
width: 100%;
|
||||
height: 88px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
|
||||
-moz-user-select: none;
|
||||
|
||||
background: white;
|
||||
border-bottom: 1px #bcbcbc solid;
|
||||
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
ul li h3 {
|
||||
margin: 0;
|
||||
padding: 9px 6px 3px 6px;
|
||||
background: white;
|
||||
|
||||
color: #424242;
|
||||
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
ul li p {
|
||||
margin: 0;
|
||||
padding: 3px 6px 10px 6px;
|
||||
background: white;
|
||||
|
||||
color: #797979;
|
||||
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
li.new {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
li .overlay {
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: calc(100% - 2px);
|
||||
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
|
||||
opacity: 0;
|
||||
animation-duration: 0.2s;
|
||||
animation-timing-function: ease-in;
|
||||
animation-fill-mode: both;
|
||||
|
||||
-webkit-animation-duration: 0.2s;
|
||||
-webkit-animation-timing-function: ease-in;
|
||||
-webkit-animation-fill-mode: both;
|
||||
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
li.edit .overlay {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.overlay .cursor {
|
||||
margin: 17px auto;
|
||||
padding: 0;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
|
||||
text-align: center;
|
||||
line-height: 50px;
|
||||
font-size: 25px;
|
||||
color: #0b0b0b;
|
||||
|
||||
border-radius: 8px;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
-moz-user-select: none;
|
||||
}
|
||||
|
||||
li.dragging {
|
||||
transition: transform 0s linear;
|
||||
-webkit-transition: -webkit-transform 0s linear;
|
||||
}
|
||||
|
||||
li.dragging .overlay {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
li[data-populated=false] {
|
||||
opacity: 0;
|
||||
}
|
||||
BIN
gaia/apps/music/components/dom-scheduler/demo-app/icons/icon128x128.png
Executable file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
gaia/apps/music/components/dom-scheduler/demo-app/icons/icon16x16.png
Executable file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
gaia/apps/music/components/dom-scheduler/demo-app/icons/icon48x48.png
Executable file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
gaia/apps/music/components/dom-scheduler/demo-app/icons/icon60x60.png
Executable file
|
After Width: | Height: | Size: 3.6 KiB |
63
gaia/apps/music/components/dom-scheduler/demo-app/index.html
Executable file
|
|
@ -0,0 +1,63 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1">
|
||||
|
||||
<link rel="stylesheet" href="css/h1.css">
|
||||
<link rel="stylesheet" href="css/ul.css">
|
||||
<link rel="stylesheet" href="css/app.css">
|
||||
|
||||
<!-- Component Style -->
|
||||
<link rel="stylesheet" type="text/css" href="bower_components/base/base.css">
|
||||
<link rel="stylesheet" type="text/css" href="bower_components/gaia-theme/gaia-theme.css">
|
||||
<link rel="stylesheet" type="text/css" href="bower_components/gaia-fonts/style.css">
|
||||
<link rel="stylesheet" type="text/css" href="bower_components/gaia-icons/gaia-icons.css">
|
||||
|
||||
<!-- Dependencies -->
|
||||
<script type="text/javascript" src="lib/dom-scheduler.js" defer></script>
|
||||
<script src="bower_components/gaia-component/gaia-component.js" defer></script>
|
||||
<script src="bower_components/gaia-button/gaia-button.js" defer></script>
|
||||
<script src="bower_components/gaia-text-input/gaia-text-input.js" defer></script>
|
||||
|
||||
|
||||
<!-- Libs -->
|
||||
<script src="bower_components/gaia-dialog/gaia-dialog.js" defer></script>
|
||||
<script src="bower_components/gaia-dialog/gaia-dialog-alert.js" defer></script>
|
||||
<script src="bower_components/fast-list/fast-list.js" defer></script>
|
||||
|
||||
<!-- App -->
|
||||
<script type="text/javascript" src="js/bacon-source.js" async></script>
|
||||
<script type="text/javascript" src="js/app.js" async></script>
|
||||
|
||||
<title>Dom Scheduler Demo</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>
|
||||
Main List
|
||||
</h1>
|
||||
<div id="h1-after"></div>
|
||||
<button>
|
||||
Edit
|
||||
</button>
|
||||
<section>
|
||||
<ul>
|
||||
<section
|
||||
><h2> </h2
|
||||
><div class="background"
|
||||
></div
|
||||
></section>
|
||||
<li
|
||||
><h3> </h3
|
||||
><p> </p
|
||||
><div class="overlay"
|
||||
><div class="cursor">↕︎</div></div
|
||||
></li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<gaia-dialog-alert id="mine">List item clicked!</gaia-dialog-alert>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
95
gaia/apps/music/components/dom-scheduler/demo-app/js/app.js
Executable file
|
|
@ -0,0 +1,95 @@
|
|||
(function() {
|
||||
var debug = false;
|
||||
var headerHeight = 50;
|
||||
var maxItemCount = 28;
|
||||
|
||||
var endEvent = ('ontouchstart' in window) ? 'touchend' : 'mouseup';
|
||||
|
||||
window.addEventListener('load', function() {
|
||||
var listContainer = document.querySelector('section');
|
||||
|
||||
var source = new BaconSource();
|
||||
var list = new FastList(listContainer, source);
|
||||
var dialog = document.querySelector('gaia-dialog-alert');
|
||||
|
||||
function updateHeader() {
|
||||
return scheduler.mutation(function() {
|
||||
var h1 = document.querySelector('h1');
|
||||
h1.textContent = 'Main List (' + source.fullLength() + ')';
|
||||
h1.scrollTop; // flush
|
||||
});
|
||||
}
|
||||
updateHeader();
|
||||
|
||||
function clearNewIndicator() {
|
||||
var h1After = document.querySelector('#h1-after');
|
||||
|
||||
if (h1After.dataset.anim == 'reveal') {
|
||||
scheduler.transition(function() {
|
||||
h1After.dataset.anim = 'hide';
|
||||
}, h1After, 'animationend');
|
||||
}
|
||||
}
|
||||
listContainer.addEventListener('top-reached', clearNewIndicator);
|
||||
|
||||
function updateNewIndicator() {
|
||||
var h1After = document.querySelector('#h1-after');
|
||||
if (h1After.dataset.anim == 'reveal') {
|
||||
return;
|
||||
}
|
||||
|
||||
scheduler.transition(function() {
|
||||
h1After.dataset.anim = 'reveal';
|
||||
}, h1After, 'animationend');
|
||||
}
|
||||
listContainer.addEventListener('hidden-new-content', updateNewIndicator);
|
||||
|
||||
function openGaiaDialog(evt) {
|
||||
scheduler.mutation(function() {
|
||||
var detail = evt.detail;
|
||||
var li = source.getRecordAt(detail.index);
|
||||
dialog.textContent = li.title + ' item clicked!';
|
||||
dialog.open(detail.clickEvt);
|
||||
});
|
||||
}
|
||||
list.list.addEventListener('item-selected', openGaiaDialog);
|
||||
|
||||
function newContentHandler() {
|
||||
var newContent = {
|
||||
title: 'NEW Bacon ' + Date.now().toString().slice(7, -1),
|
||||
body: 'Turkey BLT please.'
|
||||
};
|
||||
|
||||
source.insertAtIndex(0, newContent);
|
||||
list.insertedAtIndex(0);
|
||||
|
||||
updateHeader();
|
||||
}
|
||||
|
||||
setInterval(newContentHandler, 15000);
|
||||
window.addEventListener('new-content', newContentHandler);
|
||||
|
||||
window.pushNewContent = function() {
|
||||
window.dispatchEvent(new CustomEvent('new-content'));
|
||||
};
|
||||
|
||||
var button = document.querySelector('button');
|
||||
button.addEventListener(endEvent, function() {
|
||||
Promise.all([toggleTransitioning(), list.toggleEditMode()])
|
||||
.then(updateText)
|
||||
.then(toggleTransitioning);
|
||||
});
|
||||
|
||||
function updateText(text) {
|
||||
return scheduler.mutation(function() {
|
||||
button.textContent = list.editing ? 'Exit' : 'Edit';
|
||||
});
|
||||
}
|
||||
|
||||
function toggleTransitioning() {
|
||||
return scheduler.feedback(function() {
|
||||
button.classList.toggle('transitioning');
|
||||
}, button, 'transitionend');
|
||||
}
|
||||
});
|
||||
})();
|
||||
175
gaia/apps/music/components/dom-scheduler/demo-app/js/bacon-source.js
Executable file
|
|
@ -0,0 +1,175 @@
|
|||
(function(exports) {
|
||||
var listSize = 1042;
|
||||
var sectionCount = 25;
|
||||
|
||||
var headerHeight = 32;
|
||||
var itemHeight = 88;
|
||||
|
||||
function makeContent(prefix, name) {
|
||||
return {
|
||||
title: prefix + ' Bacon ipsum dolor ' +
|
||||
Date.now().toString().slice(7, -1),
|
||||
body: 'Turkey pig boudin ' + name.toLowerCase() +
|
||||
' de mignon drums and all ' + prefix + '.'
|
||||
}
|
||||
}
|
||||
|
||||
exports.BaconSource = function BaconSource() {
|
||||
this.sections = [];
|
||||
this.contentMap = new Map();
|
||||
this._cachedLength = null;
|
||||
|
||||
for (var i = 0; i < sectionCount; i++) {
|
||||
this.sections.push('Section ' + i);
|
||||
}
|
||||
|
||||
this.sections.forEach(function(name) {
|
||||
var content = [];
|
||||
for (var i = 0; i < (Math.random() * listSize); i++) {
|
||||
content.push(makeContent(i, name));
|
||||
}
|
||||
this.contentMap.set(name, content);
|
||||
}, this);
|
||||
};
|
||||
|
||||
exports.BaconSource.prototype = {
|
||||
populateItem: function(item, i) {
|
||||
// Simulates the data source not being ready to populate
|
||||
/*if (parseInt(Date.now() / 10) % 8 === 0) {*/
|
||||
/*return new Promise(function(resolve, reject) {*/
|
||||
/*setTimeout(resolve, Math.random() * 1000);*/
|
||||
/*});*/
|
||||
/*}*/
|
||||
|
||||
var title = item.firstChild;
|
||||
var body = title.nextSibling;
|
||||
var record = this.getRecordAt(i);
|
||||
|
||||
title.firstChild.data = record.title;
|
||||
body.firstChild.data = record.body;
|
||||
},
|
||||
|
||||
getSections: function() {
|
||||
return this.contentMap.keys();
|
||||
},
|
||||
|
||||
sectionHeaderHeight: function() {
|
||||
return headerHeight;
|
||||
},
|
||||
|
||||
fullSectionHeight: function(key) {
|
||||
return this.contentMap.get(key).length * itemHeight;
|
||||
},
|
||||
|
||||
fullSectionLength: function(key) {
|
||||
return this.contentMap(key).length;
|
||||
},
|
||||
|
||||
getRecordAt: function(index) {
|
||||
for (var section of this.contentMap.values()) {
|
||||
if (index < section.length) {
|
||||
return section[index];
|
||||
}
|
||||
index -= section.length;
|
||||
}
|
||||
},
|
||||
|
||||
getSectionFor: function(index) {
|
||||
for (var entry of this.contentMap.entries()) {
|
||||
if (index < entry[1].length) {
|
||||
return entry[0];
|
||||
}
|
||||
index -= entry[1].length;
|
||||
}
|
||||
},
|
||||
|
||||
indexAtPosition: function(pos) {
|
||||
var index = 0;
|
||||
for (var section of this.contentMap.values()) {
|
||||
pos -= headerHeight;
|
||||
var sectionHeight = section.length * itemHeight;
|
||||
if (pos > sectionHeight) {
|
||||
pos -= sectionHeight;
|
||||
index += section.length;
|
||||
continue;
|
||||
}
|
||||
for (var item of section) {
|
||||
pos -= itemHeight;
|
||||
index++;
|
||||
if (pos <= 0 || index === this.fullLength() - 1) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
positionForIndex: function(index) {
|
||||
var top = 0;
|
||||
for (var section of this.contentMap.values()) {
|
||||
top += headerHeight;
|
||||
if (index < section.length) {
|
||||
top += index * itemHeight;
|
||||
return top;
|
||||
}
|
||||
index -= section.length;
|
||||
top += section.length * itemHeight;
|
||||
}
|
||||
},
|
||||
|
||||
fullLength: function() {
|
||||
if (this._cachedLength) {
|
||||
return this._cachedLength;
|
||||
}
|
||||
|
||||
var length = 0;
|
||||
for (var section of this.contentMap.values()) {
|
||||
length += section.length;
|
||||
}
|
||||
this._cachedLength = length;
|
||||
return length;
|
||||
},
|
||||
|
||||
itemHeight: function() {
|
||||
return itemHeight;
|
||||
},
|
||||
|
||||
fullHeight: function() {
|
||||
var height = 0;
|
||||
for (var section of this.contentMap.values()) {
|
||||
height += headerHeight + section.length * itemHeight;
|
||||
}
|
||||
return height;
|
||||
},
|
||||
|
||||
insertAtIndex: function(index, record, toSection) {
|
||||
this._cachedLength = null;
|
||||
for (var entry of this.contentMap.entries()) {
|
||||
if (index < entry[1].length || entry[0] === toSection) {
|
||||
return entry[1].splice(index, 0, record);
|
||||
}
|
||||
index -= entry[1].length;
|
||||
}
|
||||
},
|
||||
|
||||
replaceAtIndex: function(index, record) {
|
||||
for (var section of this.contentMap.values()) {
|
||||
if (index < section.length) {
|
||||
return section.splice(index, 1, record);
|
||||
}
|
||||
index -= section.length;
|
||||
}
|
||||
},
|
||||
|
||||
removeAtIndex: function(index) {
|
||||
this._cachedLength = null;
|
||||
|
||||
for (var section of this.contentMap.values()) {
|
||||
if (index < section.length) {
|
||||
return section.splice(index, 1)[0];
|
||||
}
|
||||
index -= section.length;
|
||||
}
|
||||
}
|
||||
};
|
||||
})(window);
|
||||
|
||||
14
gaia/apps/music/components/dom-scheduler/demo-app/manifest.webapp
Executable file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"name": "dom-scheduler-demo",
|
||||
"description": "A Hello World app",
|
||||
"launch_path": "/index.html",
|
||||
"icons": {
|
||||
"16": "/icons/icon16x16.png",
|
||||
"48": "/icons/icon48x48.png",
|
||||
"60": "/icons/icon60x60.png",
|
||||
"128": "/icons/icon128x128.png"
|
||||
},
|
||||
"type": "certified",
|
||||
"orientation": "default",
|
||||
"permissions": {}
|
||||
}
|
||||
46
gaia/apps/music/components/dom-scheduler/index.html
Executable file
|
|
@ -0,0 +1,46 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1">
|
||||
|
||||
<link rel="stylesheet" href="demo-app/css/h1.css">
|
||||
<link rel="stylesheet" href="demo-app/css/ul.css">
|
||||
<link rel="stylesheet" href="demo-app/css/app.css">
|
||||
|
||||
<script type="text/javascript" src="lib/dom-scheduler.js" async></script>
|
||||
<script src="bower_components/fast-list/fast-list.js" defer></script>
|
||||
|
||||
<script type="text/javascript" src="demo-app/js/bacon-source.js" async></script>
|
||||
<script type="text/javascript" src="page.js" async></script>
|
||||
|
||||
<title>Dom Scheduler Demo</title>
|
||||
</head>
|
||||
|
||||
<body class="theme-communications">
|
||||
<h1>
|
||||
Main List
|
||||
</h1>
|
||||
<div id="h1-after"></div>
|
||||
<button>
|
||||
Edit
|
||||
</button>
|
||||
<section>
|
||||
<ul>
|
||||
<section
|
||||
><h2> </h2
|
||||
><div class="background"
|
||||
></div
|
||||
></section>
|
||||
<li
|
||||
><h3> </h3
|
||||
><p> </p
|
||||
><div class="overlay"
|
||||
><div class="cursor">↕︎</div></div
|
||||
></li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
338
gaia/apps/music/components/dom-scheduler/lib/dom-scheduler.js
Executable file
|
|
@ -0,0 +1,338 @@
|
|||
/* global performance */
|
||||
(function(exports) {
|
||||
'use strict';
|
||||
|
||||
var debug = false;
|
||||
function log(str) {
|
||||
if (!debug) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('🎶 ', str);
|
||||
}
|
||||
|
||||
// The naive mode just executes all blocks right away
|
||||
// (useful for performance comparison).
|
||||
var naive = false;
|
||||
var naiveExec = function(block) {
|
||||
block();
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
var directProtectionWindow = 360;
|
||||
|
||||
var Scheduler = function() {
|
||||
this._directTracking = new Map();
|
||||
|
||||
this._ongoingTransitions = 0;
|
||||
this._queuedTransitions = [];
|
||||
|
||||
this._flushing = false;
|
||||
this._pendingMutations = [];
|
||||
};
|
||||
|
||||
Scheduler.prototype = {
|
||||
// *Direct* blocks should be used for direct manipulation use cases
|
||||
// (touchevents, scrollevents...).
|
||||
// They're exectuted in a requestAnimationFrame block and are protected
|
||||
// from mutations. Request might be cancelled by subsequent direct blocks if
|
||||
// the event loop gets too busy.
|
||||
attachDirect: function(elm, evtType, block) {
|
||||
var tracking = this._directTracking;
|
||||
if (!tracking.get(elm)) {
|
||||
tracking.set(elm, {});
|
||||
}
|
||||
var tElm = tracking.get(elm);
|
||||
|
||||
if (!(evtType in tElm)) {
|
||||
tElm[evtType] = {
|
||||
rafID: null,
|
||||
protectionTimeout: null,
|
||||
blocks: []
|
||||
};
|
||||
}
|
||||
var tEvt = tElm[evtType];
|
||||
|
||||
if (!tEvt.blocks.length) {
|
||||
elm.addEventListener(evtType, this);
|
||||
}
|
||||
|
||||
tEvt.blocks.push(block);
|
||||
},
|
||||
|
||||
detachDirect: function(elm, evtType, block) {
|
||||
var tEvt = this._trackingFor(elm, evtType);
|
||||
if (!tEvt) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (tEvt.rafID) {
|
||||
window.cancelAnimationFrame(tEvt.rafID);
|
||||
}
|
||||
|
||||
if (tEvt.protectionTimeout) {
|
||||
clearTimeout(tEvt.protectionTimeout);
|
||||
tEvt.protectionTimeout = null;
|
||||
this._stopProtectingDirect();
|
||||
}
|
||||
|
||||
var toRemove = tEvt.blocks.indexOf(block);
|
||||
if (toRemove === -1) {
|
||||
log('wrong detach block');
|
||||
return;
|
||||
}
|
||||
|
||||
tEvt.blocks.splice(toRemove, 1);
|
||||
|
||||
if (!tEvt.blocks.length) {
|
||||
elm.removeEventListener(evtType, this);
|
||||
var tElm = this._directTracking.get(elm);
|
||||
delete tElm[evtType];
|
||||
|
||||
if (Object.keys(tElm).length === 0) {
|
||||
this._directTracking.delete(elm);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_trackingFor: function(elm, evtType) {
|
||||
var tracking = this._directTracking;
|
||||
if (!tracking.get(elm)) {
|
||||
log('wrong detach element');
|
||||
return;
|
||||
}
|
||||
var tElm = tracking.get(elm);
|
||||
|
||||
if (!(evtType in tElm)) {
|
||||
log('wrong detach event');
|
||||
return;
|
||||
}
|
||||
return tElm[evtType];
|
||||
},
|
||||
|
||||
_shouldProtectDirect: function() {
|
||||
var tracking = this._directTracking;
|
||||
|
||||
for (var elm of tracking.keys()) {
|
||||
var tElm = tracking.get(elm);
|
||||
for (var evtType in tElm) {
|
||||
var tEvt = tElm[evtType];
|
||||
if (tEvt.protectionTimeout !== null) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
_stopProtectingDirect: function() {
|
||||
this._dequeueTransitions();
|
||||
this._flushMutations();
|
||||
if (debug) {
|
||||
console.timeEnd('protecting');
|
||||
}
|
||||
},
|
||||
|
||||
handleEvent: function(evt) {
|
||||
var tEvt = this._trackingFor(evt.currentTarget, evt.type);
|
||||
if (!tEvt) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (naive) {
|
||||
tEvt.blocks.forEach(function(block) {
|
||||
block(evt);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (tEvt.protectionTimeout) {
|
||||
clearTimeout(tEvt.protectionTimeout);
|
||||
} else {
|
||||
if (debug) {
|
||||
console.time('protecting');
|
||||
}
|
||||
}
|
||||
|
||||
tEvt.protectionTimeout = setTimeout((function() {
|
||||
tEvt.protectionTimeout = null;
|
||||
this._stopProtectingDirect();
|
||||
}).bind(this), directProtectionWindow);
|
||||
|
||||
if (tEvt.rafID) {
|
||||
window.cancelAnimationFrame(tEvt.rafID);
|
||||
}
|
||||
|
||||
tEvt.rafID = window.requestAnimationFrame(function() {
|
||||
tEvt.blocks.forEach(function(block) {
|
||||
var startDate;
|
||||
if (debug) {
|
||||
startDate = performance.now();
|
||||
}
|
||||
|
||||
block(evt);
|
||||
|
||||
if (debug) {
|
||||
var blockDuration = performance.now() - startDate;
|
||||
if (blockDuration > 16) {
|
||||
log('Direct block took more than a frame (' +
|
||||
blockDuration.toString() + 'ms)');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
// *Feedbacks* blocks have a built in 'transitionend' wait mechanism.
|
||||
// They're protected from mutation and will be delayed during a mutation
|
||||
// flush.
|
||||
// They have the same priority as `direct` blocks.
|
||||
//
|
||||
// -> Returns a promise fullfilled at the end of the transition for chaining
|
||||
feedback: function(block, elm, evt, timeout) {
|
||||
return this._transition(block, true, elm, evt, timeout);
|
||||
},
|
||||
|
||||
// *Transitions* blocks have a built in 'transitionend' wait mechanism.
|
||||
// They're protected from mutation and will be delayed during a mutation
|
||||
// flush.
|
||||
// They will also be delayed by `direct` blocks.
|
||||
//
|
||||
// -> Returns a promise fullfilled at the end of the transition for chaining
|
||||
transition: function(block, elm, evt, timeout) {
|
||||
return this._transition(block, false, elm, evt, timeout);
|
||||
},
|
||||
|
||||
_transition: function(block, feedback, elm, evt, timeout) {
|
||||
if (naive) {
|
||||
return naiveExec(block);
|
||||
}
|
||||
|
||||
timeout = timeout || 500;
|
||||
|
||||
return new Promise((function(resolve, reject) {
|
||||
var content = (function() {
|
||||
this._ongoingTransitions++;
|
||||
|
||||
block();
|
||||
|
||||
if (!elm || !evt) {
|
||||
this._ongoingTransitions--;
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
if (debug) {
|
||||
console.time('animating');
|
||||
}
|
||||
|
||||
var finishTimeout;
|
||||
|
||||
var done = (function() {
|
||||
clearTimeout(finishTimeout);
|
||||
elm.removeEventListener(evt, done);
|
||||
|
||||
this._ongoingTransitions--;
|
||||
if (debug) {
|
||||
console.timeEnd('animating');
|
||||
}
|
||||
|
||||
if (this._ongoingTransitions === 0) {
|
||||
setTimeout(this._flushMutations.bind(this));
|
||||
}
|
||||
|
||||
resolve();
|
||||
}).bind(this);
|
||||
|
||||
elm.addEventListener(evt, done);
|
||||
finishTimeout = setTimeout(function() {
|
||||
done();
|
||||
log('Transition block saved by a timeout of ' + timeout + ' ~ ' +
|
||||
elm.style.transition);
|
||||
}, timeout);
|
||||
}).bind(this);
|
||||
|
||||
if (this._flushing || (this._shouldProtectDirect() && !feedback)) {
|
||||
this._queuedTransitions.push(content);
|
||||
} else {
|
||||
content();
|
||||
}
|
||||
}).bind(this));
|
||||
},
|
||||
|
||||
_dequeueTransitions: function() {
|
||||
if (this._queuedTransitions.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._flushing || this._shouldProtectDirect()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var transitions = this._queuedTransitions;
|
||||
transitions.forEach(function(transition) {
|
||||
transition();
|
||||
});
|
||||
this._queuedTransitions = [];
|
||||
},
|
||||
|
||||
// *Mutations* blocks should be used to write to the DOM or perform
|
||||
// actions requiring a reflow that are not direct manipulations.
|
||||
// We shoud always aim for the document to be almost visually identical
|
||||
// _before_ and _after_ a mutation block.
|
||||
// Any big change in layout/size will cause a flash/jump.
|
||||
//
|
||||
// -> Returns a promise fullfilled after the reflow for chaining
|
||||
mutation: function(block) {
|
||||
if (naive) {
|
||||
return naiveExec(block);
|
||||
}
|
||||
|
||||
return new Promise((function(resolve, reject) {
|
||||
if (this._shouldProtectDirect() || this._ongoingTransitions > 0) {
|
||||
this._pendingMutations.push({
|
||||
block: block,
|
||||
resolve: resolve
|
||||
});
|
||||
} else {
|
||||
Promise.resolve()
|
||||
.then(block)
|
||||
.then(resolve);
|
||||
}
|
||||
}).bind(this));
|
||||
},
|
||||
|
||||
_flushMutations: function() {
|
||||
if (this._pendingMutations.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._shouldProtectDirect() || this._ongoingTransitions > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._flushing = true;
|
||||
|
||||
var fulfilments =
|
||||
this._pendingMutations
|
||||
.map(function(obj) { return obj.resolve; });
|
||||
|
||||
var mutations = this._pendingMutations;
|
||||
mutations.forEach(function(mutation) {
|
||||
mutation.block();
|
||||
});
|
||||
this._pendingMutations = [];
|
||||
this._flushing = false;
|
||||
|
||||
fulfilments.forEach(function(resolve) { resolve(); });
|
||||
|
||||
this._dequeueTransitions();
|
||||
}
|
||||
};
|
||||
|
||||
exports.DomScheduler = Scheduler;
|
||||
|
||||
// We only ever want there to be
|
||||
// one instance of the scheduler
|
||||
if (!exports.scheduler || typeof exports.scheduler.mutation !== 'function') Object.defineProperty(exports, 'scheduler', {value:new Scheduler(),configurable:true,writable:true});
|
||||
})(window);
|
||||
24
gaia/apps/music/components/dom-scheduler/package.json
Executable file
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"name": "dom-scheduler",
|
||||
"main": "lib/dom-scheduler.js",
|
||||
"devDependencies": {
|
||||
"jshint": "^2.7.0",
|
||||
"karma": "^0.12.31",
|
||||
"karma-firefox-launcher": "^0.1.4",
|
||||
"karma-chrome-launcher": "^0.1.4",
|
||||
"karma-mocha": "^0.1.10",
|
||||
"karma-sinon-chai": "^0.3.0"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "./node_modules/jshint/bin/jshint lib/dom-scheduler.js test/*.js",
|
||||
"test": "./node_modules/karma/bin/karma start test/karma.conf --single-run",
|
||||
"test-dev": "./node_modules/karma/bin/karma start test/karma.conf"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/etiennesegonzac/dom-scheduler.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/etiennesegonzac/dom-scheduler/issues"
|
||||
}
|
||||
}
|
||||