forked from platypush/platypush
Added playlist editor in music.mpd web panel
This commit is contained in:
parent
1ad72a2695
commit
b7a625097d
6 changed files with 464 additions and 37 deletions
|
@ -76,6 +76,30 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
%ctrl-button {
|
||||||
|
border: 0;
|
||||||
|
padding: 0 1.5rem;
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.enabled {
|
||||||
|
color: $button-enabled-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fa-search {
|
||||||
|
color: $button-hover-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
%move {
|
||||||
|
background: $move-mode-track-bg !important;
|
||||||
|
border-top: $move-mode-track-border;
|
||||||
|
border-bottom: $move-mode-track-border;
|
||||||
|
cursor: move;
|
||||||
|
}
|
||||||
|
|
||||||
.browser,
|
.browser,
|
||||||
.search,
|
.search,
|
||||||
.playlist {
|
.playlist {
|
||||||
|
@ -97,25 +121,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
* > button {
|
* > button {
|
||||||
border: 0;
|
@extend %ctrl-button;
|
||||||
padding: 0 1.5rem;
|
|
||||||
|
|
||||||
&:disabled {
|
|
||||||
background: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.enabled {
|
|
||||||
color: $button-enabled-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fa-search {
|
|
||||||
color: $button-hover-color;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
padding: 0 .75rem;
|
padding: 0 .75rem;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,15 +152,14 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&.move:hover {
|
&.move:hover {
|
||||||
background: $move-mode-track-bg !important;
|
@extend %move;
|
||||||
border-top: $move-mode-track-border;
|
|
||||||
border-bottom: $move-mode-track-border;
|
|
||||||
cursor: move;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.playlist-add {
|
.playlist-add,
|
||||||
|
.editor {
|
||||||
|
.editor-controls,
|
||||||
.playlist-add-controls {
|
.playlist-add-controls {
|
||||||
background: $playlist-controls-bg;
|
background: $playlist-controls-bg;
|
||||||
border-bottom: $playlist-controls-border;
|
border-bottom: $playlist-controls-border;
|
||||||
|
@ -160,12 +169,35 @@
|
||||||
padding: .5rem;
|
padding: .5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input[type=text] {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
@extend %ctrl-button;
|
||||||
|
padding: 0 .75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-container,
|
||||||
.playlists-container {
|
.playlists-container {
|
||||||
max-height: 70vh;
|
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
margin: 0 -2rem;
|
margin: 0 -2rem;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.playlists-container {
|
||||||
|
max-height: 70vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-container {
|
||||||
|
max-height: 65vh;
|
||||||
|
|
||||||
|
.item {
|
||||||
|
&.move:hover {
|
||||||
|
@extend %move;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.controls {
|
.controls {
|
||||||
|
@ -355,7 +387,8 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#music-mpd-search-modal {
|
#music-mpd-search-modal,
|
||||||
|
#music-mpd-playlist-edit {
|
||||||
.dropdown {
|
.dropdown {
|
||||||
z-index: 503;
|
z-index: 503;
|
||||||
}
|
}
|
||||||
|
@ -367,6 +400,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#music-mpd-playlist-edit {
|
||||||
|
.modal {
|
||||||
|
min-width: 80rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media #{map-get($widths, 's')} {
|
@media #{map-get($widths, 's')} {
|
||||||
#music-mpd-info {
|
#music-mpd-info {
|
||||||
.modal {
|
.modal {
|
||||||
|
|
|
@ -12,12 +12,14 @@ Vue.component('music-mpd', {
|
||||||
playlistFilter: '',
|
playlistFilter: '',
|
||||||
browserFilter: '',
|
browserFilter: '',
|
||||||
playlistAddFilter: '',
|
playlistAddFilter: '',
|
||||||
|
editorFilter: '',
|
||||||
browserPath: [],
|
browserPath: [],
|
||||||
browserItems: [],
|
browserItems: [],
|
||||||
|
|
||||||
selectionMode: {
|
selectionMode: {
|
||||||
playlist: false,
|
playlist: false,
|
||||||
browser: false,
|
browser: false,
|
||||||
|
editor: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
moveMode: {
|
moveMode: {
|
||||||
|
@ -28,12 +30,15 @@ Vue.component('music-mpd', {
|
||||||
infoItem: {},
|
infoItem: {},
|
||||||
modalVisible: {
|
modalVisible: {
|
||||||
info: false,
|
info: false,
|
||||||
|
editor: false,
|
||||||
playlistAdd: false,
|
playlistAdd: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
addToPlaylistItems: [],
|
addToPlaylistItems: [],
|
||||||
|
selectedPlaylist: {},
|
||||||
selectedPlaylistItems: {},
|
selectedPlaylistItems: {},
|
||||||
selectedPlaylistAddItems: {},
|
selectedPlaylistAddItems: {},
|
||||||
|
selectedEditorItems: {},
|
||||||
selectedBrowserItems: {},
|
selectedBrowserItems: {},
|
||||||
|
|
||||||
syncTime: {
|
syncTime: {
|
||||||
|
@ -213,7 +218,7 @@ Vue.component('music-mpd', {
|
||||||
icon: 'user',
|
icon: 'user',
|
||||||
click: async function() {
|
click: async function() {
|
||||||
await self.searchArtist(item);
|
await self.searchArtist(item);
|
||||||
self.selectedPlaylistItems = {};
|
self.selectedBrowserItems = {};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -224,7 +229,7 @@ Vue.component('music-mpd', {
|
||||||
icon: 'compact-disc',
|
icon: 'compact-disc',
|
||||||
click: async function() {
|
click: async function() {
|
||||||
await self.searchAlbum(item);
|
await self.searchAlbum(item);
|
||||||
self.selectedPlaylistItems = {};
|
self.selectedBrowserItems = {};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -264,6 +269,13 @@ Vue.component('music-mpd', {
|
||||||
items.push({
|
items.push({
|
||||||
text: 'Edit',
|
text: 'Edit',
|
||||||
icon: 'pen',
|
icon: 'pen',
|
||||||
|
click: async function() {
|
||||||
|
const item = Object.values(self.selectedBrowserItems)[0];
|
||||||
|
self.selectedPlaylist.name = item.name;
|
||||||
|
await self.refreshSelectedPlaylist();
|
||||||
|
self.modalVisible.editor = true;
|
||||||
|
self.selectedBrowserItems = {};
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -297,6 +309,124 @@ Vue.component('music-mpd', {
|
||||||
|
|
||||||
return items;
|
return items;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
editorDropdownItems: function() {
|
||||||
|
var self = this;
|
||||||
|
var items = [];
|
||||||
|
|
||||||
|
if (Object.keys(this.selectedEditorItems).length === 1) {
|
||||||
|
const item = Object.values(this.selectedEditorItems)[0];
|
||||||
|
|
||||||
|
items.push(
|
||||||
|
{
|
||||||
|
text: 'Play',
|
||||||
|
icon: 'play',
|
||||||
|
click: async function() {
|
||||||
|
await self.add(item.file, position=0);
|
||||||
|
await self.playpos(0);
|
||||||
|
self.selectedEditorItems = {};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Replace and play',
|
||||||
|
icon: 'play',
|
||||||
|
click: async function() {
|
||||||
|
await self.clear();
|
||||||
|
await self.add(item.file, position=0);
|
||||||
|
await self.playpos(0);
|
||||||
|
self.selectedEditorItems = {};
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (item.artist && item.artist.length) {
|
||||||
|
items.push({
|
||||||
|
text: 'View artist',
|
||||||
|
icon: 'user',
|
||||||
|
click: async function() {
|
||||||
|
await self.searchArtist(item);
|
||||||
|
self.selectedEditorItems = {};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.album && item.album.length) {
|
||||||
|
items.push({
|
||||||
|
text: 'View album',
|
||||||
|
icon: 'compact-disc',
|
||||||
|
click: async function() {
|
||||||
|
await self.searchAlbum(item);
|
||||||
|
self.selectedEditorItems = {};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
items.push(
|
||||||
|
{
|
||||||
|
text: 'Add to queue',
|
||||||
|
icon: 'plus',
|
||||||
|
click: async function() {
|
||||||
|
const items = Object.values(self.selectedEditorItems);
|
||||||
|
const promises = items.map(item => self.add(item.file));
|
||||||
|
|
||||||
|
await Promise.all(promises);
|
||||||
|
self.selectedEditorItems = {};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'Add to playlist',
|
||||||
|
icon: 'list',
|
||||||
|
click: async function() {
|
||||||
|
self.addToPlaylistItems = Object.keys(self.selectedEditorItems);
|
||||||
|
self.modalVisible.playlistAdd = true;
|
||||||
|
await self.listplaylists();
|
||||||
|
self.selectedEditorItems = {};
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (Object.keys(this.selectedEditorItems).length < this.selectedPlaylist.items.length) {
|
||||||
|
items.push({
|
||||||
|
text: 'Move',
|
||||||
|
icon: 'retweet',
|
||||||
|
click: function() {
|
||||||
|
self.moveMode.editor = true;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
items.push(
|
||||||
|
{
|
||||||
|
text: 'Remove',
|
||||||
|
icon: 'trash',
|
||||||
|
click: async function() {
|
||||||
|
if (!confirm('Are you sure you want to remove the selected track' +
|
||||||
|
(Object.values(self.selectedEditorItems).length > 1 ? 's' : '') + ' from the playlist?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const items = Object.values(self.selectedEditorItems);
|
||||||
|
await self.playlistdelete(items.map(_ => _.pos));
|
||||||
|
self.selectedEditorItems = {};
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (Object.keys(this.selectedEditorItems).length === 1) {
|
||||||
|
const item = Object.values(self.selectedEditorItems)[0];
|
||||||
|
|
||||||
|
items.push({
|
||||||
|
text: 'View info',
|
||||||
|
icon: 'info',
|
||||||
|
click: async function() {
|
||||||
|
await self.info(item.file);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return items;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -594,6 +724,14 @@ Vue.component('music-mpd', {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
playlistmove: async function(fromPos, toPos) {
|
||||||
|
if (!this.selectedPlaylist.name) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await request('music.mpd.playlistmove', {name: this.selectedPlaylist.name, from_pos: fromPos, to_pos: toPos});
|
||||||
|
},
|
||||||
|
|
||||||
swap: async function() {
|
swap: async function() {
|
||||||
if (Object.keys(this.selectedPlaylistItems).length !== 2) {
|
if (Object.keys(this.selectedPlaylistItems).length !== 2) {
|
||||||
return;
|
return;
|
||||||
|
@ -669,6 +807,14 @@ Vue.component('music-mpd', {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
listplaylist: async function(name) {
|
||||||
|
return await request('music.mpd.listplaylist', {name: name});
|
||||||
|
},
|
||||||
|
|
||||||
|
listplaylistinfo: async function(name) {
|
||||||
|
return await request('music.mpd.listplaylistinfo', {name: name});
|
||||||
|
},
|
||||||
|
|
||||||
playlistadd: async function(items=[], playlists=[]) {
|
playlistadd: async function(items=[], playlists=[]) {
|
||||||
if (!playlists.length) {
|
if (!playlists.length) {
|
||||||
if (this.modalVisible.playlistAdd) {
|
if (this.modalVisible.playlistAdd) {
|
||||||
|
@ -696,6 +842,50 @@ Vue.component('music-mpd', {
|
||||||
this.addToPlaylistItems = [];
|
this.addToPlaylistItems = [];
|
||||||
},
|
},
|
||||||
|
|
||||||
|
playlistdelete: async function(items=[]) {
|
||||||
|
if (!items.length) {
|
||||||
|
items = Object.keys(this.selectedEditorItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!items.length || !this.selectedPlaylist.name) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await request('music.mpd.playlistdelete', {name: this.selectedPlaylist.name, pos: items});
|
||||||
|
await this.refreshSelectedPlaylist();
|
||||||
|
},
|
||||||
|
|
||||||
|
playlistclear: async function() {
|
||||||
|
if (!confirm('Are you sure that you want to clear this playlist? This operation is NOT REVERSIBLE')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await request('music.mpd.playlistclear', {name: this.selectedPlaylist.name});
|
||||||
|
await this.refreshSelectedPlaylist();
|
||||||
|
},
|
||||||
|
|
||||||
|
rename: async function() {
|
||||||
|
if (!this.selectedPlaylist.name) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var newName = prompt('New name for the playlist', this.selectedPlaylist.name);
|
||||||
|
if (!newName.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await request('music.mpd.rename', {name: this.selectedPlaylist.name, new_name: newName});
|
||||||
|
await this.listplaylists();
|
||||||
|
|
||||||
|
for (var item of this.browserItems) {
|
||||||
|
if (item.type === 'playlist' && item.name === this.selectedPlaylist.name) {
|
||||||
|
item.name = newName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.selectedPlaylist.name = newName;
|
||||||
|
},
|
||||||
|
|
||||||
onNewPlayingTrack: async function(event) {
|
onNewPlayingTrack: async function(event) {
|
||||||
var previousTrack = {
|
var previousTrack = {
|
||||||
file: this.track.file,
|
file: this.track.file,
|
||||||
|
@ -835,7 +1025,15 @@ Vue.component('music-mpd', {
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
const filter = this.playlistFilter.split(' ').filter(_ => _.length > 0).map(_ => _.toLocaleLowerCase()).join(' ');
|
const filter = this.playlistFilter.split(' ').filter(_ => _.length > 0).map(_ => _.toLocaleLowerCase()).join(' ');
|
||||||
return [track.artist || '', track.title || '', track.album || ''].join(' ').toLocaleLowerCase().indexOf() >= 0;
|
return [track.artist || '', track.title || '', track.album || ''].join(' ').toLocaleLowerCase().indexOf(filter) >= 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
matchesEditorFilter: function(track) {
|
||||||
|
if (this.editorFilter.length === 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
const filter = this.editorFilter.split(' ').filter(_ => _.length > 0).map(_ => _.toLocaleLowerCase()).join(' ');
|
||||||
|
return [track.artist || '', track.title || '', track.album || ''].join(' ').toLocaleLowerCase().indexOf(filter) >= 0;
|
||||||
},
|
},
|
||||||
|
|
||||||
matchesBrowserFilter: function(item) {
|
matchesBrowserFilter: function(item) {
|
||||||
|
@ -881,6 +1079,30 @@ Vue.component('music-mpd', {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onEditorItemClick: async function(track) {
|
||||||
|
if (this.selectionMode.editor) {
|
||||||
|
if (track.pos in this.selectedEditorItems) {
|
||||||
|
Vue.delete(this.selectedEditorItems, track.pos);
|
||||||
|
} else {
|
||||||
|
Vue.set(this.selectedEditorItems, track.pos, track);
|
||||||
|
}
|
||||||
|
} else if (this.moveMode.editor) {
|
||||||
|
var fromPos = Object.values(this.selectedEditorItems).map(_ => _.pos);
|
||||||
|
var toPos = track.pos;
|
||||||
|
this.moveMode.editor = false;
|
||||||
|
|
||||||
|
const promises = fromPos.map((pos,i) => this.playlistmove(pos, toPos+i));
|
||||||
|
await Promise.all(promises);
|
||||||
|
await this.refreshSelectedPlaylist();
|
||||||
|
} else if (track.pos in this.selectedEditorItems) {
|
||||||
|
Vue.delete(this.selectedEditorItems, track.pos);
|
||||||
|
} else {
|
||||||
|
this.selectedEditorItems = {};
|
||||||
|
Vue.set(this.selectedEditorItems, track.pos, track);
|
||||||
|
openDropdown(this.$refs.editorDropdown.$el);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
onBrowserItemClick: function(item) {
|
onBrowserItemClick: function(item) {
|
||||||
if (item.type === 'directory' && item.name === '..') {
|
if (item.type === 'directory' && item.name === '..') {
|
||||||
this.selectedBrowserItems = {};
|
this.selectedBrowserItems = {};
|
||||||
|
@ -940,6 +1162,14 @@ Vue.component('music-mpd', {
|
||||||
this.selectionMode.browser = !this.selectionMode.browser;
|
this.selectionMode.browser = !this.selectionMode.browser;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
toggleEditorSelectionMode: function() {
|
||||||
|
if (this.selectionMode.editor && Object.keys(this.selectedEditorItems).length) {
|
||||||
|
openDropdown(this.$refs.editorDropdown.$el);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.selectionMode.editor = !this.selectionMode.editor;
|
||||||
|
},
|
||||||
|
|
||||||
browserSelectAll: function() {
|
browserSelectAll: function() {
|
||||||
this.selectedBrowserItems = {};
|
this.selectedBrowserItems = {};
|
||||||
this.selectionMode.browser = true;
|
this.selectionMode.browser = true;
|
||||||
|
@ -953,6 +1183,17 @@ Vue.component('music-mpd', {
|
||||||
openDropdown(this.$refs.browserDropdown.$el);
|
openDropdown(this.$refs.browserDropdown.$el);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
editorSelectAll: function() {
|
||||||
|
this.selectedEditorItems = {};
|
||||||
|
this.selectionMode.editor = true;
|
||||||
|
|
||||||
|
for (var item of this.selectedPlaylist.items) {
|
||||||
|
Vue.set(this.selectedEditorItems, item.pos, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
openDropdown(this.$refs.editorDropdown.$el);
|
||||||
|
},
|
||||||
|
|
||||||
scrollToActiveTrack: function() {
|
scrollToActiveTrack: function() {
|
||||||
if (this.$refs.activePlaylistTrack && this.$refs.activePlaylistTrack.length) {
|
if (this.$refs.activePlaylistTrack && this.$refs.activePlaylistTrack.length) {
|
||||||
this.$refs.activePlaylistTrack[0].$el.scrollIntoView({behavior: 'smooth'});
|
this.$refs.activePlaylistTrack[0].$el.scrollIntoView({behavior: 'smooth'});
|
||||||
|
@ -979,7 +1220,17 @@ Vue.component('music-mpd', {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.add(resource);
|
await this.add(resource);
|
||||||
|
},
|
||||||
|
|
||||||
|
addToPlaylistEditorPrompt: async function() {
|
||||||
|
var resource = prompt('Path or URI of the resource to add');
|
||||||
|
if (!resource.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await request('music.mpd.playlistadd', {name: this.selectedPlaylist.name, uri: resource});
|
||||||
|
await this.refreshSelectedPlaylist();
|
||||||
},
|
},
|
||||||
|
|
||||||
savePlaylistPrompt: async function() {
|
savePlaylistPrompt: async function() {
|
||||||
|
@ -994,6 +1245,18 @@ Vue.component('music-mpd', {
|
||||||
let items = await request('music.mpd.lsinfo', {uri: this.browserPath.join('/')});
|
let items = await request('music.mpd.lsinfo', {uri: this.browserPath.join('/')});
|
||||||
this._parseBrowserItems(items);
|
this._parseBrowserItems(items);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
refreshSelectedPlaylist: async function() {
|
||||||
|
if (!this.selectedPlaylist.name) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let items = (await this.listplaylistinfo(this.selectedPlaylist.name)).map((_, i) => {
|
||||||
|
return { ..._, pos: i }
|
||||||
|
});
|
||||||
|
|
||||||
|
Vue.set(this.selectedPlaylist, 'items', items);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
created: function() {
|
created: function() {
|
||||||
|
|
|
@ -6,9 +6,7 @@
|
||||||
|
|
||||||
<script type="text/x-template" id="tmpl-music-mpd">
|
<script type="text/x-template" id="tmpl-music-mpd">
|
||||||
<div class="row music-mpd-container">
|
<div class="row music-mpd-container">
|
||||||
<music-mpd-search ref="search"
|
<music-mpd-search ref="search" @info="info" :mpd="this">
|
||||||
@info="info"
|
|
||||||
:mpd="this">
|
|
||||||
</music-mpd-search>
|
</music-mpd-search>
|
||||||
|
|
||||||
<modal id="music-mpd-info" title="Info" v-model="modalVisible.info" ref="modal">
|
<modal id="music-mpd-info" title="Info" v-model="modalVisible.info" ref="modal">
|
||||||
|
@ -33,9 +31,9 @@
|
||||||
<div class="col-8 value" v-text="infoItem.album"></div>
|
<div class="col-8 value" v-text="infoItem.album"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row" v-if="infoItem.year">
|
<div class="row" v-if="infoItem.year || infoItem.date">
|
||||||
<div class="col-4 attr">Year</div>
|
<div class="col-4 attr">Date</div>
|
||||||
<div class="col-8 value" v-text="infoItem.year"></div>
|
<div class="col-8 value" v-text="infoItem.year || infoItem.date"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row" v-if="infoItem.disc">
|
<div class="row" v-if="infoItem.disc">
|
||||||
|
@ -85,6 +83,57 @@
|
||||||
</div>
|
</div>
|
||||||
</modal>
|
</modal>
|
||||||
|
|
||||||
|
<modal id="music-mpd-playlist-edit" title="Edit/view playlist" v-model="modalVisible.editor" ref="modalEditor">
|
||||||
|
<div class="editor">
|
||||||
|
<div class="editor-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="editorFilter">
|
||||||
|
</div>
|
||||||
|
<div class="col-5 pull-right">
|
||||||
|
<button title="Add item" @click="addToPlaylistEditorPrompt">
|
||||||
|
<i class="fa fa-plus"></i>
|
||||||
|
</button>
|
||||||
|
<button title="Rename playlist" @click="rename">
|
||||||
|
<i class="fa fa-edit"></i>
|
||||||
|
</button>
|
||||||
|
<button :class="{enabled: selectionMode.editor}"
|
||||||
|
:title="selectionMode.editor ? 'End selection' : 'Start selection'"
|
||||||
|
@click="toggleEditorSelectionMode">
|
||||||
|
<i class="fa fa-check"></i>
|
||||||
|
</button>
|
||||||
|
<button title="Select all"
|
||||||
|
@click="editorSelectAll">
|
||||||
|
<i class="fa fa-check-double"></i>
|
||||||
|
</button>
|
||||||
|
<button title="Clear playlist" @click="playlistclear">
|
||||||
|
<i class="fa fa-ban"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="editor-container">
|
||||||
|
<dropdown id="music-mpd-editor-dropdown"
|
||||||
|
v-if="selectedPlaylist.items"
|
||||||
|
ref="editorDropdown"
|
||||||
|
:items="editorDropdownItems">
|
||||||
|
</dropdown>
|
||||||
|
|
||||||
|
<music-mpd-playlist-item
|
||||||
|
v-for="item in selectedPlaylist.items"
|
||||||
|
v-if="matchesEditorFilter(item)"
|
||||||
|
:key="item.pos"
|
||||||
|
:track="item"
|
||||||
|
:selected="item.pos in selectedEditorItems"
|
||||||
|
:move="moveMode.editor"
|
||||||
|
@input="onEditorItemClick">
|
||||||
|
</music-mpd-playlist-item>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</modal>
|
||||||
|
|
||||||
<div class="row panels">
|
<div class="row panels">
|
||||||
<!-- Browser section -->
|
<!-- Browser section -->
|
||||||
<div class="col-no-margin-l-3 col-no-margin-m-3 s-hidden panel browser">
|
<div class="col-no-margin-l-3 col-no-margin-m-3 s-hidden panel browser">
|
||||||
|
|
|
@ -30,7 +30,8 @@
|
||||||
|
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
<div class="left col-6">
|
<div class="left col-6">
|
||||||
<button class="btn-default" v-if="results.length" @click="$event.preventDefault(); showResults = true" title="Show results">
|
<button class="btn-default" type="button" v-if="results.length"
|
||||||
|
@click="$event.preventDefault(); showResults = true" title="Show results">
|
||||||
<i class="fa fa-list"></i>
|
<i class="fa fa-list"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -33,10 +33,6 @@ class MusicPlugin(Plugin):
|
||||||
def add(self, content):
|
def add(self, content):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
@action
|
|
||||||
def playlistadd(self, playlist):
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def clear(self):
|
def clear(self):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
|
@ -564,6 +564,26 @@ class MusicMpdPlugin(MusicPlugin):
|
||||||
return sorted(self._exec('listplaylists', return_status=False),
|
return sorted(self._exec('listplaylists', return_status=False),
|
||||||
key=lambda p: p['playlist'])
|
key=lambda p: p['playlist'])
|
||||||
|
|
||||||
|
@action
|
||||||
|
def listplaylist(self, name):
|
||||||
|
"""
|
||||||
|
List the items in the specified playlist (without metadata)
|
||||||
|
|
||||||
|
:param name: Name of the playlist
|
||||||
|
:type name: str
|
||||||
|
"""
|
||||||
|
return self._exec('listplaylist', name, return_status=False)
|
||||||
|
|
||||||
|
@action
|
||||||
|
def listplaylistinfo(self, name):
|
||||||
|
"""
|
||||||
|
List the items in the specified playlist (with metadata)
|
||||||
|
|
||||||
|
:param name: Name of the playlist
|
||||||
|
:type name: str
|
||||||
|
"""
|
||||||
|
return self._exec('listplaylistinfo', name, return_status=False)
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def playlistadd(self, name, uri):
|
def playlistadd(self, name, uri):
|
||||||
"""
|
"""
|
||||||
|
@ -582,6 +602,65 @@ class MusicMpdPlugin(MusicPlugin):
|
||||||
for res in uri:
|
for res in uri:
|
||||||
self._exec('playlistadd', name, res)
|
self._exec('playlistadd', name, res)
|
||||||
|
|
||||||
|
@action
|
||||||
|
def playlistdelete(self, name, pos):
|
||||||
|
"""
|
||||||
|
Remove one or multiple tracks from a playlist.
|
||||||
|
|
||||||
|
:param name: Playlist name
|
||||||
|
:type name: str
|
||||||
|
|
||||||
|
:param pos: Position or list of positions to remove
|
||||||
|
:type pos: int or list[int]
|
||||||
|
"""
|
||||||
|
|
||||||
|
if isinstance(pos, str):
|
||||||
|
pos = int(pos)
|
||||||
|
if isinstance(pos, int):
|
||||||
|
pos = [pos]
|
||||||
|
|
||||||
|
for p in pos:
|
||||||
|
self._exec('playlistdelete', name, p)
|
||||||
|
|
||||||
|
@action
|
||||||
|
def playlistmove(self, name, from_pos, to_pos):
|
||||||
|
"""
|
||||||
|
Change the position of a track in the specified playlist
|
||||||
|
|
||||||
|
:param name: Playlist name
|
||||||
|
:type name: str
|
||||||
|
|
||||||
|
:param from_pos: Original track position
|
||||||
|
:type from_pos: int
|
||||||
|
|
||||||
|
:param to_pos: New track position
|
||||||
|
:type to_pos: int
|
||||||
|
"""
|
||||||
|
self._exec('playlistmove', name, from_pos, to_pos)
|
||||||
|
|
||||||
|
@action
|
||||||
|
def playlistclear(self, name):
|
||||||
|
"""
|
||||||
|
Clears all the elements from the specified playlist
|
||||||
|
|
||||||
|
:param name: Playlist name
|
||||||
|
:type name: str
|
||||||
|
"""
|
||||||
|
self._exec('playlistclear', name)
|
||||||
|
|
||||||
|
@action
|
||||||
|
def rename(self, name, new_name):
|
||||||
|
"""
|
||||||
|
Rename a playlist
|
||||||
|
|
||||||
|
:param name: Original playlist name
|
||||||
|
:type name: str
|
||||||
|
|
||||||
|
:param new_name: New playlist name
|
||||||
|
:type name: str
|
||||||
|
"""
|
||||||
|
self._exec('rename', name, new_name)
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def lsinfo(self, uri=None):
|
def lsinfo(self, uri=None):
|
||||||
"""
|
"""
|
||||||
|
|
Loading…
Reference in a new issue