Settings, Configuration and Users name their tab (stab/cfgtab/utab); a link's tab wins and a tab pre-selected while its page is on screen now switches it. A playlist's sort, filters and grouping (plsort/plf/plgroup), the Files sort (fsort, always written) and the name searches of Channels, Files, Playlists and Plex (cq/fq/plq/pq+pqf) reopen from the address; the searches are written once the typing pauses.
66 lines
3.2 KiB
TypeScript
66 lines
3.2 KiB
TypeScript
// Serialize the Channels-manager content state (active tab + column sort + the column-filter map) to/
|
|
// from the address bar so a manager view is bookmarkable/shareable — the analogue of the feed's
|
|
// urlState and Plex's plexUrlState. Keys are NAMESPACED (`ctab`/`csort`/`cf`) so they're
|
|
// channels-unique (like `?v=`/`?pl`/`pscope`): no other provider ever reads or clears them, and the
|
|
// feed's URL-stripping can't delete them out from under the manager. Only non-default values are
|
|
// emitted, so URLs stay clean. The name search (`cq`) is part of the view like the rest: a bookmark of a
|
|
// searched list opens searched.
|
|
import { decodeSortParam, encodeSortParam, type SortState } from "./columnSort";
|
|
import { type FilterMap } from "./tableColumns";
|
|
import { decodeFilterMap, encodeFilterMap } from "./filterMapParam";
|
|
|
|
// The content state this mirrors. `view` is the raw tab string (the provider clamps it); `sort` is the
|
|
// raw provider SortState (healed against live columns only at the point of use), so the user's real
|
|
// preference round-trips through the URL.
|
|
export interface ChannelsUrlState {
|
|
view: string;
|
|
sort: SortState;
|
|
filters: FilterMap;
|
|
search: string;
|
|
}
|
|
|
|
export const CHANNELS_KEYS = ["ctab", "csort", "cf", "cq"] as const;
|
|
const KEYS = CHANNELS_KEYS;
|
|
const VIEWS = ["subscribed", "discovery", "hidden", "blocked"];
|
|
|
|
// Sort ⇄ `key:dir` (e.g. `subscribers:desc`) via the shared codec in columnSort. There is no default
|
|
// sort here (fallback null), so any sort emits — no default short-circuit needed. The filter map rides
|
|
// as one JSON value (lib/filterMapParam).
|
|
|
|
/** The address-bar delta for the manager content state: each namespaced key mapped to its emitted
|
|
* value, or null to delete it (so a control back at its default clears the key). Pass `null` to clear
|
|
* ALL manager keys — used when the app is off the Channels page (localStorage still holds the state; it
|
|
* re-mirrors on return). Feed the result into urlWithParams(). */
|
|
export function channelsParamUpdates(
|
|
state: ChannelsUrlState | null,
|
|
): Record<string, string | null> {
|
|
if (!state) return { ctab: null, csort: null, cf: null, cq: null };
|
|
return {
|
|
ctab: state.view && state.view !== "subscribed" ? state.view : null,
|
|
csort: encodeSortParam(state.sort),
|
|
cf: encodeFilterMap(state.filters),
|
|
cq: state.search.trim() || null,
|
|
};
|
|
}
|
|
|
|
/** True if the query string carries any manager content key we recognize (a shared manager link). */
|
|
export function hasChannelsParams(params: URLSearchParams): boolean {
|
|
return KEYS.some((k) => params.has(k));
|
|
}
|
|
|
|
/** Merge the manager content keys from the URL over a base state (the localStorage-seeded values); an
|
|
* absent or invalid key keeps the base value, so a partial link (only `?ctab=`) is honoured. */
|
|
export function channelsParamsToState(
|
|
params: URLSearchParams,
|
|
base: ChannelsUrlState,
|
|
): ChannelsUrlState {
|
|
const tab = params.get("ctab");
|
|
const csort = params.get("csort");
|
|
return {
|
|
view: tab && VIEWS.includes(tab) ? tab : base.view,
|
|
sort: csort != null ? (decodeSortParam(csort) ?? base.sort) : base.sort,
|
|
filters: params.has("cf") ? decodeFilterMap(params.get("cf") ?? "") : base.filters,
|
|
search: params.get("cq") ?? base.search,
|
|
};
|
|
}
|