Files
beachho.us/src/stores/gallery.ts
T
matt bc02d72576
CI / quality (push) Has been cancelled
New initial commit
2026-04-22 16:28:11 -07:00

172 lines
3.8 KiB
TypeScript

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<string>,
): 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<GalleryAsset[]>([]);
const isLoading = ref(false);
const loadError = ref<string | null>(null);
const activeCollection = ref<Collection | "all">("all");
const selectedTags = ref<Set<string>>(new Set());
const visibleCount = ref(BATCH);
const lightboxAssetId = ref<string | null>(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<string>();
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,
};
});