From dfd640ebdac8d2c203781f6b070c7fe857eabfee Mon Sep 17 00:00:00 2001 From: Moyasee Date: Mon, 29 Sep 2025 00:14:47 +0300 Subject: [PATCH 01/23] feat: add revert title for non-custom games --- .../game-details/modals/edit-game-modal.tsx | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/renderer/src/pages/game-details/modals/edit-game-modal.tsx b/src/renderer/src/pages/game-details/modals/edit-game-modal.tsx index 04a27779..7bfc48fa 100644 --- a/src/renderer/src/pages/game-details/modals/edit-game-modal.tsx +++ b/src/renderer/src/pages/game-details/modals/edit-game-modal.tsx @@ -157,6 +157,24 @@ export function EditGameModal({ setAssetPath(assetType, ""); }; + const getOriginalTitle = (): string => { + if (!game) return ""; + + // For non-custom games, the original title is from shopDetails assets + return shopDetails?.assets?.title || game.title || ""; + }; + + const handleRestoreDefaultTitle = () => { + const originalTitle = getOriginalTitle(); + setGameName(originalTitle); + }; + + const isTitleChanged = (): boolean => { + if (!game || isCustomGame(game)) return false; + const originalTitle = getOriginalTitle(); + return gameName.trim() !== originalTitle.trim(); + }; + const [dragOverTarget, setDragOverTarget] = useState(null); const handleDragOver = (e: React.DragEvent) => { @@ -489,6 +507,19 @@ export function EditGameModal({ onChange={handleGameNameChange} theme="dark" disabled={isUpdating} + rightContent={ + isTitleChanged() && ( + + ) + } />
From f3b4898e9ce75ae653a21d67b3c453023c457beb Mon Sep 17 00:00:00 2001 From: Moyasee Date: Mon, 29 Sep 2025 14:51:08 +0300 Subject: [PATCH 02/23] feat: proper cleanup of unused assets --- .../library/remove-game-from-library.ts | 53 +++++++++++++++++-- src/main/events/library/update-custom-game.ts | 38 +++++++++++++ .../library/update-game-custom-assets.ts | 29 ++++++++++ 3 files changed, 117 insertions(+), 3 deletions(-) diff --git a/src/main/events/library/remove-game-from-library.ts b/src/main/events/library/remove-game-from-library.ts index 6a33ffaf..4868d588 100644 --- a/src/main/events/library/remove-game-from-library.ts +++ b/src/main/events/library/remove-game-from-library.ts @@ -1,6 +1,6 @@ import { registerEvent } from "../register-event"; import { HydraApi } from "@main/services"; -import { gamesSublevel, levelKeys } from "@main/level"; +import { gamesSublevel, gamesShopAssetsSublevel, levelKeys } from "@main/level"; import type { GameShop } from "@types"; const removeGameFromLibrary = async ( @@ -12,15 +12,62 @@ const removeGameFromLibrary = async ( const game = await gamesSublevel.get(gameKey); if (game) { - await gamesSublevel.put(gameKey, { + // Collect asset paths that need to be cleaned up before marking as deleted + const assetPathsToDelete: string[] = []; + + const assetUrls = game.shop === "custom" + ? [game.iconUrl, game.logoImageUrl, game.libraryHeroImageUrl] + : [game.customIconUrl, game.customLogoImageUrl, game.customHeroImageUrl]; + + assetUrls.forEach(url => { + if (url?.startsWith("local:")) { + assetPathsToDelete.push(url.replace("local:", "")); + } + }); + + + const updatedGame = { ...game, isDeleted: true, executablePath: null, - }); + ...(game.shop !== "custom" && { + customIconUrl: null, + customLogoImageUrl: null, + customHeroImageUrl: null, + }), + }; + + await gamesSublevel.put(gameKey, updatedGame); + + + if (game.shop !== "custom") { + const existingAssets = await gamesShopAssetsSublevel.get(gameKey); + if (existingAssets) { + const resetAssets = { + ...existingAssets, + title: existingAssets.title, + }; + await gamesShopAssetsSublevel.put(gameKey, resetAssets); + } + } if (game?.remoteId) { HydraApi.delete(`/profile/games/${game.remoteId}`).catch(() => {}); } + + + if (assetPathsToDelete.length > 0) { + const fs = await import("fs"); + for (const assetPath of assetPathsToDelete) { + try { + if (fs.existsSync(assetPath)) { + await fs.promises.unlink(assetPath); + } + } catch (error) { + console.warn(`Failed to delete asset ${assetPath}:`, error); + } + } + } } }; diff --git a/src/main/events/library/update-custom-game.ts b/src/main/events/library/update-custom-game.ts index 6152c0df..168e0050 100644 --- a/src/main/events/library/update-custom-game.ts +++ b/src/main/events/library/update-custom-game.ts @@ -18,6 +18,30 @@ const updateCustomGame = async ( throw new Error("Game not found"); } + // Collect old asset paths that will be replaced + const oldAssetPaths: string[] = []; + + if (existingGame.iconUrl && iconUrl && existingGame.iconUrl !== iconUrl && existingGame.iconUrl.startsWith("local:")) { + oldAssetPaths.push(existingGame.iconUrl.replace("local:", "")); + } + if (existingGame.iconUrl && !iconUrl && existingGame.iconUrl.startsWith("local:")) { + oldAssetPaths.push(existingGame.iconUrl.replace("local:", "")); + } + + if (existingGame.logoImageUrl && logoImageUrl && existingGame.logoImageUrl !== logoImageUrl && existingGame.logoImageUrl.startsWith("local:")) { + oldAssetPaths.push(existingGame.logoImageUrl.replace("local:", "")); + } + if (existingGame.logoImageUrl && !logoImageUrl && existingGame.logoImageUrl.startsWith("local:")) { + oldAssetPaths.push(existingGame.logoImageUrl.replace("local:", "")); + } + + if (existingGame.libraryHeroImageUrl && libraryHeroImageUrl && existingGame.libraryHeroImageUrl !== libraryHeroImageUrl && existingGame.libraryHeroImageUrl.startsWith("local:")) { + oldAssetPaths.push(existingGame.libraryHeroImageUrl.replace("local:", "")); + } + if (existingGame.libraryHeroImageUrl && !libraryHeroImageUrl && existingGame.libraryHeroImageUrl.startsWith("local:")) { + oldAssetPaths.push(existingGame.libraryHeroImageUrl.replace("local:", "")); + } + const updatedGame = { ...existingGame, title, @@ -43,6 +67,20 @@ const updateCustomGame = async ( await gamesShopAssetsSublevel.put(gameKey, updatedAssets); } + // Manually delete specific old asset files instead of running full cleanup + if (oldAssetPaths.length > 0) { + const fs = await import("fs"); + for (const assetPath of oldAssetPaths) { + try { + if (fs.existsSync(assetPath)) { + await fs.promises.unlink(assetPath); + } + } catch (error) { + console.warn(`Failed to delete old asset ${assetPath}:`, error); + } + } + } + return updatedGame; }; diff --git a/src/main/events/library/update-game-custom-assets.ts b/src/main/events/library/update-game-custom-assets.ts index 866cd60e..f8206904 100644 --- a/src/main/events/library/update-game-custom-assets.ts +++ b/src/main/events/library/update-game-custom-assets.ts @@ -18,6 +18,21 @@ const updateGameCustomAssets = async ( throw new Error("Game not found"); } + // Collect old custom asset paths that will be replaced + const oldAssetPaths: string[] = []; + + const assetPairs = [ + { existing: existingGame.customIconUrl, new: customIconUrl }, + { existing: existingGame.customLogoImageUrl, new: customLogoImageUrl }, + { existing: existingGame.customHeroImageUrl, new: customHeroImageUrl } + ]; + + assetPairs.forEach(({ existing, new: newUrl }) => { + if (existing && newUrl !== undefined && existing !== newUrl && existing.startsWith("local:")) { + oldAssetPaths.push(existing.replace("local:", "")); + } + }); + const updatedGame = { ...existingGame, title, @@ -39,6 +54,20 @@ const updateGameCustomAssets = async ( await gamesShopAssetsSublevel.put(gameKey, updatedAssets); } + // Manually delete specific old custom asset files instead of running full cleanup + if (oldAssetPaths.length > 0) { + const fs = await import("fs"); + for (const assetPath of oldAssetPaths) { + try { + if (fs.existsSync(assetPath)) { + await fs.promises.unlink(assetPath); + } + } catch (error) { + console.warn(`Failed to delete old custom asset ${assetPath}:`, error); + } + } + } + return updatedGame; }; From 2bed7c0b37c83ff031ebe7b374e77e85458d9ed1 Mon Sep 17 00:00:00 2001 From: Moyasee Date: Mon, 29 Sep 2025 14:55:23 +0300 Subject: [PATCH 03/23] fix: cleaned comments and simplified function --- .../library/remove-game-from-library.ts | 22 +++++++------ src/main/events/library/update-custom-game.ts | 31 ++++++------------- .../library/update-game-custom-assets.ts | 18 ++++++----- 3 files changed, 32 insertions(+), 39 deletions(-) diff --git a/src/main/events/library/remove-game-from-library.ts b/src/main/events/library/remove-game-from-library.ts index 4868d588..c4c8be9d 100644 --- a/src/main/events/library/remove-game-from-library.ts +++ b/src/main/events/library/remove-game-from-library.ts @@ -14,18 +14,22 @@ const removeGameFromLibrary = async ( if (game) { // Collect asset paths that need to be cleaned up before marking as deleted const assetPathsToDelete: string[] = []; - - const assetUrls = game.shop === "custom" - ? [game.iconUrl, game.logoImageUrl, game.libraryHeroImageUrl] - : [game.customIconUrl, game.customLogoImageUrl, game.customHeroImageUrl]; - - assetUrls.forEach(url => { + + const assetUrls = + game.shop === "custom" + ? [game.iconUrl, game.logoImageUrl, game.libraryHeroImageUrl] + : [ + game.customIconUrl, + game.customLogoImageUrl, + game.customHeroImageUrl, + ]; + + assetUrls.forEach((url) => { if (url?.startsWith("local:")) { assetPathsToDelete.push(url.replace("local:", "")); } }); - const updatedGame = { ...game, isDeleted: true, @@ -39,13 +43,12 @@ const removeGameFromLibrary = async ( await gamesSublevel.put(gameKey, updatedGame); - if (game.shop !== "custom") { const existingAssets = await gamesShopAssetsSublevel.get(gameKey); if (existingAssets) { const resetAssets = { ...existingAssets, - title: existingAssets.title, + title: existingAssets.title, }; await gamesShopAssetsSublevel.put(gameKey, resetAssets); } @@ -55,7 +58,6 @@ const removeGameFromLibrary = async ( HydraApi.delete(`/profile/games/${game.remoteId}`).catch(() => {}); } - if (assetPathsToDelete.length > 0) { const fs = await import("fs"); for (const assetPath of assetPathsToDelete) { diff --git a/src/main/events/library/update-custom-game.ts b/src/main/events/library/update-custom-game.ts index 168e0050..141e251e 100644 --- a/src/main/events/library/update-custom-game.ts +++ b/src/main/events/library/update-custom-game.ts @@ -18,29 +18,19 @@ const updateCustomGame = async ( throw new Error("Game not found"); } - // Collect old asset paths that will be replaced const oldAssetPaths: string[] = []; - if (existingGame.iconUrl && iconUrl && existingGame.iconUrl !== iconUrl && existingGame.iconUrl.startsWith("local:")) { - oldAssetPaths.push(existingGame.iconUrl.replace("local:", "")); - } - if (existingGame.iconUrl && !iconUrl && existingGame.iconUrl.startsWith("local:")) { - oldAssetPaths.push(existingGame.iconUrl.replace("local:", "")); - } + const assetPairs = [ + { existing: existingGame.iconUrl, new: iconUrl }, + { existing: existingGame.logoImageUrl, new: logoImageUrl }, + { existing: existingGame.libraryHeroImageUrl, new: libraryHeroImageUrl } + ]; - if (existingGame.logoImageUrl && logoImageUrl && existingGame.logoImageUrl !== logoImageUrl && existingGame.logoImageUrl.startsWith("local:")) { - oldAssetPaths.push(existingGame.logoImageUrl.replace("local:", "")); - } - if (existingGame.logoImageUrl && !logoImageUrl && existingGame.logoImageUrl.startsWith("local:")) { - oldAssetPaths.push(existingGame.logoImageUrl.replace("local:", "")); - } - - if (existingGame.libraryHeroImageUrl && libraryHeroImageUrl && existingGame.libraryHeroImageUrl !== libraryHeroImageUrl && existingGame.libraryHeroImageUrl.startsWith("local:")) { - oldAssetPaths.push(existingGame.libraryHeroImageUrl.replace("local:", "")); - } - if (existingGame.libraryHeroImageUrl && !libraryHeroImageUrl && existingGame.libraryHeroImageUrl.startsWith("local:")) { - oldAssetPaths.push(existingGame.libraryHeroImageUrl.replace("local:", "")); - } + assetPairs.forEach(({ existing, new: newUrl }) => { + if (existing?.startsWith("local:") && (!newUrl || existing !== newUrl)) { + oldAssetPaths.push(existing.replace("local:", "")); + } + }); const updatedGame = { ...existingGame, @@ -67,7 +57,6 @@ const updateCustomGame = async ( await gamesShopAssetsSublevel.put(gameKey, updatedAssets); } - // Manually delete specific old asset files instead of running full cleanup if (oldAssetPaths.length > 0) { const fs = await import("fs"); for (const assetPath of oldAssetPaths) { diff --git a/src/main/events/library/update-game-custom-assets.ts b/src/main/events/library/update-game-custom-assets.ts index f8206904..392a0923 100644 --- a/src/main/events/library/update-game-custom-assets.ts +++ b/src/main/events/library/update-game-custom-assets.ts @@ -18,17 +18,21 @@ const updateGameCustomAssets = async ( throw new Error("Game not found"); } - // Collect old custom asset paths that will be replaced const oldAssetPaths: string[] = []; - + const assetPairs = [ { existing: existingGame.customIconUrl, new: customIconUrl }, { existing: existingGame.customLogoImageUrl, new: customLogoImageUrl }, - { existing: existingGame.customHeroImageUrl, new: customHeroImageUrl } + { existing: existingGame.customHeroImageUrl, new: customHeroImageUrl }, ]; - + assetPairs.forEach(({ existing, new: newUrl }) => { - if (existing && newUrl !== undefined && existing !== newUrl && existing.startsWith("local:")) { + if ( + existing && + newUrl !== undefined && + existing !== newUrl && + existing.startsWith("local:") + ) { oldAssetPaths.push(existing.replace("local:", "")); } }); @@ -43,18 +47,16 @@ const updateGameCustomAssets = async ( await gamesSublevel.put(gameKey, updatedGame); - // Also update the shop assets for non-custom games const existingAssets = await gamesShopAssetsSublevel.get(gameKey); if (existingAssets) { const updatedAssets = { ...existingAssets, - title, // Update the title in shop assets as well + title, }; await gamesShopAssetsSublevel.put(gameKey, updatedAssets); } - // Manually delete specific old custom asset files instead of running full cleanup if (oldAssetPaths.length > 0) { const fs = await import("fs"); for (const assetPath of oldAssetPaths) { From 3e93a14deb066a3a674a3f2879fc39fbdd45acae Mon Sep 17 00:00:00 2001 From: Moyasee Date: Mon, 29 Sep 2025 19:53:52 +0300 Subject: [PATCH 04/23] Fix: display actual image path in edit game modal --- .../library/update-game-custom-assets.ts | 2 +- .../game-details/modals/edit-game-modal.tsx | 60 ++++++++++++++++--- 2 files changed, 52 insertions(+), 10 deletions(-) diff --git a/src/main/events/library/update-game-custom-assets.ts b/src/main/events/library/update-game-custom-assets.ts index 392a0923..1b75e0c4 100644 --- a/src/main/events/library/update-game-custom-assets.ts +++ b/src/main/events/library/update-game-custom-assets.ts @@ -51,7 +51,7 @@ const updateGameCustomAssets = async ( if (existingAssets) { const updatedAssets = { ...existingAssets, - title, + title, }; await gamesShopAssetsSublevel.put(gameKey, updatedAssets); diff --git a/src/renderer/src/pages/game-details/modals/edit-game-modal.tsx b/src/renderer/src/pages/game-details/modals/edit-game-modal.tsx index 7bfc48fa..0948a749 100644 --- a/src/renderer/src/pages/game-details/modals/edit-game-modal.tsx +++ b/src/renderer/src/pages/game-details/modals/edit-game-modal.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useCallback } from "react"; +import { useState, useEffect, useCallback, useMemo } from "react"; import { useTranslation } from "react-i18next"; import { ImageIcon, XIcon } from "@primer/octicons-react"; @@ -32,6 +32,9 @@ export function EditGameModal({ const [iconPath, setIconPath] = useState(""); const [logoPath, setLogoPath] = useState(""); const [heroPath, setHeroPath] = useState(""); + const [iconDisplayPath, setIconDisplayPath] = useState(""); + const [logoDisplayPath, setLogoDisplayPath] = useState(""); + const [heroDisplayPath, setHeroDisplayPath] = useState(""); const [isUpdating, setIsUpdating] = useState(false); const [selectedAssetType, setSelectedAssetType] = useState("icon"); @@ -51,6 +54,10 @@ export function EditGameModal({ setIconPath(extractLocalPath(game.iconUrl)); setLogoPath(extractLocalPath(game.logoImageUrl)); setHeroPath(extractLocalPath(game.libraryHeroImageUrl)); + // For existing assets, show the asset path as display path since we don't have the original + setIconDisplayPath(extractLocalPath(game.iconUrl)); + setLogoDisplayPath(extractLocalPath(game.logoImageUrl)); + setHeroDisplayPath(extractLocalPath(game.libraryHeroImageUrl)); }, []); const setNonCustomGameAssets = useCallback( @@ -58,6 +65,10 @@ export function EditGameModal({ setIconPath(extractLocalPath(game.customIconUrl)); setLogoPath(extractLocalPath(game.customLogoImageUrl)); setHeroPath(extractLocalPath(game.customHeroImageUrl)); + // For existing assets, show the asset path as display path since we don't have the original + setIconDisplayPath(extractLocalPath(game.customIconUrl)); + setLogoDisplayPath(extractLocalPath(game.customLogoImageUrl)); + setHeroDisplayPath(extractLocalPath(game.customHeroImageUrl)); setDefaultIconUrl(shopDetails?.assets?.iconUrl || game.iconUrl || null); setDefaultLogoUrl( @@ -103,6 +114,17 @@ export function EditGameModal({ } }; + const getAssetDisplayPath = (assetType: AssetType): string => { + switch (assetType) { + case "icon": + return iconDisplayPath; + case "logo": + return logoDisplayPath; + case "hero": + return heroDisplayPath; + } + }; + const setAssetPath = (assetType: AssetType, path: string): void => { switch (assetType) { case "icon": @@ -117,6 +139,20 @@ export function EditGameModal({ } }; + const setAssetDisplayPath = (assetType: AssetType, path: string): void => { + switch (assetType) { + case "icon": + setIconDisplayPath(path); + break; + case "logo": + setLogoDisplayPath(path); + break; + case "hero": + setHeroDisplayPath(path); + break; + } + }; + const getDefaultUrl = (assetType: AssetType): string | null => { switch (assetType) { case "icon": @@ -140,21 +176,25 @@ export function EditGameModal({ }); if (filePaths && filePaths.length > 0) { + const originalPath = filePaths[0]; try { const copiedAssetUrl = await window.electron.copyCustomGameAsset( - filePaths[0], + originalPath, assetType ); setAssetPath(assetType, copiedAssetUrl.replace("local:", "")); + setAssetDisplayPath(assetType, originalPath); } catch (error) { console.error(`Failed to copy ${assetType} asset:`, error); - setAssetPath(assetType, filePaths[0]); + setAssetPath(assetType, originalPath); + setAssetDisplayPath(assetType, originalPath); } } }; const handleRestoreDefault = (assetType: AssetType) => { setAssetPath(assetType, ""); + setAssetDisplayPath(assetType, ""); }; const getOriginalTitle = (): string => { @@ -169,11 +209,11 @@ export function EditGameModal({ setGameName(originalTitle); }; - const isTitleChanged = (): boolean => { + const isTitleChanged = useMemo((): boolean => { if (!game || isCustomGame(game)) return false; const originalTitle = getOriginalTitle(); return gameName.trim() !== originalTitle.trim(); - }; + }, [game, gameName, shopDetails]); const [dragOverTarget, setDragOverTarget] = useState(null); @@ -250,6 +290,7 @@ export function EditGameModal({ const assetPath = copiedAssetUrl.replace("local:", ""); setAssetPath(assetType, assetPath); + setAssetDisplayPath(assetType, filePath); showSuccessToast( `${assetType.charAt(0).toUpperCase() + assetType.slice(1)} updated successfully!` @@ -361,7 +402,7 @@ export function EditGameModal({ }; // Helper function to reset form to initial state - const resetFormToInitialState = (game: LibraryGame | Game) => { + const resetFormToInitialState = useCallback((game: LibraryGame | Game) => { setGameName(game.title || ""); if (isCustomGame(game)) { @@ -373,7 +414,7 @@ export function EditGameModal({ } else { setNonCustomGameAssets(game as LibraryGame); } - }; + }, [setCustomGameAssets, setNonCustomGameAssets]); const handleClose = () => { if (!isUpdating && game) { @@ -396,6 +437,7 @@ export function EditGameModal({ const renderImageSection = (assetType: AssetType) => { const assetPath = getAssetPath(assetType); + const assetDisplayPath = getAssetDisplayPath(assetType); const defaultUrl = getDefaultUrl(assetType); const hasImage = assetPath || (game && !isCustomGame(game) && defaultUrl); const isDragOver = dragOverTarget === assetType; @@ -408,7 +450,7 @@ export function EditGameModal({
Date: Mon, 29 Sep 2025 20:09:04 +0300 Subject: [PATCH 05/23] fix: using for...of instead of forEach --- src/main/events/library/remove-game-from-library.ts | 4 ++-- src/main/events/library/update-custom-game.ts | 10 +++++----- src/main/events/library/update-game-custom-assets.ts | 6 +++--- .../src/pages/game-details/modals/edit-game-modal.tsx | 2 -- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/main/events/library/remove-game-from-library.ts b/src/main/events/library/remove-game-from-library.ts index c4c8be9d..92539650 100644 --- a/src/main/events/library/remove-game-from-library.ts +++ b/src/main/events/library/remove-game-from-library.ts @@ -24,11 +24,11 @@ const removeGameFromLibrary = async ( game.customHeroImageUrl, ]; - assetUrls.forEach((url) => { + for (const url of assetUrls) { if (url?.startsWith("local:")) { assetPathsToDelete.push(url.replace("local:", "")); } - }); + } const updatedGame = { ...game, diff --git a/src/main/events/library/update-custom-game.ts b/src/main/events/library/update-custom-game.ts index 141e251e..39f3551b 100644 --- a/src/main/events/library/update-custom-game.ts +++ b/src/main/events/library/update-custom-game.ts @@ -19,18 +19,18 @@ const updateCustomGame = async ( } const oldAssetPaths: string[] = []; - + const assetPairs = [ { existing: existingGame.iconUrl, new: iconUrl }, { existing: existingGame.logoImageUrl, new: logoImageUrl }, - { existing: existingGame.libraryHeroImageUrl, new: libraryHeroImageUrl } + { existing: existingGame.libraryHeroImageUrl, new: libraryHeroImageUrl }, ]; - - assetPairs.forEach(({ existing, new: newUrl }) => { + + for (const { existing, new: newUrl } of assetPairs) { if (existing?.startsWith("local:") && (!newUrl || existing !== newUrl)) { oldAssetPaths.push(existing.replace("local:", "")); } - }); + } const updatedGame = { ...existingGame, diff --git a/src/main/events/library/update-game-custom-assets.ts b/src/main/events/library/update-game-custom-assets.ts index 1b75e0c4..166b2641 100644 --- a/src/main/events/library/update-game-custom-assets.ts +++ b/src/main/events/library/update-game-custom-assets.ts @@ -26,7 +26,7 @@ const updateGameCustomAssets = async ( { existing: existingGame.customHeroImageUrl, new: customHeroImageUrl }, ]; - assetPairs.forEach(({ existing, new: newUrl }) => { + for (const { existing, new: newUrl } of assetPairs) { if ( existing && newUrl !== undefined && @@ -35,7 +35,7 @@ const updateGameCustomAssets = async ( ) { oldAssetPaths.push(existing.replace("local:", "")); } - }); + } const updatedGame = { ...existingGame, @@ -51,7 +51,7 @@ const updateGameCustomAssets = async ( if (existingAssets) { const updatedAssets = { ...existingAssets, - title, + title, }; await gamesShopAssetsSublevel.put(gameKey, updatedAssets); diff --git a/src/renderer/src/pages/game-details/modals/edit-game-modal.tsx b/src/renderer/src/pages/game-details/modals/edit-game-modal.tsx index 0948a749..5e8e2311 100644 --- a/src/renderer/src/pages/game-details/modals/edit-game-modal.tsx +++ b/src/renderer/src/pages/game-details/modals/edit-game-modal.tsx @@ -54,7 +54,6 @@ export function EditGameModal({ setIconPath(extractLocalPath(game.iconUrl)); setLogoPath(extractLocalPath(game.logoImageUrl)); setHeroPath(extractLocalPath(game.libraryHeroImageUrl)); - // For existing assets, show the asset path as display path since we don't have the original setIconDisplayPath(extractLocalPath(game.iconUrl)); setLogoDisplayPath(extractLocalPath(game.logoImageUrl)); setHeroDisplayPath(extractLocalPath(game.libraryHeroImageUrl)); @@ -65,7 +64,6 @@ export function EditGameModal({ setIconPath(extractLocalPath(game.customIconUrl)); setLogoPath(extractLocalPath(game.customLogoImageUrl)); setHeroPath(extractLocalPath(game.customHeroImageUrl)); - // For existing assets, show the asset path as display path since we don't have the original setIconDisplayPath(extractLocalPath(game.customIconUrl)); setLogoDisplayPath(extractLocalPath(game.customLogoImageUrl)); setHeroDisplayPath(extractLocalPath(game.customHeroImageUrl)); From a87e04a366eb12985d1fa1282d634cd95cf74c05 Mon Sep 17 00:00:00 2001 From: Moyasee Date: Mon, 29 Sep 2025 20:14:36 +0300 Subject: [PATCH 06/23] Fix: using node:fs instead of fs --- .../library/remove-game-from-library.ts | 2 +- src/main/events/library/update-custom-game.ts | 2 +- .../library/update-game-custom-assets.ts | 4 +-- .../game-details/modals/edit-game-modal.tsx | 27 ++++++++++--------- 4 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/main/events/library/remove-game-from-library.ts b/src/main/events/library/remove-game-from-library.ts index 92539650..a9c0d272 100644 --- a/src/main/events/library/remove-game-from-library.ts +++ b/src/main/events/library/remove-game-from-library.ts @@ -59,7 +59,7 @@ const removeGameFromLibrary = async ( } if (assetPathsToDelete.length > 0) { - const fs = await import("fs"); + const fs = await import("node:fs"); for (const assetPath of assetPathsToDelete) { try { if (fs.existsSync(assetPath)) { diff --git a/src/main/events/library/update-custom-game.ts b/src/main/events/library/update-custom-game.ts index 39f3551b..47641a6e 100644 --- a/src/main/events/library/update-custom-game.ts +++ b/src/main/events/library/update-custom-game.ts @@ -58,7 +58,7 @@ const updateCustomGame = async ( } if (oldAssetPaths.length > 0) { - const fs = await import("fs"); + const fs = await import("node:fs"); for (const assetPath of oldAssetPaths) { try { if (fs.existsSync(assetPath)) { diff --git a/src/main/events/library/update-game-custom-assets.ts b/src/main/events/library/update-game-custom-assets.ts index 166b2641..4e86bbc0 100644 --- a/src/main/events/library/update-game-custom-assets.ts +++ b/src/main/events/library/update-game-custom-assets.ts @@ -51,14 +51,14 @@ const updateGameCustomAssets = async ( if (existingAssets) { const updatedAssets = { ...existingAssets, - title, + title, }; await gamesShopAssetsSublevel.put(gameKey, updatedAssets); } if (oldAssetPaths.length > 0) { - const fs = await import("fs"); + const fs = await import("node:fs"); for (const assetPath of oldAssetPaths) { try { if (fs.existsSync(assetPath)) { diff --git a/src/renderer/src/pages/game-details/modals/edit-game-modal.tsx b/src/renderer/src/pages/game-details/modals/edit-game-modal.tsx index 5e8e2311..3979d051 100644 --- a/src/renderer/src/pages/game-details/modals/edit-game-modal.tsx +++ b/src/renderer/src/pages/game-details/modals/edit-game-modal.tsx @@ -400,19 +400,22 @@ export function EditGameModal({ }; // Helper function to reset form to initial state - const resetFormToInitialState = useCallback((game: LibraryGame | Game) => { - setGameName(game.title || ""); + const resetFormToInitialState = useCallback( + (game: LibraryGame | Game) => { + setGameName(game.title || ""); - if (isCustomGame(game)) { - setCustomGameAssets(game); - // Clear default URLs for custom games - setDefaultIconUrl(null); - setDefaultLogoUrl(null); - setDefaultHeroUrl(null); - } else { - setNonCustomGameAssets(game as LibraryGame); - } - }, [setCustomGameAssets, setNonCustomGameAssets]); + if (isCustomGame(game)) { + setCustomGameAssets(game); + // Clear default URLs for custom games + setDefaultIconUrl(null); + setDefaultLogoUrl(null); + setDefaultHeroUrl(null); + } else { + setNonCustomGameAssets(game as LibraryGame); + } + }, + [setCustomGameAssets, setNonCustomGameAssets] + ); const handleClose = () => { if (!isUpdating && game) { From 96d6b90356c7612210c5358937d872f6d320bdca Mon Sep 17 00:00:00 2001 From: Moyasee Date: Mon, 29 Sep 2025 20:20:58 +0300 Subject: [PATCH 07/23] Fix: Refactoring functions to reduce complexity --- .../library/remove-game-from-library.ts | 133 ++++++++++-------- .../library/update-game-custom-assets.ts | 90 ++++++++---- 2 files changed, 141 insertions(+), 82 deletions(-) diff --git a/src/main/events/library/remove-game-from-library.ts b/src/main/events/library/remove-game-from-library.ts index a9c0d272..438aa39a 100644 --- a/src/main/events/library/remove-game-from-library.ts +++ b/src/main/events/library/remove-game-from-library.ts @@ -1,7 +1,69 @@ import { registerEvent } from "../register-event"; import { HydraApi } from "@main/services"; import { gamesSublevel, gamesShopAssetsSublevel, levelKeys } from "@main/level"; -import type { GameShop } from "@types"; +import type { GameShop, Game } from "@types"; + +const collectAssetPathsToDelete = (game: Game): string[] => { + const assetPathsToDelete: string[] = []; + + const assetUrls = + game.shop === "custom" + ? [game.iconUrl, game.logoImageUrl, game.libraryHeroImageUrl] + : [ + game.customIconUrl, + game.customLogoImageUrl, + game.customHeroImageUrl, + ]; + + for (const url of assetUrls) { + if (url?.startsWith("local:")) { + assetPathsToDelete.push(url.replace("local:", "")); + } + } + + return assetPathsToDelete; +}; + +const updateGameAsDeleted = async (game: Game, gameKey: string): Promise => { + const updatedGame = { + ...game, + isDeleted: true, + executablePath: null, + ...(game.shop !== "custom" && { + customIconUrl: null, + customLogoImageUrl: null, + customHeroImageUrl: null, + }), + }; + + await gamesSublevel.put(gameKey, updatedGame); +}; + +const resetShopAssets = async (gameKey: string): Promise => { + const existingAssets = await gamesShopAssetsSublevel.get(gameKey); + if (existingAssets) { + const resetAssets = { + ...existingAssets, + title: existingAssets.title, + }; + await gamesShopAssetsSublevel.put(gameKey, resetAssets); + } +}; + +const deleteAssetFiles = async (assetPathsToDelete: string[]): Promise => { + if (assetPathsToDelete.length === 0) return; + + const fs = await import("node:fs"); + for (const assetPath of assetPathsToDelete) { + try { + if (fs.existsSync(assetPath)) { + await fs.promises.unlink(assetPath); + } + } catch (error) { + console.warn(`Failed to delete asset ${assetPath}:`, error); + } + } +}; const removeGameFromLibrary = async ( _event: Electron.IpcMainInvokeEvent, @@ -11,66 +73,21 @@ const removeGameFromLibrary = async ( const gameKey = levelKeys.game(shop, objectId); const game = await gamesSublevel.get(gameKey); - if (game) { - // Collect asset paths that need to be cleaned up before marking as deleted - const assetPathsToDelete: string[] = []; + if (!game) return; - const assetUrls = - game.shop === "custom" - ? [game.iconUrl, game.logoImageUrl, game.libraryHeroImageUrl] - : [ - game.customIconUrl, - game.customLogoImageUrl, - game.customHeroImageUrl, - ]; + const assetPathsToDelete = collectAssetPathsToDelete(game); + + await updateGameAsDeleted(game, gameKey); - for (const url of assetUrls) { - if (url?.startsWith("local:")) { - assetPathsToDelete.push(url.replace("local:", "")); - } - } - - const updatedGame = { - ...game, - isDeleted: true, - executablePath: null, - ...(game.shop !== "custom" && { - customIconUrl: null, - customLogoImageUrl: null, - customHeroImageUrl: null, - }), - }; - - await gamesSublevel.put(gameKey, updatedGame); - - if (game.shop !== "custom") { - const existingAssets = await gamesShopAssetsSublevel.get(gameKey); - if (existingAssets) { - const resetAssets = { - ...existingAssets, - title: existingAssets.title, - }; - await gamesShopAssetsSublevel.put(gameKey, resetAssets); - } - } - - if (game?.remoteId) { - HydraApi.delete(`/profile/games/${game.remoteId}`).catch(() => {}); - } - - if (assetPathsToDelete.length > 0) { - const fs = await import("node:fs"); - for (const assetPath of assetPathsToDelete) { - try { - if (fs.existsSync(assetPath)) { - await fs.promises.unlink(assetPath); - } - } catch (error) { - console.warn(`Failed to delete asset ${assetPath}:`, error); - } - } - } + if (game.shop !== "custom") { + await resetShopAssets(gameKey); } + + if (game?.remoteId) { + HydraApi.delete(`/profile/games/${game.remoteId}`).catch(() => {}); + } + + await deleteAssetFiles(assetPathsToDelete); }; registerEvent("removeGameFromLibrary", removeGameFromLibrary); diff --git a/src/main/events/library/update-game-custom-assets.ts b/src/main/events/library/update-game-custom-assets.ts index 4e86bbc0..2138af3a 100644 --- a/src/main/events/library/update-game-custom-assets.ts +++ b/src/main/events/library/update-game-custom-assets.ts @@ -1,23 +1,13 @@ import { registerEvent } from "../register-event"; import { gamesSublevel, gamesShopAssetsSublevel, levelKeys } from "@main/level"; -import type { GameShop } from "@types"; +import type { GameShop, Game } from "@types"; -const updateGameCustomAssets = async ( - _event: Electron.IpcMainInvokeEvent, - shop: GameShop, - objectId: string, - title: string, +const collectOldAssetPaths = ( + existingGame: Game, customIconUrl?: string | null, customLogoImageUrl?: string | null, customHeroImageUrl?: string | null -) => { - const gameKey = levelKeys.game(shop, objectId); - - const existingGame = await gamesSublevel.get(gameKey); - if (!existingGame) { - throw new Error("Game not found"); - } - +): string[] => { const oldAssetPaths: string[] = []; const assetPairs = [ @@ -37,6 +27,17 @@ const updateGameCustomAssets = async ( } } + return oldAssetPaths; +}; + +const updateGameData = async ( + gameKey: string, + existingGame: Game, + title: string, + customIconUrl?: string | null, + customLogoImageUrl?: string | null, + customHeroImageUrl?: string | null +): Promise => { const updatedGame = { ...existingGame, title, @@ -46,29 +47,70 @@ const updateGameCustomAssets = async ( }; await gamesSublevel.put(gameKey, updatedGame); + return updatedGame; +}; +const updateShopAssets = async (gameKey: string, title: string): Promise => { const existingAssets = await gamesShopAssetsSublevel.get(gameKey); if (existingAssets) { const updatedAssets = { ...existingAssets, title, }; - await gamesShopAssetsSublevel.put(gameKey, updatedAssets); } +}; - if (oldAssetPaths.length > 0) { - const fs = await import("node:fs"); - for (const assetPath of oldAssetPaths) { - try { - if (fs.existsSync(assetPath)) { - await fs.promises.unlink(assetPath); - } - } catch (error) { - console.warn(`Failed to delete old custom asset ${assetPath}:`, error); +const deleteOldAssetFiles = async (oldAssetPaths: string[]): Promise => { + if (oldAssetPaths.length === 0) return; + + const fs = await import("node:fs"); + for (const assetPath of oldAssetPaths) { + try { + if (fs.existsSync(assetPath)) { + await fs.promises.unlink(assetPath); } + } catch (error) { + console.warn(`Failed to delete old custom asset ${assetPath}:`, error); } } +}; + +const updateGameCustomAssets = async ( + _event: Electron.IpcMainInvokeEvent, + shop: GameShop, + objectId: string, + title: string, + customIconUrl?: string | null, + customLogoImageUrl?: string | null, + customHeroImageUrl?: string | null +) => { + const gameKey = levelKeys.game(shop, objectId); + + const existingGame = await gamesSublevel.get(gameKey); + if (!existingGame) { + throw new Error("Game not found"); + } + + const oldAssetPaths = collectOldAssetPaths( + existingGame, + customIconUrl, + customLogoImageUrl, + customHeroImageUrl + ); + + const updatedGame = await updateGameData( + gameKey, + existingGame, + title, + customIconUrl, + customLogoImageUrl, + customHeroImageUrl + ); + + await updateShopAssets(gameKey, title); + + await deleteOldAssetFiles(oldAssetPaths); return updatedGame; }; From 7e22344f77a0cab9bb597d89b50c17ff5b50c4f0 Mon Sep 17 00:00:00 2001 From: Moyasee Date: Mon, 29 Sep 2025 20:36:05 +0300 Subject: [PATCH 08/23] Fix: fixed fs import and started using logger.warn --- .../library/remove-game-from-library.ts | 28 ++++++++++--------- src/main/events/library/update-custom-game.ts | 5 ++-- .../library/update-game-custom-assets.ts | 5 ++-- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/main/events/library/remove-game-from-library.ts b/src/main/events/library/remove-game-from-library.ts index 438aa39a..b7033147 100644 --- a/src/main/events/library/remove-game-from-library.ts +++ b/src/main/events/library/remove-game-from-library.ts @@ -2,29 +2,30 @@ import { registerEvent } from "../register-event"; import { HydraApi } from "@main/services"; import { gamesSublevel, gamesShopAssetsSublevel, levelKeys } from "@main/level"; import type { GameShop, Game } from "@types"; +import fs from "node:fs"; +import { logger } from "@main/services"; const collectAssetPathsToDelete = (game: Game): string[] => { const assetPathsToDelete: string[] = []; - + const assetUrls = game.shop === "custom" ? [game.iconUrl, game.logoImageUrl, game.libraryHeroImageUrl] - : [ - game.customIconUrl, - game.customLogoImageUrl, - game.customHeroImageUrl, - ]; + : [game.customIconUrl, game.customLogoImageUrl, game.customHeroImageUrl]; for (const url of assetUrls) { if (url?.startsWith("local:")) { assetPathsToDelete.push(url.replace("local:", "")); } } - + return assetPathsToDelete; }; -const updateGameAsDeleted = async (game: Game, gameKey: string): Promise => { +const updateGameAsDeleted = async ( + game: Game, + gameKey: string +): Promise => { const updatedGame = { ...game, isDeleted: true, @@ -50,17 +51,18 @@ const resetShopAssets = async (gameKey: string): Promise => { } }; -const deleteAssetFiles = async (assetPathsToDelete: string[]): Promise => { +const deleteAssetFiles = async ( + assetPathsToDelete: string[] +): Promise => { if (assetPathsToDelete.length === 0) return; - - const fs = await import("node:fs"); + for (const assetPath of assetPathsToDelete) { try { if (fs.existsSync(assetPath)) { await fs.promises.unlink(assetPath); } } catch (error) { - console.warn(`Failed to delete asset ${assetPath}:`, error); + logger.warn(`Failed to delete asset ${assetPath}:`, error); } } }; @@ -76,7 +78,7 @@ const removeGameFromLibrary = async ( if (!game) return; const assetPathsToDelete = collectAssetPathsToDelete(game); - + await updateGameAsDeleted(game, gameKey); if (game.shop !== "custom") { diff --git a/src/main/events/library/update-custom-game.ts b/src/main/events/library/update-custom-game.ts index 47641a6e..82d7f45f 100644 --- a/src/main/events/library/update-custom-game.ts +++ b/src/main/events/library/update-custom-game.ts @@ -1,6 +1,8 @@ import { registerEvent } from "../register-event"; import { gamesSublevel, gamesShopAssetsSublevel, levelKeys } from "@main/level"; import type { GameShop } from "@types"; +import fs from "node:fs"; +import { logger } from "@main/services"; const updateCustomGame = async ( _event: Electron.IpcMainInvokeEvent, @@ -58,14 +60,13 @@ const updateCustomGame = async ( } if (oldAssetPaths.length > 0) { - const fs = await import("node:fs"); for (const assetPath of oldAssetPaths) { try { if (fs.existsSync(assetPath)) { await fs.promises.unlink(assetPath); } } catch (error) { - console.warn(`Failed to delete old asset ${assetPath}:`, error); + logger.warn(`Failed to delete old asset ${assetPath}:`, error); } } } diff --git a/src/main/events/library/update-game-custom-assets.ts b/src/main/events/library/update-game-custom-assets.ts index 2138af3a..1813e8a8 100644 --- a/src/main/events/library/update-game-custom-assets.ts +++ b/src/main/events/library/update-game-custom-assets.ts @@ -1,6 +1,8 @@ import { registerEvent } from "../register-event"; import { gamesSublevel, gamesShopAssetsSublevel, levelKeys } from "@main/level"; import type { GameShop, Game } from "@types"; +import fs from "node:fs"; +import { logger } from "@main/services"; const collectOldAssetPaths = ( existingGame: Game, @@ -64,14 +66,13 @@ const updateShopAssets = async (gameKey: string, title: string): Promise = const deleteOldAssetFiles = async (oldAssetPaths: string[]): Promise => { if (oldAssetPaths.length === 0) return; - const fs = await import("node:fs"); for (const assetPath of oldAssetPaths) { try { if (fs.existsSync(assetPath)) { await fs.promises.unlink(assetPath); } } catch (error) { - console.warn(`Failed to delete old custom asset ${assetPath}:`, error); + logger.warn(`Failed to delete old custom asset ${assetPath}:`, error); } } }; From a39f9ebb70abab4e598d820b9f793c64ef73d2e5 Mon Sep 17 00:00:00 2001 From: Moyasee Date: Mon, 29 Sep 2025 20:39:58 +0300 Subject: [PATCH 09/23] fix: multiple imports --- src/main/events/library/remove-game-from-library.ts | 3 +-- src/main/events/library/update-game-custom-assets.ts | 5 ++++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/events/library/remove-game-from-library.ts b/src/main/events/library/remove-game-from-library.ts index b7033147..fbb60ab2 100644 --- a/src/main/events/library/remove-game-from-library.ts +++ b/src/main/events/library/remove-game-from-library.ts @@ -1,9 +1,8 @@ import { registerEvent } from "../register-event"; -import { HydraApi } from "@main/services"; +import { HydraApi, logger } from "@main/services"; import { gamesSublevel, gamesShopAssetsSublevel, levelKeys } from "@main/level"; import type { GameShop, Game } from "@types"; import fs from "node:fs"; -import { logger } from "@main/services"; const collectAssetPathsToDelete = (game: Game): string[] => { const assetPathsToDelete: string[] = []; diff --git a/src/main/events/library/update-game-custom-assets.ts b/src/main/events/library/update-game-custom-assets.ts index 1813e8a8..4bd4e517 100644 --- a/src/main/events/library/update-game-custom-assets.ts +++ b/src/main/events/library/update-game-custom-assets.ts @@ -52,7 +52,10 @@ const updateGameData = async ( return updatedGame; }; -const updateShopAssets = async (gameKey: string, title: string): Promise => { +const updateShopAssets = async ( + gameKey: string, + title: string +): Promise => { const existingAssets = await gamesShopAssetsSublevel.get(gameKey); if (existingAssets) { const updatedAssets = { From bd053a1635fae69049688b80d6d04c702cfe6e57 Mon Sep 17 00:00:00 2001 From: Moyasee Date: Mon, 29 Sep 2025 22:04:01 +0300 Subject: [PATCH 10/23] fix: favorite and pin button overlapping playtime --- src/locales/en/translation.json | 2 + src/locales/ru/translation.json | 2 + .../user-library-game-card.scss | 2 +- .../user-library-game-card.tsx | 56 ++++++++++++++++++- 4 files changed, 59 insertions(+), 3 deletions(-) diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index ce8b4de1..93fd5b0a 100755 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -506,6 +506,8 @@ "user_profile": { "amount_hours": "{{amount}} hours", "amount_minutes": "{{amount}} minutes", + "amount_hours_short": "{{amount}}h", + "amount_minutes_short": "{{amount}}m", "last_time_played": "Last played {{period}}", "activity": "Recent Activity", "library": "Library", diff --git a/src/locales/ru/translation.json b/src/locales/ru/translation.json index 58235989..c92d7902 100644 --- a/src/locales/ru/translation.json +++ b/src/locales/ru/translation.json @@ -486,6 +486,8 @@ "user_profile": { "amount_hours": "{{amount}} часов", "amount_minutes": "{{amount}} минут", + "amount_hours_short": "{{amount}}ч", + "amount_minutes_short": "{{amount}}м", "last_time_played": "Последняя игра {{period}}", "activity": "Недавняя активность", "library": "Библиотека", diff --git a/src/renderer/src/pages/profile/profile-content/user-library-game-card.scss b/src/renderer/src/pages/profile/profile-content/user-library-game-card.scss index f072fdd5..e40061de 100644 --- a/src/renderer/src/pages/profile/profile-content/user-library-game-card.scss +++ b/src/renderer/src/pages/profile/profile-content/user-library-game-card.scss @@ -86,7 +86,7 @@ top: 8px; right: 8px; display: flex; - gap: 6px; + gap: 4px; z-index: 2; } diff --git a/src/renderer/src/pages/profile/profile-content/user-library-game-card.tsx b/src/renderer/src/pages/profile/profile-content/user-library-game-card.tsx index 860c6758..d30dbf8a 100644 --- a/src/renderer/src/pages/profile/profile-content/user-library-game-card.tsx +++ b/src/renderer/src/pages/profile/profile-content/user-library-game-card.tsx @@ -2,7 +2,7 @@ import { UserGame } from "@types"; import HydraIcon from "@renderer/assets/icons/hydra.svg?react"; import { useFormat, useToast } from "@renderer/hooks"; import { useNavigate } from "react-router-dom"; -import { useCallback, useContext, useState } from "react"; +import { useCallback, useContext, useState, useEffect, useRef } from "react"; import { buildGameAchievementPath, buildGameDetailsPath, @@ -43,6 +43,9 @@ export function UserLibraryGameCard({ const navigate = useNavigate(); const [isTooltipHovered, setIsTooltipHovered] = useState(false); const [isPinning, setIsPinning] = useState(false); + const [useShortFormat, setUseShortFormat] = useState(false); + const cardRef = useRef(null); + const playtimeRef = useRef(null); const getStatsItemCount = useCallback(() => { let statsCount = 1; @@ -94,6 +97,50 @@ export function UserLibraryGameCard({ [numberFormatter, t] ); + const formatPlayTimeShort = useCallback( + (playTimeInSeconds = 0) => { + const minutes = playTimeInSeconds / 60; + + if (minutes < MAX_MINUTES_TO_SHOW_IN_PLAYTIME) { + return t("amount_minutes_short", { + amount: minutes.toFixed(0), + }); + } + + const hours = minutes / 60; + return t("amount_hours_short", { amount: Math.floor(hours) }); + }, + [t] + ); + + const checkForOverlap = useCallback(() => { + if (!cardRef.current || !playtimeRef.current) return; + + const cardWidth = cardRef.current.offsetWidth; + const hasButtons = game.isFavorite || isMe; + + if (hasButtons && cardWidth < 180) { + setUseShortFormat(true); + } else { + setUseShortFormat(false); + } + }, [game.isFavorite, isMe]); + + useEffect(() => { + checkForOverlap(); + + const handleResize = () => { + checkForOverlap(); + }; + + window.addEventListener('resize', handleResize); + return () => window.removeEventListener('resize', handleResize); + }, [checkForOverlap]); + + useEffect(() => { + checkForOverlap(); + }, [game.isFavorite, isMe, checkForOverlap]); + const toggleGamePinned = async () => { setIsPinning(true); @@ -119,6 +166,7 @@ export function UserLibraryGameCard({ return ( <>
  • )} )} - {formatPlayTime(game.playTimeInSeconds)} + {useShortFormat + ? formatPlayTimeShort(game.playTimeInSeconds) + : formatPlayTime(game.playTimeInSeconds) + } {userProfile?.hasActiveSubscription && From 9689c19863c1ddfc1fce0f05767c98c68d3e5d61 Mon Sep 17 00:00:00 2001 From: Moyasee Date: Tue, 30 Sep 2025 00:52:46 +0300 Subject: [PATCH 11/23] fix: state fix and remake checking for overlap --- .../game-details/modals/edit-game-modal.tsx | 144 +++++++----------- .../user-library-game-card.scss | 20 +++ .../user-library-game-card.tsx | 73 ++------- 3 files changed, 93 insertions(+), 144 deletions(-) diff --git a/src/renderer/src/pages/game-details/modals/edit-game-modal.tsx b/src/renderer/src/pages/game-details/modals/edit-game-modal.tsx index 3979d051..2413cb9e 100644 --- a/src/renderer/src/pages/game-details/modals/edit-game-modal.tsx +++ b/src/renderer/src/pages/game-details/modals/edit-game-modal.tsx @@ -29,19 +29,24 @@ export function EditGameModal({ const { showSuccessToast, showErrorToast } = useToast(); const [gameName, setGameName] = useState(""); - const [iconPath, setIconPath] = useState(""); - const [logoPath, setLogoPath] = useState(""); - const [heroPath, setHeroPath] = useState(""); - const [iconDisplayPath, setIconDisplayPath] = useState(""); - const [logoDisplayPath, setLogoDisplayPath] = useState(""); - const [heroDisplayPath, setHeroDisplayPath] = useState(""); + const [assetPaths, setAssetPaths] = useState({ + icon: "", + logo: "", + hero: "", + }); + const [assetDisplayPaths, setAssetDisplayPaths] = useState({ + icon: "", + logo: "", + hero: "", + }); + const [defaultUrls, setDefaultUrls] = useState({ + icon: null as string | null, + logo: null as string | null, + hero: null as string | null, + }); const [isUpdating, setIsUpdating] = useState(false); const [selectedAssetType, setSelectedAssetType] = useState("icon"); - const [defaultIconUrl, setDefaultIconUrl] = useState(null); - const [defaultLogoUrl, setDefaultLogoUrl] = useState(null); - const [defaultHeroUrl, setDefaultHeroUrl] = useState(null); - const isCustomGame = (game: LibraryGame | Game): boolean => { return game.shop === "custom"; }; @@ -51,32 +56,36 @@ export function EditGameModal({ }; const setCustomGameAssets = useCallback((game: LibraryGame | Game) => { - setIconPath(extractLocalPath(game.iconUrl)); - setLogoPath(extractLocalPath(game.logoImageUrl)); - setHeroPath(extractLocalPath(game.libraryHeroImageUrl)); - setIconDisplayPath(extractLocalPath(game.iconUrl)); - setLogoDisplayPath(extractLocalPath(game.logoImageUrl)); - setHeroDisplayPath(extractLocalPath(game.libraryHeroImageUrl)); + setAssetPaths({ + icon: extractLocalPath(game.iconUrl), + logo: extractLocalPath(game.logoImageUrl), + hero: extractLocalPath(game.libraryHeroImageUrl), + }); + setAssetDisplayPaths({ + icon: extractLocalPath(game.iconUrl), + logo: extractLocalPath(game.logoImageUrl), + hero: extractLocalPath(game.libraryHeroImageUrl), + }); }, []); const setNonCustomGameAssets = useCallback( (game: LibraryGame) => { - setIconPath(extractLocalPath(game.customIconUrl)); - setLogoPath(extractLocalPath(game.customLogoImageUrl)); - setHeroPath(extractLocalPath(game.customHeroImageUrl)); - setIconDisplayPath(extractLocalPath(game.customIconUrl)); - setLogoDisplayPath(extractLocalPath(game.customLogoImageUrl)); - setHeroDisplayPath(extractLocalPath(game.customHeroImageUrl)); + setAssetPaths({ + icon: extractLocalPath(game.customIconUrl), + logo: extractLocalPath(game.customLogoImageUrl), + hero: extractLocalPath(game.customHeroImageUrl), + }); + setAssetDisplayPaths({ + icon: extractLocalPath(game.customIconUrl), + logo: extractLocalPath(game.customLogoImageUrl), + hero: extractLocalPath(game.customHeroImageUrl), + }); - setDefaultIconUrl(shopDetails?.assets?.iconUrl || game.iconUrl || null); - setDefaultLogoUrl( - shopDetails?.assets?.logoImageUrl || game.logoImageUrl || null - ); - setDefaultHeroUrl( - shopDetails?.assets?.libraryHeroImageUrl || - game.libraryHeroImageUrl || - null - ); + setDefaultUrls({ + icon: shopDetails?.assets?.iconUrl || game.iconUrl || null, + logo: shopDetails?.assets?.logoImageUrl || game.logoImageUrl || null, + hero: shopDetails?.assets?.libraryHeroImageUrl || game.libraryHeroImageUrl || null, + }); }, [shopDetails] ); @@ -102,64 +111,23 @@ export function EditGameModal({ }; const getAssetPath = (assetType: AssetType): string => { - switch (assetType) { - case "icon": - return iconPath; - case "logo": - return logoPath; - case "hero": - return heroPath; - } + return assetPaths[assetType]; }; const getAssetDisplayPath = (assetType: AssetType): string => { - switch (assetType) { - case "icon": - return iconDisplayPath; - case "logo": - return logoDisplayPath; - case "hero": - return heroDisplayPath; - } + return assetDisplayPaths[assetType]; }; const setAssetPath = (assetType: AssetType, path: string): void => { - switch (assetType) { - case "icon": - setIconPath(path); - break; - case "logo": - setLogoPath(path); - break; - case "hero": - setHeroPath(path); - break; - } + setAssetPaths(prev => ({ ...prev, [assetType]: path })); }; const setAssetDisplayPath = (assetType: AssetType, path: string): void => { - switch (assetType) { - case "icon": - setIconDisplayPath(path); - break; - case "logo": - setLogoDisplayPath(path); - break; - case "hero": - setHeroDisplayPath(path); - break; - } + setAssetDisplayPaths(prev => ({ ...prev, [assetType]: path })); }; const getDefaultUrl = (assetType: AssetType): string | null => { - switch (assetType) { - case "icon": - return defaultIconUrl; - case "logo": - return defaultLogoUrl; - case "hero": - return defaultHeroUrl; - } + return defaultUrls[assetType]; }; const handleSelectAsset = async (assetType: AssetType) => { @@ -324,10 +292,10 @@ export function EditGameModal({ // Helper function to prepare custom game assets const prepareCustomGameAssets = (game: LibraryGame | Game) => { - const iconUrl = iconPath ? `local:${iconPath}` : game.iconUrl; - const logoImageUrl = logoPath ? `local:${logoPath}` : game.logoImageUrl; - const libraryHeroImageUrl = heroPath - ? `local:${heroPath}` + const iconUrl = assetPaths.icon ? `local:${assetPaths.icon}` : game.iconUrl; + const logoImageUrl = assetPaths.logo ? `local:${assetPaths.logo}` : game.logoImageUrl; + const libraryHeroImageUrl = assetPaths.hero + ? `local:${assetPaths.hero}` : game.libraryHeroImageUrl; return { iconUrl, logoImageUrl, libraryHeroImageUrl }; @@ -336,9 +304,9 @@ export function EditGameModal({ // Helper function to prepare non-custom game assets const prepareNonCustomGameAssets = () => { return { - customIconUrl: iconPath ? `local:${iconPath}` : null, - customLogoImageUrl: logoPath ? `local:${logoPath}` : null, - customHeroImageUrl: heroPath ? `local:${heroPath}` : null, + customIconUrl: assetPaths.icon ? `local:${assetPaths.icon}` : null, + customLogoImageUrl: assetPaths.logo ? `local:${assetPaths.logo}` : null, + customHeroImageUrl: assetPaths.hero ? `local:${assetPaths.hero}` : null, }; }; @@ -407,9 +375,11 @@ export function EditGameModal({ if (isCustomGame(game)) { setCustomGameAssets(game); // Clear default URLs for custom games - setDefaultIconUrl(null); - setDefaultLogoUrl(null); - setDefaultHeroUrl(null); + setDefaultUrls({ + icon: null, + logo: null, + hero: null, + }); } else { setNonCustomGameAssets(game as LibraryGame); } diff --git a/src/renderer/src/pages/profile/profile-content/user-library-game-card.scss b/src/renderer/src/pages/profile/profile-content/user-library-game-card.scss index e40061de..dccd9dd1 100644 --- a/src/renderer/src/pages/profile/profile-content/user-library-game-card.scss +++ b/src/renderer/src/pages/profile/profile-content/user-library-game-card.scss @@ -8,6 +8,7 @@ display: flex; transition: all ease 0.2s; cursor: grab; + container-type: inline-size; &:hover { transform: scale(1.05); @@ -160,6 +161,25 @@ transform: translateY(-1px); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); } + + &-long { + display: inline; + } + + &-short { + display: none; + } + + // When the card is narrow (less than 180px), show short format + @container (max-width: 180px) { + &-long { + display: none; + } + + &-short { + display: inline; + } + } } &__manual-playtime { color: globals.$warning-color; diff --git a/src/renderer/src/pages/profile/profile-content/user-library-game-card.tsx b/src/renderer/src/pages/profile/profile-content/user-library-game-card.tsx index d30dbf8a..ec7736e0 100644 --- a/src/renderer/src/pages/profile/profile-content/user-library-game-card.tsx +++ b/src/renderer/src/pages/profile/profile-content/user-library-game-card.tsx @@ -2,7 +2,7 @@ import { UserGame } from "@types"; import HydraIcon from "@renderer/assets/icons/hydra.svg?react"; import { useFormat, useToast } from "@renderer/hooks"; import { useNavigate } from "react-router-dom"; -import { useCallback, useContext, useState, useEffect, useRef } from "react"; +import { useCallback, useContext, useState } from "react"; import { buildGameAchievementPath, buildGameDetailsPath, @@ -43,9 +43,7 @@ export function UserLibraryGameCard({ const navigate = useNavigate(); const [isTooltipHovered, setIsTooltipHovered] = useState(false); const [isPinning, setIsPinning] = useState(false); - const [useShortFormat, setUseShortFormat] = useState(false); - const cardRef = useRef(null); - const playtimeRef = useRef(null); + const getStatsItemCount = useCallback(() => { let statsCount = 1; @@ -82,64 +80,25 @@ export function UserLibraryGameCard({ }; const formatPlayTime = useCallback( - (playTimeInSeconds = 0) => { + (playTimeInSeconds = 0, isShort = false) => { const minutes = playTimeInSeconds / 60; if (minutes < MAX_MINUTES_TO_SHOW_IN_PLAYTIME) { - return t("amount_minutes", { + return t(isShort ? "amount_minutes_short" : "amount_minutes", { amount: minutes.toFixed(0), }); } const hours = minutes / 60; - return t("amount_hours", { amount: numberFormatter.format(hours) }); + const hoursKey = isShort ? "amount_hours_short" : "amount_hours"; + const hoursAmount = isShort ? Math.floor(hours) : numberFormatter.format(hours); + + return t(hoursKey, { amount: hoursAmount }); }, [numberFormatter, t] ); - const formatPlayTimeShort = useCallback( - (playTimeInSeconds = 0) => { - const minutes = playTimeInSeconds / 60; - if (minutes < MAX_MINUTES_TO_SHOW_IN_PLAYTIME) { - return t("amount_minutes_short", { - amount: minutes.toFixed(0), - }); - } - - const hours = minutes / 60; - return t("amount_hours_short", { amount: Math.floor(hours) }); - }, - [t] - ); - - const checkForOverlap = useCallback(() => { - if (!cardRef.current || !playtimeRef.current) return; - - const cardWidth = cardRef.current.offsetWidth; - const hasButtons = game.isFavorite || isMe; - - if (hasButtons && cardWidth < 180) { - setUseShortFormat(true); - } else { - setUseShortFormat(false); - } - }, [game.isFavorite, isMe]); - - useEffect(() => { - checkForOverlap(); - - const handleResize = () => { - checkForOverlap(); - }; - - window.addEventListener('resize', handleResize); - return () => window.removeEventListener('resize', handleResize); - }, [checkForOverlap]); - - useEffect(() => { - checkForOverlap(); - }, [game.isFavorite, isMe, checkForOverlap]); const toggleGamePinned = async () => { setIsPinning(true); @@ -166,7 +125,6 @@ export function UserLibraryGameCard({ return ( <>
  • )} - )} - {useShortFormat - ? formatPlayTimeShort(game.playTimeInSeconds) - : formatPlayTime(game.playTimeInSeconds) - } - + + {formatPlayTime(game.playTimeInSeconds)} + + + {formatPlayTime(game.playTimeInSeconds, true)} + +
  • {userProfile?.hasActiveSubscription && game.achievementCount > 0 && ( From 0f3d6ef76f36e4b10f8d9dd4060d4f3bcf6d8320 Mon Sep 17 00:00:00 2001 From: Moyasee Date: Tue, 30 Sep 2025 01:18:44 +0300 Subject: [PATCH 12/23] fix: state fix and remake checking for overlap --- .../pages/game-details/modals/edit-game-modal.tsx | 13 +++++++++---- .../profile-content/user-library-game-card.tsx | 11 +++++------ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/renderer/src/pages/game-details/modals/edit-game-modal.tsx b/src/renderer/src/pages/game-details/modals/edit-game-modal.tsx index 2413cb9e..e55772c6 100644 --- a/src/renderer/src/pages/game-details/modals/edit-game-modal.tsx +++ b/src/renderer/src/pages/game-details/modals/edit-game-modal.tsx @@ -84,7 +84,10 @@ export function EditGameModal({ setDefaultUrls({ icon: shopDetails?.assets?.iconUrl || game.iconUrl || null, logo: shopDetails?.assets?.logoImageUrl || game.logoImageUrl || null, - hero: shopDetails?.assets?.libraryHeroImageUrl || game.libraryHeroImageUrl || null, + hero: + shopDetails?.assets?.libraryHeroImageUrl || + game.libraryHeroImageUrl || + null, }); }, [shopDetails] @@ -119,11 +122,11 @@ export function EditGameModal({ }; const setAssetPath = (assetType: AssetType, path: string): void => { - setAssetPaths(prev => ({ ...prev, [assetType]: path })); + setAssetPaths((prev) => ({ ...prev, [assetType]: path })); }; const setAssetDisplayPath = (assetType: AssetType, path: string): void => { - setAssetDisplayPaths(prev => ({ ...prev, [assetType]: path })); + setAssetDisplayPaths((prev) => ({ ...prev, [assetType]: path })); }; const getDefaultUrl = (assetType: AssetType): string | null => { @@ -293,7 +296,9 @@ export function EditGameModal({ // Helper function to prepare custom game assets const prepareCustomGameAssets = (game: LibraryGame | Game) => { const iconUrl = assetPaths.icon ? `local:${assetPaths.icon}` : game.iconUrl; - const logoImageUrl = assetPaths.logo ? `local:${assetPaths.logo}` : game.logoImageUrl; + const logoImageUrl = assetPaths.logo + ? `local:${assetPaths.logo}` + : game.logoImageUrl; const libraryHeroImageUrl = assetPaths.hero ? `local:${assetPaths.hero}` : game.libraryHeroImageUrl; diff --git a/src/renderer/src/pages/profile/profile-content/user-library-game-card.tsx b/src/renderer/src/pages/profile/profile-content/user-library-game-card.tsx index ec7736e0..251a3bc7 100644 --- a/src/renderer/src/pages/profile/profile-content/user-library-game-card.tsx +++ b/src/renderer/src/pages/profile/profile-content/user-library-game-card.tsx @@ -44,7 +44,6 @@ export function UserLibraryGameCard({ const [isTooltipHovered, setIsTooltipHovered] = useState(false); const [isPinning, setIsPinning] = useState(false); - const getStatsItemCount = useCallback(() => { let statsCount = 1; if (game.achievementsPointsEarnedSum > 0) statsCount++; @@ -91,15 +90,15 @@ export function UserLibraryGameCard({ const hours = minutes / 60; const hoursKey = isShort ? "amount_hours_short" : "amount_hours"; - const hoursAmount = isShort ? Math.floor(hours) : numberFormatter.format(hours); - + const hoursAmount = isShort + ? Math.floor(hours) + : numberFormatter.format(hours); + return t(hoursKey, { amount: hoursAmount }); }, [numberFormatter, t] ); - - const toggleGamePinned = async () => { setIsPinning(true); @@ -162,7 +161,7 @@ export function UserLibraryGameCard({ )}
    )} -
    Date: Tue, 30 Sep 2025 01:43:13 +0300 Subject: [PATCH 13/23] fix: playtime font-size increased --- .../pages/profile/profile-content/user-library-game-card.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/renderer/src/pages/profile/profile-content/user-library-game-card.scss b/src/renderer/src/pages/profile/profile-content/user-library-game-card.scss index dccd9dd1..a19961fd 100644 --- a/src/renderer/src/pages/profile/profile-content/user-library-game-card.scss +++ b/src/renderer/src/pages/profile/profile-content/user-library-game-card.scss @@ -164,10 +164,12 @@ &-long { display: inline; + font-size: 12px; } &-short { display: none; + font-size: 12px; } // When the card is narrow (less than 180px), show short format From 959bed746b75f74217ee9fc052427f742a4d0f40 Mon Sep 17 00:00:00 2001 From: Moyasee Date: Tue, 30 Sep 2025 02:09:19 +0300 Subject: [PATCH 14/23] fix: original path to image not showing in modal after updating game asset --- src/main/events/library/update-custom-game.ts | 8 ++++- .../library/update-game-custom-assets.ts | 18 +++++++++-- src/preload/index.ts | 20 +++++++++--- src/renderer/src/declaration.d.ts | 10 ++++-- .../game-details/modals/edit-game-modal.tsx | 32 +++++++++++++++++-- src/types/level.types.ts | 6 ++++ 6 files changed, 81 insertions(+), 13 deletions(-) diff --git a/src/main/events/library/update-custom-game.ts b/src/main/events/library/update-custom-game.ts index 82d7f45f..62473e54 100644 --- a/src/main/events/library/update-custom-game.ts +++ b/src/main/events/library/update-custom-game.ts @@ -11,7 +11,10 @@ const updateCustomGame = async ( title: string, iconUrl?: string, logoImageUrl?: string, - libraryHeroImageUrl?: string + libraryHeroImageUrl?: string, + originalIconPath?: string, + originalLogoPath?: string, + originalHeroPath?: string ) => { const gameKey = levelKeys.game(shop, objectId); @@ -40,6 +43,9 @@ const updateCustomGame = async ( iconUrl: iconUrl || null, logoImageUrl: logoImageUrl || null, libraryHeroImageUrl: libraryHeroImageUrl || null, + originalIconPath: originalIconPath || existingGame.originalIconPath || null, + originalLogoPath: originalLogoPath || existingGame.originalLogoPath || null, + originalHeroPath: originalHeroPath || existingGame.originalHeroPath || null, }; await gamesSublevel.put(gameKey, updatedGame); diff --git a/src/main/events/library/update-game-custom-assets.ts b/src/main/events/library/update-game-custom-assets.ts index 4bd4e517..8cfc79f0 100644 --- a/src/main/events/library/update-game-custom-assets.ts +++ b/src/main/events/library/update-game-custom-assets.ts @@ -38,7 +38,10 @@ const updateGameData = async ( title: string, customIconUrl?: string | null, customLogoImageUrl?: string | null, - customHeroImageUrl?: string | null + customHeroImageUrl?: string | null, + customOriginalIconPath?: string | null, + customOriginalLogoPath?: string | null, + customOriginalHeroPath?: string | null ): Promise => { const updatedGame = { ...existingGame, @@ -46,6 +49,9 @@ const updateGameData = async ( ...(customIconUrl !== undefined && { customIconUrl }), ...(customLogoImageUrl !== undefined && { customLogoImageUrl }), ...(customHeroImageUrl !== undefined && { customHeroImageUrl }), + ...(customOriginalIconPath !== undefined && { customOriginalIconPath }), + ...(customOriginalLogoPath !== undefined && { customOriginalLogoPath }), + ...(customOriginalHeroPath !== undefined && { customOriginalHeroPath }), }; await gamesSublevel.put(gameKey, updatedGame); @@ -87,7 +93,10 @@ const updateGameCustomAssets = async ( title: string, customIconUrl?: string | null, customLogoImageUrl?: string | null, - customHeroImageUrl?: string | null + customHeroImageUrl?: string | null, + customOriginalIconPath?: string | null, + customOriginalLogoPath?: string | null, + customOriginalHeroPath?: string | null ) => { const gameKey = levelKeys.game(shop, objectId); @@ -109,7 +118,10 @@ const updateGameCustomAssets = async ( title, customIconUrl, customLogoImageUrl, - customHeroImageUrl + customHeroImageUrl, + customOriginalIconPath, + customOriginalLogoPath, + customOriginalHeroPath ); await updateShopAssets(gameKey, title); diff --git a/src/preload/index.ts b/src/preload/index.ts index e536f8c7..e4a29c90 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -158,7 +158,10 @@ contextBridge.exposeInMainWorld("electron", { title: string, iconUrl?: string, logoImageUrl?: string, - libraryHeroImageUrl?: string + libraryHeroImageUrl?: string, + originalIconPath?: string, + originalLogoPath?: string, + originalHeroPath?: string ) => ipcRenderer.invoke( "updateCustomGame", @@ -167,7 +170,10 @@ contextBridge.exposeInMainWorld("electron", { title, iconUrl, logoImageUrl, - libraryHeroImageUrl + libraryHeroImageUrl, + originalIconPath, + originalLogoPath, + originalHeroPath ), updateGameCustomAssets: ( shop: GameShop, @@ -175,7 +181,10 @@ contextBridge.exposeInMainWorld("electron", { title: string, customIconUrl?: string | null, customLogoImageUrl?: string | null, - customHeroImageUrl?: string | null + customHeroImageUrl?: string | null, + customOriginalIconPath?: string | null, + customOriginalLogoPath?: string | null, + customOriginalHeroPath?: string | null ) => ipcRenderer.invoke( "updateGameCustomAssets", @@ -184,7 +193,10 @@ contextBridge.exposeInMainWorld("electron", { title, customIconUrl, customLogoImageUrl, - customHeroImageUrl + customHeroImageUrl, + customOriginalIconPath, + customOriginalLogoPath, + customOriginalHeroPath ), createGameShortcut: ( shop: GameShop, diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index 81d18940..9477edb5 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -125,7 +125,10 @@ declare global { title: string, iconUrl?: string, logoImageUrl?: string, - libraryHeroImageUrl?: string + libraryHeroImageUrl?: string, + originalIconPath?: string, + originalLogoPath?: string, + originalHeroPath?: string ) => Promise; copyCustomGameAsset: ( sourcePath: string, @@ -141,7 +144,10 @@ declare global { title: string, customIconUrl?: string | null, customLogoImageUrl?: string | null, - customHeroImageUrl?: string | null + customHeroImageUrl?: string | null, + customOriginalIconPath?: string | null, + customOriginalLogoPath?: string | null, + customOriginalHeroPath?: string | null ) => Promise; createGameShortcut: ( shop: GameShop, diff --git a/src/renderer/src/pages/game-details/modals/edit-game-modal.tsx b/src/renderer/src/pages/game-details/modals/edit-game-modal.tsx index e55772c6..37801c6f 100644 --- a/src/renderer/src/pages/game-details/modals/edit-game-modal.tsx +++ b/src/renderer/src/pages/game-details/modals/edit-game-modal.tsx @@ -39,6 +39,11 @@ export function EditGameModal({ logo: "", hero: "", }); + const [originalAssetPaths, setOriginalAssetPaths] = useState({ + icon: "", + logo: "", + hero: "", + }); const [defaultUrls, setDefaultUrls] = useState({ icon: null as string | null, logo: null as string | null, @@ -66,6 +71,11 @@ export function EditGameModal({ logo: extractLocalPath(game.logoImageUrl), hero: extractLocalPath(game.libraryHeroImageUrl), }); + setOriginalAssetPaths({ + icon: (game as any).originalIconPath || extractLocalPath(game.iconUrl), + logo: (game as any).originalLogoPath || extractLocalPath(game.logoImageUrl), + hero: (game as any).originalHeroPath || extractLocalPath(game.libraryHeroImageUrl), + }); }, []); const setNonCustomGameAssets = useCallback( @@ -80,6 +90,11 @@ export function EditGameModal({ logo: extractLocalPath(game.customLogoImageUrl), hero: extractLocalPath(game.customHeroImageUrl), }); + setOriginalAssetPaths({ + icon: (game as any).customOriginalIconPath || extractLocalPath(game.customIconUrl), + logo: (game as any).customOriginalLogoPath || extractLocalPath(game.customLogoImageUrl), + hero: (game as any).customOriginalHeroPath || extractLocalPath(game.customHeroImageUrl), + }); setDefaultUrls({ icon: shopDetails?.assets?.iconUrl || game.iconUrl || null, @@ -118,7 +133,8 @@ export function EditGameModal({ }; const getAssetDisplayPath = (assetType: AssetType): string => { - return assetDisplayPaths[assetType]; + // Use original path if available, otherwise fall back to display path + return originalAssetPaths[assetType] || assetDisplayPaths[assetType]; }; const setAssetPath = (assetType: AssetType, path: string): void => { @@ -153,10 +169,13 @@ export function EditGameModal({ ); setAssetPath(assetType, copiedAssetUrl.replace("local:", "")); setAssetDisplayPath(assetType, originalPath); + // Store the original path for display purposes + setOriginalAssetPaths((prev) => ({ ...prev, [assetType]: originalPath })); } catch (error) { console.error(`Failed to copy ${assetType} asset:`, error); setAssetPath(assetType, originalPath); setAssetDisplayPath(assetType, originalPath); + setOriginalAssetPaths((prev) => ({ ...prev, [assetType]: originalPath })); } } }; @@ -164,6 +183,7 @@ export function EditGameModal({ const handleRestoreDefault = (assetType: AssetType) => { setAssetPath(assetType, ""); setAssetDisplayPath(assetType, ""); + setOriginalAssetPaths((prev) => ({ ...prev, [assetType]: "" })); }; const getOriginalTitle = (): string => { @@ -326,7 +346,10 @@ export function EditGameModal({ gameName.trim(), iconUrl || undefined, logoImageUrl || undefined, - libraryHeroImageUrl || undefined + libraryHeroImageUrl || undefined, + originalAssetPaths.icon || undefined, + originalAssetPaths.logo || undefined, + originalAssetPaths.hero || undefined ); }; @@ -341,7 +364,10 @@ export function EditGameModal({ gameName.trim(), customIconUrl, customLogoImageUrl, - customHeroImageUrl + customHeroImageUrl, + originalAssetPaths.icon || undefined, + originalAssetPaths.logo || undefined, + originalAssetPaths.hero || undefined ); }; diff --git a/src/types/level.types.ts b/src/types/level.types.ts index 73fce370..8a6c56a0 100644 --- a/src/types/level.types.ts +++ b/src/types/level.types.ts @@ -38,6 +38,12 @@ export interface Game { customIconUrl?: string | null; customLogoImageUrl?: string | null; customHeroImageUrl?: string | null; + originalIconPath?: string | null; + originalLogoPath?: string | null; + originalHeroPath?: string | null; + customOriginalIconPath?: string | null; + customOriginalLogoPath?: string | null; + customOriginalHeroPath?: string | null; playTimeInMilliseconds: number; unsyncedDeltaPlayTimeInMilliseconds?: number; lastTimePlayed: Date | null; From ceb236c40c0bae6e3d97ef0f844d54d9f40baa13 Mon Sep 17 00:00:00 2001 From: Moyasee Date: Tue, 30 Sep 2025 02:17:17 +0300 Subject: [PATCH 15/23] fix: async error function had too many arguments --- src/main/events/library/update-custom-game.ts | 33 ++++++--- .../library/update-game-custom-assets.ts | 72 +++++++++++++------ src/preload/index.ts | 68 ++++++------------ src/renderer/src/declaration.d.ts | 44 ++++++------ .../game-details/modals/edit-game-modal.tsx | 67 ++++++++++------- 5 files changed, 160 insertions(+), 124 deletions(-) diff --git a/src/main/events/library/update-custom-game.ts b/src/main/events/library/update-custom-game.ts index 62473e54..8129fc57 100644 --- a/src/main/events/library/update-custom-game.ts +++ b/src/main/events/library/update-custom-game.ts @@ -4,18 +4,33 @@ import type { GameShop } from "@types"; import fs from "node:fs"; import { logger } from "@main/services"; +interface UpdateCustomGameParams { + shop: GameShop; + objectId: string; + title: string; + iconUrl?: string; + logoImageUrl?: string; + libraryHeroImageUrl?: string; + originalIconPath?: string; + originalLogoPath?: string; + originalHeroPath?: string; +} + const updateCustomGame = async ( _event: Electron.IpcMainInvokeEvent, - shop: GameShop, - objectId: string, - title: string, - iconUrl?: string, - logoImageUrl?: string, - libraryHeroImageUrl?: string, - originalIconPath?: string, - originalLogoPath?: string, - originalHeroPath?: string + params: UpdateCustomGameParams ) => { + const { + shop, + objectId, + title, + iconUrl, + logoImageUrl, + libraryHeroImageUrl, + originalIconPath, + originalLogoPath, + originalHeroPath, + } = params; const gameKey = levelKeys.game(shop, objectId); const existingGame = await gamesSublevel.get(gameKey); diff --git a/src/main/events/library/update-game-custom-assets.ts b/src/main/events/library/update-game-custom-assets.ts index 8cfc79f0..57b14775 100644 --- a/src/main/events/library/update-game-custom-assets.ts +++ b/src/main/events/library/update-game-custom-assets.ts @@ -32,17 +32,32 @@ const collectOldAssetPaths = ( return oldAssetPaths; }; +interface UpdateGameDataParams { + gameKey: string; + existingGame: Game; + title: string; + customIconUrl?: string | null; + customLogoImageUrl?: string | null; + customHeroImageUrl?: string | null; + customOriginalIconPath?: string | null; + customOriginalLogoPath?: string | null; + customOriginalHeroPath?: string | null; +} + const updateGameData = async ( - gameKey: string, - existingGame: Game, - title: string, - customIconUrl?: string | null, - customLogoImageUrl?: string | null, - customHeroImageUrl?: string | null, - customOriginalIconPath?: string | null, - customOriginalLogoPath?: string | null, - customOriginalHeroPath?: string | null + params: UpdateGameDataParams ): Promise => { + const { + gameKey, + existingGame, + title, + customIconUrl, + customLogoImageUrl, + customHeroImageUrl, + customOriginalIconPath, + customOriginalLogoPath, + customOriginalHeroPath, + } = params; const updatedGame = { ...existingGame, title, @@ -86,18 +101,33 @@ const deleteOldAssetFiles = async (oldAssetPaths: string[]): Promise => { } }; +interface UpdateGameCustomAssetsParams { + shop: GameShop; + objectId: string; + title: string; + customIconUrl?: string | null; + customLogoImageUrl?: string | null; + customHeroImageUrl?: string | null; + customOriginalIconPath?: string | null; + customOriginalLogoPath?: string | null; + customOriginalHeroPath?: string | null; +} + const updateGameCustomAssets = async ( _event: Electron.IpcMainInvokeEvent, - shop: GameShop, - objectId: string, - title: string, - customIconUrl?: string | null, - customLogoImageUrl?: string | null, - customHeroImageUrl?: string | null, - customOriginalIconPath?: string | null, - customOriginalLogoPath?: string | null, - customOriginalHeroPath?: string | null + params: UpdateGameCustomAssetsParams ) => { + const { + shop, + objectId, + title, + customIconUrl, + customLogoImageUrl, + customHeroImageUrl, + customOriginalIconPath, + customOriginalLogoPath, + customOriginalHeroPath, + } = params; const gameKey = levelKeys.game(shop, objectId); const existingGame = await gamesSublevel.get(gameKey); @@ -112,7 +142,7 @@ const updateGameCustomAssets = async ( customHeroImageUrl ); - const updatedGame = await updateGameData( + const updatedGame = await updateGameData({ gameKey, existingGame, title, @@ -121,8 +151,8 @@ const updateGameCustomAssets = async ( customHeroImageUrl, customOriginalIconPath, customOriginalLogoPath, - customOriginalHeroPath - ); + customOriginalHeroPath, + }); await updateShopAssets(gameKey, title); diff --git a/src/preload/index.ts b/src/preload/index.ts index e4a29c90..b92ba137 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -152,52 +152,28 @@ contextBridge.exposeInMainWorld("electron", { deleteTempFile: (filePath: string) => ipcRenderer.invoke("deleteTempFile", filePath), cleanupUnusedAssets: () => ipcRenderer.invoke("cleanupUnusedAssets"), - updateCustomGame: ( - shop: GameShop, - objectId: string, - title: string, - iconUrl?: string, - logoImageUrl?: string, - libraryHeroImageUrl?: string, - originalIconPath?: string, - originalLogoPath?: string, - originalHeroPath?: string - ) => - ipcRenderer.invoke( - "updateCustomGame", - shop, - objectId, - title, - iconUrl, - logoImageUrl, - libraryHeroImageUrl, - originalIconPath, - originalLogoPath, - originalHeroPath - ), - updateGameCustomAssets: ( - shop: GameShop, - objectId: string, - title: string, - customIconUrl?: string | null, - customLogoImageUrl?: string | null, - customHeroImageUrl?: string | null, - customOriginalIconPath?: string | null, - customOriginalLogoPath?: string | null, - customOriginalHeroPath?: string | null - ) => - ipcRenderer.invoke( - "updateGameCustomAssets", - shop, - objectId, - title, - customIconUrl, - customLogoImageUrl, - customHeroImageUrl, - customOriginalIconPath, - customOriginalLogoPath, - customOriginalHeroPath - ), + updateCustomGame: (params: { + shop: GameShop; + objectId: string; + title: string; + iconUrl?: string; + logoImageUrl?: string; + libraryHeroImageUrl?: string; + originalIconPath?: string; + originalLogoPath?: string; + originalHeroPath?: string; + }) => ipcRenderer.invoke("updateCustomGame", params), + updateGameCustomAssets: (params: { + shop: GameShop; + objectId: string; + title: string; + customIconUrl?: string | null; + customLogoImageUrl?: string | null; + customHeroImageUrl?: string | null; + customOriginalIconPath?: string | null; + customOriginalLogoPath?: string | null; + customOriginalHeroPath?: string | null; + }) => ipcRenderer.invoke("updateGameCustomAssets", params), createGameShortcut: ( shop: GameShop, objectId: string, diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index 9477edb5..e6277888 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -119,17 +119,17 @@ declare global { logoImageUrl?: string, libraryHeroImageUrl?: string ) => Promise; - updateCustomGame: ( - shop: GameShop, - objectId: string, - title: string, - iconUrl?: string, - logoImageUrl?: string, - libraryHeroImageUrl?: string, - originalIconPath?: string, - originalLogoPath?: string, - originalHeroPath?: string - ) => Promise; + updateCustomGame: (params: { + shop: GameShop; + objectId: string; + title: string; + iconUrl?: string; + logoImageUrl?: string; + libraryHeroImageUrl?: string; + originalIconPath?: string; + originalLogoPath?: string; + originalHeroPath?: string; + }) => Promise; copyCustomGameAsset: ( sourcePath: string, assetType: "icon" | "logo" | "hero" @@ -138,17 +138,17 @@ declare global { deletedCount: number; errors: string[]; }>; - updateGameCustomAssets: ( - shop: GameShop, - objectId: string, - title: string, - customIconUrl?: string | null, - customLogoImageUrl?: string | null, - customHeroImageUrl?: string | null, - customOriginalIconPath?: string | null, - customOriginalLogoPath?: string | null, - customOriginalHeroPath?: string | null - ) => Promise; + updateGameCustomAssets: (params: { + shop: GameShop; + objectId: string; + title: string; + customIconUrl?: string | null; + customLogoImageUrl?: string | null; + customHeroImageUrl?: string | null; + customOriginalIconPath?: string | null; + customOriginalLogoPath?: string | null; + customOriginalHeroPath?: string | null; + }) => Promise; createGameShortcut: ( shop: GameShop, objectId: string, diff --git a/src/renderer/src/pages/game-details/modals/edit-game-modal.tsx b/src/renderer/src/pages/game-details/modals/edit-game-modal.tsx index 37801c6f..0f6df95d 100644 --- a/src/renderer/src/pages/game-details/modals/edit-game-modal.tsx +++ b/src/renderer/src/pages/game-details/modals/edit-game-modal.tsx @@ -73,8 +73,11 @@ export function EditGameModal({ }); setOriginalAssetPaths({ icon: (game as any).originalIconPath || extractLocalPath(game.iconUrl), - logo: (game as any).originalLogoPath || extractLocalPath(game.logoImageUrl), - hero: (game as any).originalHeroPath || extractLocalPath(game.libraryHeroImageUrl), + logo: + (game as any).originalLogoPath || extractLocalPath(game.logoImageUrl), + hero: + (game as any).originalHeroPath || + extractLocalPath(game.libraryHeroImageUrl), }); }, []); @@ -91,9 +94,15 @@ export function EditGameModal({ hero: extractLocalPath(game.customHeroImageUrl), }); setOriginalAssetPaths({ - icon: (game as any).customOriginalIconPath || extractLocalPath(game.customIconUrl), - logo: (game as any).customOriginalLogoPath || extractLocalPath(game.customLogoImageUrl), - hero: (game as any).customOriginalHeroPath || extractLocalPath(game.customHeroImageUrl), + icon: + (game as any).customOriginalIconPath || + extractLocalPath(game.customIconUrl), + logo: + (game as any).customOriginalLogoPath || + extractLocalPath(game.customLogoImageUrl), + hero: + (game as any).customOriginalHeroPath || + extractLocalPath(game.customHeroImageUrl), }); setDefaultUrls({ @@ -170,12 +179,18 @@ export function EditGameModal({ setAssetPath(assetType, copiedAssetUrl.replace("local:", "")); setAssetDisplayPath(assetType, originalPath); // Store the original path for display purposes - setOriginalAssetPaths((prev) => ({ ...prev, [assetType]: originalPath })); + setOriginalAssetPaths((prev) => ({ + ...prev, + [assetType]: originalPath, + })); } catch (error) { console.error(`Failed to copy ${assetType} asset:`, error); setAssetPath(assetType, originalPath); setAssetDisplayPath(assetType, originalPath); - setOriginalAssetPaths((prev) => ({ ...prev, [assetType]: originalPath })); + setOriginalAssetPaths((prev) => ({ + ...prev, + [assetType]: originalPath, + })); } } }; @@ -340,17 +355,17 @@ export function EditGameModal({ const { iconUrl, logoImageUrl, libraryHeroImageUrl } = prepareCustomGameAssets(game); - return window.electron.updateCustomGame( - game.shop, - game.objectId, - gameName.trim(), - iconUrl || undefined, - logoImageUrl || undefined, - libraryHeroImageUrl || undefined, - originalAssetPaths.icon || undefined, - originalAssetPaths.logo || undefined, - originalAssetPaths.hero || undefined - ); + return window.electron.updateCustomGame({ + shop: game.shop, + objectId: game.objectId, + title: gameName.trim(), + iconUrl: iconUrl || undefined, + logoImageUrl: logoImageUrl || undefined, + libraryHeroImageUrl: libraryHeroImageUrl || undefined, + originalIconPath: originalAssetPaths.icon || undefined, + originalLogoPath: originalAssetPaths.logo || undefined, + originalHeroPath: originalAssetPaths.hero || undefined, + }); }; // Helper function to update non-custom game @@ -358,17 +373,17 @@ export function EditGameModal({ const { customIconUrl, customLogoImageUrl, customHeroImageUrl } = prepareNonCustomGameAssets(); - return window.electron.updateGameCustomAssets( - game.shop, - game.objectId, - gameName.trim(), + return window.electron.updateGameCustomAssets({ + shop: game.shop, + objectId: game.objectId, + title: gameName.trim(), customIconUrl, customLogoImageUrl, customHeroImageUrl, - originalAssetPaths.icon || undefined, - originalAssetPaths.logo || undefined, - originalAssetPaths.hero || undefined - ); + customOriginalIconPath: originalAssetPaths.icon || undefined, + customOriginalLogoPath: originalAssetPaths.logo || undefined, + customOriginalHeroPath: originalAssetPaths.hero || undefined, + }); }; const handleUpdateGame = async () => { From e5646240abdbb08a6972c49078afdfc8ca45c679 Mon Sep 17 00:00:00 2001 From: Moyasee Date: Tue, 30 Sep 2025 02:18:22 +0300 Subject: [PATCH 16/23] fix: async error function had too many arguments --- .../library/update-game-custom-assets.ts | 4 +--- src/preload/index.ts | 20 +++++++++---------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/main/events/library/update-game-custom-assets.ts b/src/main/events/library/update-game-custom-assets.ts index 57b14775..1f912901 100644 --- a/src/main/events/library/update-game-custom-assets.ts +++ b/src/main/events/library/update-game-custom-assets.ts @@ -44,9 +44,7 @@ interface UpdateGameDataParams { customOriginalHeroPath?: string | null; } -const updateGameData = async ( - params: UpdateGameDataParams -): Promise => { +const updateGameData = async (params: UpdateGameDataParams): Promise => { const { gameKey, existingGame, diff --git a/src/preload/index.ts b/src/preload/index.ts index b92ba137..17c1225f 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -164,16 +164,16 @@ contextBridge.exposeInMainWorld("electron", { originalHeroPath?: string; }) => ipcRenderer.invoke("updateCustomGame", params), updateGameCustomAssets: (params: { - shop: GameShop; - objectId: string; - title: string; - customIconUrl?: string | null; - customLogoImageUrl?: string | null; - customHeroImageUrl?: string | null; - customOriginalIconPath?: string | null; - customOriginalLogoPath?: string | null; - customOriginalHeroPath?: string | null; - }) => ipcRenderer.invoke("updateGameCustomAssets", params), + shop: GameShop; + objectId: string; + title: string; + customIconUrl?: string | null; + customLogoImageUrl?: string | null; + customHeroImageUrl?: string | null; + customOriginalIconPath?: string | null; + customOriginalLogoPath?: string | null; + customOriginalHeroPath?: string | null; + }) => ipcRenderer.invoke("updateGameCustomAssets", params), createGameShortcut: ( shop: GameShop, objectId: string, From de4b039d105332fad4bc36c58d1e4dd323675f56 Mon Sep 17 00:00:00 2001 From: Moyasee Date: Tue, 30 Sep 2025 02:46:58 +0300 Subject: [PATCH 17/23] Fix: deleted favorite icon from profile game card and fixed the media for showing short format of hours --- .../user-library-game-card.scss | 24 +---------- .../user-library-game-card.tsx | 40 ++++++++----------- 2 files changed, 17 insertions(+), 47 deletions(-) diff --git a/src/renderer/src/pages/profile/profile-content/user-library-game-card.scss b/src/renderer/src/pages/profile/profile-content/user-library-game-card.scss index a19961fd..5d0d7f2c 100644 --- a/src/renderer/src/pages/profile/profile-content/user-library-game-card.scss +++ b/src/renderer/src/pages/profile/profile-content/user-library-game-card.scss @@ -91,28 +91,6 @@ z-index: 2; } - &__favorite-icon { - color: rgba(255, 255, 255, 0.8); - background: rgba(0, 0, 0, 0.4); - backdrop-filter: blur(8px); - -webkit-backdrop-filter: blur(8px); - border: solid 1px rgba(255, 255, 255, 0.15); - border-radius: 50%; - padding: 6px; - display: flex; - align-items: center; - justify-content: center; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); - transition: all ease 0.2s; - - &:hover { - background: rgba(0, 0, 0, 0.5); - border-color: rgba(255, 255, 255, 0.25); - transform: translateY(-1px); - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); - } - } - &__pin-button { color: rgba(255, 255, 255, 0.8); background: rgba(0, 0, 0, 0.4); @@ -173,7 +151,7 @@ } // When the card is narrow (less than 180px), show short format - @container (max-width: 180px) { + @container (max-width: 140px) { &-long { display: none; } diff --git a/src/renderer/src/pages/profile/profile-content/user-library-game-card.tsx b/src/renderer/src/pages/profile/profile-content/user-library-game-card.tsx index 251a3bc7..eac0912d 100644 --- a/src/renderer/src/pages/profile/profile-content/user-library-game-card.tsx +++ b/src/renderer/src/pages/profile/profile-content/user-library-game-card.tsx @@ -13,7 +13,6 @@ import { ClockIcon, TrophyIcon, AlertFillIcon, - HeartFillIcon, PinIcon, PinSlashIcon, } from "@primer/octicons-react"; @@ -135,30 +134,23 @@ export function UserLibraryGameCard({ onClick={() => navigate(buildUserGameDetailsPath(game))} >
    - {(game.isFavorite || isMe) && ( + {isMe && (
    - {game.isFavorite && ( -
    - -
    - )} - {isMe && ( - - )} +
    )}
    Date: Mon, 29 Sep 2025 22:15:53 +0100 Subject: [PATCH 18/23] feat: sync with main --- .../profile-content/profile-content.tsx | 154 +++--------------- 1 file changed, 27 insertions(+), 127 deletions(-) diff --git a/src/renderer/src/pages/profile/profile-content/profile-content.tsx b/src/renderer/src/pages/profile/profile-content/profile-content.tsx index 8de16d3d..41b11ba3 100644 --- a/src/renderer/src/pages/profile/profile-content/profile-content.tsx +++ b/src/renderer/src/pages/profile/profile-content/profile-content.tsx @@ -5,7 +5,6 @@ import { useAppDispatch, useFormat } from "@renderer/hooks"; import { setHeaderTitle } from "@renderer/features"; import { TelescopeIcon, ChevronRightIcon } from "@primer/octicons-react"; import { useTranslation } from "react-i18next"; -import { UserGame } from "@types"; import { LockedProfile } from "./locked-profile"; import { ReportProfile } from "../report-profile/report-profile"; import { FriendsBox } from "./friends-box"; @@ -17,8 +16,6 @@ import { useSectionCollapse } from "@renderer/hooks/use-section-collapse"; import { motion, AnimatePresence } from "framer-motion"; import { sectionVariants, - gameCardVariants, - gameGridVariants, chevronVariants, GAME_STATS_ANIMATION_DURATION_IN_MS, } from "./profile-animations"; @@ -38,8 +35,6 @@ export function ProfileContent() { const [statsIndex, setStatsIndex] = useState(0); const [isAnimationRunning, setIsAnimationRunning] = useState(true); const [sortBy, setSortBy] = useState("playedRecently"); - const [prevLibraryGames, setPrevLibraryGames] = useState([]); - const [prevPinnedGames, setPrevPinnedGames] = useState([]); const statsAnimation = useRef(-1); const { toggleSection, isPinnedCollapsed } = useSectionCollapse(); @@ -92,27 +87,6 @@ export function ProfileContent() { const { numberFormatter } = useFormat(); - const gamesHaveChanged = ( - current: UserGame[], - previous: UserGame[] - ): boolean => { - if (current.length !== previous.length) return true; - return current.some( - (game, index) => game.objectId !== previous[index]?.objectId - ); - }; - - const shouldAnimateLibrary = gamesHaveChanged(libraryGames, prevLibraryGames); - const shouldAnimatePinned = gamesHaveChanged(pinnedGames, prevPinnedGames); - - useEffect(() => { - setPrevLibraryGames(libraryGames); - }, [libraryGames]); - - useEffect(() => { - setPrevPinnedGames(pinnedGames); - }, [pinnedGames]); - const usersAreFriends = useMemo(() => { return userProfile?.relation?.status === "ACCEPTED"; }, [userProfile]); @@ -192,57 +166,21 @@ export function ProfileContent() { exit="collapsed" layout > - - {shouldAnimatePinned ? ( - - {pinnedGames?.map((game, index) => ( - - - - ))} - - ) : ( - pinnedGames?.map((game) => ( -
  • - -
  • - )) - )} -
    +
      + {pinnedGames?.map((game) => ( +
    • + +
    • + ))} +
    )} @@ -262,54 +200,18 @@ export function ProfileContent() {
    - - {shouldAnimateLibrary ? ( - - {libraryGames?.map((game, index) => ( - - - - ))} - - ) : ( - libraryGames?.map((game) => ( -
  • - -
  • - )) - )} -
    +
      + {libraryGames?.map((game) => ( +
    • + +
    • + ))} +
    )} @@ -338,8 +240,6 @@ export function ProfileContent() { pinnedGames, isPinnedCollapsed, toggleSection, - shouldAnimateLibrary, - shouldAnimatePinned, sortBy, ]); From 26dfb6db8edd4eb07b604fda0df6ec7b03f24472 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Tue, 30 Sep 2025 01:07:45 +0100 Subject: [PATCH 19/23] feat: new style for sidebar on game page --- src/locales/ar/translation.json | 1 - src/locales/be/translation.json | 7 +-- src/locales/bg/translation.json | 1 - src/locales/ca/translation.json | 7 +-- src/locales/cs/translation.json | 1 - src/locales/da/translation.json | 7 +-- src/locales/de/translation.json | 5 +-- src/locales/et/translation.json | 6 +-- src/locales/fa/translation.json | 7 +-- src/locales/fr/translation.json | 1 - src/locales/hu/translation.json | 7 +-- src/locales/id/translation.json | 7 +-- src/locales/it/translation.json | 7 +-- src/locales/kk/translation.json | 7 +-- src/locales/ko/translation.json | 7 +-- src/locales/nb/translation.json | 7 +-- src/locales/nl/translation.json | 7 +-- src/locales/pl/translation.json | 7 +-- src/locales/pt-BR/translation.json | 43 +++++++++++++++++-- src/locales/pt-PT/translation.json | 10 +---- src/locales/ro/translation.json | 7 +-- src/locales/ru/translation.json | 4 +- src/locales/sv/translation.json | 1 - src/locales/tr/translation.json | 1 - src/locales/uk/translation.json | 1 - src/locales/uz/translation.json | 1 - src/locales/zh/translation.json | 3 -- .../description-header.scss | 6 +-- .../gallery-slider/gallery-slider.scss | 41 ++++++++++++++++++ .../game-details/game-details-content.tsx | 23 ++++++++++ .../src/pages/game-details/game-details.scss | 30 ++++++++++--- .../sidebar-section/sidebar-section.scss | 6 +++ .../pages/game-details/sidebar/sidebar.scss | 7 ++- src/renderer/src/pages/home/home.scss | 9 ++++ src/renderer/src/pages/home/home.tsx | 2 +- 35 files changed, 169 insertions(+), 125 deletions(-) diff --git a/src/locales/ar/translation.json b/src/locales/ar/translation.json index 782d6b51..034d0cbd 100644 --- a/src/locales/ar/translation.json +++ b/src/locales/ar/translation.json @@ -4,7 +4,6 @@ "successfully_signed_in": "تم تسجيل الدخول بنجاح" }, "home": { - "featured": "مميز", "surprise_me": "مفاجئني", "no_results": "لم يتم العثور على نتائج", "start_typing": "ابدأ بالكتابة للبحث...", diff --git a/src/locales/be/translation.json b/src/locales/be/translation.json index c9d49626..8d67e693 100644 --- a/src/locales/be/translation.json +++ b/src/locales/be/translation.json @@ -1,7 +1,6 @@ { "language_name": "беларуская мова", "home": { - "featured": "Рэкамэндаванае", "surprise_me": "Здзіві мяне", "no_results": "Няма вынікаў" }, @@ -17,7 +16,6 @@ "home": "Галоўная", "favorites": "Улюбленыя" }, - "header": { "search": "Пошук", "home": "Галоўная", @@ -31,10 +29,7 @@ "downloading_metadata": "Сцягванне мэтаданых {{title}}…", "downloading": "Сцягванне {{title}}… ({{percentage}} скончана) - Канчатак {{eta}} - {{speed}}" }, - "catalogue": { - "next_page": "Наступная старонка", - "previous_page": "Папярэдняя старонка" - }, + "catalogue": {}, "game_details": { "open_download_options": "Адкрыць варыянты сцягвання", "download_options_zero": "Няма варыянтаў сцягвання", diff --git a/src/locales/bg/translation.json b/src/locales/bg/translation.json index 458b9e36..3e289700 100644 --- a/src/locales/bg/translation.json +++ b/src/locales/bg/translation.json @@ -4,7 +4,6 @@ "successfully_signed_in": "Успешно влизане" }, "home": { - "featured": "Препоръчани", "surprise_me": "Изненадай ме", "no_results": "Няма намерени резултати", "start_typing": "Започнете да пишете за търсене...", diff --git a/src/locales/ca/translation.json b/src/locales/ca/translation.json index aa69001f..96eb67e2 100644 --- a/src/locales/ca/translation.json +++ b/src/locales/ca/translation.json @@ -4,7 +4,6 @@ "successfully_signed_in": "Has entrat correctament" }, "home": { - "featured": "Destacats", "surprise_me": "Sorprèn-me", "no_results": "No s'ha trobat res" }, @@ -25,7 +24,6 @@ }, "header": { "search": "Cerca jocs", - "home": "Inici", "catalogue": "Catàleg", "downloads": "Baixades", @@ -41,10 +39,7 @@ "calculating_eta": "Descarregant {{title}}… ({{percentage}} completat) - Calculant el temps restant…", "checking_files": "Comprovant els fitxers de {{title}}… ({{percentage}} completat)" }, - "catalogue": { - "next_page": "Pàgina següent", - "previous_page": "Pàgina anterior" - }, + "catalogue": {}, "game_details": { "open_download_options": "Obre les opcions de baixada", "download_options_zero": "No hi ha opcions de baixada", diff --git a/src/locales/cs/translation.json b/src/locales/cs/translation.json index 9b501b54..6bcc8944 100644 --- a/src/locales/cs/translation.json +++ b/src/locales/cs/translation.json @@ -4,7 +4,6 @@ "successfully_signed_in": "Úspěšně přihlášen" }, "home": { - "featured": "Doporučené", "surprise_me": "Překvap mě", "no_results": "Výsledek nenalezen", "start_typing": "Začni psát pro vyhledávání...", diff --git a/src/locales/da/translation.json b/src/locales/da/translation.json index 618f085c..21a92f72 100644 --- a/src/locales/da/translation.json +++ b/src/locales/da/translation.json @@ -4,7 +4,6 @@ "successfully_signed_in": "Loggede ind successfuldt" }, "home": { - "featured": "Anbefalet", "surprise_me": "Overrask mig", "no_results": "Ingen resultater fundet", "start_typing": "Begynd at skrive for at søge...", @@ -29,7 +28,6 @@ }, "header": { "search": "Søg efter spil", - "home": "Hjem", "catalogue": "Katalog", "downloads": "Downloads", @@ -45,10 +43,7 @@ "calculating_eta": "Downloader {{title}}… ({{percentage}} færdig) - Udregner resterende tid…", "checking_files": "Checker {{title}} filer… ({{percentage}} færdig)" }, - "catalogue": { - "next_page": "Næste side", - "previous_page": "Forrige side" - }, + "catalogue": {}, "game_details": { "open_download_options": "Åben download muligheder", "download_options_zero": "Ingen download mulighed", diff --git a/src/locales/de/translation.json b/src/locales/de/translation.json index 5101f459..fb285ee0 100644 --- a/src/locales/de/translation.json +++ b/src/locales/de/translation.json @@ -4,7 +4,6 @@ "successfully_signed_in": "Erfolgreich angemeldet" }, "home": { - "featured": "Empfohlen", "surprise_me": "Überrasche mich", "no_results": "Keine Ergebnisse gefunden", "start_typing": "Tippe, um zu suchen...", @@ -59,9 +58,7 @@ "download_sources": "Download-Quellen", "result_count": "{{resultCount}} Ergebnisse", "filter_count": "{{filterCount}} verfügbar", - "clear_filters": "{{filterCount}} ausgewählte löschen", - "next_page": "Nächste Seite", - "previous_page": "Vorherige Seite" + "clear_filters": "{{filterCount}} ausgewählte löschen" }, "game_details": { "open_download_options": "Download-Optionen öffnen", diff --git a/src/locales/et/translation.json b/src/locales/et/translation.json index 119e1aab..c5566eeb 100644 --- a/src/locales/et/translation.json +++ b/src/locales/et/translation.json @@ -4,7 +4,6 @@ "successfully_signed_in": "Edukalt sisse logitud" }, "home": { - "featured": "Esile toodud", "surprise_me": "Üllata mind", "no_results": "Tulemusi ei leitud", "start_typing": "Alusta otsimiseks kirjutamist...", @@ -45,10 +44,7 @@ "calculating_eta": "{{title}} allalaadimine… ({{percentage}} valmis) - Järelejäänud aja arvutamine…", "checking_files": "{{title}} failide kontrollimine… ({{percentage}} valmis)" }, - "catalogue": { - "next_page": "Järgmine leht", - "previous_page": "Eelmine leht" - }, + "catalogue": {}, "game_details": { "open_download_options": "Ava allalaadimise valikud", "download_options_zero": "Allalaadimise valikuid pole", diff --git a/src/locales/fa/translation.json b/src/locales/fa/translation.json index be18263a..69a49b79 100644 --- a/src/locales/fa/translation.json +++ b/src/locales/fa/translation.json @@ -1,7 +1,6 @@ { "language_name": "فارسی", "home": { - "featured": "پیشنهادی", "surprise_me": "سوپرایزم کن", "no_results": "اتمام‌ای پیدا نشد" }, @@ -17,7 +16,6 @@ "home": "خانه", "favorites": "علاقه‌مندی‌ها" }, - "header": { "search": "جستجوی بازی‌ها", "home": "خانه", @@ -31,10 +29,7 @@ "downloading_metadata": "درحال دانلود متادیتاهای {{title}}…", "downloading": "در حال دانلود {{title}}… ({{percentage}} تکمیل شده) - اتمام {{eta}} - {{speed}}" }, - "catalogue": { - "next_page": "صفحه‌ی بعدی", - "previous_page": "صفحه‌ی قبلی" - }, + "catalogue": {}, "game_details": { "open_download_options": "بازکردن آپشن‌های دانلود", "download_options_zero": "هیچ آپشن دانلودی وجود ندارد", diff --git a/src/locales/fr/translation.json b/src/locales/fr/translation.json index 1c129a64..8fc07722 100644 --- a/src/locales/fr/translation.json +++ b/src/locales/fr/translation.json @@ -4,7 +4,6 @@ "successfully_signed_in": "Connecté avec succès" }, "home": { - "featured": "En vedette", "surprise_me": "Surprenez-moi", "no_results": "Aucun résultat trouvé", "start_typing": "Commencez à taper pour rechercher...", diff --git a/src/locales/hu/translation.json b/src/locales/hu/translation.json index 0cea87b0..efed5e2d 100644 --- a/src/locales/hu/translation.json +++ b/src/locales/hu/translation.json @@ -1,7 +1,6 @@ { "language_name": "Magyar", "home": { - "featured": "Featured", "surprise_me": "Lepj meg", "no_results": "Nem található" }, @@ -19,7 +18,6 @@ }, "header": { "search": "Keresés", - "home": "Főoldal", "catalogue": "Katalógus", "downloads": "Letöltések", @@ -31,10 +29,7 @@ "downloading_metadata": "{{title}} metaadatainak letöltése…", "downloading": "{{title}} letöltése… ({{percentage}} kész) - Befejezés {{eta}} - {{speed}}" }, - "catalogue": { - "next_page": "Következő olda", - "previous_page": "Előző olda" - }, + "catalogue": {}, "game_details": { "open_download_options": "Letöltési lehetőségek", "download_options_zero": "Nincs letöltési lehetőség", diff --git a/src/locales/id/translation.json b/src/locales/id/translation.json index 4fa347fc..fc72fc51 100644 --- a/src/locales/id/translation.json +++ b/src/locales/id/translation.json @@ -4,7 +4,6 @@ "successfully_signed_in": "Berhasil masuk" }, "home": { - "featured": "Unggulan", "surprise_me": "Kejutkan saya", "no_results": "Tidak ada hasil ditemukan" }, @@ -25,7 +24,6 @@ }, "header": { "search": "Cari game", - "home": "Beranda", "catalogue": "Katalog", "downloads": "Unduhan", @@ -41,10 +39,7 @@ "calculating_eta": "Mengunduh {{title}}… ({{percentage}} selesai) - Menghitung waktu yang tersisa…", "checking_files": "Memeriksa file {{title}}… ({{percentage}} selesai)" }, - "catalogue": { - "next_page": "Halaman Berikutnya", - "previous_page": "Halaman Sebelumnya" - }, + "catalogue": {}, "game_details": { "open_download_options": "Buka opsi unduhan", "download_options_zero": "Tidak ada opsi unduhan", diff --git a/src/locales/it/translation.json b/src/locales/it/translation.json index b23d1244..ac37ffe9 100644 --- a/src/locales/it/translation.json +++ b/src/locales/it/translation.json @@ -1,7 +1,6 @@ { "language_name": "Italiano", "home": { - "featured": "In primo piano", "surprise_me": "Sorprendimi", "no_results": "Nessun risultato trovato" }, @@ -20,7 +19,6 @@ }, "header": { "search": "Cerca", - "home": "Home", "catalogue": "Catalogo", "downloads": "Download", @@ -32,10 +30,7 @@ "downloading_metadata": "Scaricamento metadati di {{title}}…", "downloading": "Download di {{title}}… ({{percentage}} completato) - Conclusione {{eta}} - {{speed}}" }, - "catalogue": { - "next_page": "Pagina successiva", - "previous_page": "Pagina precedente" - }, + "catalogue": {}, "game_details": { "open_download_options": "Apri opzioni di download", "download_options_zero": "Nessuna opzione di download", diff --git a/src/locales/kk/translation.json b/src/locales/kk/translation.json index bfb009a7..48fb8181 100644 --- a/src/locales/kk/translation.json +++ b/src/locales/kk/translation.json @@ -4,7 +4,6 @@ "successfully_signed_in": "Сәтті кіру" }, "home": { - "featured": "Ұсынылған", "surprise_me": "Таңқалдыр", "no_results": "Ештеңе табылмады" }, @@ -23,7 +22,6 @@ "sign_in": "Кіру", "favorites": "Таңдаулылар" }, - "header": { "search": "Іздеу", "home": "Басты бет", @@ -40,10 +38,7 @@ "downloading": "Жүктеу {{title}}… ({{percentage}} аяқталды) - Аяқтау {{eta}} - {{speed}}", "calculating_eta": "Жүктеу {{title}}… ({{percentage}} аяқталды) - Қалған уақытты есептеу…" }, - "catalogue": { - "next_page": "Келесі бет", - "previous_page": "Алдыңғы бет" - }, + "catalogue": {}, "game_details": { "open_download_options": "Жүктеу нұсқаларын ашу", "download_options_zero": "Жүктеу нұсқалары жоқ", diff --git a/src/locales/ko/translation.json b/src/locales/ko/translation.json index 9ec389b1..a9b9c0e5 100644 --- a/src/locales/ko/translation.json +++ b/src/locales/ko/translation.json @@ -1,7 +1,6 @@ { "language_name": "한국어", "home": { - "featured": "추천", "surprise_me": "무작위 추천", "no_results": "결과 없음" }, @@ -17,7 +16,6 @@ "home": "홈", "favorites": "즐겨찾기" }, - "header": { "search": "게임 검색하기", "home": "홈", @@ -31,10 +29,7 @@ "downloading_metadata": "{{title}}의 메타데이터를 다운로드 중…", "downloading": "{{title}}의 파일들을 다운로드 중… ({{percentage}} 완료) - 완료까지 {{eta}} - {{speed}}" }, - "catalogue": { - "next_page": "다음 페이지", - "previous_page": "이전 페이지" - }, + "catalogue": {}, "game_details": { "open_download_options": "다운로드 선택지 열기", "download_options_zero": "다운로드 선택지 없음", diff --git a/src/locales/nb/translation.json b/src/locales/nb/translation.json index 8898ec7b..95bda8fe 100644 --- a/src/locales/nb/translation.json +++ b/src/locales/nb/translation.json @@ -4,7 +4,6 @@ "successfully_signed_in": "Logget inn vellykket" }, "home": { - "featured": "Anbefalinger", "surprise_me": "Overrask meg", "no_results": "Ingen resultater fundet", "start_typing": "Begynn å skrive for å søke...", @@ -29,7 +28,6 @@ }, "header": { "search": "Søk efter spill", - "home": "Hjem", "catalogue": "Katalog", "downloads": "Nedlastinger", @@ -45,10 +43,7 @@ "calculating_eta": "Laster ned {{title}}… ({{percentage}} ferdig) - Regner ut resterende tid…", "checking_files": "Sjekker {{title}} filer… ({{percentage}} ferdig)" }, - "catalogue": { - "next_page": "Neste side", - "previous_page": "Forrige side" - }, + "catalogue": {}, "game_details": { "open_download_options": "Åpne nedlastingsmuligheter", "download_options_zero": "Ingen nedlastingsmulighet", diff --git a/src/locales/nl/translation.json b/src/locales/nl/translation.json index 72d20c74..baa6df6e 100644 --- a/src/locales/nl/translation.json +++ b/src/locales/nl/translation.json @@ -1,7 +1,6 @@ { "language_name": "Nederlands", "home": { - "featured": "Uitgelicht", "surprise_me": "Verrasing", "no_results": "Geen resultaten gevonden" }, @@ -19,7 +18,6 @@ }, "header": { "search": "Zoek spellen", - "home": "Home", "catalogue": "Bibliotheek", "downloads": "Downloads", @@ -31,10 +29,7 @@ "downloading_metadata": "Downloading {{title}} metadata…", "downloading": "Downloading {{title}}… ({{percentage}} complete) - Conclusion {{eta}} - {{speed}}" }, - "catalogue": { - "next_page": "Volgende Pagina", - "previous_page": "Vorige Pagina" - }, + "catalogue": {}, "game_details": { "open_download_options": "Open download Instellingen", "download_options_zero": "Geen download Instellingen", diff --git a/src/locales/pl/translation.json b/src/locales/pl/translation.json index 86751b0e..2e0d1696 100644 --- a/src/locales/pl/translation.json +++ b/src/locales/pl/translation.json @@ -1,7 +1,6 @@ { "language_name": "Polski", "home": { - "featured": "Wyróżnione", "surprise_me": "Zaskocz mnie", "no_results": "Nie znaleziono wyników" }, @@ -20,7 +19,6 @@ }, "header": { "search": "Szukaj", - "home": "Główna", "catalogue": "Katalog", "downloads": "Pobrane", @@ -32,10 +30,7 @@ "downloading_metadata": "Pobieranie {{title}} metadata…", "downloading": "Pobieranie {{title}}… (ukończone w {{percentage}}) - Podsumowanie {{eta}} - {{speed}}" }, - "catalogue": { - "next_page": "Następna strona", - "previous_page": "Poprzednia strona" - }, + "catalogue": {}, "game_details": { "open_download_options": "Otwórz opcje pobierania", "download_options_zero": "Brak opcji pobierania", diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index 7f7f8cc1..b88c38a5 100755 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -26,7 +26,22 @@ "sign_in": "Login", "friends": "Amigos", "need_help": "Precisa de ajuda?", - "favorites": "Favoritos" + "favorites": "Favoritos", + "add_custom_game_tooltip": "Adicionar jogo personalizado", + "custom_game_modal": "Adicionar jogo personalizado", + "edit_game_modal_title": "Título", + "playable_button_title": "", + "custom_game_modal_add": "Adicionar Jogo", + "custom_game_modal_adding": "Adicionando...", + "custom_game_modal_browse": "Buscar", + "custom_game_modal_cancel": "Cancelar", + "edit_game_modal_assets": "Imagens", + "edit_game_modal_icon": "Ícone", + "edit_game_modal_browse": "Buscar", + "edit_game_modal_cancel": "Cancelar", + "edit_game_modal_enter_title": "Insira o título", + "edit_game_modal_logo": "Logo", + "edit_game_modal": "Personalizar detalhes" }, "header": { "search": "Buscar jogos", @@ -219,7 +234,18 @@ "historical_keyshop": "Preço histórico em keyshops", "language": "Idioma", "caption": "Legenda", - "audio": "Áudio" + "audio": "Áudio", + "edit_game_modal_button": "Alterar detalhes do jogo", + "game_added_to_pinned": "Jogo adicionado aos fixados", + "game_removed_from_pinned": "Jogo removido dos fixados", + "manual_playtime_tooltip": "Este tempo de jogo foi atualizado manualmente", + "manual_playtime_warning": "As suas horas de jogo serão marcadas como atualizadas manualmente. Esta ação não pode ser desfeita.", + "missing_wine_prefix": "Um prefixo Wine é necessário para criar um backup no Linux", + "update_game_playtime": "Modificar tempo de jogo", + "update_playtime": "Modificar tempo de jogo", + "update_playtime_description": "Atualizar manualmente o tempo de jogo de {{game}}", + "update_playtime_error": "Falha ao atualizar tempo de jogo", + "update_playtime_title": "Atualizar tempo de jogo" }, "activation": { "title": "Ativação", @@ -394,7 +420,8 @@ "hidden": "Oculta", "test_notification": "Testar notificação", "notification_preview": "Prévia da Notificação de Conquistas", - "enable_friend_start_game_notifications": "Quando um amigo iniciar um jogo" + "enable_friend_start_game_notifications": "Quando um amigo iniciar um jogo", + "editor_tab_code": "Código" }, "notifications": { "download_complete": "Download concluído", @@ -523,7 +550,15 @@ "show_achievements_on_profile": "Exiba suas conquistas no perfil", "show_points_on_profile": "Exiba seus pontos ganhos no perfil", "error_adding_friend": "Não foi possível enviar o pedido de amizade. Verifique o código de amizade inserido", - "friend_code_length_error": "Código de amigo deve ter 8 caracteres" + "friend_code_length_error": "Código de amigo deve ter 8 caracteres", + "top_percentile": "Top {{percentile}}%", + "playtime": "Tempo de jogo", + "played_recently": "Jogado recentemente", + "pinned": "Fixado", + "amount_minutes_short": "{{amount}}h", + "amount_hours_short": "{{amount}}h", + "game_added_to_pinned": "Jogo adicionado aos fixados", + "achievements_earned": "Conquistas recebidas" }, "achievement": { "achievement_unlocked": "Conquista desbloqueada", diff --git a/src/locales/pt-PT/translation.json b/src/locales/pt-PT/translation.json index 6c32b35b..654e94ec 100644 --- a/src/locales/pt-PT/translation.json +++ b/src/locales/pt-PT/translation.json @@ -4,7 +4,6 @@ "successfully_signed_in": "Sessão iniciada com sucesso" }, "home": { - "featured": "Destaques", "hot": "Populares", "weekly": "📅 Mais descarregados esta semana", "achievements": "🏆 Para completar", @@ -26,7 +25,8 @@ "game_has_no_executable": "O jogo não tem um executável selecionado", "sign_in": "Iniciar sessão", "friends": "Amigos", - "favorites": "Favoritos" + "favorites": "Favoritos", + "edit_game_modal_cancel": "Cancelar" }, "header": { "search": "Procurar jogos", @@ -247,9 +247,6 @@ "download_count_zero": "Sem downloads na lista", "download_count_one": "{{countFormatted}} download na lista", "download_count_other": "{{countFormatted}} downloads na lista", - "download_options_zero": "Sem downloads disponíveis", - "download_options_one": "{{countFormatted}} download disponível", - "download_options_other": "{{countFormatted}} downloads disponíveis", "download_source_url": "URL da fonte", "add_download_source_description": "Insere o URL que contém o ficheiro .json", "download_source_up_to_date": "Sincronizada", @@ -359,8 +356,6 @@ "instructions": "Verifica a forma correta de instalar algum deles na tua distribuição Linux, para garantir a execução normal do jogo" }, "catalogue": { - "next_page": "Página seguinte", - "previous_page": "Página anterior", "search": "Filtrar…", "developers": "Desenvolvedores", "genres": "Géneros", @@ -427,7 +422,6 @@ "friend_code_copied": "Código de amigo copiado", "undo_friendship_modal_text": "Isto vai remover a tua amizade com {{displayName}}", "privacy_hint": "Para controlar quem pode ver o teu perfil, acede às <0>Definições", - "profile_locked": "Este perfil é privado", "image_process_failure": "Falha ao processar a imagem", "required_field": "Este campo é obrigatório", "displayname_min_length": "O nome de apresentação deve ter pelo menos 3 caracteres", diff --git a/src/locales/ro/translation.json b/src/locales/ro/translation.json index c5a81881..8ed6fd39 100644 --- a/src/locales/ro/translation.json +++ b/src/locales/ro/translation.json @@ -1,7 +1,6 @@ { "language_name": "Română", "home": { - "featured": "Recomandate", "surprise_me": "Surprinde-mă", "no_results": "Niciun rezultat găsit" }, @@ -19,7 +18,6 @@ }, "header": { "search": "Caută jocuri", - "home": "Acasă", "catalogue": "Catalog", "downloads": "Descărcări", @@ -32,10 +30,7 @@ "downloading": "Se descarcă {{title}}... ({{percentage}} complet) - Concluzie {{eta}} - {{speed}}", "calculating_eta": "Se descarcă {{title}}... ({{percentage}} complet) - Calculare timp rămas..." }, - "catalogue": { - "next_page": "Pagina următoare", - "previous_page": "Pagina anterioară" - }, + "catalogue": {}, "game_details": { "open_download_options": "Deschide opțiunile de descărcare", "download_options_zero": "Nicio opțiune de descărcare", diff --git a/src/locales/ru/translation.json b/src/locales/ru/translation.json index c92d7902..03413554 100644 --- a/src/locales/ru/translation.json +++ b/src/locales/ru/translation.json @@ -4,14 +4,12 @@ "successfully_signed_in": "Успешный вход" }, "home": { - "featured": "Рекомендации", "surprise_me": "Удиви меня", "no_results": "Ничего не найдено", "hot": "Сейчас популярно", "start_typing": "Начинаю вводить текст...", "weekly": "📅 Лучшие игры недели", - "achievements": "🏆 Игры с достижениями", - "already_in_library": "Уже в библиотеке" + "achievements": "🏆 Игры с достижениями" }, "sidebar": { "catalogue": "Каталог", diff --git a/src/locales/sv/translation.json b/src/locales/sv/translation.json index 0972effa..901e4ca7 100644 --- a/src/locales/sv/translation.json +++ b/src/locales/sv/translation.json @@ -4,7 +4,6 @@ "successfully_signed_in": "Inloggningen lyckades" }, "home": { - "featured": "Utvalt", "surprise_me": "Överraska mig", "no_results": "Inga resultat hittades", "start_typing": "Börja skriva för att söka...", diff --git a/src/locales/tr/translation.json b/src/locales/tr/translation.json index c3fe2081..e8e1cb2b 100644 --- a/src/locales/tr/translation.json +++ b/src/locales/tr/translation.json @@ -4,7 +4,6 @@ "successfully_signed_in": "Başarıyla giriş yapıldı" }, "home": { - "featured": "Öne Çıkanlar", "surprise_me": "Beni Şaşırt", "no_results": "Sonuç bulunamadı", "start_typing": "Aramak için yazmaya başlayın...", diff --git a/src/locales/uk/translation.json b/src/locales/uk/translation.json index 48a3972d..26aa8aae 100644 --- a/src/locales/uk/translation.json +++ b/src/locales/uk/translation.json @@ -4,7 +4,6 @@ "successfully_signed_in": "Успішний вхід в систему" }, "home": { - "featured": "Рекомендоване", "surprise_me": "Здивуй мене", "no_results": "Результатів не знайдено", "start_typing": "Почніть набирати текст для пошуку...", diff --git a/src/locales/uz/translation.json b/src/locales/uz/translation.json index d20a9677..24e508af 100644 --- a/src/locales/uz/translation.json +++ b/src/locales/uz/translation.json @@ -4,7 +4,6 @@ "successfully_signed_in": "Kirish muvaffaqiyatli amalga oshirildi" }, "home": { - "featured": "Tavsiya etilgan", "surprise_me": "Hayratda qoldir", "no_results": "Natijalar topilmadi", "hot": "Eng mashhur", diff --git a/src/locales/zh/translation.json b/src/locales/zh/translation.json index 0323d991..7cdd0c92 100644 --- a/src/locales/zh/translation.json +++ b/src/locales/zh/translation.json @@ -4,7 +4,6 @@ "successfully_signed_in": "已成功登录" }, "home": { - "featured": "特色推荐", "surprise_me": "向我推荐", "no_results": "没有找到结果", "start_typing": "键入以开始搜素...", @@ -51,8 +50,6 @@ "installing_common_redist": "{{log}}…" }, "catalogue": { - "next_page": "下一页", - "previous_page": "上一页", "clear_filters": "清除已选的 {{filterCount}} 项", "developers": "开发商", "download_sources": "下载源", diff --git a/src/renderer/src/pages/game-details/description-header/description-header.scss b/src/renderer/src/pages/game-details/description-header/description-header.scss index 920e8068..8ca78eaa 100644 --- a/src/renderer/src/pages/game-details/description-header/description-header.scss +++ b/src/renderer/src/pages/game-details/description-header/description-header.scss @@ -2,7 +2,7 @@ .description-header { width: calc(100% - calc(globals.$spacing-unit * 2)); - margin: calc(globals.$spacing-unit * 1) auto; + margin: calc(globals.$spacing-unit * 1) calc(globals.$spacing-unit * 1); padding: calc(globals.$spacing-unit * 1.5); display: flex; justify-content: space-between; @@ -10,8 +10,8 @@ background-color: globals.$background-color; height: 72px; border-radius: 12px; - border: 1px solid rgba(255, 255, 255, 0.03); - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); + border: 1px solid rgba(255, 255, 255, 0.05); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); &__info { display: flex; diff --git a/src/renderer/src/pages/game-details/gallery-slider/gallery-slider.scss b/src/renderer/src/pages/game-details/gallery-slider/gallery-slider.scss index f66da32b..9483b50e 100644 --- a/src/renderer/src/pages/game-details/gallery-slider/gallery-slider.scss +++ b/src/renderer/src/pages/game-details/gallery-slider/gallery-slider.scss @@ -7,6 +7,15 @@ display: flex; flex-direction: column; align-items: center; + max-height: 80vh; + + @media (min-width: 1024px) { + max-height: 70vh; + } + + @media (min-width: 1280px) { + max-height: 60vh; + } } &__viewport { @@ -16,8 +25,19 @@ overflow: hidden; border-radius: 8px; + @media (min-width: 1024px) { + width: 80%; + max-height: 400px; + } + @media (min-width: 1280px) { width: 60%; + max-height: 500px; + } + + @media (min-width: 1536px) { + width: 50%; + max-height: 600px; } } @@ -52,10 +72,18 @@ overflow-y: hidden; gap: calc(globals.$spacing-unit / 2); + @media (min-width: 1024px) { + width: 80%; + } + @media (min-width: 1280px) { width: 60%; } + @media (min-width: 1536px) { + width: 50%; + } + &::-webkit-scrollbar-thumb { width: 20%; } @@ -79,6 +107,19 @@ border: solid 1px globals.$border-color; overflow: hidden; position: relative; + aspect-ratio: 16/9; + + @media (min-width: 1024px) { + width: 15%; + } + + @media (min-width: 1280px) { + width: 12%; + } + + @media (min-width: 1536px) { + width: 10%; + } &:hover { opacity: 0.8; 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 347e5a1c..4e9ecf14 100644 --- a/src/renderer/src/pages/game-details/game-details-content.tsx +++ b/src/renderer/src/pages/game-details/game-details-content.tsx @@ -43,6 +43,29 @@ export function GameDetailsContent() { const $images = Array.from(document.querySelectorAll("img")); $images.forEach(($image) => { $image.loading = "lazy"; + // Remove any inline width/height styles that might cause overflow + $image.removeAttribute("width"); + $image.removeAttribute("height"); + $image.removeAttribute("style"); + // Set max-width to prevent overflow + $image.style.maxWidth = "100%"; + $image.style.width = "auto"; + $image.style.height = "auto"; + $image.style.boxSizing = "border-box"; + }); + + // Handle videos the same way + const $videos = Array.from(document.querySelectorAll("video")); + $videos.forEach(($video) => { + // Remove any inline width/height styles that might cause overflow + $video.removeAttribute("width"); + $video.removeAttribute("height"); + $video.removeAttribute("style"); + // Set max-width to prevent overflow + $video.style.maxWidth = "100%"; + $video.style.width = "auto"; + $video.style.height = "auto"; + $video.style.boxSizing = "border-box"; }); return document.body.outerHTML; diff --git a/src/renderer/src/pages/game-details/game-details.scss b/src/renderer/src/pages/game-details/game-details.scss index 786a8d30..6b02dde5 100644 --- a/src/renderer/src/pages/game-details/game-details.scss +++ b/src/renderer/src/pages/game-details/game-details.scss @@ -186,9 +186,10 @@ $hero-height: 300px; &__description-content { width: 100%; - height: 100%; + min-height: 100%; min-width: 0; flex: 1; + overflow-x: hidden; } &__description { @@ -199,6 +200,8 @@ $hero-height: 300px; width: 100%; margin-left: auto; margin-right: auto; + overflow-x: auto; + min-height: auto; @media (min-width: 768px) { padding: calc(globals.$spacing-unit * 2.5) calc(globals.$spacing-unit * 2); @@ -206,20 +209,30 @@ $hero-height: 300px; @media (min-width: 1024px) { padding: calc(globals.$spacing-unit * 3) calc(globals.$spacing-unit * 2); + width: 80%; } @media (min-width: 1280px) { width: 60%; } - img { + @media (min-width: 1536px) { + width: 50%; + } + + img, + video { border-radius: 5px; margin-top: globals.$spacing-unit; margin-bottom: calc(globals.$spacing-unit * 3); - display: block; - width: 100%; - height: auto; - object-fit: cover; + display: block !important; + max-width: 100% !important; + width: auto !important; + height: auto !important; + object-fit: contain !important; + box-sizing: border-box !important; + word-wrap: break-word; + overflow-wrap: break-word; } a { @@ -247,12 +260,17 @@ $hero-height: 300px; @media (min-width: 1024px) { padding: calc(globals.$spacing-unit * 3) calc(globals.$spacing-unit * 2); + width: 80%; } @media (min-width: 1280px) { width: 60%; line-height: 22px; } + + @media (min-width: 1536px) { + width: 50%; + } } &__randomizer-button { diff --git a/src/renderer/src/pages/game-details/sidebar-section/sidebar-section.scss b/src/renderer/src/pages/game-details/sidebar-section/sidebar-section.scss index 8674b044..f86db399 100644 --- a/src/renderer/src/pages/game-details/sidebar-section/sidebar-section.scss +++ b/src/renderer/src/pages/game-details/sidebar-section/sidebar-section.scss @@ -1,6 +1,12 @@ @use "../../../scss/globals.scss"; .sidebar-section { + background-color: globals.$dark-background-color; + border-radius: 12px; + border: 1px solid rgba(255, 255, 255, 0.05); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + overflow: hidden; + &__button { padding: calc(globals.$spacing-unit * 2.5) calc(globals.$spacing-unit * 2); display: flex; diff --git a/src/renderer/src/pages/game-details/sidebar/sidebar.scss b/src/renderer/src/pages/game-details/sidebar/sidebar.scss index d1c54f84..4d212440 100755 --- a/src/renderer/src/pages/game-details/sidebar/sidebar.scss +++ b/src/renderer/src/pages/game-details/sidebar/sidebar.scss @@ -1,11 +1,14 @@ @use "../../../scss/globals.scss"; .content-sidebar { - border-left: solid 1px globals.$border-color; - background-color: globals.$dark-background-color; + background-color: transparent; height: 100%; flex-shrink: 0; width: 280px; + padding: calc(globals.$spacing-unit * 1); + display: flex; + flex-direction: column; + gap: calc(globals.$spacing-unit * 1.5); @media (min-width: 1024px) { width: 320px; diff --git a/src/renderer/src/pages/home/home.scss b/src/renderer/src/pages/home/home.scss index 497f074e..478e96a1 100644 --- a/src/renderer/src/pages/home/home.scss +++ b/src/renderer/src/pages/home/home.scss @@ -76,6 +76,15 @@ width: 24px; height: 24px; position: relative; + display: flex; + align-items: center; + justify-content: center; + } + + &__title-flame-icon { + width: 32px; + height: 32px; + object-fit: contain; } &__title { diff --git a/src/renderer/src/pages/home/home.tsx b/src/renderer/src/pages/home/home.tsx index e2f66283..0b762882 100644 --- a/src/renderer/src/pages/home/home.tsx +++ b/src/renderer/src/pages/home/home.tsx @@ -158,7 +158,7 @@ export default function Home() { Flame animation )} From 0df5486fecb7563ec0015df157c1597b4d39328b Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Tue, 30 Sep 2025 03:59:08 +0100 Subject: [PATCH 20/23] fix: fixing how long to beat broken logic --- .../game-details/sidebar/how-long-to-beat-section.tsx | 2 +- src/renderer/src/pages/game-details/sidebar/sidebar.scss | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/renderer/src/pages/game-details/sidebar/how-long-to-beat-section.tsx b/src/renderer/src/pages/game-details/sidebar/how-long-to-beat-section.tsx index 61c90389..9a29f150 100644 --- a/src/renderer/src/pages/game-details/sidebar/how-long-to-beat-section.tsx +++ b/src/renderer/src/pages/game-details/sidebar/how-long-to-beat-section.tsx @@ -25,7 +25,7 @@ export function HowLongToBeatSection({ return `${value} ${t(durationTranslation[unit])}`; }; - if (!howLongToBeatData && !isLoading) return null; + if (!howLongToBeatData || !isLoading) return null; return ( diff --git a/src/renderer/src/pages/game-details/sidebar/sidebar.scss b/src/renderer/src/pages/game-details/sidebar/sidebar.scss index 4d212440..d1c54f84 100755 --- a/src/renderer/src/pages/game-details/sidebar/sidebar.scss +++ b/src/renderer/src/pages/game-details/sidebar/sidebar.scss @@ -1,14 +1,11 @@ @use "../../../scss/globals.scss"; .content-sidebar { - background-color: transparent; + border-left: solid 1px globals.$border-color; + background-color: globals.$dark-background-color; height: 100%; flex-shrink: 0; width: 280px; - padding: calc(globals.$spacing-unit * 1); - display: flex; - flex-direction: column; - gap: calc(globals.$spacing-unit * 1.5); @media (min-width: 1024px) { width: 320px; From 4f5c345c421b7447ea4aa4a01706e485f5f641e7 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Tue, 30 Sep 2025 05:00:22 +0100 Subject: [PATCH 21/23] ci: testing webhook From 300cff2be65d710a86f4c83622e58c9573e581f1 Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Tue, 30 Sep 2025 05:37:01 +0100 Subject: [PATCH 22/23] ci: changing windows version --- .github/workflows/build.yml | 4 ++-- .github/workflows/release.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c54c431c..0f3e0a66 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,7 +10,7 @@ jobs: build: strategy: matrix: - os: [windows-latest, ubuntu-latest] + os: [windows-2022, ubuntu-latest] fail-fast: false runs-on: ${{ matrix.os }} @@ -58,7 +58,7 @@ jobs: RENDERER_VITE_TORBOX_REFERRAL_CODE: ${{ vars.RENDERER_VITE_TORBOX_REFERRAL_CODE }} - name: Build Windows - if: matrix.os == 'windows-latest' + if: matrix.os == 'windows-2022' run: yarn build:win env: MAIN_VITE_API_URL: ${{ vars.MAIN_VITE_STAGING_API_URL }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e2536c33..babfb565 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,7 +12,7 @@ jobs: build: strategy: matrix: - os: [windows-latest, ubuntu-latest] + os: [windows-2022, ubuntu-latest] runs-on: ${{ matrix.os }} @@ -59,7 +59,7 @@ jobs: RENDERER_VITE_TORBOX_REFERRAL_CODE: ${{ vars.RENDERER_VITE_TORBOX_REFERRAL_CODE }} - name: Build Windows - if: matrix.os == 'windows-latest' + if: matrix.os == 'windows-2022' run: yarn build:win env: MAIN_VITE_API_URL: ${{ vars.MAIN_VITE_API_URL }} From a625541125eeb4f927a9cdb379ca88c06c90619d Mon Sep 17 00:00:00 2001 From: Chubby Granny Chaser Date: Tue, 30 Sep 2025 06:52:48 +0100 Subject: [PATCH 23/23] fix: fixing modal issue --- src/locales/pt-BR/translation.json | 2 +- src/main/services/steam-250.ts | 2 +- .../description-header.scss | 4 +-- .../game-details/game-details-content.tsx | 34 +++++++++++-------- .../src/pages/game-details/game-details.scss | 12 ++----- .../sidebar-section/sidebar-section.scss | 5 +-- .../pages/game-details/sidebar/sidebar.scss | 5 +-- .../profile-content/profile-content.tsx | 2 ++ .../user-library-game-card.tsx | 4 ++- 9 files changed, 36 insertions(+), 34 deletions(-) diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index b88c38a5..497ca10d 100755 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -555,7 +555,7 @@ "playtime": "Tempo de jogo", "played_recently": "Jogado recentemente", "pinned": "Fixado", - "amount_minutes_short": "{{amount}}h", + "amount_minutes_short": "{{amount}}m", "amount_hours_short": "{{amount}}h", "game_added_to_pinned": "Jogo adicionado aos fixados", "achievements_earned": "Conquistas recebidas" diff --git a/src/main/services/steam-250.ts b/src/main/services/steam-250.ts index 0abc2f14..5652b0d3 100644 --- a/src/main/services/steam-250.ts +++ b/src/main/services/steam-250.ts @@ -10,7 +10,7 @@ export const requestSteam250 = async (path: string) => { const { window } = new JSDOM(response.data); const { document } = window; - return Array.from(document.querySelectorAll(".appline .title a")) + return Array.from(document.querySelectorAll("a[data-title]")) .map(($title) => { const steamGameUrl = ($title as HTMLAnchorElement).href; if (!steamGameUrl) return null; diff --git a/src/renderer/src/pages/game-details/description-header/description-header.scss b/src/renderer/src/pages/game-details/description-header/description-header.scss index 8ca78eaa..1af1480d 100644 --- a/src/renderer/src/pages/game-details/description-header/description-header.scss +++ b/src/renderer/src/pages/game-details/description-header/description-header.scss @@ -1,8 +1,7 @@ @use "../../../scss/globals.scss"; .description-header { - width: calc(100% - calc(globals.$spacing-unit * 2)); - margin: calc(globals.$spacing-unit * 1) calc(globals.$spacing-unit * 1); + width: 100%; padding: calc(globals.$spacing-unit * 1.5); display: flex; justify-content: space-between; @@ -12,6 +11,7 @@ border-radius: 12px; border: 1px solid rgba(255, 255, 255, 0.05); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + margin-bottom: calc(globals.$spacing-unit * 1); &__info { display: flex; 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 4e9ecf14..ca2ca023 100644 --- a/src/renderer/src/pages/game-details/game-details-content.tsx +++ b/src/renderer/src/pages/game-details/game-details-content.tsx @@ -191,14 +191,16 @@ export function GameDetailsContent() { {renderGameLogo()}
    - + {game && ( + + )} {game?.shop !== "custom" && (
    ); } diff --git a/src/renderer/src/pages/game-details/game-details.scss b/src/renderer/src/pages/game-details/game-details.scss index 6b02dde5..e1140d31 100644 --- a/src/renderer/src/pages/game-details/game-details.scss +++ b/src/renderer/src/pages/game-details/game-details.scss @@ -182,6 +182,8 @@ $hero-height: 300px; globals.$background-color 50%, globals.$dark-background-color 100% ); + padding: calc(globals.$spacing-unit * 1.5); + gap: calc(globals.$spacing-unit * 1.5); } &__description-content { @@ -196,22 +198,12 @@ $hero-height: 300px; user-select: text; line-height: 22px; font-size: globals.$body-font-size; - padding: calc(globals.$spacing-unit * 2) calc(globals.$spacing-unit * 1.5); width: 100%; margin-left: auto; margin-right: auto; overflow-x: auto; min-height: auto; - @media (min-width: 768px) { - padding: calc(globals.$spacing-unit * 2.5) calc(globals.$spacing-unit * 2); - } - - @media (min-width: 1024px) { - padding: calc(globals.$spacing-unit * 3) calc(globals.$spacing-unit * 2); - width: 80%; - } - @media (min-width: 1280px) { width: 60%; } diff --git a/src/renderer/src/pages/game-details/sidebar-section/sidebar-section.scss b/src/renderer/src/pages/game-details/sidebar-section/sidebar-section.scss index f86db399..69083f14 100644 --- a/src/renderer/src/pages/game-details/sidebar-section/sidebar-section.scss +++ b/src/renderer/src/pages/game-details/sidebar-section/sidebar-section.scss @@ -1,7 +1,7 @@ @use "../../../scss/globals.scss"; .sidebar-section { - background-color: globals.$dark-background-color; + background-color: globals.$background-color; border-radius: 12px; border: 1px solid rgba(255, 255, 255, 0.05); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); @@ -21,7 +21,7 @@ font-weight: bold; &:hover { - background-color: rgba(255, 255, 255, 0.05); + background-color: rgba(255, 255, 255, 0.1); } &:active { @@ -40,6 +40,7 @@ &__content { overflow: hidden; transition: max-height 0.4s cubic-bezier(0, 1, 0, 1); + background-color: globals.$dark-background-color; position: relative; } } diff --git a/src/renderer/src/pages/game-details/sidebar/sidebar.scss b/src/renderer/src/pages/game-details/sidebar/sidebar.scss index d1c54f84..06519f6c 100755 --- a/src/renderer/src/pages/game-details/sidebar/sidebar.scss +++ b/src/renderer/src/pages/game-details/sidebar/sidebar.scss @@ -1,11 +1,12 @@ @use "../../../scss/globals.scss"; .content-sidebar { - border-left: solid 1px globals.$border-color; - background-color: globals.$dark-background-color; height: 100%; flex-shrink: 0; width: 280px; + display: flex; + flex-direction: column; + gap: calc(globals.$spacing-unit * 1.5); @media (min-width: 1024px) { width: 320px; diff --git a/src/renderer/src/pages/profile/profile-content/profile-content.tsx b/src/renderer/src/pages/profile/profile-content/profile-content.tsx index 41b11ba3..56f7d20b 100644 --- a/src/renderer/src/pages/profile/profile-content/profile-content.tsx +++ b/src/renderer/src/pages/profile/profile-content/profile-content.tsx @@ -177,6 +177,7 @@ export function ProfileContent() { statIndex={statsIndex} onMouseEnter={handleOnMouseEnterGameCard} onMouseLeave={handleOnMouseLeaveGameCard} + sortBy={sortBy} /> ))} @@ -208,6 +209,7 @@ export function ProfileContent() { statIndex={statsIndex} onMouseEnter={handleOnMouseEnterGameCard} onMouseLeave={handleOnMouseLeaveGameCard} + sortBy={sortBy} /> ))} diff --git a/src/renderer/src/pages/profile/profile-content/user-library-game-card.tsx b/src/renderer/src/pages/profile/profile-content/user-library-game-card.tsx index eac0912d..a3d24958 100644 --- a/src/renderer/src/pages/profile/profile-content/user-library-game-card.tsx +++ b/src/renderer/src/pages/profile/profile-content/user-library-game-card.tsx @@ -26,6 +26,7 @@ interface UserLibraryGameCardProps { statIndex: number; onMouseEnter: () => void; onMouseLeave: () => void; + sortBy?: string; } export function UserLibraryGameCard({ @@ -33,6 +34,7 @@ export function UserLibraryGameCard({ statIndex, onMouseEnter, onMouseLeave, + sortBy, }: UserLibraryGameCardProps) { const { userProfile, isMe, getUserLibraryGames } = useContext(userProfileContext); @@ -108,7 +110,7 @@ export function UserLibraryGameCard({ !game.isPinned ); - await getUserLibraryGames(); + await getUserLibraryGames(sortBy); if (game.isPinned) { showSuccessToast(t("game_removed_from_pinned"));