172 lines
3.8 KiB
TypeScript
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,
|
|
};
|
|
});
|