forked from platypush/platypush
- Added Pushbullet JS logic to show mirror notifications on web FE
- Added JS and CSS files for dashboard and widgets
This commit is contained in:
parent
ae6467060b
commit
2f8c74c8e3
12 changed files with 306 additions and 27 deletions
|
@ -203,6 +203,11 @@ button[disabled] {
|
||||||
font-size: 25px;
|
font-size: 25px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#notification-container * > .notification-image img {
|
||||||
|
width: 80%;
|
||||||
|
height: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
#hidden-plugins-container > .plugin {
|
#hidden-plugins-container > .plugin {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
17
platypush/backend/http/static/css/dashboard.css
Normal file
17
platypush/backend/http/static/css/dashboard.css
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
body {
|
||||||
|
background: rgba(240,240,245,1.0);
|
||||||
|
font-family: "Raleway", "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
#widgets-container {
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.widget {
|
||||||
|
background: white;
|
||||||
|
border-radius: 5px;
|
||||||
|
height: 18em;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 2px 2px 0 rgba(0,0,0,0.16), 0 0 0 1px rgba(0,0,0,0.08);
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
.widget.date-time-weather {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-time-weather-container {
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1.temperature {
|
||||||
|
font-size: 45px;
|
||||||
|
margin: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.widget.date-time-weather * > .time {
|
||||||
|
font-size: 22px;
|
||||||
|
}
|
||||||
|
|
49
platypush/backend/http/static/css/widgets/music.css
Normal file
49
platypush/backend/http/static/css/widgets/music.css
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
.music-container {
|
||||||
|
width: inherit;
|
||||||
|
height: inherit;
|
||||||
|
display: table-cell;
|
||||||
|
vertical-align: middle;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.track-info {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.track-info > .artist {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.track-info > .title {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-bar {
|
||||||
|
height: 7.5px;
|
||||||
|
background: #ddd;
|
||||||
|
margin: 7.5px;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-bar > .elapsed {
|
||||||
|
height: 7.5px;
|
||||||
|
background: #98ffb0;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-elapsed {
|
||||||
|
text-align: left;
|
||||||
|
margin-left: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-total {
|
||||||
|
float: right;
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-elapsed, .time-total {
|
||||||
|
color: rgba(0, 0, 0, 0.7);
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
27
platypush/backend/http/static/js/pushbullet.js
Normal file
27
platypush/backend/http/static/js/pushbullet.js
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
$(document).ready(function() {
|
||||||
|
var onEvent = function(event) {
|
||||||
|
switch (event.args.type) {
|
||||||
|
case 'platypush.message.event.pushbullet.PushbulletEvent':
|
||||||
|
if (event.args.push_type === 'mirror') {
|
||||||
|
createNotification({
|
||||||
|
'title': event.args.title,
|
||||||
|
'text': event.args.body,
|
||||||
|
'image': 'data:image/png;base64, ' + event.args.icon,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var initEvents = function() {
|
||||||
|
window.registerEventListener(onEvent);
|
||||||
|
};
|
||||||
|
|
||||||
|
var init = function() {
|
||||||
|
initEvents();
|
||||||
|
};
|
||||||
|
|
||||||
|
init();
|
||||||
|
});
|
||||||
|
|
123
platypush/backend/http/static/js/widgets/music.js
Normal file
123
platypush/backend/http/static/js/widgets/music.js
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
$(document).ready(function() {
|
||||||
|
var $widget = $('.widget.music'),
|
||||||
|
$trackContainer = $widget.find('.track-container'),
|
||||||
|
$timeContainer = $widget.find('.time-container'),
|
||||||
|
$noTrackElement = $trackContainer.find('.no-track-info'),
|
||||||
|
$trackElement = $trackContainer.find('.track-info'),
|
||||||
|
$artistElement = $trackElement.find('[data-bind=artist]'),
|
||||||
|
$titleElement = $trackElement.find('[data-bind=title]'),
|
||||||
|
$timeElapsedElement = $timeContainer.find('.time-elapsed'),
|
||||||
|
$timeTotalElement = $timeContainer.find('.time-total'),
|
||||||
|
$elapsedTimeBar = $widget.find('.time-bar > .elapsed'),
|
||||||
|
timeElapsed,
|
||||||
|
timeTotal,
|
||||||
|
refreshElapsedInterval;
|
||||||
|
|
||||||
|
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.MusicPlayEvent':
|
||||||
|
case 'platypush.message.event.music.MusicPauseEvent':
|
||||||
|
refreshTrack(event.args.track);
|
||||||
|
|
||||||
|
case 'platypush.message.event.music.MusicStopEvent':
|
||||||
|
refreshStatus(event.args.status);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var initEvents = function() {
|
||||||
|
window.registerEventListener(onEvent);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
var setState = function(state) {
|
||||||
|
if (state === 'play') {
|
||||||
|
$noTrackElement.hide();
|
||||||
|
$trackElement.show();
|
||||||
|
$timeContainer.show();
|
||||||
|
} else if (state === 'pause') {
|
||||||
|
$noTrackElement.hide();
|
||||||
|
$trackElement.show();
|
||||||
|
$timeContainer.hide();
|
||||||
|
} else if (state === 'stop') {
|
||||||
|
$noTrackElement.show();
|
||||||
|
$trackElement.hide();
|
||||||
|
$timeContainer.hide();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var secondsToTimeString = function(seconds) {
|
||||||
|
seconds = parseInt(seconds);
|
||||||
|
|
||||||
|
if (seconds) {
|
||||||
|
return (parseInt(seconds/60) + ':' +
|
||||||
|
(seconds%60 < 10 ? '0' : '') + seconds%60);
|
||||||
|
} else {
|
||||||
|
return '-:--';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var setTrackTime = function(time) {
|
||||||
|
$timeTotalElement.text(secondsToTimeString(time));
|
||||||
|
timeTotal = parseInt(time);
|
||||||
|
};
|
||||||
|
|
||||||
|
var setTrackElapsed = function(time) {
|
||||||
|
if (refreshElapsedInterval) {
|
||||||
|
clearInterval(refreshElapsedInterval);
|
||||||
|
refreshElapsedInterval = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
timeElapsed = parseInt(time);
|
||||||
|
$timeElapsedElement.text(secondsToTimeString(timeElapsed));
|
||||||
|
|
||||||
|
var ratio = 100 * Math.min(timeElapsed/timeTotal, 1);
|
||||||
|
$elapsedTimeBar.css('width', ratio + '%');
|
||||||
|
|
||||||
|
refreshElapsedInterval = setInterval(function() {
|
||||||
|
timeElapsed += 1;
|
||||||
|
ratio = 100 * Math.min(timeElapsed/timeTotal, 1);
|
||||||
|
$elapsedTimeBar.css('width', ratio + '%');
|
||||||
|
$timeElapsedElement.text(secondsToTimeString(timeElapsed));
|
||||||
|
}, 1000);
|
||||||
|
};
|
||||||
|
|
||||||
|
var refreshStatus = function(status) {
|
||||||
|
setState(state=status.state);
|
||||||
|
if ('elapsed' in status) {
|
||||||
|
setTrackElapsed(status.elapsed);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var refreshTrack = function(track) {
|
||||||
|
setTrackTime(track.time);
|
||||||
|
$artistElement.text(track.artist);
|
||||||
|
$titleElement.text(track.title);
|
||||||
|
};
|
||||||
|
|
||||||
|
var initWidget = function() {
|
||||||
|
$.when(
|
||||||
|
execute({ type: 'request', action: 'music.mpd.currentsong' }),
|
||||||
|
execute({ type: 'request', action: 'music.mpd.status' })
|
||||||
|
).done(function(t, s) {
|
||||||
|
refreshTrack(t[0].response.output);
|
||||||
|
refreshStatus(s[0].response.output);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var init = function() {
|
||||||
|
initEvents();
|
||||||
|
initWidget();
|
||||||
|
};
|
||||||
|
|
||||||
|
init();
|
||||||
|
});
|
||||||
|
|
|
@ -7,12 +7,14 @@
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/normalize.css') }}"></script>
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/normalize.css') }}"></script>
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='font-awesome/css/font-awesome.min.css') }}"></script>
|
<link rel="stylesheet" href="{{ url_for('static', filename='font-awesome/css/font-awesome.min.css') }}"></script>
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/application.css') }}"></script>
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/application.css') }}"></script>
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/dashboard.css') }}"></script>
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/toggles.css') }}"></script>
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/toggles.css') }}"></script>
|
||||||
|
|
||||||
<script type="text/javascript" src="{{ url_for('static', filename='js/jquery-3.3.1.min.js') }}"></script>
|
<script type="text/javascript" src="{{ url_for('static', filename='js/jquery-3.3.1.min.js') }}"></script>
|
||||||
<script type="text/javascript" src="{{ url_for('static', filename='js/skeleton-tabs.js') }}"></script>
|
<script type="text/javascript" src="{{ url_for('static', filename='js/skeleton-tabs.js') }}"></script>
|
||||||
<script type="text/javascript" src="{{ url_for('static', filename='js/application.js') }}"></script>
|
<script type="text/javascript" src="{{ url_for('static', filename='js/application.js') }}"></script>
|
||||||
<script type="text/javascript" src="{{ url_for('static', filename='js/dashboard.js') }}"></script>
|
<script type="text/javascript" src="{{ url_for('static', filename='js/dashboard.js') }}"></script>
|
||||||
|
<script type="text/javascript" src="{{ url_for('static', filename='js/pushbullet.js') }}"></script>
|
||||||
<script type="text/javascript" src="{{ url_for('static', filename='js/assistant.google.js') }}"></script>
|
<script type="text/javascript" src="{{ url_for('static', filename='js/assistant.google.js') }}"></script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
window.websocket_port = {% print(websocket_port) %}
|
window.websocket_port = {% print(websocket_port) %}
|
||||||
|
@ -27,15 +29,17 @@
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<main>
|
<main>
|
||||||
{% for widget_name, widget in config['widgets'].items() %}
|
<div id="widgets-container">
|
||||||
<div class="widget {% print(utils.widget_columns_to_html_class(widget['columns'])) %}
|
{% for widget_name, widget in config['widgets'].items() %}
|
||||||
{% print(widget_name) %}"
|
<div class="widget {% print(utils.widget_columns_to_html_class(widget['columns'])) %}
|
||||||
id="{% print(widget['id'] if 'id' in widget else widget_name) %}">
|
{% print(widget_name) %}"
|
||||||
{% with properties=widget %}
|
id="{% print(widget['id'] if 'id' in widget else widget_name) %}">
|
||||||
{% include 'widgets/' + widget_name + '.html' %}
|
{% with properties=widget %}
|
||||||
{% endwith %}
|
{% include 'widgets/' + widget_name + '.html' %}
|
||||||
</div>
|
{% endwith %}
|
||||||
{% endfor %}
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="notification-container"></div>
|
<div id="notification-container"></div>
|
||||||
</main>
|
</main>
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
<script type="text/javascript" src="{{ url_for('static', filename='js/jquery-3.3.1.min.js') }}"></script>
|
<script type="text/javascript" src="{{ url_for('static', filename='js/jquery-3.3.1.min.js') }}"></script>
|
||||||
<script type="text/javascript" src="{{ url_for('static', filename='js/skeleton-tabs.js') }}"></script>
|
<script type="text/javascript" src="{{ url_for('static', filename='js/skeleton-tabs.js') }}"></script>
|
||||||
<script type="text/javascript" src="{{ url_for('static', filename='js/application.js') }}"></script>
|
<script type="text/javascript" src="{{ url_for('static', filename='js/application.js') }}"></script>
|
||||||
|
<script type="text/javascript" src="{{ url_for('static', filename='js/pushbullet.js') }}"></script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
window.websocket_port = {% print(websocket_port) %}
|
window.websocket_port = {% print(websocket_port) %}
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,18 @@
|
||||||
<script type="text/javascript" src="{{ url_for('static', filename='js/widgets/date-time-weather.js') }}"></script>
|
<script type="text/javascript" src="{{ url_for('static', filename='js/widgets/date-time-weather.js') }}"></script>
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/widgets/date-time-weather.css') }}"></script>
|
||||||
|
|
||||||
<p class="date" data-bind="date"></p>
|
<div class="date-time-weather-container">
|
||||||
<p class="time" data-bind="time"></p>
|
<div class="date" data-bind="date"></div>
|
||||||
|
<div class="time" data-bind="time"></div>
|
||||||
|
|
||||||
<h1 class="temperature">
|
<h1 class="temperature">
|
||||||
<span data-bind="temperature">N/A</span>°
|
<span data-bind="temperature">N/A</span>°
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p class="forecast" data-bind="forecast"></p>
|
<div class="forecast" data-bind="forecast"></div>
|
||||||
|
|
||||||
<!-- <p class="sensor-temperature"> -->
|
<!-- <p class="sensor-temperature"> -->
|
||||||
<!-- <span data-bind="sensor-temperature">N/A</span>° -->
|
<!-- <span data-bind="sensor-temperature">N/A</span>° -->
|
||||||
<!-- </p> -->
|
<!-- </p> -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
31
platypush/backend/http/templates/widgets/music.html
Normal file
31
platypush/backend/http/templates/widgets/music.html
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
<script type="text/javascript" src="{{ url_for('static', filename='js/widgets/music.js') }}"></script>
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/widgets/music.css') }}"></script>
|
||||||
|
|
||||||
|
<div class="music-container">
|
||||||
|
<div class="track-container">
|
||||||
|
<div class="no-track-info">No media is being played</div>
|
||||||
|
<div class="track-info">
|
||||||
|
<div class="artist" data-bind="artist"></div>
|
||||||
|
<div class="title" data-bind="title"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="time-container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="time-bar">
|
||||||
|
<div class="elapsed"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="six columns">
|
||||||
|
<div class="time-elapsed" data-bind="time-elapsed"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="six columns">
|
||||||
|
<div class="time-total" data-bind="time-total"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
|
@ -60,14 +60,6 @@ class MusicMpdBackend(Backend):
|
||||||
self.bus.post(PlaylistChangeEvent(changes=changes))
|
self.bus.post(PlaylistChangeEvent(changes=changes))
|
||||||
last_playlist = playlist
|
last_playlist = playlist
|
||||||
|
|
||||||
if 'title' in track and ('artist' not in track
|
|
||||||
or not track['artist']
|
|
||||||
or re.search('^tunein:', track['file'])):
|
|
||||||
m = re.match('^\s*(.+?)\s+-\s+(.*)\s*$', track['title'])
|
|
||||||
if m and m.group(1) and m.group(2):
|
|
||||||
track['artist'] = m.group(1)
|
|
||||||
track['title'] = m.group(2)
|
|
||||||
|
|
||||||
if state == 'play' and track != last_track:
|
if state == 'play' and track != last_track:
|
||||||
self.bus.post(NewPlayingTrackEvent(status=status, track=track))
|
self.bus.post(NewPlayingTrackEvent(status=status, track=track))
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import mpd
|
import mpd
|
||||||
|
import re
|
||||||
|
|
||||||
from platypush.message.response import Response
|
from platypush.message.response import Response
|
||||||
|
|
||||||
|
@ -105,7 +106,16 @@ class MusicMpdPlugin(MusicPlugin):
|
||||||
return Response(output=self.client.status())
|
return Response(output=self.client.status())
|
||||||
|
|
||||||
def currentsong(self):
|
def currentsong(self):
|
||||||
return Response(output=self.client.currentsong())
|
track = self.client.currentsong()
|
||||||
|
if 'title' in track and ('artist' not in track
|
||||||
|
or not track['artist']
|
||||||
|
or re.search('^tunein:', track['file'])):
|
||||||
|
m = re.match('^\s*(.+?)\s+-\s+(.*)\s*$', track['title'])
|
||||||
|
if m and m.group(1) and m.group(2):
|
||||||
|
track['artist'] = m.group(1)
|
||||||
|
track['title'] = m.group(2)
|
||||||
|
|
||||||
|
return Response(output=track)
|
||||||
|
|
||||||
def playlistinfo(self):
|
def playlistinfo(self):
|
||||||
return Response(output=self.client.playlistinfo())
|
return Response(output=self.client.playlistinfo())
|
||||||
|
|
Loading…
Reference in a new issue