128 lines
4.3 KiB
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> |