mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-02-03 00:33:49 +03:00
Merge branch 'main' into feature/check-directory-permission
This commit is contained in:
commit
99364df23d
@ -179,7 +179,12 @@
|
|||||||
"backup_from": "Backup from {{date}}",
|
"backup_from": "Backup from {{date}}",
|
||||||
"custom_backup_location_set": "Custom backup location set",
|
"custom_backup_location_set": "Custom backup location set",
|
||||||
"no_directory_selected": "No directory selected",
|
"no_directory_selected": "No directory selected",
|
||||||
"no_write_permission": "Cannot download into this directory. Click here to learn more."
|
"no_write_permission": "Cannot download into this directory. Click here to learn more.",
|
||||||
|
"reset_achievements": "Reset achievements",
|
||||||
|
"reset_achievements_description": "This will reset all achievements for {{game}}",
|
||||||
|
"reset_achievements_title": "Are you sure?",
|
||||||
|
"reset_achievements_success": "Achievements successfully reset",
|
||||||
|
"reset_achievements_error": "Failed to reset achievements"
|
||||||
},
|
},
|
||||||
"activation": {
|
"activation": {
|
||||||
"title": "Activate Hydra",
|
"title": "Activate Hydra",
|
||||||
|
@ -168,7 +168,11 @@
|
|||||||
"manage_files_description": "Gerencie quais arquivos serão feitos backup",
|
"manage_files_description": "Gerencie quais arquivos serão feitos backup",
|
||||||
"clear": "Limpar",
|
"clear": "Limpar",
|
||||||
"no_directory_selected": "Nenhum diretório selecionado",
|
"no_directory_selected": "Nenhum diretório selecionado",
|
||||||
"no_write_permission": "O download não pode ser feito neste diretório. Clique aqui para saber mais."
|
"reset_achievements": "Resetar conquistas",
|
||||||
|
"reset_achievements_description": "Isso irá resetar todas as conquistas de {{game}}",
|
||||||
|
"reset_achievements_title": "Tem certeza?",
|
||||||
|
"reset_achievements_success": "Conquistas resetadas com sucesso",
|
||||||
|
"reset_achievements_error": "Falha ao resetar conquistas"
|
||||||
},
|
},
|
||||||
"activation": {
|
"activation": {
|
||||||
"title": "Ativação",
|
"title": "Ativação",
|
||||||
|
@ -28,6 +28,7 @@ import "./library/verify-executable-path";
|
|||||||
import "./library/remove-game";
|
import "./library/remove-game";
|
||||||
import "./library/remove-game-from-library";
|
import "./library/remove-game-from-library";
|
||||||
import "./library/select-game-wine-prefix";
|
import "./library/select-game-wine-prefix";
|
||||||
|
import "./library/reset-game-achievements";
|
||||||
import "./misc/open-checkout";
|
import "./misc/open-checkout";
|
||||||
import "./misc/open-external";
|
import "./misc/open-external";
|
||||||
import "./misc/show-open-dialog";
|
import "./misc/show-open-dialog";
|
||||||
|
56
src/main/events/library/reset-game-achievements.ts
Normal file
56
src/main/events/library/reset-game-achievements.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import { gameAchievementRepository, gameRepository } from "@main/repository";
|
||||||
|
import { registerEvent } from "../register-event";
|
||||||
|
import { findAchievementFiles } from "@main/services/achievements/find-achivement-files";
|
||||||
|
import fs from "fs";
|
||||||
|
import { achievementsLogger, HydraApi, WindowManager } from "@main/services";
|
||||||
|
import { getUnlockedAchievements } from "../user/get-unlocked-achievements";
|
||||||
|
|
||||||
|
const resetGameAchievements = async (
|
||||||
|
_event: Electron.IpcMainInvokeEvent,
|
||||||
|
gameId: number
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
const game = await gameRepository.findOne({ where: { id: gameId } });
|
||||||
|
|
||||||
|
if (!game) return;
|
||||||
|
|
||||||
|
const achievementFiles = findAchievementFiles(game);
|
||||||
|
|
||||||
|
if (achievementFiles.length) {
|
||||||
|
for (const achievementFile of achievementFiles) {
|
||||||
|
achievementsLogger.log(`deleting ${achievementFile.filePath}`);
|
||||||
|
await fs.promises.rm(achievementFile.filePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await gameAchievementRepository.update(
|
||||||
|
{ objectId: game.objectID },
|
||||||
|
{
|
||||||
|
unlockedAchievements: null,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await HydraApi.delete(`/profile/games/achievements/${game.remoteId}`).then(
|
||||||
|
() =>
|
||||||
|
achievementsLogger.log(
|
||||||
|
`Deleted achievements from ${game.remoteId} - ${game.objectID} - ${game.title}`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const gameAchievements = await getUnlockedAchievements(
|
||||||
|
game.objectID,
|
||||||
|
game.shop,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
WindowManager.mainWindow?.webContents.send(
|
||||||
|
`on-update-achievements-${game.objectID}-${game.shop}`,
|
||||||
|
gameAchievements
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
achievementsLogger.error(error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
registerEvent("resetGameAchievements", resetGameAchievements);
|
@ -130,6 +130,8 @@ contextBridge.exposeInMainWorld("electron", {
|
|||||||
ipcRenderer.invoke("deleteGameFolder", gameId),
|
ipcRenderer.invoke("deleteGameFolder", gameId),
|
||||||
getGameByObjectId: (objectId: string) =>
|
getGameByObjectId: (objectId: string) =>
|
||||||
ipcRenderer.invoke("getGameByObjectId", objectId),
|
ipcRenderer.invoke("getGameByObjectId", objectId),
|
||||||
|
resetGameAchievements: (gameId: number) =>
|
||||||
|
ipcRenderer.invoke("resetGameAchievements", gameId),
|
||||||
onGamesRunning: (
|
onGamesRunning: (
|
||||||
cb: (
|
cb: (
|
||||||
gamesRunning: Pick<GameRunning, "id" | "sessionDurationInMillis">[]
|
gamesRunning: Pick<GameRunning, "id" | "sessionDurationInMillis">[]
|
||||||
|
2
src/renderer/src/declaration.d.ts
vendored
2
src/renderer/src/declaration.d.ts
vendored
@ -122,7 +122,7 @@ declare global {
|
|||||||
) => void
|
) => void
|
||||||
) => () => Electron.IpcRenderer;
|
) => () => Electron.IpcRenderer;
|
||||||
onLibraryBatchComplete: (cb: () => void) => () => Electron.IpcRenderer;
|
onLibraryBatchComplete: (cb: () => void) => () => Electron.IpcRenderer;
|
||||||
|
resetGameAchievements: (gameId: number) => Promise<void>;
|
||||||
/* User preferences */
|
/* User preferences */
|
||||||
getUserPreferences: () => Promise<UserPreferences | null>;
|
getUserPreferences: () => Promise<UserPreferences | null>;
|
||||||
updateUserPreferences: (
|
updateUserPreferences: (
|
||||||
|
@ -5,8 +5,9 @@ import type { Game } from "@types";
|
|||||||
import * as styles from "./game-options-modal.css";
|
import * as styles from "./game-options-modal.css";
|
||||||
import { gameDetailsContext } from "@renderer/context";
|
import { gameDetailsContext } from "@renderer/context";
|
||||||
import { DeleteGameModal } from "@renderer/pages/downloads/delete-game-modal";
|
import { DeleteGameModal } from "@renderer/pages/downloads/delete-game-modal";
|
||||||
import { useDownload, useToast } from "@renderer/hooks";
|
import { useDownload, useToast, useUserDetails } from "@renderer/hooks";
|
||||||
import { RemoveGameFromLibraryModal } from "./remove-from-library-modal";
|
import { RemoveGameFromLibraryModal } from "./remove-from-library-modal";
|
||||||
|
import { ResetAchievementsModal } from "./reset-achievements-modal";
|
||||||
import { FileDirectoryIcon, FileIcon } from "@primer/octicons-react";
|
import { FileDirectoryIcon, FileIcon } from "@primer/octicons-react";
|
||||||
import { debounce } from "lodash-es";
|
import { debounce } from "lodash-es";
|
||||||
|
|
||||||
@ -25,12 +26,20 @@ export function GameOptionsModal({
|
|||||||
|
|
||||||
const { showSuccessToast, showErrorToast } = useToast();
|
const { showSuccessToast, showErrorToast } = useToast();
|
||||||
|
|
||||||
const { updateGame, setShowRepacksModal, repacks, selectGameExecutable } =
|
const {
|
||||||
useContext(gameDetailsContext);
|
updateGame,
|
||||||
|
setShowRepacksModal,
|
||||||
|
repacks,
|
||||||
|
selectGameExecutable,
|
||||||
|
achievements,
|
||||||
|
} = useContext(gameDetailsContext);
|
||||||
|
|
||||||
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||||
const [showRemoveGameModal, setShowRemoveGameModal] = useState(false);
|
const [showRemoveGameModal, setShowRemoveGameModal] = useState(false);
|
||||||
const [launchOptions, setLaunchOptions] = useState(game.launchOptions ?? "");
|
const [launchOptions, setLaunchOptions] = useState(game.launchOptions ?? "");
|
||||||
|
const [showResetAchievementsModal, setShowResetAchievementsModal] =
|
||||||
|
useState(false);
|
||||||
|
const [isDeletingAchievements, setIsDeletingAchievements] = useState(false);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
removeGameInstaller,
|
removeGameInstaller,
|
||||||
@ -39,6 +48,12 @@ export function GameOptionsModal({
|
|||||||
cancelDownload,
|
cancelDownload,
|
||||||
} = useDownload();
|
} = useDownload();
|
||||||
|
|
||||||
|
const { userDetails } = useUserDetails();
|
||||||
|
|
||||||
|
const hasAchievements =
|
||||||
|
(achievements?.filter((achievement) => achievement.unlocked).length ?? 0) >
|
||||||
|
0;
|
||||||
|
|
||||||
const deleting = isGameDeleting(game.id);
|
const deleting = isGameDeleting(game.id);
|
||||||
|
|
||||||
const { lastPacket } = useDownload();
|
const { lastPacket } = useDownload();
|
||||||
@ -141,6 +156,19 @@ export function GameOptionsModal({
|
|||||||
const shouldShowWinePrefixConfiguration =
|
const shouldShowWinePrefixConfiguration =
|
||||||
window.electron.platform === "linux";
|
window.electron.platform === "linux";
|
||||||
|
|
||||||
|
const handleResetAchievements = async () => {
|
||||||
|
setIsDeletingAchievements(true);
|
||||||
|
try {
|
||||||
|
await window.electron.resetGameAchievements(game.id);
|
||||||
|
await updateGame();
|
||||||
|
showSuccessToast(t("reset_achievements_success"));
|
||||||
|
} catch (error) {
|
||||||
|
showErrorToast(t("reset_achievements_error"));
|
||||||
|
} finally {
|
||||||
|
setIsDeletingAchievements(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const shouldShowLaunchOptionsConfiguration = false;
|
const shouldShowLaunchOptionsConfiguration = false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -158,6 +186,13 @@ export function GameOptionsModal({
|
|||||||
game={game}
|
game={game}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<ResetAchievementsModal
|
||||||
|
visible={showResetAchievementsModal}
|
||||||
|
onClose={() => setShowResetAchievementsModal(false)}
|
||||||
|
resetAchievements={handleResetAchievements}
|
||||||
|
game={game}
|
||||||
|
/>
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
visible={visible}
|
visible={visible}
|
||||||
title={game.title}
|
title={game.title}
|
||||||
@ -313,6 +348,20 @@ export function GameOptionsModal({
|
|||||||
>
|
>
|
||||||
{t("remove_from_library")}
|
{t("remove_from_library")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
onClick={() => setShowResetAchievementsModal(true)}
|
||||||
|
theme="danger"
|
||||||
|
disabled={
|
||||||
|
deleting ||
|
||||||
|
isDeletingAchievements ||
|
||||||
|
!hasAchievements ||
|
||||||
|
!userDetails
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{t("reset_achievements")}
|
||||||
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setShowDeleteModal(true);
|
setShowDeleteModal(true);
|
||||||
|
@ -0,0 +1,48 @@
|
|||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Button, Modal } from "@renderer/components";
|
||||||
|
import * as styles from "./remove-from-library-modal.css";
|
||||||
|
import type { Game } from "@types";
|
||||||
|
type ResetAchievementsModalProps = Readonly<{
|
||||||
|
visible: boolean;
|
||||||
|
game: Game;
|
||||||
|
onClose: () => void;
|
||||||
|
resetAchievements: () => Promise<void>;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export function ResetAchievementsModal({
|
||||||
|
onClose,
|
||||||
|
game,
|
||||||
|
visible,
|
||||||
|
resetAchievements,
|
||||||
|
}: ResetAchievementsModalProps) {
|
||||||
|
const { t } = useTranslation("game_details");
|
||||||
|
|
||||||
|
const handleResetAchievements = async () => {
|
||||||
|
try {
|
||||||
|
await resetAchievements();
|
||||||
|
} finally {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
visible={visible}
|
||||||
|
onClose={onClose}
|
||||||
|
title={t("reset_achievements_title")}
|
||||||
|
description={t("reset_achievements_description", {
|
||||||
|
game: game.title,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<div className={styles.deleteActionsButtonsCtn}>
|
||||||
|
<Button onClick={handleResetAchievements} theme="outline">
|
||||||
|
{t("reset_achievements")}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button onClick={onClose} theme="primary">
|
||||||
|
{t("cancel")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user