From 02d19d26a03d9290d5abf7daae431fac504fd47b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Renan=20Ara=C3=BAjo?= Date: Mon, 30 Mar 2026 20:17:50 -0300 Subject: [PATCH 1/2] feat(cloud): add game name metadata to cloud backups Enhance cloud backup functionality by storing and displaying game names alongside backup metadata. This improves user experience by making it easier to identify backups in the cloud list without relying solely on game IDs. Changes include: - Update uploadSnapshot to accept and store gameName in Google Drive app properties - Modify listAllCloudSnapshots to retrieve and include gameName from metadata - Enhance CloudBackupListModal to fetch game names and display them in backup list - Update GameDetail to pass game name when uploading snapshots --- README.md | 6 +++++- src/components/CloudBackupListModal.tsx | 16 ++++++++++++++-- src/components/GameDetail.tsx | 1 + src/lib/googleDrive.ts | 12 ++++++++---- 4 files changed, 28 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 19050c6..eeb72da 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Checkpoint is a save game backup tool for local games. Create timestamped backup - **100% Private** - Your data stays on your device. We have zero access to your saves or Google Drive - **Smart Protection** - Automatically backs up current save before restoring - **Process Detection** - Prevents restore while game is running -- **Cross-Platform** - Windows and Linux support +- **Cross-Platform** - Windows, macOS, and Linux support ## Your Data, Your Control @@ -29,6 +29,9 @@ Checkpoint is built with privacy as the foundation: ### Windows - Download the `.exe` installer +### macOS +- Download the `.dmg` installer or `.app.tar.gz` archive + ### Linux - **Ubuntu/Debian**: `checkpoint_0.1.0_amd64.deb` - **Fedora**: `checkpoint-0.1.0-1.x86_64.rpm` @@ -72,6 +75,7 @@ Checkpoint is built with privacy as the foundation: ## System Requirements - **Windows**: Windows 10 or later +- **macOS**: macOS 10.15 Catalina or later - **Linux**: Any modern distro with GTK3 ## Support diff --git a/src/components/CloudBackupListModal.tsx b/src/components/CloudBackupListModal.tsx index bbbe967..986e045 100644 --- a/src/components/CloudBackupListModal.tsx +++ b/src/components/CloudBackupListModal.tsx @@ -5,6 +5,7 @@ import { useProfile } from '../lib/profileContext'; import { useToast } from '../lib/toastContext'; import { ConfirmModal } from './ConfirmModal'; import { listAllCloudSnapshots, downloadSnapshot, deleteCloudSnapshot, type CloudBackupItem } from '../lib/googleDrive'; +import { listGames } from '../lib/api'; interface CloudBackupListModalProps { isOpen: boolean; @@ -36,8 +37,19 @@ export function CloudBackupListModal({ isOpen, onClose, onDownload }: CloudBacku throw new Error(t('errors.notAuthenticated')); } - const allBackups = await listAllCloudSnapshots(token); - setBackups(allBackups); + const [allBackups, games] = await Promise.all([ + listAllCloudSnapshots(token), + listGames() + ]); + + const gamesMap = new Map(games.map(g => [g.id, g.name])); + + const backupsWithNames = allBackups.map(b => ({ + ...b, + gameName: b.gameName || (b.gameId ? gamesMap.get(b.gameId) : undefined) + })); + + setBackups(backupsWithNames); } catch (err) { const errorMsg = err instanceof Error ? err.message : t('cloud.failedLoadCloudBackups'); addToast(errorMsg, 'error'); diff --git a/src/components/GameDetail.tsx b/src/components/GameDetail.tsx index 7a22915..8ded153 100644 --- a/src/components/GameDetail.tsx +++ b/src/components/GameDetail.tsx @@ -496,6 +496,7 @@ export function GameDetail({ game, onBack, onGameDeleted, onGameUpdated, setLoad const fileId = await uploadSnapshot( token, game.id, + game.name, snapshot.name, zipBlob ); diff --git a/src/lib/googleDrive.ts b/src/lib/googleDrive.ts index 3b87896..0b63a6d 100644 --- a/src/lib/googleDrive.ts +++ b/src/lib/googleDrive.ts @@ -121,12 +121,16 @@ export async function getDriveStorageInfo(accessToken: string): Promise<{ used: export async function uploadSnapshot( accessToken: string, gameId: string, + gameName: string, snapshotName: string, fileBlob: Blob ): Promise { const metadata = { name: `${gameId}/${snapshotName}.zip`, - parents: ['appDataFolder'] + parents: ['appDataFolder'], + appProperties: { + gameName: gameName + } }; const boundary = '-------314159265358979323846'; @@ -191,7 +195,7 @@ export async function listCloudSnapshots(accessToken: string, gameId: string): P export async function listAllCloudSnapshots(accessToken: string): Promise { const query = encodeURIComponent(`name contains '/' and trashed = false`); - const response = await fetch(`${GOOGLE_DRIVE_ENDPOINT}/files?q=${query}&spaces=appDataFolder&fields=files(id,name,modifiedTime,size)`, { + const response = await fetch(`${GOOGLE_DRIVE_ENDPOINT}/files?q=${query}&spaces=appDataFolder&fields=files(id,name,modifiedTime,size,appProperties)`, { headers: { Authorization: `Bearer ${accessToken}` } }); @@ -202,7 +206,7 @@ export async function listAllCloudSnapshots(accessToken: string): Promise { + return files.map((file: { id: string; name: string; modifiedTime: string; size?: string; appProperties?: { gameName?: string } }) => { const nameParts = file.name.split('/'); const isSnapshot = nameParts.length === 2 && nameParts[1].endsWith('.zip'); @@ -213,7 +217,7 @@ export async function listAllCloudSnapshots(accessToken: string): Promise file.gameId); } From d15233d7c1573c5bebd7122b7263fa76cc00e3f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Renan=20Ara=C3=BAjo?= Date: Fri, 10 Apr 2026 12:41:14 -0300 Subject: [PATCH 2/2] refactor(ui): move cloud backup modal header styles to CSS classes Replace inline styles in CloudBackupListModal header with new CSS classes for improved maintainability and consistency. --- src/App.css | 18 ++++++++++++++++++ src/components/CloudBackupListModal.tsx | 6 +++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/App.css b/src/App.css index 1f88b97..1db03b8 100644 --- a/src/App.css +++ b/src/App.css @@ -631,6 +631,24 @@ html { color: var(--text-primary); } +.cloud-backup-modal-title { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.cloud-backup-modal-title-icon { + flex-shrink: 0; + display: block; +} + +.cloud-backup-modal-heading { + font-size: 1rem; + font-weight: 700; + line-height: 1; + color: var(--text-primary); +} + .modal-close { background: var(--bg-tertiary); border: none; diff --git a/src/components/CloudBackupListModal.tsx b/src/components/CloudBackupListModal.tsx index 986e045..0230a0d 100644 --- a/src/components/CloudBackupListModal.tsx +++ b/src/components/CloudBackupListModal.tsx @@ -173,9 +173,9 @@ export function CloudBackupListModal({ isOpen, onClose, onDownload }: CloudBacku >
e.stopPropagation()} style={{ maxWidth: '600px' }}>
-
- -

{t('cloud.cloudBackups')}

+
+ +
{t('cloud.cloudBackups')}