diff --git a/src/assets/locales/en.json b/src/assets/locales/en.json index 2bacaa53..c1c86f25 100644 --- a/src/assets/locales/en.json +++ b/src/assets/locales/en.json @@ -1029,7 +1029,10 @@ "lowPerformanceModeLabel": "Low performance mode", "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. If a source is greyed out, it means the extension is required for that source.

(The default order is best for most users)", - "sourceOrderEnableLabel": "Custom source order" + "sourceOrderEnableLabel": "Custom source order", + "embedOrder": "Reordering embeds", + "embedOrderDescription": "Drag and drop to reorder embeds. This will determine the order in which embeds are checked for the media you are trying to watch.

(The default order is best for most users)", + "embedOrderEnableLabel": "Custom embed order" }, "reset": "Reset", "save": "Save", diff --git a/src/backend/accounts/settings.ts b/src/backend/accounts/settings.ts index 4c39e21e..b9092023 100644 --- a/src/backend/accounts/settings.ts +++ b/src/backend/accounts/settings.ts @@ -21,6 +21,8 @@ export interface SettingsInput { forceCompactEpisodeView?: boolean; sourceOrder?: string[]; enableSourceOrder?: boolean; + embedOrder?: string[]; + enableEmbedOrder?: boolean; proxyTmdb?: boolean; enableLowPerformanceMode?: boolean; enableNativeSubtitles?: boolean; @@ -43,6 +45,8 @@ export interface SettingsResponse { enableCarouselView?: boolean; sourceOrder?: string[]; enableSourceOrder?: boolean; + embedOrder?: string[]; + enableEmbedOrder?: boolean; proxyTmdb?: boolean; enableLowPerformanceMode?: boolean; enableNativeSubtitles?: boolean; diff --git a/src/backend/helpers/providerApi.ts b/src/backend/helpers/providerApi.ts index 93eac9d9..c0c96db7 100644 --- a/src/backend/helpers/providerApi.ts +++ b/src/backend/helpers/providerApi.ts @@ -73,9 +73,19 @@ export function makeProviderUrl(base: string) { addQueryDataToUrl(url, { id: sourceId }); return url.toString(); }, - scrapeAll(media: ScrapeMedia) { + scrapeAll( + media: ScrapeMedia, + sourceOrder?: string[], + embedOrder?: string[], + ) { const url = makeUrl("/scrape"); addQueryDataToUrl(url, scrapeMediaToQueryMedia(media)); + if (sourceOrder && sourceOrder.length > 0) { + url.searchParams.set("sourceOrder", sourceOrder.join(",")); + } + if (embedOrder && embedOrder.length > 0) { + url.searchParams.set("embedOrder", embedOrder.join(",")); + } return url.toString(); }, scrapeEmbed(embedId: string, embedUrl: string) { diff --git a/src/hooks/useProviderScrape.tsx b/src/hooks/useProviderScrape.tsx index e6382d78..90e7f069 100644 --- a/src/hooks/useProviderScrape.tsx +++ b/src/hooks/useProviderScrape.tsx @@ -155,6 +155,8 @@ export function useScrape() { const preferredSourceOrder = usePreferencesStore((s) => s.sourceOrder); const enableSourceOrder = usePreferencesStore((s) => s.enableSourceOrder); + const preferredEmbedOrder = usePreferencesStore((s) => s.embedOrder); + const enableEmbedOrder = usePreferencesStore((s) => s.enableEmbedOrder); const startScraping = useCallback( async (media: ScrapeMedia) => { @@ -163,7 +165,11 @@ export function useScrape() { startScrape(); const baseUrlMaker = makeProviderUrl(providerApiUrl); const conn = await connectServerSideEvents( - baseUrlMaker.scrapeAll(media), + baseUrlMaker.scrapeAll( + media, + enableSourceOrder ? preferredSourceOrder : undefined, + enableEmbedOrder ? preferredEmbedOrder : undefined, + ), ["completed", "noOutput"], ); conn.on("init", initEvent); @@ -183,6 +189,8 @@ export function useScrape() { media, // Only pass sourceOrder if enableSourceOrder is true sourceOrder: enableSourceOrder ? preferredSourceOrder : undefined, + // Only pass embedOrder if enableEmbedOrder is true + embedOrder: enableEmbedOrder ? preferredEmbedOrder : undefined, events: { init: initEvent, start: startEvent, @@ -203,6 +211,8 @@ export function useScrape() { startScrape, preferredSourceOrder, enableSourceOrder, + preferredEmbedOrder, + enableEmbedOrder, ], ); diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx index 5dfb172d..9daf2b06 100644 --- a/src/pages/Settings.tsx +++ b/src/pages/Settings.tsx @@ -151,6 +151,11 @@ export function SettingsPage() { const sourceOrder = usePreferencesStore((s) => s.sourceOrder); const setSourceOrder = usePreferencesStore((s) => s.setSourceOrder); + const enableSourceOrder = usePreferencesStore((s) => s.enableSourceOrder); + const setEnableSourceOrder = usePreferencesStore( + (s) => s.setEnableSourceOrder, + ); + const enableDiscover = usePreferencesStore((s) => s.enableDiscover); const setEnableDiscover = usePreferencesStore((s) => s.setEnableDiscover); @@ -165,11 +170,6 @@ export function SettingsPage() { const enableImageLogos = usePreferencesStore((s) => s.enableImageLogos); const setEnableImageLogos = usePreferencesStore((s) => s.setEnableImageLogos); - const enableSourceOrder = usePreferencesStore((s) => s.enableSourceOrder); - const setEnableSourceOrder = usePreferencesStore( - (s) => s.setEnableSourceOrder, - ); - const proxyTmdb = usePreferencesStore((s) => s.proxyTmdb); const setProxyTmdb = usePreferencesStore((s) => s.setProxyTmdb); @@ -348,6 +348,7 @@ export function SettingsPage() { setEnableDetailsModal(state.enableDetailsModal.state); setEnableImageLogos(state.enableImageLogos.state); setSourceOrder(state.sourceOrder.state); + setEnableSourceOrder(state.enableSourceOrder.state); setAppLanguage(state.appLanguage.state); setTheme(state.theme.state); setSubStyling(state.subtitleStyling.state); @@ -389,6 +390,7 @@ export function SettingsPage() { setEnableDetailsModal, setEnableImageLogos, setSourceOrder, + setEnableSourceOrder, setAppLanguage, setTheme, setSubStyling, @@ -397,7 +399,6 @@ export function SettingsPage() { updateProfile, logout, setBackendUrl, - setEnableSourceOrder, setProxyTmdb, setEnableCarouselView, setForceCompactEpisodeView, diff --git a/src/pages/admin/AdminPage.tsx b/src/pages/admin/AdminPage.tsx index dc25c6b3..ccc85001 100644 --- a/src/pages/admin/AdminPage.tsx +++ b/src/pages/admin/AdminPage.tsx @@ -8,6 +8,7 @@ import { TMDBTestPart } from "@/pages/parts/admin/TMDBTestPart"; import { WorkerTestPart } from "@/pages/parts/admin/WorkerTestPart"; import { BackendTestPart } from "../parts/admin/BackendTestPart"; +import { EmbedOrderPart } from "../parts/admin/EmbedOrderPart"; export function AdminPage() { return ( @@ -22,6 +23,7 @@ export function AdminPage() { + ); diff --git a/src/pages/parts/admin/EmbedOrderPart.tsx b/src/pages/parts/admin/EmbedOrderPart.tsx new file mode 100644 index 00000000..3df23cbb --- /dev/null +++ b/src/pages/parts/admin/EmbedOrderPart.tsx @@ -0,0 +1,91 @@ +import { useMemo } from "react"; +import { Trans, useTranslation } from "react-i18next"; +import { useNavigate } from "react-router-dom"; + +import { getAllProviders, getProviders } from "@/backend/providers/providers"; +import { Button } from "@/components/buttons/Button"; +import { Toggle } from "@/components/buttons/Toggle"; +import { SortableList } from "@/components/form/SortableList"; +import { Heading2 } from "@/components/utils/Text"; +import { usePreferencesStore } from "@/stores/preferences"; + +export function EmbedOrderPart() { + const { t } = useTranslation(); + const navigate = useNavigate(); + + const embedOrder = usePreferencesStore((s) => s.embedOrder); + const setEmbedOrder = usePreferencesStore((s) => s.setEmbedOrder); + const enableEmbedOrder = usePreferencesStore((s) => s.enableEmbedOrder); + const setEnableEmbedOrder = usePreferencesStore((s) => s.setEnableEmbedOrder); + + const allEmbeds = getAllProviders().listEmbeds(); + + const embedItems = useMemo(() => { + const currentDeviceEmbeds = getProviders().listEmbeds(); + + // If embed order is empty, show all available embeds + if (embedOrder.length === 0) { + return allEmbeds.map((e) => ({ + id: e.id, + name: e.name || e.id, + disabled: !currentDeviceEmbeds.find((embed) => embed.id === e.id), + })); + } + + // Otherwise, show embeds in the specified order + return embedOrder.map((id) => ({ + id, + name: allEmbeds.find((e) => e.id === id)?.name || id, + disabled: !currentDeviceEmbeds.find((e) => e.id === id), + })); + }, [embedOrder, allEmbeds]); + + return ( +
+ Embed Order Settings +
+

