fc3e: PostsFilterBar — artist autocomplete + platform v-select with URL-driven hydration
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,140 @@
|
|||||||
|
<template>
|
||||||
|
<div class="fc-posts-filters">
|
||||||
|
<v-autocomplete
|
||||||
|
v-model="artistModel"
|
||||||
|
:items="artistOptions"
|
||||||
|
:loading="artistLoading"
|
||||||
|
:search="artistQuery"
|
||||||
|
item-title="name"
|
||||||
|
item-value="id"
|
||||||
|
label="Artist"
|
||||||
|
density="compact"
|
||||||
|
hide-details
|
||||||
|
clearable
|
||||||
|
no-filter
|
||||||
|
return-object
|
||||||
|
style="min-width: 240px"
|
||||||
|
@update:search="onArtistSearch"
|
||||||
|
@update:model-value="onArtistPicked"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<v-select
|
||||||
|
v-model="platformModel"
|
||||||
|
:items="platformItems"
|
||||||
|
label="Platform"
|
||||||
|
density="compact"
|
||||||
|
hide-details
|
||||||
|
clearable
|
||||||
|
style="min-width: 180px"
|
||||||
|
@update:model-value="emitFilters"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<v-btn
|
||||||
|
v-if="hasFilters"
|
||||||
|
variant="text" size="small"
|
||||||
|
@click="clearAll"
|
||||||
|
>Clear</v-btn>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed, onMounted, ref, watch } from 'vue'
|
||||||
|
import { useSourcesStore } from '../../stores/sources.js'
|
||||||
|
import { usePlatformsStore } from '../../stores/platforms.js'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
artistId: { type: Number, default: null },
|
||||||
|
platform: { type: String, default: null },
|
||||||
|
})
|
||||||
|
const emit = defineEmits(['update:filters'])
|
||||||
|
|
||||||
|
const sourcesStore = useSourcesStore()
|
||||||
|
const platformsStore = usePlatformsStore()
|
||||||
|
|
||||||
|
// Artist autocomplete state
|
||||||
|
const artistQuery = ref('')
|
||||||
|
const artistOptions = ref([])
|
||||||
|
const artistLoading = ref(false)
|
||||||
|
const artistModel = ref(null)
|
||||||
|
|
||||||
|
// Platform v-select state
|
||||||
|
const platformItems = computed(() =>
|
||||||
|
(platformsStore.platforms || []).map(p => ({ title: p.key, value: p.key }))
|
||||||
|
)
|
||||||
|
const platformModel = ref(props.platform)
|
||||||
|
|
||||||
|
const hasFilters = computed(
|
||||||
|
() => artistModel.value != null || platformModel.value != null
|
||||||
|
)
|
||||||
|
|
||||||
|
let _searchTimer = null
|
||||||
|
function onArtistSearch(q) {
|
||||||
|
artistQuery.value = q
|
||||||
|
clearTimeout(_searchTimer)
|
||||||
|
if (!q || !q.trim()) {
|
||||||
|
artistOptions.value = artistModel.value ? [artistModel.value] : []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_searchTimer = setTimeout(async () => {
|
||||||
|
artistLoading.value = true
|
||||||
|
try {
|
||||||
|
const results = await sourcesStore.autocompleteArtist(q, 20)
|
||||||
|
artistOptions.value = results
|
||||||
|
} finally {
|
||||||
|
artistLoading.value = false
|
||||||
|
}
|
||||||
|
}, 200)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onArtistPicked() {
|
||||||
|
emitFilters()
|
||||||
|
}
|
||||||
|
|
||||||
|
function emitFilters() {
|
||||||
|
emit('update:filters', {
|
||||||
|
artist_id: artistModel.value?.id ?? null,
|
||||||
|
platform: platformModel.value || null,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearAll() {
|
||||||
|
artistModel.value = null
|
||||||
|
platformModel.value = null
|
||||||
|
artistOptions.value = []
|
||||||
|
artistQuery.value = ''
|
||||||
|
emitFilters()
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await platformsStore.loadAll()
|
||||||
|
// If a deep-link arrives with an artist_id, hydrate the selected display.
|
||||||
|
if (props.artistId != null) {
|
||||||
|
const seed = { id: props.artistId, name: `Artist #${props.artistId}` }
|
||||||
|
artistModel.value = seed
|
||||||
|
artistOptions.value = [seed]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => props.artistId, (val) => {
|
||||||
|
if (val == null) {
|
||||||
|
artistModel.value = null
|
||||||
|
} else if (artistModel.value?.id !== val) {
|
||||||
|
const seed = { id: val, name: `Artist #${val}` }
|
||||||
|
artistModel.value = seed
|
||||||
|
artistOptions.value = [seed]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => props.platform, (val) => {
|
||||||
|
if (platformModel.value !== val) platformModel.value = val || null
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.fc-posts-filters {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.75rem;
|
||||||
|
align-items: center;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user