80 lines
2.5 KiB
Svelte
80 lines
2.5 KiB
Svelte
<script lang="ts">
|
|
import type { PageData } from './$types';
|
|
import type { GridItem } from '$lib/types/item';
|
|
import ItemCard from '$lib/components/item/ItemCard.svelte';
|
|
import Pagination from '$lib/components/ui/Pagination.svelte';
|
|
import { onSSE } from '$lib/stores/sse';
|
|
import { pinEvent } from '$lib/stores/pin';
|
|
import { onDestroy } from 'svelte';
|
|
|
|
let { data }: { data: PageData } = $props();
|
|
|
|
// Local mutable copy of the items list — SSE patches it live
|
|
let items = $state<GridItem[]>(data.items);
|
|
let total = $state(data.total);
|
|
|
|
// Re-sync when server data changes (e.g. navigation / invalidation)
|
|
$effect(() => {
|
|
items = data.items;
|
|
total = data.total;
|
|
});
|
|
|
|
// ── Live grid updates via SSE ─────────────────────────────────────────────
|
|
const unsubSSE = onSSE((event) => {
|
|
if (event.type === 'new_item') {
|
|
const d = event.data as GridItem & { dest: string; username: string };
|
|
if (!items.some(i => i.id === d.id)) {
|
|
items = [d, ...items];
|
|
total += 1;
|
|
}
|
|
} else if (event.type === 'delete_item') {
|
|
const d = event.data as { id: number };
|
|
const before = items.length;
|
|
items = items.filter(i => i.id !== d.id);
|
|
if (items.length < before) total = Math.max(0, total - 1);
|
|
}
|
|
});
|
|
onDestroy(unsubSSE);
|
|
|
|
// ── Pin store: patch the grid card is_pinned live ─────────────────────────
|
|
const unsubPin = pinEvent.subscribe((evt) => {
|
|
if (!evt) return;
|
|
items = items.map(i => i.id === evt.itemId ? { ...i, is_pinned: evt.pinned } : i);
|
|
});
|
|
onDestroy(unsubPin);
|
|
</script>
|
|
|
|
<svelte:head>
|
|
<title>f0ckm — {total} items</title>
|
|
</svelte:head>
|
|
|
|
<div class="max-w-[1600px] mx-auto px-3 py-4">
|
|
<!-- Grid header -->
|
|
{#if total > 0}
|
|
<p class="text-xs opacity-40 mb-3 font-mono">{total.toLocaleString()} items</p>
|
|
{/if}
|
|
|
|
<!-- Thumbnail grid -->
|
|
{#if items.length > 0}
|
|
<div class="
|
|
grid gap-1.5
|
|
grid-cols-[repeat(auto-fill,minmax(140px,1fr))]
|
|
sm:grid-cols-[repeat(auto-fill,minmax(180px,1fr))]
|
|
md:grid-cols-[repeat(auto-fill,minmax(200px,1fr))]
|
|
">
|
|
{#each items as item (item.id)}
|
|
<ItemCard {item} link={data.link} />
|
|
{/each}
|
|
</div>
|
|
|
|
{#if data.pagination}
|
|
<Pagination pagination={data.pagination} link={data.link} />
|
|
{/if}
|
|
{:else}
|
|
<div class="flex flex-col items-center justify-center py-32 gap-4 opacity-40">
|
|
<i class="fa-solid fa-box-open text-5xl"></i>
|
|
<p class="text-sm">No items found.</p>
|
|
</div>
|
|
{/if}
|
|
</div>
|