+ {t("settings.preferences.embedOrder")} +

+
+ navigate("/onboarding/extension")} + /> + ), + }} + /> +
setEnableEmbedOrder(!enableEmbedOrder)} + className="bg-dropdown-background hover:bg-dropdown-hoverBackground select-none my-4 cursor-pointer space-x-3 flex items-center max-w-[25rem] py-3 px-4 rounded-lg" + > + +

+ {t("settings.preferences.embedOrderEnableLabel")} +

+
+
+ + {enableEmbedOrder && ( +
+ setEmbedOrder(items.map((item) => item.id))} + /> + +
+ )} +
+
+ ); +} diff --git a/src/stores/preferences/index.tsx b/src/stores/preferences/index.tsx index 211739be..e367be70 100644 --- a/src/stores/preferences/index.tsx +++ b/src/stores/preferences/index.tsx @@ -14,6 +14,8 @@ export interface PreferencesStore { forceCompactEpisodeView: boolean; sourceOrder: string[]; enableSourceOrder: boolean; + embedOrder: string[]; + enableEmbedOrder: boolean; proxyTmdb: boolean; febboxKey: string | null; realDebridKey: string | null; @@ -31,6 +33,8 @@ export interface PreferencesStore { setForceCompactEpisodeView(v: boolean): void; setSourceOrder(v: string[]): void; setEnableSourceOrder(v: boolean): void; + setEmbedOrder(v: string[]): void; + setEnableEmbedOrder(v: boolean): void; setProxyTmdb(v: boolean): void; setFebboxKey(v: string | null): void; setRealDebridKey(v: string | null): void; @@ -52,6 +56,8 @@ export const usePreferencesStore = create( forceCompactEpisodeView: false, sourceOrder: [], enableSourceOrder: false, + embedOrder: [], + enableEmbedOrder: false, proxyTmdb: false, febboxKey: null, realDebridKey: null, @@ -112,6 +118,16 @@ export const usePreferencesStore = create( s.enableSourceOrder = v; }); }, + setEmbedOrder(v) { + set((s) => { + s.embedOrder = v; + }); + }, + setEnableEmbedOrder(v) { + set((s) => { + s.enableEmbedOrder = v; + }); + }, setProxyTmdb(v) { set((s) => { s.proxyTmdb = v;