feat: adding motion

This commit is contained in:
Chubby Granny Chaser
2025-05-30 14:07:59 +01:00
parent 97a414e77f
commit 4d950b30fb
6 changed files with 132 additions and 97 deletions

View File

@@ -57,6 +57,7 @@
"electron-log": "^5.2.4", "electron-log": "^5.2.4",
"electron-updater": "^6.6.2", "electron-updater": "^6.6.2",
"file-type": "^20.5.0", "file-type": "^20.5.0",
"framer-motion": "^12.15.0",
"i18next": "^23.11.2", "i18next": "^23.11.2",
"i18next-browser-languagedetector": "^7.2.1", "i18next-browser-languagedetector": "^7.2.1",
"jsdom": "^24.0.0", "jsdom": "^24.0.0",

View File

@@ -217,7 +217,9 @@
"freeze_backup": "Pin it so it's not overwritten by automatic backups", "freeze_backup": "Pin it so it's not overwritten by automatic backups",
"unfreeze_backup": "Unpin it", "unfreeze_backup": "Unpin it",
"backup_frozen": "Backup pinned", "backup_frozen": "Backup pinned",
"backup_unfrozen": "Backup unpinned" "backup_unfrozen": "Backup unpinned",
"backup_freeze_failed": "Failed to freeze backup",
"backup_freeze_failed_description": "You must leave at least one free slot for automatic backups"
}, },
"activation": { "activation": {
"title": "Activate Hydra", "title": "Activate Hydra",

View File

@@ -204,7 +204,9 @@
"freeze_backup": "Fixar para não ser apagado por backups automáticos", "freeze_backup": "Fixar para não ser apagado por backups automáticos",
"unfreeze_backup": "Remover dos fixados", "unfreeze_backup": "Remover dos fixados",
"backup_frozen": "Backup fixado", "backup_frozen": "Backup fixado",
"backup_unfrozen": "Backup removido dos fixados" "backup_unfrozen": "Backup removido dos fixados",
"backup_freeze_failed": "Falha ao fixar backup",
"backup_freeze_failed_description": "Você deve deixar pelo menos um espaço livre para backups automáticos"
}, },
"activation": { "activation": {
"title": "Ativação", "title": "Ativação",

View File

@@ -135,6 +135,7 @@ export function CloudSyncContextProvider({
getGameArtifacts(); getGameArtifacts();
} catch (err) { } catch (err) {
logger.error("Failed to toggle artifact freeze", objectId, shop, err); logger.error("Failed to toggle artifact freeze", objectId, shop, err);
throw err;
} finally { } finally {
setFreezingArtifact(false); setFreezingArtifact(false);
} }

View File

@@ -1,7 +1,6 @@
import { Button, Modal, ModalProps } from "@renderer/components"; import { Button, Modal, ModalProps } from "@renderer/components";
import { useContext, useEffect, useMemo, useState } from "react"; import { useContext, useEffect, useMemo, useState } from "react";
import { cloudSyncContext, gameDetailsContext } from "@renderer/context"; import { cloudSyncContext, gameDetailsContext } from "@renderer/context";
import "./cloud-sync-modal.scss"; import "./cloud-sync-modal.scss";
import { formatBytes } from "@shared"; import { formatBytes } from "@shared";
import { import {
@@ -22,6 +21,8 @@ import { AxiosProgressEvent } from "axios";
import { formatDownloadProgress } from "@renderer/helpers"; import { formatDownloadProgress } from "@renderer/helpers";
import { CloudSyncRenameArtifactModal } from "../cloud-sync-rename-artifact-modal/cloud-sync-rename-artifact-modal"; import { CloudSyncRenameArtifactModal } from "../cloud-sync-rename-artifact-modal/cloud-sync-rename-artifact-modal";
import { GameArtifact } from "@types"; import { GameArtifact } from "@types";
import { motion, AnimatePresence } from "framer-motion";
import { orderBy } from "lodash-es";
export interface CloudSyncModalProps export interface CloudSyncModalProps
extends Omit<ModalProps, "children" | "title"> {} extends Omit<ModalProps, "children" | "title"> {}
@@ -35,7 +36,6 @@ export function CloudSyncModal({ visible, onClose }: CloudSyncModalProps) {
); );
const { t } = useTranslation("game_details"); const { t } = useTranslation("game_details");
const { formatDate, formatDateTime } = useDate(); const { formatDate, formatDateTime } = useDate();
const { const {
@@ -60,10 +60,8 @@ export function CloudSyncModal({ visible, onClose }: CloudSyncModalProps) {
const handleDeleteArtifactClick = async (gameArtifactId: string) => { const handleDeleteArtifactClick = async (gameArtifactId: string) => {
setDeletingArtifact(true); setDeletingArtifact(true);
try { try {
await deleteGameArtifact(gameArtifactId); await deleteGameArtifact(gameArtifactId);
showSuccessToast(t("backup_deleted")); showSuccessToast(t("backup_deleted"));
} catch (err) { } catch (err) {
showErrorToast("backup_deletion_failed"); showErrorToast("backup_deletion_failed");
@@ -81,7 +79,6 @@ export function CloudSyncModal({ visible, onClose }: CloudSyncModalProps) {
setBackupDownloadProgress(progressEvent); setBackupDownloadProgress(progressEvent);
} }
); );
return () => { return () => {
removeBackupDownloadProgressListener(); removeBackupDownloadProgressListener();
}; };
@@ -96,12 +93,14 @@ export function CloudSyncModal({ visible, onClose }: CloudSyncModalProps) {
artifactId: string, artifactId: string,
isFrozen: boolean isFrozen: boolean
) => { ) => {
await toggleArtifactFreeze(artifactId, isFrozen); try {
await toggleArtifactFreeze(artifactId, isFrozen);
if (isFrozen) { showSuccessToast(isFrozen ? t("backup_frozen") : t("backup_unfrozen"));
showSuccessToast(t("backup_frozen")); } catch (err) {
} else { showErrorToast(
showSuccessToast(t("backup_unfrozen")); t("backup_freeze_failed"),
t("backup_freeze_failed_description")
);
} }
}; };
@@ -123,7 +122,6 @@ export function CloudSyncModal({ visible, onClose }: CloudSyncModalProps) {
</span> </span>
); );
} }
if (restoringBackup) { if (restoringBackup) {
return ( return (
<span className="cloud-sync-modal__backup-state-label"> <span className="cloud-sync-modal__backup-state-label">
@@ -136,7 +134,6 @@ export function CloudSyncModal({ visible, onClose }: CloudSyncModalProps) {
</span> </span>
); );
} }
if (loadingPreview) { if (loadingPreview) {
return ( return (
<span className="cloud-sync-modal__backup-state-label"> <span className="cloud-sync-modal__backup-state-label">
@@ -145,19 +142,15 @@ export function CloudSyncModal({ visible, onClose }: CloudSyncModalProps) {
</span> </span>
); );
} }
if (artifacts.length >= backupsPerGameLimit) { if (artifacts.length >= backupsPerGameLimit) {
return t("max_number_of_artifacts_reached"); return t("max_number_of_artifacts_reached");
} }
if (!backupPreview) { if (!backupPreview) {
return t("no_backup_preview"); return t("no_backup_preview");
} }
if (artifacts.length === 0) { if (artifacts.length === 0) {
return t("no_backups"); return t("no_backups");
} }
return ""; return "";
}, [ }, [
uploadingBackup, uploadingBackup,
@@ -194,7 +187,6 @@ export function CloudSyncModal({ visible, onClose }: CloudSyncModalProps) {
<div className="cloud-sync-modal__title-container"> <div className="cloud-sync-modal__title-container">
<h2>{gameTitle}</h2> <h2>{gameTitle}</h2>
<p>{backupStateLabel}</p> <p>{backupStateLabel}</p>
<button <button
type="button" type="button"
className="cloud-sync-modal__manage-files-button" className="cloud-sync-modal__manage-files-button"
@@ -235,83 +227,99 @@ export function CloudSyncModal({ visible, onClose }: CloudSyncModalProps) {
{artifacts.length > 0 ? ( {artifacts.length > 0 ? (
<ul className="cloud-sync-modal__artifacts"> <ul className="cloud-sync-modal__artifacts">
{artifacts.map((artifact) => ( <AnimatePresence>
<li key={artifact.id} className="cloud-sync-modal__artifact"> {orderBy(artifacts, [(a) => !a.isFrozen], ["asc"]).map(
<div className="cloud-sync-modal__artifact-info"> (artifact) => (
<div className="cloud-sync-modal__artifact-header"> <motion.li
<button key={artifact.id}
type="button" className="cloud-sync-modal__artifact"
className="cloud-sync-modal__artifact-label" layout
onClick={() => setArtifactToRename(artifact)} initial={{ opacity: 0, y: 10 }}
> animate={{ opacity: 1, y: 0 }}
{artifact.label ?? exit={{ opacity: 0, y: -10 }}
t("backup_from", { transition={{ duration: 0.2 }}
date: formatDate(artifact.createdAt),
})}
<PencilIcon />
</button>
<small>{formatBytes(artifact.artifactLengthInBytes)}</small>
</div>
<span className="cloud-sync-modal__artifact-meta">
<DeviceDesktopIcon size={14} />
{artifact.hostname}
</span>
<span className="cloud-sync-modal__artifact-meta">
<InfoIcon size={14} />
{artifact.downloadOptionTitle ??
t("no_download_option_info")}
</span>
<span className="cloud-sync-modal__artifact-meta">
<ClockIcon size={14} />
{formatDateTime(artifact.createdAt)}
</span>
</div>
<div className="cloud-sync-modal__artifact-actions">
<Button
type="button"
tooltip={
artifact.isFrozen
? t("unfreeze_backup")
: t("freeze_backup")
}
theme={artifact.isFrozen ? "primary" : "outline"}
onClick={() =>
handleFreezeArtifactClick(artifact.id, !artifact.isFrozen)
}
disabled={disableActions}
> >
{artifact.isFrozen ? <PinSlashIcon /> : <PinIcon />} <div className="cloud-sync-modal__artifact-info">
</Button> <div className="cloud-sync-modal__artifact-header">
<Button <button
type="button" type="button"
onClick={() => handleBackupInstallClick(artifact.id)} className="cloud-sync-modal__artifact-label"
disabled={disableActions} onClick={() => setArtifactToRename(artifact)}
theme="outline" >
> {artifact.label ??
{restoringBackup ? ( t("backup_from", {
<SyncIcon className="cloud-sync-modal__sync-icon" /> date: formatDate(artifact.createdAt),
) : ( })}
<HistoryIcon /> <PencilIcon />
)} </button>
{t("install_backup")} <small>
</Button> {formatBytes(artifact.artifactLengthInBytes)}
<Button </small>
type="button" </div>
onClick={() => handleDeleteArtifactClick(artifact.id)}
disabled={disableActions || artifact.isFrozen} <span className="cloud-sync-modal__artifact-meta">
theme="outline" <DeviceDesktopIcon size={14} />
tooltip={t("delete_backup")} {artifact.hostname}
> </span>
<TrashIcon />
</Button> <span className="cloud-sync-modal__artifact-meta">
</div> <InfoIcon size={14} />
</li> {artifact.downloadOptionTitle ??
))} t("no_download_option_info")}
</span>
<span className="cloud-sync-modal__artifact-meta">
<ClockIcon size={14} />
{formatDateTime(artifact.createdAt)}
</span>
</div>
<div className="cloud-sync-modal__artifact-actions">
<Button
type="button"
tooltip={
artifact.isFrozen
? t("unfreeze_backup")
: t("freeze_backup")
}
theme={artifact.isFrozen ? "primary" : "outline"}
onClick={() =>
handleFreezeArtifactClick(
artifact.id,
!artifact.isFrozen
)
}
disabled={disableActions}
>
{artifact.isFrozen ? <PinSlashIcon /> : <PinIcon />}
</Button>
<Button
type="button"
onClick={() => handleBackupInstallClick(artifact.id)}
disabled={disableActions}
theme="outline"
>
{restoringBackup ? (
<SyncIcon className="cloud-sync-modal__sync-icon" />
) : (
<HistoryIcon />
)}
{t("install_backup")}
</Button>
<Button
type="button"
onClick={() => handleDeleteArtifactClick(artifact.id)}
disabled={disableActions || artifact.isFrozen}
theme="outline"
tooltip={t("delete_backup")}
>
<TrashIcon />
</Button>
</div>
</motion.li>
)
)}
</AnimatePresence>
</ul> </ul>
) : ( ) : (
<p>{t("no_backups_created")}</p> <p>{t("no_backups_created")}</p>

View File

@@ -5646,6 +5646,15 @@ formdata-polyfill@^4.0.10:
dependencies: dependencies:
fetch-blob "^3.1.2" fetch-blob "^3.1.2"
framer-motion@^12.15.0:
version "12.15.0"
resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-12.15.0.tgz#6892283fc7967b071f537d6d160ab49e3d5e73ae"
integrity sha512-XKg/LnKExdLGugZrDILV7jZjI599785lDIJZLxMiiIFidCsy0a4R2ZEf+Izm67zyOuJgQYTHOmodi7igQsw3vg==
dependencies:
motion-dom "^12.15.0"
motion-utils "^12.12.1"
tslib "^2.4.0"
fs-extra@^10.0.0, fs-extra@^10.1.0: fs-extra@^10.0.0, fs-extra@^10.1.0:
version "10.1.0" version "10.1.0"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf"
@@ -7304,6 +7313,18 @@ module-error@^1.0.1:
resolved "https://registry.yarnpkg.com/module-error/-/module-error-1.0.2.tgz#8d1a48897ca883f47a45816d4fb3e3c6ba404d86" resolved "https://registry.yarnpkg.com/module-error/-/module-error-1.0.2.tgz#8d1a48897ca883f47a45816d4fb3e3c6ba404d86"
integrity sha512-0yuvsqSCv8LbaOKhnsQ/T5JhyFlCYLPXK3U2sgV10zoKQwzs/MyfuQUOZQ1V/6OCOJsK/TRgNVrPuPDqtdMFtA== integrity sha512-0yuvsqSCv8LbaOKhnsQ/T5JhyFlCYLPXK3U2sgV10zoKQwzs/MyfuQUOZQ1V/6OCOJsK/TRgNVrPuPDqtdMFtA==
motion-dom@^12.15.0:
version "12.15.0"
resolved "https://registry.yarnpkg.com/motion-dom/-/motion-dom-12.15.0.tgz#eca7c9d8c28976b8c920f175f92d5288f5a17785"
integrity sha512-D2ldJgor+2vdcrDtKJw48k3OddXiZN1dDLLWrS8kiHzQdYVruh0IoTwbJBslrnTXIPgFED7PBN2Zbwl7rNqnhA==
dependencies:
motion-utils "^12.12.1"
motion-utils@^12.12.1:
version "12.12.1"
resolved "https://registry.yarnpkg.com/motion-utils/-/motion-utils-12.12.1.tgz#63e28751325cb9d1cd684f3c273a570022b0010e"
integrity sha512-f9qiqUHm7hWSLlNW8gS9pisnsN7CRFRD58vNjptKdsqFLpkVnX00TNeD6Q0d27V9KzT7ySFyK1TZ/DShfVOv6w==
ms@^2.0.0, ms@^2.1.1, ms@^2.1.3: ms@^2.0.0, ms@^2.1.1, ms@^2.1.3:
version "2.1.3" version "2.1.3"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
@@ -9041,7 +9062,7 @@ ts-node@^10.9.2:
v8-compile-cache-lib "^3.0.1" v8-compile-cache-lib "^3.0.1"
yn "3.1.1" yn "3.1.1"
tslib@^2.0.0, tslib@^2.1.0: tslib@^2.0.0, tslib@^2.1.0, tslib@^2.4.0:
version "2.8.1" version "2.8.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f"
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==