music.mpd vue.js refactoring WIP
This commit is contained in:
parent
e1ddf7bb3b
commit
85bdd54f7e
14 changed files with 531 additions and 81 deletions
|
@ -9,7 +9,17 @@
|
||||||
background: $slider-bg;
|
background: $slider-bg;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|
||||||
&::-webkit-slider-thumb,
|
// Cursed be thy name Chrome for forcing designers to this hysterical redundancy
|
||||||
|
&::-webkit-slider-thumb {
|
||||||
|
@include appearance(none);
|
||||||
|
width: 25px;
|
||||||
|
height: 25px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 0;
|
||||||
|
background: $slider-thumb-bg;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
&::-moz-range-thumb {
|
&::-moz-range-thumb {
|
||||||
@include appearance(none);
|
@include appearance(none);
|
||||||
width: 25px;
|
width: 25px;
|
||||||
|
@ -20,27 +30,36 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
&[disabled]::-webkit-slider-thumb,
|
&[disabled]::-webkit-slider-thumb {
|
||||||
|
display: none;
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
&[disabled]::-moz-range-thumb {
|
&[disabled]::-moz-range-thumb {
|
||||||
display: none;
|
display: none;
|
||||||
width: 0;
|
width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.disabled {
|
&.disabled { opacity: 0.3; }
|
||||||
opacity: 0.3;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-moz-range-track {
|
&::-moz-range-track {
|
||||||
@include appearance(none);
|
@include appearance(none);
|
||||||
}
|
}
|
||||||
|
|
||||||
&::-webkit-progress-value,
|
&::-webkit-progress-value {
|
||||||
|
background: $slider-progress-bg;
|
||||||
|
height: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
&::-moz-range-progress {
|
&::-moz-range-progress {
|
||||||
background: $slider-progress-bg;
|
background: $slider-progress-bg;
|
||||||
height: 15px;
|
height: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&[disabled]::-webkit-progress-value,
|
&[disabled]::-webkit-progress-value {
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
|
||||||
&[disabled]::-moz-range-progress {
|
&[disabled]::-moz-range-progress {
|
||||||
background: none;
|
background: none;
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,7 +69,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
* > .fa {
|
* > .fa {
|
||||||
font-size: 3rem;
|
font-size: 2.5rem;
|
||||||
|
color: $light-hue-icon-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
* > .color-logo {
|
* > .color-logo {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
$light-hue-properties-bg: rgba(239,239,240,0.5);
|
$light-hue-properties-bg: rgba(239,239,240,0.5);
|
||||||
$light-hue-properties-hover-bg: white;
|
$light-hue-properties-hover-bg: white;
|
||||||
$light-hue-properties-shadow: 0 0 4px 2px rgba(187,187,187,0.75);
|
$light-hue-properties-shadow: 0 0 4px 2px rgba(187,187,187,0.75);
|
||||||
|
$light-hue-icon-color: #555;
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
&:nth-child(odd) { background: rgba(255, 255, 255, 0.0); }
|
&:nth-child(odd) { background: rgba(255, 255, 255, 0.0); }
|
||||||
&:nth-child(even) { background: $default-bg-3; }
|
&:nth-child(even) { background: $default-bg-3; }
|
||||||
&:hover { background: $hover-bg !important; }
|
&:hover { background: $hover-bg !important; }
|
||||||
&.selected { background: $selected-bg; }
|
&.selected { background: $selected-bg !important; }
|
||||||
|
|
||||||
.artist {
|
.artist {
|
||||||
font-size: $artist-font-size;
|
font-size: $artist-font-size;
|
||||||
|
@ -38,7 +38,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
&.enabled {
|
&.enabled {
|
||||||
color: $button-enabled-color;
|
color: $button-enabled-color !important;
|
||||||
|
.fa { color: $button-enabled-color !important; }
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
|
@ -51,8 +52,11 @@
|
||||||
.panels {
|
.panels {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
|
.spacer {
|
||||||
|
height: 5rem;
|
||||||
|
}
|
||||||
|
|
||||||
.browser, .playlist {
|
.browser, .playlist {
|
||||||
height: 100vh - 16rem;
|
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,9 +68,6 @@
|
||||||
|
|
||||||
.item {
|
.item {
|
||||||
background: none;
|
background: none;
|
||||||
&:nth-of-type(2) {
|
|
||||||
margin-top: 4.5rem;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.fa {
|
.fa {
|
||||||
|
@ -116,10 +117,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.spacer {
|
|
||||||
height: 5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty {
|
.empty {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -135,10 +132,6 @@
|
||||||
height: 4rem;
|
height: 4rem;
|
||||||
@include animation(active-track 5s infinite);
|
@include animation(active-track 5s infinite);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:first-child {
|
|
||||||
margin-top: 4.5rem;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -239,7 +232,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#music-mpd-playlist-dropdown {
|
.dropdown {
|
||||||
width: 20rem;
|
width: 20rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit bdfa9823c8b1e25a5c822f6c719ec0e38ead7f71
|
Subproject commit 3afe50bda5308c27f7c8eee597663948ffbd084e
|
|
@ -100,5 +100,20 @@ function openDropdown(element) {
|
||||||
document.addEventListener('click', clickHndl);
|
document.addEventListener('click', clickHndl);
|
||||||
element.className = element.className.split(' ').filter(c => c !== 'hidden').join(' ');
|
element.className = element.className.split(' ').filter(c => c !== 'hidden').join(' ');
|
||||||
openedDropdown = element;
|
openedDropdown = element;
|
||||||
|
|
||||||
|
const maxLeft = Math.min(window.innerWidth, element.parentElement.clientWidth) + element.parentElement.scrollLeft;
|
||||||
|
const maxTop = Math.min(window.innerHeight, element.parentElement.clientHeight) + element.parentElement.scrollTop;
|
||||||
|
|
||||||
|
if (element.parentElement.offsetLeft + element.offsetLeft + parseFloat(getComputedStyle(element).width) >= maxLeft) {
|
||||||
|
if (parseFloat(element.style.left) - parseFloat(getComputedStyle(element).width) >= 0) {
|
||||||
|
element.style.left = (parseFloat(element.style.left) - parseFloat(getComputedStyle(element).width)) + 'px';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.parentElement.offsetTop + element.offsetTop + parseFloat(getComputedStyle(element).height) >= maxTop) {
|
||||||
|
if (parseFloat(element.style.top) - parseFloat(getComputedStyle(element).height) >= 0) {
|
||||||
|
element.style.top = (parseFloat(element.style.top) - parseFloat(getComputedStyle(element).height)) + 'px';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,24 @@
|
||||||
Vue.component('music-mpd-browser-item', {
|
Vue.component('music-mpd-browser-item', {
|
||||||
template: '#tmpl-music-mpd-browser-item',
|
template: '#tmpl-music-mpd-browser-item',
|
||||||
props: ['type','name'],
|
props: {
|
||||||
|
id: { type: String, },
|
||||||
|
type: { type: String, },
|
||||||
|
name: { type: String, },
|
||||||
|
file: { type: String, },
|
||||||
|
time: { type: String, },
|
||||||
|
artist: { type: String, },
|
||||||
|
title: { type: String, },
|
||||||
|
date: { type: String, },
|
||||||
|
track: { type: String, },
|
||||||
|
genre: { type: String, },
|
||||||
|
lastModified: { type: String, },
|
||||||
|
albumUri: { type: String, },
|
||||||
|
|
||||||
|
selected: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
},
|
},
|
||||||
|
|
|
@ -32,15 +32,20 @@ Vue.component('music-mpd', {
|
||||||
computed: {
|
computed: {
|
||||||
playlistDropdownItems: function() {
|
playlistDropdownItems: function() {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
var items = [];
|
||||||
|
|
||||||
return [
|
if (Object.keys(this.selectedPlaylistItems).length === 1) {
|
||||||
{
|
items.push({
|
||||||
text: 'Play',
|
text: 'Play',
|
||||||
icon: 'play',
|
icon: 'play',
|
||||||
click: async function() {
|
click: async function() {
|
||||||
await self.playpos();
|
await self.playpos();
|
||||||
|
self.selectedPlaylistItems = {};
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
items.push(
|
||||||
{
|
{
|
||||||
text: 'Add to playlist',
|
text: 'Add to playlist',
|
||||||
icon: 'list',
|
icon: 'list',
|
||||||
|
@ -54,13 +59,141 @@ Vue.component('music-mpd', {
|
||||||
icon: 'trash',
|
icon: 'trash',
|
||||||
click: async function() {
|
click: async function() {
|
||||||
await self.del();
|
await self.del();
|
||||||
|
self.selectedPlaylistItems = {};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (Object.keys(this.selectedPlaylistItems).length === 1) {
|
||||||
|
items.push({
|
||||||
|
text: 'View track info',
|
||||||
|
icon: 'info',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return items;
|
||||||
|
},
|
||||||
|
|
||||||
|
browserDropdownItems: function() {
|
||||||
|
var self = this;
|
||||||
|
var items = [];
|
||||||
|
|
||||||
|
if (Object.keys(this.selectedBrowserItems).length === 1 &&
|
||||||
|
Object.values(this.selectedBrowserItems)[0].type === 'directory') {
|
||||||
|
items.push({
|
||||||
|
text: 'Open',
|
||||||
|
icon: 'folder',
|
||||||
|
click: async function() {
|
||||||
|
await self.cd();
|
||||||
|
self.selectedBrowserItems = {};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(this.selectedBrowserItems).length === 1) {
|
||||||
|
items.push(
|
||||||
|
{
|
||||||
|
text: 'Add and play',
|
||||||
|
icon: 'play',
|
||||||
|
click: async function() {
|
||||||
|
const item = Object.values(self.selectedBrowserItems)[0];
|
||||||
|
var promise;
|
||||||
|
|
||||||
|
switch (item.type) {
|
||||||
|
case 'playlist':
|
||||||
|
promise = self.load(item.name);
|
||||||
|
break;
|
||||||
|
case 'file':
|
||||||
|
promise = self.add(item.name, position=0);
|
||||||
|
break;
|
||||||
|
case 'directory':
|
||||||
|
promise = self.add(item.name);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.warning('Unable to handle type: ' + item.type);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
await promise;
|
||||||
|
await self.playpos(0);
|
||||||
|
self.selectedBrowserItems = {};
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'View track info',
|
text: 'Replace and play',
|
||||||
icon: 'info',
|
icon: 'play',
|
||||||
|
click: async function() {
|
||||||
|
await self.clear();
|
||||||
|
|
||||||
|
const item = Object.values(self.selectedBrowserItems)[0];
|
||||||
|
var promise;
|
||||||
|
|
||||||
|
switch (item.type) {
|
||||||
|
case 'playlist':
|
||||||
|
promise = self.load(item.name);
|
||||||
|
break;
|
||||||
|
case 'file':
|
||||||
|
promise = self.add(item.name, position=0);
|
||||||
|
break;
|
||||||
|
case 'directory':
|
||||||
|
promise = self.add(item.name);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.warning('Unable to handle type: ' + item.type);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
await promise;
|
||||||
|
await self.playpos(0);
|
||||||
|
self.selectedBrowserItems = {};
|
||||||
},
|
},
|
||||||
];
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
items.push(
|
||||||
|
{
|
||||||
|
text: 'Add to queue',
|
||||||
|
icon: 'plus',
|
||||||
|
click: async function() {
|
||||||
|
const items = Object.values(self.selectedBrowserItems);
|
||||||
|
const promises = items.map(item => item.type === 'playlist' ? self.load(item.name) : self.add(item.name));
|
||||||
|
|
||||||
|
await Promise.all(promises);
|
||||||
|
self.selectedBrowserItems = {};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (Object.keys(this.selectedBrowserItems).length === 1
|
||||||
|
&& Object.values(this.selectedBrowserItems)[0].type === 'playlist') {
|
||||||
|
items.push({
|
||||||
|
text: 'Edit',
|
||||||
|
icon: 'pen',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.values(this.selectedBrowserItems).filter(item => item.type === 'playlist').length === Object.values(this.selectedBrowserItems).length) {
|
||||||
|
items.push({
|
||||||
|
text: 'Remove',
|
||||||
|
icon: 'trash',
|
||||||
|
click: async function() {
|
||||||
|
const items = Object.values(self.selectedBrowserItems);
|
||||||
|
await self.rm(playlists);
|
||||||
|
self.selectedBrowserItems = {};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(this.selectedBrowserItems).length === 1
|
||||||
|
&& Object.values(this.selectedBrowserItems)[0].type === 'file') {
|
||||||
|
items.push({
|
||||||
|
text: 'View info',
|
||||||
|
icon: 'info',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return items;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -83,6 +216,30 @@ Vue.component('music-mpd', {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Hack-ish workaround to get the browser and playlist panels to keep their height
|
||||||
|
// in sync with the nav and control bars, as both those elements are fixed.
|
||||||
|
adjustLayout: function() {
|
||||||
|
const adjust = (self) => {
|
||||||
|
const nav = document.querySelector('nav');
|
||||||
|
const panels = document.querySelectorAll('.music-mpd-container .panels .panel');
|
||||||
|
const controls = document.querySelector('.music-mpd-container .controls');
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
const panelHeight = window.innerHeight - nav.clientHeight - controls.clientHeight - 5;
|
||||||
|
if (panelHeight >= 0) {
|
||||||
|
for (const panel of panels) {
|
||||||
|
if (panelHeight != parseFloat(panel.style.height)) {
|
||||||
|
panel.style.height = panelHeight + 'px';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
adjust(this)();
|
||||||
|
setInterval(adjust(this), 2000);
|
||||||
|
},
|
||||||
|
|
||||||
_parseStatus: async function(status) {
|
_parseStatus: async function(status) {
|
||||||
if (!status || status.length === 0) {
|
if (!status || status.length === 0) {
|
||||||
status = await request('music.mpd.status');
|
status = await request('music.mpd.status');
|
||||||
|
@ -146,15 +303,24 @@ Vue.component('music-mpd', {
|
||||||
for (var item of browserItems) {
|
for (var item of browserItems) {
|
||||||
if (item.directory) {
|
if (item.directory) {
|
||||||
this.browserItems.push({
|
this.browserItems.push({
|
||||||
|
id: 'directory:' + item.directory,
|
||||||
type: 'directory',
|
type: 'directory',
|
||||||
name: item.directory,
|
name: item.directory,
|
||||||
});
|
});
|
||||||
} else if (item.playlist) {
|
} else if (item.playlist) {
|
||||||
this.browserItems.push({
|
this.browserItems.push({
|
||||||
|
id: 'playlist:' + item.playlist,
|
||||||
type: 'playlist',
|
type: 'playlist',
|
||||||
name: item.playlist,
|
name: item.playlist,
|
||||||
'last-modified': item['last-modified'],
|
'last-modified': item['last-modified'],
|
||||||
});
|
});
|
||||||
|
} else if (item.file) {
|
||||||
|
this.browserItems.push({
|
||||||
|
id: item.file,
|
||||||
|
type: 'file',
|
||||||
|
name: item.file,
|
||||||
|
...item,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -193,6 +359,18 @@ Vue.component('music-mpd', {
|
||||||
this._parseStatus(status);
|
this._parseStatus(status);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
consume: async function() {
|
||||||
|
await request('music.mpd.consume');
|
||||||
|
let status = await request('music.mpd.status');
|
||||||
|
this._parseStatus(status);
|
||||||
|
},
|
||||||
|
|
||||||
|
single: async function() {
|
||||||
|
await request('music.mpd.single');
|
||||||
|
let status = await request('music.mpd.status');
|
||||||
|
this._parseStatus(status);
|
||||||
|
},
|
||||||
|
|
||||||
playPause: async function() {
|
playPause: async function() {
|
||||||
await request('music.mpd.pause');
|
await request('music.mpd.pause');
|
||||||
let status = await request('music.mpd.status');
|
let status = await request('music.mpd.status');
|
||||||
|
@ -277,6 +455,27 @@ Vue.component('music-mpd', {
|
||||||
this._parseStatus(status);
|
this._parseStatus(status);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
add: async function(resource, position=null) {
|
||||||
|
var args = {resource: resource};
|
||||||
|
if (position != null) {
|
||||||
|
args.position = position;
|
||||||
|
}
|
||||||
|
|
||||||
|
let status = await request('music.mpd.add', args);
|
||||||
|
this._parseStatus(status);
|
||||||
|
|
||||||
|
let playlist = await request('music.mpd.playlistinfo');
|
||||||
|
this._parsePlaylist(playlist);
|
||||||
|
},
|
||||||
|
|
||||||
|
load: async function(item) {
|
||||||
|
let status = await request('music.mpd.load', {playlist:item});
|
||||||
|
this._parseStatus(status);
|
||||||
|
|
||||||
|
let playlist = await request('music.mpd.playlistinfo');
|
||||||
|
this._parsePlaylist(playlist);
|
||||||
|
},
|
||||||
|
|
||||||
del: async function() {
|
del: async function() {
|
||||||
const positions = Object.keys(this.selectedPlaylistItems);
|
const positions = Object.keys(this.selectedPlaylistItems);
|
||||||
if (!positions.length) {
|
if (!positions.length) {
|
||||||
|
@ -291,6 +490,22 @@ Vue.component('music-mpd', {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
rm: async function(items) {
|
||||||
|
if (!items) {
|
||||||
|
items = Object.values(this.selectedBrowserItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!items.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let status = await request('music.mpd.rm', {resource: items.map(_ => _.name)});
|
||||||
|
this._parseStatus(status);
|
||||||
|
|
||||||
|
items = await request('music.mpd.lsinfo', {uri: this.browserPath.join('/')});
|
||||||
|
this._parseBrowserItems(items);
|
||||||
|
},
|
||||||
|
|
||||||
swap: async function() {
|
swap: async function() {
|
||||||
if (Object.keys(this.selectedPlaylistItems).length !== 2) {
|
if (Object.keys(this.selectedPlaylistItems).length !== 2) {
|
||||||
return;
|
return;
|
||||||
|
@ -306,6 +521,21 @@ Vue.component('music-mpd', {
|
||||||
this._parsePlaylist(playlist);
|
this._parsePlaylist(playlist);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
cd: async function() {
|
||||||
|
const item = Object.values(this.selectedBrowserItems)[0];
|
||||||
|
|
||||||
|
if (item.name === '..') {
|
||||||
|
if (this.browserPath.length) {
|
||||||
|
this.browserPath.pop();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.browserPath = item.name.split('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
const items = await request('music.mpd.lsinfo', {uri: this.browserPath.join('/')});
|
||||||
|
this._parseBrowserItems(items);
|
||||||
|
},
|
||||||
|
|
||||||
onNewPlayingTrack: async function(event) {
|
onNewPlayingTrack: async function(event) {
|
||||||
var previousTrack = {
|
var previousTrack = {
|
||||||
file: this.track.file,
|
file: this.track.file,
|
||||||
|
@ -406,6 +636,14 @@ Vue.component('music-mpd', {
|
||||||
this.status.random = event.state;
|
this.status.random = event.state;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onConsumeChange: function(event) {
|
||||||
|
this.status.consume = event.state;
|
||||||
|
},
|
||||||
|
|
||||||
|
onSingleChange: function(event) {
|
||||||
|
this.status.single = event.state;
|
||||||
|
},
|
||||||
|
|
||||||
startTimer: function() {
|
startTimer: function() {
|
||||||
if (this.timer != null) {
|
if (this.timer != null) {
|
||||||
this.stopTimer();
|
this.stopTimer();
|
||||||
|
@ -458,7 +696,6 @@ Vue.component('music-mpd', {
|
||||||
Vue.set(this.selectedPlaylistItems, track.pos, track);
|
Vue.set(this.selectedPlaylistItems, track.pos, track);
|
||||||
}
|
}
|
||||||
} else if (track.pos in this.selectedPlaylistItems) {
|
} else if (track.pos in this.selectedPlaylistItems) {
|
||||||
// TODO when track clicked twice
|
|
||||||
Vue.delete(this.selectedPlaylistItems, track.pos);
|
Vue.delete(this.selectedPlaylistItems, track.pos);
|
||||||
} else {
|
} else {
|
||||||
this.selectedPlaylistItems = {};
|
this.selectedPlaylistItems = {};
|
||||||
|
@ -467,18 +704,68 @@ Vue.component('music-mpd', {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
togglePlaylistSelectionMode: function() {
|
onBrowserItemClick: function(item) {
|
||||||
this.selectionMode.playlist = !this.selectionMode.playlist;
|
if (item.type === 'directory' && item.name === '..') {
|
||||||
if (!this.selectionMode.playlist) {
|
this.selectedBrowserItems = {};
|
||||||
this.selectedPlaylistItems = {};
|
this.selectedBrowserItems[item.id] = item;
|
||||||
|
this.cd();
|
||||||
|
this.selectedBrowserItems = {};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.selectionMode.browser) {
|
||||||
|
if (item.id in this.selectedBrowserItems) {
|
||||||
|
Vue.delete(this.selectedBrowserItems, item.id);
|
||||||
|
} else {
|
||||||
|
Vue.set(this.selectedBrowserItems, item.id, item);
|
||||||
|
}
|
||||||
|
} else if (item.id in this.selectedBrowserItems) {
|
||||||
|
Vue.delete(this.selectedBrowserItems, item.id);
|
||||||
|
} else {
|
||||||
|
this.selectedBrowserItems = {};
|
||||||
|
Vue.set(this.selectedBrowserItems, item.id, item);
|
||||||
|
openDropdown(this.$refs.browserDropdown.$el);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleBrowserSelectionMode: function() {
|
togglePlaylistSelectionMode: function() {
|
||||||
this.selectionMode.browser = !this.selectionMode.browser;
|
if (this.selectionMode.playlist && Object.keys(this.selectedPlaylistItems).length) {
|
||||||
if (!this.selectionMode.browser) {
|
openDropdown(this.$refs.playlistDropdown.$el);
|
||||||
this.selectedBrowserItems = {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.selectionMode.playlist = !this.selectionMode.playlist;
|
||||||
|
},
|
||||||
|
|
||||||
|
playlistSelectAll: function() {
|
||||||
|
this.selectedPlaylistItems = {};
|
||||||
|
this.selectionMode.playlist = true;
|
||||||
|
|
||||||
|
for (var track of this.playlist) {
|
||||||
|
this.selectedPlaylistItems[track.pos] = track;
|
||||||
|
}
|
||||||
|
|
||||||
|
openDropdown(this.$refs.playlistDropdown.$el);
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleBrowserSelectionMode: function() {
|
||||||
|
if (this.selectionMode.browser && Object.keys(this.selectedBrowserItems).length) {
|
||||||
|
openDropdown(this.$refs.browserDropdown.$el);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.selectionMode.browser = !this.selectionMode.browser;
|
||||||
|
},
|
||||||
|
|
||||||
|
browserSelectAll: function() {
|
||||||
|
this.selectedBrowserItems = {};
|
||||||
|
this.selectionMode.browser = true;
|
||||||
|
|
||||||
|
for (var item of this.browserItems) {
|
||||||
|
if (item.type !== 'directory' && item.name !== '..') {
|
||||||
|
this.selectedBrowserItems[item.id] = item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
openDropdown(this.$refs.browserDropdown.$el);
|
||||||
},
|
},
|
||||||
|
|
||||||
scrollToActiveTrack: function() {
|
scrollToActiveTrack: function() {
|
||||||
|
@ -486,6 +773,28 @@ Vue.component('music-mpd', {
|
||||||
this.$refs.activePlaylistTrack[0].$el.scrollIntoView({behavior: 'smooth'});
|
this.$refs.activePlaylistTrack[0].$el.scrollIntoView({behavior: 'smooth'});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
addToPlaylistPrompt: async function() {
|
||||||
|
var resource = prompt('Path or URI of the resource to add');
|
||||||
|
if (!resource.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.add(resource);
|
||||||
|
},
|
||||||
|
|
||||||
|
savePlaylistPrompt: async function() {
|
||||||
|
var name = prompt('Playlist name');
|
||||||
|
if (!name.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let status = await request('music.mpd.save', {name: name});
|
||||||
|
this._parseStatus(status);
|
||||||
|
|
||||||
|
let items = await request('music.mpd.lsinfo', {uri: this.browserPath.join('/')});
|
||||||
|
this._parseBrowserItems(items);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
created: function() {
|
created: function() {
|
||||||
|
@ -499,9 +808,12 @@ Vue.component('music-mpd', {
|
||||||
registerEventHandler(this.onVolumeChange, 'platypush.message.event.music.VolumeChangeEvent');
|
registerEventHandler(this.onVolumeChange, 'platypush.message.event.music.VolumeChangeEvent');
|
||||||
registerEventHandler(this.onRepeatChange, 'platypush.message.event.music.PlaybackRepeatModeChangeEvent');
|
registerEventHandler(this.onRepeatChange, 'platypush.message.event.music.PlaybackRepeatModeChangeEvent');
|
||||||
registerEventHandler(this.onRandomChange, 'platypush.message.event.music.PlaybackRandomModeChangeEvent');
|
registerEventHandler(this.onRandomChange, 'platypush.message.event.music.PlaybackRandomModeChangeEvent');
|
||||||
|
registerEventHandler(this.onConsumeChange, 'platypush.message.event.music.PlaybackConsumeModeChangeEvent');
|
||||||
|
registerEventHandler(this.onSingleChange, 'platypush.message.event.music.PlaybackSingleModeChangeEvent');
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted: function() {
|
mounted: function() {
|
||||||
|
this.adjustLayout();
|
||||||
this.scrollToActiveTrack();
|
this.scrollToActiveTrack();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/skeleton.css') }}">
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/skeleton.css') }}">
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/normalize.css') }}">
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/normalize.css') }}">
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='font-awesome/css/font-awesome.min.css') }}">
|
<link rel="stylesheet" href="{{ url_for('static', filename='font-awesome/css/all.css') }}">
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/dist/webpanel.css') }}">
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/dist/webpanel.css') }}">
|
||||||
|
|
||||||
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/vue.js') }}"></script>
|
<script type="text/javascript" src="{{ url_for('static', filename='js/lib/vue.js') }}"></script>
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
|
|
||||||
<div class="row slider-container bri-properties" v-if="value.bri !== undefined">
|
<div class="row slider-container bri-properties" v-if="value.bri !== undefined">
|
||||||
<div class="col-2">
|
<div class="col-2">
|
||||||
<i class="fa fa-lightbulb-o"></i>
|
<i class="fa fa-lightbulb"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="slider-container col-10">
|
<div class="slider-container col-10">
|
||||||
<input class="slider bri" type="range" min="0" max="255" v-model="value.bri" @change="changed">
|
<input class="slider bri" type="range" min="0" max="255" v-model="value.bri" @change="changed">
|
||||||
|
|
|
@ -1,13 +1,16 @@
|
||||||
<script type="application/javascript" src="{{ url_for('static', filename='js/plugins/music.mpd/browser.js') }}"></script>
|
<script type="application/javascript" src="{{ url_for('static', filename='js/plugins/music.mpd/browser.js') }}"></script>
|
||||||
|
|
||||||
<script type="text/x-template" id="tmpl-music-mpd-browser-item">
|
<script type="text/x-template" id="tmpl-music-mpd-browser-item">
|
||||||
<div class="row item">
|
<div class="row item browser-item"
|
||||||
|
:class="{selected: selected}"
|
||||||
|
@click="$emit('input', {id: (type === 'file' ? '' : (type + ':')) + name, name: name, type: type})">
|
||||||
<div class="col-1 icon">
|
<div class="col-1 icon">
|
||||||
<i class="fa fa-folder" v-if="type == 'directory'"></i>
|
<i class="fa fa-folder" v-if="type === 'directory'"></i>
|
||||||
<i class="fa fa-list" v-else-if="type == 'playlist'"></i>
|
<i class="fa fa-list" v-else-if="type === 'playlist'"></i>
|
||||||
|
<i class="fa fa-music" v-else-if="type === 'file'"></i>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-11 name" v-text="name"></div>
|
<div class="col-11 name">{% raw %}{{ type === 'file' ? (artist || '') + (artist ? ' - ' : '') + (title || '[No Title]') : name.split('/').pop() }}{% endraw %}</div>
|
||||||
</div>
|
</div>
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -5,58 +5,84 @@
|
||||||
<div class="row music-mpd-container">
|
<div class="row music-mpd-container">
|
||||||
<div class="row panels">
|
<div class="row panels">
|
||||||
<!-- Browser section -->
|
<!-- Browser section -->
|
||||||
<div class="col-no-margin-l-3 col-no-margin-m-4 s-hidden browser">
|
<div class="col-no-margin-l-3 col-no-margin-m-3 s-hidden panel browser">
|
||||||
<div class="col-s-12 col-m-4 col-l-3 browser-controls">
|
<div class="col-s-12 col-no-margin-m-3 col-no-margin-l-3 browser-controls">
|
||||||
<div class="col-8 filter-container">
|
<div class="col-7 filter-container">
|
||||||
<i class="fa fa-filter input-icon"></i>
|
<i class="fa fa-filter input-icon"></i>
|
||||||
<input type="text" class="with-icon" v-model="browserFilter">
|
<input type="text" class="with-icon" v-model="browserFilter">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-4 buttons pull-right">
|
<div class="col-5 buttons pull-right">
|
||||||
<button title="Add to queue">
|
|
||||||
<i class="fa fa-plus"></i>
|
|
||||||
</button>
|
|
||||||
<button title="Remove tracks" v-if="selectionMode.playlist"
|
|
||||||
:disabled="Object.keys(selectedPlaylistItems).length === 0"
|
|
||||||
@click="del">
|
|
||||||
<i class="fa fa-trash"></i>
|
|
||||||
</button>
|
|
||||||
<button :class="{enabled: selectionMode.browser}"
|
<button :class="{enabled: selectionMode.browser}"
|
||||||
:title="selectionMode.browser ? 'End selection' : 'Start selection'"
|
:title="selectionMode.browser ? 'End selection' : 'Start selection'"
|
||||||
@click="toggleBrowserSelectionMode">
|
@click="toggleBrowserSelectionMode">
|
||||||
<i class="fa fa-check-square"></i>
|
<i class="fa fa-check"></i>
|
||||||
|
</button>
|
||||||
|
<button title="Select all"
|
||||||
|
@click="browserSelectAll">
|
||||||
|
<i class="fa fa-check-double"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<dropdown id="music-mpd-browser-dropdown"
|
||||||
|
v-if="browserItems.length > 0"
|
||||||
|
ref="browserDropdown"
|
||||||
|
:items="browserDropdownItems">
|
||||||
|
</dropdown>
|
||||||
|
|
||||||
|
<div class="spacer"></div>
|
||||||
|
|
||||||
|
<music-mpd-browser-item
|
||||||
|
v-if="browserPath.length > 0"
|
||||||
|
key=".."
|
||||||
|
id="directory:.."
|
||||||
|
type="directory"
|
||||||
|
name=".."
|
||||||
|
:selected="'directory:..' in selectedBrowserItems"
|
||||||
|
@input="onBrowserItemClick">
|
||||||
|
</music-mpd-browser-item>
|
||||||
|
|
||||||
<music-mpd-browser-item
|
<music-mpd-browser-item
|
||||||
v-for="item in browserItems"
|
v-for="item in browserItems"
|
||||||
v-if="matchesBrowserFilter(item)"
|
v-if="matchesBrowserFilter(item)"
|
||||||
:key="item.type + '-' + item.name"
|
:key="item.id"
|
||||||
|
:id="item.id"
|
||||||
:type="item.type"
|
:type="item.type"
|
||||||
:name="item.name">
|
:name="item.name"
|
||||||
|
:file="item.file"
|
||||||
|
:time="item.time"
|
||||||
|
:artist="item.artist"
|
||||||
|
:title="item.title"
|
||||||
|
:date="item.date"
|
||||||
|
:track="item.track"
|
||||||
|
:genre="item.genre"
|
||||||
|
:lastModified="item['last-modified']"
|
||||||
|
:albumUri="item['x-albumuri']"
|
||||||
|
:selected="item.id in selectedBrowserItems"
|
||||||
|
@input="onBrowserItemClick">
|
||||||
</music-mpd-browser-item>
|
</music-mpd-browser-item>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Playlist section -->
|
<!-- Playlist section -->
|
||||||
<div class="col-s-12 col-no-margin-m-8 col-no-margin-l-9 playlist">
|
<div class="col-s-12 col-no-margin-m-9 col-no-margin-l-9 panel playlist">
|
||||||
<div class="row empty" v-if="playlist.length === 0">
|
<div class="row empty" v-if="playlist.length === 0">
|
||||||
<i class="fa fa-list"></i>
|
<i class="fa fa-list"></i>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-s-12 col-m-8 col-l-9 playlist-controls" v-else>
|
<div class="col-s-12 col-m-9 col-l-9 playlist-controls" v-else>
|
||||||
<div class="col-8 filter-container">
|
<div class="col-7 filter-container">
|
||||||
<i class="fa fa-filter input-icon"></i>
|
<i class="fa fa-filter input-icon"></i>
|
||||||
<input type="text" class="with-icon" v-model="playlistFilter">
|
<input type="text" class="with-icon" v-model="playlistFilter">
|
||||||
</div>
|
</div>
|
||||||
<div class="col-4 buttons pull-right">
|
<div class="col-5 buttons pull-right">
|
||||||
<button title="Search">
|
<button title="Search">
|
||||||
<i class="fa fa-search"></i>
|
<i class="fa fa-search"></i>
|
||||||
</button>
|
</button>
|
||||||
<button title="Add item">
|
<button title="Add item" @click="addToPlaylistPrompt">
|
||||||
<i class="fa fa-plus"></i>
|
<i class="fa fa-plus"></i>
|
||||||
</button>
|
</button>
|
||||||
<button title="Save playlist" v-if="playlist.length">
|
<button title="Save playlist" v-if="playlist.length">
|
||||||
<i class="fa fa-save"></i>
|
<i class="fa fa-save" @click="savePlaylistPrompt"></i>
|
||||||
</button>
|
</button>
|
||||||
<button title="Swap tracks"
|
<button title="Swap tracks"
|
||||||
v-if="selectionMode.playlist && playlist.length > 1"
|
v-if="selectionMode.playlist && playlist.length > 1"
|
||||||
|
@ -64,15 +90,14 @@
|
||||||
@click="swap">
|
@click="swap">
|
||||||
<i class="fa fa-retweet"></i>
|
<i class="fa fa-retweet"></i>
|
||||||
</button>
|
</button>
|
||||||
<button title="Remove tracks" v-if="selectionMode.playlist"
|
|
||||||
:disabled="Object.keys(selectedPlaylistItems).length === 0"
|
|
||||||
@click="del">
|
|
||||||
<i class="fa fa-trash"></i>
|
|
||||||
</button>
|
|
||||||
<button :class="{enabled: selectionMode.playlist}"
|
<button :class="{enabled: selectionMode.playlist}"
|
||||||
:title="selectionMode.playlist ? 'End selection' : 'Start selection'"
|
:title="selectionMode.playlist ? 'End selection' : 'Start selection'"
|
||||||
@click="togglePlaylistSelectionMode">
|
@click="togglePlaylistSelectionMode">
|
||||||
<i class="fa fa-check-square"></i>
|
<i class="fa fa-check"></i>
|
||||||
|
</button>
|
||||||
|
<button title="Select all"
|
||||||
|
@click="playlistSelectAll">
|
||||||
|
<i class="fa fa-check-double"></i>
|
||||||
</button>
|
</button>
|
||||||
<button title="Clear playlist" @click="clear">
|
<button title="Clear playlist" @click="clear">
|
||||||
<i class="fa fa-ban"></i>
|
<i class="fa fa-ban"></i>
|
||||||
|
@ -141,11 +166,17 @@
|
||||||
|
|
||||||
<div class="col-3 pull-right">
|
<div class="col-3 pull-right">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
<button @click="single" :class="{enabled: status.single}" title="Toggle single mode">
|
||||||
|
<i class="fa fa-chess-pawn"></i>
|
||||||
|
</button>
|
||||||
|
<button @click="consume" :class="{enabled: status.consume}" title="Toggle consume mode">
|
||||||
|
<i class="fa fa-utensils"></i>
|
||||||
|
</button>
|
||||||
<button @click="random" :class="{enabled: status.random}" title="Toggle shuffle">
|
<button @click="random" :class="{enabled: status.random}" title="Toggle shuffle">
|
||||||
<i class="fa fa-random"></i>
|
<i class="fa fa-random"></i>
|
||||||
</button>
|
</button>
|
||||||
<button @click="repeat" :class="{enabled: status.repeat}" title="Toggle repeat">
|
<button @click="repeat" :class="{enabled: status.repeat}" title="Toggle repeat">
|
||||||
<i class="fa fa-repeat"></i>
|
<i class="fa fa-redo"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script type="application/javascript" src="{{ url_for('static', filename='js/plugins/music.mpd/playlist.js') }}"></script>
|
<script type="application/javascript" src="{{ url_for('static', filename='js/plugins/music.mpd/playlist.js') }}"></script>
|
||||||
|
|
||||||
<script type="text/x-template" id="tmpl-music-mpd-playlist-item">
|
<script type="text/x-template" id="tmpl-music-mpd-playlist-item">
|
||||||
<div class="row item"
|
<div class="row item playlist-item"
|
||||||
:class="{selected: selected, active: active}"
|
:class="{selected: selected, active: active}"
|
||||||
@click="$emit('input', track)">
|
@click="$emit('input', track)">
|
||||||
<div class="col-5 artist" v-text="track.artist"></div>
|
<div class="col-5 artist" v-text="track.artist"></div>
|
||||||
|
|
|
@ -225,6 +225,34 @@ class MusicMpdPlugin(MusicPlugin):
|
||||||
value = 1 if value == 0 else 0
|
value = 1 if value == 0 else 0
|
||||||
return self._exec('random', value)
|
return self._exec('random', value)
|
||||||
|
|
||||||
|
@action
|
||||||
|
def consume(self, value=None):
|
||||||
|
"""
|
||||||
|
Set consume mode
|
||||||
|
|
||||||
|
:param value: If set, set the consume state this value (true/false). Default: None (toggle current state)
|
||||||
|
:type value: bool
|
||||||
|
"""
|
||||||
|
|
||||||
|
if value is None:
|
||||||
|
value = int(self.status().output['consume'])
|
||||||
|
value = 1 if value == 0 else 0
|
||||||
|
return self._exec('consume', value)
|
||||||
|
|
||||||
|
@action
|
||||||
|
def single(self, value=None):
|
||||||
|
"""
|
||||||
|
Set single mode
|
||||||
|
|
||||||
|
:param value: If set, set the consume state this value (true/false). Default: None (toggle current state)
|
||||||
|
:type value: bool
|
||||||
|
"""
|
||||||
|
|
||||||
|
if value is None:
|
||||||
|
value = int(self.status().output['single'])
|
||||||
|
value = 1 if value == 0 else 0
|
||||||
|
return self._exec('single', value)
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def repeat(self, value=None):
|
def repeat(self, value=None):
|
||||||
"""
|
"""
|
||||||
|
@ -248,17 +276,24 @@ class MusicMpdPlugin(MusicPlugin):
|
||||||
return self._exec('shuffle')
|
return self._exec('shuffle')
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def add(self, resource, queue=False, position=None):
|
def save(self, name):
|
||||||
|
"""
|
||||||
|
Save the current tracklist to a new playlist with the specified name
|
||||||
|
|
||||||
|
:param name: Name of the playlist
|
||||||
|
:type name: str
|
||||||
|
"""
|
||||||
|
return self._exec('save', name)
|
||||||
|
|
||||||
|
@action
|
||||||
|
def add(self, resource, position=None):
|
||||||
"""
|
"""
|
||||||
Add a resource (track, album, artist, folder etc.) to the current playlist
|
Add a resource (track, album, artist, folder etc.) to the current playlist
|
||||||
|
|
||||||
:param resource: Resource path or URI
|
:param resource: Resource path or URI
|
||||||
:type resource: str
|
:type resource: str
|
||||||
|
|
||||||
:param queue: If true then the tracks will be queued after the currently playing track (default: False)
|
:param position: Position where the track(s) will be inserted (default: end of the playlist)
|
||||||
:type queue: bool
|
|
||||||
|
|
||||||
:param position: Position where the track(s) will be inserted if queue is false (default: end of the playlist)
|
|
||||||
:type position: int
|
:type position: int
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -278,7 +313,7 @@ class MusicMpdPlugin(MusicPlugin):
|
||||||
r = self._parse_resource(resource)
|
r = self._parse_resource(resource)
|
||||||
|
|
||||||
if position is None:
|
if position is None:
|
||||||
return self._exec('insert' if queue else 'add', r)
|
return self._exec('add', r)
|
||||||
return self._exec('addid', r, position)
|
return self._exec('addid', r, position)
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
@ -288,8 +323,30 @@ class MusicMpdPlugin(MusicPlugin):
|
||||||
|
|
||||||
:param positions: Positions of the tracks to be removed
|
:param positions: Positions of the tracks to be removed
|
||||||
:type positions: list[int]
|
:type positions: list[int]
|
||||||
|
|
||||||
|
:return: The modified playlist
|
||||||
"""
|
"""
|
||||||
return self._exec('delete', *positions)
|
|
||||||
|
for pos in sorted(positions, key=int, reverse=True):
|
||||||
|
self._exec('delete', pos)
|
||||||
|
return self.playlistinfo()
|
||||||
|
|
||||||
|
@action
|
||||||
|
def rm(self, playlist):
|
||||||
|
"""
|
||||||
|
Permanently remove playlist(s) by name
|
||||||
|
|
||||||
|
:param playlist: Name or list of playlist names to remove
|
||||||
|
:type playlist: str or list[str]
|
||||||
|
"""
|
||||||
|
|
||||||
|
if isinstance(playlist, str):
|
||||||
|
playlist = [playlist]
|
||||||
|
elif not isinstance(playlist, list):
|
||||||
|
raise RuntimeError('Invalid type for playlist: {}'.format(type(playlist)))
|
||||||
|
|
||||||
|
for p in playlist:
|
||||||
|
self._exec('rm', p)
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def move(self, from_pos, to_pos):
|
def move(self, from_pos, to_pos):
|
||||||
|
|
Loading…
Reference in a new issue