From decadee00a0a2d52aeaf25e9fab8de4e50faaef6 Mon Sep 17 00:00:00 2001 From: Fabio Manganiello Date: Thu, 12 Apr 2018 13:04:56 +0200 Subject: [PATCH] Implemented music search from web panel --- .../backend/http/static/css/application.css | 174 +++-------- .../backend/http/static/css/music.mpd.css | 173 +++++++++++ .../backend/http/static/js/application.js | 22 ++ platypush/backend/http/static/js/music.mpd.js | 270 +++++++++++++++++- .../http/templates/plugins/music.mpd.html | 89 ++++++ platypush/plugins/music/mpd/__init__.py | 15 + 6 files changed, 607 insertions(+), 136 deletions(-) create mode 100644 platypush/backend/http/static/css/music.mpd.css diff --git a/platypush/backend/http/static/css/application.css b/platypush/backend/http/static/css/application.css index 751097f3a..2dab9f307 100644 --- a/platypush/backend/http/static/css/application.css +++ b/platypush/backend/http/static/css/application.css @@ -20,6 +20,43 @@ header { font-weight: bold; } +.modal { + display: none; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + overflow: hidden; + z-index: 999; + background-color: rgba(10,10,10,0.85); + font-family: "Raleway", "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif; +} + + .modal-container { + margin: 5% auto auto auto; + width: 70%; + background: white; + border-radius: 10px; + } + + .modal-header { + border-bottom: 1px solid #ccc; + margin: 0.5rem auto; + padding: 0.5rem; + text-align: center; + background-color: #f0f0f0; + border-radius: 10px 10px 0 0; + text-transform: uppercase; + font-weight: 400px; + letter-spacing: .1rem; + line-height: 38px; + } + + .modal-body { + padding: 2.5rem 2rem 1.5rem 2rem; + } + #date-time { text-align: right; padding-right: 30px; @@ -56,138 +93,10 @@ button[disabled] { border: 1px solid; } - .playback-controls { - text-align: center; - border-bottom: 1px solid #e8eaf0; - padding-bottom: 12px; - } - - .playback-controls * > button.enabled { - color: #59df3e; - } - .button[disabled] { background: rgba(240,240,240,1) } - .track-info { - text-align: center; - margin: -20px -20px 0 -20px; - padding: 10px 20px; - } - - .track-info > .artist { - font-weight: bold; - display: block; - } - -#player-left-side { - overflow-y: hidden; -} - -#player-right-side { - margin-left: 0; - width: 78%; -} - -#playlist-controls, #browser-controls { - margin-bottom: 7.5px; - padding-bottom: 7.5px; - border-bottom: 1px solid #ddd; - height: 3.8rem; -} - -#playlist-controls { - text-align: right; -} - -#playlist-content, #music-browser { - height: 27.2rem; - overflow-y: scroll; -} - -#music-browser { - padding-top: 0; -} - -#browser-filter { - width: 95%; - margin-bottom: 1.5rem; -} - -#playlist-filter-container { - height: 5rem; -} - -#playlist-filter { - width: 100%; - margin-bottom: 1.5rem; -} - -.music-pane { - height: 40rem; - padding: 15px 15px 0 5px; - background: #f8f8f8; -} - - .music-item { - padding: 5px; - cursor: pointer; - } - - .music-item.selected { - background-color: #c8ffd0 !important; - } - - .music-item:hover { - background-color: #daf8e2 !important; - } - - .music-item:nth-child(odd) { - background-color: #f2f2f2; - } - - .playlist-track.active { - height: 4rem; - padding-top: 1.5rem; - font-size: 1.7rem; - border-radius: 10px; - animation: active-track 5s; - -moz-animation: active-track 5s infinite; - -webkit-animation: active-track 5s infinite; - } - - @keyframes active-track { - 0% { background: #d4ffe3; } - 50% { background: #9cdfb0; } - 100% { background: #d4ffe3; } - } - - @-moz-keyframes active-track { - 0% { background: #d4ffe3; } - 50% { background: #9cdfb0; } - 100% { background: #d4ffe3; } - } - - @-webkit-keyframes active-track { - 0% { background: #d4ffe3; } - 50% { background: #9cdfb0; } - 100% { background: #d4ffe3; } - } - - .playlist-track > .track-time { - text-align: right; - color: #666; - } - -#track-seeker-container { - margin-bottom: 10px; -} - -#volume-ctrl-container { - margin-top: 15px; -} - .slider { -webkit-appearance: none; appearance: none; @@ -226,3 +135,12 @@ button[disabled] { cursor: pointer; } +.btn-primary { + background-color: #d8ffe0 !important; + border: 1px solid #98efb0 !important; +} + +.right-side { + text-align: right !important; +} + diff --git a/platypush/backend/http/static/css/music.mpd.css b/platypush/backend/http/static/css/music.mpd.css new file mode 100644 index 000000000..7c8def7b9 --- /dev/null +++ b/platypush/backend/http/static/css/music.mpd.css @@ -0,0 +1,173 @@ +#player-left-side { + overflow-y: hidden; +} + +#player-right-side { + margin-left: 0; + width: 78%; +} + + .playback-controls { + text-align: center; + border-bottom: 1px solid #e8eaf0; + padding-bottom: 12px; + } + + .playback-controls * > button.enabled { + color: #59df3e; + } + + + .track-info { + text-align: center; + margin: -20px -20px 0 -20px; + padding: 10px 20px; + } + + .track-info > .artist { + font-weight: bold; + display: block; + } + +#playlist-controls, #browser-controls { + margin-bottom: 7.5px; + padding-bottom: 7.5px; + border-bottom: 1px solid #ddd; + height: 3.8rem; +} + +#playlist-controls { + text-align: right; +} + +#playlist-content, #music-browser { + height: 27.2rem; + overflow-y: scroll; +} + +#music-browser { + padding-top: 0; +} + +#browser-filter { + width: 95%; + margin-bottom: 1.5rem; +} + +#playlist-filter-container { + height: 5rem; +} + +#playlist-filter { + width: 100%; + margin-bottom: 1.5rem; +} + +.music-pane { + height: 40rem; + padding: 15px 15px 0 5px; + background: #f8f8f8; +} + + .music-item { + padding: 5px; + cursor: pointer; + } + + .music-item.selected { + background-color: #c8ffd0 !important; + } + + .music-item:hover { + background-color: #daf8e2 !important; + } + + .music-item:nth-child(odd) { + background-color: #f2f2f2; + } + + .playlist-track.active { + height: 4rem; + padding-top: 1.5rem; + font-size: 1.7rem; + border-radius: 10px; + animation: active-track 5s; + -moz-animation: active-track 5s infinite; + -webkit-animation: active-track 5s infinite; + } + + @keyframes active-track { + 0% { background: #d4ffe3; } + 50% { background: #9cdfb0; } + 100% { background: #d4ffe3; } + } + + @-moz-keyframes active-track { + 0% { background: #d4ffe3; } + 50% { background: #9cdfb0; } + 100% { background: #d4ffe3; } + } + + @-webkit-keyframes active-track { + 0% { background: #d4ffe3; } + 50% { background: #9cdfb0; } + 100% { background: #d4ffe3; } + } + + .playlist-track > .track-time { + text-align: right; + color: #666; + } + +#track-seeker-container { + margin-bottom: 10px; +} + +#volume-ctrl-container { + margin-top: 15px; +} + +#music-search-form { + margin-bottom: 1rem; +} + + #music-search-form * > input[type=text] { + width: 100%; + } + + #music-search-form > .row { + padding: 0.5rem; + } + + .music-form-bottom { + text-align: right; + margin-top: 2rem; + border-top: 1px solid #ddd; + } + + .music-form-bottom input { + margin-top: 2rem; + } + +#music-search-results-form { + display: none; + margin-top: -2rem; +} + +#music-search-results-container { + display: none; + max-height: 50rem; + margin-top: -1.4rem; + overflow-y: auto; + overflow-x: hidden; +} + + #music-search-results-head { + padding: 0.5rem 1rem; + background-color: #eaeaea; + border-radius: 5px 5px 0 0; + border-bottom: 1px solid #bbb; + letter-spacing: .1rem; + line-height: 3.5rem; + } + diff --git a/platypush/backend/http/static/js/application.js b/platypush/backend/http/static/js/application.js index 03a35596b..5604e1332 100644 --- a/platypush/backend/http/static/js/application.js +++ b/platypush/backend/http/static/js/application.js @@ -144,10 +144,32 @@ $(document).ready(function() { }); }; + var initModalOpenBindings = function() { + $('body').on('click touch', '[data-modal]', function(event) { + var $source = $(event.target); + var $modal = $($source.data('modal')); + $modal.fadeIn(); + }); + }; + + var initModalCloseBindings = function() { + $('body').on('click touch', '[data-dismiss-modal]', function(event) { + var $source = $(event.target); + var $modal = $($source.data('dismiss-modal')); + $modal.fadeOut(); + }); + }; + + var initModals = function() { + initModalOpenBindings(); + initModalCloseBindings(); + }; + var init = function() { initWebsocket(); initElements(); initDateTime(); + initModals(); }; window.registerEventListener = registerEventListener; diff --git a/platypush/backend/http/static/js/music.mpd.js b/platypush/backend/http/static/js/music.mpd.js index 6f029f5b6..66b8a8a8e 100644 --- a/platypush/backend/http/static/js/music.mpd.js +++ b/platypush/backend/http/static/js/music.mpd.js @@ -6,7 +6,16 @@ $(document).ready(function() { curTrackElapsed = { timestamp: null, elapsed: null, - }; + }, + + $musicSearchForm = $('#music-search-form'), + $musicSearchResults = $('#music-search-results'), + $musicSearchResultsContainer = $('#music-search-results-container'), + $musicSearchResultsForm = $('#music-search-results-form'), + $musicResultsAddBtn = $('#music-results-add'), + $musicResultsPlayBtn = $('#music-results-play'), + $resetSearchBtn = $('#music-search-reset'); + $doSearchBtns = $('.do-search-btns'); var execute = function(request, onSuccess, onError, onComplete) { request['target'] = 'localhost'; @@ -39,6 +48,24 @@ $(document).ready(function() { }); }; + var formatMinutes = function(time) { + if (typeof time === 'string') { + time = parseInt(time); + } else if (isNaN(time)) { + console.warn('Unexpected non-numeric value in formatMinutes'); + console.log(time); + return undefined; + } + + if (!time) { + return '-:--'; + } + + var minutes = parseInt(time/60); + var seconds = time%60; + return (minutes < 10 ? '0' : '') + minutes + ':' + (seconds < 10 ? '0' : '') + seconds; + }; + var updateControls = function(status, track) { var $playbackControls = $('.playback-controls'); var $playlistContent = $('#playlist-content'); @@ -80,7 +107,7 @@ $(document).ready(function() { $curTrack.find('.track').hide(); $curTrack.find('.no-track').show(); - $trackSeeker.attr('disabled', true); + $trackSeeker.prop('disabled', true); $('.seek-time').text('-:--'); break; @@ -91,7 +118,7 @@ $(document).ready(function() { $curTrack.find('.track').show(); $curTrack.find('.no-track').hide(); - $trackSeeker.removeAttr('disabled'); + $trackSeeker.prop('disabled', false); $('#seek-time-elapsed').text(elapsed ? elapsed : '-:--'); $('#seek-time-length').text(length ? length : '-:--'); break; @@ -103,7 +130,7 @@ $(document).ready(function() { $curTrack.find('.track').show(); $curTrack.find('.no-track').hide(); - $trackSeeker.removeAttr('disabled'); + $trackSeeker.prop('disabled', false); $('#seek-time-elapsed').text(elapsed ? elapsed : '-:--'); $('#seek-time-length').text(length ? length : '-:--'); @@ -424,9 +451,9 @@ $(document).ready(function() { $parentElement.on('click touch', onDirectorySelect); $parentElement.appendTo($browserContent); - $addButton.removeAttr('disabled'); + $addButton.prop('disabled', false); } else { - $addButton.attr('disabled', 'disabled'); + $addButton.prop('disabled', true); } for (var directory of directories.sort()) { @@ -506,7 +533,7 @@ $(document).ready(function() { $playbackControls.on('click', function(evt) { var action = $(this).data('action'); var $btn = $(this); - $btn.attr('disabled', true); + $btn.prop('disabled', true); execute( { @@ -520,7 +547,7 @@ $(document).ready(function() { onError=undefined, onComplete = function() { - $btn.removeAttr('disabled'); + $btn.prop('disabled', false); } ); }); @@ -618,10 +645,237 @@ $(document).ready(function() { }); }; + var initSearch = function() { + $musicSearchForm.on('submit', function(event) { + var searchData = $(this).serializeArray().reduce(function(obj, item) { + var value = item.value.trim(); + if (value.length > 0) { + obj[item.name] = item.value; + } + + return obj; + }, {}); + + var args = {}; + var searchFilters = {}; + + if ('any' in searchData) { + args = { + type: 'any', + filter: searchData.any + }; + + searchFilters.any = searchData.any; + } else { + if ('albumartist' in searchData) { + args = { + type: 'albumartist', + filter: searchData.albumartist + }; + + searchFilters.albumartist = searchData.albumartist; + } + + if ('album' in searchData) { + args = { + type: 'album', + filter: searchData.album + }; + + searchFilters.album = searchData.album; + } + + if ('title' in searchData) { + args = { + type: 'title', + filter: searchData.title + }; + + searchFilters.title = searchData.title; + } + } + + $(this).find('input').prop('disabled', true); + + execute( + { + type: 'request', + action: 'music.mpd.search', + args: args + }, + + onSuccess = function(response) { + var results = response.response.output; + if (!results) { + return false; + } + + if (Object.keys(searchFilters).length > 1) { + results = results.filter(function(item) { + return ( + ('title' in searchFilters && 'title' in item + ? item.title.toLowerCase().indexOf( + searchFilters.title.toLowerCase()) >= 0 : true) && + ('album' in searchFilters && 'album' in item + ? item.album.toLowerCase().indexOf( + searchFilters.album.toLowerCase()) >= 0 : true) && + ('albumartist' in searchFilters && 'artist' in item + ? item.artist.toLowerCase().indexOf( + searchFilters.albumartist.toLowerCase()) >= 0 : true) + ); + }); + } + + for (var item of results) { + var $item = $('
') + .addClass('row').addClass('music-item') + .addClass('music-search-item') + .data('file', item.file); + + var $artist = $('
') + .addClass('three columns').addClass('artist') + .addClass('music-search-item-artist') + + if ('artist' in item) { + $artist.text(item.artist); + } else { + $artist.html(' '); + } + + var $title = $('
') + .addClass('four columns').addClass('title') + .addClass('music-search-item-title'); + + if ('title' in item) { + $title.text(item.title); + } else { + $title.html(' '); + } + + var $album = $('
') + .addClass('four columns').addClass('album') + .addClass('music-search-item-album'); + + if ('album' in item) { + $album.text(item.album); + } else { + $album.html(' '); + } + + var $time = $('
') + .addClass('one column').addClass('time') + .addClass('music-search-item-time') + .text('time' in item ? formatMinutes(item.time) : '-:--'); + + $artist.appendTo($item); + $title.appendTo($item); + $album.appendTo($item); + $time.appendTo($item); + $item.appendTo($musicSearchResults); + } + }, + + onError = function(xhr, status, error) { + console.error(error); + }, + + onComplete = function() { + $musicSearchForm.find('input').prop('disabled', false); + $musicSearchForm.hide(); + $musicSearchResultsContainer.show(); + $musicSearchResultsForm.show(); + } + ); + + return false; + }); + + $resetSearchBtn.on('click', function(event) { + $musicSearchResultsForm.hide(); + $musicSearchResultsContainer.hide(); + $musicSearchResults.html(''); + $musicSearchForm.show(); + + $musicResultsAddBtn.removeData('file'); + $musicResultsAddBtn.prop('disabled', true); + $musicResultsPlayBtn.removeData('file'); + $musicResultsPlayBtn.prop('disabled', true); + }); + + $musicSearchResults.on('click', '.music-search-item', function(event) { + var isCurrentlySelected = $(this).hasClass('selected'); + $('.music-search-item').removeClass('selected'); + + if (isCurrentlySelected) { + $musicResultsAddBtn.removeData('file'); + $musicResultsAddBtn.prop('disabled', true); + $musicResultsPlayBtn.removeData('file'); + $musicResultsPlayBtn.prop('disabled', true); + + $(this).removeClass('selected'); + } else { + var file = $(this).data('file'); + + $musicResultsAddBtn.data('file', file); + $musicResultsAddBtn.prop('disabled', false); + $musicResultsPlayBtn.data('file', file); + $musicResultsPlayBtn.prop('disabled', false); + $(this).addClass('selected'); + } + }); + + $musicSearchResultsForm.on('submit', function(event) { + return false; + }); + + $musicResultsAddBtn.on('click', function(event) { + var file = $(this).data('file'); + if (!file) { + return false; + } + + execute( + { + type: 'request', + action: 'music.mpd.add', + args: { + resource: file + } + }, + + onSuccess = function(response) { + initPlaylist(); + } + ); + }); + + $musicResultsPlayBtn.on('click', function(event) { + var file = $(this).data('file'); + if (!file) { + return false; + } + + execute( + { + type: 'request', + action: 'music.mpd.play', + args: { + resource: file + } + }, + + onSuccess = function(response) { + initPlaylist(); + } + ); + }); + }; + var init = function() { initStatus(); initPlaylist(); initBrowser(); + initSearch(); initBindings(); }; diff --git a/platypush/backend/http/templates/plugins/music.mpd.html b/platypush/backend/http/templates/plugins/music.mpd.html index 985e775c0..15ceb858e 100644 --- a/platypush/backend/http/templates/plugins/music.mpd.html +++ b/platypush/backend/http/templates/plugins/music.mpd.html @@ -1,4 +1,90 @@ + + +
No media is being played @@ -63,6 +149,9 @@ +
diff --git a/platypush/plugins/music/mpd/__init__.py b/platypush/plugins/music/mpd/__init__.py index 90afbd0e2..427b983c8 100644 --- a/platypush/plugins/music/mpd/__init__.py +++ b/platypush/plugins/music/mpd/__init__.py @@ -121,6 +121,21 @@ class MusicMpdPlugin(MusicPlugin): def plchanges(self, version): return Response(output=self.client.plchanges(version)) + def find(self, type, filter, *args, **kwargs): + return Response( + output=self.client.find(type, filter, *args, **kwargs)) + + def findadd(self, type, filter, *args, **kwargs): + return Response( + output=self.client.findadd(type, filter, *args, **kwargs)) + + def search(self, type, filter, *args, **kwargs): + return Response( + output=self.client.search(type, filter, *args, **kwargs)) + + def searchadd(self, type, filter, *args, **kwargs): + return Response( + output=self.client.searchadd(type, filter, *args, **kwargs)) # vim:sw=4:ts=4:et: