diff --git a/package.json b/package.json index f869f42f..827f3faa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hydralauncher", - "version": "2.1.7-preview", + "version": "3.0.0", "description": "Hydra", "main": "./out/main/index.js", "author": "Los Broxas", diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index a4426d2c..c5664c1e 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -148,7 +148,19 @@ "backup_deleted": "Backup deleted", "backup_restored": "Backup restored", "see_all_achievements": "See all achievements", - "sign_in_to_see_achievements": "Sign in to see achievements" + "sign_in_to_see_achievements": "Sign in to see achievements", + "mapping_method_automatic": "Automatic", + "mapping_method_manual": "Manual", + "mapping_method_label": "Mapping method", + "files_automatically_mapped": "Files automatically mapped", + "no_backups_created": "No backups created for this game", + "manage_files": "Manage files", + "loading_save_preview": "Searching for save games…", + "wine_prefix": "Wine Prefix", + "wine_prefix_description": "The Wine prefix used to run this game", + "no_download_option_info": "No information available", + "backup_deletion_failed": "Failed to delete backup", + "max_number_of_artifacts_reached": "Maximum number of backups reached for this game" }, "activation": { "title": "Activate Hydra", @@ -333,7 +345,9 @@ "report_reason_spam": "Spam", "report_reason_other": "Other", "profile_reported": "Profile reported", - "your_friend_code": "Your friend code:" + "your_friend_code": "Your friend code:", + "upload_banner": "Upload banner", + "uploading_banner": "Uploading banner…" }, "achievement": { "achievement_unlocked": "Achievement unlocked", diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index 77ebe906..925ea486 100644 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -5,9 +5,9 @@ }, "home": { "featured": "Destaques", - "trending": "Populares", - "hot": "Populares agora", + "hot": "Populares", "weekly": "📅 Mais baixados da semana", + "achievements": "🏆 Pra platinar", "surprise_me": "Surpreenda-me", "no_results": "Nenhum resultado encontrado", "start_typing": "Comece a digitar para pesquisar…" @@ -144,7 +144,19 @@ "backup_deleted": "Backup apagado", "backup_restored": "Backup restaurado", "see_all_achievements": "Ver todas as conquistas", - "sign_in_to_see_achievements": "Faça login para ver as conquistas" + "sign_in_to_see_achievements": "Faça login para ver as conquistas", + "mapping_method_automatic": "Automático", + "mapping_method_manual": "Manual", + "mapping_method_label": "Método de mapeamento", + "files_automatically_mapped": "Arquivos automaticamente mapeados", + "no_backups_created": "Nenhum backup criado para este jogo", + "manage_files": "Gerenciar arquivos", + "loading_save_preview": "Buscando por arquivos de salvamento…", + "wine_prefix": "Prefixo Wine", + "wine_prefix_description": "O prefixo Wine que foi utilizado para instalar o jogo", + "no_download_option_info": "Sem informações disponíveis", + "backup_deletion_failed": "Falha ao apagar backup", + "max_number_of_artifacts_reached": "Número máximo de backups atingido para este jogo" }, "activation": { "title": "Ativação", @@ -335,7 +347,9 @@ "report_reason_spam": "Spam", "report_reason_other": "Outro", "profile_reported": "Perfil reportado", - "your_friend_code": "Seu código de amigo:" + "your_friend_code": "Seu código de amigo:", + "upload_banner": "Carregar banner", + "uploading_banner": "Carregando banner…" }, "achievement": { "achievement_unlocked": "Conquista desbloqueada", diff --git a/src/main/events/catalogue/get-catalogue.ts b/src/main/events/catalogue/get-catalogue.ts index 145f3166..a0542f25 100644 --- a/src/main/events/catalogue/get-catalogue.ts +++ b/src/main/events/catalogue/get-catalogue.ts @@ -15,7 +15,7 @@ const getCatalogue = async ( }); const response = await HydraApi.get<{ objectId: string; shop: GameShop }[]>( - `/games/${category}?${params.toString()}`, + `/catalogue/${category}?${params.toString()}`, {}, { needsAuth: false } ); diff --git a/src/main/events/index.ts b/src/main/events/index.ts index ffdfc354..0fac695b 100644 --- a/src/main/events/index.ts +++ b/src/main/events/index.ts @@ -66,6 +66,7 @@ import "./cloud-save/upload-save-game"; import "./cloud-save/delete-game-artifact"; import "./notifications/publish-new-repacks-notification"; import { isPortableVersion } from "@main/helpers"; +import "./misc/show-item-in-folder"; ipcMain.handle("ping", () => "pong"); ipcMain.handle("getVersion", () => appVersion); diff --git a/src/main/services/ludusavi.ts b/src/main/services/ludusavi.ts index 8d0b2ee9..8b64ba11 100644 --- a/src/main/services/ludusavi.ts +++ b/src/main/services/ludusavi.ts @@ -7,9 +7,6 @@ import path from "node:path"; import YAML from "yaml"; import ludusaviWorkerPath from "../workers/ludusavi.worker?modulePath"; -import axios from "axios"; - -let a: Record | null = null; export class Ludusavi { private static ludusaviPath = path.join(app.getPath("appData"), "ludusavi"); @@ -65,26 +62,14 @@ export class Ludusavi { } static async getBackupPreview( - _shop: GameShop, + shop: GameShop, objectId: string, backupPath: string ): Promise { - if (!a) { - await axios - .get( - "https://gist.githubusercontent.com/thegrannychaseroperation/b23d53e654e3ea060066a5c01b0cacc8/raw/57bf254a1c99dab9315136f660ff7b3d547de215/keys.json" - ) - .then((response) => { - a = response.data; - return response.data; - }); - } + const games = await this.findGames(shop, objectId); - const game = a?.[objectId]; - - // if (!games.length) return null; - - // const [game] = games; + if (!games.length) return null; + const [game] = games; const backupData = await this.worker.run( { title: game, backupPath, preview: true }, diff --git a/src/preload/index.ts b/src/preload/index.ts index 0eadd409..3d541408 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -215,6 +215,8 @@ contextBridge.exposeInMainWorld("electron", { openExternal: (src: string) => ipcRenderer.invoke("openExternal", src), showOpenDialog: (options: Electron.OpenDialogOptions) => ipcRenderer.invoke("showOpenDialog", options), + showItemInFolder: (path: string) => + ipcRenderer.invoke("showItemInFolder", path), platform: process.platform, /* Auto update */ diff --git a/src/renderer/src/components/bottom-panel/bottom-panel.tsx b/src/renderer/src/components/bottom-panel/bottom-panel.tsx index 045158a5..0d28a26e 100644 --- a/src/renderer/src/components/bottom-panel/bottom-panel.tsx +++ b/src/renderer/src/components/bottom-panel/bottom-panel.tsx @@ -81,10 +81,10 @@ export function BottomPanel() { {status} - {/* + {sessionHash ? `${sessionHash} -` : ""} v{version} " {VERSION_CODENAME}" - */} + ); } diff --git a/src/renderer/src/constants.ts b/src/renderer/src/constants.ts index 5835640f..1cea1d3a 100644 --- a/src/renderer/src/constants.ts +++ b/src/renderer/src/constants.ts @@ -1,6 +1,6 @@ import { Downloader } from "@shared"; -export const VERSION_CODENAME = "Leviticus"; +export const VERSION_CODENAME = "Skyscraper"; export const DOWNLOADER_NAME = { [Downloader.RealDebrid]: "Real-Debrid", 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 6db61332..5671ca56 100644 --- a/src/renderer/src/context/cloud-sync/cloud-sync.context.tsx +++ b/src/renderer/src/context/cloud-sync/cloud-sync.context.tsx @@ -1,4 +1,3 @@ -import { gameBackupsTable } from "@renderer/dexie"; import { useToast } from "@renderer/hooks"; import { logger } from "@renderer/logger"; import type { LudusaviBackup, GameArtifact, GameShop } from "@types"; @@ -7,7 +6,6 @@ import React, { useCallback, useEffect, useMemo, - useRef, useState, } from "react"; import { useTranslation } from "react-i18next"; @@ -31,8 +29,10 @@ export interface CloudSyncContext { deleteGameArtifact: (gameArtifactId: string) => Promise; setShowCloudSyncFilesModal: React.Dispatch>; getGameBackupPreview: () => Promise; + getGameArtifacts: () => Promise; restoringBackup: boolean; uploadingBackup: boolean; + loadingPreview: boolean; } export const cloudSyncContext = createContext({ @@ -47,8 +47,10 @@ export const cloudSyncContext = createContext({ showCloudSyncFilesModal: false, setShowCloudSyncFilesModal: () => {}, getGameBackupPreview: async () => {}, + getGameArtifacts: async () => {}, restoringBackup: false, uploadingBackup: false, + loadingPreview: false, }); const { Provider } = cloudSyncContext; @@ -67,8 +69,6 @@ export function CloudSyncContextProvider({ }: CloudSyncContextProviderProps) { const { t } = useTranslation("game_details"); - const backupPreviewLock = useRef(""); - const [artifacts, setArtifacts] = useState([]); const [showCloudSyncModal, setShowCloudSyncModal] = useState(false); const [backupPreview, setBackupPreview] = useState( @@ -77,6 +77,7 @@ export function CloudSyncContextProvider({ const [restoringBackup, setRestoringBackup] = useState(false); const [uploadingBackup, setUploadingBackup] = useState(false); const [showCloudSyncFilesModal, setShowCloudSyncFilesModal] = useState(false); + const [loadingPreview, setLoadingPreview] = useState(false); const { showSuccessToast } = useToast(); @@ -88,32 +89,25 @@ export function CloudSyncContextProvider({ [objectId, shop] ); - const getGameBackupPreview = useCallback(async () => { - const backupPreviewLockKey = `${objectId}-${shop}`; + const getGameArtifacts = useCallback(async () => { + const results = await window.electron.getGameArtifacts(objectId, shop); + setArtifacts(results); + }, [objectId, shop]); - if (backupPreviewLock.current !== backupPreviewLockKey) { - backupPreviewLock.current = backupPreviewLockKey; - await Promise.allSettled([ - window.electron.getGameArtifacts(objectId, shop).then((results) => { - setArtifacts(results); - }), - window.electron - .getGameBackupPreview(objectId, shop) - .then((preview) => { - backupPreviewLock.current = ""; - if (preview && Object.keys(preview.games).length) { - setBackupPreview(preview); - } - }) - .catch((err) => { - logger.error( - "Failed to get game backup preview", - objectId, - shop, - err - ); - }), - ]); + const getGameBackupPreview = useCallback(async () => { + setLoadingPreview(true); + + try { + const preview = await window.electron.getGameBackupPreview( + objectId, + shop + ); + + setBackupPreview(preview); + } catch (err) { + logger.error("Failed to get game backup preview", objectId, shop, err); + } finally { + setLoadingPreview(false); } }, [objectId, shop]); @@ -131,14 +125,8 @@ export function CloudSyncContextProvider({ shop, () => { showSuccessToast(t("backup_uploaded")); - setUploadingBackup(false); - gameBackupsTable.add({ - objectId, - shop, - createdAt: new Date(), - }); - + getGameArtifacts(); getGameBackupPreview(); } ); @@ -148,6 +136,7 @@ export function CloudSyncContextProvider({ showSuccessToast(t("backup_restored")); setRestoringBackup(false); + getGameArtifacts(); getGameBackupPreview(); }); @@ -155,15 +144,23 @@ export function CloudSyncContextProvider({ removeUploadCompleteListener(); removeDownloadCompleteListener(); }; - }, [objectId, shop, showSuccessToast, t, getGameBackupPreview]); + }, [ + objectId, + shop, + showSuccessToast, + t, + getGameBackupPreview, + getGameArtifacts, + ]); const deleteGameArtifact = useCallback( async (gameArtifactId: string) => { return window.electron.deleteGameArtifact(gameArtifactId).then(() => { getGameBackupPreview(); + getGameArtifacts(); }); }, - [getGameBackupPreview] + [getGameBackupPreview, getGameArtifacts] ); useEffect(() => { @@ -194,12 +191,14 @@ export function CloudSyncContextProvider({ restoringBackup, uploadingBackup, showCloudSyncFilesModal, + loadingPreview, setShowCloudSyncModal, uploadSaveGame, downloadGameArtifact, deleteGameArtifact, setShowCloudSyncFilesModal, getGameBackupPreview, + getGameArtifacts, }} > {children} diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index e6a47959..14bc828b 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -171,6 +171,7 @@ declare global { showOpenDialog: ( options: Electron.OpenDialogOptions ) => Promise; + showItemInFolder: (path: string) => Promise; platform: NodeJS.Platform; /* Auto update */ diff --git a/src/renderer/src/dexie.ts b/src/renderer/src/dexie.ts index e0e86a7f..2e3565ce 100644 --- a/src/renderer/src/dexie.ts +++ b/src/renderer/src/dexie.ts @@ -1,13 +1,6 @@ import type { GameShop, HowLongToBeatCategory } from "@types"; import { Dexie } from "dexie"; -export interface GameBackup { - id?: number; - shop: GameShop; - objectId: string; - createdAt: Date; -} - export interface HowLongToBeatEntry { id?: number; objectId: string; @@ -22,13 +15,11 @@ export const db = new Dexie("Hydra"); db.version(4).stores({ repacks: `++id, title, uris, fileSize, uploadDate, downloadSourceId, repacker, createdAt, updatedAt`, downloadSources: `++id, url, name, etag, downloadCount, status, createdAt, updatedAt`, - gameBackups: `++id, [shop+objectId], createdAt`, howLongToBeatEntries: `++id, categories, [shop+objectId], createdAt, updatedAt`, }); export const downloadSourcesTable = db.table("downloadSources"); export const repacksTable = db.table("repacks"); -export const gameBackupsTable = db.table("gameBackups"); export const howLongToBeatEntriesTable = db.table( "howLongToBeatEntries" ); diff --git a/src/renderer/src/hooks/use-user-details.ts b/src/renderer/src/hooks/use-user-details.ts index 1872c95d..5c06965c 100644 --- a/src/renderer/src/hooks/use-user-details.ts +++ b/src/renderer/src/hooks/use-user-details.ts @@ -14,7 +14,6 @@ import type { UserDetails, } from "@types"; import { UserFriendModalTab } from "@renderer/pages/shared-modals/user-friend-modal"; -import { gameBackupsTable } from "@renderer/dexie"; export function useUserDetails() { const dispatch = useAppDispatch(); @@ -33,7 +32,6 @@ export function useUserDetails() { dispatch(setUserDetails(null)); dispatch(setProfileBackground(null)); - await gameBackupsTable.clear(); window.localStorage.removeItem("userDetails"); }, [dispatch]); @@ -130,7 +128,7 @@ export function useUserDetails() { const unblockUser = (userId: string) => window.electron.unblockUser(userId); const hasActiveSubscription = useMemo(() => { - if (!userDetails?.subscription) { + if (!userDetails?.subscription?.plan) { return false; } diff --git a/src/renderer/src/pages/game-details/cloud-sync-files-modal/cloud-sync-files-modal.css.ts b/src/renderer/src/pages/game-details/cloud-sync-files-modal/cloud-sync-files-modal.css.ts index bb3335fa..f6a46a08 100644 --- a/src/renderer/src/pages/game-details/cloud-sync-files-modal/cloud-sync-files-modal.css.ts +++ b/src/renderer/src/pages/game-details/cloud-sync-files-modal/cloud-sync-files-modal.css.ts @@ -1,26 +1,9 @@ import { style } from "@vanilla-extract/css"; -import { SPACING_UNIT, vars } from "../../../theme.css"; +import { SPACING_UNIT } from "../../../theme.css"; -export const artifacts = style({ - display: "flex", +export const mappingMethods = style({ + display: "grid", gap: `${SPACING_UNIT}px`, - flexDirection: "column", - listStyle: "none", - margin: "0", - padding: "0", -}); - -export const artifactButton = style({ - display: "flex", - textAlign: "left", - flexDirection: "row", - alignItems: "center", - gap: `${SPACING_UNIT}px`, - color: vars.color.body, - padding: `${SPACING_UNIT * 2}px`, - backgroundColor: vars.color.darkBackground, - border: `1px solid ${vars.color.border}`, - borderRadius: "4px", - justifyContent: "space-between", + gridTemplateColumns: "repeat(2, 1fr)", }); diff --git a/src/renderer/src/pages/game-details/cloud-sync-files-modal/cloud-sync-files-modal.tsx b/src/renderer/src/pages/game-details/cloud-sync-files-modal/cloud-sync-files-modal.tsx index ab416773..3bcba8a7 100644 --- a/src/renderer/src/pages/game-details/cloud-sync-files-modal/cloud-sync-files-modal.tsx +++ b/src/renderer/src/pages/game-details/cloud-sync-files-modal/cloud-sync-files-modal.tsx @@ -1,18 +1,34 @@ -import { Modal, ModalProps } from "@renderer/components"; -import { useContext, useMemo } from "react"; +import { Button, Modal, ModalProps } from "@renderer/components"; +import { useContext, useMemo, useState } from "react"; import { cloudSyncContext } from "@renderer/context"; -// import { useTranslation } from "react-i18next"; +import { useTranslation } from "react-i18next"; +import { CheckCircleFillIcon } from "@primer/octicons-react"; + +import * as styles from "./cloud-sync-files-modal.css"; +import { formatBytes } from "@shared"; +import { vars } from "@renderer/theme.css"; +// import { useToast } from "@renderer/hooks"; export interface CloudSyncFilesModalProps extends Omit {} +export enum FileMappingMethod { + Automatic = "AUTOMATIC", + Manual = "MANUAL", +} + export function CloudSyncFilesModal({ visible, onClose, }: CloudSyncFilesModalProps) { + const [selectedFileMappingMethod, setSelectedFileMappingMethod] = + useState(FileMappingMethod.Automatic); const { backupPreview } = useContext(cloudSyncContext); + // const { gameTitle } = useContext(gameDetailsContext); - // const { t } = useTranslation("game_details"); + const { t } = useTranslation("game_details"); + + // const { showSuccessToast } = useToast(); const files = useMemo(() => { if (!backupPreview) { @@ -20,6 +36,7 @@ export function CloudSyncFilesModal({ } const [game] = Object.values(backupPreview.games); + if (!game) return []; const entries = Object.entries(game.files); return entries.map(([key, value]) => { @@ -27,6 +44,19 @@ export function CloudSyncFilesModal({ }); }, [backupPreview]); + // const handleAddCustomPathClick = useCallback(async () => { + // const { filePaths } = await window.electron.showOpenDialog({ + // properties: ["openDirectory"], + // }); + + // if (filePaths && filePaths.length > 0) { + // const path = filePaths[0]; + // await window.electron.selectGameBackupDirectory(gameTitle, path); + // showSuccessToast("custom_backup_location_set"); + // getGameBackupPreview(); + // } + // }, [gameTitle, showSuccessToast, getGameBackupPreview]); + return ( - {/*
- {["AUTOMATIC", "CUSTOM"].map((downloader) => ( - - ))} -
*/} +
+ {t("mapping_method_label")} - {/* - {t("select_directory")} - - } - /> */} - - - - - - - - - - - {files.map((file) => ( - - - - - +
+ {Object.values(FileMappingMethod).map((mappingMethod) => ( + ))} -
-
ArquivoHashTamanho
{file.path}{file.change}{file.path}
+
+ + +
+ {/* + + {t("select_executable")} + + } + /> */} + +

{t("files_automatically_mapped")}

+ +
    + {files.map((file) => ( +
  • + +

    {formatBytes(file.bytes)}

    +
  • + ))} +
+
); } diff --git a/src/renderer/src/pages/game-details/cloud-sync-modal/cloud-sync-modal.css.ts b/src/renderer/src/pages/game-details/cloud-sync-modal/cloud-sync-modal.css.ts index 77231403..dc2d0031 100644 --- a/src/renderer/src/pages/game-details/cloud-sync-modal/cloud-sync-modal.css.ts +++ b/src/renderer/src/pages/game-details/cloud-sync-modal/cloud-sync-modal.css.ts @@ -49,3 +49,17 @@ export const progress = style({ backgroundColor: vars.color.muted, }, }); + +export const manageFilesButton = style({ + margin: "0", + padding: "0", + alignSelf: "flex-start", + fontSize: 14, + cursor: "pointer", + textDecoration: "underline", + color: vars.color.body, + ":disabled": { + cursor: "not-allowed", + opacity: vars.opacity.disabled, + }, +}); 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 87260f59..7a546eb4 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 @@ -6,7 +6,6 @@ import * as styles from "./cloud-sync-modal.css"; import { formatBytes } from "@shared"; import { format } from "date-fns"; import { - CheckCircleFillIcon, ClockIcon, DeviceDesktopIcon, HistoryIcon, @@ -16,18 +15,16 @@ import { UploadIcon, } from "@primer/octicons-react"; import { useToast } from "@renderer/hooks"; -import { GameBackup, gameBackupsTable } from "@renderer/dexie"; import { useTranslation } from "react-i18next"; import { AxiosProgressEvent } from "axios"; import { formatDownloadProgress } from "@renderer/helpers"; -import { SPACING_UNIT, vars } from "@renderer/theme.css"; +import { SPACING_UNIT } from "@renderer/theme.css"; export interface CloudSyncModalProps extends Omit {} export function CloudSyncModal({ visible, onClose }: CloudSyncModalProps) { const [deletingArtifact, setDeletingArtifact] = useState(false); - const [lastBackup, setLastBackup] = useState(null); const [backupDownloadProgress, setBackupDownloadProgress] = useState(null); @@ -38,6 +35,7 @@ export function CloudSyncModal({ visible, onClose }: CloudSyncModalProps) { backupPreview, uploadingBackup, restoringBackup, + loadingPreview, uploadSaveGame, downloadGameArtifact, deleteGameArtifact, @@ -64,11 +62,6 @@ export function CloudSyncModal({ visible, onClose }: CloudSyncModalProps) { }; useEffect(() => { - gameBackupsTable - .where({ shop: shop, objectId }) - .last() - .then((lastBackup) => setLastBackup(lastBackup || null)); - const removeBackupDownloadProgressListener = window.electron.onBackupDownloadProgress( objectId!, @@ -111,20 +104,19 @@ export function CloudSyncModal({ visible, onClose }: CloudSyncModalProps) { ); } - if (lastBackup) { + if (loadingPreview) { return ( - - - - - {t("last_backup_date", { - date: format(lastBackup.createdAt, "dd/MM/yyyy HH:mm"), - })} + + {t("loading_save_preview")} ); } + if (artifacts.length >= 2) { + return t("max_number_of_artifacts_reached"); + } + if (!backupPreview) { return t("no_backup_preview"); } @@ -133,93 +125,76 @@ export function CloudSyncModal({ visible, onClose }: CloudSyncModalProps) { }, [ uploadingBackup, backupDownloadProgress?.progress, - lastBackup, backupPreview, restoringBackup, + loadingPreview, + artifacts, t, ]); const disableActions = uploadingBackup || restoringBackup || deletingArtifact; return ( - <> - {/* {}} - onClose={() => {}} - visible - /> */} - - +
+
+

{gameTitle}

+

{backupStateLabel}

+ + +
+ + +
+ +
-
-

{gameTitle}

-

{backupStateLabel}

- - -
- - -
- -
-
-

{t("backups")}

- {artifacts.length} / 2 -
- -
- Espaço usado - -
+

{t("backups")}

+ {artifacts.length} / 2
+
+ {artifacts.length > 0 ? (
    - {artifacts.map((artifact) => ( + {artifacts.map((artifact, index) => (
  • -

    Backup do dia {format(artifact.createdAt, "dd/MM")}

    +

    {t("backup_title", { number: index + 1 })}

    {formatBytes(artifact.artifactLengthInBytes)}
    @@ -272,7 +247,9 @@ export function CloudSyncModal({ visible, onClose }: CloudSyncModalProps) {
  • ))}
-
- + ) : ( +

{t("no_backups_created")}

+ )} + ); } diff --git a/src/renderer/src/pages/game-details/game-details-content.tsx b/src/renderer/src/pages/game-details/game-details-content.tsx index a112e9d1..b852916f 100644 --- a/src/renderer/src/pages/game-details/game-details-content.tsx +++ b/src/renderer/src/pages/game-details/game-details-content.tsx @@ -36,7 +36,7 @@ export function GameDetailsContent() { const { userDetails } = useUserDetails(); - const { setShowCloudSyncModal, getGameBackupPreview } = + const { setShowCloudSyncModal, getGameBackupPreview, getGameArtifacts } = useContext(cloudSyncContext); const aboutTheGame = useMemo(() => { @@ -108,7 +108,8 @@ export function GameDetailsContent() { useEffect(() => { getGameBackupPreview(); - }, [getGameBackupPreview]); + getGameArtifacts(); + }, [getGameBackupPreview, getGameArtifacts]); return (
diff --git a/src/renderer/src/pages/home/home.tsx b/src/renderer/src/pages/home/home.tsx index ad306726..61417b1f 100644 --- a/src/renderer/src/pages/home/home.tsx +++ b/src/renderer/src/pages/home/home.tsx @@ -34,6 +34,7 @@ export default function Home() { >({ [CatalogueCategory.Hot]: [], [CatalogueCategory.Weekly]: [], + [CatalogueCategory.Achievements]: [], }); const getCatalogue = useCallback((category: CatalogueCategory) => { diff --git a/src/renderer/src/pages/profile/profile-hero/profile-hero.css.ts b/src/renderer/src/pages/profile/profile-hero/profile-hero.css.ts index fd02d11f..2080e445 100644 --- a/src/renderer/src/pages/profile/profile-hero/profile-hero.css.ts +++ b/src/renderer/src/pages/profile/profile-hero/profile-hero.css.ts @@ -52,8 +52,7 @@ export const profileDisplayName = style({ display: "flex", alignItems: "center", position: "relative", - textShadow: - "0 0 40px rgb(0 0 0), 0 0 20px rgb(0 0 0 / 50%), 0 0 10px rgb(0 0 0 / 20%)", + textShadow: "0 0 5px rgb(0 0 0 / 40%)", }); export const heroPanel = style({ diff --git a/src/renderer/src/pages/profile/upload-background-image-button/upload-background-image-button.tsx b/src/renderer/src/pages/profile/upload-background-image-button/upload-background-image-button.tsx index a11c9069..3e8e2971 100644 --- a/src/renderer/src/pages/profile/upload-background-image-button/upload-background-image-button.tsx +++ b/src/renderer/src/pages/profile/upload-background-image-button/upload-background-image-button.tsx @@ -5,11 +5,14 @@ import { userProfileContext } from "@renderer/context"; import * as styles from "./upload-background-image-button.css"; import { useToast, useUserDetails } from "@renderer/hooks"; +import { useTranslation } from "react-i18next"; export function UploadBackgroundImageButton() { const [isUploadingBackgroundImage, setIsUploadingBackgorundImage] = useState(false); - const { hasActiveSubscription } = useUserDetails(); + const { userDetails } = useUserDetails(); + + const { t } = useTranslation("user_profile"); const { isMe, setSelectedBackgroundImage } = useContext(userProfileContext); const { patchUser, fetchUserDetails } = useUserDetails(); @@ -44,7 +47,8 @@ export function UploadBackgroundImageButton() { } }; - if (!isMe || !hasActiveSubscription) return null; + if (!isMe || !userDetails?.subscription) return null; + if (userDetails.subscription.plan.name !== "plus") return null; return ( ); } diff --git a/src/shared/constants.ts b/src/shared/constants.ts index 0a466695..f0f6d2f0 100644 --- a/src/shared/constants.ts +++ b/src/shared/constants.ts @@ -14,6 +14,7 @@ export enum DownloadSourceStatus { export enum CatalogueCategory { Hot = "hot", Weekly = "weekly", + Achievements = "achievements", } export enum SteamContentDescriptor { diff --git a/src/types/index.ts b/src/types/index.ts index c3a91053..2f6746f4 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -237,7 +237,7 @@ export type SubscriptionStatus = "active" | "pending" | "cancelled"; export interface Subscription { id: string; status: SubscriptionStatus; - plan: { id: string; name: string }; + plan: { id: string; name: "basic" | "plus" }; expiresAt: Date | null; } diff --git a/src/types/ludusavi.types.ts b/src/types/ludusavi.types.ts index 01561b4d..55f3f506 100644 --- a/src/types/ludusavi.types.ts +++ b/src/types/ludusavi.types.ts @@ -1,6 +1,7 @@ export interface LudusaviScanChange { change: "New" | "Different" | "Removed" | "Same" | "Unknown"; decision: "Processed" | "Cancelled" | "Ignore"; + bytes: number; } export interface LudusaviGame extends LudusaviScanChange {