spotters: most-active-in sort with country/region selector, collection tracking (seen marks, progress, filters), refresh scripts score activity and report new NL entries
Some checks failed
Deploy production / deploy (push) Has been cancelled
Some checks failed
Deploy production / deploy (push) Has been cancelled
This commit is contained in:
50
src/lib/spotting/seen.svelte.ts
Normal file
50
src/lib/spotting/seen.svelte.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
// Client-side collection state for the spotting pokedexes: optimistic overrides on top of
|
||||
// what the server sent, so a toggle updates every card/panel for that entry at once.
|
||||
import { invalidateAll } from '$app/navigation';
|
||||
|
||||
export type SpotTracker = 'cars' | 'planes' | 'trains' | 'wildlife';
|
||||
|
||||
const overrides = $state<Record<string, boolean>>({});
|
||||
const key = (tracker: SpotTracker, id: number) => `${tracker}:${id}`;
|
||||
|
||||
export function isSeen(tracker: SpotTracker, id: number, fromServer: boolean): boolean {
|
||||
const k = key(tracker, id);
|
||||
return k in overrides ? overrides[k] : fromServer;
|
||||
}
|
||||
|
||||
/** Owner-only POST; reverts the optimistic state when the server refuses. */
|
||||
export async function saveSeen(
|
||||
tracker: SpotTracker,
|
||||
id: number,
|
||||
seen: boolean,
|
||||
extra: { seenOn?: string | null; note?: string | null } = {}
|
||||
): Promise<boolean> {
|
||||
const k = key(tracker, id);
|
||||
const before = k in overrides ? overrides[k] : undefined;
|
||||
overrides[k] = seen;
|
||||
try {
|
||||
const res = await fetch(`/trackers/${tracker}/seen`, {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({ id, seen, ...extra })
|
||||
});
|
||||
if (!res.ok) throw new Error(String(res.status));
|
||||
await invalidateAll();
|
||||
delete overrides[k];
|
||||
return true;
|
||||
} catch {
|
||||
if (before === undefined) delete overrides[k];
|
||||
else overrides[k] = before;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
type HasSeen = { id: number; seen: boolean };
|
||||
/** How many of these entries are collected (respecting optimistic toggles). */
|
||||
export const countSeen = (tracker: SpotTracker, items: HasSeen[]) =>
|
||||
items.reduce((n, x) => n + (isSeen(tracker, x.id, x.seen) ? 1 : 0), 0);
|
||||
/** Client-side ?got= filter for group pages that list every entry at once. */
|
||||
export function filterGot<T extends HasSeen>(tracker: SpotTracker, items: T[], got: string): T[] {
|
||||
if (got !== 'yes' && got !== 'no') return items;
|
||||
return items.filter((x) => isSeen(tracker, x.id, x.seen) === (got === 'yes'));
|
||||
}
|
||||
Reference in New Issue
Block a user