diff --git a/platypush/backend/http/static/css/source/common/elements/switch.scss b/platypush/backend/http/static/css/source/common/elements/switch.scss index 4870a90a6..6e871fdb7 100644 --- a/platypush/backend/http/static/css/source/common/elements/switch.scss +++ b/platypush/backend/http/static/css/source/common/elements/switch.scss @@ -12,7 +12,7 @@ display: inline-block; text-align: center; user-select: none; - padding-top: 1rem; + padding: .5rem 0; input[type=checkbox] { display: none !important; @@ -39,7 +39,7 @@ border-radius: 50%; box-shadow: $switch-shadow-1; display: block; - margin: 0 auto; + margin: .5rem auto 0 auto; font-size: 1.4em; transition: all 350ms ease-in; diff --git a/platypush/backend/http/static/css/source/common/vars.scss b/platypush/backend/http/static/css/source/common/vars.scss index 7f6a1b3d5..cc14a3e40 100644 --- a/platypush/backend/http/static/css/source/common/vars.scss +++ b/platypush/backend/http/static/css/source/common/vars.scss @@ -3,9 +3,13 @@ $default-bg: white !default; $default-bg-2: #f4f5f6 !default; $default-bg-3: #f1f3f2 !default; $default-bg-4: #edf0ee !default; +$default-bg-5: #f8f8f8 !default; $default-fg: black !default; $default-fg-2: #333333 !default; +$default-fg-3: #888888 !default; $default-font-size: 1.5rem !default; +$default-shadow: 2px 2px 2px #ccc !default; +$default-hover-fg: #35b870 !default; $default-font-family: "Raleway", "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif !default; $default-border: 1px solid #e1e4e8 !default; diff --git a/platypush/backend/http/static/css/source/webpanel/plugins/music.snapcast/index.scss b/platypush/backend/http/static/css/source/webpanel/plugins/music.snapcast/index.scss new file mode 100644 index 000000000..89500ede9 --- /dev/null +++ b/platypush/backend/http/static/css/source/webpanel/plugins/music.snapcast/index.scss @@ -0,0 +1,106 @@ +@import 'common/vars'; +@import 'common/mixins'; +@import 'common/layout'; + +$host-border: $default-border-2; +$host-shadow: $default-shadow; + +.music-snapcast-container { + .host { + width: 95%; + margin: 2rem auto; + border: $host-border; + border-radius: 1rem; + box-shadow: $host-shadow; + + .head { + padding: 1rem .5rem; + background: $default-bg-4; + border-bottom: $host-border; + border-radius: 1rem 1rem 0 0; + display: flex; + align-items: center; + + .name { + padding-left: .5rem; + text-transform: uppercase; + + &:hover { + color: $default-hover-fg; + cursor: pointer; + } + } + + button { + padding: 0; + border: 0; + &:hover { color: $default-hover-fg; } + } + } + + .group { + .head { + background: $default-bg-5; + border-radius: 0; + } + + .head, + .client { + padding: 0 1rem; + } + + .client { + display: flex; + align-items: center; + + &.offline { color: $default-fg-3; } + &:hover { background: $hover-bg; } + + .name { + &:hover { + color: $default-hover-fg; + cursor: pointer; + } + } + } + } + + .icon { margin-right: 1rem; } + } + + .modal { + .info { + .row { + &:hover { background: $hover-bg; } + .value { + text-align: right; + } + } + } + } +} + +@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: 65vw; + } + } +} + diff --git a/platypush/backend/http/static/js/plugins/music.snapcast/client.js b/platypush/backend/http/static/js/plugins/music.snapcast/client.js new file mode 100644 index 000000000..7c79eb291 --- /dev/null +++ b/platypush/backend/http/static/js/plugins/music.snapcast/client.js @@ -0,0 +1,37 @@ +Vue.component('music-snapcast-client', { + template: '#tmpl-music-snapcast-client', + props: { + config: { type: Object }, + connected: { type: Boolean }, + host: { type: Object }, + id: { type: String }, + groupId: { type: String }, + lastSeen: { type: Object }, + snapclient: { type: Object }, + server: { type: Object }, + bus: { type: Object }, + }, + + methods: { + muteToggled: function(event) { + this.bus.$emit('client-mute-changed', { + id: this.id, + server: this.server, + value: !event.value, + }); + }, + + volumeChanged: function(event) { + this.bus.$emit('client-volume-changed', { + id: this.id, + server: this.server, + value: parseInt(event.target.value), + }); + }, + }, +}); + +Vue.component('music-snapcast-client-info', { + props: ['info'], +}); + diff --git a/platypush/backend/http/static/js/plugins/music.snapcast/group.js b/platypush/backend/http/static/js/plugins/music.snapcast/group.js new file mode 100644 index 000000000..91f7d10a5 --- /dev/null +++ b/platypush/backend/http/static/js/plugins/music.snapcast/group.js @@ -0,0 +1,27 @@ +Vue.component('music-snapcast-group', { + template: '#tmpl-music-snapcast-group', + props: { + id: { type: String }, + clients: { type: Object }, + muted: { type: Boolean }, + name: { type: String }, + stream: { type: Object }, + server: { type: Object }, + bus: { type: Object }, + }, + + methods: { + muteToggled: function(event) { + this.bus.$emit('group-mute-changed', { + id: this.id, + server: this.server, + value: !event.value, + }); + }, + }, +}); + +Vue.component('music-snapcast-group-info', { + props: ['info'], +}); + diff --git a/platypush/backend/http/static/js/plugins/music.snapcast/host.js b/platypush/backend/http/static/js/plugins/music.snapcast/host.js new file mode 100644 index 000000000..16b971039 --- /dev/null +++ b/platypush/backend/http/static/js/plugins/music.snapcast/host.js @@ -0,0 +1,20 @@ +Vue.component('music-snapcast-host', { + template: '#tmpl-music-snapcast-host', + props: { + groups: { type: Object }, + server: { type: Object }, + streams: { type: Object }, + bus: { type: Object }, + }, + + data: function() { + return { + collapsed: false, + }; + }, +}); + +Vue.component('music-snapcast-host-info', { + props: ['info'], +}); + diff --git a/platypush/backend/http/static/js/plugins/music.snapcast/index.js b/platypush/backend/http/static/js/plugins/music.snapcast/index.js new file mode 100644 index 000000000..bec4eae48 --- /dev/null +++ b/platypush/backend/http/static/js/plugins/music.snapcast/index.js @@ -0,0 +1,178 @@ +Vue.component('music-snapcast', { + template: '#tmpl-music-snapcast', + props: ['config'], + data: function() { + return { + hosts: {}, + modal: { + host: { + visible: false, + info: {}, + }, + group: { + visible: false, + info: {}, + }, + client: { + visible: false, + info: {}, + }, + }, + + bus: new Vue({}), + }; + }, + + methods: { + refresh: async function() { + let hosts = await request('music.snapcast.get_backend_hosts'); + let promises = Object.keys(hosts).map( + (host) => request('music.snapcast.status', {host: host, port: hosts[host]}) + ); + + let statuses = await Promise.all(promises); + this.hosts = {}; + + for (const status of statuses) { + status.server.host.port = hosts[status.server.host.name]; + var groups = {}; + + for (const group of status.groups) { + var clients = {}; + for (const client of group.clients) { + clients[client.id] = client; + } + + group.clients = clients; + groups[group.id] = group; + } + + status.groups = groups; + var streams = {}; + + for (const stream of status.streams) { + streams[stream.id] = stream; + } + + status.streams = streams; + Vue.set(this.hosts, status.server.host.name, status); + } + }, + + onClientUpdate: function(event) { + for (const groupId of Object.keys(this.hosts[event.host].groups)) { + 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: function(event) { + this.hosts[event.host].groups[event.group].stream_id = event.stream; + }, + + onServerUpdate: function(event) { + this.refresh(); + }, + + onStreamUpdate: function(event) { + this.streams[event.stream_id] = event.stream; + }, + + onClientVolumeChange: function(event) { + for (const groupId of Object.keys(this.hosts[event.host].groups)) { + if (event.client in this.hosts[event.host].groups[groupId].clients) { + 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: function(event) { + this.hosts[event.host].groups[event.group].muted = event.muted; + }, + + modalShow: function(event) { + switch(event.type) { + case 'host': + this.modal[event.type].info = this.hosts[event.host]; + break; + case 'group': + this.modal[event.type].info = this.hosts[event.host].groups[event.group]; + break; + case 'client': + this.modal[event.type].info = this.hosts[event.host].groups[event.group].clients[event.client]; + break; + } + + this.modal[event.type].visible = true; + }, + + groupMute: async function(event) { + await request('music.snapcast.mute', { + group: event.id, + host: event.server.ip || event.server.name, + port: event.server.port, + mute: event.value, + }); + + this.hosts[event.server.name].groups[event.id].muted = event.value; + }, + + clientMute: async function(event) { + await request('music.snapcast.mute', { + client: event.id, + host: event.server.ip || event.server.name, + port: event.server.port, + mute: event.value, + }); + + for (const groupId of Object.keys(this.hosts[event.server.name].groups)) { + if (event.id in this.hosts[event.server.name].groups[groupId].clients) { + this.hosts[event.server.name].groups[groupId].clients[event.id].config.volume.muted = event.value; + } + } + }, + + clientSetVolume: async function(event) { + await request('music.snapcast.volume', { + client: event.id, + host: event.server.ip || event.server.name, + port: event.server.port, + volume: event.value, + }); + + for (const groupId of Object.keys(this.hosts[event.server.name].groups)) { + if (event.id in this.hosts[event.server.name].groups[groupId].clients) { + this.hosts[event.server.name].groups[groupId].clients[event.id].config.volume.percent = event.value; + } + } + }, + }, + + created: function() { + this.refresh(); + + registerEventHandler(this.onClientUpdate, + 'platypush.message.event.music.snapcast.ClientConnectedEvent', + 'platypush.message.event.music.snapcast.ClientDisconnectedEvent', + 'platypush.message.event.music.snapcast.ClientNameChangeEvent'); + + registerEventHandler(this.onGroupStreamChange, 'platypush.message.event.music.snapcast.GroupStreamChangeEvent'); + registerEventHandler(this.onServerUpdate, 'platypush.message.event.music.snapcast.ServerUpdateEvent'); + registerEventHandler(this.onStreamUpdate, 'platypush.message.event.music.snapcast.StreamUpdateEvent'); + registerEventHandler(this.onClientVolumeChange, 'platypush.message.event.music.snapcast.ClientVolumeChangeEvent'); + registerEventHandler(this.onGroupMuteChange, 'platypush.message.event.music.snapcast.GroupMuteChangeEvent'); + + this.bus.$on('group-mute-changed', this.groupMute); + this.bus.$on('client-mute-changed', this.clientMute); + this.bus.$on('client-volume-changed', this.clientSetVolume); + this.bus.$on('modal-show', this.modalShow); + }, +}); + diff --git a/platypush/backend/http/templates/plugins/music.snapcast/client.html b/platypush/backend/http/templates/plugins/music.snapcast/client.html new file mode 100644 index 000000000..00392b624 --- /dev/null +++ b/platypush/backend/http/templates/plugins/music.snapcast/client.html @@ -0,0 +1,16 @@ + + + + diff --git a/platypush/backend/http/templates/plugins/music.snapcast/group.html b/platypush/backend/http/templates/plugins/music.snapcast/group.html new file mode 100644 index 000000000..d189a8282 --- /dev/null +++ b/platypush/backend/http/templates/plugins/music.snapcast/group.html @@ -0,0 +1,33 @@ +{% include 'plugins/music.snapcast/client.html' %} + + + + + diff --git a/platypush/backend/http/templates/plugins/music.snapcast/host.html b/platypush/backend/http/templates/plugins/music.snapcast/host.html new file mode 100644 index 000000000..b79cd4d44 --- /dev/null +++ b/platypush/backend/http/templates/plugins/music.snapcast/host.html @@ -0,0 +1,34 @@ +{% include 'plugins/music.snapcast/group.html' %} + + + + + diff --git a/platypush/backend/http/templates/plugins/music.snapcast/index.html b/platypush/backend/http/templates/plugins/music.snapcast/index.html new file mode 100644 index 000000000..4d48531f3 --- /dev/null +++ b/platypush/backend/http/templates/plugins/music.snapcast/index.html @@ -0,0 +1,83 @@ +{% include 'plugins/music.snapcast/host.html' %} + + +