From 9d3d62740142f94e31dedf3f61ce6713a6b8a9cd Mon Sep 17 00:00:00 2001 From: Sucukdeluxe Date: Tue, 25 Aug 2026 05:16:20 +0200 Subject: [PATCH] feat(settings): label provider order with account modes --- src/renderer/App.tsx | 31 ++++++++++++------ .../views/settings/AccountWorkspace.tsx | 27 +++++++++------- src/renderer/views/settings/settings.css | 13 ++++++++ tests/settings-view.test.tsx | 32 +++++++++++++++++-- 4 files changed, 79 insertions(+), 24 deletions(-) diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 8e3b385..9626de6 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -975,16 +975,27 @@ const KNOWN_HOSTERS: { id: string; label: string }[] = [ { id: "isra", label: "Isra.cloud" } ]; +export function buildProviderOrderEntry(provider: DebridProvider, settings: RendererSettings): { + id: DebridProvider; + label: string; + icon: string; +} { + const legacyMegaService = settings.megaDebridPreferApi ? "megadebrid-api" : "megadebrid-web"; + const service = provider === "megadebrid" ? legacyMegaService : provider as AccountService; + const kind = getConfiguredAccountKind(settings, service); + const option = kind ? ACCOUNT_OPTIONS.find((entry) => entry.kind === kind) : null; + const modeLabel = option?.modeLabel || (provider === "megadebrid" ? (settings.megaDebridPreferApi ? "API" : "Web-Login") : ""); + const serviceLabel = option?.serviceLabel || providerLabels[provider]; + return { + id: provider, + label: modeLabel ? `${serviceLabel} (${modeLabel})` : serviceLabel, + icon: ACCOUNT_SERVICE_ICONS[option?.service || service] + }; +} + function providerLabelWithMode(provider: DebridProvider, settings: RendererSettings): string { - const base = providerLabels[provider]; - if (provider === "megadebrid" || provider === "megadebrid-api" || provider === "megadebrid-web") { - return base; - } - const kind = getConfiguredAccountKind(settings, provider as AccountService); - if (!kind) return base; - const opt = ACCOUNT_OPTIONS.find((o) => o.kind === kind); - return opt?.modeLabel ? `${base} (${opt.modeLabel})` : base; -} + return buildProviderOrderEntry(provider, settings).label; +} function formatAllDebridSourceLabel(source: AllDebridHostInfo["source"]): string { return source === "web" ? "Web-Login" : "API-Key"; @@ -5586,7 +5597,7 @@ export function App(): ReactElement { statusSort: accountStatusSort, runtime: accountRuntimeModel, rules: { - providerOrder: activeProviderOrder.map((provider) => providerLabelWithMode(provider, settingsDraft)), + providerOrder: activeProviderOrder.map((provider) => buildProviderOrderEntry(provider, settingsDraft)), routing: routingEntries.map(([hosterId, provider]) => `${KNOWN_HOSTERS.find((hoster) => hoster.id === hosterId)?.label || hosterId} → ${providerLabelWithMode(provider, settingsDraft)}`), autoFallback: settingsDraft.autoProviderFallback, rememberCredentials: settingsDraft.rememberToken, diff --git a/src/renderer/views/settings/AccountWorkspace.tsx b/src/renderer/views/settings/AccountWorkspace.tsx index d036cba..ec31c63 100644 --- a/src/renderer/views/settings/AccountWorkspace.tsx +++ b/src/renderer/views/settings/AccountWorkspace.tsx @@ -81,7 +81,7 @@ function getAccountPanelNavigationIndex(currentIndex: number, key: string): numb } export interface AccountRulesViewModel { - providerOrder: readonly string[]; + providerOrder: readonly { id: string; label: string; icon: string }[]; routing: readonly string[]; autoFallback: boolean; rememberCredentials?: boolean; @@ -505,21 +505,24 @@ function AccountRules({ model, actions }: AccountWorkspaceProps): ReactElement { Keine Provider konfiguriert. ) : (
    - {model.rules.providerOrder.map((provider, index) => ( -
  1. ( +
  2. actions.onProviderDragOver?.(event, index)} onDragStart={(event) => actions.onProviderDragStart?.(event, index)} onDrop={(event) => actions.onProviderDrop?.(event, index)} - > - {provider} - {actions.onMoveProvider ? ( - - - - + > + + + {provider.label} + + {actions.onMoveProvider ? ( + + + + ) : null}
  3. ))} diff --git a/src/renderer/views/settings/settings.css b/src/renderer/views/settings/settings.css index eda0b1c..214bec5 100644 --- a/src/renderer/views/settings/settings.css +++ b/src/renderer/views/settings/settings.css @@ -1067,6 +1067,19 @@ gap: 4px; } +.settings-provider-order-provider { + display: inline-flex; + align-items: center; + min-width: 0; + gap: 9px; + color: var(--ui-text); +} + +.settings-provider-order-provider img { + flex: 0 0 auto; + object-fit: contain; +} + .settings-provider-order-actions button { width: 30px; height: 28px; diff --git a/tests/settings-view.test.tsx b/tests/settings-view.test.tsx index f2a1575..4da0708 100644 --- a/tests/settings-view.test.tsx +++ b/tests/settings-view.test.tsx @@ -4,7 +4,7 @@ import { renderToStaticMarkup } from "react-dom/server"; import { describe, expect, it } from "vitest"; import { defaultSettings } from "../src/main/constants"; import { createRendererSettings, createRendererState } from "../src/main/renderer-state"; -import { buildAccountAddFields, buildAccountCreateProviderOrderUpdate, createAccountDialogState, createDiscardedSettingsState, createSettingsDraft, resolveSettingsSaveCompletion } from "../src/renderer/App"; +import { buildAccountAddFields, buildAccountCreateProviderOrderUpdate, buildProviderOrderEntry, createAccountDialogState, createDiscardedSettingsState, createSettingsDraft, resolveSettingsSaveCompletion } from "../src/renderer/App"; import { buildAccountReplaceCommand, createAccountEditState, type AccountEditTarget } from "../src/renderer/account-edit"; import { buildScopedAccountEnabledState, @@ -292,7 +292,10 @@ function workspaceModel(): AccountWorkspaceViewModel { selectedIds: [buildAccountRowId("megadebrid-api", "API", "mega-premium")], busy: false, rules: { - providerOrder: ["Debrid-Link", "Real-Debrid"], + providerOrder: [ + { id: "debridlink", label: "Debrid-Link (API)", icon: "./provider-icons/debrid-link.ico" }, + { id: "realdebrid", label: "Real-Debrid (Web-Login)", icon: "./provider-icons/real-debrid.png" } + ], routing: ["rapidgator.net → Debrid-Link"], autoFallback: true }, @@ -1146,6 +1149,10 @@ describe("account workspace", () => { expect(html).toContain("Hoster-Routing"); expect(html).toContain("Automatischer Fallback"); expect(html).toContain("Provider-Laufzeit"); + expect(html).toContain("Debrid-Link (API)"); + expect(html).toContain("Real-Debrid (Web-Login)"); + expect(html).toContain("./provider-icons/debrid-link.ico"); + expect(html).toContain("./provider-icons/real-debrid.png"); }); it("keeps add and edit dialogs separate and every secret field protected", () => { @@ -1347,6 +1354,27 @@ describe("account workspace", () => { }); describe("settings App integration", () => { + it("projects provider order entries with logos and explicit login modes", () => { + const settings = createRendererSettings({ + ...defaultSettings(), + megaDebridWebEnabled: true, + megaDebridWebCredentials: "member:password", + deepbridApiKey: "test-key", + providerOrder: ["megadebrid-web", "deepbrid"] + }); + + expect(buildProviderOrderEntry("megadebrid-web", settings)).toEqual({ + id: "megadebrid-web", + label: "Mega-Debrid (Web-Login)", + icon: "./provider-icons/mega-debrid.png" + }); + expect(buildProviderOrderEntry("deepbrid", settings)).toEqual({ + id: "deepbrid", + label: "Deepbrid (API)", + icon: "./provider-icons/deepbrid.png" + }); + }); + it("preserves the write-only webhook during live snapshots and clears it for backup reseeding", () => { const safe = createRendererSettings({ ...defaultSettings(), notifyUrl: "https://private.example.test/hook" }); const current = { ...safe, archivePasswordList: "loaded-password", notifyUrl: "https://private.example.test/hook" };