mirror of
				https://github.com/hydralauncher/hydra.git
				synced 2025-03-09 15:40:26 +00:00 
			
		
		
		
	Merge pull request #1301 from hydralauncher/feature/reset-achievements
feat: add reset achievements modal
This commit is contained in:
		
						commit
						385db5c936
					
				
					 8 changed files with 171 additions and 6 deletions
				
			
		| 
						 | 
				
			
			@ -179,7 +179,12 @@
 | 
			
		|||
    "backup_from": "Backup from {{date}}",
 | 
			
		||||
    "custom_backup_location_set": "Custom backup location set",
 | 
			
		||||
    "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": {
 | 
			
		||||
    "title": "Activate Hydra",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -168,7 +168,11 @@
 | 
			
		|||
    "manage_files_description": "Gerencie quais arquivos serão feitos backup",
 | 
			
		||||
    "clear": "Limpar",
 | 
			
		||||
    "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": {
 | 
			
		||||
    "title": "Ativação",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -28,6 +28,7 @@ import "./library/verify-executable-path";
 | 
			
		|||
import "./library/remove-game";
 | 
			
		||||
import "./library/remove-game-from-library";
 | 
			
		||||
import "./library/select-game-wine-prefix";
 | 
			
		||||
import "./library/reset-game-achievements";
 | 
			
		||||
import "./misc/open-checkout";
 | 
			
		||||
import "./misc/open-external";
 | 
			
		||||
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),
 | 
			
		||||
  getGameByObjectId: (objectId: string) =>
 | 
			
		||||
    ipcRenderer.invoke("getGameByObjectId", objectId),
 | 
			
		||||
  resetGameAchievements: (gameId: number) =>
 | 
			
		||||
    ipcRenderer.invoke("resetGameAchievements", gameId),
 | 
			
		||||
  onGamesRunning: (
 | 
			
		||||
    cb: (
 | 
			
		||||
      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
 | 
			
		||||
    ) => () => Electron.IpcRenderer;
 | 
			
		||||
    onLibraryBatchComplete: (cb: () => void) => () => Electron.IpcRenderer;
 | 
			
		||||
 | 
			
		||||
    resetGameAchievements: (gameId: number) => Promise<void>;
 | 
			
		||||
    /* User preferences */
 | 
			
		||||
    getUserPreferences: () => Promise<UserPreferences | null>;
 | 
			
		||||
    updateUserPreferences: (
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,8 +5,9 @@ import type { Game } from "@types";
 | 
			
		|||
import * as styles from "./game-options-modal.css";
 | 
			
		||||
import { gameDetailsContext } from "@renderer/context";
 | 
			
		||||
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 { ResetAchievementsModal } from "./reset-achievements-modal";
 | 
			
		||||
import { FileDirectoryIcon, FileIcon } from "@primer/octicons-react";
 | 
			
		||||
import { debounce } from "lodash-es";
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -25,12 +26,20 @@ export function GameOptionsModal({
 | 
			
		|||
 | 
			
		||||
  const { showSuccessToast, showErrorToast } = useToast();
 | 
			
		||||
 | 
			
		||||
  const { updateGame, setShowRepacksModal, repacks, selectGameExecutable } =
 | 
			
		||||
    useContext(gameDetailsContext);
 | 
			
		||||
  const {
 | 
			
		||||
    updateGame,
 | 
			
		||||
    setShowRepacksModal,
 | 
			
		||||
    repacks,
 | 
			
		||||
    selectGameExecutable,
 | 
			
		||||
    achievements,
 | 
			
		||||
  } = useContext(gameDetailsContext);
 | 
			
		||||
 | 
			
		||||
  const [showDeleteModal, setShowDeleteModal] = useState(false);
 | 
			
		||||
  const [showRemoveGameModal, setShowRemoveGameModal] = useState(false);
 | 
			
		||||
  const [launchOptions, setLaunchOptions] = useState(game.launchOptions ?? "");
 | 
			
		||||
  const [showResetAchievementsModal, setShowResetAchievementsModal] =
 | 
			
		||||
    useState(false);
 | 
			
		||||
  const [isDeletingAchievements, setIsDeletingAchievements] = useState(false);
 | 
			
		||||
 | 
			
		||||
  const {
 | 
			
		||||
    removeGameInstaller,
 | 
			
		||||
| 
						 | 
				
			
			@ -39,6 +48,12 @@ export function GameOptionsModal({
 | 
			
		|||
    cancelDownload,
 | 
			
		||||
  } = useDownload();
 | 
			
		||||
 | 
			
		||||
  const { userDetails } = useUserDetails();
 | 
			
		||||
 | 
			
		||||
  const hasAchievements =
 | 
			
		||||
    (achievements?.filter((achievement) => achievement.unlocked).length ?? 0) >
 | 
			
		||||
    0;
 | 
			
		||||
 | 
			
		||||
  const deleting = isGameDeleting(game.id);
 | 
			
		||||
 | 
			
		||||
  const { lastPacket } = useDownload();
 | 
			
		||||
| 
						 | 
				
			
			@ -141,6 +156,19 @@ export function GameOptionsModal({
 | 
			
		|||
  const shouldShowWinePrefixConfiguration =
 | 
			
		||||
    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;
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
| 
						 | 
				
			
			@ -158,6 +186,13 @@ export function GameOptionsModal({
 | 
			
		|||
        game={game}
 | 
			
		||||
      />
 | 
			
		||||
 | 
			
		||||
      <ResetAchievementsModal
 | 
			
		||||
        visible={showResetAchievementsModal}
 | 
			
		||||
        onClose={() => setShowResetAchievementsModal(false)}
 | 
			
		||||
        resetAchievements={handleResetAchievements}
 | 
			
		||||
        game={game}
 | 
			
		||||
      />
 | 
			
		||||
 | 
			
		||||
      <Modal
 | 
			
		||||
        visible={visible}
 | 
			
		||||
        title={game.title}
 | 
			
		||||
| 
						 | 
				
			
			@ -313,6 +348,20 @@ export function GameOptionsModal({
 | 
			
		|||
            >
 | 
			
		||||
              {t("remove_from_library")}
 | 
			
		||||
            </Button>
 | 
			
		||||
 | 
			
		||||
            <Button
 | 
			
		||||
              onClick={() => setShowResetAchievementsModal(true)}
 | 
			
		||||
              theme="danger"
 | 
			
		||||
              disabled={
 | 
			
		||||
                deleting ||
 | 
			
		||||
                isDeletingAchievements ||
 | 
			
		||||
                !hasAchievements ||
 | 
			
		||||
                !userDetails
 | 
			
		||||
              }
 | 
			
		||||
            >
 | 
			
		||||
              {t("reset_achievements")}
 | 
			
		||||
            </Button>
 | 
			
		||||
 | 
			
		||||
            <Button
 | 
			
		||||
              onClick={() => {
 | 
			
		||||
                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…
	
	Add table
		Add a link
		
	
		Reference in a new issue