From be7a8fd1033c7648d5c68ca16547abf3da73de8b Mon Sep 17 00:00:00 2001 From: Sucukdeluxe Date: Tue, 3 Mar 2026 20:42:28 +0100 Subject: [PATCH] Add progress sorting, extraction priority by packageOrder, auto-expand extracting packages - Fortschritt column is now clickable/sortable (ascending/descending by package %) - Extraction queue respects packageOrder: top packages get extracted first - Packages currently extracting are auto-expanded so user can see progress - Increased Fortschritt column width for better spacing Co-Authored-By: Claude Opus 4.6 --- src/main/download-manager.ts | 28 ++++++++++++----- src/renderer/App.tsx | 58 +++++++++++++++++++++++++++++++----- src/renderer/styles.css | 6 ++-- 3 files changed, 74 insertions(+), 18 deletions(-) diff --git a/src/main/download-manager.ts b/src/main/download-manager.ts index 6ad9d3a..e710c2f 100644 --- a/src/main/download-manager.ts +++ b/src/main/download-manager.ts @@ -775,7 +775,7 @@ export class DownloadManager extends EventEmitter { private packagePostProcessActive = 0; - private packagePostProcessWaiters: Array<() => void> = []; + private packagePostProcessWaiters: Array<{ packageId: string; resolve: () => void }> = []; private packagePostProcessTasks = new Map>(); @@ -1175,7 +1175,7 @@ export class DownloadManager extends EventEmitter { this.hybridExtractRequeue.clear(); this.packagePostProcessQueue = Promise.resolve(); this.packagePostProcessActive = 0; - for (const waiter of this.packagePostProcessWaiters) { waiter(); } + for (const waiter of this.packagePostProcessWaiters) { waiter.resolve(); } this.packagePostProcessWaiters = []; this.summary = null; this.nonResumableActive = 0; @@ -2996,24 +2996,36 @@ export class DownloadManager extends EventEmitter { } } - private async acquirePostProcessSlot(): Promise { + private async acquirePostProcessSlot(packageId: string): Promise { const maxConcurrent = this.settings.maxParallelExtract || 2; if (this.packagePostProcessActive < maxConcurrent) { this.packagePostProcessActive += 1; return; } await new Promise((resolve) => { - this.packagePostProcessWaiters.push(resolve); + this.packagePostProcessWaiters.push({ packageId, resolve }); }); this.packagePostProcessActive += 1; } private releasePostProcessSlot(): void { this.packagePostProcessActive -= 1; - const next = this.packagePostProcessWaiters.shift(); - if (next) { - next(); + if (this.packagePostProcessWaiters.length === 0) return; + // Pick the waiter whose package appears earliest in packageOrder + const order = this.session.packageOrder; + let bestIdx = 0; + let bestOrder = order.indexOf(this.packagePostProcessWaiters[0].packageId); + if (bestOrder === -1) bestOrder = Infinity; + for (let i = 1; i < this.packagePostProcessWaiters.length; i++) { + let pos = order.indexOf(this.packagePostProcessWaiters[i].packageId); + if (pos === -1) pos = Infinity; + if (pos < bestOrder) { + bestOrder = pos; + bestIdx = i; + } } + const [next] = this.packagePostProcessWaiters.splice(bestIdx, 1); + next.resolve(); } private runPackagePostProcessing(packageId: string): Promise { @@ -3027,7 +3039,7 @@ export class DownloadManager extends EventEmitter { this.packagePostProcessAbortControllers.set(packageId, abortController); const task = (async () => { - await this.acquirePostProcessSlot(); + await this.acquirePostProcessSlot(packageId); try { await this.handlePackagePostProcessing(packageId, abortController.signal); } catch (error) { diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 7c42338..ab78b06 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -374,7 +374,33 @@ function sortPackageOrderByHoster(order: string[], packages: Record, items: Record, descending: boolean): string[] { + const sorted = [...order]; + sorted.sort((a, b) => { + const progressA = computePackageProgress(packages[a], items); + const progressB = computePackageProgress(packages[b], items); + const cmp = progressA - progressB; + return descending ? -cmp : cmp; + }); + return sorted; +} + +function computePackageProgress(pkg: PackageEntry | undefined, items: Record): number { + if (!pkg) return 0; + const ids = pkg.itemIds ?? []; + if (ids.length === 0) return 0; + let totalDown = 0; + let totalSize = 0; + for (const id of ids) { + const item = items[id]; + if (!item) continue; + totalDown += item.downloadedBytes || 0; + totalSize += item.totalBytes || item.downloadedBytes || 0; + } + return totalSize > 0 ? totalDown / totalSize : 0; +} + +type PkgSortColumn = "name" | "size" | "hoster" | "progress"; function sameStringArray(a: string[], b: string[]): boolean { if (a.length !== b.length) { @@ -808,6 +834,25 @@ export function App(): ReactElement { } }, [snapshot.session.running]); + // Auto-expand packages that are currently extracting + useEffect(() => { + const extractingPkgIds: string[] = []; + for (const pkg of packages) { + if (collapsedPackages[pkg.id]) { + const items = (pkg.itemIds ?? []).map((id) => snapshot.session.items[id]).filter(Boolean); + const isExtracting = items.some((item) => item.fullStatus?.startsWith("Entpacken -") && !item.fullStatus?.includes("Done")); + if (isExtracting) extractingPkgIds.push(pkg.id); + } + } + if (extractingPkgIds.length > 0) { + setCollapsedPackages((prev) => { + const next = { ...prev }; + for (const id of extractingPkgIds) next[id] = false; + return next; + }); + } + }, [packages, snapshot.session.items]); + const allPackagesCollapsed = useMemo(() => ( packages.length > 0 && packages.every((pkg) => collapsedPackages[pkg.id]) ), [packages, collapsedPackages]); @@ -2105,13 +2150,10 @@ export function App(): ReactElement {
- {(["name", "size", "hoster"] as PkgSortColumn[]).flatMap((col) => { - const labels: Record = { name: "Name", size: "Geladen / Größe", hoster: "Hoster" }; + {(["name", "progress", "size", "hoster"] as PkgSortColumn[]).flatMap((col) => { + const labels: Record = { name: "Name", progress: "Fortschritt", size: "Geladen / Größe", hoster: "Hoster" }; const isActive = downloadsSortColumn === col; - const before: React.ReactNode[] = []; - if (col === "size") before.push(Fortschritt); return [ - ...before, 0 ? packageOrderRef.current : snapshot.session.packageOrder; let sorted: string[]; - if (col === "size") { + if (col === "progress") { + sorted = sortPackageOrderByProgress(baseOrder, snapshot.session.packages, snapshot.session.items, nextDesc); + } else if (col === "size") { sorted = sortPackageOrderBySize(baseOrder, snapshot.session.packages, snapshot.session.items, nextDesc); } else if (col === "hoster") { sorted = sortPackageOrderByHoster(baseOrder, snapshot.session.packages, snapshot.session.items, nextDesc); diff --git a/src/renderer/styles.css b/src/renderer/styles.css index 7359290..dc24ed3 100644 --- a/src/renderer/styles.css +++ b/src/renderer/styles.css @@ -577,7 +577,7 @@ body, .pkg-column-header { display: grid; - grid-template-columns: 1fr 70px 160px 220px 180px 100px; + grid-template-columns: 1fr 90px 160px 220px 180px 100px; gap: 8px; padding: 5px 12px; background: var(--card); @@ -603,7 +603,7 @@ body, .pkg-columns { display: grid; - grid-template-columns: 1fr 70px 160px 220px 180px 100px; + grid-template-columns: 1fr 90px 160px 220px 180px 100px; gap: 8px; align-items: center; min-width: 0; @@ -1272,7 +1272,7 @@ td { .item-row { display: grid; - grid-template-columns: 1fr 70px 160px 220px 180px 100px; + grid-template-columns: 1fr 90px 160px 220px 180px 100px; gap: 8px; align-items: center; margin: 0 -12px;