mirror of
https://github.com/hydralauncher/hydra.git
synced 2026-01-30 06:11:03 +00:00
refactor: add cancel download confirmation modal and enhance download management in DownloadGroup
This commit is contained in:
@@ -404,6 +404,9 @@
|
|||||||
"completed": "Completed",
|
"completed": "Completed",
|
||||||
"removed": "Not downloaded",
|
"removed": "Not downloaded",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
|
"cancel_download": "Cancel download",
|
||||||
|
"cancel_download_description": "Are you sure you want to cancel this download? This will delete all downloaded files.",
|
||||||
|
"keep_downloading": "Keep downloading",
|
||||||
"filter": "Filter downloaded games",
|
"filter": "Filter downloaded games",
|
||||||
"remove": "Remove",
|
"remove": "Remove",
|
||||||
"downloading_metadata": "Downloading metadata…",
|
"downloading_metadata": "Downloading metadata…",
|
||||||
|
|||||||
@@ -34,7 +34,9 @@ export const loadState = async () => {
|
|||||||
|
|
||||||
await import("./events");
|
await import("./events");
|
||||||
|
|
||||||
if (!userPreferences?.useNativeHttpDownloader) {
|
// Only spawn aria2 if user explicitly disabled native HTTP downloader
|
||||||
|
// Default is to use native HTTP downloader (aria2 is opt-in)
|
||||||
|
if (userPreferences?.useNativeHttpDownloader === false) {
|
||||||
Aria2.spawn();
|
Aria2.spawn();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,8 +126,9 @@ export const loadState = async () => {
|
|||||||
|
|
||||||
// For torrents or if JS downloader is disabled, use Python RPC
|
// For torrents or if JS downloader is disabled, use Python RPC
|
||||||
const isTorrent = downloadToResume?.downloader === Downloader.Torrent;
|
const isTorrent = downloadToResume?.downloader === Downloader.Torrent;
|
||||||
|
// Default to true - native HTTP downloader is enabled by default
|
||||||
const useJsDownloader =
|
const useJsDownloader =
|
||||||
userPreferences?.useNativeHttpDownloader && !isTorrent;
|
(userPreferences?.useNativeHttpDownloader ?? true) && !isTorrent;
|
||||||
|
|
||||||
if (useJsDownloader && downloadToResume) {
|
if (useJsDownloader && downloadToResume) {
|
||||||
// Start Python RPC for seeding only, then resume HTTP download with JS
|
// Start Python RPC for seeding only, then resume HTTP download with JS
|
||||||
|
|||||||
@@ -111,7 +111,8 @@ export class DownloadManager {
|
|||||||
levelKeys.userPreferences,
|
levelKeys.userPreferences,
|
||||||
{ valueEncoding: "json" }
|
{ valueEncoding: "json" }
|
||||||
);
|
);
|
||||||
return userPreferences?.useNativeHttpDownloader ?? false;
|
// Default to true - native HTTP downloader is enabled by default (opt-out)
|
||||||
|
return userPreferences?.useNativeHttpDownloader ?? true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static isHttpDownloader(downloader: Downloader): boolean {
|
private static isHttpDownloader(downloader: Downloader): boolean {
|
||||||
@@ -483,6 +484,9 @@ export class DownloadManager {
|
|||||||
filename?: string;
|
filename?: string;
|
||||||
headers?: Record<string, string>;
|
headers?: Record<string, string>;
|
||||||
} | null> {
|
} | null> {
|
||||||
|
// If resuming and we already have a folderName, use it to ensure we find the partial file
|
||||||
|
const resumingFilename = download.folderName || undefined;
|
||||||
|
|
||||||
switch (download.downloader) {
|
switch (download.downloader) {
|
||||||
case Downloader.Gofile: {
|
case Downloader.Gofile: {
|
||||||
const id = download.uri.split("/").pop();
|
const id = download.uri.split("/").pop();
|
||||||
@@ -490,6 +494,7 @@ export class DownloadManager {
|
|||||||
const downloadLink = await GofileApi.getDownloadLink(id!);
|
const downloadLink = await GofileApi.getDownloadLink(id!);
|
||||||
await GofileApi.checkDownloadUrl(downloadLink);
|
await GofileApi.checkDownloadUrl(downloadLink);
|
||||||
const filename =
|
const filename =
|
||||||
|
resumingFilename ||
|
||||||
this.extractFilename(download.uri, downloadLink) ||
|
this.extractFilename(download.uri, downloadLink) ||
|
||||||
this.extractFilename(downloadLink);
|
this.extractFilename(downloadLink);
|
||||||
|
|
||||||
@@ -504,6 +509,7 @@ export class DownloadManager {
|
|||||||
const id = download.uri.split("/").pop();
|
const id = download.uri.split("/").pop();
|
||||||
const downloadUrl = await PixelDrainApi.getDownloadUrl(id!);
|
const downloadUrl = await PixelDrainApi.getDownloadUrl(id!);
|
||||||
const filename =
|
const filename =
|
||||||
|
resumingFilename ||
|
||||||
this.extractFilename(download.uri, downloadUrl) ||
|
this.extractFilename(download.uri, downloadUrl) ||
|
||||||
this.extractFilename(downloadUrl);
|
this.extractFilename(downloadUrl);
|
||||||
|
|
||||||
@@ -516,6 +522,7 @@ export class DownloadManager {
|
|||||||
case Downloader.Qiwi: {
|
case Downloader.Qiwi: {
|
||||||
const downloadUrl = await QiwiApi.getDownloadUrl(download.uri);
|
const downloadUrl = await QiwiApi.getDownloadUrl(download.uri);
|
||||||
const filename =
|
const filename =
|
||||||
|
resumingFilename ||
|
||||||
this.extractFilename(download.uri, downloadUrl) ||
|
this.extractFilename(download.uri, downloadUrl) ||
|
||||||
this.extractFilename(downloadUrl);
|
this.extractFilename(downloadUrl);
|
||||||
|
|
||||||
@@ -528,6 +535,7 @@ export class DownloadManager {
|
|||||||
case Downloader.Datanodes: {
|
case Downloader.Datanodes: {
|
||||||
const downloadUrl = await DatanodesApi.getDownloadUrl(download.uri);
|
const downloadUrl = await DatanodesApi.getDownloadUrl(download.uri);
|
||||||
const filename =
|
const filename =
|
||||||
|
resumingFilename ||
|
||||||
this.extractFilename(download.uri, downloadUrl) ||
|
this.extractFilename(download.uri, downloadUrl) ||
|
||||||
this.extractFilename(downloadUrl);
|
this.extractFilename(downloadUrl);
|
||||||
|
|
||||||
@@ -543,6 +551,7 @@ export class DownloadManager {
|
|||||||
);
|
);
|
||||||
const directUrl = await BuzzheavierApi.getDirectLink(download.uri);
|
const directUrl = await BuzzheavierApi.getDirectLink(download.uri);
|
||||||
const filename =
|
const filename =
|
||||||
|
resumingFilename ||
|
||||||
this.extractFilename(download.uri, directUrl) ||
|
this.extractFilename(download.uri, directUrl) ||
|
||||||
this.extractFilename(directUrl);
|
this.extractFilename(directUrl);
|
||||||
|
|
||||||
@@ -558,6 +567,7 @@ export class DownloadManager {
|
|||||||
);
|
);
|
||||||
const directUrl = await FuckingFastApi.getDirectLink(download.uri);
|
const directUrl = await FuckingFastApi.getDirectLink(download.uri);
|
||||||
const filename =
|
const filename =
|
||||||
|
resumingFilename ||
|
||||||
this.extractFilename(download.uri, directUrl) ||
|
this.extractFilename(download.uri, directUrl) ||
|
||||||
this.extractFilename(directUrl);
|
this.extractFilename(directUrl);
|
||||||
|
|
||||||
@@ -570,6 +580,7 @@ export class DownloadManager {
|
|||||||
case Downloader.Mediafire: {
|
case Downloader.Mediafire: {
|
||||||
const downloadUrl = await MediafireApi.getDownloadUrl(download.uri);
|
const downloadUrl = await MediafireApi.getDownloadUrl(download.uri);
|
||||||
const filename =
|
const filename =
|
||||||
|
resumingFilename ||
|
||||||
this.extractFilename(download.uri, downloadUrl) ||
|
this.extractFilename(download.uri, downloadUrl) ||
|
||||||
this.extractFilename(downloadUrl);
|
this.extractFilename(downloadUrl);
|
||||||
|
|
||||||
@@ -583,6 +594,7 @@ export class DownloadManager {
|
|||||||
const downloadUrl = await RealDebridClient.getDownloadUrl(download.uri);
|
const downloadUrl = await RealDebridClient.getDownloadUrl(download.uri);
|
||||||
if (!downloadUrl) throw new Error(DownloadError.NotCachedOnRealDebrid);
|
if (!downloadUrl) throw new Error(DownloadError.NotCachedOnRealDebrid);
|
||||||
const filename =
|
const filename =
|
||||||
|
resumingFilename ||
|
||||||
this.extractFilename(download.uri, downloadUrl) ||
|
this.extractFilename(download.uri, downloadUrl) ||
|
||||||
this.extractFilename(downloadUrl);
|
this.extractFilename(downloadUrl);
|
||||||
|
|
||||||
@@ -599,7 +611,7 @@ export class DownloadManager {
|
|||||||
return {
|
return {
|
||||||
url,
|
url,
|
||||||
savePath: download.downloadPath,
|
savePath: download.downloadPath,
|
||||||
filename: name,
|
filename: resumingFilename || name,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
case Downloader.Hydra: {
|
case Downloader.Hydra: {
|
||||||
@@ -608,6 +620,7 @@ export class DownloadManager {
|
|||||||
);
|
);
|
||||||
if (!downloadUrl) throw new Error(DownloadError.NotCachedOnHydra);
|
if (!downloadUrl) throw new Error(DownloadError.NotCachedOnHydra);
|
||||||
const filename =
|
const filename =
|
||||||
|
resumingFilename ||
|
||||||
this.extractFilename(download.uri, downloadUrl) ||
|
this.extractFilename(download.uri, downloadUrl) ||
|
||||||
this.extractFilename(downloadUrl);
|
this.extractFilename(downloadUrl);
|
||||||
|
|
||||||
@@ -623,6 +636,7 @@ export class DownloadManager {
|
|||||||
);
|
);
|
||||||
const downloadUrl = await VikingFileApi.getDownloadUrl(download.uri);
|
const downloadUrl = await VikingFileApi.getDownloadUrl(download.uri);
|
||||||
const filename =
|
const filename =
|
||||||
|
resumingFilename ||
|
||||||
this.extractFilename(download.uri, downloadUrl) ||
|
this.extractFilename(download.uri, downloadUrl) ||
|
||||||
this.extractFilename(downloadUrl);
|
this.extractFilename(downloadUrl);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { GameShop, LibraryGame, SeedingStatus } from "@types";
|
import type { GameShop, LibraryGame, SeedingStatus } from "@types";
|
||||||
|
|
||||||
import { Badge, Button } from "@renderer/components";
|
import { Badge, Button, ConfirmationModal } from "@renderer/components";
|
||||||
import {
|
import {
|
||||||
formatDownloadProgress,
|
formatDownloadProgress,
|
||||||
buildGameDetailsPath,
|
buildGameDetailsPath,
|
||||||
@@ -219,7 +219,7 @@ interface HeroDownloadViewProps {
|
|||||||
calculateETA: () => string;
|
calculateETA: () => string;
|
||||||
pauseDownload: (shop: GameShop, objectId: string) => void;
|
pauseDownload: (shop: GameShop, objectId: string) => void;
|
||||||
resumeDownload: (shop: GameShop, objectId: string) => void;
|
resumeDownload: (shop: GameShop, objectId: string) => void;
|
||||||
cancelDownload: (shop: GameShop, objectId: string) => void;
|
onCancelClick: (shop: GameShop, objectId: string) => void;
|
||||||
t: (key: string) => string;
|
t: (key: string) => string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,7 +238,7 @@ function HeroDownloadView({
|
|||||||
calculateETA,
|
calculateETA,
|
||||||
pauseDownload,
|
pauseDownload,
|
||||||
resumeDownload,
|
resumeDownload,
|
||||||
cancelDownload,
|
onCancelClick,
|
||||||
t,
|
t,
|
||||||
}: Readonly<HeroDownloadViewProps>) {
|
}: Readonly<HeroDownloadViewProps>) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -353,7 +353,7 @@ function HeroDownloadView({
|
|||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => cancelDownload(game.shop, game.objectId)}
|
onClick={() => onCancelClick(game.shop, game.objectId)}
|
||||||
className="download-group__glass-btn"
|
className="download-group__glass-btn"
|
||||||
>
|
>
|
||||||
<XCircleIcon size={14} />
|
<XCircleIcon size={14} />
|
||||||
@@ -523,6 +523,13 @@ export function DownloadGroup({
|
|||||||
const [optimisticallyResumed, setOptimisticallyResumed] = useState<
|
const [optimisticallyResumed, setOptimisticallyResumed] = useState<
|
||||||
Record<string, boolean>
|
Record<string, boolean>
|
||||||
>({});
|
>({});
|
||||||
|
const [cancelModalVisible, setCancelModalVisible] = useState(false);
|
||||||
|
const [gameToCancelShop, setGameToCancelShop] = useState<GameShop | null>(
|
||||||
|
null
|
||||||
|
);
|
||||||
|
const [gameToCancelObjectId, setGameToCancelObjectId] = useState<
|
||||||
|
string | null
|
||||||
|
>(null);
|
||||||
|
|
||||||
const extractDominantColor = useCallback(
|
const extractDominantColor = useCallback(
|
||||||
async (imageUrl: string, gameId: string) => {
|
async (imageUrl: string, gameId: string) => {
|
||||||
@@ -658,6 +665,27 @@ export function DownloadGroup({
|
|||||||
[updateLibrary]
|
[updateLibrary]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleCancelClick = useCallback((shop: GameShop, objectId: string) => {
|
||||||
|
setGameToCancelShop(shop);
|
||||||
|
setGameToCancelObjectId(objectId);
|
||||||
|
setCancelModalVisible(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleConfirmCancel = useCallback(async () => {
|
||||||
|
if (gameToCancelShop && gameToCancelObjectId) {
|
||||||
|
await cancelDownload(gameToCancelShop, gameToCancelObjectId);
|
||||||
|
}
|
||||||
|
setCancelModalVisible(false);
|
||||||
|
setGameToCancelShop(null);
|
||||||
|
setGameToCancelObjectId(null);
|
||||||
|
}, [gameToCancelShop, gameToCancelObjectId, cancelDownload]);
|
||||||
|
|
||||||
|
const handleCancelModalClose = useCallback(() => {
|
||||||
|
setCancelModalVisible(false);
|
||||||
|
setGameToCancelShop(null);
|
||||||
|
setGameToCancelObjectId(null);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const getGameActions = (game: LibraryGame): DropdownMenuItem[] => {
|
const getGameActions = (game: LibraryGame): DropdownMenuItem[] => {
|
||||||
const download = lastPacket?.download;
|
const download = lastPacket?.download;
|
||||||
const isGameDownloading = isGameDownloadingMap[game.id];
|
const isGameDownloading = isGameDownloadingMap[game.id];
|
||||||
@@ -728,7 +756,7 @@ export function DownloadGroup({
|
|||||||
{
|
{
|
||||||
label: t("cancel"),
|
label: t("cancel"),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
cancelDownload(game.shop, game.objectId);
|
handleCancelClick(game.shop, game.objectId);
|
||||||
},
|
},
|
||||||
icon: <XCircleIcon />,
|
icon: <XCircleIcon />,
|
||||||
},
|
},
|
||||||
@@ -753,7 +781,7 @@ export function DownloadGroup({
|
|||||||
{
|
{
|
||||||
label: t("cancel"),
|
label: t("cancel"),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
cancelDownload(game.shop, game.objectId);
|
handleCancelClick(game.shop, game.objectId);
|
||||||
},
|
},
|
||||||
icon: <XCircleIcon />,
|
icon: <XCircleIcon />,
|
||||||
},
|
},
|
||||||
@@ -811,136 +839,162 @@ export function DownloadGroup({
|
|||||||
const dominantColor = dominantColors[game.id] || "#fff";
|
const dominantColor = dominantColors[game.id] || "#fff";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HeroDownloadView
|
<>
|
||||||
game={game}
|
<ConfirmationModal
|
||||||
isGameDownloading={isGameDownloading}
|
visible={cancelModalVisible}
|
||||||
isGameExtracting={isGameExtracting}
|
title={t("cancel_download")}
|
||||||
downloadSpeed={downloadSpeed}
|
descriptionText={t("cancel_download_description")}
|
||||||
finalDownloadSize={finalDownloadSize}
|
confirmButtonLabel={t("cancel")}
|
||||||
peakSpeed={peakSpeed}
|
cancelButtonLabel={t("keep_downloading")}
|
||||||
currentProgress={currentProgress}
|
onConfirm={handleConfirmCancel}
|
||||||
dominantColor={dominantColor}
|
onClose={handleCancelModalClose}
|
||||||
lastPacket={lastPacket}
|
/>
|
||||||
speedHistory={gameSpeedHistory}
|
<HeroDownloadView
|
||||||
formatSpeed={formatSpeed}
|
game={game}
|
||||||
calculateETA={calculateETA}
|
isGameDownloading={isGameDownloading}
|
||||||
pauseDownload={pauseDownload}
|
isGameExtracting={isGameExtracting}
|
||||||
resumeDownload={resumeDownload}
|
downloadSpeed={downloadSpeed}
|
||||||
cancelDownload={cancelDownload}
|
finalDownloadSize={finalDownloadSize}
|
||||||
t={t}
|
peakSpeed={peakSpeed}
|
||||||
/>
|
currentProgress={currentProgress}
|
||||||
|
dominantColor={dominantColor}
|
||||||
|
lastPacket={lastPacket}
|
||||||
|
speedHistory={gameSpeedHistory}
|
||||||
|
formatSpeed={formatSpeed}
|
||||||
|
calculateETA={calculateETA}
|
||||||
|
pauseDownload={pauseDownload}
|
||||||
|
resumeDownload={resumeDownload}
|
||||||
|
onCancelClick={handleCancelClick}
|
||||||
|
t={t}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<>
|
||||||
className={`download-group ${isQueuedGroup ? "download-group--queued" : ""} ${isCompletedGroup ? "download-group--completed" : ""}`}
|
<ConfirmationModal
|
||||||
>
|
visible={cancelModalVisible}
|
||||||
<div className="download-group__header">
|
title={t("cancel_download")}
|
||||||
<div className="download-group__header-title-group">
|
descriptionText={t("cancel_download_description")}
|
||||||
<h2>{title}</h2>
|
confirmButtonLabel={t("cancel")}
|
||||||
<h3 className="download-group__header-count">{library.length}</h3>
|
cancelButtonLabel={t("keep_downloading")}
|
||||||
|
onConfirm={handleConfirmCancel}
|
||||||
|
onClose={handleCancelModalClose}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className={`download-group ${isQueuedGroup ? "download-group--queued" : ""} ${isCompletedGroup ? "download-group--completed" : ""}`}
|
||||||
|
>
|
||||||
|
<div className="download-group__header">
|
||||||
|
<div className="download-group__header-title-group">
|
||||||
|
<h2>{title}</h2>
|
||||||
|
<h3 className="download-group__header-count">{library.length}</h3>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<ul className="download-group__simple-list">
|
<ul className="download-group__simple-list">
|
||||||
{downloadInfo.map(({ game, size, progress, isSeeding: seeding }) => {
|
{downloadInfo.map(({ game, size, progress, isSeeding: seeding }) => {
|
||||||
return (
|
return (
|
||||||
<li key={game.id} className="download-group__simple-card">
|
<li key={game.id} className="download-group__simple-card">
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => navigate(buildGameDetailsPath(game))}
|
|
||||||
className="download-group__simple-thumbnail"
|
|
||||||
>
|
|
||||||
<img src={game.libraryImageUrl || ""} alt={game.title} />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div className="download-group__simple-info">
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => navigate(buildGameDetailsPath(game))}
|
onClick={() => navigate(buildGameDetailsPath(game))}
|
||||||
className="download-group__simple-title-button"
|
className="download-group__simple-thumbnail"
|
||||||
>
|
>
|
||||||
<h3 className="download-group__simple-title">{game.title}</h3>
|
<img src={game.libraryImageUrl || ""} alt={game.title} />
|
||||||
</button>
|
</button>
|
||||||
<div className="download-group__simple-meta">
|
|
||||||
<div className="download-group__simple-meta-row">
|
<div className="download-group__simple-info">
|
||||||
<Badge>
|
<button
|
||||||
{DOWNLOADER_NAME[Number(game.download!.downloader)]}
|
type="button"
|
||||||
</Badge>
|
onClick={() => navigate(buildGameDetailsPath(game))}
|
||||||
</div>
|
className="download-group__simple-title-button"
|
||||||
<div className="download-group__simple-meta-row">
|
>
|
||||||
{extraction?.visibleId === game.id ? (
|
<h3 className="download-group__simple-title">
|
||||||
<span className="download-group__simple-extracting">
|
{game.title}
|
||||||
{t("extracting")} (
|
</h3>
|
||||||
{Math.round(extraction.progress * 100)}%)
|
</button>
|
||||||
</span>
|
<div className="download-group__simple-meta">
|
||||||
) : (
|
<div className="download-group__simple-meta-row">
|
||||||
<span className="download-group__simple-size">
|
<Badge>
|
||||||
<DownloadIcon size={14} />
|
{DOWNLOADER_NAME[Number(game.download!.downloader)]}
|
||||||
{size}
|
</Badge>
|
||||||
</span>
|
</div>
|
||||||
)}
|
<div className="download-group__simple-meta-row">
|
||||||
{game.download?.progress === 1 && seeding && (
|
{extraction?.visibleId === game.id ? (
|
||||||
<span className="download-group__simple-seeding">
|
<span className="download-group__simple-extracting">
|
||||||
{t("seeding")}
|
{t("extracting")} (
|
||||||
</span>
|
{Math.round(extraction.progress * 100)}%)
|
||||||
)}
|
</span>
|
||||||
|
) : (
|
||||||
|
<span className="download-group__simple-size">
|
||||||
|
<DownloadIcon size={14} />
|
||||||
|
{size}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{game.download?.progress === 1 && seeding && (
|
||||||
|
<span className="download-group__simple-seeding">
|
||||||
|
{t("seeding")}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{isQueuedGroup && (
|
{isQueuedGroup && (
|
||||||
<div className="download-group__simple-progress">
|
<div className="download-group__simple-progress">
|
||||||
<span className="download-group__simple-progress-text">
|
<span className="download-group__simple-progress-text">
|
||||||
{formatDownloadProgress(progress)}
|
{formatDownloadProgress(progress)}
|
||||||
</span>
|
</span>
|
||||||
<div className="download-group__progress-bar download-group__progress-bar--small">
|
<div className="download-group__progress-bar download-group__progress-bar--small">
|
||||||
<div
|
<div
|
||||||
className="download-group__progress-fill"
|
className="download-group__progress-fill"
|
||||||
style={{
|
style={{
|
||||||
width: `${progress * 100}%`,
|
width: `${progress * 100}%`,
|
||||||
backgroundColor: "#fff",
|
backgroundColor: "#fff",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="download-group__simple-actions">
|
<div className="download-group__simple-actions">
|
||||||
{game.download?.progress === 1 && (
|
{game.download?.progress === 1 && (
|
||||||
<Button
|
<Button
|
||||||
theme="primary"
|
theme="primary"
|
||||||
onClick={() => openGameInstaller(game.shop, game.objectId)}
|
onClick={() =>
|
||||||
disabled={isGameDeleting(game.id)}
|
openGameInstaller(game.shop, game.objectId)
|
||||||
className="download-group__simple-menu-btn"
|
}
|
||||||
>
|
disabled={isGameDeleting(game.id)}
|
||||||
<PlayIcon size={16} />
|
className="download-group__simple-menu-btn"
|
||||||
</Button>
|
>
|
||||||
)}
|
<PlayIcon size={16} />
|
||||||
{isQueuedGroup && game.download?.progress !== 1 && (
|
</Button>
|
||||||
<Button
|
)}
|
||||||
theme="primary"
|
{isQueuedGroup && game.download?.progress !== 1 && (
|
||||||
onClick={() => resumeDownload(game.shop, game.objectId)}
|
<Button
|
||||||
className="download-group__simple-menu-btn"
|
theme="primary"
|
||||||
tooltip={t("resume")}
|
onClick={() => resumeDownload(game.shop, game.objectId)}
|
||||||
>
|
className="download-group__simple-menu-btn"
|
||||||
<DownloadIcon size={16} />
|
tooltip={t("resume")}
|
||||||
</Button>
|
>
|
||||||
)}
|
<DownloadIcon size={16} />
|
||||||
<DropdownMenu align="end" items={getGameActions(game)}>
|
</Button>
|
||||||
<Button
|
)}
|
||||||
theme="outline"
|
<DropdownMenu align="end" items={getGameActions(game)}>
|
||||||
className="download-group__simple-menu-btn"
|
<Button
|
||||||
>
|
theme="outline"
|
||||||
<ThreeBarsIcon />
|
className="download-group__simple-menu-btn"
|
||||||
</Button>
|
>
|
||||||
</DropdownMenu>
|
<ThreeBarsIcon />
|
||||||
</div>
|
</Button>
|
||||||
</li>
|
</DropdownMenu>
|
||||||
);
|
</div>
|
||||||
})}
|
</li>
|
||||||
</ul>
|
);
|
||||||
</div>
|
})}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user