diff --git a/frontend/src/components/gallery/GalleryFacetPanel.vue b/frontend/src/components/gallery/GalleryFacetPanel.vue new file mode 100644 index 0000000..bb09916 --- /dev/null +++ b/frontend/src/components/gallery/GalleryFacetPanel.vue @@ -0,0 +1,146 @@ + + + + + diff --git a/frontend/src/components/gallery/GalleryFilterBar.vue b/frontend/src/components/gallery/GalleryFilterBar.vue index bc4f885..7d17896 100644 --- a/frontend/src/components/gallery/GalleryFilterBar.vue +++ b/frontend/src/components/gallery/GalleryFilterBar.vue @@ -1,5 +1,6 @@ @@ -70,8 +82,9 @@ import { computed, ref } from 'vue' import { useRouter } from 'vue-router' import { useApi } from '../../composables/useApi.js' -import { useGalleryStore } from '../../stores/gallery.js' +import { cloneFilter, filterToQuery, useGalleryStore } from '../../stores/gallery.js' import { useTagStore } from '../../stores/tags.js' +import GalleryFacetPanel from './GalleryFacetPanel.vue' const store = useGalleryStore() const tagStore = useTagStore() @@ -88,13 +101,31 @@ const searchItems = ref([]) const searchLoading = ref(false) let debounce = null +// The faceted-refine sub-filters (platform / curation flags / date range). +const refineCount = computed(() => { + const f = store.filter + return (f.platform ? 1 : 0) + (f.untagged ? 1 : 0) + (f.no_artist ? 1 : 0) + + (f.date_from ? 1 : 0) + (f.date_to ? 1 : 0) +}) +const hasRefineFilters = computed(() => refineCount.value > 0) + const hasActiveFilters = computed(() => store.filter.tag_ids.length > 0 || store.filter.artist_id != null || store.filter.media_type != null || - store.filter.sort !== 'newest' + store.filter.sort !== 'newest' || + hasRefineFilters.value ) +// Auto-open the panel when arriving with refine filters already in the URL +// (deep-link / back button) so the active facets are visible, not hidden. +const refineOpen = ref(hasRefineFilters.value) +function toggleRefine() { + // The panel fetches facets itself on mount (and refetches on filter change); + // opening it is enough. + refineOpen.value = !refineOpen.value +} + function iconFor(raw) { if (raw.kind === 'artist') return 'mdi-account' return { character: 'mdi-account-circle', fandom: 'mdi-book-open-page-variant', @@ -162,27 +193,19 @@ function clearAll() { router.push({ name: 'gallery', query: {} }) } // Single write path: clone the current filter, mutate, serialize to the URL. // The route watcher in GalleryView applies it to the store and reloads. +// cloneFilter/filterToQuery are shared with GalleryFacetPanel so the refine +// sub-filters survive a bar push and vice versa. function pushFilter(mutate) { - const f = store.filter - const n = { - tag_ids: [...f.tag_ids], - artist_id: f.artist_id, - media_type: f.media_type, - sort: f.sort, - } + const n = cloneFilter(store.filter) mutate(n) - const q = {} - if (n.tag_ids.length) q.tag_id = n.tag_ids.join(',') - if (n.artist_id) q.artist_id = String(n.artist_id) - if (n.media_type) q.media = n.media_type - if (n.sort && n.sort !== 'newest') q.sort = n.sort - router.push({ name: 'gallery', query: q }) + router.push({ name: 'gallery', query: filterToQuery(n) }) }