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:
Fabio Manganiello 2019-06-07 00:43:39 +02:00
parent 7df0cec14e
commit 0b6b29f043
7 changed files with 215 additions and 35 deletions

View file

@ -21,13 +21,13 @@
&.selected { background: $selected-bg !important; }
.artist {
font-size: $artist-font-size;
font-size: .9em;
}
}
* > .duration {
color: $duration-color;
font-size: $duration-font-size;
font-size: .7em;
}
* > button {
@ -65,7 +65,7 @@
background: $browser-panel-bg;
border-right: $default-border-2;
padding: .3rem 1rem 6rem 1rem;
font-size: $browser-font-size;
font-size: .9em;
.item {
background: none;
@ -163,6 +163,15 @@
.artist {
font-weight: bold;
}
a {
color: initial;
text-decoration: none;
&:hover {
color: $track-info-hover-color;
}
}
}
}
@ -226,8 +235,8 @@
* > .elapsed-time,
* > .total-time {
font-size: $control-time-font-size;
color: $control-time-color;
font-size: .7em;
color: .7em;
}
* > .elapsed-time {
@ -267,10 +276,6 @@
}
}
.dropdown {
z-index: 503;
}
.results-controls {
position: fixed;
padding: 0;
@ -322,6 +327,12 @@
}
}
#music-mpd-search-modal {
.dropdown {
z-index: 503;
}
}
@media #{map-get($widths, 's')} {
#music-mpd-info {
.modal {

View file

@ -2,18 +2,13 @@ $button-enabled-color: #59df3e;
$button-hover-color: $button-enabled-color;
$play-button-hover-color: #64ef4a;
$artist-font-size: $font-size * 0.9333;
$duration-color: #666;
$duration-font-size: $font-size * 0.86666;
$control-panel-bg: rgba(245,245,245,0.95);
$control-panel-shadow: 0 -2.5px 4px 0 #c0c0c0;
$control-time-color: #666;
$control-time-font-size: $font-size * 0.666666;
$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-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-attr-color: #777;
$track-info-hover-color: rgb(46,190,110);

View file

@ -69,6 +69,28 @@ Vue.component('modal', {
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) {
var handler = (self) => {
return () => {

View file

@ -46,6 +46,8 @@ Vue.component('music-mpd', {
var items = [];
if (Object.keys(this.selectedPlaylistItems).length === 1) {
const track = Object.values(this.selectedPlaylistItems)[0];
items.push({
text: 'Play',
icon: 'play',
@ -53,9 +55,32 @@ Vue.component('music-mpd', {
await self.playpos();
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({
text: 'Add to playlist',
icon: 'list',
@ -85,8 +110,7 @@ Vue.component('music-mpd', {
text: 'View track info',
icon: 'info',
click: async function() {
self.infoItem = Object.values(self.selectedPlaylistItems)[0];
self.modalVisible.info = true;
await self.info(Object.values(self.selectedPlaylistItems)[0]);
},
});
}
@ -111,6 +135,8 @@ Vue.component('music-mpd', {
}
if (Object.keys(this.selectedBrowserItems).length === 1) {
const item = Object.values(this.selectedBrowserItems)[0];
items.push(
{
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(
@ -547,6 +595,41 @@ Vue.component('music-mpd', {
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) {
var previousTrack = {
file: this.track.file,
@ -791,7 +874,21 @@ Vue.component('music-mpd', {
scrollToActiveTrack: function() {
if (this.$refs.activePlaylistTrack && this.$refs.activePlaylistTrack.length) {
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() {

View file

@ -5,13 +5,14 @@ Vue.component('music-mpd-search', {
return {
visible: false,
showResults: false,
results: false,
results: [],
filter: '',
selectionMode: false,
selectedItems: {},
query: {
any: '',
file: '',
artist: '',
title: '',
album: '',
@ -66,9 +67,36 @@ Vue.component('music-mpd-search', {
);
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({
text: 'View info',
icon: 'info',
click: function() {
self.$emit('info', item);
},
});
}
@ -86,7 +114,9 @@ Vue.component('music-mpd-search', {
return items;
}, []);
this.results = [];
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();
@ -135,11 +165,18 @@ Vue.component('music-mpd-search', {
this.selectionMode = true;
for (var item of this.results) {
this.selectedItems[item.id] = item;
this.selectedItems[item.file] = item;
}
openDropdown(this.$refs.dropdown.$el);
},
resetQuery: function() {
this.filter = '';
for (const attr of Object.keys(this.query)) {
this.query[attr] = '';
}
},
},
});

View file

@ -6,7 +6,10 @@
<script type="text/x-template" id="tmpl-music-mpd">
<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">
<div class="info-container">
@ -30,6 +33,11 @@
<div class="col-8 value" v-text="infoItem.album"></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="col-4 attr">Disc</div>
<div class="col-8 value" v-text="infoItem.disc"></div>
@ -174,8 +182,14 @@
<div class="row controls">
<div class="col-3 track-container">
<div class="track-info" v-if="status.state == 'play' || status.state == 'pause'">
<div class="row artist" v-text="track.artist"></div>
<div class="row title" v-text="track.title"></div>
<div class="row artist">
<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>

View file

@ -4,10 +4,19 @@
<modal id="music-mpd-search-modal" title="Search" v-model="visible"
:width="showResults ? '90vw' : 'initial'" ref="modal"
@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">
<form ref="form" @submit.prevent="search" :class="{hidden: showResults}">
<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 class="row">
<input type="text" v-model.lazy.trim="query.artist" placeholder="Artist">
@ -56,12 +65,6 @@
</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="no-results" v-if="results.length === 0">No results</div>
<div v-else>