forked from platypush/platypush
- Added hidden plugins configuration for plugins that shouldn't be shown
on the web panel as tabs - Added support for popup notifications on the web panel - Added voice assistant interactive notifications to the web panel - Added new playing music notifications to the web panel
This commit is contained in:
parent
fcdc4d1af8
commit
02e951bd57
12 changed files with 241 additions and 129 deletions
|
@ -25,6 +25,9 @@ class HttpBackend(Backend):
|
||||||
|
|
||||||
websocket_ping_tries = 3
|
websocket_ping_tries = 3
|
||||||
websocket_ping_timeout = 60.0
|
websocket_ping_timeout = 60.0
|
||||||
|
hidden_plugins = {
|
||||||
|
'assistant.google'
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(self, port=8008, websocket_port=8009, disable_websocket=False,
|
def __init__(self, port=8008, websocket_port=8009, disable_websocket=False,
|
||||||
token=None, **kwargs):
|
token=None, **kwargs):
|
||||||
|
@ -98,13 +101,17 @@ class HttpBackend(Backend):
|
||||||
def index():
|
def index():
|
||||||
configured_plugins = Config.get_plugins()
|
configured_plugins = Config.get_plugins()
|
||||||
enabled_plugins = {}
|
enabled_plugins = {}
|
||||||
|
hidden_plugins = {}
|
||||||
|
|
||||||
for plugin, conf in configured_plugins.items():
|
for plugin, conf in configured_plugins.items():
|
||||||
template_file = os.path.join('plugins', plugin + '.html')
|
template_file = os.path.join('plugins', plugin + '.html')
|
||||||
if os.path.isfile(os.path.join(template_dir, template_file)):
|
if os.path.isfile(os.path.join(template_dir, template_file)):
|
||||||
enabled_plugins[plugin] = conf
|
if plugin in self.hidden_plugins:
|
||||||
|
hidden_plugins[plugin] = conf
|
||||||
|
else:
|
||||||
|
enabled_plugins[plugin] = conf
|
||||||
|
|
||||||
return render_template('index.html', plugins=enabled_plugins,
|
return render_template('index.html', plugins=enabled_plugins, hidden_plugins=hidden_plugins,
|
||||||
token=self.token, websocket_port=self.websocket_port)
|
token=self.token, websocket_port=self.websocket_port)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -144,3 +144,66 @@ button[disabled] {
|
||||||
text-align: right !important;
|
text-align: right !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#notification-container {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#notification-container > .notification {
|
||||||
|
background: rgba(180,245,188,0.85);
|
||||||
|
border: 1px solid #bbb;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
margin-right: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#notification-container > .notification:hover {
|
||||||
|
background: rgba(160,235,168,0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
#notification-container * > .notification-title {
|
||||||
|
padding: 4px 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-transform: capitalize;
|
||||||
|
line-height: 30px;
|
||||||
|
letter-spacing: .1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#notification-container * > .notification-body {
|
||||||
|
height: 6em;
|
||||||
|
overflow: hidden;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
letter-spacing: .05rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#notification-container * > .notification-text {
|
||||||
|
margin-left: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#notification-container * > .notification-image {
|
||||||
|
height: 100%;
|
||||||
|
text-align: center;
|
||||||
|
padding-top: .6em;
|
||||||
|
padding-bottom: .6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#notification-container * > .notification-image-item {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#notification-container * > .fa.notification-image-item {
|
||||||
|
margin-top: .8em;
|
||||||
|
margin-left: .2em;
|
||||||
|
font-size: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#hidden-plugins-container > .plugin {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
0
platypush/backend/http/static/css/assistant.google.css
Normal file
0
platypush/backend/http/static/css/assistant.google.css
Normal file
|
@ -176,3 +176,112 @@ $(document).ready(function() {
|
||||||
init();
|
init();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function execute(request, onSuccess, onError, onComplete) {
|
||||||
|
request['target'] = 'localhost';
|
||||||
|
return $.ajax({
|
||||||
|
type: 'POST',
|
||||||
|
url: '/execute',
|
||||||
|
contentType: 'application/json',
|
||||||
|
dataType: 'json',
|
||||||
|
data: JSON.stringify(request),
|
||||||
|
complete: function() {
|
||||||
|
if (onComplete) {
|
||||||
|
onComplete();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
if (onError) {
|
||||||
|
onError(xhr, status, error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
success: function(response, status, xhr) {
|
||||||
|
if (onSuccess) {
|
||||||
|
onSuccess(response, status, xhr);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeSend: function(xhr) {
|
||||||
|
if (window.token) {
|
||||||
|
xhr.setRequestHeader('X-Token', window.token);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function createNotification(options) {
|
||||||
|
var $notificationContainer = $('#notification-container');
|
||||||
|
var $notification = $('<div></div>').addClass('notification');
|
||||||
|
var $title = $('<div></div>').addClass('notification-title');
|
||||||
|
var timeout = 'timeout' in options ? options.timeout : 10000;
|
||||||
|
|
||||||
|
if ('title' in options) {
|
||||||
|
$title.text(options.title);
|
||||||
|
}
|
||||||
|
|
||||||
|
var $body = $('<div></div>').addClass('notification-body');
|
||||||
|
var $imgDiv = $('<div></div>').addClass('notification-image').addClass('three columns');
|
||||||
|
var $img = $('<i></i>').addClass('fa fa-bell').addClass('three columns').addClass('notification-image-item');
|
||||||
|
var $text = $('<div></div>').addClass('notification-text').addClass('nine columns')
|
||||||
|
|
||||||
|
if ('image' in options) {
|
||||||
|
$img = $('<img></img>').attr('src', options.image);
|
||||||
|
} else if ('icon' in options) {
|
||||||
|
$img.removeClass('fa-bell').addClass('fa-' + options.icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('text' in options) {
|
||||||
|
$text.text(options.text);
|
||||||
|
} else if ('html' in options) {
|
||||||
|
$text.html(options.html);
|
||||||
|
}
|
||||||
|
|
||||||
|
$img.appendTo($imgDiv);
|
||||||
|
$imgDiv.appendTo($body);
|
||||||
|
$text.appendTo($body);
|
||||||
|
|
||||||
|
var clickHandler;
|
||||||
|
var removeTimeoutId;
|
||||||
|
|
||||||
|
var removeNotification = function() {
|
||||||
|
$notification.fadeOut(300, function() {
|
||||||
|
$notification.remove();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var setRemoveTimeout = function() {
|
||||||
|
if (timeout) {
|
||||||
|
removeTimeoutId = setTimeout(removeNotification, timeout);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
setRemoveTimeout();
|
||||||
|
|
||||||
|
if ('onclick' in options) {
|
||||||
|
clickHandler = options.onclick;
|
||||||
|
} else {
|
||||||
|
clickHandler = removeNotification;
|
||||||
|
}
|
||||||
|
|
||||||
|
$notification.on('click', clickHandler);
|
||||||
|
$notification.hover(
|
||||||
|
// on hover start
|
||||||
|
function() {
|
||||||
|
if (removeTimeoutId) {
|
||||||
|
clearTimeout(removeTimeoutId);
|
||||||
|
removeTimeoutId = undefined;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// on hover end
|
||||||
|
function() {
|
||||||
|
if (timeout) {
|
||||||
|
setRemoveTimeout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
$title.appendTo($notification);
|
||||||
|
$body.appendTo($notification);
|
||||||
|
$notification.prependTo($notificationContainer);
|
||||||
|
$notification.fadeIn();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
36
platypush/backend/http/static/js/assistant.google.js
Normal file
36
platypush/backend/http/static/js/assistant.google.js
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
$(document).ready(function() {
|
||||||
|
var onEvent = function(event) {
|
||||||
|
switch (event.args.type) {
|
||||||
|
case 'platypush.message.event.assistant.ConversationStartEvent':
|
||||||
|
createNotification({
|
||||||
|
'text': 'Assistant listening',
|
||||||
|
'icon': 'microphone',
|
||||||
|
});
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'platypush.message.event.assistant.SpeechRecognizedEvent':
|
||||||
|
createNotification({
|
||||||
|
'title': 'Speech recognized',
|
||||||
|
'text': event.args.phrase,
|
||||||
|
'icon': 'microphone',
|
||||||
|
});
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'platypush.message.event.assistant.ConversationEndEvent':
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var initEvents = function() {
|
||||||
|
window.registerEventListener(onEvent);
|
||||||
|
};
|
||||||
|
|
||||||
|
var init = function() {
|
||||||
|
initEvents();
|
||||||
|
};
|
||||||
|
|
||||||
|
init();
|
||||||
|
});
|
||||||
|
|
|
@ -3,37 +3,6 @@ $(document).ready(function() {
|
||||||
$controlsContainer = $('.zb-controls-container'),
|
$controlsContainer = $('.zb-controls-container'),
|
||||||
curDirection = undefined;
|
curDirection = undefined;
|
||||||
|
|
||||||
var execute = function(request, onSuccess, onError, onComplete) {
|
|
||||||
request['target'] = 'localhost';
|
|
||||||
return $.ajax({
|
|
||||||
type: 'POST',
|
|
||||||
url: '/execute',
|
|
||||||
contentType: 'application/json',
|
|
||||||
dataType: 'json',
|
|
||||||
data: JSON.stringify(request),
|
|
||||||
complete: function() {
|
|
||||||
if (onComplete) {
|
|
||||||
onComplete();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
error: function(xhr, status, error) {
|
|
||||||
if (onError) {
|
|
||||||
onError(xhr, status, error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
success: function(response, status, xhr) {
|
|
||||||
if (onSuccess) {
|
|
||||||
onSuccess(response, status, xhr);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
beforeSend: function(xhr) {
|
|
||||||
if (window.token) {
|
|
||||||
xhr.setRequestHeader('X-Token', window.token);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
var initBindings = function() {
|
var initBindings = function() {
|
||||||
$controlsContainer.on('mousedown touchstart', '.zb-ctrl-btn[data-direction]', function() {
|
$controlsContainer.on('mousedown touchstart', '.zb-ctrl-btn[data-direction]', function() {
|
||||||
var direction = $(this).data('direction');
|
var direction = $(this).data('direction');
|
||||||
|
|
|
@ -6,37 +6,6 @@ $(document).ready(function() {
|
||||||
$lightsList = $('#lights-list'),
|
$lightsList = $('#lights-list'),
|
||||||
$scenesList = $('#scenes-list');
|
$scenesList = $('#scenes-list');
|
||||||
|
|
||||||
var execute = function(request, onSuccess, onError, onComplete) {
|
|
||||||
request['target'] = 'localhost';
|
|
||||||
return $.ajax({
|
|
||||||
type: 'POST',
|
|
||||||
url: '/execute',
|
|
||||||
contentType: 'application/json',
|
|
||||||
dataType: 'json',
|
|
||||||
data: JSON.stringify(request),
|
|
||||||
complete: function() {
|
|
||||||
if (onComplete) {
|
|
||||||
onComplete();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
error: function(xhr, status, error) {
|
|
||||||
if (onError) {
|
|
||||||
onError(xhr, status, error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
success: function(response, status, xhr) {
|
|
||||||
if (onSuccess) {
|
|
||||||
onSuccess(response, status, xhr);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
beforeSend: function(xhr) {
|
|
||||||
if (window.token) {
|
|
||||||
xhr.setRequestHeader('X-Token', window.token);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
var createPowerToggleElement = function(data) {
|
var createPowerToggleElement = function(data) {
|
||||||
var id = data['type'] + '_' + data['id'];
|
var id = data['type'] + '_' + data['id'];
|
||||||
var $powerToggle = $('<div></div>').addClass('toggle toggle--push light-ctrl-switch-container');
|
var $powerToggle = $('<div></div>').addClass('toggle toggle--push light-ctrl-switch-container');
|
||||||
|
|
|
@ -16,37 +16,6 @@ $(document).ready(function() {
|
||||||
$resetSearchBtn = $('#music-search-reset');
|
$resetSearchBtn = $('#music-search-reset');
|
||||||
$doSearchBtns = $('.do-search-btns');
|
$doSearchBtns = $('.do-search-btns');
|
||||||
|
|
||||||
var execute = function(request, onSuccess, onError, onComplete) {
|
|
||||||
request['target'] = 'localhost';
|
|
||||||
$.ajax({
|
|
||||||
type: 'POST',
|
|
||||||
url: '/execute',
|
|
||||||
contentType: 'application/json',
|
|
||||||
dataType: 'json',
|
|
||||||
data: JSON.stringify(request),
|
|
||||||
complete: function() {
|
|
||||||
if (onComplete) {
|
|
||||||
onComplete();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
error: function(xhr, status, error) {
|
|
||||||
if (onError) {
|
|
||||||
onError(xhr, status, error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
success: function(response, status, xhr) {
|
|
||||||
if (onSuccess) {
|
|
||||||
onSuccess(response, status, xhr);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
beforeSend: function(xhr) {
|
|
||||||
if (window.token) {
|
|
||||||
xhr.setRequestHeader('X-Token', window.token);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
var formatMinutes = function(time) {
|
var formatMinutes = function(time) {
|
||||||
if (typeof time === 'string') {
|
if (typeof time === 'string') {
|
||||||
time = parseInt(time);
|
time = parseInt(time);
|
||||||
|
@ -198,10 +167,17 @@ $(document).ready(function() {
|
||||||
|
|
||||||
var onEvent = function(event) {
|
var onEvent = function(event) {
|
||||||
switch (event.args.type) {
|
switch (event.args.type) {
|
||||||
|
case 'platypush.message.event.music.NewPlayingTrackEvent':
|
||||||
|
createNotification({
|
||||||
|
'icon': 'play',
|
||||||
|
'html': '<b>' + ('artist' in event.args.track ? event.args.track.artist : '')
|
||||||
|
+ '</b><br/>'
|
||||||
|
+ ('title' in event.args.track ? event.args.track.title : '[No name]'),
|
||||||
|
});
|
||||||
|
|
||||||
case 'platypush.message.event.music.MusicStopEvent':
|
case 'platypush.message.event.music.MusicStopEvent':
|
||||||
case 'platypush.message.event.music.MusicPlayEvent':
|
case 'platypush.message.event.music.MusicPlayEvent':
|
||||||
case 'platypush.message.event.music.MusicPauseEvent':
|
case 'platypush.message.event.music.MusicPauseEvent':
|
||||||
case 'platypush.message.event.music.NewPlayingTrackEvent':
|
|
||||||
updateControls(status=event.args.status, track=event.args.track);
|
updateControls(status=event.args.status, track=event.args.track);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
|
@ -2,37 +2,6 @@ $(document).ready(function() {
|
||||||
var $container = $('#tts-container'),
|
var $container = $('#tts-container'),
|
||||||
$ttsForm = $('#tts-form');
|
$ttsForm = $('#tts-form');
|
||||||
|
|
||||||
var execute = function(request, onSuccess, onError, onComplete) {
|
|
||||||
request['target'] = 'localhost';
|
|
||||||
return $.ajax({
|
|
||||||
type: 'POST',
|
|
||||||
url: '/execute',
|
|
||||||
contentType: 'application/json',
|
|
||||||
dataType: 'json',
|
|
||||||
data: JSON.stringify(request),
|
|
||||||
complete: function() {
|
|
||||||
if (onComplete) {
|
|
||||||
onComplete();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
error: function(xhr, status, error) {
|
|
||||||
if (onError) {
|
|
||||||
onError(xhr, status, error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
success: function(response, status, xhr) {
|
|
||||||
if (onSuccess) {
|
|
||||||
onSuccess(response, status, xhr);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
beforeSend: function(xhr) {
|
|
||||||
if (window.token) {
|
|
||||||
xhr.setRequestHeader('X-Token', window.token);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
var initBindings = function() {
|
var initBindings = function() {
|
||||||
$ttsForm.on('submit', function(event) {
|
$ttsForm.on('submit', function(event) {
|
||||||
var formData = $(this).serializeArray().reduce(function(obj, item) {
|
var formData = $(this).serializeArray().reduce(function(obj, item) {
|
||||||
|
|
|
@ -57,6 +57,17 @@
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="notification-container"></div>
|
||||||
|
|
||||||
|
<div id="hidden-plugins-container">
|
||||||
|
{% for plugin in hidden_plugins.keys()|sort() %}
|
||||||
|
{% set configuration = plugins[plugin] %}
|
||||||
|
<div class="plugin" id="{% print plugin %}">
|
||||||
|
{% include 'plugins/' + plugin + '.html' %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
<script type="text/javascript" src="{{ url_for('static', filename='js/assistant.google.js') }}"></script>
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/assistant.google.css') }}"></script>
|
||||||
|
|
|
@ -6,12 +6,12 @@
|
||||||
<div class="eight columns">
|
<div class="eight columns">
|
||||||
<input type="text" name="phrase" placeholder="Text to say">
|
<input type="text" name="phrase" placeholder="Text to say">
|
||||||
</div>
|
</div>
|
||||||
<div class="three columns">
|
<div class="two columns">
|
||||||
<input type="text" name="lang" placeholder="Lang code">
|
<input type="text" name="lang" placeholder="Lang code">
|
||||||
</div>
|
</div>
|
||||||
<div class="one column">
|
<div class="one column">
|
||||||
<button type="submit">
|
<button type="submit">
|
||||||
<i class="fa fa-microphone"></i>
|
<i class="fa fa-volume-up"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
Loading…
Reference in a new issue