diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 23891c7..631084e 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -40,8 +40,7 @@ import { } from "../shared/provider-daily-limits"; import { preservePackageOrderForDisplay, sortPackageOrderByName } from "./package-order"; import { pruneSelection, releaseAccountSelectionFocus, resolveEscapeSelectionScope, shouldClearDownloadSelection } from "./selection"; -import { buildConfiguredProviderOrder, buildScopedAccountEnabledState, getAccountDialogSelectableOptions, matchesAccountModeFilter, pruneAccountRowSelections, resolveAccountStatusState, resolveAccountUsername, resolveVisibleAccountKind, runOptimisticAccountUpdate, updateAccountRowSelection } from "./account-ui"; -import type { AccountModeFilter } from "./account-ui"; +import { buildConfiguredProviderOrder, buildScopedAccountEnabledState, filterAccountDialogOptions, getAccountDialogSelectableOptions, pruneAccountRowSelections, resolveAccountStatusState, resolveAccountUsername, resolveVisibleAccountKind, runOptimisticAccountUpdate, updateAccountRowSelection } from "./account-ui"; import { buildAccountDeleteCommand, buildAccountReplaceCommand, buildAccountSecretRequest, createAccountEditState, validateAccountEdit } from "./account-edit"; import type { AccountEditState, AccountEditTarget, AccountKind, AccountService, SingleAccountKind } from "./account-edit"; import { ACCOUNT_SERVICE_ICONS } from "./account-service-icons"; @@ -1614,7 +1613,7 @@ export function App(): ReactElement { const [accountEditSecretVisible, setAccountEditSecretVisible] = useState>({}); const [accountEditSecretBusy, setAccountEditSecretBusy] = useState(null); const [accountDialogSearch, setAccountDialogSearch] = useState(""); - const [accountDialogModeFilter, setAccountDialogModeFilter] = useState("all"); + const [accountDialogServiceFilter, setAccountDialogServiceFilter] = useState("all"); const [keyStatsPopup, setKeyStatsPopup] = useState(null); const [debridLinkHostLimits, setDebridLinkHostLimits] = useState>({}); const [debridLinkHostLimitsLoading, setDebridLinkHostLimitsLoading] = useState(false); @@ -2432,28 +2431,12 @@ export function App(): ReactElement { accountDialog.service ); }, [accountDialog, availableAccountOptions]); - const accountDialogSearchQuery = accountDialogSearch.trim().toLowerCase(); + const accountDialogServiceFilters = useMemo(() => ( + [...new Set(accountDialogSelectableOptions.map((option) => option.serviceLabel))] + ), [accountDialogSelectableOptions]); const filteredAccountDialogOptions = useMemo(() => ( - accountDialogSelectableOptions.filter((option) => { - const matchesMode = accountDialogModeFilter === "all" - || (accountDialogModeFilter === "api" && option.modeLabel === "API") - || (accountDialogModeFilter === "web" && (option.modeLabel.startsWith("Web") || option.kind === "ddownload-login" || option.kind === "linksnappy-login")); - if (!matchesMode) { - return false; - } - if (!accountDialogSearchQuery) { - return true; - } - const haystack = [ - option.title, - option.serviceLabel, - option.modeLabel, - option.pickerDescription, - getAccountPickerFunctionLabel(option) - ].join(" ").toLowerCase(); - return haystack.includes(accountDialogSearchQuery); - }) - ), [accountDialogModeFilter, accountDialogSearchQuery, accountDialogSelectableOptions]); + filterAccountDialogOptions(accountDialogSelectableOptions, accountDialogSearch, accountDialogServiceFilter) + ), [accountDialogSearch, accountDialogSelectableOptions, accountDialogServiceFilter]); const handleUpdateResult = async ( result: UpdateCheckResult, source: "manual" | "startup", @@ -2686,7 +2669,7 @@ export function App(): ReactElement { const openCreateAccountDialog = (): void => { setAccountDialogSearch(""); - setAccountDialogModeFilter("all"); + setAccountDialogServiceFilter("all"); setAccountDialog(createAccountDialogState("create", availableAccountOptions[0]?.kind ?? null, settingsDraft)); }; @@ -2725,7 +2708,7 @@ export function App(): ReactElement { const closeAccountDialog = useCallback((): void => { setAccountDialog(null); setAccountDialogSearch(""); - setAccountDialogModeFilter("all"); + setAccountDialogServiceFilter("all"); }, []); const closeAccountEditDialog = useCallback((): void => { @@ -5186,7 +5169,7 @@ export function App(): ReactElement { updateAccountDialogKind(optionId as AccountKind), onFieldChange: (fieldId, value) => setAccountDialog((current) => current ? { ...current, [fieldId]: value } : current), onClose: closeAccountDialog, @@ -5198,7 +5181,8 @@ export function App(): ReactElement { model={{ open: Boolean(accountDialog), query: accountDialogSearch, - filter: accountDialogModeFilter, + serviceFilter: accountDialogServiceFilter, + serviceFilters: accountDialogServiceFilters, options: accountAddOptions, selectedOptionId: accountDialog?.kind ?? null, fields: accountAddFields, diff --git a/src/renderer/account-ui.ts b/src/renderer/account-ui.ts index 3edb712..f27f2c8 100644 --- a/src/renderer/account-ui.ts +++ b/src/renderer/account-ui.ts @@ -16,6 +16,27 @@ export function matchesAccountModeFilter(option: AccountModeOption, filter: Acco return option.modeLabel.startsWith("Web"); } +export function filterAccountDialogOptions(options: readonly T[], query: string, serviceFilter: string): T[] { + const normalizedQuery = query.trim().toLocaleLowerCase("de-DE"); + return options.filter((option) => { + if (serviceFilter !== "all" && option.serviceLabel !== serviceFilter) { + return false; + } + if (!normalizedQuery) { + return true; + } + return [option.title, option.modeLabel, option.pickerDescription] + .join(" ") + .toLocaleLowerCase("de-DE") + .includes(normalizedQuery); + }); +} + export function buildConfiguredProviderOrder( currentOrder: readonly DebridProvider[], configuredProviders: readonly DebridProvider[] diff --git a/src/renderer/i18n.ts b/src/renderer/i18n.ts index abc4f67..9a54ddf 100644 --- a/src/renderer/i18n.ts +++ b/src/renderer/i18n.ts @@ -75,7 +75,7 @@ const pairs = [ ["Füge einen Account hinzu, um Downloads über einen Anbieter zu starten.", "Add an account to start downloads through a provider."], ["Noch keine Accounts", "No accounts yet"], ["Keine Provider konfiguriert.", "No providers configured."], ["Keine eigenen Zuordnungen.", "No custom assignments."], ["Hoster-Routing hinzufügen", "Add hoster routing"], ["Hoster hinzufügen…", "Add hoster…"], ["Eigener Hoster…", "Custom hoster…"], ["Noch keine Rotations-Ereignisse.", "No rotation events yet."], ["Prüfen und speichern", "Check and save"], ["Wähle einen Dienst und trage die passenden Zugangsdaten ein.", "Choose a service and enter the matching credentials."], ["Accounts durchsuchen", "Search accounts"], - ["Dienst / Zugangstyp", "Service / access type"], ["Dienst", "Service"], ["Typ/Funktion", "Type/function"], ["Dienst oder Zugangstyp suchen", "Search service or access type"], ["Account-Typ filtern", "Filter account type"], ["Verfügbare Account-Typen", "Available account types"], ["Keine passenden Account-Typen.", "No matching account types."], + ["Dienst / Zugangstyp", "Service / access type"], ["Dienst", "Service"], ["Typ/Funktion", "Type/function"], ["Dienst oder Zugangstyp suchen", "Search service or access type"], ["Zugangstyp suchen, z. B. API oder Web", "Search access type, e.g. API or web"], ["Dienst filtern", "Filter service"], ["Alle Dienste", "All services"], ["Account-Typ filtern", "Filter account type"], ["Verfügbare Account-Typen", "Available account types"], ["Keine passenden Account-Typen.", "No matching account types."], ["Prüfen", "Check"], ["Bearbeite ausschließlich den ausgewählten Account.", "Edit only the selected account."], ["Account bearbeiten", "Edit account"], ["Account aktiviert", "Account enabled"], ["Immer erste Tonspur", "Always first audio track"], ["Pro Download", "Per download"], ["Keine Archive löschen", "Do not delete archives"], ["Archive in Papierkorb", "Move archives to recycle bin"], ["Archive löschen", "Delete archives"], ["Accounts und Verwendungsregeln.", "Accounts and usage rules."], ["Premium Account", "Premium account"], ["Zugang ungültig", "Invalid access"], ["Prüft…", "Checking…"], ["Geschützter Zugang", "Protected access"], diff --git a/src/renderer/views/settings/AccountWorkspace.tsx b/src/renderer/views/settings/AccountWorkspace.tsx index 5f3b0d2..97f2930 100644 --- a/src/renderer/views/settings/AccountWorkspace.tsx +++ b/src/renderer/views/settings/AccountWorkspace.tsx @@ -23,7 +23,6 @@ import { getAccountTableMinWidth, getSettingsSelectNavigationIndex, resizeAccountTableColumn, - type AccountAddFilter, type AccountAddOption, type AccountRowViewModel, type AccountTableColumnId, @@ -176,10 +175,11 @@ export interface AccountDialogField { secretBusy?: boolean; } -export interface AccountAddDialogModel { - open: boolean; - query: string; - filter: AccountAddFilter; +export interface AccountAddDialogModel { + open: boolean; + query: string; + serviceFilter: string; + serviceFilters: readonly string[]; options: readonly AccountAddOption[]; selectedOptionId: string | null; fields: readonly AccountDialogField[]; @@ -187,9 +187,9 @@ export interface AccountAddDialogModel { busy: boolean; } -export interface AccountAddDialogActions { - onQueryChange: (value: string) => void; - onFilterChange: (filter: AccountAddFilter) => void; +export interface AccountAddDialogActions { + onQueryChange: (value: string) => void; + onServiceFilterChange: (service: string) => void; onOptionSelect: (optionId: string) => void; onFieldChange: (fieldId: string, value: string) => void; onClose: () => void; @@ -667,16 +667,34 @@ export function AccountAddDialog({ >
Dienst / Zugangstyp - actions.onQueryChange(event.target.value)} - placeholder="Dienst oder Zugangstyp suchen" - type="search" - value={model.query} - /> +
+ actions.onQueryChange(event.target.value)} + onKeyDown={(event) => { + if (event.key !== "Enter" || !model.options[0]) { + return; + } + event.preventDefault(); + actions.onOptionSelect(model.options[0].id); + }} + placeholder="Zugangstyp suchen, z. B. API oder Web" + type="search" + value={model.query} + /> + +