Plex now has the same personal-views system as the feed — the rail header icon opens the quick-pick popover, the rail title opens the manager modal (list / create / rename / delete / reorder / set-default / apply), and the active view leads the chip row with a "modified" dot. One SavedViewsProvider serves both modules: it switches `source` (feed ⟷ plex) by the active page (both filter providers are global, so it reads whichever the page owns) and runs the same logic on per-source wiring — the live filters, apply, a stable signature (feed filtersToParams / plex plexFiltersSignature), coercion (asFilters / coercePlexFilters), and what to strip on save. Moved below PlexProvider in the tree so it can read plex state; resets sticky/modal on a source switch. The shared popover/modal/chip are unchanged; the modal describes a plex view's captured filters via a pure buildPlexActiveFilters (extracted from usePlexActiveFilters — no duplication). api.savedViews/createView take a source; `SavedView.filters` is now an opaque blob. New coercePlexFilters/plexFilters- Signature (also dedups PlexProvider's collection migration). Empty-state copy genericised (no longer says "feed"). A Plex view captures the rail filters (not watch-state/sort/scope). Verified end-to-end on localdev: create/apply/preview/default/delete on plex, feed views stay separate + unregressed (402 vitest), no console errors.
41 lines
1.6 KiB
TypeScript
41 lines
1.6 KiB
TypeScript
// Saved "smart views": per-user named snapshots of a module's filters, scoped by `source`
|
|
// ("feed" | "plex"). Only touches core's req; `filters` is an opaque JSON blob (each module casts it
|
|
// to its own filter shape), so this stays independent of feed.ts / plex.ts.
|
|
import { req } from "./core";
|
|
|
|
// A module's saved filter snapshot. `filters` is opaque here — the feed reads it as FeedFilters, Plex
|
|
// as PlexFilters; `source` is implicit (the caller queries per source).
|
|
export interface SavedView {
|
|
id: number;
|
|
name: string;
|
|
filters: Record<string, unknown>;
|
|
position: number;
|
|
is_default: boolean;
|
|
}
|
|
|
|
// The module a view belongs to. "feed" is the default so existing callers keep working.
|
|
type ViewSource = "feed" | "plex";
|
|
|
|
export const savedViewsApi = {
|
|
savedViews: (source: ViewSource = "feed"): Promise<SavedView[]> =>
|
|
req(`/api/saved-views?source=${source}`),
|
|
createView: (
|
|
name: string,
|
|
filters: Record<string, unknown>,
|
|
source: ViewSource = "feed",
|
|
): Promise<SavedView> =>
|
|
req("/api/saved-views", {
|
|
method: "POST",
|
|
body: JSON.stringify({ name, filters, source }),
|
|
}),
|
|
updateView: (
|
|
id: number,
|
|
patch: Partial<{ name: string; filters: Record<string, unknown>; is_default: boolean }>,
|
|
): Promise<SavedView> =>
|
|
req(`/api/saved-views/${id}`, { method: "PATCH", body: JSON.stringify(patch) }),
|
|
deleteView: (id: number): Promise<{ deleted: number }> =>
|
|
req(`/api/saved-views/${id}`, { method: "DELETE" }),
|
|
reorderViews: (ids: number[]): Promise<{ ok: boolean }> =>
|
|
req("/api/saved-views/reorder", { method: "POST", body: JSON.stringify({ ids }) }),
|
|
};
|