import { defineStore } from "pinia"; import { computed, ref, watch } from "vue"; import { loadGalleryAssets } from "@/data"; import type { Collection, GalleryAsset } from "@/types/gallery"; export const BATCH = 12; function assetMatchesFilters( asset: GalleryAsset, collection: Collection | "all", selectedTags: Set, ): boolean { if (collection !== "all" && asset.collection !== collection) { return false; } if (selectedTags.size === 0) { return true; } return [...selectedTags].some((t) => asset.tags.includes(t)); } export const useGalleryStore = defineStore("gallery", () => { const allAssets = ref([]); const isLoading = ref(false); const loadError = ref(null); const activeCollection = ref("all"); const selectedTags = ref>(new Set()); const visibleCount = ref(BATCH); const lightboxAssetId = ref(null); const filteredAssets = computed(() => allAssets.value.filter((a) => assetMatchesFilters(a, activeCollection.value, selectedTags.value), ), ); const visibleAssets = computed(() => filteredAssets.value.slice(0, visibleCount.value), ); const hasMore = computed( () => visibleCount.value < filteredAssets.value.length, ); const allTags = computed(() => { const s = new Set(); for (const a of allAssets.value) { for (const t of a.tags) { s.add(t); } } return [...s].sort((a, b) => a.localeCompare(b)); }); function setCollection(c: Collection | "all") { activeCollection.value = c; } async function init() { if (isLoading.value) { return; } isLoading.value = true; loadError.value = null; try { allAssets.value = await loadGalleryAssets(); resetPagination(); } catch (error) { loadError.value = "Unable to load gallery assets."; allAssets.value = []; resetPagination(); console.error(error); } finally { isLoading.value = false; } } function toggleTag(tag: string) { const next = new Set(selectedTags.value); if (next.has(tag)) { next.delete(tag); } else { next.add(tag); } selectedTags.value = next; } function clearTags() { selectedTags.value = new Set(); } function loadMore() { if (!hasMore.value) { return; } visibleCount.value = Math.min( visibleCount.value + BATCH, filteredAssets.value.length, ); } function resetPagination() { visibleCount.value = Math.min(BATCH, filteredAssets.value.length); } watch([activeCollection, selectedTags], () => { resetPagination(); }); function openLightbox(assetId: string) { lightboxAssetId.value = assetId; } function closeLightbox() { lightboxAssetId.value = null; } const lightboxAsset = computed(() => { const id = lightboxAssetId.value; if (!id) { return null; } return filteredAssets.value.find((a) => a.id === id) ?? null; }); const lightboxIndex = computed(() => { const id = lightboxAssetId.value; if (!id) { return -1; } return filteredAssets.value.findIndex((a) => a.id === id); }); function stepLightbox(delta: number) { const list = filteredAssets.value; if (list.length === 0) { return; } let i = lightboxIndex.value; if (i < 0) { i = 0; } const next = (i + delta + list.length) % list.length; lightboxAssetId.value = list[next]!.id; } return { allAssets, isLoading, loadError, activeCollection, selectedTags, visibleAssets, filteredAssets, hasMore, allTags, setCollection, init, toggleTag, clearTags, loadMore, resetPagination, lightboxAssetId, lightboxAsset, lightboxIndex, openLightbox, closeLightbox, stepLightbox, }; });