add embed order on admin page

This commit is contained in:
Pas 2025-08-14 09:41:36 -06:00
parent aef4745e61
commit 6a50fec37b
8 changed files with 146 additions and 9 deletions

View file

@ -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 <bold>extension</bold> is required for that source. <br><br> <strong>(The default order is best for most users)</strong>",
"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. <br><br> <strong>(The default order is best for most users)</strong>",
"embedOrderEnableLabel": "Custom embed order"
},
"reset": "Reset",
"save": "Save",

View file

@ -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;

View file

@ -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) {

View file

@ -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<RunOutput | "">(
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,
],
);

View file

@ -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,

View file

@ -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() {
<TMDBTestPart />
<M3U8TestPart />
<RegionSelectorPart />
<EmbedOrderPart />
</ThinContainer>
</SubPageLayout>
);

View file

@ -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 (
<div className="space-y-6">
<Heading2>Embed Order Settings</Heading2>
<div className="flex flex-col gap-3">
<p className="text-white font-bold">
{t("settings.preferences.embedOrder")}
</p>
<div className="max-w-[25rem] font-medium">
<Trans
i18nKey="settings.preferences.embedOrderDescription"
components={{
bold: (
<span
className="text-type-link font-bold cursor-pointer"
onClick={() => navigate("/onboarding/extension")}
/>
),
}}
/>
<div
onClick={() => 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"
>
<Toggle enabled={enableEmbedOrder} />
<p className="flex-1 text-white font-bold">
{t("settings.preferences.embedOrderEnableLabel")}
</p>
</div>
</div>
{enableEmbedOrder && (
<div className="w-full flex flex-col gap-4">
<SortableList
items={embedItems}
setItems={(items) => setEmbedOrder(items.map((item) => item.id))}
/>
<Button
className="max-w-[25rem]"
theme="secondary"
onClick={() => setEmbedOrder(allEmbeds.map((e) => e.id))}
>
{t("settings.reset")}
</Button>
</div>
)}
</div>
</div>
);
}

View file

@ -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;