Add option for fullscreen covers

This commit is contained in:
wvffle 2022-11-04 07:03:34 +00:00 committed by Kasper Seweryn
parent 9efe5d18b1
commit ed9cb97ba6
3 changed files with 98 additions and 82 deletions

View File

@ -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 }} &mdash; {{ currentTrack.albumTitle }}</h2>
</div>
</Transition>
</div> </div>
<h1 class="ui header"> <h1 class="ui header">
<div class="content ellipsis"> <div class="content ellipsis">

View File

@ -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 }} &mdash; {{ 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>

View File

@ -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 {