- Combobox gains `error` (said in the list, not "No matches") and a soft `disabled` (keeps the focus, announced, opens and changes nothing). - UserPicker passes both: the share dialog's recipient picker is off while a share is pending, and the share and quota pickers say when the user list failed to load. - ChannelPicker and the Plex playlist/collection pickers say a failed load too. - The channel menu's Tags row says loading / failed instead of "no tags yet".
49 lines
2.0 KiB
TypeScript
49 lines
2.0 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}
|
|
emptyText={t("sidebar.noChannelMatch")}
|
|
selectedLabels={selectedLabels}
|
|
/>
|
|
);
|
|
}
|