diff --git a/src/main/events/torrenting/resume-game-download.ts b/src/main/events/torrenting/resume-game-download.ts
index 48bb1c12..4525e2df 100644
--- a/src/main/events/torrenting/resume-game-download.ts
+++ b/src/main/events/torrenting/resume-game-download.ts
@@ -13,7 +13,11 @@ const resumeGameDownload = async (
const download = await downloadsSublevel.get(gameKey);
- if (download?.status === "paused") {
+ if (
+ download &&
+ (download.status === "paused" || download.status === "active") &&
+ download.progress !== 1
+ ) {
await DownloadManager.pauseDownload();
for await (const [key, value] of downloadsSublevel.iterator()) {
diff --git a/src/main/main.ts b/src/main/main.ts
index 1cadcebd..c176efa7 100644
--- a/src/main/main.ts
+++ b/src/main/main.ts
@@ -1,5 +1,5 @@
import { downloadsSublevel } from "./level/sublevels/downloads";
-import { sortBy } from "lodash-es";
+import { orderBy } from "lodash-es";
import { Downloader } from "@shared";
import { levelKeys, db } from "./level";
import type { UserPreferences } from "@types";
@@ -68,7 +68,7 @@ export const loadState = async () => {
.values()
.all()
.then((games) => {
- return sortBy(games, "timestamp", "DESC");
+ return orderBy(games, "timestamp", "desc");
});
downloads.forEach((download) => {
diff --git a/src/main/services/download/download-manager.ts b/src/main/services/download/download-manager.ts
index 4dcebbb0..1a79f8f0 100644
--- a/src/main/services/download/download-manager.ts
+++ b/src/main/services/download/download-manager.ts
@@ -20,7 +20,7 @@ import { RealDebridClient } from "./real-debrid";
import path from "path";
import { logger } from "../logger";
import { db, downloadsSublevel, gamesSublevel, levelKeys } from "@main/level";
-import { sortBy } from "lodash-es";
+import { orderBy } from "lodash-es";
import { TorBoxClient } from "./torbox";
import { GameFilesManager } from "../game-files-manager";
import { HydraDebridClient } from "./hydra-debrid";
@@ -194,10 +194,10 @@ export class DownloadManager {
.values()
.all()
.then((games) => {
- return sortBy(
+ return orderBy(
games.filter((game) => game.status === "paused" && game.queued),
"timestamp",
- "DESC"
+ "desc"
);
});
diff --git a/src/renderer/src/pages/downloads/download-group.scss b/src/renderer/src/pages/downloads/download-group.scss
index 4fd6ec02..0b9deea3 100644
--- a/src/renderer/src/pages/downloads/download-group.scss
+++ b/src/renderer/src/pages/downloads/download-group.scss
@@ -109,21 +109,15 @@
display: flex;
align-items: center;
transition: opacity 0.2s ease;
+ outline: none;
&:hover {
opacity: 0.8;
}
- &:focus {
- outline: 2px solid rgba(255, 255, 255, 0.5);
- outline-offset: 4px;
- border-radius: 4px;
- }
-
+ &:focus,
&:focus-visible {
- outline: 2px solid rgba(255, 255, 255, 0.5);
- outline-offset: 4px;
- border-radius: 4px;
+ outline: none;
}
}
@@ -205,7 +199,7 @@
&__hero-action-row {
display: flex;
justify-content: space-between;
- align-items: flex-end;
+ align-items: flex-start;
gap: calc(globals.$spacing-unit * 3);
margin-top: calc(globals.$spacing-unit * 4);
margin-bottom: calc(globals.$spacing-unit * 2);
diff --git a/src/renderer/src/pages/downloads/download-group.tsx b/src/renderer/src/pages/downloads/download-group.tsx
index a202af36..bcecbc7c 100644
--- a/src/renderer/src/pages/downloads/download-group.tsx
+++ b/src/renderer/src/pages/downloads/download-group.tsx
@@ -397,6 +397,14 @@ function HeroDownloadView({
)}
+
+ {game.download?.downloader && (
+
+
+ {DOWNLOADER_NAME[game.download.downloader]}
+
+
+ )}
@@ -437,14 +445,54 @@ export function DownloadGroup({
const {
lastPacket,
- pauseDownload,
- resumeDownload,
+ pauseDownload: pauseDownloadOriginal,
+ resumeDownload: resumeDownloadOriginal,
cancelDownload,
isGameDeleting,
pauseSeeding,
resumeSeeding,
} = useDownload();
+ // Wrap resumeDownload with optimistic update
+ const resumeDownload = useCallback(
+ async (shop: GameShop, objectId: string) => {
+ const gameId = `${shop}:${objectId}`;
+
+ // Optimistically mark as downloading
+ setOptimisticallyResumed((prev) => ({ ...prev, [gameId]: true }));
+
+ try {
+ await resumeDownloadOriginal(shop, objectId);
+ } catch (error) {
+ // If resume fails, remove optimistic state
+ setOptimisticallyResumed((prev) => {
+ const next = { ...prev };
+ delete next[gameId];
+ return next;
+ });
+ throw error;
+ }
+ },
+ [resumeDownloadOriginal]
+ );
+
+ // Wrap pauseDownload to clear optimistic state
+ const pauseDownload = useCallback(
+ async (shop: GameShop, objectId: string) => {
+ const gameId = `${shop}:${objectId}`;
+
+ // Clear optimistic state when pausing
+ setOptimisticallyResumed((prev) => {
+ const next = { ...prev };
+ delete next[gameId];
+ return next;
+ });
+
+ await pauseDownloadOriginal(shop, objectId);
+ },
+ [pauseDownloadOriginal]
+ );
+
const { formatDistance } = useDate();
const [peakSpeeds, setPeakSpeeds] = useState
>({});
@@ -452,6 +500,9 @@ export function DownloadGroup({
const [dominantColors, setDominantColors] = useState>(
{}
);
+ const [optimisticallyResumed, setOptimisticallyResumed] = useState<
+ Record
+ >({});
const extractDominantColor = useCallback(
async (imageUrl: string, gameId: string) => {
@@ -469,6 +520,45 @@ export function DownloadGroup({
[dominantColors]
);
+ // Clear optimistic state when actual download starts or library updates
+ useEffect(() => {
+ if (lastPacket?.gameId) {
+ const gameId = lastPacket.gameId;
+
+ // Clear optimistic state when actual download starts
+ setOptimisticallyResumed((prev) => {
+ const next = { ...prev };
+ delete next[gameId];
+ return next;
+ });
+ }
+ }, [lastPacket?.gameId]);
+
+ // Clear optimistic state for games that are no longer active after library update
+ useEffect(() => {
+ setOptimisticallyResumed((prev) => {
+ const next = { ...prev };
+ let changed = false;
+
+ for (const gameId in next) {
+ if (next[gameId]) {
+ const game = library.find((g) => g.id === gameId);
+ // Clear if game doesn't exist or download status is not active
+ if (
+ !game ||
+ game.download?.status !== "active" ||
+ lastPacket?.gameId === gameId
+ ) {
+ delete next[gameId];
+ changed = true;
+ }
+ }
+ }
+
+ return changed ? next : prev;
+ });
+ }, [library, lastPacket?.gameId]);
+
useEffect(() => {
if (lastPacket?.gameId && lastPacket.downloadSpeed !== undefined) {
const gameId = lastPacket.gameId;
@@ -552,10 +642,12 @@ export function DownloadGroup({
const isGameDownloadingMap = useMemo(() => {
const map: Record = {};
for (const game of library) {
- map[game.id] = lastPacket?.gameId === game.id;
+ map[game.id] =
+ lastPacket?.gameId === game.id ||
+ optimisticallyResumed[game.id] === true;
}
return map;
- }, [library, lastPacket?.gameId]);
+ }, [library, lastPacket?.gameId, optimisticallyResumed]);
const getFinalDownloadSize = (game: LibraryGame) => {
const download = game.download!;
@@ -830,7 +922,7 @@ export function DownloadGroup({
disabled={isGameDeleting(game.id)}
className="download-group__simple-menu-btn"
>
-
+
)}
{isQueuedGroup && game.download?.progress !== 1 && (