Finalized new music.mpd web interface

This commit is contained in:
Fabio Manganiello 2021-01-02 14:33:01 +01:00
parent d2887b7454
commit 67d3b40772
17 changed files with 292 additions and 34 deletions

View file

@ -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-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> <!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-5fea187e.d6e3f8eb.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.adf909a2.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-5fea187e.4466d92f.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.3770dd06.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.3770dd06.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

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

View file

@ -223,13 +223,14 @@ export default {
mounted() { mounted() {
const self = this const self = this
this.lastSync = this.getTime()
this.$watch(() => self.track, (track) => { this.$watch(() => this.track, (track) => {
if (!track || self.status?.state !== 'play') if (!track || self.status?.state !== 'play')
self.lastSync = this.getTime() self.lastSync = this.getTime()
}) })
this.$watch(() => self.status, () => { this.$watch(() => this.status, () => {
self.lastSync = this.getTime() self.lastSync = this.getTime()
}) })

View file

@ -30,6 +30,11 @@
:results="searchResults" @clear="$emit('search-clear')" @info="$emit('info', $event)" :results="searchResults" @clear="$emit('search-clear')" @info="$emit('info', $event)"
@play="$emit('play', $event)" @load="$emit('add-to-tracklist', $event)" @play="$emit('play', $event)" @load="$emit('add-to-tracklist', $event)"
@add-to-playlist="openAddToPlaylist" /> @add-to-playlist="openAddToPlaylist" />
<Library :loading="loading" v-else-if="selectedView === 'library'" @search="search"
:results="libraryResults" :path="path" @clear="$emit('search-clear')" @info="$emit('info', $event)"
@play="$emit('play', $event)" @load="$emit('add-to-tracklist', $event)"
@add-to-playlist="openAddToPlaylist" @cd="$emit('cd', $event)" />
</div> </div>
</main> </main>
</MediaView> </MediaView>
@ -71,6 +76,16 @@
<div class="col-3 attr">Duration</div> <div class="col-3 attr">Duration</div>
<div class="col-9 value" v-text="convertTime(trackInfo.time)" /> <div class="col-9 value" v-text="convertTime(trackInfo.time)" />
</div> </div>
<div class="row track" v-if="trackInfo.track">
<div class="col-3 attr">Track</div>
<div class="col-9 value" v-text="trackInfo.track" />
</div>
<div class="row disc" v-if="trackInfo.disc">
<div class="col-3 attr">Disc</div>
<div class="col-9 value" v-text="trackInfo.disc" />
</div>
</div> </div>
</Modal> </Modal>
</div> </div>
@ -112,6 +127,7 @@ 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 Search from "@/components/panels/Music/Search";
import Library from "@/components/panels/Music/Library";
import Utils from "@/Utils"; import Utils from "@/Utils";
export default { export default {
@ -120,10 +136,10 @@ export default {
'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', 'info', 'playlist-add', 'add-to-playlist', 'add-to-tracklist-from-edited-playlist', 'remove-from-playlist', 'info', 'playlist-add', 'add-to-playlist',
'playlist-track-move', 'search', 'search-clear'], 'playlist-track-move', 'search', 'search-clear', 'cd'],
mixins: [Utils, MediaUtils], mixins: [Utils, MediaUtils],
components: {Loading, Modal, Nav, MediaView, Playlist, Playlists, FormFooter, Search}, components: {Loading, Modal, Nav, MediaView, Playlist, Playlists, FormFooter, Search, Library},
props: { props: {
pluginName: { pluginName: {
type: String, type: String,
@ -171,6 +187,14 @@ export default {
searchResults: { searchResults: {
type: Array, type: Array,
}, },
libraryResults: {
type: Array,
},
path: {
type: String,
},
}, },
data() { data() {

View file

@ -0,0 +1,217 @@
<template>
<div class="library fade-in">
<Loading v-if="loading" />
<MusicHeader>
<label class="search-box">
<input type="search" placeholder="Filter" v-model="filter">
</label>
</MusicHeader>
<div class="results">
<div class="row track back-track" @click="back" v-if="path !== '/'">
<div class="icon-container">
<i class="icon fa fa-folder" />
</div>
<div class="result-container">
<div class="title">..</div>
</div>
</div>
<div class="row track" :class="{selected: selectedResults.has(i), hidden: !displayedResults.has(i)}"
v-for="(result, i) in results" :key="i" @click="resultClick(i, $event)">
<div class="col-10 left-side">
<div class="icon-container">
<i class="icon fa fa-folder" v-if="result.directory" />
<i class="icon fa fa-music" v-else-if="result.file" />
</div>
<div class="info">
<div class="title">
<span v-if="result.directory" v-text="result.directory.split('/').pop()" />
<span v-else-if="result.title" v-text="result.title" />
</div>
<div class="artist-album">
<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>
</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 Loading from "@/components/Loading";
import Dropdown from "@/components/elements/Dropdown";
import DropdownItem from "@/components/elements/DropdownItem";
import MediaUtils from "@/components/Media/Utils";
import MusicHeader from "@/components/panels/Music/Header";
export default {
name: "Library",
components: {Dropdown, DropdownItem, MusicHeader, Loading},
mixins: [MediaUtils],
emits: ['search', 'play', 'load', 'add-to-playlist', 'info', 'cd'],
props: {
loading: {
type: Boolean,
default: false,
},
results: {
type: Array,
},
path: {
type: String,
},
},
data() {
return {
selectedResults: new Set(),
filter: '',
query: {
any: '',
artist: '',
title: '',
album: '',
},
}
},
computed: {
displayedResults() {
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 ||
(result?.directory || '').toLowerCase().indexOf(filter) >= 0
}))
},
},
methods: {
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) {
if (this.selectedResults.has(pos))
this.selectedResults.delete(pos)
else
this.selectedResults.add(pos)
} else {
if (this.results[pos].directory) {
this.$emit('cd', this.results[pos].directory)
} else {
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])
})
},
back() {
const path = this.path.split('/')
this.$emit('cd', path.slice(0, path.length-1).join('/'))
},
},
}
</script>
<style lang="scss" scoped>
@import 'track.scss';
.library {
width: 100%;
display: flex;
flex-direction: column;
.results {
overflow: auto;
height: 100%;
.track {
display: flex;
align-items: center;
justify-content: left;
.left-side {
display: inline-flex;
align-items: center;
}
}
.icon {
opacity: .5;
margin-right: .75em;
}
}
::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>

View file

@ -21,11 +21,9 @@
border-top: 2px solid $default-hover-fg; border-top: 2px solid $default-hover-fg;
} }
.title, .artist, .album, .duration {
&::selection { &::selection {
background: rgba(0, 0, 0, 0) !important; background: rgba(0, 0, 0, 0) !important;
} }
}
.title { .title {
font-size: 1em; font-size: 1em;

View file

@ -2,18 +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" :search-results="searchResults" @play="play" @pause="pause" @stop="stop" :track-info="trackInfo" :search-results="searchResults" :library-results="libraryResults" :path="path"
@previous="previous" @next="next" @clear="clear" @set-volume="setVolume" @seek="seek" @consume="consume" @play="play" @pause="pause" @stop="stop" @previous="previous" @next="next" @clear="clear"
@random="random" @repeat="repeat" @status-update="refreshStatus(true)" @set-volume="setVolume" @seek="seek" @consume="consume" @random="random" @repeat="repeat"
@playlist-update="refresh(true)" @new-playing-track="refreshStatus(true)" @status-update="refreshStatus(true)" @playlist-update="refresh(true)"
@remove-from-tracklist="removeFromTracklist" @add-to-tracklist="addToTracklist" @swap-tracks="swapTracks" @new-playing-track="refreshStatus(true)" @remove-from-tracklist="removeFromTracklist"
@load-playlist="loadPlaylist" @play-playlist="playPlaylist" @remove-playlist="removePlaylist" @add-to-tracklist="addToTracklist" @swap-tracks="swapTracks" @load-playlist="loadPlaylist"
@tracklist-move="moveTracklistTracks" @tracklist-save="saveToPlaylist" @play-playlist="playPlaylist" @remove-playlist="removePlaylist" @tracklist-move="moveTracklistTracks"
@playlist-edit="playlistEditChanged" @tracklist-save="saveToPlaylist" @playlist-edit="playlistEditChanged"
@add-to-tracklist-from-edited-playlist="addToTracklistFromEditedPlaylist" @add-to-tracklist-from-edited-playlist="addToTracklistFromEditedPlaylist"
@remove-from-playlist="removeFromPlaylist" @info="trackInfo = $event" @playlist-add="playlistAdd" @remove-from-playlist="removeFromPlaylist" @info="trackInfo = $event" @playlist-add="playlistAdd"
@add-to-playlist="addToPlaylist" @playlist-track-move="playlistTrackMove" @search="search" @add-to-playlist="addToPlaylist" @playlist-track-move="playlistTrackMove" @search="search"
@search-clear="searchResults = []" /> @search-clear="searchResults = []" @cd="cd"/>
</template> </template>
<script> <script>
@ -42,6 +42,8 @@ export default {
editedPlaylistTracks: [], editedPlaylistTracks: [],
trackInfo: null, trackInfo: null,
searchResults: [], searchResults: [],
libraryResults: [],
path: '/',
} }
}, },
@ -133,7 +135,7 @@ 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) { } else if (event?.file) {
await this.request('music.mpd.play', {resource: 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')
@ -313,10 +315,24 @@ export default {
this.loading = false this.loading = false
} }
}, },
async cd(path) {
this.loading = true
try {
this.libraryResults = (await this.request('music.mpd.lsinfo', {uri: path})).
filter((result) => !result.playlist)
this.path = path
} finally {
this.loading = false
}
},
}, },
mounted() { mounted() {
this.refresh() this.refresh()
this.cd(this.path)
}, },
} }
</script> </script>