A picker whose options failed to load said so, but the only way back was closing its dialog. Combobox takes `onRetry`: a Retry beside the message for the pointer, and Enter in the field for the keyboard (the panel is a listbox, which may hold options only, so the alert names Enter for assistive tech). Every caller with an error state passes its query's refetch: both user pickers, the channel picker and the two Plex pickers.
50 lines
2.1 KiB
TypeScript
50 lines
2.1 KiB
TypeScript
import { useTranslation } from "react-i18next";
|
|
import { useQuery } from "@tanstack/react-query";
|
|
import { qk } from "../lib/queryKeys";
|
|
import { api, type FeedFilters } from "../lib/api";
|
|
import Combobox, { type ComboboxOption } from "./ui/Combobox";
|
|
|
|
// Pick channels to narrow the feed to — several OR together, so every other filter then applies
|
|
// across all of them. Until this existed the filter was settable only via a shared "?channel=" link:
|
|
// the panel had a remove button for something nothing could apply.
|
|
//
|
|
// It owns its channel query rather than letting the Sidebar hold it: the Sidebar is mounted for the
|
|
// whole feed page, but SidePanel only renders its children once the panel is open, so keeping the
|
|
// fetch here means a subscription list of a few hundred loads when the picker is actually looked at.
|
|
export default function ChannelPicker({
|
|
filters,
|
|
setFilters,
|
|
}: {
|
|
filters: FeedFilters;
|
|
setFilters: (f: FeedFilters) => void;
|
|
}) {
|
|
const { t } = useTranslation();
|
|
const channelsQuery = useQuery({
|
|
queryKey: qk.channels(),
|
|
queryFn: api.channels,
|
|
staleTime: 5 * 60_000,
|
|
});
|
|
|
|
const all = channelsQuery.data ?? [];
|
|
// The list order isn't alphabetical — it's the user's channel priority — and Combobox preserves the
|
|
// options order, so it stays meaningful. selectedLabels keeps a picked channel named even if a later
|
|
// narrowing ever drops it from the visible rows.
|
|
const options: ComboboxOption[] = all.map((c) => ({ value: c.id, label: c.title ?? c.id }));
|
|
const selectedLabels = Object.fromEntries(all.map((c) => [c.id, c.title ?? c.id]));
|
|
|
|
return (
|
|
<Combobox
|
|
label={t("sidebar.channelSearch")}
|
|
placeholder={t("sidebar.channelSearch")}
|
|
options={options}
|
|
selected={filters.channelIds}
|
|
onChange={(channelIds) => setFilters({ ...filters, channelIds })}
|
|
loading={channelsQuery.isLoading}
|
|
error={channelsQuery.isError && !channelsQuery.data ? t("common.loadError") : undefined}
|
|
onRetry={() => void channelsQuery.refetch()}
|
|
emptyText={t("sidebar.noChannelMatch")}
|
|
selectedLabels={selectedLabels}
|
|
/>
|
|
);
|
|
}
|