forked from platypush/platypush
Migrated music.snapcast UI
This commit is contained in:
parent
370a7d4c15
commit
7a7e00bea2
64 changed files with 1430 additions and 148 deletions
2
platypush/backend/http/dist/index.html
vendored
2
platypush/backend/http/dist/index.html
vendored
|
@ -1 +1 @@
|
||||||
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/favicon.ico"><title>platypush</title><link href="/static/css/chunk-076c5199.fc1132f4.css" rel="prefetch"><link href="/static/css/chunk-1293e286.fc1132f4.css" rel="prefetch"><link href="/static/css/chunk-14f3b6ed.fc1132f4.css" rel="prefetch"><link href="/static/css/chunk-24ff873d.25b446f2.css" rel="prefetch"><link href="/static/css/chunk-35b45d59.3285a5c5.css" rel="prefetch"><link href="/static/css/chunk-3d60f62e.4fa298b8.css" rel="prefetch"><link href="/static/css/chunk-4201fea8.ab45ee69.css" rel="prefetch"><link href="/static/css/chunk-45939517.ce9c914b.css" rel="prefetch"><link href="/static/css/chunk-4bbbb9a3.8b96bf08.css" rel="prefetch"><link href="/static/css/chunk-4bc2706b.1e09b17a.css" rel="prefetch"><link href="/static/css/chunk-545459d0.4806f03d.css" rel="prefetch"><link href="/static/css/chunk-59396623.5084b7e8.css" rel="prefetch"><link href="/static/css/chunk-62a3d08e.d329d923.css" rel="prefetch"><link href="/static/css/chunk-9684cd10.6046f7ac.css" rel="prefetch"><link href="/static/css/chunk-abbc1cdc.fc1132f4.css" rel="prefetch"><link href="/static/css/chunk-cb418146.1cc7af9d.css" rel="prefetch"><link href="/static/css/chunk-cd9a889e.b5ec8fac.css" rel="prefetch"><link href="/static/css/chunk-d8561e02.0d73779a.css" rel="prefetch"><link href="/static/css/chunk-e8078048.04620e86.css" rel="prefetch"><link href="/static/css/chunk-fa20b8a0.d7316f99.css" rel="prefetch"><link href="/static/js/chunk-076c5199.377e9834.js" rel="prefetch"><link href="/static/js/chunk-1293e286.eb2fa695.js" rel="prefetch"><link href="/static/js/chunk-14f3b6ed.d6fcafdc.js" rel="prefetch"><link href="/static/js/chunk-24ff873d.691c883d.js" rel="prefetch"><link href="/static/js/chunk-2d0cc2be.b3a583d9.js" rel="prefetch"><link href="/static/js/chunk-2d2091df.1e51ae4c.js" rel="prefetch"><link href="/static/js/chunk-2d21da1a.adf909a2.js" rel="prefetch"><link href="/static/js/chunk-2d237d41.3427f74b.js" rel="prefetch"><link href="/static/js/chunk-35b45d59.9a57c504.js" rel="prefetch"><link href="/static/js/chunk-3d60f62e.907e4050.js" rel="prefetch"><link href="/static/js/chunk-4201fea8.29361f0f.js" rel="prefetch"><link href="/static/js/chunk-45939517.c0034c6b.js" rel="prefetch"><link href="/static/js/chunk-4bbbb9a3.251fff37.js" rel="prefetch"><link href="/static/js/chunk-4bc2706b.38882fe9.js" rel="prefetch"><link href="/static/js/chunk-545459d0.da1ea7e5.js" rel="prefetch"><link href="/static/js/chunk-59396623.19b5fca7.js" rel="prefetch"><link href="/static/js/chunk-62a3d08e.17d3c86d.js" rel="prefetch"><link href="/static/js/chunk-9684cd10.7051bb65.js" rel="prefetch"><link href="/static/js/chunk-abbc1cdc.47491a05.js" rel="prefetch"><link href="/static/js/chunk-cb418146.7a824439.js" rel="prefetch"><link href="/static/js/chunk-cd9a889e.ec43fdb3.js" rel="prefetch"><link href="/static/js/chunk-d8561e02.1e366cb3.js" rel="prefetch"><link href="/static/js/chunk-e8078048.ce29b8d4.js" rel="prefetch"><link href="/static/js/chunk-fa20b8a0.78555b70.js" rel="prefetch"><link href="/static/css/app.9ee642c5.css" rel="preload" as="style"><link href="/static/css/chunk-vendors.5dad8b00.css" rel="preload" as="style"><link href="/static/js/app.1abebcd8.js" rel="preload" as="script"><link href="/static/js/chunk-vendors.30e3a6cb.js" rel="preload" as="script"><link href="/static/css/chunk-vendors.5dad8b00.css" rel="stylesheet"><link href="/static/css/app.9ee642c5.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but platypush doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="/static/js/chunk-vendors.30e3a6cb.js"></script><script src="/static/js/app.1abebcd8.js"></script></body></html>
|
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/favicon.ico"><title>platypush</title><link href="/static/css/chunk-076c5199.f186cc51.css" rel="prefetch"><link href="/static/css/chunk-0918db6a.44bbe779.css" rel="prefetch"><link href="/static/css/chunk-1293e286.f186cc51.css" rel="prefetch"><link href="/static/css/chunk-14f3b6ed.f186cc51.css" rel="prefetch"><link href="/static/css/chunk-21660230.75b51be7.css" rel="prefetch"><link href="/static/css/chunk-24ff873d.197de139.css" rel="prefetch"><link href="/static/css/chunk-35b45d59.b7730bd4.css" rel="prefetch"><link href="/static/css/chunk-4201fea8.42d666a4.css" rel="prefetch"><link href="/static/css/chunk-45557166.ab71816e.css" rel="prefetch"><link href="/static/css/chunk-45939517.4d7c2357.css" rel="prefetch"><link href="/static/css/chunk-4bbbb9a3.7294303f.css" rel="prefetch"><link href="/static/css/chunk-5841cc7d.d0bad316.css" rel="prefetch"><link href="/static/css/chunk-62a3d08e.c4e19f9e.css" rel="prefetch"><link href="/static/css/chunk-6c27e200.680ba1de.css" rel="prefetch"><link href="/static/css/chunk-9684cd10.43a25f0f.css" rel="prefetch"><link href="/static/css/chunk-9f884670.a8a2d99a.css" rel="prefetch"><link href="/static/css/chunk-abbc1cdc.f186cc51.css" rel="prefetch"><link href="/static/css/chunk-cb418146.678c9c97.css" rel="prefetch"><link href="/static/css/chunk-d0b841c2.5506a233.css" rel="prefetch"><link href="/static/css/chunk-d8561e02.7c71cffb.css" rel="prefetch"><link href="/static/css/chunk-e8078048.eda53677.css" rel="prefetch"><link href="/static/css/chunk-fa20b8a0.c233115f.css" rel="prefetch"><link href="/static/js/chunk-076c5199.377e9834.js" rel="prefetch"><link href="/static/js/chunk-0918db6a.cd3d5e70.js" rel="prefetch"><link href="/static/js/chunk-1293e286.eb2fa695.js" rel="prefetch"><link href="/static/js/chunk-14f3b6ed.d6fcafdc.js" rel="prefetch"><link href="/static/js/chunk-21660230.f7af277b.js" rel="prefetch"><link href="/static/js/chunk-24ff873d.691c883d.js" rel="prefetch"><link href="/static/js/chunk-2d0cc2be.b3a583d9.js" rel="prefetch"><link href="/static/js/chunk-2d2091df.1e51ae4c.js" rel="prefetch"><link href="/static/js/chunk-2d21da1a.adf909a2.js" rel="prefetch"><link href="/static/js/chunk-2d237d41.3427f74b.js" rel="prefetch"><link href="/static/js/chunk-35b45d59.9a57c504.js" rel="prefetch"><link href="/static/js/chunk-4201fea8.29361f0f.js" rel="prefetch"><link href="/static/js/chunk-45557166.4035bc76.js" rel="prefetch"><link href="/static/js/chunk-45939517.c0034c6b.js" rel="prefetch"><link href="/static/js/chunk-4bbbb9a3.251fff37.js" rel="prefetch"><link href="/static/js/chunk-5841cc7d.2979e631.js" rel="prefetch"><link href="/static/js/chunk-62a3d08e.17d3c86d.js" rel="prefetch"><link href="/static/js/chunk-6c27e200.bc387aa4.js" rel="prefetch"><link href="/static/js/chunk-9684cd10.7051bb65.js" rel="prefetch"><link href="/static/js/chunk-9f884670.aa7caaf0.js" rel="prefetch"><link href="/static/js/chunk-abbc1cdc.47491a05.js" rel="prefetch"><link href="/static/js/chunk-cb418146.7a824439.js" rel="prefetch"><link href="/static/js/chunk-d0b841c2.c0e1a82a.js" rel="prefetch"><link href="/static/js/chunk-d8561e02.1e366cb3.js" rel="prefetch"><link href="/static/js/chunk-e8078048.ce29b8d4.js" rel="prefetch"><link href="/static/js/chunk-fa20b8a0.78555b70.js" rel="prefetch"><link href="/static/css/app.e06a419a.css" rel="preload" as="style"><link href="/static/css/chunk-vendors.5dad8b00.css" rel="preload" as="style"><link href="/static/js/app.dfa8f1e3.js" rel="preload" as="script"><link href="/static/js/chunk-vendors.30e3a6cb.js" rel="preload" as="script"><link href="/static/css/chunk-vendors.5dad8b00.css" rel="stylesheet"><link href="/static/css/app.e06a419a.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but platypush doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="/static/js/chunk-vendors.30e3a6cb.js"></script><script src="/static/js/app.dfa8f1e3.js"></script></body></html>
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
11
platypush/backend/http/dist/static/css/chunk-45557166.ab71816e.css
vendored
Normal file
11
platypush/backend/http/dist/static/css/chunk-45557166.ab71816e.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
platypush/backend/http/dist/static/css/chunk-9f884670.a8a2d99a.css
vendored
Normal file
1
platypush/backend/http/dist/static/css/chunk-9f884670.a8a2d99a.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
15
platypush/backend/http/dist/static/css/chunk-fa20b8a0.c233115f.css
vendored
Normal file
15
platypush/backend/http/dist/static/css/chunk-fa20b8a0.c233115f.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
platypush/backend/http/dist/static/js/app.dfa8f1e3.js
vendored
Normal file
2
platypush/backend/http/dist/static/js/app.dfa8f1e3.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
platypush/backend/http/dist/static/js/app.dfa8f1e3.js.map
vendored
Normal file
1
platypush/backend/http/dist/static/js/app.dfa8f1e3.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
2
platypush/backend/http/dist/static/js/chunk-0918db6a.cd3d5e70.js
vendored
Normal file
2
platypush/backend/http/dist/static/js/chunk-0918db6a.cd3d5e70.js
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-0918db6a"],{"3cbf":function(e,t,r){},"4de4":function(e,t,r){"use strict";var n=r("23e7"),c=r("b727").filter,a=r("1dde"),o=r("ae40"),i=a("filter"),u=o("filter");n({target:"Array",proto:!0,forced:!i||!u},{filter:function(e){return c(this,e,arguments.length>1?arguments[1]:void 0)}})},5530:function(e,t,r){"use strict";r.d(t,"a",(function(){return a}));r("a4d3"),r("4de4"),r("4160"),r("e439"),r("dbb4"),r("b64b"),r("159b");function n(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function c(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function a(e){for(var t=1;t<arguments.length;t++){var r=null!=arguments[t]?arguments[t]:{};t%2?c(Object(r),!0).forEach((function(t){n(e,t,r[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(r)):c(Object(r)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(r,t))}))}return e}},a79d9:function(e,t,r){"use strict";var n=r("7a23"),c=Object(n["K"])("data-v-1502d8a8");Object(n["u"])("data-v-1502d8a8");var a={class:"torrent-container"},o={class:"header-container"},i={class:"view-container"};Object(n["s"])();var u=c((function(e,t,r,c,u,d){var b=Object(n["z"])("Header"),s=Object(n["z"])("TorrentView");return Object(n["r"])(),Object(n["e"])("div",a,[Object(n["h"])("div",o,[Object(n["h"])(b,{onTorrentAdd:t[1]||(t[1]=function(e){return d.download(e)})})]),Object(n["h"])("div",i,[Object(n["h"])(s,{"plugin-name":r.pluginName},null,8,["plugin-name"])])])})),d=(r("96cf"),r("1da1")),b=Object(n["K"])("data-v-6133f14d");Object(n["u"])("data-v-6133f14d");var s={class:"row"},f={class:"col-s-12 col-m-9 col-l-7 left side"},l={class:"search-box"};Object(n["s"])();var O=b((function(e,t,r,c,a,o){return Object(n["r"])(),Object(n["e"])("div",{class:["header",{"with-filter":e.filterVisible}]},[Object(n["h"])("div",s,[Object(n["h"])("div",f,[Object(n["h"])("form",{onSubmit:t[2]||(t[2]=Object(n["J"])((function(t){return e.$emit("torrent-add",a.torrentURL)}),["prevent"]))},[Object(n["h"])("label",l,[Object(n["I"])(Object(n["h"])("input",{type:"search",placeholder:"Add torrent URL","onUpdate:modelValue":t[1]||(t[1]=function(e){return a.torrentURL=e})},null,512),[[n["F"],a.torrentURL]])])],32)])])],2)})),p={name:"Header",emits:["torrent-add"],data:function(){return{torrentURL:""}}};r("f774");p.render=O,p.__scopeId="data-v-6133f14d";var j=p,v=r("0cc1"),h=r("3e54"),w={name:"Panel",components:{TorrentView:v["a"],Header:j},mixins:[h["a"]],props:{pluginName:{type:String,required:!0}},methods:{download:function(e){var t=this;return Object(d["a"])(regeneratorRuntime.mark((function r(){return regeneratorRuntime.wrap((function(r){while(1)switch(r.prev=r.next){case 0:return r.next=2,t.request("".concat(t.pluginName,".download"),{torrent:e});case 2:case"end":return r.stop()}}),r)})))()}}};r("b170");w.render=u,w.__scopeId="data-v-1502d8a8";t["a"]=w},b170:function(e,t,r){"use strict";r("3cbf")},ba28:function(e,t,r){},dbb4:function(e,t,r){var n=r("23e7"),c=r("83ab"),a=r("56ef"),o=r("fc6a"),i=r("06cf"),u=r("8418");n({target:"Object",stat:!0,sham:!c},{getOwnPropertyDescriptors:function(e){var t,r,n=o(e),c=i.f,d=a(n),b={},s=0;while(d.length>s)r=c(n,t=d[s++]),void 0!==r&&u(b,t,r);return b}})},e439:function(e,t,r){var n=r("23e7"),c=r("d039"),a=r("fc6a"),o=r("06cf").f,i=r("83ab"),u=c((function(){o(1)})),d=!i||u;n({target:"Object",stat:!0,forced:d,sham:!i},{getOwnPropertyDescriptor:function(e,t){return o(a(e),t)}})},f774:function(e,t,r){"use strict";r("ba28")}}]);
|
||||||
|
//# sourceMappingURL=chunk-0918db6a.cd3d5e70.js.map
|
1
platypush/backend/http/dist/static/js/chunk-0918db6a.cd3d5e70.js.map
vendored
Normal file
1
platypush/backend/http/dist/static/js/chunk-0918db6a.cd3d5e70.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
2
platypush/backend/http/dist/static/js/chunk-21660230.f7af277b.js
vendored
Normal file
2
platypush/backend/http/dist/static/js/chunk-21660230.f7af277b.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
platypush/backend/http/dist/static/js/chunk-21660230.f7af277b.js.map
vendored
Normal file
1
platypush/backend/http/dist/static/js/chunk-21660230.f7af277b.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
platypush/backend/http/dist/static/js/chunk-45557166.4035bc76.js
vendored
Normal file
2
platypush/backend/http/dist/static/js/chunk-45557166.4035bc76.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
platypush/backend/http/dist/static/js/chunk-45557166.4035bc76.js.map
vendored
Normal file
1
platypush/backend/http/dist/static/js/chunk-45557166.4035bc76.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -1,2 +0,0 @@
|
||||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-4bc2706b"],{"3cbf":function(e,t,n){},a79d9:function(e,t,n){"use strict";var r=n("7a23"),a=Object(r["K"])("data-v-1502d8a8");Object(r["u"])("data-v-1502d8a8");var c={class:"torrent-container"},o={class:"header-container"},i={class:"view-container"};Object(r["s"])();var d=a((function(e,t,n,a,d,u){var s=Object(r["z"])("Header"),b=Object(r["z"])("TorrentView");return Object(r["r"])(),Object(r["e"])("div",c,[Object(r["h"])("div",o,[Object(r["h"])(s,{onTorrentAdd:t[1]||(t[1]=function(e){return u.download(e)})})]),Object(r["h"])("div",i,[Object(r["h"])(b,{"plugin-name":n.pluginName},null,8,["plugin-name"])])])})),u=(n("96cf"),n("1da1")),s=Object(r["K"])("data-v-6133f14d");Object(r["u"])("data-v-6133f14d");var b={class:"row"},l={class:"col-s-12 col-m-9 col-l-7 left side"},f={class:"search-box"};Object(r["s"])();var j=s((function(e,t,n,a,c,o){return Object(r["r"])(),Object(r["e"])("div",{class:["header",{"with-filter":e.filterVisible}]},[Object(r["h"])("div",b,[Object(r["h"])("div",l,[Object(r["h"])("form",{onSubmit:t[2]||(t[2]=Object(r["J"])((function(t){return e.$emit("torrent-add",c.torrentURL)}),["prevent"]))},[Object(r["h"])("label",f,[Object(r["I"])(Object(r["h"])("input",{type:"search",placeholder:"Add torrent URL","onUpdate:modelValue":t[1]||(t[1]=function(e){return c.torrentURL=e})},null,512),[[r["F"],c.torrentURL]])])],32)])])],2)})),p={name:"Header",emits:["torrent-add"],data:function(){return{torrentURL:""}}};n("f774");p.render=j,p.__scopeId="data-v-6133f14d";var O=p,v=n("0cc1"),h=n("3e54"),m={name:"Panel",components:{TorrentView:v["a"],Header:O},mixins:[h["a"]],props:{pluginName:{type:String,required:!0}},methods:{download:function(e){var t=this;return Object(u["a"])(regeneratorRuntime.mark((function n(){return regeneratorRuntime.wrap((function(n){while(1)switch(n.prev=n.next){case 0:return n.next=2,t.request("".concat(t.pluginName,".download"),{torrent:e});case 2:case"end":return n.stop()}}),n)})))()}}};n("b170");m.render=d,m.__scopeId="data-v-1502d8a8";t["a"]=m},b170:function(e,t,n){"use strict";n("3cbf")},ba28:function(e,t,n){},f774:function(e,t,n){"use strict";n("ba28")}}]);
|
|
||||||
//# sourceMappingURL=chunk-4bc2706b.38882fe9.js.map
|
|
File diff suppressed because one or more lines are too long
|
@ -1,2 +0,0 @@
|
||||||
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-545459d0"],{8285:function(e,n,u){"use strict";var t=u("7a23"),o=Object(t["K"])("data-v-12a0983b"),i=o((function(e,n,u,o,i,a){return Object(t["r"])(),Object(t["e"])("label",null,[Object(t["h"])("input",{class:"slider",type:"range",min:u.range[0],max:u.range[1],value:u.value,disabled:u.disabled,onChange:n[1]||(n[1]=function(n){return e.$emit("input",n)}),onMouseup:n[2]||(n[2]=function(n){return e.$emit("mouseup",n)}),onInput:n[3]||(n[3]=function(n){return e.$emit("input",n)}),onMousedown:n[4]||(n[4]=function(n){return e.$emit("mousedown",n)}),onTouch:n[5]||(n[5]=function(n){return e.$emit("input",n)}),onTouchstart:n[6]||(n[6]=function(n){return e.$emit("mousedown",n)}),onTouchend:n[7]||(n[7]=function(n){return e.$emit("mouseup",n)})},null,40,["min","max","value","disabled"])])})),a=(u("a9e3"),{name:"Slider",emits:["input","mouseup","mousedown"],props:{value:{type:Number},disabled:{type:Boolean,default:!1},range:{type:Array,default:function(){return[0,100]}}}});u("ee52");a.render=i,a.__scopeId="data-v-12a0983b";n["a"]=a},e1773:function(e,n,u){},ee52:function(e,n,u){"use strict";u("e1773")}}]);
|
|
||||||
//# sourceMappingURL=chunk-545459d0.da1ea7e5.js.map
|
|
|
@ -1 +0,0 @@
|
||||||
{"version":3,"sources":["webpack:///./src/components/elements/Slider.vue","webpack:///./src/components/elements/Slider.vue?7dba","webpack:///./src/components/elements/Slider.vue?5806"],"names":["class","type","min","range","max","value","disabled","$emit","$event","name","emits","props","Number","Boolean","default","Array","render","__scopeId"],"mappings":"uNACE,eAKQ,cAJN,eAGqF,SAH9EA,MAAM,SAASC,KAAK,QAASC,IAAK,EAAAC,MAAK,GAAMC,IAAK,EAAAD,MAAK,GAAME,MAAO,EAAAA,MAAQC,SAAU,EAAAA,SACrF,SAAM,+BAAE,EAAAC,MAAK,QAAUC,KAAU,UAAO,+BAAE,EAAAD,MAAK,UAAYC,KAAU,QAAK,+BAAE,EAAAD,MAAK,QAAUC,KAC3F,YAAS,+BAAE,EAAAD,MAAK,YAAcC,KAAU,QAAK,+BAAE,EAAAD,MAAK,QAAUC,KAC9D,aAAU,+BAAE,EAAAD,MAAK,YAAcC,KAAU,WAAQ,+BAAE,EAAAD,MAAK,UAAYC,M,+CAKjE,G,UAAA,CACbC,KAAM,SACNC,MAAO,CAAC,QAAS,UAAW,aAC5BC,MAAO,CACLN,MAAO,CACLJ,KAAMW,QAGRN,SAAU,CACRL,KAAMY,QACNC,SAAS,GAGXX,MAAO,CACLF,KAAMc,MACND,QAAS,iBAAM,CAAC,EAAG,U,UCpBzB,EAAOE,OAAS,EAChB,EAAOC,UAAY,kBAEJ,U,0DCRf","file":"static/js/chunk-545459d0.da1ea7e5.js","sourcesContent":["<template>\n <label>\n <input class=\"slider\" type=\"range\" :min=\"range[0]\" :max=\"range[1]\" :value=\"value\" :disabled=\"disabled\"\n @change=\"$emit('input', $event)\" @mouseup=\"$emit('mouseup', $event)\" @input=\"$emit('input', $event)\"\n @mousedown=\"$emit('mousedown', $event)\" @touch=\"$emit('input', $event)\"\n @touchstart=\"$emit('mousedown', $event)\" @touchend=\"$emit('mouseup', $event)\">\n </label>\n</template>\n\n<script>\nexport default {\n name: \"Slider\",\n emits: ['input', 'mouseup', 'mousedown'],\n props: {\n value: {\n type: Number,\n },\n\n disabled: {\n type: Boolean,\n default: false,\n },\n\n range: {\n type: Array,\n default: () => [0, 100],\n },\n },\n}\n</script>\n\n<style lang=\"scss\" scoped>\n.slider {\n @include appearance(none);\n @include transition(opacity .2s);\n width: 100%;\n height: 1em;\n border-radius: 0.33em;\n background: $slider-bg;\n outline: none;\n\n @mixin slider-thumb {\n @include appearance(none);\n width: 1.5em;\n height: 1.5em;\n border-radius: 50%;\n border: 0;\n background: $slider-thumb-bg;\n cursor: pointer;\n }\n\n &::-webkit-slider-thumb { @include slider-thumb; }\n &::-moz-range-thumb { @include slider-thumb; }\n &::-moz-range-track { @include appearance(none); }\n\n &::-webkit-progress-value,\n &::-moz-range-progress {\n background: $slider-progress-bg;\n height: 1em;\n }\n\n &[disabled] {\n &::-webkit-progress-value,\n &::-moz-range-progress {\n background: none;\n }\n\n &::-webkit-slider-thumb,\n &::-moz-range-thumb {\n display: none;\n width: 0;\n }\n }\n}\n</style>","import { render } from \"./Slider.vue?vue&type=template&id=12a0983b&scoped=true&bindings={\\\"value\\\":\\\"props\\\",\\\"disabled\\\":\\\"props\\\",\\\"range\\\":\\\"props\\\"}\"\nimport script from \"./Slider.vue?vue&type=script&lang=js\"\nexport * from \"./Slider.vue?vue&type=script&lang=js\"\n\nimport \"./Slider.vue?vue&type=style&index=0&id=12a0983b&lang=scss&scoped=true\"\nscript.render = render\nscript.__scopeId = \"data-v-12a0983b\"\n\nexport default script","export * from \"-!../../../node_modules/mini-css-extract-plugin/dist/loader.js??ref--8-oneOf-1-0!../../../node_modules/css-loader/dist/cjs.js??ref--8-oneOf-1-1!../../../node_modules/vue-loader-v16/dist/stylePostLoader.js!../../../node_modules/postcss-loader/src/index.js??ref--8-oneOf-1-2!../../../node_modules/sass-loader/dist/cjs.js??ref--8-oneOf-1-3!../../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../../node_modules/vue-loader-v16/dist/index.js??ref--0-1!./Slider.vue?vue&type=style&index=0&id=12a0983b&lang=scss&scoped=true\""],"sourceRoot":""}
|
|
2
platypush/backend/http/dist/static/js/chunk-5841cc7d.2979e631.js
vendored
Normal file
2
platypush/backend/http/dist/static/js/chunk-5841cc7d.2979e631.js
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-5841cc7d"],{"4de4":function(e,t,n){"use strict";var r=n("23e7"),o=n("b727").filter,u=n("1dde"),i=n("ae40"),c=u("filter"),a=i("filter");r({target:"Array",proto:!0,forced:!c||!a},{filter:function(e){return o(this,e,arguments.length>1?arguments[1]:void 0)}})},5530:function(e,t,n){"use strict";n.d(t,"a",(function(){return u}));n("a4d3"),n("4de4"),n("4160"),n("e439"),n("dbb4"),n("b64b"),n("159b");function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function o(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function u(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?o(Object(n),!0).forEach((function(t){r(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):o(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}},8285:function(e,t,n){"use strict";var r=n("7a23"),o=Object(r["K"])("data-v-12a0983b"),u=o((function(e,t,n,o,u,i){return Object(r["r"])(),Object(r["e"])("label",null,[Object(r["h"])("input",{class:"slider",type:"range",min:n.range[0],max:n.range[1],value:n.value,disabled:n.disabled,onChange:t[1]||(t[1]=function(t){return e.$emit("input",t)}),onMouseup:t[2]||(t[2]=function(t){return e.$emit("mouseup",t)}),onInput:t[3]||(t[3]=function(t){return e.$emit("input",t)}),onMousedown:t[4]||(t[4]=function(t){return e.$emit("mousedown",t)}),onTouch:t[5]||(t[5]=function(t){return e.$emit("input",t)}),onTouchstart:t[6]||(t[6]=function(t){return e.$emit("mousedown",t)}),onTouchend:t[7]||(t[7]=function(t){return e.$emit("mouseup",t)})},null,40,["min","max","value","disabled"])])})),i=(n("a9e3"),{name:"Slider",emits:["input","mouseup","mousedown"],props:{value:{type:Number},disabled:{type:Boolean,default:!1},range:{type:Array,default:function(){return[0,100]}}}});n("ee52");i.render=u,i.__scopeId="data-v-12a0983b";t["a"]=i},dbb4:function(e,t,n){var r=n("23e7"),o=n("83ab"),u=n("56ef"),i=n("fc6a"),c=n("06cf"),a=n("8418");r({target:"Object",stat:!0,sham:!o},{getOwnPropertyDescriptors:function(e){var t,n,r=i(e),o=c.f,f=u(r),s={},b=0;while(f.length>b)n=o(r,t=f[b++]),void 0!==n&&a(s,t,n);return s}})},e1773:function(e,t,n){},e439:function(e,t,n){var r=n("23e7"),o=n("d039"),u=n("fc6a"),i=n("06cf").f,c=n("83ab"),a=o((function(){i(1)})),f=!c||a;r({target:"Object",stat:!0,forced:f,sham:!c},{getOwnPropertyDescriptor:function(e,t){return i(u(e),t)}})},ee52:function(e,t,n){"use strict";n("e1773")}}]);
|
||||||
|
//# sourceMappingURL=chunk-5841cc7d.2979e631.js.map
|
1
platypush/backend/http/dist/static/js/chunk-5841cc7d.2979e631.js.map
vendored
Normal file
1
platypush/backend/http/dist/static/js/chunk-5841cc7d.2979e631.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
platypush/backend/http/dist/static/js/chunk-6c27e200.bc387aa4.js
vendored
Normal file
2
platypush/backend/http/dist/static/js/chunk-6c27e200.bc387aa4.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
platypush/backend/http/dist/static/js/chunk-6c27e200.bc387aa4.js.map
vendored
Normal file
1
platypush/backend/http/dist/static/js/chunk-6c27e200.bc387aa4.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
2
platypush/backend/http/dist/static/js/chunk-9f884670.aa7caaf0.js
vendored
Normal file
2
platypush/backend/http/dist/static/js/chunk-9f884670.aa7caaf0.js
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-9f884670"],{"0279":function(e,t,a){"use strict";var c=a("7a23"),n=Object(c["K"])("data-v-8fae7678");Object(c["u"])("data-v-8fae7678");var s=Object(c["h"])("div",{class:"switch"},[Object(c["h"])("div",{class:"dot"})],-1),i={class:"label"};Object(c["s"])();var o=n((function(e,t,a,n,o,l){return Object(c["r"])(),Object(c["e"])("div",{class:["power-switch",{disabled:a.disabled}],onClick:t[1]||(t[1]=function(){return l.onInput.apply(l,arguments)})},[Object(c["h"])("input",{type:"checkbox",checked:a.value},null,8,["checked"]),Object(c["h"])("label",null,[s,Object(c["h"])("span",i,[Object(c["y"])(e.$slots,"default")])])],2)})),l={name:"ToggleSwitch",emits:["input"],props:{value:{type:Boolean,default:!1},disabled:{type:Boolean,default:!1}},methods:{onInput:function(e){if(e.stopPropagation(),this.disabled)return!1;this.$emit("input",e)}}};a("5b0a");l.render=o,l.__scopeId="data-v-8fae7678";t["a"]=l},"5b0a":function(e,t,a){"use strict";a("7ef9")},"7ef9":function(e,t,a){}}]);
|
||||||
|
//# sourceMappingURL=chunk-9f884670.aa7caaf0.js.map
|
1
platypush/backend/http/dist/static/js/chunk-9f884670.aa7caaf0.js.map
vendored
Normal file
1
platypush/backend/http/dist/static/js/chunk-9f884670.aa7caaf0.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -18,6 +18,9 @@
|
||||||
"music.mpd": {
|
"music.mpd": {
|
||||||
"class": "fas fa-music"
|
"class": "fas fa-music"
|
||||||
},
|
},
|
||||||
|
"music.snapcast": {
|
||||||
|
"class": "fa fa-volume-up"
|
||||||
|
},
|
||||||
"torrent": {
|
"torrent": {
|
||||||
"class": "fa fa-magnet"
|
"class": "fa fa-magnet"
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,107 @@
|
||||||
|
<template>
|
||||||
|
<div class="row client" :class="{offline: !connected}">
|
||||||
|
<div class="col-s-12 col-m-3 name" v-text="config.name?.length ? config.name : host.name"
|
||||||
|
@click="$emit('modal-show', {type: 'client', client: id, group: groupId, host: server.name})">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-s-12 col-m-9 controls">
|
||||||
|
<div class="col-10 slider-container">
|
||||||
|
<Slider :range="[0, 100]" :value="config.volume.percent"
|
||||||
|
@mouseup="$emit('volume-change', {host: server.name, client: id, volume: $event.target.value})" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-2 switch pull-right">
|
||||||
|
<ToggleSwitch :value="!config.volume.muted"
|
||||||
|
@input="$emit('mute-toggle', {host: server.name, client: id, muted: !config.volume.muted})" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import ToggleSwitch from "@/components/elements/ToggleSwitch";
|
||||||
|
import Slider from "@/components/elements/Slider";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Client",
|
||||||
|
components: {Slider, ToggleSwitch},
|
||||||
|
emits: ['volume-change', 'mute-toggle', 'modal-show'],
|
||||||
|
|
||||||
|
props: {
|
||||||
|
config: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
connected: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
host: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
id: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
groupId: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
lastSeen: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
|
||||||
|
snapclient: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
server: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.client {
|
||||||
|
@include until($tablet) {
|
||||||
|
flex-direction: column;
|
||||||
|
border-bottom: $default-border;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name, .controls {
|
||||||
|
@include until($tablet) {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.offline {
|
||||||
|
color: $disabled-fg;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: $hover-bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
@include until($tablet) {
|
||||||
|
padding-bottom: .5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: $default-hover-fg;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,91 @@
|
||||||
|
<template>
|
||||||
|
<div class="group">
|
||||||
|
<div class="head">
|
||||||
|
<div class="col-10 name" @click="$emit('modal-show', {type: 'group', group: id, host: server.name})">
|
||||||
|
<i class="icon fa" :class="{'fa-play': stream.status === 'playing', 'fa-stop': stream.status !== 'playing'}"></i>
|
||||||
|
{{ name || stream.id || id }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-2 switch pull-right">
|
||||||
|
<ToggleSwitch :value="!muted"
|
||||||
|
@input="$emit('group-mute-toggle', {host: server.name, group: id, muted: !muted})" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="body">
|
||||||
|
<Client v-for="client in clients" :key="client.id"
|
||||||
|
:config="client.config"
|
||||||
|
:connected="client.connected"
|
||||||
|
:server="server"
|
||||||
|
:host="client.host"
|
||||||
|
:groupId="id"
|
||||||
|
:id="client.id"
|
||||||
|
:lastSeen="client.lastSeen"
|
||||||
|
:snapclient="client.snapclient"
|
||||||
|
@modal-show="$emit('modal-show', $event)"
|
||||||
|
@volume-change="$emit('client-volume-change', $event)"
|
||||||
|
@mute-toggle="$emit('client-mute-toggle', $event)" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import ToggleSwitch from "@/components/elements/ToggleSwitch";
|
||||||
|
import Client from "@/components/panels/MusicSnapcast/Client";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Group",
|
||||||
|
components: {Client, ToggleSwitch},
|
||||||
|
emits: ['group-mute-toggle', 'modal-show', 'client-volume-change', 'client-mute-toggle'],
|
||||||
|
props: {
|
||||||
|
id: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
clients: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
|
||||||
|
muted: {
|
||||||
|
type: Boolean,
|
||||||
|
},
|
||||||
|
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
stream: {
|
||||||
|
type: Object,
|
||||||
|
},
|
||||||
|
|
||||||
|
server: {
|
||||||
|
type: Object,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.group {
|
||||||
|
.head {
|
||||||
|
display: flex;
|
||||||
|
background: $default-bg-4;
|
||||||
|
border-top: $default-border-2;
|
||||||
|
border-bottom: $default-border-2;
|
||||||
|
border-radius: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: $default-hover-fg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.head,
|
||||||
|
.client {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 1em .5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,104 @@
|
||||||
|
<template>
|
||||||
|
<div class="host">
|
||||||
|
<div class="header">
|
||||||
|
<div class="col-10 name" @click="$emit('modal-show', {type: 'host', host: server.host.name})">
|
||||||
|
<i class="icon fa fa-server"></i>
|
||||||
|
{{ server.host.name }}
|
||||||
|
</div>
|
||||||
|
<div class="col-2 buttons pull-right">
|
||||||
|
<button type="button" @click="collapsed = !collapsed">
|
||||||
|
<i class="icon fa" :class="{'fa-chevron-up': !collapsed, 'fa-chevron-down': collapsed}"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="group-container" v-if="!collapsed">
|
||||||
|
<Group v-for="(group, id) in groups" :key="id"
|
||||||
|
:id="group.id"
|
||||||
|
:name="group.name"
|
||||||
|
:server="server.host"
|
||||||
|
:muted="group.muted"
|
||||||
|
:clients="group.clients"
|
||||||
|
:stream="streams[group.stream_id]"
|
||||||
|
@modal-show="$emit('modal-show', $event)"
|
||||||
|
@group-mute-toggle="$emit('group-mute-toggle', $event)"
|
||||||
|
@client-mute-toggle="$emit('client-mute-toggle', $event)"
|
||||||
|
@client-volume-change="$emit('client-volume-change', $event)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Group from "@/components/panels/MusicSnapcast/Group";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Host",
|
||||||
|
emits: ['modal-show', 'group-mute-toggle', 'client-mute-toggle', 'client-volume-change'],
|
||||||
|
components: {Group},
|
||||||
|
|
||||||
|
props: {
|
||||||
|
groups: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
|
||||||
|
server: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
|
||||||
|
streams: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
collapsed: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.host {
|
||||||
|
width: 95%;
|
||||||
|
max-width: 1000px;
|
||||||
|
margin: 1em auto;
|
||||||
|
border: $default-border-2;
|
||||||
|
border-radius: .5em;
|
||||||
|
box-shadow: $border-shadow-bottom-right;
|
||||||
|
background: $default-bg-2;
|
||||||
|
|
||||||
|
.header {
|
||||||
|
padding: .5em;
|
||||||
|
background: $default-bg-5;
|
||||||
|
border-bottom: $default-border-2;
|
||||||
|
border-radius: .5em .5em 0 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.name {
|
||||||
|
text-transform: uppercase;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: $default-hover-fg;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
background: none;
|
||||||
|
&:hover { color: $default-hover-fg; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,500 @@
|
||||||
|
<template>
|
||||||
|
<div class="music-snapcast-container">
|
||||||
|
<Loading v-if="loading" />
|
||||||
|
|
||||||
|
<div class="info">
|
||||||
|
<Modal title="Server info" ref="modalHost">
|
||||||
|
<ModalHost :info="hosts[selectedHost]" v-if="selectedHost" />
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info">
|
||||||
|
<Modal title="Group info" ref="modalGroup">
|
||||||
|
<ModalGroup :group="hosts[selectedHost].groups[selectedGroup]" :streams="hosts[selectedHost].streams"
|
||||||
|
:clients="clientsByHost[selectedHost]" :loading="loading" @add-client="addClientToGroup"
|
||||||
|
@remove-client="removeClientFromGroup" @stream-change="streamChange"
|
||||||
|
@rename-group="renameGroup($event)" v-if="selectedGroup" />
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info">
|
||||||
|
<Modal title="Client info" ref="modalClient">
|
||||||
|
<ModalClient :client="hosts[selectedHost].groups[selectedGroup].clients[selectedClient]" :loading="loading"
|
||||||
|
@remove-client="removeClient" @rename-client="renameClient($event)" v-if="selectedClient" />
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Host v-for="(host, id) in hosts" :key="id"
|
||||||
|
:server="host.server"
|
||||||
|
:streams="host.streams"
|
||||||
|
:groups="host.groups"
|
||||||
|
@group-mute-toggle="groupMute($event)"
|
||||||
|
@client-mute-toggle="clientMute($event)"
|
||||||
|
@client-volume-change="clientSetVolume($event)"
|
||||||
|
@modal-show="onModalShow($event)" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Modal from "@/components/Modal";
|
||||||
|
import Utils from "@/Utils";
|
||||||
|
import Host from "@/components/panels/MusicSnapcast/Host";
|
||||||
|
import ModalHost from "@/components/panels/MusicSnapcast/modals/Host";
|
||||||
|
import ModalGroup from "@/components/panels/MusicSnapcast/modals/Group";
|
||||||
|
import ModalClient from "@/components/panels/MusicSnapcast/modals/Client";
|
||||||
|
import Loading from "@/components/Loading";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "MusicSnapcast",
|
||||||
|
mixins: [Utils],
|
||||||
|
components: {Loading, Modal, Host, ModalHost, ModalGroup, ModalClient},
|
||||||
|
|
||||||
|
data: function() {
|
||||||
|
return {
|
||||||
|
loading: false,
|
||||||
|
hosts: {},
|
||||||
|
ports: {},
|
||||||
|
selectedHost: null,
|
||||||
|
selectedGroup: null,
|
||||||
|
selectedClient: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
clientsByHost() {
|
||||||
|
return Object.entries(this.hosts).reduce((hosts, [name, info]) => {
|
||||||
|
hosts[name] = {}
|
||||||
|
|
||||||
|
Object.values(info.groups).forEach((group) => {
|
||||||
|
Object.entries(group.clients).forEach(([clientId, client]) => {
|
||||||
|
hosts[name][clientId] = client
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return hosts
|
||||||
|
}, {})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
parseServerStatus(status) {
|
||||||
|
status.server.host.port = this.ports[status.server.host.name]
|
||||||
|
this.hosts[status.server.host.name] = {
|
||||||
|
...status,
|
||||||
|
groups: status.groups.map((group) => {
|
||||||
|
return {
|
||||||
|
...group,
|
||||||
|
clients: group.clients.reduce((clients, client) => {
|
||||||
|
clients[client.id] = client
|
||||||
|
return clients
|
||||||
|
}, {}),
|
||||||
|
}
|
||||||
|
}).reduce((groups, group) => {
|
||||||
|
groups[group.id] = group
|
||||||
|
return groups
|
||||||
|
}, {}),
|
||||||
|
|
||||||
|
streams: status.streams.reduce((streams, stream) => {
|
||||||
|
streams[stream.id] = stream
|
||||||
|
return streams
|
||||||
|
}, {}),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async refresh() {
|
||||||
|
this.loading = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const hosts = await this.request('music.snapcast.get_backend_hosts')
|
||||||
|
const statuses = await Promise.all(Object.keys(hosts).map(
|
||||||
|
async (host) => this.request('music.snapcast.status', {host: host, port: hosts[host]})
|
||||||
|
))
|
||||||
|
|
||||||
|
this.hosts = {}
|
||||||
|
statuses.forEach((status) => {
|
||||||
|
this.ports[status.server.host.name] = hosts[status.server.host.name]
|
||||||
|
this.parseServerStatus(status)
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async refreshHost(host) {
|
||||||
|
if (!(host in this.hosts))
|
||||||
|
return
|
||||||
|
|
||||||
|
this.parseServerStatus(await this.request('music.snapcast.status', {
|
||||||
|
host: host,
|
||||||
|
port: this.ports[host]
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
|
||||||
|
async addClientToGroup(clientId) {
|
||||||
|
this.loading = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!this.selectedHost || !this.selectedGroup || !(clientId in this.clientsByHost[this.selectedHost]))
|
||||||
|
return
|
||||||
|
|
||||||
|
const clients = [...new Set([clientId,
|
||||||
|
...Object.keys(this.hosts[this.selectedHost].groups[this.selectedGroup].clients)])]
|
||||||
|
|
||||||
|
await this.request('music.snapcast.group_set_clients', {
|
||||||
|
host: this.selectedHost,
|
||||||
|
port: this.ports[this.selectedHost],
|
||||||
|
group: this.selectedGroup,
|
||||||
|
clients: clients,
|
||||||
|
})
|
||||||
|
|
||||||
|
await this.refreshHost(this.selectedHost)
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async removeClientFromGroup(clientId) {
|
||||||
|
this.loading = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!this.selectedHost || !this.selectedGroup || !(clientId in this.clientsByHost[this.selectedHost]))
|
||||||
|
return
|
||||||
|
|
||||||
|
const clients = new Set([...Object.keys(this.hosts[this.selectedHost].groups[this.selectedGroup].clients)])
|
||||||
|
if (!clients.has(clientId))
|
||||||
|
return
|
||||||
|
|
||||||
|
clients.delete(clientId)
|
||||||
|
|
||||||
|
await this.request('music.snapcast.group_set_clients', {
|
||||||
|
host: this.selectedHost,
|
||||||
|
port: this.ports[this.selectedHost],
|
||||||
|
group: this.selectedGroup,
|
||||||
|
clients: [...clients],
|
||||||
|
})
|
||||||
|
|
||||||
|
await this.refreshHost(this.selectedHost)
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async renameGroup(name) {
|
||||||
|
this.loading = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!this.selectedHost || !this.selectedGroup)
|
||||||
|
return
|
||||||
|
|
||||||
|
await this.request('music.snapcast.set_group_name', {
|
||||||
|
host: this.selectedHost,
|
||||||
|
port: this.ports[this.selectedHost],
|
||||||
|
group: this.selectedGroup,
|
||||||
|
name: name,
|
||||||
|
})
|
||||||
|
|
||||||
|
await this.refreshHost(this.selectedHost)
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async renameClient(name) {
|
||||||
|
this.loading = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!this.selectedHost || !this.selectedClient)
|
||||||
|
return
|
||||||
|
|
||||||
|
await this.request('music.snapcast.set_client_name', {
|
||||||
|
host: this.selectedHost,
|
||||||
|
port: this.ports[this.selectedHost],
|
||||||
|
client: this.selectedClient,
|
||||||
|
name: name,
|
||||||
|
})
|
||||||
|
|
||||||
|
await this.refreshHost(this.selectedHost)
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async removeClient() {
|
||||||
|
this.loading = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!(this.selectedHost && this.selectedClient))
|
||||||
|
return
|
||||||
|
|
||||||
|
await this.request('music.snapcast.delete_client', {
|
||||||
|
host: this.selectedHost,
|
||||||
|
port: this.ports[this.selectedHost],
|
||||||
|
client: this.selectedClient,
|
||||||
|
})
|
||||||
|
|
||||||
|
this.$refs.modalClient.close()
|
||||||
|
await this.refreshHost(this.selectedHost)
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async streamChange(streamId) {
|
||||||
|
this.loading = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.request('music.snapcast.group_set_stream', {
|
||||||
|
host: this.selectedHost,
|
||||||
|
port: this.ports[this.selectedHost],
|
||||||
|
group: this.selectedGroup,
|
||||||
|
stream_id: streamId,
|
||||||
|
})
|
||||||
|
|
||||||
|
await this.refreshHost(this.selectedHost)
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onClientUpdate(event) {
|
||||||
|
Object.keys(this.hosts[event.host].groups).forEach((groupId) => {
|
||||||
|
if (event.client.id in this.hosts[event.host].groups[groupId].clients) {
|
||||||
|
this.hosts[event.host].groups[groupId].clients[event.client.id] = event.client
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
onGroupStreamChange(event) {
|
||||||
|
this.hosts[event.host].groups[event.group].stream_id = event.stream
|
||||||
|
},
|
||||||
|
|
||||||
|
onServerUpdate(event) {
|
||||||
|
this.parseServerStatus(event.server)
|
||||||
|
},
|
||||||
|
|
||||||
|
onStreamUpdate(event) {
|
||||||
|
this.hosts[event.host].streams[event.stream.id] = event.stream
|
||||||
|
},
|
||||||
|
|
||||||
|
onClientVolumeChange(event) {
|
||||||
|
Object.keys(this.hosts[event.host].groups).forEach((groupId) => {
|
||||||
|
if (!(event.client in this.hosts[event.host].groups[groupId].clients))
|
||||||
|
return
|
||||||
|
|
||||||
|
if (event.volume != null)
|
||||||
|
this.hosts[event.host].groups[groupId].clients[event.client].config.volume.percent = event.volume
|
||||||
|
|
||||||
|
if (event.muted != null)
|
||||||
|
this.hosts[event.host].groups[groupId].clients[event.client].config.volume.muted = event.muted
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
onGroupMuteChange(event) {
|
||||||
|
this.hosts[event.host].groups[event.group].muted = event.muted
|
||||||
|
},
|
||||||
|
|
||||||
|
modalShow(event) {
|
||||||
|
switch(event.type) {
|
||||||
|
case 'host':
|
||||||
|
this.modal[event.type].info = this.hosts[event.host]
|
||||||
|
break
|
||||||
|
case 'group':
|
||||||
|
this.modal[event.type].info.server = this.hosts[event.host].server
|
||||||
|
this.modal[event.type].info.group = this.hosts[event.host].groups[event.group]
|
||||||
|
this.modal[event.type].info.streams = this.hosts[event.host].streams
|
||||||
|
this.modal[event.type].info.clients = {}
|
||||||
|
|
||||||
|
for (const group of Object.values(this.hosts[event.host].groups)) {
|
||||||
|
for (const client of Object.values(group.clients)) {
|
||||||
|
this.modal[event.type].info.clients[client.id] = client
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
case 'client':
|
||||||
|
this.modal[event.type].info = this.hosts[event.host].groups[event.group].clients[event.client]
|
||||||
|
this.modal[event.type].info.server = this.hosts[event.host].server
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
this.modal[event.type].visible = true
|
||||||
|
},
|
||||||
|
|
||||||
|
async groupMute(event) {
|
||||||
|
await this.request('music.snapcast.mute', {
|
||||||
|
group: event.group,
|
||||||
|
host: event.host,
|
||||||
|
port: this.ports[event.host],
|
||||||
|
mute: event.muted,
|
||||||
|
})
|
||||||
|
|
||||||
|
await this.refreshHost(event.host)
|
||||||
|
},
|
||||||
|
|
||||||
|
async clientMute(event) {
|
||||||
|
await this.request('music.snapcast.mute', {
|
||||||
|
client: event.client,
|
||||||
|
host: event.host,
|
||||||
|
port: this.ports[event.host],
|
||||||
|
mute: event.muted,
|
||||||
|
})
|
||||||
|
|
||||||
|
await this.refreshHost(event.host)
|
||||||
|
},
|
||||||
|
|
||||||
|
async clientSetVolume(event) {
|
||||||
|
await this.request('music.snapcast.volume', {
|
||||||
|
client: event.client,
|
||||||
|
host: event.host,
|
||||||
|
port: this.ports[event.host],
|
||||||
|
volume: event.volume,
|
||||||
|
})
|
||||||
|
|
||||||
|
await this.refreshHost(event.host)
|
||||||
|
},
|
||||||
|
|
||||||
|
onModalShow(event) {
|
||||||
|
switch (event.type) {
|
||||||
|
case 'host':
|
||||||
|
this.selectedHost = event.host
|
||||||
|
this.$refs.modalHost.show()
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'group':
|
||||||
|
this.selectedHost = event.host
|
||||||
|
this.selectedGroup = event.group
|
||||||
|
this.$refs.modalGroup.show()
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'client':
|
||||||
|
this.selectedHost = event.host
|
||||||
|
this.selectedGroup = event.group
|
||||||
|
this.selectedClient = event.client
|
||||||
|
this.$refs.modalClient.show()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.refresh()
|
||||||
|
|
||||||
|
this.subscribe(this.onClientUpdate, null,
|
||||||
|
'platypush.message.event.music.snapcast.ClientConnectedEvent',
|
||||||
|
'platypush.message.event.music.snapcast.ClientDisconnectedEvent',
|
||||||
|
'platypush.message.event.music.snapcast.ClientNameChangeEvent')
|
||||||
|
|
||||||
|
this.subscribe(this.onGroupStreamChange, null, 'platypush.message.event.music.snapcast.GroupStreamChangeEvent')
|
||||||
|
this.subscribe(this.onServerUpdate, null, 'platypush.message.event.music.snapcast.ServerUpdateEvent')
|
||||||
|
this.subscribe(this.onStreamUpdate, null, 'platypush.message.event.music.snapcast.StreamUpdateEvent')
|
||||||
|
this.subscribe(this.onClientVolumeChange, null, 'platypush.message.event.music.snapcast.ClientVolumeChangeEvent')
|
||||||
|
this.subscribe(this.onGroupMuteChange, null, 'platypush.message.event.music.snapcast.GroupMuteChangeEvent')
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.music-snapcast-container {
|
||||||
|
width: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
background: $background-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep(.info) {
|
||||||
|
.modal {
|
||||||
|
.content {
|
||||||
|
width: 90%;
|
||||||
|
max-width: 800px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.body {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: .75em;
|
||||||
|
padding: 1em;
|
||||||
|
|
||||||
|
@include until($tablet) {
|
||||||
|
flex-direction: column;
|
||||||
|
border-bottom: $default-border;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include from($desktop) {
|
||||||
|
padding: 1em 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
@include from($tablet) {
|
||||||
|
justify-content: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include until($tablet) {
|
||||||
|
width: 100%;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include until($tablet) {
|
||||||
|
.label {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-child(odd) {
|
||||||
|
background: $background-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-child(even) {
|
||||||
|
background: $default-bg-3;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: $hover-bg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons {
|
||||||
|
background: initial;
|
||||||
|
margin-top: 1.5em;
|
||||||
|
padding-top: 1.5em;
|
||||||
|
border-top: $default-border-2;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media #{map-get($widths, 's')} {
|
||||||
|
.music-snapcast-container {
|
||||||
|
.modal {
|
||||||
|
width: 80vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media #{map-get($widths, 'm')} {
|
||||||
|
.music-snapcast-container {
|
||||||
|
.modal {
|
||||||
|
width: 70vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media #{map-get($widths, 'l')} {
|
||||||
|
.music-snapcast-container {
|
||||||
|
.modal {
|
||||||
|
width: 45vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,178 @@
|
||||||
|
<template>
|
||||||
|
<div class="client-modal">
|
||||||
|
<div class="info" v-if="client">
|
||||||
|
<div class="row">
|
||||||
|
<div class="label col-s-12 col-m-3">ID</div>
|
||||||
|
<div class="value col-s-12 col-m-9" v-text="client.id"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="client.config?.name?.length || client.host?.name">
|
||||||
|
<div class="label col-s-12 col-m-3">Name</div>
|
||||||
|
<div class="value col-s-12 col-m-9">
|
||||||
|
<span class="name" v-text="client.config?.name || client.host?.name"></span>
|
||||||
|
<button title="Rename" @click="renameClient">
|
||||||
|
<i class="fa fa-edit" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="label col-s-12 col-m-3">Connected</div>
|
||||||
|
<div class="value col-s-12 col-m-9" v-text="client.connected"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="label col-s-12 col-m-3">Volume</div>
|
||||||
|
<div class="value col-s-12 col-m-9">{{ client.config.volume.percent }}%</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="label col-s-12 col-m-3">Muted</div>
|
||||||
|
<div class="value col-s-12 col-m-9" v-text="client.config.volume.muted"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="label col-s-12 col-m-3">Latency</div>
|
||||||
|
<div class="value col-s-12 col-m-9" v-text="client.config.latency"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="client.host.ip && client.host.ip.length">
|
||||||
|
<div class="label col-s-12 col-m-3">IP Address</div>
|
||||||
|
<div class="value col-s-12 col-m-9" v-text="client.host.ip"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="client.host.mac && client.host.mac.length">
|
||||||
|
<div class="label col-s-12 col-m-3">MAC Address</div>
|
||||||
|
<div class="value col-s-12 col-m-9" v-text="client.host.mac"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="client.host.os && client.host.os.length">
|
||||||
|
<div class="label col-s-12 col-m-3">OS</div>
|
||||||
|
<div class="value col-s-12 col-m-9" v-text="client.host.os"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="client.host.arch && client.host.arch.length">
|
||||||
|
<div class="label col-s-12 col-m-3">Architecture</div>
|
||||||
|
<div class="value col-s-12 col-m-9" v-text="client.host.arch"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="label col-s-12 col-m-3">Client name</div>
|
||||||
|
<div class="value col-s-12 col-m-9" v-text="client.snapclient.name"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="label col-s-12 col-m-3">Client version</div>
|
||||||
|
<div class="value col-s-12 col-m-9" v-text="client.snapclient.version"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="label col-s-12 col-m-3">Protocol version</div>
|
||||||
|
<div class="value col-s-12 col-m-9" v-text="client.snapclient.protocolVersion"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="buttons">
|
||||||
|
<div class="row">
|
||||||
|
<button type="button" :disabled="loading" @click="removeClient">
|
||||||
|
<i class="fas fa-trash" />
|
||||||
|
<span class="name">Remove client</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "ClientModal",
|
||||||
|
emits: ['remove-client', 'rename-client'],
|
||||||
|
props: {
|
||||||
|
loading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
client: {
|
||||||
|
type: Object,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
removeClient() {
|
||||||
|
if (!window.confirm('Are you sure that you want to remove this client?'))
|
||||||
|
return
|
||||||
|
|
||||||
|
this.$emit('remove-client')
|
||||||
|
},
|
||||||
|
|
||||||
|
renameClient() {
|
||||||
|
const name = (window.prompt('New client name',
|
||||||
|
this.client.config.name?.length ? this.client.config.name : this.client.host.name) || '').trim()
|
||||||
|
|
||||||
|
if (!name.length)
|
||||||
|
return
|
||||||
|
|
||||||
|
this.$emit('rename-client', name)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.client-modal {
|
||||||
|
max-height: 75vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.info {
|
||||||
|
height: 80%;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0 .5em;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: $default-hover-fg-2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons {
|
||||||
|
height: 20%;
|
||||||
|
margin: 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
|
||||||
|
.row {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
padding: 1em;
|
||||||
|
color: #900;
|
||||||
|
border-color: #900;
|
||||||
|
|
||||||
|
.name {
|
||||||
|
margin-left: .5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: $hover-bg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,161 @@
|
||||||
|
<template>
|
||||||
|
<div class="info">
|
||||||
|
<div class="section name">
|
||||||
|
<div class="title">Name</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="name-value">
|
||||||
|
<span class="name" v-text="group.name?.length ? group.name : 'default'" />
|
||||||
|
<button class="pull-right" title="Rename" @click="renameGroup">
|
||||||
|
<i class="fa fa-edit" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section clients" v-if="Object.keys(group?.clients || {}).length > 0">
|
||||||
|
<div class="title">Clients</div>
|
||||||
|
<div class="row" ref="groupClients" v-for="(client, id) in (clients || {})" :key="id">
|
||||||
|
<label class="client" :for="'snapcast-client-' + client.id">
|
||||||
|
<input type="checkbox"
|
||||||
|
class="client"
|
||||||
|
:id="`snapcast-client-${client.id}`"
|
||||||
|
:value="client.id"
|
||||||
|
:checked="client.id in group.clients"
|
||||||
|
:disabled="loading"
|
||||||
|
@input="$emit($event.target.checked ? 'add-client' : 'remove-client', client.id)">
|
||||||
|
{{ client.host.name }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section streams" v-if="group?.stream_id">
|
||||||
|
<div class="title">Stream</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="label col-3">ID</div>
|
||||||
|
<div class="value col-9">
|
||||||
|
<label>
|
||||||
|
<select ref="streamSelect" @change="$emit('stream-change', $event.target.value)">
|
||||||
|
<option
|
||||||
|
v-for="(stream, id) in streams" :key="id"
|
||||||
|
v-text="streams[group.stream_id].id"
|
||||||
|
:name="stream.id"
|
||||||
|
:value="stream.id"
|
||||||
|
:disabled="loading"
|
||||||
|
:selected="stream.id === group.stream_id">
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="streams?.[group.stream_id]?.status">
|
||||||
|
<div class="label col-m-3">Status</div>
|
||||||
|
<div class="value col-m-9" v-text="streams[group.stream_id].status"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="streams?.[group?.stream_id]?.uri?.host">
|
||||||
|
<div class="label col-s-12 col-m-3">Host</div>
|
||||||
|
<div class="value col-s-12 col-m-9" v-text="streams[group.stream_id].uri.host"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="streams?.[group?.stream_id]?.uri?.path">
|
||||||
|
<div class="label col-s-12 col-m-3">Path</div>
|
||||||
|
<div class="value col-s-12 col-m-9" v-text="streams[group.stream_id].uri.path"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="streams?.[group?.stream_id]?.uri?.raw">
|
||||||
|
<div class="label col-s-12 col-m-3">URI</div>
|
||||||
|
<div class="value col-s-12 col-m-9" v-text="streams[group.stream_id].uri.raw"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "GroupModal",
|
||||||
|
emits: ['add-client', 'remove-client', 'stream-change', 'rename-group'],
|
||||||
|
props: {
|
||||||
|
loading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
group: {
|
||||||
|
type: Object,
|
||||||
|
},
|
||||||
|
|
||||||
|
clients: {
|
||||||
|
type: Object,
|
||||||
|
},
|
||||||
|
|
||||||
|
streams: {
|
||||||
|
type: Object,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
renameGroup() {
|
||||||
|
const name = (prompt('New group name', this.group.name) || '').trim()
|
||||||
|
if (!name?.length)
|
||||||
|
return
|
||||||
|
|
||||||
|
this.$emit('rename-group', name)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.info {
|
||||||
|
.row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section {
|
||||||
|
padding: 1.5em;
|
||||||
|
|
||||||
|
.row {
|
||||||
|
align-items: normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
label.client {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 1em;
|
||||||
|
padding-left: .5em;
|
||||||
|
padding-bottom: .5em;
|
||||||
|
margin-bottom: .5em;
|
||||||
|
border-bottom: $default-border;
|
||||||
|
}
|
||||||
|
|
||||||
|
.client {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
input {
|
||||||
|
margin-right: .5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.name-value {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
button {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
margin: 0 1em;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: $default-hover-fg-2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,69 @@
|
||||||
|
<template>
|
||||||
|
<div class="info">
|
||||||
|
<div class="row" v-if="info?.server?.host?.ip?.length">
|
||||||
|
<div class="label col-3">IP Address</div>
|
||||||
|
<div class="value col-9" v-text="info.server.host.ip"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="info?.server?.host?.mac?.length">
|
||||||
|
<div class="label col-3">MAC Address</div>
|
||||||
|
<div class="value col-9" v-text="info.server.host.mac"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="info?.server?.host?.name?.length">
|
||||||
|
<div class="label col-3">Name</div>
|
||||||
|
<div class="value col-9" v-text="info.server.host.name"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="info?.server?.host?.port">
|
||||||
|
<div class="label col-3">Port</div>
|
||||||
|
<div class="value col-9" v-text="info.server.host.port"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="info?.server?.host?.os?.length">
|
||||||
|
<div class="label col-3">OS</div>
|
||||||
|
<div class="value col-9" v-text="info.server.host.os"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="info?.server?.host?.arch?.length">
|
||||||
|
<div class="label col-3">Architecture</div>
|
||||||
|
<div class="value col-9" v-text="info.server.host.arch"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="info?.server?.snapserver?.name?.length">
|
||||||
|
<div class="label col-3">Server name</div>
|
||||||
|
<div class="value col-9" v-text="info.server.snapserver.name"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="info?.server?.snapserver?.version?.length">
|
||||||
|
<div class="label col-3">Server version</div>
|
||||||
|
<div class="value col-9" v-text="info.server.snapserver.version"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="info?.server?.snapserver?.protocolVersion">
|
||||||
|
<div class="label col-3">Protocol version</div>
|
||||||
|
<div class="value col-9" v-text="info.server.snapserver.protocolVersion"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row" v-if="info?.server?.snapserver?.controlProtocolVersion">
|
||||||
|
<div class="label col-3">Control protocol version</div>
|
||||||
|
<div class="value col-9" v-text="info.server.snapserver.controlProtocolVersion"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "HostModal",
|
||||||
|
props: {
|
||||||
|
info: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -99,6 +99,7 @@ $widths: (
|
||||||
.pull-right {
|
.pull-right {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
float: right;
|
float: right;
|
||||||
|
justify-content: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hidden {
|
.hidden {
|
||||||
|
|
|
@ -50,6 +50,7 @@ $border-shadow-top: 0 -2.5px 4px 0 $default-shadow-color;
|
||||||
$border-shadow-bottom: 0 3px 2px -1px $default-shadow-color;
|
$border-shadow-bottom: 0 3px 2px -1px $default-shadow-color;
|
||||||
$border-shadow-left: -2.5px 0 4px 0 $default-shadow-color;
|
$border-shadow-left: -2.5px 0 4px 0 $default-shadow-color;
|
||||||
$border-shadow-right: 2.5px 0 4px 0 $default-shadow-color;
|
$border-shadow-right: 2.5px 0 4px 0 $default-shadow-color;
|
||||||
|
$border-shadow-bottom-right: 2.5px 2.5px 3px 0 $default-shadow-color;
|
||||||
|
|
||||||
//// Modals
|
//// Modals
|
||||||
$modal-header-bg: #e0e0e0 !default;
|
$modal-header-bg: #e0e0e0 !default;
|
||||||
|
@ -76,6 +77,9 @@ $default-hover-fg-2: #38cf80 !default;
|
||||||
$hover-bg: #bef6da !default;
|
$hover-bg: #bef6da !default;
|
||||||
$active-bg: #8fefb7 !default;
|
$active-bg: #8fefb7 !default;
|
||||||
|
|
||||||
|
/// Disabled
|
||||||
|
$disabled-fg: rgb(155, 155, 155);
|
||||||
|
|
||||||
/// Navigator
|
/// Navigator
|
||||||
$nav-bg: #002626 !default;
|
$nav-bg: #002626 !default;
|
||||||
$nav-fg: #e8f8e8 !default;
|
$nav-fg: #e8f8e8 !default;
|
||||||
|
|
|
@ -212,9 +212,10 @@ class MusicSnapcastPlugin(Plugin):
|
||||||
|
|
||||||
return self._status(sock)
|
return self._status(sock)
|
||||||
finally:
|
finally:
|
||||||
try: sock.close()
|
try:
|
||||||
except: pass
|
sock.close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def mute(self, client=None, group=None, mute=None, host=None, port=None):
|
def mute(self, client=None, group=None, mute=None, host=None, port=None):
|
||||||
|
@ -268,8 +269,10 @@ class MusicSnapcastPlugin(Plugin):
|
||||||
self._send(sock, request)
|
self._send(sock, request)
|
||||||
return self._recv(sock)
|
return self._recv(sock)
|
||||||
finally:
|
finally:
|
||||||
try: sock.close()
|
try:
|
||||||
except: pass
|
sock.close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def volume(self, client, volume=None, delta=None, mute=None, host=None,
|
def volume(self, client, volume=None, delta=None, mute=None, host=None,
|
||||||
|
@ -336,8 +339,10 @@ class MusicSnapcastPlugin(Plugin):
|
||||||
self._send(sock, request)
|
self._send(sock, request)
|
||||||
return self._recv(sock)
|
return self._recv(sock)
|
||||||
finally:
|
finally:
|
||||||
try: sock.close()
|
try:
|
||||||
except: pass
|
sock.close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def set_client_name(self, client, name, host=None, port=None):
|
def set_client_name(self, client, name, host=None, port=None):
|
||||||
|
@ -374,8 +379,50 @@ class MusicSnapcastPlugin(Plugin):
|
||||||
self._send(sock, request)
|
self._send(sock, request)
|
||||||
return self._recv(sock)
|
return self._recv(sock)
|
||||||
finally:
|
finally:
|
||||||
try: sock.close()
|
try:
|
||||||
except: pass
|
sock.close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@action
|
||||||
|
def set_group_name(self, group, name, host=None, port=None):
|
||||||
|
"""
|
||||||
|
Set/change the name of a group
|
||||||
|
|
||||||
|
:param group: Group ID to rename
|
||||||
|
:type group: str
|
||||||
|
|
||||||
|
:param name: New name
|
||||||
|
:type name: str
|
||||||
|
|
||||||
|
:param host: Snapcast server (default: default configured host)
|
||||||
|
:type host: str
|
||||||
|
|
||||||
|
:param port: Snapcast server port (default: default configured port)
|
||||||
|
:type port: int
|
||||||
|
"""
|
||||||
|
|
||||||
|
sock = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
sock = self._connect(host or self.host, port or self.port)
|
||||||
|
request = {
|
||||||
|
'id': self._get_req_id(),
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'method': 'Group.SetName',
|
||||||
|
'params': {
|
||||||
|
'id': group,
|
||||||
|
'name': name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self._send(sock, request)
|
||||||
|
return self._recv(sock)
|
||||||
|
finally:
|
||||||
|
try:
|
||||||
|
sock.close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def set_latency(self, client, latency, host=None, port=None):
|
def set_latency(self, client, latency, host=None, port=None):
|
||||||
|
@ -413,8 +460,10 @@ class MusicSnapcastPlugin(Plugin):
|
||||||
self._send(sock, request)
|
self._send(sock, request)
|
||||||
return self._recv(sock)
|
return self._recv(sock)
|
||||||
finally:
|
finally:
|
||||||
try: sock.close()
|
try:
|
||||||
except: pass
|
sock.close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def delete_client(self, client, host=None, port=None):
|
def delete_client(self, client, host=None, port=None):
|
||||||
|
@ -447,8 +496,10 @@ class MusicSnapcastPlugin(Plugin):
|
||||||
self._send(sock, request)
|
self._send(sock, request)
|
||||||
return self._recv(sock)
|
return self._recv(sock)
|
||||||
finally:
|
finally:
|
||||||
try: sock.close()
|
try:
|
||||||
except: pass
|
sock.close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def group_set_clients(self, group, clients, host=None, port=None):
|
def group_set_clients(self, group, clients, host=None, port=None):
|
||||||
|
@ -490,9 +541,10 @@ class MusicSnapcastPlugin(Plugin):
|
||||||
self._send(sock, request)
|
self._send(sock, request)
|
||||||
return self._recv(sock)
|
return self._recv(sock)
|
||||||
finally:
|
finally:
|
||||||
try: sock.close()
|
try:
|
||||||
except: pass
|
sock.close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def group_set_stream(self, group, stream_id, host=None, port=None):
|
def group_set_stream(self, group, stream_id, host=None, port=None):
|
||||||
|
@ -530,9 +582,10 @@ class MusicSnapcastPlugin(Plugin):
|
||||||
self._send(sock, request)
|
self._send(sock, request)
|
||||||
return self._recv(sock)
|
return self._recv(sock)
|
||||||
finally:
|
finally:
|
||||||
try: sock.close()
|
try:
|
||||||
except: pass
|
sock.close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def get_backend_hosts(self):
|
def get_backend_hosts(self):
|
||||||
|
@ -616,5 +669,4 @@ class MusicSnapcastPlugin(Plugin):
|
||||||
|
|
||||||
return {'hosts': playing_hosts}
|
return {'hosts': playing_hosts}
|
||||||
|
|
||||||
|
|
||||||
# vim:sw=4:ts=4:et:
|
# vim:sw=4:ts=4:et:
|
||||||
|
|
|
@ -142,9 +142,6 @@ websocket-client
|
||||||
# mpv player plugin
|
# mpv player plugin
|
||||||
# python-mpv
|
# python-mpv
|
||||||
|
|
||||||
# SCSS/SASS to CSS compiler for web pages style
|
|
||||||
pyScss
|
|
||||||
|
|
||||||
# Support for NFC tags
|
# Support for NFC tags
|
||||||
# nfcpy >= 1.0
|
# nfcpy >= 1.0
|
||||||
# ndeflib
|
# ndeflib
|
||||||
|
|
1
setup.py
1
setup.py
|
@ -58,7 +58,6 @@ setup(
|
||||||
'redis',
|
'redis',
|
||||||
'requests',
|
'requests',
|
||||||
'croniter',
|
'croniter',
|
||||||
'pyScss',
|
|
||||||
'sqlalchemy',
|
'sqlalchemy',
|
||||||
'websockets',
|
'websockets',
|
||||||
'websocket-client',
|
'websocket-client',
|
||||||
|
|
Loading…
Reference in a new issue