import { defineStore } from 'pinia' import { ref, computed } from 'vue' import { useApi } from '../composables/useApi.js' import { useInflightToken } from '../composables/useInflightToken.js' import { usePostsStore } from './posts.js' const PAGE = 60 export const useArtistStore = defineStore('artist', () => { const api = useApi() const overview = ref(null) const images = ref([]) const nextCursor = ref(null) const loading = ref(false) const imagesLoading = ref(false) const error = ref(null) const notFound = ref(false) let started = false // Rapid artist-to-artist navigation used to render the previous // artist's overview/images briefly when the second load resolved // after the third. Audit 2026-06-02. const inflight = useInflightToken() async function load (slug) { // Cross-artist reset: clear this store AND the posts store so the new // artist doesn't briefly render with the previous artist's content // when the user is on the Posts tab. (Gallery tab uses this artist // store's own images list — cleared above.) inflight.cancel() overview.value = null images.value = [] nextCursor.value = null notFound.value = false started = false error.value = null loading.value = true usePostsStore().$reset?.() const t = inflight.claim() try { const body = await api.get(`/api/artist/${encodeURIComponent(slug)}`) if (!t.isCurrent()) return overview.value = body await loadMoreImages(slug) } catch (e) { if (!t.isCurrent()) return if (e.status === 404) notFound.value = true else error.value = e.message } finally { if (t.isCurrent()) loading.value = false } } async function loadMoreImages (slug) { if (imagesLoading.value) return if (started && nextCursor.value === null) return imagesLoading.value = true const t = inflight.claim() try { const params = { limit: PAGE } if (nextCursor.value) params.cursor = nextCursor.value const body = await api.get( `/api/artist/${encodeURIComponent(slug)}/images`, { params } ) if (!t.isCurrent()) return images.value.push(...body.images) nextCursor.value = body.next_cursor started = true } catch (e) { if (!t.isCurrent()) return error.value = e.message } finally { if (t.isCurrent()) imagesLoading.value = false } } // Rename the DISPLAY name only (#130). The slug is immutable, so the route // (slug-based) and on-disk paths are unaffected — just patch the shown name. async function rename (name) { if (!overview.value) return const id = overview.value.id const body = await api.patch(`/api/artists/${id}`, { body: { name } }) if (overview.value && overview.value.id === id) overview.value.name = body.name return body } const hasMoreImages = computed(() => !started || nextCursor.value !== null) const postCount = computed(() => overview.value?.post_count ?? null) const imageCount = computed(() => overview.value?.image_count ?? null) const lastAdded = computed(() => overview.value?.date_range?.max ?? null) return { overview, images, loading, imagesLoading, error, notFound, hasMoreImages, postCount, imageCount, lastAdded, load, loadMoreImages, rename, } })