forked from platypush/platypush
[Music UI] Use the Playlist
component also for playlist editors.
This commit is contained in:
parent
e881fedc59
commit
5ef7313bdc
5 changed files with 146 additions and 100 deletions
|
@ -26,6 +26,9 @@
|
||||||
:selected-device="selectedDevice"
|
:selected-device="selectedDevice"
|
||||||
:active-device="activeDevice"
|
:active-device="activeDevice"
|
||||||
:show-nav-button="!navVisible"
|
:show-nav-button="!navVisible"
|
||||||
|
:with-clear="true"
|
||||||
|
:with-save="true"
|
||||||
|
:with-swap="true"
|
||||||
v-if="selectedView === 'playing'"
|
v-if="selectedView === 'playing'"
|
||||||
@play="$emit('play', $event)"
|
@play="$emit('play', $event)"
|
||||||
@clear="$emit('clear')"
|
@clear="$emit('clear')"
|
||||||
|
@ -44,6 +47,7 @@
|
||||||
<Playlists :playlists="playlists"
|
<Playlists :playlists="playlists"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:devices="devices"
|
:devices="devices"
|
||||||
|
:status="status"
|
||||||
:selected-device="selectedDevice"
|
:selected-device="selectedDevice"
|
||||||
:active-device="activeDevice"
|
:active-device="activeDevice"
|
||||||
:edited-playlist="editedPlaylist"
|
:edited-playlist="editedPlaylist"
|
||||||
|
@ -54,7 +58,7 @@
|
||||||
@load="$emit('load-playlist', $event)"
|
@load="$emit('load-playlist', $event)"
|
||||||
@remove="$emit('remove-playlist', $event)"
|
@remove="$emit('remove-playlist', $event)"
|
||||||
@playlist-edit="$emit('playlist-edit', $event)"
|
@playlist-edit="$emit('playlist-edit', $event)"
|
||||||
@load-track="$emit('add-to-tracklist-from-edited-playlist', $event)"
|
@load-tracks="$emit('add-to-tracklist-from-edited-playlist', $event)"
|
||||||
@remove-track="$emit('remove-from-playlist', $event)"
|
@remove-track="$emit('remove-from-playlist', $event)"
|
||||||
@info="$emit('info', $event)"
|
@info="$emit('info', $event)"
|
||||||
@playlist-add="$emit('playlist-add', $event)"
|
@playlist-add="$emit('playlist-add', $event)"
|
||||||
|
|
|
@ -5,6 +5,10 @@
|
||||||
<div class="header-container">
|
<div class="header-container">
|
||||||
<MusicHeader ref="header">
|
<MusicHeader ref="header">
|
||||||
<div class="col-7 filter">
|
<div class="col-7 filter">
|
||||||
|
<button class="back-btn" title="Back" @click="$emit('back')" v-if="withBack">
|
||||||
|
<i class="fas fa-arrow-left" />
|
||||||
|
</button>
|
||||||
|
|
||||||
<label>
|
<label>
|
||||||
<input type="search" placeholder="Filter" v-model="filter">
|
<input type="search" placeholder="Filter" v-model="filter">
|
||||||
</label>
|
</label>
|
||||||
|
@ -19,8 +23,9 @@
|
||||||
<DropdownItem text="Add track" icon-class="fa fa-plus" @click="addTrack" />
|
<DropdownItem text="Add track" icon-class="fa fa-plus" @click="addTrack" />
|
||||||
<DropdownItem text="Refresh status" icon-class="fa fa-sync" @click="$emit('refresh-status')" v-if="devices != null" />
|
<DropdownItem text="Refresh status" icon-class="fa fa-sync" @click="$emit('refresh-status')" v-if="devices != null" />
|
||||||
<DropdownItem text="Save as playlist" icon-class="fa fa-save" :disabled="!tracks?.length"
|
<DropdownItem text="Save as playlist" icon-class="fa fa-save" :disabled="!tracks?.length"
|
||||||
@click="playlistSave" />
|
@click="playlistSave" v-if="withSave" />
|
||||||
<DropdownItem text="Swap tracks" icon-class="fa fa-retweet" v-if="selectedTracks?.length === 2"
|
<DropdownItem text="Swap tracks" icon-class="fa fa-retweet"
|
||||||
|
v-if="withSwap && selectedTracks?.length === 2"
|
||||||
@click="$emit('swap', selectedTracks)" />
|
@click="$emit('swap', selectedTracks)" />
|
||||||
<DropdownItem :text="selectionMode ? 'End selection' : 'Start selection'" icon-class="far fa-check-square"
|
<DropdownItem :text="selectionMode ? 'End selection' : 'Start selection'" icon-class="far fa-check-square"
|
||||||
:disabled="!tracks?.length" @click="selectionMode = !selectionMode" />
|
:disabled="!tracks?.length" @click="selectionMode = !selectionMode" />
|
||||||
|
@ -30,7 +35,8 @@
|
||||||
<DropdownItem :text="'Remove track' + (selectedTracks.length > 1 ? 's' : '')"
|
<DropdownItem :text="'Remove track' + (selectedTracks.length > 1 ? 's' : '')"
|
||||||
icon-class="fa fa-trash" v-if="selectedTracks.length > 0"
|
icon-class="fa fa-trash" v-if="selectedTracks.length > 0"
|
||||||
@click="$emit('remove', [...(new Set(selectedTracks))])" />
|
@click="$emit('remove', [...(new Set(selectedTracks))])" />
|
||||||
<DropdownItem text="Clear playlist" icon-class="fa fa-ban" :disabled="!tracks?.length" @click="$emit('clear')" />
|
<DropdownItem text="Clear playlist" icon-class="fa fa-ban"
|
||||||
|
:disabled="!tracks?.length" @click="$emit('clear')" v-if="withClear" />
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
|
|
||||||
<Dropdown title="Players" icon-class="fa fa-volume-up" v-if="Object.keys(devices || {}).length">
|
<Dropdown title="Players" icon-class="fa fa-volume-up" v-if="Object.keys(devices || {}).length">
|
||||||
|
@ -84,7 +90,9 @@
|
||||||
|
|
||||||
<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('play', {pos: i})" />
|
<DropdownItem text="Play" icon-class="fa fa-play" @click="onMenuPlay" />
|
||||||
|
<DropdownItem text="Add to queue" icon-class="fa fa-plus"
|
||||||
|
@click="$emit('add-to-queue', [...(new Set([...selectedTracks, i]))])" v-if="withAddToQueue" />
|
||||||
<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="Info" icon-class="fa fa-info" @click="$emit('info', tracks[i])" />
|
<DropdownItem text="Info" icon-class="fa fa-info" @click="$emit('info', tracks[i])" />
|
||||||
|
@ -109,6 +117,10 @@ export default {
|
||||||
components: {DropdownItem, Dropdown, MusicHeader, Loading},
|
components: {DropdownItem, Dropdown, MusicHeader, Loading},
|
||||||
emits: [
|
emits: [
|
||||||
'add',
|
'add',
|
||||||
|
'add-to-playlist',
|
||||||
|
'add-to-queue',
|
||||||
|
'add-to-queue-and-play',
|
||||||
|
'back',
|
||||||
'clear',
|
'clear',
|
||||||
'info',
|
'info',
|
||||||
'move',
|
'move',
|
||||||
|
@ -159,6 +171,31 @@ export default {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
withAddToQueue: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
withBack: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
withClear: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
withSave: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
withSwap: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
|
@ -259,7 +296,7 @@ export default {
|
||||||
trackClass(i) {
|
trackClass(i) {
|
||||||
return {
|
return {
|
||||||
selected: this.selectedTracksSet.has(i),
|
selected: this.selectedTracksSet.has(i),
|
||||||
active: this.status?.playingPos === i,
|
active: !this.withAddToQueue && this.status?.playingPos === i,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -271,6 +308,13 @@ export default {
|
||||||
this.$emit('add', track)
|
this.$emit('add', track)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onMenuPlay(i) {
|
||||||
|
if (this.withAddToQueue)
|
||||||
|
this.$emit('add-to-queue-and-play', [...(new Set([...this.selectedTracks, i]))])
|
||||||
|
else
|
||||||
|
this.$emit('play', {pos: i})
|
||||||
|
},
|
||||||
|
|
||||||
onTrackDragStart(track) {
|
onTrackDragStart(track) {
|
||||||
this.sourcePos = track
|
this.sourcePos = track
|
||||||
},
|
},
|
||||||
|
@ -364,6 +408,7 @@ export default {
|
||||||
|
|
||||||
.playlist {
|
.playlist {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
|
@ -374,10 +419,18 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter {
|
.filter {
|
||||||
input {
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
label {
|
||||||
|
flex-grow: 1;
|
||||||
|
|
||||||
|
input[type="search"] {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.buttons {
|
.buttons {
|
||||||
direction: rtl;
|
direction: rtl;
|
||||||
|
@ -388,6 +441,12 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:deep(.header) {
|
||||||
|
.back-btn {
|
||||||
|
padding-left: .25em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.body {
|
.body {
|
||||||
height: calc(100% - #{$music-header-height} - #{$media-ctrl-panel-height});
|
height: calc(100% - #{$music-header-height} - #{$media-ctrl-panel-height});
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
|
|
@ -2,76 +2,28 @@
|
||||||
<Loading v-if="loading" />
|
<Loading v-if="loading" />
|
||||||
|
|
||||||
<div class="editor-container fade-in" v-else-if="editedPlaylist != null">
|
<div class="editor-container fade-in" v-else-if="editedPlaylist != null">
|
||||||
<div class="header-container">
|
<Playlist
|
||||||
<MusicHeader ref="header">
|
:tracks="tracks || []"
|
||||||
<div class="col-8 filter">
|
:status="status"
|
||||||
<button class="back-btn" title="Back" @click="$emit('playlist-edit', null)">
|
:devices="devices"
|
||||||
<i class="fas fa-arrow-left" />
|
:selected-device="selectedDevice"
|
||||||
</button>
|
:active-device="activeDevice"
|
||||||
|
:show-nav-button="showNavButton"
|
||||||
<label class="search-box">
|
:with-add-to-queue="true"
|
||||||
<input type="search" placeholder="Filter" v-model="trackFilter">
|
:with-back="true"
|
||||||
</label>
|
@add="$emit('playlist-add', $event)"
|
||||||
</div>
|
@add-to-playlist="$emit('add-to-playlist', $event)"
|
||||||
|
@add-to-queue="$emit('load-tracks', {tracks: $event, play: false})"
|
||||||
<div class="col-4 buttons pull-right">
|
@add-to-queue-and-play="$emit('load-tracks', {tracks: $event, play: true})"
|
||||||
<Dropdown title="Players" icon-class="fa fa-volume-up" v-if="Object.keys(devices || {}).length">
|
@back="$emit('playlist-edit', null)"
|
||||||
<DropdownItem v-for="(device, id) in devices" :key="id" v-text="device.name"
|
@info="$emit('info', $event)"
|
||||||
:item-class="{active: activeDevice === id, selected: selectedDevice === id}"
|
@move="$emit('track-move', {...$event, playlist: editedPlaylist})"
|
||||||
icon-class="fa fa-volume-up" @click="$emit('select-device', id)" />
|
@play="$emit('load-tracks', {tracks: [$event], play: true})"
|
||||||
</Dropdown>
|
@refresh-status="$emit('refresh-status')"
|
||||||
|
@remove="$emit('remove-track', $event)"
|
||||||
<Dropdown title="Actions" icon-class="fa fa-ellipsis-h">
|
@search="$emit('search', $event)"
|
||||||
<DropdownItem text="Add track" icon-class="fa fa-plus" @click="addTrack" />
|
@select-device="$emit('select-device', $event)"
|
||||||
<DropdownItem text="Refresh status" icon-class="fa fa-sync" @click="$emit('refresh-status')" v-if="devices != null" />
|
@toggle-nav="$emit('toggle-nav')" />
|
||||||
</Dropdown>
|
|
||||||
|
|
||||||
<button class="mobile" title="Menu" @click="$emit('toggle-nav')" v-if="showNavButton">
|
|
||||||
<i class="fas fa-bars" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</MusicHeader>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="editor" ref="editor">
|
|
||||||
<div class="no-content" v-if="!tracks?.length">
|
|
||||||
No tracks found
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row track" draggable="true" v-for="(track, i) in tracks" :key="i"
|
|
||||||
:class="{selected: selectedTracksSet.has(i), active: status?.playingPos === i, hidden: !displayedTracks.has(i)}"
|
|
||||||
@dragstart="onTrackDragStart(i)" @dragend="onTrackDragEnd(i)" @dragover="onTrackDragOver(i)"
|
|
||||||
@click="onTrackClick($event, i)" @dblclick="$emit('load-track', {pos: i, play: true})">
|
|
||||||
<div class="col-10">
|
|
||||||
<div class="title">
|
|
||||||
{{ track.title || '[No Title]' }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="artist" v-if="track.artist">
|
|
||||||
<a :href="$route.fullPath" v-text="track.artist" @click.prevent="$emit('search', {artist: track.artist})" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="album" v-if="track.album">
|
|
||||||
<a :href="$route.fullPath" v-text="track.album"
|
|
||||||
@click.prevent="$emit('search', {artist: track.artist, album: track.album})" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-2 right-side">
|
|
||||||
<span class="duration" v-text="track.time ? convertTime(track.time) : '-:--'" />
|
|
||||||
|
|
||||||
<span class="actions">
|
|
||||||
<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="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="Remove" icon-class="fa fa-trash" @click="$emit('remove-track', [...(new Set([...selectedTracks, i]))])" />
|
|
||||||
<DropdownItem text="Info" icon-class="fa fa-info" @click.stop="$emit('info', tracks[i])" />
|
|
||||||
</Dropdown>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="playlists fade-in" v-else>
|
<div class="playlists fade-in" v-else>
|
||||||
|
@ -134,13 +86,34 @@ import MediaUtils from "@/components/Media/Utils";
|
||||||
import Dropdown from "@/components/elements/Dropdown";
|
import Dropdown from "@/components/elements/Dropdown";
|
||||||
import DropdownItem from "@/components/elements/DropdownItem";
|
import DropdownItem from "@/components/elements/DropdownItem";
|
||||||
import Loading from "@/components/Loading";
|
import Loading from "@/components/Loading";
|
||||||
|
import Playlist from "./Playlist";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Playlists",
|
name: "Playlists",
|
||||||
mixins: [MediaUtils],
|
mixins: [MediaUtils],
|
||||||
components: {DropdownItem, Dropdown, MusicHeader, Loading},
|
components: {
|
||||||
emits: ['play', 'load', 'remove', 'playlist-edit', 'search', 'remove-track', 'load-track', 'info',
|
Dropdown,
|
||||||
'playlist-add', 'add-to-playlist', 'track-move', 'refresh-status', 'select-device'],
|
DropdownItem,
|
||||||
|
MusicHeader,
|
||||||
|
Loading,
|
||||||
|
Playlist,
|
||||||
|
},
|
||||||
|
|
||||||
|
emits: [
|
||||||
|
'add-to-playlist',
|
||||||
|
'info',
|
||||||
|
'load',
|
||||||
|
'load-tracks',
|
||||||
|
'play',
|
||||||
|
'playlist-add',
|
||||||
|
'playlist-edit',
|
||||||
|
'refresh-status',
|
||||||
|
'remove',
|
||||||
|
'remove-track',
|
||||||
|
'search',
|
||||||
|
'select-device',
|
||||||
|
'track-move',
|
||||||
|
],
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
playlists: {
|
playlists: {
|
||||||
|
@ -166,6 +139,11 @@ export default {
|
||||||
type: Object,
|
type: Object,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
status: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
|
||||||
selectedDevice: {
|
selectedDevice: {
|
||||||
type: String,
|
type: String,
|
||||||
},
|
},
|
||||||
|
@ -264,14 +242,6 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
addTrack() {
|
|
||||||
const track = prompt('Track path or URL')
|
|
||||||
if (!track?.length)
|
|
||||||
return
|
|
||||||
|
|
||||||
this.$emit('playlist-add', track)
|
|
||||||
},
|
|
||||||
|
|
||||||
onTrackDragStart(track) {
|
onTrackDragStart(track) {
|
||||||
this.sourcePos = track
|
this.sourcePos = track
|
||||||
},
|
},
|
||||||
|
|
|
@ -210,15 +210,18 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
async addToTracklistFromEditedPlaylist(event) {
|
async addToTracklistFromEditedPlaylist(event) {
|
||||||
const track = this.editedPlaylistTracks[event.pos]
|
const tracks = event?.tracks?.map(
|
||||||
if (!track)
|
(pos) => this.editedPlaylistTracks[pos]
|
||||||
|
)?.filter((track) => track?.file)?.map((track) => track.file)
|
||||||
|
|
||||||
|
if (!tracks?.length)
|
||||||
return
|
return
|
||||||
|
|
||||||
await this.request('music.mpd.add', {resource: track.file})
|
await Promise.all(tracks.map((track) => this.request('music.mpd.add', {resource: track})))
|
||||||
await this.refresh(true)
|
await this.refresh(true)
|
||||||
|
|
||||||
if (event.play)
|
if (event.play)
|
||||||
await this.request('music.mpd.play_pos', {pos: this.tracks.length-1})
|
await this.request('music.mpd.play_pos', {pos: this.tracks.length - tracks.length})
|
||||||
},
|
},
|
||||||
|
|
||||||
async removeFromPlaylist(positions) {
|
async removeFromPlaylist(positions) {
|
||||||
|
|
|
@ -224,15 +224,25 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
async addToTracklistFromEditedPlaylist(event) {
|
async addToTracklistFromEditedPlaylist(event) {
|
||||||
const track = this.editedPlaylistTracks[event.pos]
|
const tracks = event?.tracks?.map(
|
||||||
if (!track)
|
(pos) => this.editedPlaylistTracks[pos]
|
||||||
|
)?.filter((track) => track?.file)?.map((track) => track.file)
|
||||||
|
|
||||||
|
if (!tracks?.length)
|
||||||
return
|
return
|
||||||
|
|
||||||
const method = event.play ? 'play' : 'add'
|
if (event.play && tracks.length === 1) {
|
||||||
await this.request(`music.spotify.${method}`, {
|
await this.request('music.spotify.play', {
|
||||||
device: this.selectedDevice,
|
device: this.selectedDevice,
|
||||||
resource: track.uri
|
resource: tracks[0],
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
await Promise.all(tracks.map((track) => this.request('music.spotify.add', {
|
||||||
|
device: this.selectedDevice,
|
||||||
|
resource: track,
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
await this.refresh(true)
|
await this.refresh(true)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue