Implemented search in music.mpd
This commit is contained in:
parent
d10649e1f1
commit
d2887b7454
21 changed files with 358 additions and 52 deletions
2
platypush/backend/http/dist/index.html
vendored
2
platypush/backend/http/dist/index.html
vendored
|
@ -1 +1 @@
|
||||||
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/favicon.ico"><title>platypush</title><link href="/static/css/chunk-24ff873d.64d9bc0b.css" rel="prefetch"><link href="/static/css/chunk-3b44ec4e.0c4a18da.css" rel="prefetch"><link href="/static/css/chunk-45939517.e4a1ddf3.css" rel="prefetch"><link href="/static/css/chunk-4bbbb9a3.3108d379.css" rel="prefetch"><link href="/static/css/chunk-4c0b0f48.009b6a70.css" rel="prefetch"><link href="/static/css/chunk-4eeb8349.2026dd4f.css" rel="prefetch"><link href="/static/css/chunk-52804492.a64fd302.css" rel="prefetch"><link href="/static/css/chunk-53360c78.c486a396.css" rel="prefetch"><link href="/static/css/chunk-62a3d08e.6cb54f10.css" rel="prefetch"><link href="/static/css/chunk-d8561e02.b52f89a0.css" rel="prefetch"><link href="/static/css/chunk-e8078048.c6785c78.css" rel="prefetch"><link href="/static/js/chunk-24ff873d.691c883d.js" rel="prefetch"><link href="/static/js/chunk-2d2091df.1e51ae4c.js" rel="prefetch"><link href="/static/js/chunk-2d21da1a.6bb60047.js" rel="prefetch"><link href="/static/js/chunk-3b44ec4e.904c7e10.js" rel="prefetch"><link href="/static/js/chunk-45939517.c0034c6b.js" rel="prefetch"><link href="/static/js/chunk-4bbbb9a3.251fff37.js" rel="prefetch"><link href="/static/js/chunk-4c0b0f48.366980a2.js" rel="prefetch"><link href="/static/js/chunk-4eeb8349.5c94d58c.js" rel="prefetch"><link href="/static/js/chunk-52804492.1cbed362.js" rel="prefetch"><link href="/static/js/chunk-53360c78.51ee7c96.js" rel="prefetch"><link href="/static/js/chunk-62a3d08e.17d3c86d.js" rel="prefetch"><link href="/static/js/chunk-d8561e02.1e366cb3.js" rel="prefetch"><link href="/static/js/chunk-e8078048.ce29b8d4.js" rel="prefetch"><link href="/static/css/app.4868c461.css" rel="preload" as="style"><link href="/static/css/chunk-vendors.5dad8b00.css" rel="preload" as="style"><link href="/static/js/app.ce952734.js" rel="preload" as="script"><link href="/static/js/chunk-vendors.30e3a6cb.js" rel="preload" as="script"><link href="/static/css/chunk-vendors.5dad8b00.css" rel="stylesheet"><link href="/static/css/app.4868c461.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but platypush doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="/static/js/chunk-vendors.30e3a6cb.js"></script><script src="/static/js/app.ce952734.js"></script></body></html>
|
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/favicon.ico"><title>platypush</title><link href="/static/css/chunk-24ff873d.64d9bc0b.css" rel="prefetch"><link href="/static/css/chunk-3b44ec4e.0c4a18da.css" rel="prefetch"><link href="/static/css/chunk-45939517.e4a1ddf3.css" rel="prefetch"><link href="/static/css/chunk-4bbbb9a3.3108d379.css" rel="prefetch"><link href="/static/css/chunk-4c0b0f48.009b6a70.css" rel="prefetch"><link href="/static/css/chunk-4eeb8349.2026dd4f.css" rel="prefetch"><link href="/static/css/chunk-53360c78.c486a396.css" rel="prefetch"><link href="/static/css/chunk-62a3d08e.6cb54f10.css" rel="prefetch"><link href="/static/css/chunk-d8561e02.b52f89a0.css" rel="prefetch"><link href="/static/css/chunk-e8078048.c6785c78.css" rel="prefetch"><link href="/static/css/chunk-e9fa4af6.f205d678.css" rel="prefetch"><link href="/static/js/chunk-24ff873d.691c883d.js" rel="prefetch"><link href="/static/js/chunk-2d2091df.1e51ae4c.js" rel="prefetch"><link href="/static/js/chunk-2d21da1a.911f2770.js" rel="prefetch"><link href="/static/js/chunk-3b44ec4e.904c7e10.js" rel="prefetch"><link href="/static/js/chunk-45939517.c0034c6b.js" rel="prefetch"><link href="/static/js/chunk-4bbbb9a3.251fff37.js" rel="prefetch"><link href="/static/js/chunk-4c0b0f48.366980a2.js" rel="prefetch"><link href="/static/js/chunk-4eeb8349.5c94d58c.js" rel="prefetch"><link href="/static/js/chunk-53360c78.51ee7c96.js" rel="prefetch"><link href="/static/js/chunk-62a3d08e.17d3c86d.js" rel="prefetch"><link href="/static/js/chunk-d8561e02.1e366cb3.js" rel="prefetch"><link href="/static/js/chunk-e8078048.ce29b8d4.js" rel="prefetch"><link href="/static/js/chunk-e9fa4af6.567a1ae1.js" rel="prefetch"><link href="/static/css/app.4868c461.css" rel="preload" as="style"><link href="/static/css/chunk-vendors.5dad8b00.css" rel="preload" as="style"><link href="/static/js/app.6e791066.js" rel="preload" as="script"><link href="/static/js/chunk-vendors.30e3a6cb.js" rel="preload" as="script"><link href="/static/css/chunk-vendors.5dad8b00.css" rel="stylesheet"><link href="/static/css/app.4868c461.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but platypush doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="/static/js/chunk-vendors.30e3a6cb.js"></script><script src="/static/js/app.6e791066.js"></script></body></html>
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
platypush/backend/http/dist/static/js/chunk-2d21da1a.911f2770.js
vendored
Normal file
2
platypush/backend/http/dist/static/js/chunk-2d21da1a.911f2770.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
platypush/backend/http/dist/static/js/chunk-2d21da1a.911f2770.js.map
vendored
Normal file
1
platypush/backend/http/dist/static/js/chunk-2d21da1a.911f2770.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
platypush/backend/http/dist/static/js/chunk-e9fa4af6.567a1ae1.js
vendored
Normal file
2
platypush/backend/http/dist/static/js/chunk-e9fa4af6.567a1ae1.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
platypush/backend/http/dist/static/js/chunk-e9fa4af6.567a1ae1.js.map
vendored
Normal file
1
platypush/backend/http/dist/static/js/chunk-e9fa4af6.567a1ae1.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -79,11 +79,12 @@
|
||||||
<div class="track-container col-s-8 col-m-8 col-l-3">
|
<div class="track-container col-s-8 col-m-8 col-l-3">
|
||||||
<div class="track-info" v-if="track && status?.state !== 'stop'">
|
<div class="track-info" v-if="track && status?.state !== 'stop'">
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<a href="#" v-text="track.title" @click="$emit('search', {album: track.album})" v-if="track.album"></a>
|
<a :href="$route.fullPath" v-text="track.title"
|
||||||
|
@click.prevent="$emit('search', {artist: track.artist, album: track.album})" v-if="track.album"></a>
|
||||||
<span v-text="track.title" v-else></span>
|
<span v-text="track.title" v-else></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="artist" v-if="track.artist">
|
<div class="artist" v-if="track.artist">
|
||||||
<a href="#" v-text="track.artist" @click="$emit('search', {artist: track.artist})"></a>
|
<a :href="$route.fullPath" v-text="track.artist" @click.prevent="$emit('search', {artist: track.artist})"></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
<Controls :status="status" :track="track" @play="$emit('play', $event)" @pause="$emit('pause', $event)"
|
<Controls :status="status" :track="track" @play="$emit('play', $event)" @pause="$emit('pause', $event)"
|
||||||
@stop="$emit('stop')" @previous="$emit('previous')" @next="$emit('next')" @seek="$emit('seek', $event)"
|
@stop="$emit('stop')" @previous="$emit('previous')" @next="$emit('next')" @seek="$emit('seek', $event)"
|
||||||
@set-volume="$emit('set-volume', $event)" @consume="$emit('consume', $event)"
|
@set-volume="$emit('set-volume', $event)" @consume="$emit('consume', $event)"
|
||||||
@repeat="$emit('repeat', $event)" @random="$emit('random', $event)" />
|
@repeat="$emit('repeat', $event)" @random="$emit('random', $event)" @search="$emit('search', $event)" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -18,7 +18,7 @@ import Controls from "@/components/Media/Controls";
|
||||||
export default {
|
export default {
|
||||||
name: "View",
|
name: "View",
|
||||||
components: {Controls},
|
components: {Controls},
|
||||||
emits: ['play', 'pause', 'stop', 'next', 'previous', 'set-volume', 'seek', 'consume', 'random', 'repeat'],
|
emits: ['play', 'pause', 'stop', 'next', 'previous', 'set-volume', 'seek', 'consume', 'random', 'repeat', 'search'],
|
||||||
props: {
|
props: {
|
||||||
pluginName: {
|
pluginName: {
|
||||||
type: String,
|
type: String,
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<MediaView :plugin-name="pluginName" :status="status" :track="track" @play="$emit('play', $event)"
|
<MediaView :plugin-name="pluginName" :status="status" :track="track" @play="$emit('play', $event)"
|
||||||
@pause="$emit('pause')" @stop="$emit('stop')" @previous="$emit('previous')" @next="$emit('next')"
|
@pause="$emit('pause')" @stop="$emit('stop')" @previous="$emit('previous')" @next="$emit('next')"
|
||||||
@set-volume="$emit('set-volume', $event)" @seek="$emit('seek', $event)" @consume="$emit('consume', $event)"
|
@set-volume="$emit('set-volume', $event)" @seek="$emit('seek', $event)" @consume="$emit('consume', $event)"
|
||||||
@repeat="$emit('repeat', $event)" @random="$emit('random', $event)" v-else>
|
@repeat="$emit('repeat', $event)" @random="$emit('random', $event)" @search="search" v-else>
|
||||||
<main>
|
<main>
|
||||||
<div class="nav-container">
|
<div class="nav-container">
|
||||||
<Nav :selected-view="selectedView" @input="selectedView = $event" />
|
<Nav :selected-view="selectedView" @input="selectedView = $event" />
|
||||||
|
@ -15,16 +15,21 @@
|
||||||
@play="$emit('play', $event)" @clear="$emit('clear')" @swap="$emit('swap-tracks', $event)"
|
@play="$emit('play', $event)" @clear="$emit('clear')" @swap="$emit('swap-tracks', $event)"
|
||||||
@add="$emit('add-to-tracklist', $event)" @remove="$emit('remove-from-tracklist', $event)"
|
@add="$emit('add-to-tracklist', $event)" @remove="$emit('remove-from-tracklist', $event)"
|
||||||
@move="$emit('tracklist-move', $event)" @save="$emit('tracklist-save', $event)"
|
@move="$emit('tracklist-move', $event)" @save="$emit('tracklist-save', $event)"
|
||||||
@track-info="$emit('track-info', $event)" @add-to-playlist="openAddToPlaylist" />
|
@info="$emit('info', $event)" @add-to-playlist="openAddToPlaylist" @search="search" />
|
||||||
|
|
||||||
<Playlists :playlists="playlists" :loading="loading" v-else-if="selectedView === 'playlists'"
|
<Playlists :playlists="playlists" :loading="loading" v-else-if="selectedView === 'playlists'"
|
||||||
:edited-playlist="editedPlaylist" :tracks="editedPlaylistTracks"
|
:edited-playlist="editedPlaylist" :tracks="editedPlaylistTracks"
|
||||||
@play="$emit('play-playlist', $event)" @load="$emit('load-playlist', $event)"
|
@play="$emit('play-playlist', $event)" @load="$emit('load-playlist', $event)"
|
||||||
@remove="$emit('remove-playlist', $event)" @playlist-edit="$emit('playlist-edit', $event)"
|
@remove="$emit('remove-playlist', $event)" @playlist-edit="$emit('playlist-edit', $event)"
|
||||||
@load-track="$emit('add-to-tracklist-from-edited-playlist', $event)"
|
@load-track="$emit('add-to-tracklist-from-edited-playlist', $event)"
|
||||||
@remove-track="$emit('remove-from-playlist', $event)" @track-info="$emit('track-info', $event)"
|
@remove-track="$emit('remove-from-playlist', $event)" @info="$emit('info', $event)"
|
||||||
@playlist-add="$emit('playlist-add', $event)" @add-to-playlist="openAddToPlaylist"
|
@playlist-add="$emit('playlist-add', $event)" @add-to-playlist="openAddToPlaylist"
|
||||||
@track-move="$emit('playlist-track-move', $event)"/>
|
@track-move="$emit('playlist-track-move', $event)" @search="search" />
|
||||||
|
|
||||||
|
<Search :loading="loading" v-else-if="selectedView === 'search'" @search="search"
|
||||||
|
:results="searchResults" @clear="$emit('search-clear')" @info="$emit('info', $event)"
|
||||||
|
@play="$emit('play', $event)" @load="$emit('add-to-tracklist', $event)"
|
||||||
|
@add-to-playlist="openAddToPlaylist"/>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</MediaView>
|
</MediaView>
|
||||||
|
@ -40,8 +45,7 @@
|
||||||
<div class="row artist" v-if="trackInfo.artist">
|
<div class="row artist" v-if="trackInfo.artist">
|
||||||
<div class="col-3 attr">Artist</div>
|
<div class="col-3 attr">Artist</div>
|
||||||
<div class="col-9 value">
|
<div class="col-9 value">
|
||||||
<a :href="$route.fullPath" v-text="trackInfo.artist"
|
<a :href="$route.fullPath" v-text="trackInfo.artist" @click.prevent="search({artist: trackInfo.artist})" />
|
||||||
@click.stop="$emit('search', {artist: trackInfo.artist})" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -54,7 +58,7 @@
|
||||||
<div class="col-3 attr">Album</div>
|
<div class="col-3 attr">Album</div>
|
||||||
<div class="col-9 value">
|
<div class="col-9 value">
|
||||||
<a :href="$route.fullPath" v-text="trackInfo.album"
|
<a :href="$route.fullPath" v-text="trackInfo.album"
|
||||||
@click.stop="$emit('search', {album: trackInfo.album})" />
|
@click.prevent="search({artist: trackInfo.artist, album: trackInfo.album})" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -81,9 +85,8 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="playlists">
|
<div class="playlists">
|
||||||
<label class="row playlist"
|
<label class="row playlist" v-for="(playlist, i) in playlists" :key="i"
|
||||||
:class="{hidden: playlistFilter?.length > 0 && playlist.name.toLowerCase().indexOf(playlistFilter.toLowerCase()) < 0}"
|
:class="{hidden: playlistFilter?.length > 0 && playlist.name.toLowerCase().indexOf(playlistFilter.toLowerCase()) < 0}">
|
||||||
v-for="(playlist, i) in playlists" :key="i">
|
|
||||||
<input type="checkbox" :checked="selectedPlaylists[i]"
|
<input type="checkbox" :checked="selectedPlaylists[i]"
|
||||||
@change="selectedPlaylists[i] = $event.target.checked" />
|
@change="selectedPlaylists[i] = $event.target.checked" />
|
||||||
<span class="name" v-text="playlist.name" />
|
<span class="name" v-text="playlist.name" />
|
||||||
|
@ -108,6 +111,7 @@ import MediaView from "@/components/Media/View";
|
||||||
import Nav from "@/components/panels/Music/Nav";
|
import Nav from "@/components/panels/Music/Nav";
|
||||||
import Playlist from "@/components/panels/Music/Playlist";
|
import Playlist from "@/components/panels/Music/Playlist";
|
||||||
import Playlists from "@/components/panels/Music/Playlists";
|
import Playlists from "@/components/panels/Music/Playlists";
|
||||||
|
import Search from "@/components/panels/Music/Search";
|
||||||
import Utils from "@/Utils";
|
import Utils from "@/Utils";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -115,11 +119,11 @@ export default {
|
||||||
emits: ['play', 'pause', 'stop', 'clear', 'previous', 'next', 'set-volume', 'seek', 'consume', 'repeat', 'random',
|
emits: ['play', 'pause', 'stop', 'clear', 'previous', 'next', 'set-volume', 'seek', 'consume', 'repeat', 'random',
|
||||||
'status-update', 'playlist-update', 'new-playing-track', 'add-to-tracklist', 'remove-from-tracklist',
|
'status-update', 'playlist-update', 'new-playing-track', 'add-to-tracklist', 'remove-from-tracklist',
|
||||||
'swap-tracks', 'play-playlist', 'load-playlist', 'remove-playlist', 'tracklist-move', 'tracklist-save',
|
'swap-tracks', 'play-playlist', 'load-playlist', 'remove-playlist', 'tracklist-move', 'tracklist-save',
|
||||||
'add-to-tracklist-from-edited-playlist', 'remove-from-playlist', 'track-info', 'playlist-add', 'add-to-playlist',
|
'add-to-tracklist-from-edited-playlist', 'remove-from-playlist', 'info', 'playlist-add', 'add-to-playlist',
|
||||||
'playlist-track-move'],
|
'playlist-track-move', 'search', 'search-clear'],
|
||||||
|
|
||||||
mixins: [Utils, MediaUtils],
|
mixins: [Utils, MediaUtils],
|
||||||
components: {Loading, Modal, Nav, MediaView, Playlist, Playlists, FormFooter},
|
components: {Loading, Modal, Nav, MediaView, Playlist, Playlists, FormFooter, Search},
|
||||||
props: {
|
props: {
|
||||||
pluginName: {
|
pluginName: {
|
||||||
type: String,
|
type: String,
|
||||||
|
@ -163,6 +167,10 @@ export default {
|
||||||
trackInfo: {
|
trackInfo: {
|
||||||
type: String,
|
type: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
searchResults: {
|
||||||
|
type: Array,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
|
@ -227,6 +235,12 @@ export default {
|
||||||
this.addToPlaylistTrack = null
|
this.addToPlaylistTrack = null
|
||||||
this.playlistFilter = ''
|
this.playlistFilter = ''
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async search(filter) {
|
||||||
|
this.$emit('search', filter)
|
||||||
|
this.$refs.trackInfo.isVisible = false
|
||||||
|
this.selectedView = 'search'
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
|
@ -345,6 +359,10 @@ main {
|
||||||
@include from($tablet) {
|
@include from($tablet) {
|
||||||
width: 35em;
|
width: 35em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.file {
|
||||||
|
user-select: text;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -27,7 +27,7 @@ export default {
|
||||||
return {
|
return {
|
||||||
playing: {
|
playing: {
|
||||||
iconClass: 'fas fa-play',
|
iconClass: 'fas fa-play',
|
||||||
displayName: 'Now Playing',
|
displayName: 'Queue',
|
||||||
},
|
},
|
||||||
|
|
||||||
search: {
|
search: {
|
||||||
|
|
|
@ -54,12 +54,12 @@
|
||||||
|
|
||||||
<div class="artist" v-if="track.artist">
|
<div class="artist" v-if="track.artist">
|
||||||
<a :href="$route.fullPath" v-text="track.artist"
|
<a :href="$route.fullPath" v-text="track.artist"
|
||||||
@click.stop="$emit('search', {artist: track.artist})" />
|
@click.prevent="$emit('search', {artist: track.artist})" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="album" v-if="track.album">
|
<div class="album" v-if="track.album">
|
||||||
<a :href="$route.fullPath" v-text="track.album"
|
<a :href="$route.fullPath" v-text="track.album"
|
||||||
@click.stop="$emit('search', {artist: track.album})" />
|
@click.prevent="$emit('search', {artist: track.artist, album: track.album})" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -71,7 +71,7 @@
|
||||||
<DropdownItem text="Play" icon-class="fa fa-play" @click="$emit('play', {pos: i})" />
|
<DropdownItem text="Play" icon-class="fa fa-play" @click="$emit('play', {pos: i})" />
|
||||||
<DropdownItem text="Add to playlist" icon-class="fa fa-list-ul" @click="$emit('add-to-playlist', track)" />
|
<DropdownItem text="Add to playlist" icon-class="fa fa-list-ul" @click="$emit('add-to-playlist', track)" />
|
||||||
<DropdownItem text="Remove" icon-class="fa fa-trash" @click="$emit('remove', [...(new Set([...selectedTracks, i]))])" />
|
<DropdownItem text="Remove" icon-class="fa fa-trash" @click="$emit('remove', [...(new Set([...selectedTracks, i]))])" />
|
||||||
<DropdownItem text="Track info" icon-class="fa fa-info" @click="$emit('track-info', tracks[i])" />
|
<DropdownItem text="Info" icon-class="fa fa-info" @click="$emit('info', tracks[i])" />
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -90,7 +90,7 @@ export default {
|
||||||
name: "Playlist",
|
name: "Playlist",
|
||||||
mixins: [MediaUtils],
|
mixins: [MediaUtils],
|
||||||
components: {DropdownItem, Dropdown, MusicHeader},
|
components: {DropdownItem, Dropdown, MusicHeader},
|
||||||
emits: ['play', 'clear', 'add', 'remove', 'swap', 'search', 'move', 'save', 'track-info'],
|
emits: ['play', 'clear', 'add', 'remove', 'swap', 'search', 'move', 'save', 'info'],
|
||||||
props: {
|
props: {
|
||||||
tracks: {
|
tracks: {
|
||||||
type: Array,
|
type: Array,
|
||||||
|
|
|
@ -33,11 +33,12 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="artist" v-if="track.artist">
|
<div class="artist" v-if="track.artist">
|
||||||
<a :href="$route.fullPath" v-text="track.artist" @click.stop="$emit('search', {artist: track.artist})" />
|
<a :href="$route.fullPath" v-text="track.artist" @click.prevent="$emit('search', {artist: track.artist})" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="album" v-if="track.album">
|
<div class="album" v-if="track.album">
|
||||||
<a :href="$route.fullPath" v-text="track.album" @click.stop="$emit('search', {artist: track.album})" />
|
<a :href="$route.fullPath" v-text="track.album"
|
||||||
|
@click.prevent="$emit('search', {artist: track.artist, album: track.album})" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -47,10 +48,10 @@
|
||||||
<span class="actions">
|
<span class="actions">
|
||||||
<Dropdown title="Actions" icon-class="fa fa-ellipsis-h">
|
<Dropdown title="Actions" icon-class="fa fa-ellipsis-h">
|
||||||
<DropdownItem text="Play" icon-class="fa fa-play" @click="$emit('load-track', {pos: i, play: true})" />
|
<DropdownItem text="Play" icon-class="fa fa-play" @click="$emit('load-track', {pos: i, play: true})" />
|
||||||
<DropdownItem text="Add to tracklist" icon-class="fa fa-plus" @click="$emit('load-track', {pos: i, play: false})" />
|
<DropdownItem text="Add to queue" icon-class="fa fa-plus" @click="$emit('load-track', {pos: i, play: false})" />
|
||||||
<DropdownItem text="Add to playlist" icon-class="fa fa-list-ul" @click="$emit('add-to-playlist', track)" />
|
<DropdownItem text="Add to playlist" icon-class="fa fa-list-ul" @click="$emit('add-to-playlist', track)" />
|
||||||
<DropdownItem text="Remove" icon-class="fa fa-trash" @click="$emit('remove-track', [...(new Set([...selectedTracks, i]))])" />
|
<DropdownItem text="Remove" icon-class="fa fa-trash" @click="$emit('remove-track', [...(new Set([...selectedTracks, i]))])" />
|
||||||
<DropdownItem text="Show info" icon-class="fa fa-info" @click.stop="$emit('track-info', tracks[i])" />
|
<DropdownItem text="Info" icon-class="fa fa-info" @click.stop="$emit('info', tracks[i])" />
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -106,7 +107,7 @@ export default {
|
||||||
name: "Playlists",
|
name: "Playlists",
|
||||||
mixins: [MediaUtils],
|
mixins: [MediaUtils],
|
||||||
components: {DropdownItem, Dropdown, MusicHeader},
|
components: {DropdownItem, Dropdown, MusicHeader},
|
||||||
emits: ['play', 'load', 'remove', 'playlist-edit', 'search', 'remove-track', 'load-track', 'track-info',
|
emits: ['play', 'load', 'remove', 'playlist-edit', 'search', 'remove-track', 'load-track', 'info',
|
||||||
'playlist-add', 'add-to-playlist', 'track-move'],
|
'playlist-add', 'add-to-playlist', 'track-move'],
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
|
|
|
@ -0,0 +1,264 @@
|
||||||
|
<template>
|
||||||
|
<div class="search fade-in" :class="{'form-collapsed': formCollapsed}">
|
||||||
|
<form class="search-form" v-if="!formCollapsed" @submit.prevent="$emit('search', filteredQuery)">
|
||||||
|
<div class="row">
|
||||||
|
<label>
|
||||||
|
<input type="text" placeholder="Any" v-model="query.any" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<label>
|
||||||
|
<input type="text" placeholder="Artist" v-model="query.artist" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<label>
|
||||||
|
<input type="text" placeholder="Title" v-model="query.title" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<label>
|
||||||
|
<input type="text" placeholder="Album" v-model="query.album" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<FormFooter>
|
||||||
|
<button @click="clear">
|
||||||
|
<i class="icon fa fa-times" />
|
||||||
|
<span class="btn-title">Clear</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button type="submit">
|
||||||
|
<i class="icon fa fa-search" />
|
||||||
|
<span class="btn-title">Search</span>
|
||||||
|
</button>
|
||||||
|
</FormFooter>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<MusicHeader v-else>
|
||||||
|
<label class="search-box">
|
||||||
|
<input type="search" placeholder="Filter" v-model="filter">
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<span class="buttons">
|
||||||
|
<button @click="clear">
|
||||||
|
<i class="icon fa fa-times" />
|
||||||
|
<span class="btn-title">Clear</span>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</MusicHeader>
|
||||||
|
|
||||||
|
<div class="results">
|
||||||
|
<div class="row track" :class="{selected: selectedResults.has(i), hidden: !displayedTracks.has(i)}"
|
||||||
|
v-for="(result, i) in results" :key="i" @click="resultClick(i, $event)">
|
||||||
|
<div class="col-10">
|
||||||
|
<div class="title">
|
||||||
|
{{ result.title || '[No Title]' }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="artist" v-text="result.artist" v-if="result.artist?.length" />
|
||||||
|
<div class="album" v-text="result.album" v-if="result.album?.length" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-2 right-side">
|
||||||
|
<span class="duration" v-text="result.time && parseInt(result.time) ? convertTime(result.time) : '-:--'" />
|
||||||
|
|
||||||
|
<span class="actions">
|
||||||
|
<Dropdown title="Actions" icon-class="fa fa-ellipsis-h">
|
||||||
|
<DropdownItem text="Play" icon-class="fa fa-play" @click="play(i)" />
|
||||||
|
<DropdownItem text="Add to queue" icon-class="fa fa-plus" @click="load(i)" />
|
||||||
|
<DropdownItem text="Add to playlist" icon-class="fa fa-list-ul" @click="$emit('add-to-playlist', result)" />
|
||||||
|
<DropdownItem text="Info" icon-class="fa fa-info" @click="$emit('info', result)" />
|
||||||
|
</Dropdown>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Dropdown from "@/components/elements/Dropdown";
|
||||||
|
import DropdownItem from "@/components/elements/DropdownItem";
|
||||||
|
import FormFooter from "@/components/elements/FormFooter";
|
||||||
|
import MediaUtils from "@/components/Media/Utils";
|
||||||
|
import MusicHeader from "@/components/panels/Music/Header";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Search",
|
||||||
|
components: {Dropdown, DropdownItem, FormFooter, MusicHeader},
|
||||||
|
mixins: [MediaUtils],
|
||||||
|
emits: ['search', 'clear', 'play', 'load', 'add-to-playlist', 'info'],
|
||||||
|
props: {
|
||||||
|
loading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
results: {
|
||||||
|
type: Array,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
selectedResults: new Set(),
|
||||||
|
filter: '',
|
||||||
|
query: {
|
||||||
|
any: '',
|
||||||
|
artist: '',
|
||||||
|
title: '',
|
||||||
|
album: '',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
formCollapsed() {
|
||||||
|
return this.results?.length > 0
|
||||||
|
},
|
||||||
|
|
||||||
|
filteredQuery() {
|
||||||
|
return Object.entries(this.query).filter((o) => o[1]?.length).reduce((obj, [k, v]) => {
|
||||||
|
obj[k] = v
|
||||||
|
return obj
|
||||||
|
}, {})
|
||||||
|
},
|
||||||
|
|
||||||
|
displayedTracks() {
|
||||||
|
return new Set([...Array(this.results?.length || 0).keys()].filter((i) => {
|
||||||
|
const result = this.results[i]
|
||||||
|
if (!this.filter?.length)
|
||||||
|
return result
|
||||||
|
|
||||||
|
const filter = this.filter.toLowerCase()
|
||||||
|
return (result?.artist || '').toLowerCase().indexOf(filter) >= 0 ||
|
||||||
|
(result?.title || '').toLowerCase().indexOf(filter) >= 0 ||
|
||||||
|
(result?.album || '').toLowerCase().indexOf(filter) >= 0
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
clear() {
|
||||||
|
this.$emit('clear')
|
||||||
|
this.selectedResults = new Set()
|
||||||
|
},
|
||||||
|
|
||||||
|
resultClick(pos, event) {
|
||||||
|
if (event.shiftKey) {
|
||||||
|
if (this.selectedResults.size > 0 && !this.selectedResults.has(pos)) {
|
||||||
|
const results = [...this.selectedResults]
|
||||||
|
const min = Math.min(Math.min(results), pos)
|
||||||
|
const max = Math.max(Math.max(results), pos)
|
||||||
|
this.selectedResults = new Set([...Array(max-min+1).keys()].map((i) => i+min))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!event.ctrlKey)
|
||||||
|
this.selectedResults = new Set()
|
||||||
|
if (this.selectedResults.has(pos))
|
||||||
|
this.selectedResults.delete(pos)
|
||||||
|
else
|
||||||
|
this.selectedResults.add(pos)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
play(pos) {
|
||||||
|
this.$emit('play', this.results[pos])
|
||||||
|
if (this.selectedResults.size)
|
||||||
|
this.selectedResults.forEach((result) => {
|
||||||
|
this.$emit('load', result)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
load(pos) {
|
||||||
|
if (!this.selectedResults.has(pos))
|
||||||
|
this.selectedResults.add(pos)
|
||||||
|
|
||||||
|
this.selectedResults.forEach((i) => {
|
||||||
|
this.$emit('load', this.results[i])
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import 'track.scss';
|
||||||
|
|
||||||
|
.search {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
&:not(.form-collapsed) {
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
width: calc(100% - 2em);
|
||||||
|
max-width: 30em;
|
||||||
|
height: 17em;
|
||||||
|
background: $default-bg-5;
|
||||||
|
display: flex;
|
||||||
|
padding: 2em;
|
||||||
|
border-radius: 1.5em;
|
||||||
|
|
||||||
|
.row {
|
||||||
|
margin: .25em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type=text] {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep(.form-footer) {
|
||||||
|
height: 3em;
|
||||||
|
padding-right: 0;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep(button) {
|
||||||
|
border: 0;
|
||||||
|
|
||||||
|
&[type=submit] {
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border: 0;
|
||||||
|
color: $default-hover-fg-2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.results {
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
::v-deep(.header) {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.search-box {
|
||||||
|
width: 70%;
|
||||||
|
|
||||||
|
input[type=search] {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons {
|
||||||
|
width: 30%;
|
||||||
|
display: inline-flex;
|
||||||
|
justify-content: right;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -2,16 +2,18 @@
|
||||||
<Loading v-if="loading" />
|
<Loading v-if="loading" />
|
||||||
<MusicPlugin plugin-name="music.mpd" :loading="loading" :config="config" :tracks="tracks" :status="status"
|
<MusicPlugin plugin-name="music.mpd" :loading="loading" :config="config" :tracks="tracks" :status="status"
|
||||||
:playlists="playlists" :edited-playlist="editedPlaylist" :edited-playlist-tracks="editedPlaylistTracks"
|
:playlists="playlists" :edited-playlist="editedPlaylist" :edited-playlist-tracks="editedPlaylistTracks"
|
||||||
:track-info="trackInfo" @play="play" @pause="pause" @stop="stop" @previous="previous" @next="next"
|
:track-info="trackInfo" :search-results="searchResults" @play="play" @pause="pause" @stop="stop"
|
||||||
@clear="clear" @set-volume="setVolume" @seek="seek" @consume="consume" @random="random" @repeat="repeat"
|
@previous="previous" @next="next" @clear="clear" @set-volume="setVolume" @seek="seek" @consume="consume"
|
||||||
@status-update="refreshStatus(true)" @playlist-update="refresh(true)"
|
@random="random" @repeat="repeat" @status-update="refreshStatus(true)"
|
||||||
@new-playing-track="refreshStatus(true)" @remove-from-tracklist="removeFromTracklist"
|
@playlist-update="refresh(true)" @new-playing-track="refreshStatus(true)"
|
||||||
@add-to-tracklist="addToTracklist" @swap-tracks="swapTracks" @load-playlist="loadPlaylist"
|
@remove-from-tracklist="removeFromTracklist" @add-to-tracklist="addToTracklist" @swap-tracks="swapTracks"
|
||||||
@play-playlist="playPlaylist" @remove-playlist="removePlaylist" @tracklist-move="moveTracklistTracks"
|
@load-playlist="loadPlaylist" @play-playlist="playPlaylist" @remove-playlist="removePlaylist"
|
||||||
@tracklist-save="saveToPlaylist" @playlist-edit="playlistEditChanged"
|
@tracklist-move="moveTracklistTracks" @tracklist-save="saveToPlaylist"
|
||||||
|
@playlist-edit="playlistEditChanged"
|
||||||
@add-to-tracklist-from-edited-playlist="addToTracklistFromEditedPlaylist"
|
@add-to-tracklist-from-edited-playlist="addToTracklistFromEditedPlaylist"
|
||||||
@remove-from-playlist="removeFromPlaylist" @track-info="trackInfo = $event" @playlist-add="playlistAdd"
|
@remove-from-playlist="removeFromPlaylist" @info="trackInfo = $event" @playlist-add="playlistAdd"
|
||||||
@add-to-playlist="addToPlaylist" @playlist-track-move="playlistTrackMove" />
|
@add-to-playlist="addToPlaylist" @playlist-track-move="playlistTrackMove" @search="search"
|
||||||
|
@search-clear="searchResults = []" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -39,6 +41,7 @@ export default {
|
||||||
editedPlaylist: null,
|
editedPlaylist: null,
|
||||||
editedPlaylistTracks: [],
|
editedPlaylistTracks: [],
|
||||||
trackInfo: null,
|
trackInfo: null,
|
||||||
|
searchResults: [],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -130,6 +133,8 @@ export default {
|
||||||
async play(event) {
|
async play(event) {
|
||||||
if (event?.pos != null) {
|
if (event?.pos != null) {
|
||||||
await this.request('music.mpd.play_pos', {pos: event.pos})
|
await this.request('music.mpd.play_pos', {pos: event.pos})
|
||||||
|
} else if (event.file) {
|
||||||
|
await this.request('music.mpd.play', {resource: event.file})
|
||||||
} else {
|
} else {
|
||||||
await this.request('music.mpd.play')
|
await this.request('music.mpd.play')
|
||||||
}
|
}
|
||||||
|
@ -191,6 +196,9 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
async addToTracklist(resource) {
|
async addToTracklist(resource) {
|
||||||
|
if (resource.file)
|
||||||
|
resource = resource.file
|
||||||
|
|
||||||
await this.request('music.mpd.add', {resource: resource})
|
await this.request('music.mpd.add', {resource: resource})
|
||||||
await this.refresh(true)
|
await this.refresh(true)
|
||||||
},
|
},
|
||||||
|
@ -295,6 +303,16 @@ export default {
|
||||||
|
|
||||||
await this.playlistEditChanged(event.playlist)
|
await this.playlistEditChanged(event.playlist)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async search(query) {
|
||||||
|
this.loading = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.searchResults = await this.request('music.mpd.search', {filter: query})
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
|
|
|
@ -34,6 +34,8 @@ $dashboard-bg: url('/img/dashboard-bg-light.jpg');
|
||||||
$border-color-1: #e1e4e8 !default;
|
$border-color-1: #e1e4e8 !default;
|
||||||
$border-color-2: #dddddd !default;
|
$border-color-2: #dddddd !default;
|
||||||
$border-color-3: #cccccc !default;
|
$border-color-3: #cccccc !default;
|
||||||
|
$border-focus: 1px solid rgba(127, 216, 95, 0.83);
|
||||||
|
$border-hover: 1px solid rgba(159, 180, 152, 0.83);
|
||||||
|
|
||||||
$default-border: 1px solid $border-color-1 !default;
|
$default-border: 1px solid $border-color-1 !default;
|
||||||
$default-border-2: 1px solid $border-color-2 !default;
|
$default-border-2: 1px solid $border-color-2 !default;
|
||||||
|
|
Loading…
Reference in a new issue