Merge pull request #1795 from hydralauncher/feat/revert-title-and-cleanup-fix

Fix: Playtime font size and original path in edit game modal fix
This commit is contained in:
Chubby Granny Chaser
2025-09-30 00:22:56 +01:00
committed by GitHub
8 changed files with 206 additions and 98 deletions

View File

@@ -4,15 +4,33 @@ import type { GameShop } from "@types";
import fs from "node:fs"; import fs from "node:fs";
import { logger } from "@main/services"; 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 ( const updateCustomGame = async (
_event: Electron.IpcMainInvokeEvent, _event: Electron.IpcMainInvokeEvent,
shop: GameShop, params: UpdateCustomGameParams
objectId: string,
title: string,
iconUrl?: string,
logoImageUrl?: string,
libraryHeroImageUrl?: string
) => { ) => {
const {
shop,
objectId,
title,
iconUrl,
logoImageUrl,
libraryHeroImageUrl,
originalIconPath,
originalLogoPath,
originalHeroPath,
} = params;
const gameKey = levelKeys.game(shop, objectId); const gameKey = levelKeys.game(shop, objectId);
const existingGame = await gamesSublevel.get(gameKey); const existingGame = await gamesSublevel.get(gameKey);
@@ -40,6 +58,9 @@ const updateCustomGame = async (
iconUrl: iconUrl || null, iconUrl: iconUrl || null,
logoImageUrl: logoImageUrl || null, logoImageUrl: logoImageUrl || null,
libraryHeroImageUrl: libraryHeroImageUrl || null, libraryHeroImageUrl: libraryHeroImageUrl || null,
originalIconPath: originalIconPath || existingGame.originalIconPath || null,
originalLogoPath: originalLogoPath || existingGame.originalLogoPath || null,
originalHeroPath: originalHeroPath || existingGame.originalHeroPath || null,
}; };
await gamesSublevel.put(gameKey, updatedGame); await gamesSublevel.put(gameKey, updatedGame);

View File

@@ -32,20 +32,39 @@ const collectOldAssetPaths = (
return oldAssetPaths; return oldAssetPaths;
}; };
const updateGameData = async ( interface UpdateGameDataParams {
gameKey: string, gameKey: string;
existingGame: Game, existingGame: Game;
title: string, title: string;
customIconUrl?: string | null, customIconUrl?: string | null;
customLogoImageUrl?: string | null, customLogoImageUrl?: string | null;
customHeroImageUrl?: string | null customHeroImageUrl?: string | null;
): Promise<Game> => { customOriginalIconPath?: string | null;
customOriginalLogoPath?: string | null;
customOriginalHeroPath?: string | null;
}
const updateGameData = async (params: UpdateGameDataParams): Promise<Game> => {
const {
gameKey,
existingGame,
title,
customIconUrl,
customLogoImageUrl,
customHeroImageUrl,
customOriginalIconPath,
customOriginalLogoPath,
customOriginalHeroPath,
} = params;
const updatedGame = { const updatedGame = {
...existingGame, ...existingGame,
title, title,
...(customIconUrl !== undefined && { customIconUrl }), ...(customIconUrl !== undefined && { customIconUrl }),
...(customLogoImageUrl !== undefined && { customLogoImageUrl }), ...(customLogoImageUrl !== undefined && { customLogoImageUrl }),
...(customHeroImageUrl !== undefined && { customHeroImageUrl }), ...(customHeroImageUrl !== undefined && { customHeroImageUrl }),
...(customOriginalIconPath !== undefined && { customOriginalIconPath }),
...(customOriginalLogoPath !== undefined && { customOriginalLogoPath }),
...(customOriginalHeroPath !== undefined && { customOriginalHeroPath }),
}; };
await gamesSublevel.put(gameKey, updatedGame); await gamesSublevel.put(gameKey, updatedGame);
@@ -80,15 +99,33 @@ const deleteOldAssetFiles = async (oldAssetPaths: string[]): Promise<void> => {
} }
}; };
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 ( const updateGameCustomAssets = async (
_event: Electron.IpcMainInvokeEvent, _event: Electron.IpcMainInvokeEvent,
shop: GameShop, params: UpdateGameCustomAssetsParams
objectId: string,
title: string,
customIconUrl?: string | null,
customLogoImageUrl?: string | null,
customHeroImageUrl?: string | null
) => { ) => {
const {
shop,
objectId,
title,
customIconUrl,
customLogoImageUrl,
customHeroImageUrl,
customOriginalIconPath,
customOriginalLogoPath,
customOriginalHeroPath,
} = params;
const gameKey = levelKeys.game(shop, objectId); const gameKey = levelKeys.game(shop, objectId);
const existingGame = await gamesSublevel.get(gameKey); const existingGame = await gamesSublevel.get(gameKey);
@@ -103,14 +140,17 @@ const updateGameCustomAssets = async (
customHeroImageUrl customHeroImageUrl
); );
const updatedGame = await updateGameData( const updatedGame = await updateGameData({
gameKey, gameKey,
existingGame, existingGame,
title, title,
customIconUrl, customIconUrl,
customLogoImageUrl, customLogoImageUrl,
customHeroImageUrl customHeroImageUrl,
); customOriginalIconPath,
customOriginalLogoPath,
customOriginalHeroPath,
});
await updateShopAssets(gameKey, title); await updateShopAssets(gameKey, title);

View File

@@ -152,40 +152,28 @@ contextBridge.exposeInMainWorld("electron", {
deleteTempFile: (filePath: string) => deleteTempFile: (filePath: string) =>
ipcRenderer.invoke("deleteTempFile", filePath), ipcRenderer.invoke("deleteTempFile", filePath),
cleanupUnusedAssets: () => ipcRenderer.invoke("cleanupUnusedAssets"), cleanupUnusedAssets: () => ipcRenderer.invoke("cleanupUnusedAssets"),
updateCustomGame: ( updateCustomGame: (params: {
shop: GameShop, shop: GameShop;
objectId: string, objectId: string;
title: string, title: string;
iconUrl?: string, iconUrl?: string;
logoImageUrl?: string, logoImageUrl?: string;
libraryHeroImageUrl?: string libraryHeroImageUrl?: string;
) => originalIconPath?: string;
ipcRenderer.invoke( originalLogoPath?: string;
"updateCustomGame", originalHeroPath?: string;
shop, }) => ipcRenderer.invoke("updateCustomGame", params),
objectId, updateGameCustomAssets: (params: {
title, shop: GameShop;
iconUrl, objectId: string;
logoImageUrl, title: string;
libraryHeroImageUrl customIconUrl?: string | null;
), customLogoImageUrl?: string | null;
updateGameCustomAssets: ( customHeroImageUrl?: string | null;
shop: GameShop, customOriginalIconPath?: string | null;
objectId: string, customOriginalLogoPath?: string | null;
title: string, customOriginalHeroPath?: string | null;
customIconUrl?: string | null, }) => ipcRenderer.invoke("updateGameCustomAssets", params),
customLogoImageUrl?: string | null,
customHeroImageUrl?: string | null
) =>
ipcRenderer.invoke(
"updateGameCustomAssets",
shop,
objectId,
title,
customIconUrl,
customLogoImageUrl,
customHeroImageUrl
),
createGameShortcut: ( createGameShortcut: (
shop: GameShop, shop: GameShop,
objectId: string, objectId: string,

View File

@@ -119,14 +119,17 @@ declare global {
logoImageUrl?: string, logoImageUrl?: string,
libraryHeroImageUrl?: string libraryHeroImageUrl?: string
) => Promise<Game>; ) => Promise<Game>;
updateCustomGame: ( updateCustomGame: (params: {
shop: GameShop, shop: GameShop;
objectId: string, objectId: string;
title: string, title: string;
iconUrl?: string, iconUrl?: string;
logoImageUrl?: string, logoImageUrl?: string;
libraryHeroImageUrl?: string libraryHeroImageUrl?: string;
) => Promise<Game>; originalIconPath?: string;
originalLogoPath?: string;
originalHeroPath?: string;
}) => Promise<Game>;
copyCustomGameAsset: ( copyCustomGameAsset: (
sourcePath: string, sourcePath: string,
assetType: "icon" | "logo" | "hero" assetType: "icon" | "logo" | "hero"
@@ -135,14 +138,17 @@ declare global {
deletedCount: number; deletedCount: number;
errors: string[]; errors: string[];
}>; }>;
updateGameCustomAssets: ( updateGameCustomAssets: (params: {
shop: GameShop, shop: GameShop;
objectId: string, objectId: string;
title: string, title: string;
customIconUrl?: string | null, customIconUrl?: string | null;
customLogoImageUrl?: string | null, customLogoImageUrl?: string | null;
customHeroImageUrl?: string | null customHeroImageUrl?: string | null;
) => Promise<Game>; customOriginalIconPath?: string | null;
customOriginalLogoPath?: string | null;
customOriginalHeroPath?: string | null;
}) => Promise<Game>;
createGameShortcut: ( createGameShortcut: (
shop: GameShop, shop: GameShop,
objectId: string, objectId: string,

View File

@@ -39,6 +39,11 @@ export function EditGameModal({
logo: "", logo: "",
hero: "", hero: "",
}); });
const [originalAssetPaths, setOriginalAssetPaths] = useState({
icon: "",
logo: "",
hero: "",
});
const [defaultUrls, setDefaultUrls] = useState({ const [defaultUrls, setDefaultUrls] = useState({
icon: null as string | null, icon: null as string | null,
logo: null as string | null, logo: null as string | null,
@@ -66,6 +71,14 @@ export function EditGameModal({
logo: extractLocalPath(game.logoImageUrl), logo: extractLocalPath(game.logoImageUrl),
hero: extractLocalPath(game.libraryHeroImageUrl), 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( const setNonCustomGameAssets = useCallback(
@@ -80,11 +93,25 @@ export function EditGameModal({
logo: extractLocalPath(game.customLogoImageUrl), logo: extractLocalPath(game.customLogoImageUrl),
hero: extractLocalPath(game.customHeroImageUrl), 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({ setDefaultUrls({
icon: shopDetails?.assets?.iconUrl || game.iconUrl || null, icon: shopDetails?.assets?.iconUrl || game.iconUrl || null,
logo: shopDetails?.assets?.logoImageUrl || game.logoImageUrl || null, logo: shopDetails?.assets?.logoImageUrl || game.logoImageUrl || null,
hero: shopDetails?.assets?.libraryHeroImageUrl || game.libraryHeroImageUrl || null, hero:
shopDetails?.assets?.libraryHeroImageUrl ||
game.libraryHeroImageUrl ||
null,
}); });
}, },
[shopDetails] [shopDetails]
@@ -115,15 +142,16 @@ export function EditGameModal({
}; };
const getAssetDisplayPath = (assetType: AssetType): string => { 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 => { const setAssetPath = (assetType: AssetType, path: string): void => {
setAssetPaths(prev => ({ ...prev, [assetType]: path })); setAssetPaths((prev) => ({ ...prev, [assetType]: path }));
}; };
const setAssetDisplayPath = (assetType: AssetType, path: string): void => { const setAssetDisplayPath = (assetType: AssetType, path: string): void => {
setAssetDisplayPaths(prev => ({ ...prev, [assetType]: path })); setAssetDisplayPaths((prev) => ({ ...prev, [assetType]: path }));
}; };
const getDefaultUrl = (assetType: AssetType): string | null => { const getDefaultUrl = (assetType: AssetType): string | null => {
@@ -150,10 +178,19 @@ export function EditGameModal({
); );
setAssetPath(assetType, copiedAssetUrl.replace("local:", "")); setAssetPath(assetType, copiedAssetUrl.replace("local:", ""));
setAssetDisplayPath(assetType, originalPath); setAssetDisplayPath(assetType, originalPath);
// Store the original path for display purposes
setOriginalAssetPaths((prev) => ({
...prev,
[assetType]: originalPath,
}));
} catch (error) { } catch (error) {
console.error(`Failed to copy ${assetType} asset:`, error); console.error(`Failed to copy ${assetType} asset:`, error);
setAssetPath(assetType, originalPath); setAssetPath(assetType, originalPath);
setAssetDisplayPath(assetType, originalPath); setAssetDisplayPath(assetType, originalPath);
setOriginalAssetPaths((prev) => ({
...prev,
[assetType]: originalPath,
}));
} }
} }
}; };
@@ -161,6 +198,7 @@ export function EditGameModal({
const handleRestoreDefault = (assetType: AssetType) => { const handleRestoreDefault = (assetType: AssetType) => {
setAssetPath(assetType, ""); setAssetPath(assetType, "");
setAssetDisplayPath(assetType, ""); setAssetDisplayPath(assetType, "");
setOriginalAssetPaths((prev) => ({ ...prev, [assetType]: "" }));
}; };
const getOriginalTitle = (): string => { const getOriginalTitle = (): string => {
@@ -293,7 +331,9 @@ export function EditGameModal({
// Helper function to prepare custom game assets // Helper function to prepare custom game assets
const prepareCustomGameAssets = (game: LibraryGame | Game) => { const prepareCustomGameAssets = (game: LibraryGame | Game) => {
const iconUrl = assetPaths.icon ? `local:${assetPaths.icon}` : game.iconUrl; 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 const libraryHeroImageUrl = assetPaths.hero
? `local:${assetPaths.hero}` ? `local:${assetPaths.hero}`
: game.libraryHeroImageUrl; : game.libraryHeroImageUrl;
@@ -315,14 +355,17 @@ export function EditGameModal({
const { iconUrl, logoImageUrl, libraryHeroImageUrl } = const { iconUrl, logoImageUrl, libraryHeroImageUrl } =
prepareCustomGameAssets(game); prepareCustomGameAssets(game);
return window.electron.updateCustomGame( return window.electron.updateCustomGame({
game.shop, shop: game.shop,
game.objectId, objectId: game.objectId,
gameName.trim(), title: gameName.trim(),
iconUrl || undefined, iconUrl: iconUrl || undefined,
logoImageUrl || undefined, logoImageUrl: logoImageUrl || undefined,
libraryHeroImageUrl || undefined libraryHeroImageUrl: libraryHeroImageUrl || undefined,
); originalIconPath: originalAssetPaths.icon || undefined,
originalLogoPath: originalAssetPaths.logo || undefined,
originalHeroPath: originalAssetPaths.hero || undefined,
});
}; };
// Helper function to update non-custom game // Helper function to update non-custom game
@@ -330,14 +373,17 @@ export function EditGameModal({
const { customIconUrl, customLogoImageUrl, customHeroImageUrl } = const { customIconUrl, customLogoImageUrl, customHeroImageUrl } =
prepareNonCustomGameAssets(); prepareNonCustomGameAssets();
return window.electron.updateGameCustomAssets( return window.electron.updateGameCustomAssets({
game.shop, shop: game.shop,
game.objectId, objectId: game.objectId,
gameName.trim(), title: gameName.trim(),
customIconUrl, customIconUrl,
customLogoImageUrl, customLogoImageUrl,
customHeroImageUrl customHeroImageUrl,
); customOriginalIconPath: originalAssetPaths.icon || undefined,
customOriginalLogoPath: originalAssetPaths.logo || undefined,
customOriginalHeroPath: originalAssetPaths.hero || undefined,
});
}; };
const handleUpdateGame = async () => { const handleUpdateGame = async () => {

View File

@@ -164,10 +164,12 @@
&-long { &-long {
display: inline; display: inline;
font-size: 12px;
} }
&-short { &-short {
display: none; display: none;
font-size: 12px;
} }
// When the card is narrow (less than 180px), show short format // When the card is narrow (less than 180px), show short format

View File

@@ -44,7 +44,6 @@ export function UserLibraryGameCard({
const [isTooltipHovered, setIsTooltipHovered] = useState(false); const [isTooltipHovered, setIsTooltipHovered] = useState(false);
const [isPinning, setIsPinning] = useState(false); const [isPinning, setIsPinning] = useState(false);
const getStatsItemCount = useCallback(() => { const getStatsItemCount = useCallback(() => {
let statsCount = 1; let statsCount = 1;
if (game.achievementsPointsEarnedSum > 0) statsCount++; if (game.achievementsPointsEarnedSum > 0) statsCount++;
@@ -91,15 +90,15 @@ export function UserLibraryGameCard({
const hours = minutes / 60; const hours = minutes / 60;
const hoursKey = isShort ? "amount_hours_short" : "amount_hours"; 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 }); return t(hoursKey, { amount: hoursAmount });
}, },
[numberFormatter, t] [numberFormatter, t]
); );
const toggleGamePinned = async () => { const toggleGamePinned = async () => {
setIsPinning(true); setIsPinning(true);
@@ -162,7 +161,7 @@ export function UserLibraryGameCard({
)} )}
</div> </div>
)} )}
<div <div
className="user-library-game__playtime" className="user-library-game__playtime"
data-tooltip-place="top" data-tooltip-place="top"
data-tooltip-content={ data-tooltip-content={

View File

@@ -38,6 +38,12 @@ export interface Game {
customIconUrl?: string | null; customIconUrl?: string | null;
customLogoImageUrl?: string | null; customLogoImageUrl?: string | null;
customHeroImageUrl?: 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; playTimeInMilliseconds: number;
unsyncedDeltaPlayTimeInMilliseconds?: number; unsyncedDeltaPlayTimeInMilliseconds?: number;
lastTimePlayed: Date | null; lastTimePlayed: Date | null;