diff --git a/src/assets/locales/en.json b/src/assets/locales/en.json index acab2c56..b5976dd5 100644 --- a/src/assets/locales/en.json +++ b/src/assets/locales/en.json @@ -528,7 +528,7 @@ "autoplayDescription": "Automatically play the next episode in a series after reaching the end. Can be enabled by users with the browser extension, a custom proxy, or with the default setup if allowed by the host.", "autoplayLabel": "Autoplay", "sourceOrder": "Reordering sources", - "sourceOrderDescription": "Drag and drop to reorder sources. This will determine the order in which sources are checked for the media you are trying to watch.", + "sourceOrderDescription": "Drag and drop to reorder sources. This will determine the order in which sources are checked for the media you are trying to watch. If a source is greyed out, it means it is not available on your device.", "title": "Preferences" }, "reset": "Reset", diff --git a/src/backend/providers/providers.ts b/src/backend/providers/providers.ts index ac4a7dfa..fba9b4d5 100644 --- a/src/backend/providers/providers.ts +++ b/src/backend/providers/providers.ts @@ -25,3 +25,11 @@ export function getProviders() { target: targets.BROWSER, }); } + +export function getAllProviders() { + return makeProviders({ + fetcher: makeStandardFetcher(fetch), + target: targets.BROWSER_EXTENSION, + consistentIpForRequests: true, + }); +} diff --git a/src/components/form/SortableList.tsx b/src/components/form/SortableList.tsx index b139b8e4..43da96fa 100644 --- a/src/components/form/SortableList.tsx +++ b/src/components/form/SortableList.tsx @@ -22,6 +22,7 @@ import { Icon, Icons } from "../Icon"; export interface Item { id: string; name: string; + disabled?: boolean; } function SortableItem(props: { item: Item }) { @@ -40,11 +41,13 @@ function SortableItem(props: { item: Item }) { {...attributes} {...listeners} className={classNames( - "bg-dropdown-background hover:bg-dropdown-hoverBackground select-none cursor-pointer space-x-3 flex items-center max-w-[25rem] py-3 px-4 rounded-lg touch-none", - transform && "cursor-grabbing", + "bg-dropdown-background hover:bg-dropdown-hoverBackground select-none space-x-3 flex items-center max-w-[25rem] py-3 px-4 rounded-lg touch-none", + props.item.disabled && "opacity-50", + transform ? "cursor-grabbing" : "cursor-grab", )} > {props.item.name} + {props.item.disabled && } ); diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx index 720d55ea..03aea1db 100644 --- a/src/pages/Settings.tsx +++ b/src/pages/Settings.tsx @@ -11,7 +11,7 @@ import { import { getSessions, updateSession } from "@/backend/accounts/sessions"; import { updateSettings } from "@/backend/accounts/settings"; import { editUser } from "@/backend/accounts/user"; -import { getProviders } from "@/backend/providers/providers"; +import { getAllProviders, getProviders } from "@/backend/providers/providers"; import { Button } from "@/components/buttons/Button"; import { WideContainer } from "@/components/layout/WideContainer"; import { UserIcons } from "@/components/UserIcon"; @@ -155,6 +155,22 @@ export function SettingsPage() { sourceOrder, ); + const availableSources = useMemo(() => { + const sources = getAllProviders().listSources(); + const sourceIDs = sources.map((s) => s.id); + const stateSources = state.sourceOrder.state; + + // Filter out sources that are not in `stateSources` and are in `sources` + const updatedSources = stateSources.filter((ss) => sourceIDs.includes(ss)); + + // Add sources from `sources` that are not in `stateSources` + const missingSources = sources + .filter((s) => !stateSources.includes(s.id)) + .map((s) => s.id); + + return [...updatedSources, ...missingSources]; + }, [state.sourceOrder.state]); + useEffect(() => { setPreviewTheme(activeTheme ?? "default"); }, [setPreviewTheme, activeTheme]); @@ -281,13 +297,7 @@ export function SettingsPage() { setEnableThumbnails={state.enableThumbnails.set} enableAutoplay={state.enableAutoplay.state} setEnableAutoplay={state.enableAutoplay.set} - sourceOrder={ - state.sourceOrder.state.length > 0 - ? state.sourceOrder.state - : getProviders() - .listSources() - .map((s) => s.id) - } + sourceOrder={availableSources} setSourceOrder={state.sourceOrder.set} /> diff --git a/src/pages/parts/settings/PreferencesPart.tsx b/src/pages/parts/settings/PreferencesPart.tsx index 4057f47c..d127655f 100644 --- a/src/pages/parts/settings/PreferencesPart.tsx +++ b/src/pages/parts/settings/PreferencesPart.tsx @@ -1,7 +1,9 @@ import classNames from "classnames"; +import { useMemo } from "react"; import { useTranslation } from "react-i18next"; -import { getProviders } from "@/backend/providers/providers"; +import { getAllProviders, getProviders } from "@/backend/providers/providers"; +import { Button } from "@/components/buttons/Button"; import { Toggle } from "@/components/buttons/Toggle"; import { FlagIcon } from "@/components/FlagIcon"; import { Dropdown } from "@/components/form/Dropdown"; @@ -38,6 +40,17 @@ export function PreferencesPart(props: { (item) => item.id === getLocaleInfo(props.language)?.code, ); + const allSources = getAllProviders().listSources(); + + const sourceItems = useMemo(() => { + const currentDeviceSources = getProviders().listSources(); + return props.sourceOrder.map((id) => ({ + id, + name: allSources.find((s) => s.id === id)?.name || id, + disabled: !currentDeviceSources.find((s) => s.id === id), + })); + }, [props.sourceOrder, allSources]); + return (
{t("settings.preferences.title")} @@ -108,17 +121,18 @@ export function PreferencesPart(props: {

({ - id, - name: - getProviders() - .listSources() - .find((s) => s.id === id)?.name || id, - }))} + items={sourceItems} setItems={(items) => props.setSourceOrder(items.map((item) => item.id)) } /> +
);