Per-user browse preferences
Adds a preferences JSON field on the user plus GET/POST /api/auth/preferences/ (merge semantics, validated keys), surfaced in /auth/me/ and typed on the frontend. The Account page gains a Browsing preferences card: landing page, default rating filter, default sort, items per page and thumbnail size. Signed-in users also sync these while browsing (the Library sidebar's rating/sort/per-page controls and the new thumbnail slider), debounced; on load the account's values seed the local UI state, so settings follow the user across browsers. Guests keep the existing localStorage behaviour. The thumbnail size drives the media grids (Library, Online, pool detail) between 140 and 320px columns. Verified the API against the dev server: merge keeps untouched keys, invalid values 400, values round-trip through /auth/me/.
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
/** Browse controls shared by the Library page and the preferences card. */
|
||||
|
||||
export const sortOptions = [
|
||||
{ value: "-created_at", label: "Newest first" },
|
||||
{ value: "created_at", label: "Oldest first" },
|
||||
{ value: "-size", label: "Largest first" },
|
||||
{ value: "size", label: "Smallest first" },
|
||||
{ value: "name", label: "Name A–Z" },
|
||||
{ value: "-name", label: "Name Z–A" },
|
||||
];
|
||||
|
||||
export const perPageOptions = [24, 48, 96];
|
||||
|
||||
export const ratingFilters = [
|
||||
{
|
||||
value: "s",
|
||||
label: "Safe",
|
||||
active: "border-ctp-green/40 bg-ctp-green/15 text-ctp-green",
|
||||
},
|
||||
{
|
||||
value: "q",
|
||||
label: "Questionable",
|
||||
active: "border-ctp-peach/40 bg-ctp-peach/15 text-ctp-peach",
|
||||
},
|
||||
{
|
||||
value: "e",
|
||||
label: "Explicit",
|
||||
active: "border-ctp-red/40 bg-ctp-red/15 text-ctp-red",
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,55 @@
|
||||
import { api, getToken } from "@/lib/api";
|
||||
import type { UserPreferences } from "@/lib/types";
|
||||
import { useUi } from "@/store/ui";
|
||||
|
||||
/** Seed the local browse settings from the signed-in user's saved preferences. */
|
||||
export function applyPreferences(
|
||||
preferences: UserPreferences | null | undefined,
|
||||
): void {
|
||||
if (!preferences) return;
|
||||
const ui = useUi.getState();
|
||||
if (Array.isArray(preferences.ratings)) ui.setRatings(preferences.ratings);
|
||||
if (typeof preferences.ordering === "string") ui.setOrdering(preferences.ordering);
|
||||
if (typeof preferences.per_page === "number") ui.setPerPage(preferences.per_page);
|
||||
if (typeof preferences.zoom === "number") ui.setZoom(preferences.zoom);
|
||||
}
|
||||
|
||||
/** The browse settings currently in effect, as a preferences payload. */
|
||||
export function preferencesPayload(extra?: UserPreferences): UserPreferences {
|
||||
const ui = useUi.getState();
|
||||
return {
|
||||
ratings: ui.ratings,
|
||||
ordering: ui.ordering,
|
||||
per_page: ui.perPage,
|
||||
zoom: ui.zoom,
|
||||
...extra,
|
||||
};
|
||||
}
|
||||
|
||||
export function savePreferences(
|
||||
patch: UserPreferences,
|
||||
): Promise<UserPreferences> {
|
||||
return api<UserPreferences>("/api/auth/preferences/", {
|
||||
method: "POST",
|
||||
json: patch,
|
||||
});
|
||||
}
|
||||
|
||||
let pending: number | null = null;
|
||||
let pendingPatch: UserPreferences = {};
|
||||
|
||||
/**
|
||||
* Persist browse settings for the signed-in user, debounced so slider drags
|
||||
* do not spam the API. Guests keep using localStorage only.
|
||||
*/
|
||||
export function schedulePreferenceSave(patch: UserPreferences): void {
|
||||
if (!getToken()) return;
|
||||
pendingPatch = { ...pendingPatch, ...patch };
|
||||
if (pending !== null) window.clearTimeout(pending);
|
||||
pending = window.setTimeout(() => {
|
||||
pending = null;
|
||||
const body = preferencesPayload(pendingPatch);
|
||||
pendingPatch = {};
|
||||
void savePreferences(body).catch(() => undefined);
|
||||
}, 700);
|
||||
}
|
||||
@@ -1,3 +1,11 @@
|
||||
export interface UserPreferences {
|
||||
landing_page?: "library" | "online" | "pools" | "followed";
|
||||
ratings?: string[];
|
||||
ordering?: string;
|
||||
per_page?: number;
|
||||
zoom?: number;
|
||||
}
|
||||
|
||||
export interface User {
|
||||
id: number;
|
||||
username: string;
|
||||
@@ -11,6 +19,7 @@ export interface User {
|
||||
avatar_url: string | null;
|
||||
e621_username: string;
|
||||
e621_configured: boolean;
|
||||
preferences: UserPreferences;
|
||||
}
|
||||
|
||||
export interface AdminUser {
|
||||
|
||||
Reference in New Issue
Block a user