Add option for fullscreen covers
This commit is contained in:
parent
9efe5d18b1
commit
ed9cb97ba6
|
@ -1,7 +1,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { QueueItemSource } from '~/types'
|
import type { QueueItemSource } from '~/types'
|
||||||
|
|
||||||
import { whenever, watchDebounced, useCurrentElement, useScrollLock } from '@vueuse/core'
|
import { whenever, watchDebounced, useCurrentElement, useScrollLock, useFullscreen, useIdle, refAutoReset } from '@vueuse/core'
|
||||||
import { nextTick, ref, computed, watchEffect, onMounted } from 'vue'
|
import { nextTick, ref, computed, watchEffect, onMounted } from 'vue'
|
||||||
import { useFocusTrap } from '@vueuse/integrations/useFocusTrap'
|
import { useFocusTrap } from '@vueuse/integrations/useFocusTrap'
|
||||||
import { useGettext } from 'vue3-gettext'
|
import { useGettext } from 'vue3-gettext'
|
||||||
|
@ -148,6 +148,18 @@ const hideArtist = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const cover = ref()
|
||||||
|
const { isFullscreen: fullscreen, enter, exit } = useFullscreen(cover)
|
||||||
|
const { idle } = useIdle(2000)
|
||||||
|
|
||||||
|
const showTrackInfo = refAutoReset(false, 5000)
|
||||||
|
whenever(currentTrack, () => (showTrackInfo.value = true))
|
||||||
|
|
||||||
|
const milkdrop = ref()
|
||||||
|
const loadRandomPreset = () => {
|
||||||
|
milkdrop.value?.loadRandomPreset()
|
||||||
|
}
|
||||||
|
|
||||||
enum CoverType {
|
enum CoverType {
|
||||||
COVER_ART,
|
COVER_ART,
|
||||||
MILK_DROP
|
MILK_DROP
|
||||||
|
@ -170,15 +182,27 @@ const coverType = ref(CoverType.COVER_ART)
|
||||||
class="ui basic segment"
|
class="ui basic segment"
|
||||||
>
|
>
|
||||||
<template v-if="currentTrack">
|
<template v-if="currentTrack">
|
||||||
<div class="cover-container">
|
<div
|
||||||
|
ref="cover"
|
||||||
|
:class="['cover-container', { idle, fullscreen }]"
|
||||||
|
>
|
||||||
<div class="cover">
|
<div class="cover">
|
||||||
|
<template v-if="coverType === CoverType.COVER_ART">
|
||||||
|
<img
|
||||||
|
v-if="fullscreen"
|
||||||
|
class="cover-shadow"
|
||||||
|
:src="$store.getters['instance/absoluteUrl'](currentTrack.coverUrl)"
|
||||||
|
>
|
||||||
<img
|
<img
|
||||||
v-if="coverType === CoverType.COVER_ART"
|
|
||||||
ref="cover"
|
ref="cover"
|
||||||
alt=""
|
alt=""
|
||||||
:src="$store.getters['instance/absoluteUrl'](currentTrack.coverUrl)"
|
:src="$store.getters['instance/absoluteUrl'](currentTrack.coverUrl)"
|
||||||
>
|
>
|
||||||
<milk-drop v-else-if="coverType === CoverType.MILK_DROP" />
|
</template>
|
||||||
|
<milk-drop
|
||||||
|
v-else-if="coverType === CoverType.MILK_DROP"
|
||||||
|
ref="milkdrop"
|
||||||
|
/>
|
||||||
|
|
||||||
<div class="cover-buttons">
|
<div class="cover-buttons">
|
||||||
<button
|
<button
|
||||||
|
@ -195,8 +219,26 @@ const coverType = ref(CoverType.COVER_ART)
|
||||||
>
|
>
|
||||||
<i class="icon image outline" />
|
<i class="icon image outline" />
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
v-if="!fullscreen"
|
||||||
|
class="ui secondary button"
|
||||||
|
@click="enter"
|
||||||
|
>
|
||||||
|
<i class="icon expand arrows alternate" />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Transition name="queue">
|
||||||
|
<div
|
||||||
|
v-if="fullscreen && (!idle || showTrackInfo)"
|
||||||
|
class="track-info"
|
||||||
|
@click="loadRandomPreset()"
|
||||||
|
>
|
||||||
|
<h1>{{ currentTrack.title }}</h1>
|
||||||
|
<h2>{{ currentTrack.artistName }} — {{ currentTrack.albumTitle }}</h2>
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
</div>
|
</div>
|
||||||
<h1 class="ui header">
|
<h1 class="ui header">
|
||||||
<div class="content ellipsis">
|
<div class="content ellipsis">
|
||||||
|
|
|
@ -2,100 +2,35 @@
|
||||||
import { useMilkDrop } from '~/composables/audio/visualizer'
|
import { useMilkDrop } from '~/composables/audio/visualizer'
|
||||||
|
|
||||||
import { onScopeDispose, ref } from 'vue'
|
import { onScopeDispose, ref } from 'vue'
|
||||||
import { refAutoReset, useFullscreen, useIdle, useRafFn, whenever } from '@vueuse/core'
|
import { useRafFn } from '@vueuse/core'
|
||||||
import { useQueue } from '~/composables/audio/queue'
|
|
||||||
|
|
||||||
const milkdrop = ref()
|
const milkdrop = ref()
|
||||||
const canvas = ref()
|
const canvas = ref()
|
||||||
|
|
||||||
const { isFullscreen: fullscreen, enter, exit } = useFullscreen(milkdrop)
|
|
||||||
const { visualizer, loadRandomPreset, render } = useMilkDrop(canvas)
|
const { visualizer, loadRandomPreset, render } = useMilkDrop(canvas)
|
||||||
const { currentTrack } = useQueue()
|
|
||||||
const { idle } = useIdle(2000)
|
|
||||||
|
|
||||||
const showTrackInfo = refAutoReset(false, 5000)
|
|
||||||
whenever(currentTrack, () => (showTrackInfo.value = true))
|
|
||||||
|
|
||||||
const { pause } = useRafFn(render)
|
const { pause } = useRafFn(render)
|
||||||
|
|
||||||
onScopeDispose(() => {
|
onScopeDispose(() => {
|
||||||
exit()
|
|
||||||
pause()
|
pause()
|
||||||
if (visualizer.value) {
|
if (visualizer.value) {
|
||||||
visualizer.value.loseGLContext()
|
visualizer.value.loseGLContext()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
loadRandomPreset
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
ref="milkdrop"
|
ref="milkdrop"
|
||||||
:class="['visualizer', { idle, fullscreen }]"
|
class="visualizer"
|
||||||
>
|
>
|
||||||
<canvas
|
<canvas
|
||||||
ref="canvas"
|
ref="canvas"
|
||||||
@click="loadRandomPreset()"
|
@click="loadRandomPreset()"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Teleport to=".cover > .cover-buttons">
|
|
||||||
<button
|
|
||||||
v-if="!fullscreen"
|
|
||||||
class="ui secondary button"
|
|
||||||
@click="enter"
|
|
||||||
>
|
|
||||||
<i class="icon expand arrows alternate" />
|
|
||||||
</button>
|
|
||||||
</Teleport>
|
|
||||||
|
|
||||||
<Transition name="slide-down">
|
|
||||||
<div
|
|
||||||
v-if="fullscreen && (!idle || showTrackInfo)"
|
|
||||||
class="track-info"
|
|
||||||
@click="loadRandomPreset()"
|
|
||||||
>
|
|
||||||
<h1>{{ currentTrack.title }}</h1>
|
|
||||||
<h2>{{ currentTrack.artistName }} — {{ currentTrack.albumTitle }}</h2>
|
|
||||||
</div>
|
|
||||||
</Transition>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.visualizer {
|
|
||||||
&.idle {
|
|
||||||
cursor: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.fullscreen {
|
|
||||||
.slide-down-enter-active,
|
|
||||||
.slide-down-leave-active {
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
will-change: transform, opacity;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slide-down-enter-from,
|
|
||||||
.slide-down-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(5vh);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.track-info {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 2;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
background: linear-gradient(to bottom, #0000, #000);
|
|
||||||
color: #fff;
|
|
||||||
text-align: left;
|
|
||||||
padding: 3em 1em 1em;
|
|
||||||
|
|
||||||
h1, h2 {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -249,6 +249,10 @@
|
||||||
|
|
||||||
|
|
||||||
// Wvffle's styles
|
// Wvffle's styles
|
||||||
|
.theme-light .cover-container.fullscreen {
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
.component-queue {
|
.component-queue {
|
||||||
#queue-grid {
|
#queue-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
@ -289,6 +293,10 @@
|
||||||
width: 50vh;
|
width: 50vh;
|
||||||
max-width: 90%;
|
max-width: 90%;
|
||||||
|
|
||||||
|
&.idle {
|
||||||
|
cursor: none;
|
||||||
|
}
|
||||||
|
|
||||||
.cover {
|
.cover {
|
||||||
height: 0;
|
height: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -302,10 +310,15 @@
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 1;
|
||||||
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas {
|
img {
|
||||||
z-index: 1;
|
max-width: 100vh;
|
||||||
|
max-height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover .cover-buttons {
|
&:hover .cover-buttons {
|
||||||
|
@ -313,6 +326,12 @@
|
||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cover-shadow {
|
||||||
|
transform: scale(1.2);
|
||||||
|
filter: blur(15vh);
|
||||||
|
border-radius: 22vh;
|
||||||
|
}
|
||||||
|
|
||||||
.cover-buttons {
|
.cover-buttons {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 1em;
|
bottom: 1em;
|
||||||
|
@ -347,6 +366,26 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.track-info {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 3;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
background: linear-gradient(to bottom, #0000, #000);
|
||||||
|
color: #fff;
|
||||||
|
text-align: left;
|
||||||
|
padding: 3em 1em 1em;
|
||||||
|
|
||||||
|
h1, h2 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(.fullscreen) .track-info {
|
||||||
|
transition: none !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress-wrapper {
|
.progress-wrapper {
|
||||||
|
|
Loading…
Reference in New Issue