diff --git a/src/main/events/cloud-save/toggle-artifact-freeze.ts b/src/main/events/cloud-save/toggle-artifact-freeze.ts new file mode 100644 index 00000000..d532d459 --- /dev/null +++ b/src/main/events/cloud-save/toggle-artifact-freeze.ts @@ -0,0 +1,16 @@ +import { registerEvent } from "../register-event"; +import { HydraApi } from "@main/services"; + +const toggleArtifactFreeze = async ( + _event: Electron.IpcMainInvokeEvent, + gameArtifactId: string, + freeze: boolean +) => { + if (freeze) { + await HydraApi.put(`/profile/games/artifacts/${gameArtifactId}/freeze`); + } else { + await HydraApi.put(`/profile/games/artifacts/${gameArtifactId}/unfreeze`); + } +}; + +registerEvent("toggleArtifactFreeze", toggleArtifactFreeze); diff --git a/src/main/events/index.ts b/src/main/events/index.ts index ad72163e..94520467 100644 --- a/src/main/events/index.ts +++ b/src/main/events/index.ts @@ -86,6 +86,7 @@ import "./cloud-save/get-game-backup-preview"; import "./cloud-save/upload-save-game"; import "./cloud-save/delete-game-artifact"; import "./cloud-save/select-game-backup-path"; +import "./cloud-save/toggle-artifact-freeze"; import "./notifications/publish-new-repacks-notification"; import "./themes/add-custom-theme"; import "./themes/delete-custom-theme"; diff --git a/src/preload/index.ts b/src/preload/index.ts index 6695fa2b..8cf2492d 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -238,6 +238,8 @@ contextBridge.exposeInMainWorld("electron", { downloadOptionTitle: string | null ) => ipcRenderer.invoke("uploadSaveGame", objectId, shop, downloadOptionTitle), + toggleArtifactFreeze: (gameArtifactId: string, freeze: boolean) => + ipcRenderer.invoke("toggleArtifactFreeze", gameArtifactId, freeze), downloadGameArtifact: ( objectId: string, shop: GameShop, diff --git a/src/renderer/src/context/cloud-sync/cloud-sync.context.tsx b/src/renderer/src/context/cloud-sync/cloud-sync.context.tsx index 5671ca56..51b7d332 100644 --- a/src/renderer/src/context/cloud-sync/cloud-sync.context.tsx +++ b/src/renderer/src/context/cloud-sync/cloud-sync.context.tsx @@ -30,9 +30,14 @@ export interface CloudSyncContext { setShowCloudSyncFilesModal: React.Dispatch>; getGameBackupPreview: () => Promise; getGameArtifacts: () => Promise; + toggleArtifactFreeze: ( + gameArtifactId: string, + freeze: boolean + ) => Promise; restoringBackup: boolean; uploadingBackup: boolean; loadingPreview: boolean; + freezingArtifact: boolean; } export const cloudSyncContext = createContext({ @@ -47,10 +52,12 @@ export const cloudSyncContext = createContext({ showCloudSyncFilesModal: false, setShowCloudSyncFilesModal: () => {}, getGameBackupPreview: async () => {}, + toggleArtifactFreeze: async () => {}, getGameArtifacts: async () => {}, restoringBackup: false, uploadingBackup: false, loadingPreview: false, + freezingArtifact: false, }); const { Provider } = cloudSyncContext; @@ -78,6 +85,7 @@ export function CloudSyncContextProvider({ const [uploadingBackup, setUploadingBackup] = useState(false); const [showCloudSyncFilesModal, setShowCloudSyncFilesModal] = useState(false); const [loadingPreview, setLoadingPreview] = useState(false); + const [freezingArtifact, setFreezingArtifact] = useState(false); const { showSuccessToast } = useToast(); @@ -119,6 +127,21 @@ export function CloudSyncContextProvider({ [objectId, shop] ); + const toggleArtifactFreeze = useCallback( + async (gameArtifactId: string, freeze: boolean) => { + setFreezingArtifact(true); + try { + await window.electron.toggleArtifactFreeze(gameArtifactId, freeze); + getGameArtifacts(); + } catch (err) { + logger.error("Failed to toggle artifact freeze", objectId, shop, err); + } finally { + setFreezingArtifact(false); + } + }, + [objectId, shop, getGameArtifacts] + ); + useEffect(() => { const removeUploadCompleteListener = window.electron.onUploadComplete( objectId, @@ -192,6 +215,7 @@ export function CloudSyncContextProvider({ uploadingBackup, showCloudSyncFilesModal, loadingPreview, + freezingArtifact, setShowCloudSyncModal, uploadSaveGame, downloadGameArtifact, @@ -199,6 +223,7 @@ export function CloudSyncContextProvider({ setShowCloudSyncFilesModal, getGameBackupPreview, getGameArtifacts, + toggleArtifactFreeze, }} > {children} diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index 7eaa4ee0..a3063591 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -202,6 +202,10 @@ declare global { shop: GameShop, downloadOptionTitle: string | null ) => Promise; + toggleArtifactFreeze: ( + gameArtifactId: string, + freeze: boolean + ) => Promise; downloadGameArtifact: ( objectId: string, shop: GameShop, diff --git a/src/renderer/src/pages/game-details/cloud-sync-modal/cloud-sync-modal.tsx b/src/renderer/src/pages/game-details/cloud-sync-modal/cloud-sync-modal.tsx index bd70aec1..0f6a2a92 100644 --- a/src/renderer/src/pages/game-details/cloud-sync-modal/cloud-sync-modal.tsx +++ b/src/renderer/src/pages/game-details/cloud-sync-modal/cloud-sync-modal.tsx @@ -7,10 +7,13 @@ import { formatBytes } from "@shared"; import { ClockIcon, DeviceDesktopIcon, - HistoryIcon, + DownloadIcon, InfoIcon, + LockIcon, + PencilIcon, SyncIcon, TrashIcon, + UnlockIcon, UploadIcon, } from "@primer/octicons-react"; import { useAppSelector, useDate, useToast } from "@renderer/hooks"; @@ -36,9 +39,11 @@ export function CloudSyncModal({ visible, onClose }: CloudSyncModalProps) { uploadingBackup, restoringBackup, loadingPreview, + freezingArtifact, uploadSaveGame, downloadGameArtifact, deleteGameArtifact, + toggleArtifactFreeze, setShowCloudSyncFilesModal, getGameBackupPreview, } = useContext(cloudSyncContext); @@ -82,6 +87,13 @@ export function CloudSyncModal({ visible, onClose }: CloudSyncModalProps) { downloadGameArtifact(artifactId); }; + const handleFreezeArtifactClick = async (artifactId: string) => { + await toggleArtifactFreeze( + artifactId, + !artifacts.find((artifact) => artifact.id === artifactId)?.isFrozen + ); + }; + useEffect(() => { if (visible) { getGameBackupPreview(); @@ -147,7 +159,8 @@ export function CloudSyncModal({ visible, onClose }: CloudSyncModalProps) { t, ]); - const disableActions = uploadingBackup || restoringBackup || deletingArtifact; + const disableActions = + uploadingBackup || restoringBackup || deletingArtifact || freezingArtifact; const isMissingWinePrefix = window.electron.platform === "linux" && !game?.winePrefixPath; @@ -208,12 +221,14 @@ export function CloudSyncModal({ visible, onClose }: CloudSyncModalProps) {
  • -

    +

    + + + {formatBytes(artifact.artifactLengthInBytes)}
    @@ -234,23 +249,37 @@ export function CloudSyncModal({ visible, onClose }: CloudSyncModalProps) {
    +