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; } &.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 {

View file

@ -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);

View file

@ -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 () => {

View file

@ -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() {

View file

@ -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] = '';
}
},
}, },
}); });

View file

@ -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>

View file

@ -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>