- 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_timeout = 60.0
|
||||
hidden_plugins = {
|
||||
'assistant.google'
|
||||
}
|
||||
|
||||
def __init__(self, port=8008, websocket_port=8009, disable_websocket=False,
|
||||
token=None, **kwargs):
|
||||
|
@ -98,13 +101,17 @@ class HttpBackend(Backend):
|
|||
def index():
|
||||
configured_plugins = Config.get_plugins()
|
||||
enabled_plugins = {}
|
||||
hidden_plugins = {}
|
||||
|
||||
for plugin, conf in configured_plugins.items():
|
||||
template_file = os.path.join('plugins', plugin + '.html')
|
||||
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)
|
||||
|
||||
|
||||
|
|
|
@ -144,3 +144,66 @@ button[disabled] {
|
|||
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();
|
||||
});
|
||||
|
||||
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'),
|
||||
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() {
|
||||
$controlsContainer.on('mousedown touchstart', '.zb-ctrl-btn[data-direction]', function() {
|
||||
var direction = $(this).data('direction');
|
||||
|
|
|
@ -6,37 +6,6 @@ $(document).ready(function() {
|
|||
$lightsList = $('#lights-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 id = data['type'] + '_' + data['id'];
|
||||
var $powerToggle = $('<div></div>').addClass('toggle toggle--push light-ctrl-switch-container');
|
||||
|
|
|
@ -16,37 +16,6 @@ $(document).ready(function() {
|
|||
$resetSearchBtn = $('#music-search-reset');
|
||||
$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) {
|
||||
if (typeof time === 'string') {
|
||||
time = parseInt(time);
|
||||
|
@ -198,10 +167,17 @@ $(document).ready(function() {
|
|||
|
||||
var onEvent = function(event) {
|
||||
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.MusicPlayEvent':
|
||||
case 'platypush.message.event.music.MusicPauseEvent':
|
||||
case 'platypush.message.event.music.NewPlayingTrackEvent':
|
||||
updateControls(status=event.args.status, track=event.args.track);
|
||||
break;
|
||||
|
||||
|
|
|
@ -2,37 +2,6 @@ $(document).ready(function() {
|
|||
var $container = $('#tts-container'),
|
||||
$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() {
|
||||
$ttsForm.on('submit', function(event) {
|
||||
var formData = $(this).serializeArray().reduce(function(obj, item) {
|
||||
|
|
|
@ -57,6 +57,17 @@
|
|||
</div>
|
||||
{% endfor %}
|
||||
</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>
|
||||
<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">
|
||||
<input type="text" name="phrase" placeholder="Text to say">
|
||||
</div>
|
||||
<div class="three columns">
|
||||
<div class="two columns">
|
||||
<input type="text" name="lang" placeholder="Lang code">
|
||||
</div>
|
||||
<div class="one column">
|
||||
<button type="submit">
|
||||
<i class="fa fa-microphone"></i>
|
||||
<i class="fa fa-volume-up"></i>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
|
Loading…
Reference in a new issue