forked from platypush/platypush
Implemented support for adding tracks to playlists
This commit is contained in:
parent
0b6b29f043
commit
1ad72a2695
7 changed files with 210 additions and 14 deletions
|
@ -130,6 +130,12 @@
|
|||
}
|
||||
|
||||
.item {
|
||||
.empty {
|
||||
font-size: 1em;
|
||||
display: block;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
&.active {
|
||||
height: 4rem;
|
||||
@include animation(active-track 5s infinite);
|
||||
|
@ -144,6 +150,24 @@
|
|||
}
|
||||
}
|
||||
|
||||
.playlist-add {
|
||||
.playlist-add-controls {
|
||||
background: $playlist-controls-bg;
|
||||
border-bottom: $playlist-controls-border;
|
||||
border-radius: 0;
|
||||
box-shadow: $filter-bar-shadow;
|
||||
margin: -2.5rem -2rem 0 -2rem;
|
||||
padding: .5rem;
|
||||
}
|
||||
|
||||
.playlists-container {
|
||||
max-height: 70vh;
|
||||
overflow: auto;
|
||||
margin: 0 -2rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.controls {
|
||||
@extend .vertical-center;
|
||||
position: fixed;
|
||||
|
@ -296,12 +320,16 @@
|
|||
position: relative;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
width: 20rem;
|
||||
}
|
||||
|
||||
.filter-container {
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
#music-mpd-info {
|
||||
.modal {
|
||||
.body {
|
||||
|
@ -333,6 +361,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
#music-mpd-playlist-add {
|
||||
.modal {
|
||||
min-width: 50rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media #{map-get($widths, 's')} {
|
||||
#music-mpd-info {
|
||||
.modal {
|
||||
|
|
|
@ -26,4 +26,5 @@ $search-modal-footer-border: 1px solid #ccc;
|
|||
$info-modal-row-border: 1px solid #ddd;
|
||||
$info-modal-attr-color: #777;
|
||||
$track-info-hover-color: rgb(46,190,110);
|
||||
$filter-bar-shadow: 0 2.5px 4px 0 #bbb;
|
||||
|
||||
|
|
|
@ -8,8 +8,10 @@ Vue.component('music-mpd', {
|
|||
status: {},
|
||||
timer: null,
|
||||
playlist: [],
|
||||
playlists: [],
|
||||
playlistFilter: '',
|
||||
browserFilter: '',
|
||||
playlistAddFilter: '',
|
||||
browserPath: [],
|
||||
browserItems: [],
|
||||
|
||||
|
@ -26,9 +28,12 @@ Vue.component('music-mpd', {
|
|||
infoItem: {},
|
||||
modalVisible: {
|
||||
info: false,
|
||||
playlistAdd: false,
|
||||
},
|
||||
|
||||
addToPlaylistItems: [],
|
||||
selectedPlaylistItems: {},
|
||||
selectedPlaylistAddItems: {},
|
||||
selectedBrowserItems: {},
|
||||
|
||||
syncTime: {
|
||||
|
@ -84,6 +89,12 @@ Vue.component('music-mpd', {
|
|||
items.push({
|
||||
text: 'Add to playlist',
|
||||
icon: 'list',
|
||||
click: async function() {
|
||||
self.addToPlaylistItems = Object.values(self.selectedPlaylistItems).map(_ => _.file);
|
||||
self.selectedPlaylistItems = {};
|
||||
self.modalVisible.playlistAdd = true;
|
||||
await self.listplaylists();
|
||||
},
|
||||
});
|
||||
|
||||
if (Object.keys(this.selectedPlaylistItems).length < this.playlist.length) {
|
||||
|
@ -233,6 +244,21 @@ Vue.component('music-mpd', {
|
|||
},
|
||||
);
|
||||
|
||||
if (Object.values(this.selectedBrowserItems).filter(_ => _.type === 'file').length === Object.values(this.selectedBrowserItems).length) {
|
||||
items.push(
|
||||
{
|
||||
text: 'Add to playlist',
|
||||
icon: 'list',
|
||||
click: async function() {
|
||||
self.addToPlaylistItems = Object.keys(self.selectedBrowserItems);
|
||||
self.modalVisible.playlistAdd = true;
|
||||
await self.listplaylists();
|
||||
self.selectedBrowserItems = {};
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (Object.keys(this.selectedBrowserItems).length === 1
|
||||
&& Object.values(this.selectedBrowserItems)[0].type === 'playlist') {
|
||||
items.push({
|
||||
|
@ -263,6 +289,9 @@ Vue.component('music-mpd', {
|
|||
items.push({
|
||||
text: 'View info',
|
||||
icon: 'info',
|
||||
click: async function() {
|
||||
await self.info(Object.values(self.selectedBrowserItems)[0].name);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -338,7 +367,7 @@ Vue.component('music-mpd', {
|
|||
}
|
||||
|
||||
for (const [attr, value] of Object.entries(track)) {
|
||||
if (['id','pos','time'].indexOf(attr) >= 0) {
|
||||
if (['id','pos','time','track','disc'].indexOf(attr) >= 0) {
|
||||
Vue.set(this.track, attr, parseInt(value));
|
||||
} else {
|
||||
Vue.set(this.track, attr, value);
|
||||
|
@ -355,7 +384,7 @@ Vue.component('music-mpd', {
|
|||
|
||||
for (var track of playlist) {
|
||||
for (const [attr, value] of Object.entries(track)) {
|
||||
if (['time','pos','id'].indexOf(attr) >= 0) {
|
||||
if (['time','pos','id','track','disc'].indexOf(attr) >= 0) {
|
||||
track[attr] = parseInt(value);
|
||||
} else {
|
||||
track[attr] = value;
|
||||
|
@ -599,7 +628,8 @@ Vue.component('music-mpd', {
|
|||
var info = item;
|
||||
|
||||
if (typeof(item) === 'string') {
|
||||
item = await request('music.mpd.search', {filter: {file: info}});
|
||||
var items = await request('music.mpd.search', {filter: ['file', info]});
|
||||
item = items.length ? items[0] : {file: info};
|
||||
}
|
||||
|
||||
this.infoItem = item;
|
||||
|
@ -630,6 +660,42 @@ Vue.component('music-mpd', {
|
|||
await this.$refs.search.search();
|
||||
},
|
||||
|
||||
listplaylists: async function() {
|
||||
this.playlists = [];
|
||||
let playlists = await request('music.mpd.listplaylists');
|
||||
|
||||
for (const p of playlists) {
|
||||
this.playlists.push(p);
|
||||
}
|
||||
},
|
||||
|
||||
playlistadd: async function(items=[], playlists=[]) {
|
||||
if (!playlists.length) {
|
||||
if (this.modalVisible.playlistAdd) {
|
||||
playlists = Object.keys(this.selectedPlaylistAddItems);
|
||||
}
|
||||
}
|
||||
|
||||
if (!items.length) {
|
||||
items = this.addToPlaylistItems;
|
||||
}
|
||||
|
||||
if (!items.length || !playlists.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
var promises = [];
|
||||
for (const playlist of playlists) {
|
||||
promises.push(request('music.mpd.playlistadd', {
|
||||
name: playlist, uri: items.map(_ => typeof(_) === 'object' ? _.file : _)
|
||||
}));
|
||||
}
|
||||
|
||||
await Promise.all(promises);
|
||||
this.modalVisible.playlistAdd = false;
|
||||
this.addToPlaylistItems = [];
|
||||
},
|
||||
|
||||
onNewPlayingTrack: async function(event) {
|
||||
var previousTrack = {
|
||||
file: this.track.file,
|
||||
|
@ -777,7 +843,15 @@ Vue.component('music-mpd', {
|
|||
return true;
|
||||
|
||||
const filter = this.browserFilter.toLocaleLowerCase().split(' ').filter(_ => _.length > 0).join(' ');
|
||||
return item.name.toLocaleLowerCase().indexOf(filter) >= 0;
|
||||
return (item.artist || '').concat(item.name).toLocaleLowerCase().indexOf(filter) >= 0;
|
||||
},
|
||||
|
||||
matchesPlaylistAddFilter: function(item) {
|
||||
if (this.playlistAddFilter.length === 0)
|
||||
return true;
|
||||
|
||||
const filter = this.playlistAddFilter.toLocaleLowerCase().split(' ').filter(_ => _.length > 0).join(' ');
|
||||
return (item.playlist || '').toLocaleLowerCase().indexOf(filter) >= 0;
|
||||
},
|
||||
|
||||
onPlaylistItemClick: async function(track) {
|
||||
|
@ -831,6 +905,14 @@ Vue.component('music-mpd', {
|
|||
}
|
||||
},
|
||||
|
||||
onPlaylistAddItemClick: function(playlist) {
|
||||
if (playlist.playlist in this.selectedPlaylistAddItems) {
|
||||
Vue.delete(this.selectedPlaylistAddItems, playlist.playlist);
|
||||
} else {
|
||||
Vue.set(this.selectedPlaylistAddItems, playlist.playlist, playlist);
|
||||
}
|
||||
},
|
||||
|
||||
togglePlaylistSelectionMode: function() {
|
||||
if (this.selectionMode.playlist && Object.keys(this.selectedPlaylistItems).length) {
|
||||
openDropdown(this.$refs.playlistDropdown.$el);
|
||||
|
|
|
@ -66,6 +66,21 @@ Vue.component('music-mpd-search', {
|
|||
},
|
||||
);
|
||||
|
||||
if (Object.values(this.selectedItems).filter(_ => _.time != null).length === Object.values(this.selectedItems).length) {
|
||||
items.push(
|
||||
{
|
||||
text: 'Add to playlist',
|
||||
icon: 'list',
|
||||
click: async function() {
|
||||
self.mpd.addToPlaylistItems = Object.values(self.selectedItems).map(_ => _.file);
|
||||
self.mpd.modalVisible.playlistAdd = true;
|
||||
await self.mpd.listplaylists();
|
||||
self.selectedItems = {};
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (Object.keys(this.selectedItems).length === 1) {
|
||||
const item = Object.values(this.selectedItems)[0];
|
||||
|
||||
|
@ -118,11 +133,17 @@ Vue.component('music-mpd-search', {
|
|||
var results = await request('music.mpd.search', {filter: filter});
|
||||
|
||||
this.results = results.sort((a,b) => {
|
||||
const tokenize = (t) => {
|
||||
return ''.concat(t.artist || '', '-', t.album || '', '-', t.disc || '', '-', t.track || '', t.title || '').toLocaleLowerCase();
|
||||
};
|
||||
|
||||
return tokenize(a).localeCompare(tokenize(b));
|
||||
if (a.artist != b.artist)
|
||||
return (a.artist || '').localeCompare(b.artist || '');
|
||||
if (a.album != b.album)
|
||||
return (a.album || '').localeCompare(b.album || '');
|
||||
if (a.track != b.track)
|
||||
return parseInt(a.track || 0) > parseInt(b.track || 0);
|
||||
if (a.title != b.title)
|
||||
return (a.title || '').localeCompare(b.title || '');
|
||||
if (a.file != b.file)
|
||||
return (a.file || '').localeCompare(b.file || '');
|
||||
return 0;
|
||||
});
|
||||
|
||||
this.showResults = true;
|
||||
|
@ -177,6 +198,16 @@ Vue.component('music-mpd-search', {
|
|||
this.query[attr] = '';
|
||||
}
|
||||
},
|
||||
|
||||
resetForm: function() {
|
||||
this.resetQuery();
|
||||
this.showResults = false;
|
||||
var self = this;
|
||||
|
||||
setTimeout(() => {
|
||||
self.$refs.form.querySelector('input[type=text]:first-child').focus()
|
||||
}, 100)
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -55,6 +55,36 @@
|
|||
</div>
|
||||
</modal>
|
||||
|
||||
<modal id="music-mpd-playlist-add" title="Add to playlist" v-model="modalVisible.playlistAdd" ref="modalPlaylistAdd">
|
||||
<div class="playlist-add">
|
||||
<div class="playlist-add-controls">
|
||||
<div class="row">
|
||||
<div class="col-7 filter-container">
|
||||
<i class="fa fa-filter input-icon"></i>
|
||||
<input type="text" class="with-icon" v-model="playlistAddFilter">
|
||||
</div>
|
||||
<div class="col-5 pull-right">
|
||||
<button class="btn-primary" type="button"
|
||||
@click="playlistadd"
|
||||
:disabled="Object.keys(selectedPlaylistAddItems).length === 0">
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="playlists-container">
|
||||
<div class="item"
|
||||
:class="{selected: p.playlist in selectedPlaylistAddItems}"
|
||||
v-for="p in playlists"
|
||||
v-if="matchesPlaylistAddFilter(p)"
|
||||
v-text="p.playlist"
|
||||
@click="onPlaylistAddItemClick(p)">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</modal>
|
||||
|
||||
<div class="row panels">
|
||||
<!-- Browser section -->
|
||||
<div class="col-no-margin-l-3 col-no-margin-m-3 s-hidden panel browser">
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
|
||||
<div class="footer">
|
||||
<div class="left col-6">
|
||||
<button class="btn-default" v-if="results.length" @click="showResults = true" title="Show results">
|
||||
<button class="btn-default" v-if="results.length" @click="$event.preventDefault(); showResults = true" title="Show results">
|
||||
<i class="fa fa-list"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -59,7 +59,7 @@
|
|||
@click="selectAll">
|
||||
<i class="fa fa-check-double"></i>
|
||||
</button>
|
||||
<button class="btn-default" @click="showResults = false" title="Show search form">
|
||||
<button class="btn-default" title="Show search form" @click="resetForm">
|
||||
<i class="fa fa-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
@ -564,6 +564,24 @@ class MusicMpdPlugin(MusicPlugin):
|
|||
return sorted(self._exec('listplaylists', return_status=False),
|
||||
key=lambda p: p['playlist'])
|
||||
|
||||
@action
|
||||
def playlistadd(self, name, uri):
|
||||
"""
|
||||
Add one or multiple resources to a playlist.
|
||||
|
||||
:param name: Playlist name
|
||||
:type name: str
|
||||
|
||||
:param uri: URI or path of the resource(s) to be added
|
||||
:type uri: str or list[str]
|
||||
"""
|
||||
|
||||
if isinstance(uri, str):
|
||||
uri = [uri]
|
||||
|
||||
for res in uri:
|
||||
self._exec('playlistadd', name, res)
|
||||
|
||||
@action
|
||||
def lsinfo(self, uri=None):
|
||||
"""
|
||||
|
|
Loading…
Reference in a new issue