From 63f8289d0a7807374fc91be2a39b3892c67c1aa8 Mon Sep 17 00:00:00 2001 From: Moyasee Date: Fri, 12 Dec 2025 12:44:02 +0200 Subject: [PATCH] feat: implement archive deletion prompt and translations for confirmation messages --- src/locales/en/translation.json | 6 ++- src/locales/pt-BR/translation.json | 6 ++- src/main/events/library/delete-archive.ts | 23 ++++++++++ src/main/events/library/index.ts | 1 + src/main/services/game-files-manager.ts | 28 +++++------- src/preload/index.ts | 11 +++++ src/renderer/src/app.tsx | 17 ++++++- src/renderer/src/declaration.d.ts | 4 ++ .../archive-deletion-error-modal.tsx | 44 +++++++++++++++++++ .../src/pages/downloads/download-group.scss | 2 +- .../src/pages/downloads/downloads.tsx | 6 ++- .../pages/game-details/hero/hero-panel.scss | 2 +- 12 files changed, 127 insertions(+), 23 deletions(-) create mode 100644 src/main/events/library/delete-archive.ts create mode 100644 src/renderer/src/pages/downloads/archive-deletion-error-modal.tsx diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 3709a546..9be4ff26 100755 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -416,7 +416,11 @@ "resume_seeding": "Resume seeding", "options": "Manage", "extract": "Extract files", - "extracting": "Extracting files…" + "extracting": "Extracting files…", + "delete_archive_title": "Would you like to delete {{fileName}}?", + "delete_archive_description": "The file has been successfully extracted and it's no longer needed.", + "yes": "Yes", + "no": "No" }, "settings": { "downloads_path": "Downloads path", diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index 30a46278..ee0da176 100755 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -404,7 +404,11 @@ "resume_seeding": "Semear", "options": "Gerenciar", "extract": "Extrair arquivos", - "extracting": "Extraindo arquivos…" + "extracting": "Extraindo arquivos…", + "delete_archive_title": "Deseja deletar {{fileName}}?", + "delete_archive_description": "O arquivo foi extraído com sucesso e não é mais necessário.", + "yes": "Sim", + "no": "Não" }, "settings": { "downloads_path": "Diretório dos downloads", diff --git a/src/main/events/library/delete-archive.ts b/src/main/events/library/delete-archive.ts new file mode 100644 index 00000000..9cf64a63 --- /dev/null +++ b/src/main/events/library/delete-archive.ts @@ -0,0 +1,23 @@ +import fs from "node:fs"; + +import { registerEvent } from "../register-event"; +import { logger } from "@main/services"; + +const deleteArchive = async ( + _event: Electron.IpcMainInvokeEvent, + filePath: string +) => { + try { + if (fs.existsSync(filePath)) { + await fs.promises.unlink(filePath); + logger.info(`Deleted archive: ${filePath}`); + return true; + } + return true; + } catch (err) { + logger.error(`Failed to delete archive: ${filePath}`, err); + return false; + } +}; + +registerEvent("deleteArchive", deleteArchive); diff --git a/src/main/events/library/index.ts b/src/main/events/library/index.ts index d9d628d0..75fc5cd9 100644 --- a/src/main/events/library/index.ts +++ b/src/main/events/library/index.ts @@ -8,6 +8,7 @@ import "./close-game"; import "./copy-custom-game-asset"; import "./create-game-shortcut"; import "./create-steam-shortcut"; +import "./delete-archive"; import "./delete-game-folder"; import "./extract-game-download"; import "./get-default-wine-prefix-selection-path"; diff --git a/src/main/services/game-files-manager.ts b/src/main/services/game-files-manager.ts index 3e0f1b47..f3684a0a 100644 --- a/src/main/services/game-files-manager.ts +++ b/src/main/services/game-files-manager.ts @@ -116,17 +116,15 @@ export class GameFilesManager { } } - for (const file of compressedFiles) { - const extractionPath = path.join(directoryPath, file); + const archivePaths = compressedFiles + .map((file) => path.join(directoryPath, file)) + .filter((archivePath) => fs.existsSync(archivePath)); - try { - if (fs.existsSync(extractionPath)) { - await fs.promises.unlink(extractionPath); - logger.info(`Deleted archive: ${file}`); - } - } catch (err) { - logger.error(`Failed to delete file: ${file}`, err); - } + if (archivePaths.length > 0) { + WindowManager.mainWindow?.webContents.send( + "on-archive-deletion-prompt", + archivePaths + ); } } @@ -186,12 +184,10 @@ export class GameFilesManager { await this.extractFilesInDirectory(extractionPath); if (fs.existsSync(extractionPath) && fs.existsSync(filePath)) { - try { - await fs.promises.unlink(filePath); - logger.info(`Deleted archive: ${download.folderName}`); - } catch (err) { - logger.error(`Failed to delete file: ${download.folderName}`, err); - } + WindowManager.mainWindow?.webContents.send( + "on-archive-deletion-prompt", + [filePath] + ); } await downloadsSublevel.put(this.gameKey, { diff --git a/src/preload/index.ts b/src/preload/index.ts index 7be92065..5579b6fb 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -279,6 +279,17 @@ contextBridge.exposeInMainWorld("electron", { ipcRenderer.on("on-extraction-progress", listener); return () => ipcRenderer.removeListener("on-extraction-progress", listener); }, + onArchiveDeletionPrompt: (cb: (archivePaths: string[]) => void) => { + const listener = ( + _event: Electron.IpcRendererEvent, + archivePaths: string[] + ) => cb(archivePaths); + ipcRenderer.on("on-archive-deletion-prompt", listener); + return () => + ipcRenderer.removeListener("on-archive-deletion-prompt", listener); + }, + deleteArchive: (filePath: string) => + ipcRenderer.invoke("deleteArchive", filePath), /* Hardware */ getDiskFreeSpace: (path: string) => diff --git a/src/renderer/src/app.tsx b/src/renderer/src/app.tsx index 9c65d959..6619c890 100644 --- a/src/renderer/src/app.tsx +++ b/src/renderer/src/app.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useRef } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import { Sidebar, BottomPanel, Header, Toast } from "@renderer/components"; import { @@ -26,6 +26,7 @@ import { useTranslation } from "react-i18next"; import { UserFriendModal } from "./pages/shared-modals/user-friend-modal"; import { useSubscription } from "./hooks/use-subscription"; import { HydraCloudModal } from "./pages/shared-modals/hydra-cloud/hydra-cloud-modal"; +import { ArchiveDeletionModal } from "./pages/downloads/archive-deletion-error-modal"; import { injectCustomCss, @@ -80,6 +81,10 @@ export function App() { const { showSuccessToast } = useToast(); + const [showArchiveDeletionModal, setShowArchiveDeletionModal] = + useState(false); + const [archivePaths, setArchivePaths] = useState([]); + useEffect(() => { Promise.all([ levelDBService.get("userPreferences", null, "json"), @@ -193,6 +198,10 @@ export function App() { dispatch(clearExtraction()); updateLibrary(); }), + window.electron.onArchiveDeletionPrompt((paths) => { + setArchivePaths(paths); + setShowArchiveDeletionModal(true); + }), ]; return () => { @@ -290,6 +299,12 @@ export function App() { feature={hydraCloudFeature} /> + setShowArchiveDeletionModal(false)} + /> + {userDetails && ( void ) => () => Electron.IpcRenderer; + onArchiveDeletionPrompt: ( + cb: (archivePaths: string[]) => void + ) => () => Electron.IpcRenderer; + deleteArchive: (filePath: string) => Promise; getDefaultWinePrefixSelectionPath: () => Promise; createSteamShortcut: (shop: GameShop, objectId: string) => Promise; diff --git a/src/renderer/src/pages/downloads/archive-deletion-error-modal.tsx b/src/renderer/src/pages/downloads/archive-deletion-error-modal.tsx new file mode 100644 index 00000000..ff931a61 --- /dev/null +++ b/src/renderer/src/pages/downloads/archive-deletion-error-modal.tsx @@ -0,0 +1,44 @@ +import { useTranslation } from "react-i18next"; +import { ConfirmationModal } from "@renderer/components"; + +interface ArchiveDeletionModalProps { + visible: boolean; + archivePaths: string[]; + onClose: () => void; +} + +export function ArchiveDeletionModal({ + visible, + archivePaths, + onClose, +}: Readonly) { + const { t } = useTranslation("downloads"); + + const fullFileName = + archivePaths.length > 0 ? (archivePaths[0].split(/[/\\]/).pop() ?? "") : ""; + + const maxLength = 40; + const fileName = + fullFileName.length > maxLength + ? `${fullFileName.slice(0, maxLength)}…` + : fullFileName; + + const handleConfirm = async () => { + for (const archivePath of archivePaths) { + await window.electron.deleteArchive(archivePath); + } + onClose(); + }; + + return ( + + ); +} diff --git a/src/renderer/src/pages/downloads/download-group.scss b/src/renderer/src/pages/downloads/download-group.scss index 9d6cb111..bfd8fbda 100644 --- a/src/renderer/src/pages/downloads/download-group.scss +++ b/src/renderer/src/pages/downloads/download-group.scss @@ -538,7 +538,7 @@ border-radius: 4px; &--extraction { - background-color: #4caf50; + background-color: #fff; } } } diff --git a/src/renderer/src/pages/downloads/downloads.tsx b/src/renderer/src/pages/downloads/downloads.tsx index 35403ba1..10d817f1 100644 --- a/src/renderer/src/pages/downloads/downloads.tsx +++ b/src/renderer/src/pages/downloads/downloads.tsx @@ -40,11 +40,13 @@ export default function Downloads() { useEffect(() => { window.electron.onSeedingStatus((value) => setSeedingStatus(value)); - const unsubscribe = window.electron.onExtractionComplete(() => { + const unsubscribeExtraction = window.electron.onExtractionComplete(() => { updateLibrary(); }); - return () => unsubscribe(); + return () => { + unsubscribeExtraction(); + }; }, [updateLibrary]); const handleOpenGameInstaller = (shop: GameShop, objectId: string) => diff --git a/src/renderer/src/pages/game-details/hero/hero-panel.scss b/src/renderer/src/pages/game-details/hero/hero-panel.scss index 10265b9e..6aa4d311 100644 --- a/src/renderer/src/pages/game-details/hero/hero-panel.scss +++ b/src/renderer/src/pages/game-details/hero/hero-panel.scss @@ -83,7 +83,7 @@ &--extraction { &::-webkit-progress-value { - background-color: #4caf50; + background-color: #fff; } } }