From 62f3bd94deb74519421e20ffabee931bc6abcf84 Mon Sep 17 00:00:00 2001 From: Sucukdeluxe Date: Tue, 3 Mar 2026 23:55:42 +0100 Subject: [PATCH] Remove speed limit toolbar button, fix hoster stats grouping, add Ctrl+A select all, fix context menu clipping Co-Authored-By: Claude Sonnet 4.6 --- package.json | 2 +- src/renderer/App.tsx | 58 ++++++++++++++++++++++++++------------------ 2 files changed, 35 insertions(+), 25 deletions(-) diff --git a/package.json b/package.json index e762401..e5f8071 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "real-debrid-downloader", - "version": "1.5.79", + "version": "1.5.80", "description": "Real-Debrid Downloader Desktop (Electron + React + TypeScript)", "main": "build/main/main/main.js", "author": "Sucukdeluxe", diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 7dd53f3..e4d634c 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -1,4 +1,4 @@ -import { DragEvent, KeyboardEvent, ReactElement, memo, useCallback, useDeferredValue, useEffect, useMemo, useRef, useState } from "react"; +import { DragEvent, KeyboardEvent, ReactElement, memo, useCallback, useDeferredValue, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react"; import type { AppSettings, AppTheme, @@ -97,7 +97,7 @@ function extractHoster(url: string): string { try { const host = new URL(url).hostname.replace(/^www\./, ""); const parts = host.split("."); - return parts.length >= 2 ? parts.slice(-2).join(".") : host; + return parts.length >= 2 ? parts[parts.length - 2] : host; } catch { return ""; } } @@ -466,6 +466,8 @@ export function App(): ReactElement { const latestStateRef = useRef(null); const snapshotRef = useRef(snapshot); snapshotRef.current = snapshot; + const tabRef = useRef(tab); + tabRef.current = tab; const stateFlushTimerRef = useRef | null>(null); const toastTimerRef = useRef | null>(null); const [dragOver, setDragOver] = useState(false); @@ -504,6 +506,7 @@ export function App(): ReactElement { const confirmQueueRef = useRef void }>>([]); const importQueueFocusHandlerRef = useRef<(() => void) | null>(null); const [contextMenu, setContextMenu] = useState(null); + const ctxMenuRef = useRef(null); const [linkPopup, setLinkPopup] = useState(null); const [selectedIds, setSelectedIds] = useState>(new Set()); const [deleteConfirm, setDeleteConfirm] = useState<{ ids: Set; dontAsk: boolean } | null>(null); @@ -1648,6 +1651,18 @@ export function App(): ReactElement { }; }, [contextMenu]); + useLayoutEffect(() => { + if (!contextMenu || !ctxMenuRef.current) return; + const el = ctxMenuRef.current; + const rect = el.getBoundingClientRect(); + if (rect.bottom > window.innerHeight) { + el.style.top = `${Math.max(0, contextMenu.y - rect.height)}px`; + } + if (rect.right > window.innerWidth) { + el.style.left = `${Math.max(0, contextMenu.x - rect.width)}px`; + } + }, [contextMenu]); + const executeDeleteSelection = useCallback((ids: Set): void => { const current = snapshotRef.current; for (const id of ids) { @@ -1753,6 +1768,13 @@ export function App(): ReactElement { void onImportDlc(); return; } + if (!e.shiftKey && e.key.toLowerCase() === "a") { + if (tabRef.current === "downloads") { + e.preventDefault(); + setSelectedIds(new Set(Object.keys(snapshotRef.current.session.packages))); + } + return; + } } }; window.addEventListener("keydown", handler); @@ -1797,14 +1819,14 @@ export function App(): ReactElement { const providerStats = useMemo(() => { const stats: Record = {}; for (const item of Object.values(snapshot.session.items)) { - const provider = item.provider || "unknown"; - if (!stats[provider]) { - stats[provider] = { total: 0, completed: 0, failed: 0, bytes: 0 }; + const hoster = extractHoster(item.url) || "unknown"; + if (!stats[hoster]) { + stats[hoster] = { total: 0, completed: 0, failed: 0, bytes: 0 }; } - stats[provider].total += 1; - if (item.status === "completed") stats[provider].completed += 1; - if (item.status === "failed") stats[provider].failed += 1; - stats[provider].bytes += item.downloadedBytes; + stats[hoster].total += 1; + if (item.status === "completed") stats[hoster].completed += 1; + if (item.status === "failed") stats[hoster].failed += 1; + stats[hoster].bytes += item.downloadedBytes; } return Object.entries(stats); }, [snapshot.session.items]); @@ -2030,18 +2052,6 @@ export function App(): ReactElement { > -
-
{snapshot.reconnectSeconds > 0 && (
Reconnect: {snapshot.reconnectSeconds}s
@@ -2351,11 +2361,11 @@ export function App(): ReactElement {
-

Provider-Statistik

+

Hoster-Statistik

{providerStats.map(([provider, stats]) => (
- {provider === "unknown" ? "Unbekannt" : providerLabels[provider as DebridProvider] || provider} + {provider === "unknown" ? "Unbekannt" : provider}
0 ? (stats.completed / stats.total) * 100 : 0}%` }} /> @@ -2686,7 +2696,7 @@ export function App(): ReactElement { const hasPackages = [...selectedIds].some((id) => snapshot.session.packages[id]); const hasItems = [...selectedIds].some((id) => snapshot.session.items[id]); return ( -
e.stopPropagation()}> +
e.stopPropagation()}> {(!contextMenu.itemId || multi) && hasPackages && (