parent
9234720710
commit
53d9015e17
|
@ -97,14 +97,14 @@ if (store.state.auth.authenticated) {
|
||||||
<set-instance-modal v-model:show="showSetInstanceModal" />
|
<set-instance-modal v-model:show="showSetInstanceModal" />
|
||||||
<service-messages />
|
<service-messages />
|
||||||
<transition name="queue">
|
<transition name="queue">
|
||||||
<queue v-if="store.state.ui.queueFocused" />
|
<queue v-show="store.state.ui.queueFocused" />
|
||||||
</transition>
|
</transition>
|
||||||
|
|
||||||
<router-view v-slot="{ Component }">
|
<router-view v-slot="{ Component }">
|
||||||
<template v-if="Component">
|
<template v-if="Component">
|
||||||
<keep-alive :max="1">
|
<keep-alive :max="1">
|
||||||
<Suspense v-if="!store.state.ui.queueFocused">
|
<Suspense>
|
||||||
<component :is="Component" />
|
<component :is="Component" v-show="!store.state.ui.queueFocused" />
|
||||||
<template #fallback>
|
<template #fallback>
|
||||||
<!-- TODO (wvffle): Add loader -->
|
<!-- TODO (wvffle): Add loader -->
|
||||||
Loading...
|
Loading...
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useStore } from '~/store'
|
import { useStore } from '~/store'
|
||||||
import { nextTick, onMounted, ref, computed, onBeforeMount, onUnmounted } from 'vue'
|
import { nextTick, ref, computed, onBeforeMount, onUnmounted } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import time from '~/utils/time'
|
import time from '~/utils/time'
|
||||||
import { useFocusTrap } from '@vueuse/integrations/useFocusTrap'
|
import { useFocusTrap } from '@vueuse/integrations/useFocusTrap'
|
||||||
import TrackFavoriteIcon from '~/components/favorites/TrackFavoriteIcon.vue'
|
import TrackFavoriteIcon from '~/components/favorites/TrackFavoriteIcon.vue'
|
||||||
import TrackPlaylistIcon from '~/components/playlists/TrackPlaylistIcon.vue'
|
import TrackPlaylistIcon from '~/components/playlists/TrackPlaylistIcon.vue'
|
||||||
import Draggable from 'vuedraggable'
|
import Draggable from 'vuedraggable'
|
||||||
import { whenever, useTimeoutFn, useWindowScroll, useWindowSize } from '@vueuse/core'
|
import { whenever, watchDebounced } from '@vueuse/core'
|
||||||
import { useGettext } from 'vue3-gettext'
|
import { useGettext } from 'vue3-gettext'
|
||||||
import useQueue from '~/composables/audio/useQueue'
|
import useQueue from '~/composables/audio/useQueue'
|
||||||
import usePlayer from '~/composables/audio/usePlayer'
|
import usePlayer from '~/composables/audio/usePlayer'
|
||||||
|
@ -28,33 +28,18 @@ activate()
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
const currentIndex = computed(() => store.state.queue.currentIndex)
|
const currentIndex = computed(() => store.state.queue.currentIndex)
|
||||||
|
|
||||||
const { y: pageYOffset } = useWindowScroll()
|
|
||||||
const { height: windowHeight } = useWindowSize()
|
|
||||||
const scrollToCurrent = async () => {
|
const scrollToCurrent = async () => {
|
||||||
await nextTick()
|
await nextTick()
|
||||||
|
|
||||||
const item = queueModal.value?.querySelector('.queue-item.active')
|
const item = queueModal.value?.querySelector('.queue-item.active')
|
||||||
const { top } = item?.getBoundingClientRect() ?? { top: 0 }
|
item?.scrollIntoView({ behavior: store.state.ui.queueFocused ? 'smooth' : 'auto' })
|
||||||
|
|
||||||
window.scrollTo({
|
|
||||||
top: top + pageYOffset.value - windowHeight.value / 2,
|
|
||||||
behavior: 'smooth'
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
await nextTick()
|
|
||||||
// NOTE: delay is to let transition work
|
|
||||||
useTimeoutFn(scrollToCurrent, 400)
|
|
||||||
})
|
|
||||||
|
|
||||||
const { $pgettext } = useGettext()
|
const { $pgettext } = useGettext()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
playing,
|
playing,
|
||||||
loading: isLoadingAudio,
|
loading: isLoadingAudio,
|
||||||
errored,
|
errored,
|
||||||
focused: playerFocused,
|
|
||||||
duration,
|
duration,
|
||||||
durationFormatted,
|
durationFormatted,
|
||||||
currentTimeFormatted,
|
currentTimeFormatted,
|
||||||
|
@ -66,7 +51,6 @@ const {
|
||||||
} = usePlayer()
|
} = usePlayer()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
focused: queueFocused,
|
|
||||||
currentTrack,
|
currentTrack,
|
||||||
hasNext,
|
hasNext,
|
||||||
isEmpty: emptyQueue,
|
isEmpty: emptyQueue,
|
||||||
|
@ -94,7 +78,7 @@ const labels = computed(() => ({
|
||||||
selectTrack: $pgettext('*/*/*', 'Select track')
|
selectTrack: $pgettext('*/*/*', 'Select track')
|
||||||
}))
|
}))
|
||||||
|
|
||||||
whenever(queueFocused, scrollToCurrent, { immediate: true })
|
watchDebounced(() => store.state.ui.queueFocused, scrollToCurrent, { debounce: 400 })
|
||||||
whenever(currentTrack, scrollToCurrent, { immediate: true })
|
whenever(currentTrack, scrollToCurrent, { immediate: true })
|
||||||
|
|
||||||
whenever(
|
whenever(
|
||||||
|
@ -124,18 +108,14 @@ const play = (index: number) => {
|
||||||
class="main with-background component-queue"
|
class="main with-background component-queue"
|
||||||
:aria-label="labels.queue"
|
:aria-label="labels.queue"
|
||||||
>
|
>
|
||||||
<div :class="['ui vertical stripe queue segment', playerFocused ? 'player-focused' : '']">
|
<div id="queue-grid">
|
||||||
<div class="ui fluid container">
|
|
||||||
<div
|
|
||||||
id="queue-grid"
|
|
||||||
class="ui stackable grid"
|
|
||||||
>
|
|
||||||
<div class="ui six wide column current-track">
|
|
||||||
<div
|
<div
|
||||||
id="player"
|
id="player"
|
||||||
class="ui basic segment"
|
class="ui basic segment"
|
||||||
>
|
>
|
||||||
<template v-if="currentTrack">
|
<template v-if="currentTrack">
|
||||||
|
<div class="cover-container">
|
||||||
|
<div class="cover">
|
||||||
<img
|
<img
|
||||||
v-if="currentTrack.cover && currentTrack.cover.urls.large_square_crop"
|
v-if="currentTrack.cover && currentTrack.cover.urls.large_square_crop"
|
||||||
ref="cover"
|
ref="cover"
|
||||||
|
@ -154,6 +134,8 @@ const play = (index: number) => {
|
||||||
alt=""
|
alt=""
|
||||||
src="../assets/audio/default-cover.png"
|
src="../assets/audio/default-cover.png"
|
||||||
>
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<h1 class="ui header">
|
<h1 class="ui header">
|
||||||
<div class="content ellipsis">
|
<div class="content ellipsis">
|
||||||
<router-link
|
<router-link
|
||||||
|
@ -202,7 +184,7 @@ const play = (index: number) => {
|
||||||
</translate>
|
</translate>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="additional-controls tablet-and-below">
|
<div class="additional-controls desktop-and-below">
|
||||||
<track-favorite-icon
|
<track-favorite-icon
|
||||||
v-if="$store.state.auth.authenticated"
|
v-if="$store.state.auth.authenticated"
|
||||||
:track="currentTrack"
|
:track="currentTrack"
|
||||||
|
@ -269,7 +251,7 @@ const play = (index: number) => {
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="player-controls tablet-and-below">
|
<div class="player-controls desktop-and-below">
|
||||||
<span
|
<span
|
||||||
role="button"
|
role="button"
|
||||||
:title="labels.previous"
|
:title="labels.previous"
|
||||||
|
@ -314,9 +296,8 @@ const play = (index: number) => {
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div>
|
||||||
<div class="ui ten wide column queue-column">
|
<div class="ui basic clearing segment">
|
||||||
<div class="ui basic clearing fixed-header segment">
|
|
||||||
<h2 class="ui header">
|
<h2 class="ui header">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<button
|
<button
|
||||||
|
@ -355,12 +336,13 @@ const play = (index: number) => {
|
||||||
</div>
|
</div>
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
<table class="ui compact very basic fixed single line selectable unstackable table">
|
<table class="ui compact very basic fixed single line selectable unstackable table">
|
||||||
<draggable
|
<draggable
|
||||||
v-model="tracks"
|
v-model="tracks"
|
||||||
tag="tbody"
|
|
||||||
handle=".handle"
|
handle=".handle"
|
||||||
item-key="id"
|
item-key="id"
|
||||||
|
tag="tbody"
|
||||||
@update="reorder"
|
@update="reorder"
|
||||||
>
|
>
|
||||||
<template #item="{ element: track, index }">
|
<template #item="{ element: track, index }">
|
||||||
|
@ -431,7 +413,6 @@ const play = (index: number) => {
|
||||||
</template>
|
</template>
|
||||||
</draggable>
|
</draggable>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="$store.state.radios.running"
|
v-if="$store.state.radios.running"
|
||||||
class="ui info message"
|
class="ui info message"
|
||||||
|
@ -460,6 +441,5 @@ const play = (index: number) => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,28 +1,26 @@
|
||||||
|
|
||||||
.queue.segment.player-focused #queue-grid #player {
|
|
||||||
@include media("<desktop") {
|
|
||||||
padding-bottom: $bottom-player-height + 2rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.queue-controls {
|
.queue-controls {
|
||||||
|
|
||||||
@include media("<desktop") {
|
@include media("<desktop") {
|
||||||
height: $bottom-player-height;
|
height: $bottom-player-height;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.ui.fixed-header.segment {
|
.ui.clearing.segment {
|
||||||
background-color: var(--site-background);
|
background-color: var(--site-background);
|
||||||
box-shadow: var(--secondary-menu-box-shadow);
|
box-shadow: var(--secondary-menu-box-shadow);
|
||||||
|
margin: 0 !important;
|
||||||
}
|
}
|
||||||
.queue-enter-active, .queue-leave-active {
|
|
||||||
transition: all 0.2s ease-in-out;
|
.queue-enter-active,
|
||||||
.current-track, .queue-column {
|
.queue-leave-active {
|
||||||
opacity: 0;
|
transition: all 0.2s ease;
|
||||||
}
|
will-change: transform, opacity;
|
||||||
}
|
}
|
||||||
.queue-enter, .queue-leave-to {
|
|
||||||
transform: translateY(100vh);
|
.queue-enter-from,
|
||||||
|
.queue-leave-to {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
transform: translateY(5vh);
|
||||||
}
|
}
|
||||||
|
|
||||||
.component-queue {
|
.component-queue {
|
||||||
|
@ -33,11 +31,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&.main {
|
&.main {
|
||||||
position: absolute;
|
position: fixed;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
padding-bottom: 3em;
|
|
||||||
}
|
}
|
||||||
&.main > .button {
|
&.main > .button {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
@ -48,25 +45,13 @@
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.queue.segment:not(.player-focused) {
|
|
||||||
#player {
|
|
||||||
@include media("<desktop") {
|
|
||||||
height: 0;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.queue.segment #player {
|
.queue.segment #player {
|
||||||
padding: 0em;
|
padding: 0em;
|
||||||
> * {
|
> * {
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.player-focused .grid > .ui.queue-column {
|
|
||||||
@include media("<desktop") {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.queue-column {
|
.queue-column {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
@ -116,7 +101,7 @@
|
||||||
@include media("<desktop") {
|
@include media("<desktop") {
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
}
|
}
|
||||||
@include media(">desktop") {
|
@include media(">=desktop") {
|
||||||
right: 1em;
|
right: 1em;
|
||||||
left: 38%;
|
left: 38%;
|
||||||
}
|
}
|
||||||
|
@ -168,8 +153,7 @@
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.progress-wrapper, .warning.message {
|
.progress-wrapper, .warning.message {
|
||||||
max-width: 25em;
|
width: 25em;
|
||||||
margin: 0 auto;
|
|
||||||
}
|
}
|
||||||
.ui.progress .bar {
|
.ui.progress .bar {
|
||||||
transition: none;
|
transition: none;
|
||||||
|
@ -243,3 +227,65 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Wvffle's styles
|
||||||
|
.component-queue {
|
||||||
|
#queue-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 37.5% 62.5%;
|
||||||
|
|
||||||
|
@include media("<desktop") {
|
||||||
|
grid-template-columns: 1fr 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#player {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
.cover-container {
|
||||||
|
width: 50vh;
|
||||||
|
max-width: 90%;
|
||||||
|
|
||||||
|
.cover {
|
||||||
|
height: 0;
|
||||||
|
width: 100%;
|
||||||
|
padding-bottom: 100%;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
img {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-wrapper {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
width: 54vh;
|
||||||
|
max-width: 90%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> div {
|
||||||
|
height: calc(100vh - 4rem);
|
||||||
|
margin: 0 !important;
|
||||||
|
|
||||||
|
&:nth-child(2) {
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: auto 1fr;
|
||||||
|
|
||||||
|
> :nth-child(2) {
|
||||||
|
overflow-y: auto;
|
||||||
|
padding-bottom: 2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -53,6 +53,11 @@ a {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.desktop-and-below {
|
||||||
|
@include media(">=desktop") {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
.tablet-and-up {
|
.tablet-and-up {
|
||||||
@include media("<tablet") {
|
@include media("<tablet") {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
|
|
Loading…
Reference in New Issue