forked from platypush/platypush
Added skeleton UI for Snapcast plugin on web panel
This commit is contained in:
parent
7aefe4e520
commit
004868f526
5 changed files with 340 additions and 9 deletions
|
@ -15,7 +15,7 @@ from flask import Flask, Response, abort, jsonify, request as http_request, \
|
||||||
from redis import Redis
|
from redis import Redis
|
||||||
|
|
||||||
from platypush.config import Config
|
from platypush.config import Config
|
||||||
from platypush.context import get_backend, get_or_create_event_loop
|
from platypush.context import get_backend, get_plugin, get_or_create_event_loop
|
||||||
from platypush.message import Message
|
from platypush.message import Message
|
||||||
from platypush.message.event import Event, StopEvent
|
from platypush.message.event import Event, StopEvent
|
||||||
from platypush.message.event.web.widget import WidgetUpdateEvent
|
from platypush.message.event.web.widget import WidgetUpdateEvent
|
||||||
|
@ -279,8 +279,10 @@ class HttpBackend(Backend):
|
||||||
else:
|
else:
|
||||||
enabled_plugins[plugin] = conf
|
enabled_plugins[plugin] = conf
|
||||||
|
|
||||||
return render_template('index.html', plugins=enabled_plugins, hidden_plugins=hidden_plugins,
|
return render_template('index.html', plugins=enabled_plugins,
|
||||||
token=Config.get('token'), websocket_port=self.websocket_port,
|
hidden_plugins=hidden_plugins, utils=HttpUtils,
|
||||||
|
token=Config.get('token'),
|
||||||
|
websocket_port=self.websocket_port,
|
||||||
has_ssl=self.ssl_context is not None)
|
has_ssl=self.ssl_context is not None)
|
||||||
|
|
||||||
|
|
||||||
|
@ -548,5 +550,9 @@ class HttpUtils(object):
|
||||||
return json.loads(data)
|
return json.loads(data)
|
||||||
|
|
||||||
|
|
||||||
# vim:sw=4:ts=4:et:
|
@classmethod
|
||||||
|
def get_plugin(cls, plugin):
|
||||||
|
return get_plugin(plugin)
|
||||||
|
|
||||||
|
|
||||||
|
# vim:sw=4:ts=4:et:
|
||||||
|
|
305
platypush/backend/http/static/js/music.snapcast.js
Normal file
305
platypush/backend/http/static/js/music.snapcast.js
Normal file
|
@ -0,0 +1,305 @@
|
||||||
|
$(document).ready(function() {
|
||||||
|
var statuses = [],
|
||||||
|
$container = $('#snapcast-container');
|
||||||
|
|
||||||
|
var createPowerToggleElement = function(data) {
|
||||||
|
data = data || {};
|
||||||
|
|
||||||
|
var $powerToggle = $('<div></div>').addClass('toggle toggle--push switch-container');
|
||||||
|
var $input = $('<input></input>').attr('type', 'checkbox')
|
||||||
|
.attr('id', data.id).addClass('toggle--checkbox');
|
||||||
|
|
||||||
|
for (var attr of Object.keys(data)) {
|
||||||
|
$input.data(attr, data[attr]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var $label = $('<label></label>').attr('for', data.id).addClass('toggle--btn');
|
||||||
|
|
||||||
|
$input.appendTo($powerToggle);
|
||||||
|
$label.appendTo($powerToggle);
|
||||||
|
|
||||||
|
if ('on' in data && data['on']) {
|
||||||
|
$input.prop('checked', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $powerToggle;
|
||||||
|
};
|
||||||
|
|
||||||
|
var update = function(statuses) {
|
||||||
|
$container.html('');
|
||||||
|
|
||||||
|
var networkNames = Object.keys(window.config.snapcast_hosts);
|
||||||
|
for (var i=0; i < networkNames.length; i++) {
|
||||||
|
var status = statuses[i];
|
||||||
|
var networkName = networkNames[i];
|
||||||
|
var name = status.server.host.name || status.server.host.ip;
|
||||||
|
|
||||||
|
var $host = $('<div></div>')
|
||||||
|
.addClass('snapcast-host-container')
|
||||||
|
.data('name', name)
|
||||||
|
.data('network-name', networkName);
|
||||||
|
|
||||||
|
var $header = $('<div></div>').addClass('row')
|
||||||
|
.addClass('snapcast-host-header');
|
||||||
|
|
||||||
|
var $title = $('<h1></h1>').text(name);
|
||||||
|
|
||||||
|
$title.appendTo($header);
|
||||||
|
$header.appendTo($host);
|
||||||
|
|
||||||
|
for (var group of status.groups) {
|
||||||
|
var groupName = group.name || group.stream_id;
|
||||||
|
var $group = $('<div></div>')
|
||||||
|
.addClass('snapcast-group-container')
|
||||||
|
.data('name', groupName)
|
||||||
|
.data('id', group.id);
|
||||||
|
|
||||||
|
var $groupHeader = $('<div></div>').addClass('row')
|
||||||
|
.addClass('snapcast-group-header');
|
||||||
|
|
||||||
|
var $groupTitle = $('<h2></h2>')
|
||||||
|
.addClass('eleven columns')
|
||||||
|
.text(groupName);
|
||||||
|
|
||||||
|
var $groupSettings = $('<i></i>')
|
||||||
|
.addClass('snapcast-group-settings')
|
||||||
|
.addClass('one column')
|
||||||
|
.addClass('fa fa-cog')
|
||||||
|
.data('name', groupName)
|
||||||
|
.data('id', group.id);
|
||||||
|
|
||||||
|
$groupTitle.appendTo($groupHeader);
|
||||||
|
$groupSettings.appendTo($groupHeader);
|
||||||
|
$groupHeader.appendTo($group);
|
||||||
|
|
||||||
|
for (var client of group.clients) {
|
||||||
|
var clientName = client.config.name || client.host.name || client.host.ip;
|
||||||
|
var $client = $('<div></div>')
|
||||||
|
.addClass('snapcast-client-container')
|
||||||
|
.data('name', clientName)
|
||||||
|
.data('id', client.id);
|
||||||
|
|
||||||
|
var $clientHeader = $('<div></div>').addClass('row')
|
||||||
|
.addClass('snapcast-client-header');
|
||||||
|
|
||||||
|
var $clientTitle = $('<h3></h3>')
|
||||||
|
.addClass('eleven columns')
|
||||||
|
.data('connected', client.connected)
|
||||||
|
.text(clientName);
|
||||||
|
|
||||||
|
var $clientMuteToggle = createPowerToggleElement({
|
||||||
|
type: 'client',
|
||||||
|
id: client.id,
|
||||||
|
on: !client.config.volume.muted,
|
||||||
|
}).addClass('one column');
|
||||||
|
|
||||||
|
$clientTitle.appendTo($clientHeader);
|
||||||
|
$clientMuteToggle.appendTo($clientHeader);
|
||||||
|
$clientHeader.appendTo($client);
|
||||||
|
$client.appendTo($group);
|
||||||
|
}
|
||||||
|
|
||||||
|
$group.appendTo($host);
|
||||||
|
}
|
||||||
|
|
||||||
|
$host.appendTo($container);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var initUi = function() {
|
||||||
|
var promises = [];
|
||||||
|
|
||||||
|
for (var host of Object.keys(window.config.snapcast_hosts)) {
|
||||||
|
promises.push(
|
||||||
|
execute({
|
||||||
|
type: 'request',
|
||||||
|
action: 'music.snapcast.status',
|
||||||
|
args: {
|
||||||
|
host: host,
|
||||||
|
port: window.config.snapcast_hosts[host],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$.when.apply($, promises)
|
||||||
|
.done(function() {
|
||||||
|
statuses = [];
|
||||||
|
for (var status of arguments) {
|
||||||
|
statuses.push(status[0].response.output);
|
||||||
|
}
|
||||||
|
|
||||||
|
update(statuses);
|
||||||
|
}).then(function() {
|
||||||
|
initBindings();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var initBindings = function() {
|
||||||
|
// $roomsList.on('click touch', '.room-item', function() {
|
||||||
|
// $('.room-item').removeClass('selected');
|
||||||
|
// $('.room-lights-item').hide();
|
||||||
|
// $('.room-scenes-item').hide();
|
||||||
|
|
||||||
|
// var roomId = $(this).data('id');
|
||||||
|
// var $roomLights = $('.room-lights-item').filter(function(i, item) {
|
||||||
|
// return $(item).data('id') === roomId
|
||||||
|
// });
|
||||||
|
|
||||||
|
// var $roomScenes = $('.room-scenes-item').filter(function(i, item) {
|
||||||
|
// return $(item).data('room-id') === roomId
|
||||||
|
// });
|
||||||
|
|
||||||
|
// $(this).addClass('selected');
|
||||||
|
// $roomLights.show();
|
||||||
|
// $roomScenes.show();
|
||||||
|
// });
|
||||||
|
|
||||||
|
// $scenesList.on('click touch', '.scene-item', function() {
|
||||||
|
// $('.scene-item').removeClass('selected');
|
||||||
|
// $(this).addClass('selected');
|
||||||
|
|
||||||
|
// execute({
|
||||||
|
// type: 'request',
|
||||||
|
// action: 'light.hue.scene',
|
||||||
|
// args: {
|
||||||
|
// name: $(this).data('name')
|
||||||
|
// }
|
||||||
|
// }, refreshStatus);
|
||||||
|
// });
|
||||||
|
|
||||||
|
// $lightsList.on('click touch', '.light-item-name', function() {
|
||||||
|
// var $lightItem = $(this).parents('.light-item');
|
||||||
|
// var $colorSelector = $lightItem.find('.light-color-selector');
|
||||||
|
|
||||||
|
// $('.light-color-selector').hide();
|
||||||
|
// $colorSelector.toggle();
|
||||||
|
|
||||||
|
// $('.light-item').removeClass('selected');
|
||||||
|
// $lightItem.addClass('selected');
|
||||||
|
// });
|
||||||
|
|
||||||
|
// $lightsList.on('click touch', '.light-ctrl-switch', function(e) {
|
||||||
|
// e.stopPropagation();
|
||||||
|
|
||||||
|
// var $lightItem = $($(this).parents('.light-item'));
|
||||||
|
// var type = $lightItem.data('type');
|
||||||
|
// var name = $lightItem.data('name');
|
||||||
|
// var isOn = $lightItem.data('on');
|
||||||
|
// var action = 'light.hue.' + (isOn ? 'off' : 'on');
|
||||||
|
// var key = (type == 'light' ? 'lights' : 'groups');
|
||||||
|
// var args = {
|
||||||
|
// type: 'request',
|
||||||
|
// action: action,
|
||||||
|
// args: {}
|
||||||
|
// };
|
||||||
|
|
||||||
|
// args['args'][key] = [name];
|
||||||
|
// execute(args, function() {
|
||||||
|
// $lightItem.data('on', !isOn);
|
||||||
|
// refreshStatus();
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
|
||||||
|
// $lightsList.on('click touch', '.animation-switch', function(e) {
|
||||||
|
// e.stopPropagation();
|
||||||
|
|
||||||
|
// var turnedOn = $(this).prop('checked');
|
||||||
|
// var args = {};
|
||||||
|
// args['groups'] = $(this).parents('.animation-item').data('name');
|
||||||
|
// args['animation'] = $(this).parents('.animation-item')
|
||||||
|
// .find('input.animation-type:checked').data('type');
|
||||||
|
|
||||||
|
// var $animationCtrl = $(this).parents('.animation-item')
|
||||||
|
// .find('.animation-container').filter(
|
||||||
|
// (index, node) => $(node).data('animation-type') === args['animation']
|
||||||
|
// );
|
||||||
|
|
||||||
|
// var params = $animationCtrl.find('*').filter(
|
||||||
|
// (index, node) => $(node).data('animation-property'))
|
||||||
|
// .toArray().reduce(
|
||||||
|
// (map, input) => {
|
||||||
|
// if ($(input).val().length) {
|
||||||
|
// var val = $(input).val();
|
||||||
|
// val = Array.isArray(val) ? val.map((i) => parseFloat(i)) : parseFloat(val);
|
||||||
|
// map[$(input).data('animation-property')] = val;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return map
|
||||||
|
// }, {}
|
||||||
|
// );
|
||||||
|
|
||||||
|
// for (var p of Object.keys(params)) {
|
||||||
|
// args[p] = params[p];
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (turnedOn) {
|
||||||
|
// execute(
|
||||||
|
// {
|
||||||
|
// type: 'request',
|
||||||
|
// action: 'light.hue.animate',
|
||||||
|
// args: args,
|
||||||
|
// },
|
||||||
|
|
||||||
|
// onSuccess = function() {
|
||||||
|
// $(this).prop('checked', true);
|
||||||
|
// }
|
||||||
|
// );
|
||||||
|
// } else {
|
||||||
|
// execute(
|
||||||
|
// {
|
||||||
|
// type: 'request',
|
||||||
|
// action: 'light.hue.stop_animation',
|
||||||
|
// },
|
||||||
|
|
||||||
|
// onSuccess = function() {
|
||||||
|
// $(this).prop('checked', false);
|
||||||
|
// }
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
// $lightsList.on('mouseup touchend', '.light-slider', function() {
|
||||||
|
// var property = $(this).data('property');
|
||||||
|
// var type = $(this).data('type');
|
||||||
|
// var name = $(this).data('name');
|
||||||
|
// var args = {
|
||||||
|
// type: 'request',
|
||||||
|
// action: 'light.hue.' + property,
|
||||||
|
// args: { value: $(this).val() }
|
||||||
|
// };
|
||||||
|
|
||||||
|
// if (type === 'light') {
|
||||||
|
// args.args.lights = [name];
|
||||||
|
// } else {
|
||||||
|
// args.args.groups = [name];
|
||||||
|
// }
|
||||||
|
|
||||||
|
// execute(args, refreshStatus);
|
||||||
|
// });
|
||||||
|
|
||||||
|
// $lightsList.on('click touch', 'input.animation-type', function(e) {
|
||||||
|
// var type = $(this).data('type');
|
||||||
|
// var $animationContainers = $(this).parents('.animation-item').find('.animation-container')
|
||||||
|
// var $animationContainer = $(this).parents('.animation-item').find('.animation-container')
|
||||||
|
// .filter(function() { return $(this).data('animationType') === type })
|
||||||
|
|
||||||
|
// $animationContainers.hide();
|
||||||
|
// $animationContainer.show();
|
||||||
|
// });
|
||||||
|
|
||||||
|
// if (window.config.light.hue.default_group) {
|
||||||
|
// var $defaultRoomItem = $roomsList.find('.room-item').filter(
|
||||||
|
// (i, r) => $(r).data('name') == window.config.light.hue.default_group);
|
||||||
|
|
||||||
|
// $defaultRoomItem.click();
|
||||||
|
// }
|
||||||
|
};
|
||||||
|
|
||||||
|
var init = function() {
|
||||||
|
initUi();
|
||||||
|
};
|
||||||
|
|
||||||
|
init();
|
||||||
|
});
|
||||||
|
|
|
@ -55,10 +55,11 @@
|
||||||
|
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
{% for plugin in plugins.keys()|sort() %}
|
{% for plugin in plugins.keys()|sort() %}
|
||||||
{% set configuration = plugins[plugin] %}
|
{% with configuration=plugins[plugin], utils=utils %}
|
||||||
<div class="tab-pane plugin-tab-content" id="{% print plugin %}">
|
<div class="tab-pane plugin-tab-content" id="{% print plugin %}">
|
||||||
{% include 'plugins/' + plugin + '.html' %}
|
{% include 'plugins/' + plugin + '.html' %}
|
||||||
</div>
|
</div>
|
||||||
|
{% endwith %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
18
platypush/backend/http/templates/plugins/music.snapcast.html
Normal file
18
platypush/backend/http/templates/plugins/music.snapcast.html
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<script type="text/javascript" src="{{ url_for('static', filename='js/music.snapcast.js') }}"></script>
|
||||||
|
<!-- <link rel="stylesheet" href="{{ url_for('static', filename='css/light.hue.css') }}"></script> -->
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
window.config = window.config || {};
|
||||||
|
window.config.snapcast_hosts = {};
|
||||||
|
|
||||||
|
hosts = JSON.parse('{{ utils.to_json(utils.get_plugin("music.snapcast").backend_hosts) | safe }}');
|
||||||
|
ports = JSON.parse('{{ utils.to_json(utils.get_plugin("music.snapcast").backend_ports) | safe }}');
|
||||||
|
|
||||||
|
for (var i=0; i < hosts.length; i++) {
|
||||||
|
window.config.snapcast_hosts[hosts[i]] = ports[i];
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div id="snapcast-container" class="row">
|
||||||
|
</div>
|
||||||
|
|
|
@ -36,7 +36,8 @@ class MusicSnapcastPlugin(Plugin):
|
||||||
self._latest_req_id_lock = threading.RLock()
|
self._latest_req_id_lock = threading.RLock()
|
||||||
|
|
||||||
backend = get_backend('music.snapcast')
|
backend = get_backend('music.snapcast')
|
||||||
self.backend_hosts = backend.hosts if backend else []
|
self.backend_hosts = backend.hosts if backend else [self.host]
|
||||||
|
self.backend_ports = backend.ports if backend else [self.port]
|
||||||
|
|
||||||
def _get_req_id(self):
|
def _get_req_id(self):
|
||||||
with self._latest_req_id_lock:
|
with self._latest_req_id_lock:
|
||||||
|
|
Loading…
Reference in a new issue