Frontend - Feed: DOM-windowing via @tanstack/svelte-virtual (new VirtualFeed). The window virtualizer keeps the sticky header, pull-to-refresh, infinite-scroll sentinel and bottom nav working. List = dynamic measured heights keyed by upload id + anchorTo:start so an SSE prepend doesn't jump a scrolled reader; grid = measured square rows. Drops the content-visibility band-aid and the old FeedGrid. measureElement(null) on row unmount prevents ResizeObserver retention; stable option callbacks + guarded setOptions avoid O(n) re-measure on like/comment patches. - Global: viewport-fit=cover (activates safe-area insets), lang=de, PWA manifest + maskable icon + apple-touch metas, prefers-reduced-motion, safe-area-top headers. - Feed reactivity: like/comment SSE patch the single card in place; upload-processed debounced in-place merge; IntersectionObserver leak fixed; shared 60s clock. - CLS: list cards reserve the skeleton aspect box. - Destructive actions (promote/demote/unban, gallery release) routed through ConfirmSheet (host + admin). - Export OOM: streamed download via single-use ticket instead of res.blob(). - Shared primitives: IconButton (44px hit area), scrollLock action; UploadSheet a11y. - Polish: api timeout + non-JSON guard, focus-trap offsetParent fix, pull-to-refresh passive:false + guards, diashow keyboard a11y, Toaster assertive errors, dark-mode gaps, aria-pressed on like/chip state, CameraCapture mic-on-video + retry, ConfirmSheet spinner, upload beforeunload warning, emoji->SVG icons. Backend - social.rs: like/comment SSE broadcasts now carry the fresh count so feed clients patch one card in place instead of refetching page 1. - admin.rs / main.rs: supporting changes for the above. Verified: svelte-check 0 errors, frontend build clean, /feed SSR 200. Runtime feed scroll-feel validation still owed (documented in FOLLOWUPS.md). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
57 lines
1.9 KiB
TypeScript
57 lines
1.9 KiB
TypeScript
// Per-device "Datenmodus" — Saver loads compressed previews (default), Original loads
|
|
// the full file via the auth-gated `/api/v1/upload/{id}/original` endpoint.
|
|
//
|
|
// Stored per-device in localStorage (not per-user) because data plans are a property
|
|
// of the device the guest is currently holding, not their identity.
|
|
//
|
|
// Used by:
|
|
// - Feed cards (FeedListCard / VirtualFeed grid tiles) to pick which URL to render
|
|
// - Lightbox
|
|
// - Diashow
|
|
// See [docs/FEATURES.md §2.5] for the user-facing model.
|
|
|
|
import { writable } from 'svelte/store';
|
|
import { browser } from '$app/environment';
|
|
|
|
export type DataMode = 'saver' | 'original';
|
|
|
|
const KEY = 'eventsnap_data_mode';
|
|
const DEFAULT: DataMode = 'saver';
|
|
|
|
function readInitial(): DataMode {
|
|
if (!browser) return DEFAULT;
|
|
const raw = localStorage.getItem(KEY);
|
|
return raw === 'original' || raw === 'saver' ? raw : DEFAULT;
|
|
}
|
|
|
|
export const dataMode = writable<DataMode>(readInitial());
|
|
|
|
if (browser) {
|
|
dataMode.subscribe((value) => {
|
|
try {
|
|
localStorage.setItem(KEY, value);
|
|
} catch {
|
|
// localStorage may be unavailable (Safari private mode); ignore.
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Build the URL for a feed upload given the current data mode and the URL variants
|
|
* the backend returned. Centralised so every consumer (cards, lightbox, diashow)
|
|
* follows the same fallback rule:
|
|
* Original mode → original API route. Falls back to preview if no upload id is
|
|
* available (defensive — shouldn't happen in practice).
|
|
* Saver mode → preview URL (compressed), falling back to thumbnail and then
|
|
* original.
|
|
*/
|
|
export function pickMediaUrl(
|
|
mode: DataMode,
|
|
upload: { id: string; preview_url: string | null; thumbnail_url: string | null }
|
|
): string {
|
|
if (mode === 'original') {
|
|
return `/api/v1/upload/${upload.id}/original`;
|
|
}
|
|
return upload.preview_url ?? upload.thumbnail_url ?? `/api/v1/upload/${upload.id}/original`;
|
|
}
|