muzika-gromche/Frontend/src/components/library/TrackCard.vue

128 lines
4.3 KiB
Vue

<script setup lang="ts">
import Card from '@/components/library/Card.vue';
import ColorSwatch from '@/components/library/ColorSwatch.vue';
import { openTrack } from '@/router';
import { formatTime, timeSeriesIsEmpty, type AudioTrack } from '@/lib/AudioTrack';
import Explicit from '@material-design-icons/svg/filled/explicit.svg';
import Lyrics from '@material-design-icons/svg/filled/lyrics.svg';
import AutoAwesome from '@material-design-icons/svg/filled/auto_awesome.svg';
import LibraryMusic from '@material-design-icons/svg/two-tone/library_music.svg';
import { computed, shallowRef } from 'vue';
import TrackCardBadge from './TrackCardBadge.vue';
import CardSeparator from './CardSeparator.vue';
const {
track,
} = defineProps<{
track: AudioTrack,
}>();
const hasLyrics = computed<boolean>(() => track.Lyrics.length !== 0);
const hasEffects = computed<boolean>(() =>
timeSeriesIsEmpty(track.DrunknessLoopOffsetTimeSeries) ||
timeSeriesIsEmpty(track.CondensationLoopOffsetTimeSeries)
);
const trackPalettePreview = computed<string[]>(() => track.Palette.slice(0, 6));
// preview color per track name (reactive map)
const previewColor = shallowRef<string | undefined>(undefined);
function updatePreview(pos: number) {
if (!track || !track.Palette || track.Palette.length === 0) return;
if (!isFinite(pos)) {
// reset to default
previewColor.value = "";
return;
}
// pick an index in the palette (wrap)
const n = track.Palette.length;
const idx = Math.floor(pos * n) % n;
previewColor.value = track.Palette[idx] || undefined;
}
</script>
<template>
<Card class="card" tabindex="0" @activate="openTrack(track)" @playhead="(pos) => updatePreview(pos)">
<div class="card-grid tw:grid tw:p-2 tw:gap-2">
<!-- preview -->
<!-- square aspect trick didn't work in grid, reverted to fixed size -->
<div style="grid-area: preview;"
class="tw:w-32 tw:h-32 tw:max-sm:max-w-24 tw:max-sm:max-h-24 tw:self-center card-border">
<LibraryMusic class="tw:h-full tw:w-full card-preview" :style="{ color: previewColor }" />
</div>
<!-- info -->
<div style="grid-area: info;" class="tw:grid tw:grid-cols-1 tw:flex-col tw:gap-0 tw:items-end tw:justify-between">
<div class="tw:flex tw:flex-row tw:gap-1 tw:items-center">
<h3 class="tw:text-xl ellided" title="Song's codename as seen in game">
{{ track.Name }}
</h3>
<TrackCardBadge v-if="track.IsExplicit" :icon="Explicit" title="Warning: Explicit Content" />
<div class="tw:flex-1" /> <!-- separator -->
<TrackCardBadge v-if="hasLyrics" :icon="Lyrics" title="Contains Lyrics" />
<TrackCardBadge v-if="hasEffects" :icon="AutoAwesome" title="Contains Visual Effects" />
</div>
<CardSeparator />
<p class="ellided inactive-color" title="Artist">
{{ track.Artist }}
</p>
<p class="ellided inactive-color" title="Song">
{{ track.Song }}
</p>
<CardSeparator />
</div>
<!-- palette -->
<div style="grid-area: palette;" class="tw:self-center tw:py-1 tw:max-sm:ps-2 tw:flex tw:gap-1">
<ColorSwatch v-for="color in trackPalettePreview" :color />
</div>
<!-- timing -->
<div style="grid-area: timing;" class="tw:flex tw:flex-col tw:text-xs">
<div title="Intro duration" class="tw:font-mono">{{ formatTime(track.WindUpTimer) }}</div>
<div title="Loop offset" class="tw:font-mono" v-if="track.LoopOffset > 0">{{ track.LoopOffset }} beats</div>
<div title="Loop duration" class="tw:font-mono">{{ formatTime(track.FileDurationLoop) }}</div>
</div>
</div>
</Card>
</template>
<style scoped>
@reference "tailwindcss";
.card-grid {
grid-template-areas:
"preview info info"
"preview palette timing";
grid-template-columns: max-content 1fr auto;
grid-template-rows: 1fr auto;
@variant max-sm {
grid-template-areas:
"preview info info"
"palette palette timing";
}
}
.card-preview {
fill: currentColor;
color: var(--inactive-text-color);
transition: color 0.1s linear;
}
.card.hover-enabled:hover .card-preview,
.card.selected .card-preview {
color: var(--active-text-color);
}
.ellided {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.inactive-color {
color: var(--inactive-text-color);
}
</style>