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))
}
/>
+
);