forked from platypush/platypush
[Music UI] Many improvements to the interface.
This commit is contained in:
parent
80c2f0d8dd
commit
3282588c6e
7 changed files with 246 additions and 43 deletions
|
@ -1,9 +1,18 @@
|
|||
<template>
|
||||
<div class="extension fade-in" :class="{hidden: !expanded}">
|
||||
<div class="row">
|
||||
<div class="col-3">
|
||||
<div class="image-container" @click.prevent="onImageClick" v-if="status?.state !== 'stop'">
|
||||
<div class="remote-image-container" v-if="track?.image">
|
||||
<img class="image" :src="track.image" :alt="track.title">
|
||||
</div>
|
||||
<div class="col-6 buttons">
|
||||
|
||||
<div class="icon-container" v-else>
|
||||
<i class="icon fas fa-compact-disc"
|
||||
:class="{playing: status?.state === 'play'}" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row buttons-container">
|
||||
<div class="buttons">
|
||||
<div class="buttons">
|
||||
<button @click="$emit('previous')" title="Play previous track" v-if="buttons_.previous">
|
||||
<i class="icon fa fa-step-backward"></i>
|
||||
|
@ -16,8 +25,6 @@
|
|||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
|
@ -185,7 +192,12 @@ export default {
|
|||
methods: {
|
||||
getTime() {
|
||||
return (new Date()).getTime() / 1000
|
||||
}
|
||||
},
|
||||
|
||||
onImageClick() {
|
||||
if (this.track?.artist && this.track?.album)
|
||||
this.$emit('search', {artist: this.track.artist, album: this.track.album})
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
|
@ -248,15 +260,98 @@ button {
|
|||
flex: 1;
|
||||
}
|
||||
|
||||
:deep(.progress-bar-container, .volume-slider-container) {
|
||||
font-size: 1.25em;
|
||||
}
|
||||
|
||||
:deep(.volume-slider-container) {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.buttons-container {
|
||||
width: calc(100% + 1em);
|
||||
margin-left: -0.5em;
|
||||
font-size: 2em;
|
||||
justify-content: center;
|
||||
box-shadow: $border-shadow-bottom;
|
||||
|
||||
button {
|
||||
text-align: center;
|
||||
|
||||
&:hover {
|
||||
color: $default-hover-fg;
|
||||
}
|
||||
|
||||
i {
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.image-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
|
||||
.remote-image-container {
|
||||
height: 30vh;
|
||||
|
||||
.image {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-container {
|
||||
padding: 0.05em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 15em;
|
||||
opacity: 0.5;
|
||||
border: $default-border-2;
|
||||
box-shadow: $border-shadow-bottom;
|
||||
|
||||
&:hover {
|
||||
color: $default-hover-fg;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
&.playing {
|
||||
animation-duration: 3s;
|
||||
animation-name: rotate;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(359deg);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.controls {
|
||||
|
@ -277,8 +372,9 @@ button {
|
|||
align-items: center;
|
||||
margin-left: 0;
|
||||
|
||||
@include until($tablet) {
|
||||
align-items: center;
|
||||
@include until($desktop) {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
a {
|
||||
|
|
|
@ -46,9 +46,10 @@
|
|||
:devices="devices"
|
||||
:selected-device="selectedDevice"
|
||||
:active-device="activeDevice"
|
||||
v-else-if="selectedView === 'playlists'"
|
||||
:edited-playlist="editedPlaylist"
|
||||
:tracks="editedPlaylistTracks"
|
||||
:show-nav-button="!navVisible"
|
||||
v-else-if="selectedView === 'playlists'"
|
||||
@play="$emit('play-playlist', $event)"
|
||||
@load="$emit('load-playlist', $event)"
|
||||
@remove="$emit('remove-playlist', $event)"
|
||||
|
@ -60,20 +61,44 @@
|
|||
@add-to-playlist="openAddToPlaylist"
|
||||
@track-move="$emit('playlist-track-move', $event)"
|
||||
@search="search"
|
||||
@toggle-nav="navVisible = !navVisible"
|
||||
@refresh-status="refreshStatus"
|
||||
@select-device="selectDevice" />
|
||||
|
||||
<Search :loading="loading" v-else-if="selectedView === 'search'" :devices="devices"
|
||||
:selected-device="selectedDevice" :active-device="activeDevice" @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" @refresh-status="refreshStatus" @select-device="selectDevice" />
|
||||
<Search :loading="loading"
|
||||
:results="searchResults"
|
||||
:devices="devices"
|
||||
:selected-device="selectedDevice"
|
||||
:active-device="activeDevice"
|
||||
:show-nav-button="!navVisible"
|
||||
v-else-if="selectedView === 'search'"
|
||||
@search="search"
|
||||
@clear="$emit('search-clear')"
|
||||
@info="$emit('info', $event)"
|
||||
@play="$emit('play', $event)"
|
||||
@load="$emit('add-to-tracklist', $event)"
|
||||
@add-to-playlist="openAddToPlaylist"
|
||||
@refresh-status="refreshStatus"
|
||||
@toggle-nav="navVisible = !navVisible"
|
||||
@select-device="selectDevice" />
|
||||
|
||||
<Library :loading="loading" v-else-if="selectedView === 'library'" :devices="devices"
|
||||
:selected-device="selectedDevice" :active-device="activeDevice" @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)" @refresh-status="refreshStatus"
|
||||
<Library :loading="loading"
|
||||
:results="libraryResults"
|
||||
:path="path"
|
||||
:devices="devices"
|
||||
:selected-device="selectedDevice"
|
||||
:active-device="activeDevice"
|
||||
:show-nav-button="!navVisible"
|
||||
v-else-if="selectedView === 'library'"
|
||||
@search="search"
|
||||
@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)"
|
||||
@toggle-nav="navVisible = !navVisible"
|
||||
@refresh-status="refreshStatus"
|
||||
@select-device="selectDevice" />
|
||||
</div>
|
||||
</main>
|
||||
|
|
|
@ -3,9 +3,15 @@
|
|||
<Loading v-if="loading" />
|
||||
|
||||
<MusicHeader>
|
||||
<label class="search-box">
|
||||
<label class="col-10 search-box">
|
||||
<input type="search" placeholder="Filter" v-model="filter">
|
||||
</label>
|
||||
|
||||
<div class="col-2 buttons">
|
||||
<button class="mobile" title="Menu" @click="$emit('toggle-nav')" v-if="showNavButton">
|
||||
<i class="fas fa-bars" />
|
||||
</button>
|
||||
</div>
|
||||
</MusicHeader>
|
||||
|
||||
<div class="results">
|
||||
|
@ -67,7 +73,18 @@ export default {
|
|||
name: "Library",
|
||||
components: {Dropdown, DropdownItem, MusicHeader, Loading},
|
||||
mixins: [MediaUtils],
|
||||
emits: ['search', 'play', 'load', 'add-to-playlist', 'info', 'cd', 'refresh-status', 'select-device'],
|
||||
emits: [
|
||||
'add-to-playlist',
|
||||
'cd',
|
||||
'info',
|
||||
'load',
|
||||
'play',
|
||||
'refresh-status',
|
||||
'search',
|
||||
'select-device',
|
||||
'toggle-nav',
|
||||
],
|
||||
|
||||
props: {
|
||||
loading: {
|
||||
type: Boolean,
|
||||
|
@ -93,6 +110,11 @@ export default {
|
|||
activeDevice: {
|
||||
type: String,
|
||||
},
|
||||
|
||||
showNavButton: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
|
|
|
@ -171,6 +171,7 @@ export default {
|
|||
targetPos: null,
|
||||
centerPos: 0,
|
||||
mounted: false,
|
||||
scrollTimeout: null,
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -296,10 +297,24 @@ export default {
|
|||
const bodyHeight = parseFloat(getComputedStyle(this.$refs.body).height)
|
||||
const scrollHeight = this.$refs.body.scrollHeight
|
||||
|
||||
if (offset < 5)
|
||||
this.centerPos = Math.max(0, parseInt(this.centerPos - (this.maxVisibleTracks / 1.5)))
|
||||
else if (offset === scrollHeight - bodyHeight)
|
||||
this.centerPos = Math.min(this.tracks.length - 1, parseInt(this.centerPos + (this.maxVisibleTracks / 1.5)))
|
||||
if (offset < 5) {
|
||||
if (this.scrollTimeout)
|
||||
return
|
||||
|
||||
this.scrollTimeout = setTimeout(() => {
|
||||
this.centerPos = Math.max(0, parseInt(this.centerPos - (this.maxVisibleTracks / 1.5)))
|
||||
this.$refs.body.scrollTop = 6
|
||||
this.scrollTimeout = null
|
||||
}, 250)
|
||||
} else if (offset >= (scrollHeight - bodyHeight - 5)) {
|
||||
if (this.scrollTimeout)
|
||||
return
|
||||
|
||||
this.scrollTimeout = setTimeout(() => {
|
||||
this.centerPos = Math.min(this.tracks.length - 1, parseInt(this.centerPos + (this.maxVisibleTracks / 1.5)))
|
||||
this.scrollTimeout = null
|
||||
}, 250)
|
||||
}
|
||||
},
|
||||
|
||||
playlistSave() {
|
||||
|
|
|
@ -21,12 +21,13 @@
|
|||
icon-class="fa fa-volume-up" @click="$emit('select-device', id)" />
|
||||
</Dropdown>
|
||||
|
||||
<button title="Refresh status" @click="$emit('refresh-status')" v-if="devices != null">
|
||||
<i class="fa fa-sync"></i>
|
||||
</button>
|
||||
<Dropdown title="Actions" icon-class="fa fa-ellipsis-h">
|
||||
<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" />
|
||||
</Dropdown>
|
||||
|
||||
<button class="add-btn" title="Add track" @click="addTrack">
|
||||
<i class="fas fa-plus" />
|
||||
<button class="mobile" title="Menu" @click="$emit('toggle-nav')" v-if="showNavButton">
|
||||
<i class="fas fa-bars" />
|
||||
</button>
|
||||
</div>
|
||||
</MusicHeader>
|
||||
|
@ -76,13 +77,13 @@
|
|||
<div class="playlists fade-in" v-else>
|
||||
<div class="header-container">
|
||||
<MusicHeader ref="header">
|
||||
<div class="col-8 filter">
|
||||
<div class="col-7 filter">
|
||||
<label>
|
||||
<input type="search" placeholder="Filter" v-model="filter">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="col-4 buttons">
|
||||
<div class="col-5 buttons">
|
||||
<Dropdown title="Players" icon-class="fa fa-volume-up" v-if="Object.keys(devices || {}).length">
|
||||
<DropdownItem v-for="(device, id) in devices" :key="id" v-text="device.name"
|
||||
:item-class="{active: activeDevice === id, selected: selectedDevice === id}"
|
||||
|
@ -92,6 +93,10 @@
|
|||
<button title="Refresh status" @click="$emit('refresh-status')" v-if="devices != null">
|
||||
<i class="fa fa-sync"></i>
|
||||
</button>
|
||||
|
||||
<button class="mobile" title="Menu" @click="$emit('toggle-nav')" v-if="showNavButton">
|
||||
<i class="fas fa-bars" />
|
||||
</button>
|
||||
</div>
|
||||
</MusicHeader>
|
||||
</div>
|
||||
|
@ -168,6 +173,11 @@ export default {
|
|||
activeDevice: {
|
||||
type: String,
|
||||
},
|
||||
|
||||
showNavButton: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
|
@ -341,6 +351,7 @@ export default {
|
|||
|
||||
.header {
|
||||
.buttons {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
@ -362,10 +373,6 @@ export default {
|
|||
padding-left: .25em;
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.search-box {
|
||||
input {
|
||||
width: 65%;
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
<template>
|
||||
<div class="search fade-in" :class="{'form-collapsed': formCollapsed}">
|
||||
<button class="nav-toggler mobile floating" title="Menu" @click="$emit('toggle-nav')" v-if="showNavButton && !formCollapsed">
|
||||
<i class="fas fa-bars" />
|
||||
</button>
|
||||
|
||||
<div class="form-container" v-if="!formCollapsed" @submit.prevent="$emit('search', filteredQuery)">
|
||||
<form class="search-form">
|
||||
<div class="row">
|
||||
|
@ -41,14 +45,17 @@
|
|||
</div>
|
||||
|
||||
<MusicHeader v-else>
|
||||
<label class="search-box">
|
||||
<label class="col-10 search-box">
|
||||
<button class="back-btn" title="Back" @click="clear">
|
||||
<i class="fas fa-arrow-left" />
|
||||
</button>
|
||||
|
||||
<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>
|
||||
<span class="col-2 buttons">
|
||||
<button class="mobile" title="Menu" @click="$emit('toggle-nav')" v-if="showNavButton">
|
||||
<i class="fas fa-bars" />
|
||||
</button>
|
||||
</span>
|
||||
</MusicHeader>
|
||||
|
@ -93,7 +100,18 @@ export default {
|
|||
name: "Search",
|
||||
components: {Dropdown, DropdownItem, FormFooter, MusicHeader},
|
||||
mixins: [MediaUtils],
|
||||
emits: ['search', 'clear', 'play', 'load', 'add-to-playlist', 'info', 'refresh-status', 'select-device'],
|
||||
emits: [
|
||||
'add-to-playlist',
|
||||
'clear',
|
||||
'info',
|
||||
'load',
|
||||
'play',
|
||||
'refresh-status',
|
||||
'search',
|
||||
'select-device',
|
||||
'toggle-nav',
|
||||
],
|
||||
|
||||
props: {
|
||||
loading: {
|
||||
type: Boolean,
|
||||
|
@ -115,6 +133,11 @@ export default {
|
|||
activeDevice: {
|
||||
type: String,
|
||||
},
|
||||
|
||||
showNavButton: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
|
@ -210,6 +233,14 @@ export default {
|
|||
height: calc(100% - #{$media-ctrl-panel-height});
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
|
||||
.nav-toggler.floating {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
&:not(.form-collapsed) {
|
||||
justify-content: center;
|
||||
|
@ -276,6 +307,13 @@ export default {
|
|||
|
||||
.search-box {
|
||||
width: 70%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.back-btn {
|
||||
margin-left: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
input[type=search] {
|
||||
width: 100%;
|
||||
|
|
|
@ -147,7 +147,7 @@ $slider-thumb-disabled-bg: rgba(0,215,80,0.3) !default;
|
|||
$slider-thumb-shadow: #475c40 !default;
|
||||
$slider-thumb-border: #83997896 !default;
|
||||
$slider-hover-on-hover-bg: #d2d2d2 !default;
|
||||
$slider-progress-bg: rgba(0,215,80,0.5) !default;
|
||||
$slider-progress-bg: rgb(0,215,80) !default;
|
||||
|
||||
//// Input element
|
||||
$input-icon-fg: #888;
|
||||
|
|
Loading…
Reference in a new issue