mirror of
https://github.com/koel/koel
synced 2024-12-24 11:33:05 +00:00
149 lines
3.3 KiB
Vue
149 lines
3.3 KiB
Vue
|
<template>
|
||
|
<section id="vizContainer" :class="{ fullscreen: isFullscreen }" @dblclick="toggleFullscreen">
|
||
|
<div class="artifacts">
|
||
|
<div class="credits" v-if="selectedVisualizer">
|
||
|
<h3>{{ selectedVisualizer.name }}</h3>
|
||
|
<p class="text-secondary" v-if="selectedVisualizer.credits">
|
||
|
by {{ selectedVisualizer.credits.author }}
|
||
|
<a :href="selectedVisualizer.credits.url" target="_blank">
|
||
|
<icon :icon="faUpRightFromSquare"/>
|
||
|
</a>
|
||
|
</p>
|
||
|
</div>
|
||
|
|
||
|
<select v-model="selectedId">
|
||
|
<option disabled value="-1">Pick a visualizer</option>
|
||
|
<option v-for="v in visualizers" :key="v.id" :value="v.id">{{ v.name }}</option>
|
||
|
</select>
|
||
|
</div>
|
||
|
<div ref="el" class="viz"/>
|
||
|
</section>
|
||
|
</template>
|
||
|
|
||
|
<script lang="ts" setup>
|
||
|
import { faUpRightFromSquare } from '@fortawesome/free-solid-svg-icons'
|
||
|
import { nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||
|
import { logger } from '@/utils'
|
||
|
import { preferenceStore as preferences, visualizerStore } from '@/stores'
|
||
|
|
||
|
const visualizers = visualizerStore.all
|
||
|
let destroyVisualizer: () => void
|
||
|
|
||
|
const el = ref<HTMLElement>()
|
||
|
const selectedId = ref<Visualizer['id']>()
|
||
|
const isFullscreen = ref(false)
|
||
|
|
||
|
const render = async (viz: Visualizer) => {
|
||
|
if (!el.value) {
|
||
|
await nextTick()
|
||
|
await render(viz)
|
||
|
}
|
||
|
|
||
|
freeUp()
|
||
|
|
||
|
try {
|
||
|
destroyVisualizer = await viz.init(el.value!)
|
||
|
} catch (e) {
|
||
|
// in e.g., DOM testing, the call will fail due to the lack of proper API support
|
||
|
logger.warn('Failed to initialize visualizer', e)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const selectedVisualizer = ref<Visualizer>()
|
||
|
|
||
|
watch(selectedId, id => {
|
||
|
preferences.visualizer = id
|
||
|
selectedVisualizer.value = visualizerStore.getVisualizerById(id || 'default')!
|
||
|
render(selectedVisualizer.value)
|
||
|
})
|
||
|
|
||
|
const toggleFullscreen = () => {
|
||
|
isFullscreen.value ? document.exitFullscreen() : el.value?.requestFullscreen()
|
||
|
isFullscreen.value = !isFullscreen.value
|
||
|
}
|
||
|
|
||
|
onMounted(() => {
|
||
|
selectedId.value = preferences.visualizer || 'default'
|
||
|
|
||
|
if (!visualizerStore.getVisualizerById(selectedId.value)) {
|
||
|
selectedId.value = 'default'
|
||
|
}
|
||
|
})
|
||
|
|
||
|
const freeUp = () => {
|
||
|
destroyVisualizer?.()
|
||
|
el.value && (el.value.innerHTML = '')
|
||
|
}
|
||
|
|
||
|
onBeforeUnmount(() => freeUp())
|
||
|
</script>
|
||
|
|
||
|
<style lang="scss">
|
||
|
#vizContainer {
|
||
|
.viz {
|
||
|
height: 100%;
|
||
|
width: 100%;
|
||
|
positioN: absolute;
|
||
|
z-index: 0;
|
||
|
|
||
|
canvas {
|
||
|
transition: opacity 0.3s;
|
||
|
height: 100%;
|
||
|
width: 100%;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
.artifacts {
|
||
|
position: absolute;
|
||
|
z-index: 1;
|
||
|
height: 100%;
|
||
|
width: 100%;
|
||
|
top: 0;
|
||
|
left: 0;
|
||
|
opacity: 0;
|
||
|
padding: 24px;
|
||
|
transition: opacity 0.3s ease-in-out;
|
||
|
}
|
||
|
|
||
|
&:hover {
|
||
|
.artifacts {
|
||
|
opacity: 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
.credits {
|
||
|
padding: 14px 28px 14px 14px;
|
||
|
background: rgba(0, 0, 0, .5);
|
||
|
width: fit-content;
|
||
|
position: absolute;
|
||
|
bottom: 24px;
|
||
|
|
||
|
h3 {
|
||
|
font-size: 1.2rem;
|
||
|
margin-bottom: .3rem;
|
||
|
}
|
||
|
|
||
|
a {
|
||
|
margin-left: .5rem;
|
||
|
display: inline-block;
|
||
|
vertical-align: middle;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
select {
|
||
|
position: absolute;
|
||
|
bottom: 24px;
|
||
|
right: 24px;
|
||
|
}
|
||
|
|
||
|
&.fullscreen {
|
||
|
// :fullscreen pseudo support is kind of buggy, so we use a class instead.
|
||
|
background: var(--color-bg-primary);
|
||
|
|
||
|
.close {
|
||
|
opacity: 0 !important;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
</style>
|