Two operator-reported Artists-view bugs. Layout: ArtistCard previews used aspect-ratio: 3/1 + max-height: 220px — when a lone grid column got wide (>~660px), the max-height made the aspect-ratio box shrink its own WIDTH to keep the ratio, leaving dead space beside the 3 thumbnails. Dropped max-height (kept the min-height floor) so the strip fills the card width; lowered the grid min 440→360px so a lone column stays <732px (strip ≲244px) and desktop gets 2+ columns. Flicker: isEmpty checked !loading, but on the first render loading is still false (the initial loadMore fires in onMounted, after paint) so "No artists match" flashed for a frame before the spinner/data. Added a reactive `loaded` flag (true after the first load attempt, reset on reset()); isEmpty now also requires it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
90 lines
2.8 KiB
Vue
90 lines
2.8 KiB
Vue
<template>
|
|
<v-container fluid class="pt-2 pb-6">
|
|
|
|
<div class="fc-artists__controls">
|
|
<v-text-field
|
|
v-model="search" density="compact" variant="outlined"
|
|
prepend-inner-icon="mdi-magnify" hide-details
|
|
placeholder="Search artists" clearable style="max-width: 320px;"
|
|
/>
|
|
<v-select
|
|
v-model="platformModel"
|
|
:items="platformItems"
|
|
density="compact" variant="outlined" hide-details
|
|
placeholder="Platform" clearable style="max-width: 220px;"
|
|
/>
|
|
</div>
|
|
|
|
<v-alert v-if="store.error" type="error" variant="tonal" closable class="my-4">
|
|
Failed to load: {{ store.error }}
|
|
</v-alert>
|
|
<v-alert v-else-if="store.isEmpty" type="info" variant="tonal" class="my-4">
|
|
No artists match.
|
|
</v-alert>
|
|
|
|
<div class="fc-artists__grid">
|
|
<ArtistCard v-for="c in store.cards" :key="c.id" :card="c" />
|
|
</div>
|
|
|
|
<div v-if="store.loading" class="fc-artists__sentinel">
|
|
<v-progress-circular indeterminate color="accent" size="28" />
|
|
</div>
|
|
<div v-else-if="store.hasMore" ref="sentinelEl" class="fc-artists__sentinel" />
|
|
</v-container>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, watch, onMounted } from 'vue'
|
|
import { useArtistDirectoryStore } from '../stores/artistDirectory.js'
|
|
import { usePlatformsStore } from '../stores/platforms.js'
|
|
import { useInfiniteScroll } from '../composables/useInfiniteScroll.js'
|
|
import ArtistCard from '../components/discovery/ArtistCard.vue'
|
|
|
|
const store = useArtistDirectoryStore()
|
|
const platformsStore = usePlatformsStore()
|
|
|
|
const search = ref('')
|
|
const platformModel = ref(null)
|
|
const sentinelEl = ref(null)
|
|
|
|
const platformItems = ref([])
|
|
|
|
let debounce = null
|
|
watch(search, (v) => {
|
|
if (debounce) clearTimeout(debounce)
|
|
debounce = setTimeout(() => store.setQuery(v || ''), 300)
|
|
})
|
|
watch(platformModel, (v) => store.setPlatform(v || null))
|
|
|
|
useInfiniteScroll(sentinelEl, () => {
|
|
if (store.hasMore && !store.loading) store.loadMore()
|
|
})
|
|
|
|
onMounted(async () => {
|
|
await platformsStore.loadAll()
|
|
platformItems.value = (platformsStore.platforms || []).map(p => ({
|
|
title: p.key, value: p.key,
|
|
}))
|
|
store.reset()
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
.fc-artists__controls {
|
|
display: flex; align-items: center; gap: 16px;
|
|
flex-wrap: wrap; margin-bottom: 16px;
|
|
}
|
|
.fc-artists__grid {
|
|
display: grid;
|
|
/* min(360px, 100%) so a card never exceeds the viewport (phones collapse to
|
|
one column). 360 keeps a LONE column under ~732px wide, so the 3/1 preview
|
|
strip stays ≲244px tall and always fills the card width (no dead space),
|
|
while giving 2+ columns on a normal desktop. */
|
|
grid-template-columns: repeat(auto-fill, minmax(min(360px, 100%), 1fr));
|
|
gap: 12px;
|
|
}
|
|
.fc-artists__sentinel {
|
|
display: flex; justify-content: center; padding: 32px 0; min-height: 60px;
|
|
}
|
|
</style>
|