diff --git a/src/main/events/library/cleanup-unused-assets.ts b/src/main/events/library/cleanup-unused-assets.ts index 9cde548d..22490c07 100644 --- a/src/main/events/library/cleanup-unused-assets.ts +++ b/src/main/events/library/cleanup-unused-assets.ts @@ -9,27 +9,27 @@ const getCustomGamesAssetsPath = () => { const getAllCustomGameAssets = async (): Promise => { const assetsPath = getCustomGamesAssetsPath(); - + if (!fs.existsSync(assetsPath)) { return []; } const files = await fs.promises.readdir(assetsPath); - return files.map(file => path.join(assetsPath, file)); + return files.map((file) => path.join(assetsPath, file)); }; const getUsedAssetPaths = async (): Promise> => { // Get all custom games from the level database const { gamesSublevel } = await import("@main/level"); const allGames = await gamesSublevel.iterator().all(); - + const customGames = allGames .map(([_key, game]) => game) - .filter(game => game.shop === "custom" && !game.isDeleted); + .filter((game) => game.shop === "custom" && !game.isDeleted); const usedPaths = new Set(); - customGames.forEach(game => { + customGames.forEach((game) => { // Extract file paths from local URLs if (game.iconUrl?.startsWith("local:")) { usedPaths.add(game.iconUrl.replace("local:", "")); @@ -45,11 +45,14 @@ const getUsedAssetPaths = async (): Promise> => { return usedPaths; }; -export const cleanupUnusedAssets = async (): Promise<{ deletedCount: number; errors: string[] }> => { +export const cleanupUnusedAssets = async (): Promise<{ + deletedCount: number; + errors: string[]; +}> => { try { const allAssets = await getAllCustomGameAssets(); const usedAssets = await getUsedAssetPaths(); - + const errors: string[] = []; let deletedCount = 0; @@ -70,4 +73,4 @@ export const cleanupUnusedAssets = async (): Promise<{ deletedCount: number; err } }; -ipcMain.handle("cleanupUnusedAssets", cleanupUnusedAssets); \ No newline at end of file +ipcMain.handle("cleanupUnusedAssets", cleanupUnusedAssets); diff --git a/src/main/events/library/copy-custom-game-asset.ts b/src/main/events/library/copy-custom-game-asset.ts index dfb7bf47..07c3d6f7 100644 --- a/src/main/events/library/copy-custom-game-asset.ts +++ b/src/main/events/library/copy-custom-game-asset.ts @@ -26,7 +26,7 @@ const copyCustomGameAsset = async ( // Get file extension const fileExtension = path.extname(sourcePath); - + // Generate unique filename const uniqueId = randomUUID(); const fileName = `${assetType}-${uniqueId}${fileExtension}`; @@ -39,4 +39,4 @@ const copyCustomGameAsset = async ( return `local:${destinationPath}`; }; -registerEvent("copyCustomGameAsset", copyCustomGameAsset); \ No newline at end of file +registerEvent("copyCustomGameAsset", copyCustomGameAsset); diff --git a/src/main/events/library/update-game-custom-assets.ts b/src/main/events/library/update-game-custom-assets.ts index 6e504e92..74446fb0 100644 --- a/src/main/events/library/update-game-custom-assets.ts +++ b/src/main/events/library/update-game-custom-assets.ts @@ -21,9 +21,9 @@ const updateGameCustomAssets = async ( const updatedGame = { ...existingGame, title, - customIconUrl: customIconUrl ?? existingGame.customIconUrl, - customLogoImageUrl: customLogoImageUrl ?? existingGame.customLogoImageUrl, - customHeroImageUrl: customHeroImageUrl ?? existingGame.customHeroImageUrl, + customIconUrl: customIconUrl !== undefined ? customIconUrl : existingGame.customIconUrl, + customLogoImageUrl: customLogoImageUrl !== undefined ? customLogoImageUrl : existingGame.customLogoImageUrl, + customHeroImageUrl: customHeroImageUrl !== undefined ? customHeroImageUrl : existingGame.customHeroImageUrl, }; await gamesSublevel.put(gameKey, updatedGame); diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index 0515f1f0..45bdb20d 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -130,7 +130,10 @@ declare global { sourcePath: string, assetType: "icon" | "logo" | "hero" ) => Promise; - cleanupUnusedAssets: () => Promise<{ deletedCount: number; errors: string[] }>; + cleanupUnusedAssets: () => Promise<{ + deletedCount: number; + errors: string[]; + }>; updateGameCustomAssets: ( shop: GameShop, objectId: string, 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 22f56630..6f3c2ee2 100644 --- a/src/renderer/src/pages/game-details/game-details-content.tsx +++ b/src/renderer/src/pages/game-details/game-details-content.tsx @@ -232,6 +232,7 @@ export function GameDetailsContent() { visible={showEditGameModal} onClose={() => setShowEditGameModal(false)} game={game} + shopDetails={shopDetails} onGameUpdated={handleGameUpdated} /> 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 b922a231..45653649 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,10 +1,10 @@ import { useState, useEffect } from "react"; import { useTranslation } from "react-i18next"; -import { ImageIcon } from "@primer/octicons-react"; +import { ImageIcon, ReplyIcon } from "@primer/octicons-react"; import { Modal, TextField, Button } from "@renderer/components"; import { useToast } from "@renderer/hooks"; -import type { LibraryGame, Game } from "@types"; +import type { LibraryGame, Game, ShopDetailsWithAssets } from "@types"; import "./edit-game-modal.scss"; @@ -12,6 +12,7 @@ export interface EditGameModalProps { visible: boolean; onClose: () => void; game: LibraryGame | Game | null; + shopDetails?: ShopDetailsWithAssets | null; onGameUpdated: (updatedGame: any) => void; } @@ -19,6 +20,7 @@ export function EditGameModal({ visible, onClose, game, + shopDetails, onGameUpdated, }: Readonly) { const { t } = useTranslation("sidebar"); @@ -29,6 +31,11 @@ export function EditGameModal({ const [logoPath, setLogoPath] = useState(""); const [heroPath, setHeroPath] = useState(""); const [isUpdating, setIsUpdating] = useState(false); + + // Store default image URLs for non-custom games + const [defaultIconUrl, setDefaultIconUrl] = useState(null); + const [defaultLogoUrl, setDefaultLogoUrl] = useState(null); + const [defaultHeroUrl, setDefaultHeroUrl] = useState(null); // Helper function to check if game is a custom game const isCustomGame = (game: LibraryGame | Game): boolean => { @@ -52,6 +59,11 @@ export function EditGameModal({ setIconPath(extractLocalPath(game.customIconUrl)); setLogoPath(extractLocalPath(game.customLogoImageUrl)); setHeroPath(extractLocalPath(game.customHeroImageUrl)); + + // Store default URLs for restore functionality from shopDetails.assets + setDefaultIconUrl(shopDetails?.assets?.iconUrl || game.iconUrl || null); + setDefaultLogoUrl(shopDetails?.assets?.logoImageUrl || game.logoImageUrl || null); + setDefaultHeroUrl(shopDetails?.assets?.libraryHeroImageUrl || game.libraryHeroImageUrl || null); }; useEffect(() => { @@ -64,7 +76,7 @@ export function EditGameModal({ setNonCustomGameAssets(game as LibraryGame); } } - }, [game, visible]); + }, [game, visible, shopDetails]); const handleGameNameChange = (event: React.ChangeEvent) => { setGameName(event.target.value); @@ -151,6 +163,19 @@ export function EditGameModal({ } }; + // Helper functions to restore default images for non-custom games + const handleRestoreDefaultIcon = () => { + setIconPath(""); + }; + + const handleRestoreDefaultLogo = () => { + setLogoPath(""); + }; + + const handleRestoreDefaultHero = () => { + setHeroPath(""); + }; + // Helper function to prepare custom game assets const prepareCustomGameAssets = (game: LibraryGame | Game) => { const iconUrl = iconPath ? `local:${iconPath}` : game.iconUrl; @@ -235,6 +260,10 @@ export function EditGameModal({ if (isCustomGame(game)) { setCustomGameAssets(game); + // Clear default URLs for custom games + setDefaultIconUrl(null); + setDefaultLogoUrl(null); + setDefaultHeroUrl(null); } else { setNonCustomGameAssets(game as LibraryGame); } @@ -250,14 +279,26 @@ export function EditGameModal({ const isFormValid = gameName.trim(); const getIconPreviewUrl = (): string | undefined => { + if (!isCustomGame(game!)) { + // For non-custom games, show custom image if set, otherwise show default + return iconPath ? `local:${iconPath}` : defaultIconUrl || undefined; + } return iconPath ? `local:${iconPath}` : undefined; }; const getLogoPreviewUrl = (): string | undefined => { + if (!isCustomGame(game!)) { + // For non-custom games, show custom image if set, otherwise show default + return logoPath ? `local:${logoPath}` : defaultLogoUrl || undefined; + } return logoPath ? `local:${logoPath}` : undefined; }; const getHeroPreviewUrl = (): string | undefined => { + if (!isCustomGame(game!)) { + // For non-custom games, show custom image if set, otherwise show default + return heroPath ? `local:${heroPath}` : defaultHeroUrl || undefined; + } return heroPath ? `local:${heroPath}` : undefined; }; @@ -287,19 +328,32 @@ export function EditGameModal({ readOnly theme="dark" rightContent={ - +
+ + {!isCustomGame(game!) && iconPath && ( + + )} +
} /> - {iconPath && ( + {(iconPath || (!isCustomGame(game!) && defaultIconUrl)) && (
- - {t("edit_custom_game_modal_browse")} - +
+ + {!isCustomGame(game!) && logoPath && ( + + )} +
} /> - {logoPath && ( + {(logoPath || (!isCustomGame(game!) && defaultLogoUrl)) && (
- - {t("edit_custom_game_modal_browse")} - +
+ + {!isCustomGame(game!) && heroPath && ( + + )} +
} /> - {heroPath && ( + {(heroPath || (!isCustomGame(game!) && defaultHeroUrl)) && (
{t("remove_files")}