import { defineStore } from 'pinia' import { ref, computed } from 'vue' import { useApi } from '../composables/useApi.js' export const useGalleryStore = defineStore('gallery', () => { const api = useApi() const images = ref([]) const dateGroups = ref([]) // [{year, month, image_ids}] const nextCursor = ref(null) const loading = ref(false) const error = ref(null) const filter = ref({ tag_id: null }) const timelineBuckets = ref([]) const timelineLoading = ref(false) let inflightId = 0 async function loadInitial() { images.value = [] dateGroups.value = [] nextCursor.value = null await loadMore() } async function loadMore() { if (loading.value) return loading.value = true error.value = null const myId = ++inflightId try { const params = { limit: 50 } if (filter.value.tag_id) params.tag_id = filter.value.tag_id if (nextCursor.value) params.cursor = nextCursor.value const body = await api.get('/api/gallery/scroll', { params }) if (myId !== inflightId) return // stale response images.value.push(...body.images) dateGroups.value = mergeGroups(dateGroups.value, body.date_groups) nextCursor.value = body.next_cursor } catch (e) { error.value = e.message } finally { if (myId === inflightId) loading.value = false } } async function loadTimeline() { timelineLoading.value = true try { const params = filter.value.tag_id ? { tag_id: filter.value.tag_id } : {} timelineBuckets.value = await api.get('/api/gallery/timeline', { params }) } finally { timelineLoading.value = false } } async function jumpTo(year, month) { const params = { year, month } if (filter.value.tag_id) params.tag_id = filter.value.tag_id const body = await api.get('/api/gallery/jump', { params }) if (body.cursor) { images.value = [] dateGroups.value = [] nextCursor.value = body.cursor await loadMore() } } function setTagFilter(tagId) { filter.value.tag_id = tagId loadInitial() loadTimeline() } const hasMore = computed(() => nextCursor.value !== null) const isEmpty = computed(() => !loading.value && images.value.length === 0 && error.value === null) return { images, dateGroups, hasMore, isEmpty, loading, error, filter, timelineBuckets, timelineLoading, loadInitial, loadMore, loadTimeline, jumpTo, setTagFilter } }) function mergeGroups(existing, incoming) { // Merge sequential groups with the same (year, month) instead of duplicating. const merged = [...existing] for (const g of incoming) { const tail = merged[merged.length - 1] if (tail && tail.year === g.year && tail.month === g.month) { tail.image_ids = [...tail.image_ids, ...g.image_ids] } else { merged.push({ ...g, image_ids: [...g.image_ids] }) } } return merged }