Improved support for multiple modals and added ability to search for albums and artists on the fly from tracks in music.mpd - vue.js refactoring WIP
This commit is contained in:
parent
7df0cec14e
commit
0b6b29f043
7 changed files with 215 additions and 35 deletions
|
@ -21,13 +21,13 @@
|
||||||
&.selected { background: $selected-bg !important; }
|
&.selected { background: $selected-bg !important; }
|
||||||
|
|
||||||
.artist {
|
.artist {
|
||||||
font-size: $artist-font-size;
|
font-size: .9em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
* > .duration {
|
* > .duration {
|
||||||
color: $duration-color;
|
color: $duration-color;
|
||||||
font-size: $duration-font-size;
|
font-size: .7em;
|
||||||
}
|
}
|
||||||
|
|
||||||
* > button {
|
* > button {
|
||||||
|
@ -65,7 +65,7 @@
|
||||||
background: $browser-panel-bg;
|
background: $browser-panel-bg;
|
||||||
border-right: $default-border-2;
|
border-right: $default-border-2;
|
||||||
padding: .3rem 1rem 6rem 1rem;
|
padding: .3rem 1rem 6rem 1rem;
|
||||||
font-size: $browser-font-size;
|
font-size: .9em;
|
||||||
|
|
||||||
.item {
|
.item {
|
||||||
background: none;
|
background: none;
|
||||||
|
@ -163,6 +163,15 @@
|
||||||
.artist {
|
.artist {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: initial;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: $track-info-hover-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -226,8 +235,8 @@
|
||||||
|
|
||||||
* > .elapsed-time,
|
* > .elapsed-time,
|
||||||
* > .total-time {
|
* > .total-time {
|
||||||
font-size: $control-time-font-size;
|
font-size: .7em;
|
||||||
color: $control-time-color;
|
color: .7em;
|
||||||
}
|
}
|
||||||
|
|
||||||
* > .elapsed-time {
|
* > .elapsed-time {
|
||||||
|
@ -267,10 +276,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown {
|
|
||||||
z-index: 503;
|
|
||||||
}
|
|
||||||
|
|
||||||
.results-controls {
|
.results-controls {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
@ -322,6 +327,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#music-mpd-search-modal {
|
||||||
|
.dropdown {
|
||||||
|
z-index: 503;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media #{map-get($widths, 's')} {
|
@media #{map-get($widths, 's')} {
|
||||||
#music-mpd-info {
|
#music-mpd-info {
|
||||||
.modal {
|
.modal {
|
||||||
|
|
|
@ -2,18 +2,13 @@ $button-enabled-color: #59df3e;
|
||||||
$button-hover-color: $button-enabled-color;
|
$button-hover-color: $button-enabled-color;
|
||||||
$play-button-hover-color: #64ef4a;
|
$play-button-hover-color: #64ef4a;
|
||||||
|
|
||||||
$artist-font-size: $font-size * 0.9333;
|
|
||||||
|
|
||||||
$duration-color: #666;
|
$duration-color: #666;
|
||||||
$duration-font-size: $font-size * 0.86666;
|
|
||||||
|
|
||||||
$control-panel-bg: rgba(245,245,245,0.95);
|
$control-panel-bg: rgba(245,245,245,0.95);
|
||||||
$control-panel-shadow: 0 -2.5px 4px 0 #c0c0c0;
|
$control-panel-shadow: 0 -2.5px 4px 0 #c0c0c0;
|
||||||
$control-time-color: #666;
|
$control-time-color: #666;
|
||||||
$control-time-font-size: $font-size * 0.666666;
|
|
||||||
|
|
||||||
$browser-panel-bg: rgba(248,250,250,0.95);
|
$browser-panel-bg: rgba(248,250,250,0.95);
|
||||||
$browser-font-size: $font-size * 0.8666;
|
|
||||||
|
|
||||||
$empty-playlist-color: rgba(200,200,200,0.7);
|
$empty-playlist-color: rgba(200,200,200,0.7);
|
||||||
$empty-playlist-shadow: 2px 1px rgb(235,235,235);
|
$empty-playlist-shadow: 2px 1px rgb(235,235,235);
|
||||||
|
@ -30,4 +25,5 @@ $search-modal-footer-border: 1px solid #ccc;
|
||||||
|
|
||||||
$info-modal-row-border: 1px solid #ddd;
|
$info-modal-row-border: 1px solid #ddd;
|
||||||
$info-modal-attr-color: #777;
|
$info-modal-attr-color: #777;
|
||||||
|
$track-info-hover-color: rgb(46,190,110);
|
||||||
|
|
||||||
|
|
|
@ -69,6 +69,28 @@ Vue.component('modal', {
|
||||||
this.prevValue = this.value;
|
this.prevValue = this.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.value) {
|
||||||
|
// Make sure that a newly opened or visible+updated modal always comes to the front
|
||||||
|
const myZIndex = parseInt(getComputedStyle(this.$el).zIndex);
|
||||||
|
var maxZIndex = myZIndex;
|
||||||
|
var outermostModals = [];
|
||||||
|
|
||||||
|
for (const modal of document.querySelectorAll('.modal-container:not(.hidden)')) {
|
||||||
|
const zIndex = parseInt(getComputedStyle(modal).zIndex);
|
||||||
|
|
||||||
|
if (zIndex > maxZIndex) {
|
||||||
|
maxZIndex = zIndex;
|
||||||
|
outermostModals = [modal];
|
||||||
|
} else if (zIndex == maxZIndex) {
|
||||||
|
outermostModals.push(modal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outermostModals.indexOf(this.$el) < 0 || outermostModals.length > 1) {
|
||||||
|
this.$el.style.zIndex = maxZIndex+1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (this.value && this.timeout && !this.timeoutId) {
|
if (this.value && this.timeout && !this.timeoutId) {
|
||||||
var handler = (self) => {
|
var handler = (self) => {
|
||||||
return () => {
|
return () => {
|
||||||
|
|
|
@ -46,14 +46,39 @@ Vue.component('music-mpd', {
|
||||||
var items = [];
|
var items = [];
|
||||||
|
|
||||||
if (Object.keys(this.selectedPlaylistItems).length === 1) {
|
if (Object.keys(this.selectedPlaylistItems).length === 1) {
|
||||||
|
const track = Object.values(this.selectedPlaylistItems)[0];
|
||||||
|
|
||||||
items.push({
|
items.push({
|
||||||
text: 'Play',
|
text: 'Play',
|
||||||
icon: 'play',
|
icon: 'play',
|
||||||
click: async function() {
|
click: async function() {
|
||||||
await self.playpos();
|
await self.playpos();
|
||||||
self.selectedPlaylistItems = {};
|
self.selectedPlaylistItems = {};
|
||||||
},
|
},
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (track.artist && track.artist.length) {
|
||||||
|
items.push({
|
||||||
|
text: 'View artist',
|
||||||
|
icon: 'user',
|
||||||
|
click: async function() {
|
||||||
|
await self.searchArtist(track);
|
||||||
|
self.selectedPlaylistItems = {};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (track.album && track.album.length) {
|
||||||
|
items.push({
|
||||||
|
text: 'View album',
|
||||||
|
icon: 'compact-disc',
|
||||||
|
click: async function() {
|
||||||
|
await self.searchAlbum(track);
|
||||||
|
self.selectedPlaylistItems = {};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
items.push({
|
items.push({
|
||||||
|
@ -85,8 +110,7 @@ Vue.component('music-mpd', {
|
||||||
text: 'View track info',
|
text: 'View track info',
|
||||||
icon: 'info',
|
icon: 'info',
|
||||||
click: async function() {
|
click: async function() {
|
||||||
self.infoItem = Object.values(self.selectedPlaylistItems)[0];
|
await self.info(Object.values(self.selectedPlaylistItems)[0]);
|
||||||
self.modalVisible.info = true;
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -111,6 +135,8 @@ Vue.component('music-mpd', {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Object.keys(this.selectedBrowserItems).length === 1) {
|
if (Object.keys(this.selectedBrowserItems).length === 1) {
|
||||||
|
const item = Object.values(this.selectedBrowserItems)[0];
|
||||||
|
|
||||||
items.push(
|
items.push(
|
||||||
{
|
{
|
||||||
text: 'Play',
|
text: 'Play',
|
||||||
|
@ -169,6 +195,28 @@ Vue.component('music-mpd', {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (item.artist && item.artist.length) {
|
||||||
|
items.push({
|
||||||
|
text: 'View artist',
|
||||||
|
icon: 'user',
|
||||||
|
click: async function() {
|
||||||
|
await self.searchArtist(item);
|
||||||
|
self.selectedPlaylistItems = {};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.album && item.album.length) {
|
||||||
|
items.push({
|
||||||
|
text: 'View album',
|
||||||
|
icon: 'compact-disc',
|
||||||
|
click: async function() {
|
||||||
|
await self.searchAlbum(item);
|
||||||
|
self.selectedPlaylistItems = {};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
items.push(
|
items.push(
|
||||||
|
@ -547,6 +595,41 @@ Vue.component('music-mpd', {
|
||||||
this._parseBrowserItems(items);
|
this._parseBrowserItems(items);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
info: async function(item) {
|
||||||
|
var info = item;
|
||||||
|
|
||||||
|
if (typeof(item) === 'string') {
|
||||||
|
item = await request('music.mpd.search', {filter: {file: info}});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.infoItem = item;
|
||||||
|
this.modalVisible.info = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
searchArtist: async function(item) {
|
||||||
|
await this.search({artist: item.artist});
|
||||||
|
},
|
||||||
|
|
||||||
|
searchAlbum: async function(item) {
|
||||||
|
var query = {};
|
||||||
|
|
||||||
|
if (item['x-albumuri']) {
|
||||||
|
query.file = item['x-albumuri'];
|
||||||
|
} else {
|
||||||
|
query.artist = item.albumartist || item.artist;
|
||||||
|
query.album = item.album;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.search(query);
|
||||||
|
},
|
||||||
|
|
||||||
|
search: async function(query) {
|
||||||
|
this.$refs.search.resetQuery();
|
||||||
|
this.$refs.search.query = query;
|
||||||
|
this.$refs.search.visible = true;
|
||||||
|
await this.$refs.search.search();
|
||||||
|
},
|
||||||
|
|
||||||
onNewPlayingTrack: async function(event) {
|
onNewPlayingTrack: async function(event) {
|
||||||
var previousTrack = {
|
var previousTrack = {
|
||||||
file: this.track.file,
|
file: this.track.file,
|
||||||
|
@ -791,7 +874,21 @@ Vue.component('music-mpd', {
|
||||||
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'});
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const self = this;
|
||||||
|
setTimeout(() => {
|
||||||
|
var parent = self.$refs.activePlaylistTrack[0].$el.parentElement;
|
||||||
|
if (parent.clientHeight + parent.scrollTop < parent.scrollHeight) {
|
||||||
|
if (parent.scrollTop-50 > 0) {
|
||||||
|
parent.scrollTop -= 50;
|
||||||
|
} else {
|
||||||
|
parent.scrollTop = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 750);
|
||||||
},
|
},
|
||||||
|
|
||||||
addToPlaylistPrompt: async function() {
|
addToPlaylistPrompt: async function() {
|
||||||
|
|
|
@ -5,13 +5,14 @@ Vue.component('music-mpd-search', {
|
||||||
return {
|
return {
|
||||||
visible: false,
|
visible: false,
|
||||||
showResults: false,
|
showResults: false,
|
||||||
results: false,
|
results: [],
|
||||||
filter: '',
|
filter: '',
|
||||||
selectionMode: false,
|
selectionMode: false,
|
||||||
selectedItems: {},
|
selectedItems: {},
|
||||||
|
|
||||||
query: {
|
query: {
|
||||||
any: '',
|
any: '',
|
||||||
|
file: '',
|
||||||
artist: '',
|
artist: '',
|
||||||
title: '',
|
title: '',
|
||||||
album: '',
|
album: '',
|
||||||
|
@ -66,9 +67,36 @@ Vue.component('music-mpd-search', {
|
||||||
);
|
);
|
||||||
|
|
||||||
if (Object.keys(this.selectedItems).length === 1) {
|
if (Object.keys(this.selectedItems).length === 1) {
|
||||||
|
const item = Object.values(this.selectedItems)[0];
|
||||||
|
|
||||||
|
if (item.artist && item.artist.length) {
|
||||||
|
items.push({
|
||||||
|
text: 'View artist',
|
||||||
|
icon: 'user',
|
||||||
|
click: async function() {
|
||||||
|
await self.mpd.searchArtist(item);
|
||||||
|
self.selectedItems = {};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.album && item.album.length) {
|
||||||
|
items.push({
|
||||||
|
text: 'View album',
|
||||||
|
icon: 'compact-disc',
|
||||||
|
click: async function() {
|
||||||
|
await self.mpd.searchAlbum(item);
|
||||||
|
self.selectedItems = {};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
items.push({
|
items.push({
|
||||||
text: 'View info',
|
text: 'View info',
|
||||||
icon: 'info',
|
icon: 'info',
|
||||||
|
click: function() {
|
||||||
|
self.$emit('info', item);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,7 +114,9 @@ Vue.component('music-mpd-search', {
|
||||||
return items;
|
return items;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
this.results = [];
|
||||||
var results = await request('music.mpd.search', {filter: filter});
|
var results = await request('music.mpd.search', {filter: filter});
|
||||||
|
|
||||||
this.results = results.sort((a,b) => {
|
this.results = results.sort((a,b) => {
|
||||||
const tokenize = (t) => {
|
const tokenize = (t) => {
|
||||||
return ''.concat(t.artist || '', '-', t.album || '', '-', t.disc || '', '-', t.track || '', t.title || '').toLocaleLowerCase();
|
return ''.concat(t.artist || '', '-', t.album || '', '-', t.disc || '', '-', t.track || '', t.title || '').toLocaleLowerCase();
|
||||||
|
@ -135,11 +165,18 @@ Vue.component('music-mpd-search', {
|
||||||
this.selectionMode = true;
|
this.selectionMode = true;
|
||||||
|
|
||||||
for (var item of this.results) {
|
for (var item of this.results) {
|
||||||
this.selectedItems[item.id] = item;
|
this.selectedItems[item.file] = item;
|
||||||
}
|
}
|
||||||
|
|
||||||
openDropdown(this.$refs.dropdown.$el);
|
openDropdown(this.$refs.dropdown.$el);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
resetQuery: function() {
|
||||||
|
this.filter = '';
|
||||||
|
for (const attr of Object.keys(this.query)) {
|
||||||
|
this.query[attr] = '';
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,10 @@
|
||||||
|
|
||||||
<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" :mpd="this"></music-mpd-search>
|
<music-mpd-search ref="search"
|
||||||
|
@info="info"
|
||||||
|
:mpd="this">
|
||||||
|
</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">
|
||||||
<div class="info-container">
|
<div class="info-container">
|
||||||
|
@ -30,6 +33,11 @@
|
||||||
<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="col-4 attr">Year</div>
|
||||||
|
<div class="col-8 value" v-text="infoItem.year"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="row" v-if="infoItem.disc">
|
<div class="row" v-if="infoItem.disc">
|
||||||
<div class="col-4 attr">Disc</div>
|
<div class="col-4 attr">Disc</div>
|
||||||
<div class="col-8 value" v-text="infoItem.disc"></div>
|
<div class="col-8 value" v-text="infoItem.disc"></div>
|
||||||
|
@ -174,8 +182,14 @@
|
||||||
<div class="row controls">
|
<div class="row controls">
|
||||||
<div class="col-3 track-container">
|
<div class="col-3 track-container">
|
||||||
<div class="track-info" v-if="status.state == 'play' || status.state == 'pause'">
|
<div class="track-info" v-if="status.state == 'play' || status.state == 'pause'">
|
||||||
<div class="row artist" v-text="track.artist"></div>
|
<div class="row artist">
|
||||||
<div class="row title" v-text="track.title"></div>
|
<a href="#" v-text="track.artist" @click="searchArtist(track)" v-if="track.artist"></a>
|
||||||
|
<span v-text="track.artist" v-else></span>
|
||||||
|
</div>
|
||||||
|
<div class="row title">
|
||||||
|
<a href="#" v-text="track.title" @click="searchAlbum(track)" v-if="track.album"></a>
|
||||||
|
<span v-text="track.title" v-else></span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -4,10 +4,19 @@
|
||||||
<modal id="music-mpd-search-modal" title="Search" v-model="visible"
|
<modal id="music-mpd-search-modal" title="Search" v-model="visible"
|
||||||
:width="showResults ? '90vw' : 'initial'" ref="modal"
|
:width="showResults ? '90vw' : 'initial'" ref="modal"
|
||||||
@open="$refs.form.querySelector('input[type=text]:first-child').focus()">
|
@open="$refs.form.querySelector('input[type=text]:first-child').focus()">
|
||||||
|
<dropdown id="music-mpd-search-dropdown"
|
||||||
|
v-if="results.length > 0"
|
||||||
|
ref="dropdown"
|
||||||
|
:items="dropdownItems">
|
||||||
|
</dropdown>
|
||||||
|
|
||||||
<div class="search">
|
<div class="search">
|
||||||
<form ref="form" @submit.prevent="search" :class="{hidden: showResults}">
|
<form ref="form" @submit.prevent="search" :class="{hidden: showResults}">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<input type="text" v-model.lazy.trim="query.any" placeholder="Free-text search">
|
<input type="text" v-model.lazy.trim="query.any" placeholder="Any field">
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<input type="text" v-model.lazy.trim="query.file" placeholder="Filename or URI">
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<input type="text" v-model.lazy.trim="query.artist" placeholder="Artist">
|
<input type="text" v-model.lazy.trim="query.artist" placeholder="Artist">
|
||||||
|
@ -56,12 +65,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<dropdown id="music-mpd-search-dropdown"
|
|
||||||
v-if="results.length > 0"
|
|
||||||
ref="dropdown"
|
|
||||||
:items="dropdownItems">
|
|
||||||
</dropdown>
|
|
||||||
|
|
||||||
<div class="results" :class="{hidden: !showResults}">
|
<div class="results" :class="{hidden: !showResults}">
|
||||||
<div class="no-results" v-if="results.length === 0">No results</div>
|
<div class="no-results" v-if="results.length === 0">No results</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
|
|
Loading…
Reference in a new issue