feat(plex): port the saved-views (personal views) UI to Plex (frontend)
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.
This commit is contained in:
@@ -16,10 +16,11 @@ import {
|
||||
verticalListSortingStrategy,
|
||||
} from "@dnd-kit/sortable";
|
||||
import { CSS } from "@dnd-kit/utilities";
|
||||
import type { SavedView } from "../lib/api";
|
||||
import { coercePlexFilters, type FeedFilters, type SavedView } from "../lib/api";
|
||||
import Modal from "./Modal";
|
||||
import { useSavedViews, asFilters } from "./SavedViewsProvider";
|
||||
import { useActiveFilters } from "../lib/useActiveFilters";
|
||||
import { buildPlexActiveFilters } from "./usePlexActiveFilters";
|
||||
|
||||
// Master–detail manager for saved ("personal") views: the left column lists the views (drag to
|
||||
// reorder) plus a "save the current filters" input; the right column shows the selected view's name,
|
||||
@@ -72,19 +73,38 @@ function ViewRow({
|
||||
|
||||
export default function PersonalViewsModal() {
|
||||
const { t } = useTranslation();
|
||||
const { views, activeViewId, apply, create, rename, toggleDefault, remove, reorder, closeModal } =
|
||||
useSavedViews();
|
||||
const {
|
||||
source,
|
||||
views,
|
||||
activeViewId,
|
||||
apply,
|
||||
create,
|
||||
rename,
|
||||
toggleDefault,
|
||||
remove,
|
||||
reorder,
|
||||
closeModal,
|
||||
} = useSavedViews();
|
||||
const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 4 } }));
|
||||
|
||||
const [selectedId, setSelectedId] = useState<number | null>(activeViewId ?? views[0]?.id ?? null);
|
||||
const selected = views.find((v) => v.id === selectedId) ?? null;
|
||||
const [draftName, setDraftName] = useState("");
|
||||
|
||||
// The selected view's captured filters, as the same human labels the toolbar chip row shows.
|
||||
const captured = useActiveFilters(
|
||||
selected ? asFilters(selected.filters) : ({ tags: [], channelIds: [] } as never),
|
||||
() => {},
|
||||
// The selected view's captured filters, as the same human labels its module's chip row shows. The
|
||||
// feed hook runs unconditionally (fed empty filters off-feed); Plex uses the pure builder (no-op
|
||||
// removes — this is display-only). A plex view captures its rail filters, not the watch-state.
|
||||
const noop = () => {};
|
||||
const feedCaptured = useActiveFilters(
|
||||
source === "feed" && selected
|
||||
? asFilters(selected.filters as unknown as FeedFilters)
|
||||
: ({ tags: [], channelIds: [] } as never),
|
||||
noop,
|
||||
);
|
||||
const captured =
|
||||
source === "plex" && selected
|
||||
? buildPlexActiveFilters(coercePlexFilters(selected.filters), "all", noop, noop, t)
|
||||
: feedCaptured;
|
||||
|
||||
async function onDragEnd(e: DragEndEvent) {
|
||||
const { active, over } = e;
|
||||
|
||||
Reference in New Issue
Block a user