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

feat: added ability to reset assets for custom game
This commit is contained in:
Chubby Granny Chaser
2025-09-30 20:19:23 +01:00
committed by GitHub
3 changed files with 106 additions and 25 deletions

View File

@@ -70,7 +70,13 @@
"edit_game_modal_icon_resolution": "Recommended resolution: 256x256px", "edit_game_modal_icon_resolution": "Recommended resolution: 256x256px",
"edit_game_modal_logo_resolution": "Recommended resolution: 640x360px", "edit_game_modal_logo_resolution": "Recommended resolution: 640x360px",
"edit_game_modal_hero_resolution": "Recommended resolution: 1920x620px", "edit_game_modal_hero_resolution": "Recommended resolution: 1920x620px",
"edit_game_modal_assets": "Assets" "edit_game_modal_assets": "Assets",
"edit_game_modal_drop_icon_image_here": "Drop icon image here",
"edit_game_modal_drop_logo_image_here": "Drop logo image here",
"edit_game_modal_drop_hero_image_here": "Drop hero image here",
"edit_game_modal_drop_to_replace_icon": "Drop to replace icon",
"edit_game_modal_drop_to_replace_logo": "Drop to replace logo",
"edit_game_modal_drop_to_replace_hero": "Drop to replace hero"
}, },
"header": { "header": {
"search": "Search games", "search": "Search games",

View File

@@ -67,7 +67,14 @@
"edit_game_modal_image_filter": "Изображение", "edit_game_modal_image_filter": "Изображение",
"edit_game_modal_icon_resolution": "Рекомендуемое разрешение: 256x256px", "edit_game_modal_icon_resolution": "Рекомендуемое разрешение: 256x256px",
"edit_game_modal_logo_resolution": "Рекомендуемое разрешение: 640x360px", "edit_game_modal_logo_resolution": "Рекомендуемое разрешение: 640x360px",
"edit_game_modal_hero_resolution": "Рекомендуемое разрешение: 1920x620px" "edit_game_modal_hero_resolution": "Рекомендуемое разрешение: 1920x620px",
"edit_game_modal_assets": "Ресурсы",
"edit_game_modal_drop_icon_image_here": "Перетащите изображение иконки сюда",
"edit_game_modal_drop_logo_image_here": "Перетащите изображение логотипа сюда",
"edit_game_modal_drop_hero_image_here": "Перетащите изображение обложки сюда",
"edit_game_modal_drop_to_replace_icon": "Перетащите для замены иконки",
"edit_game_modal_drop_to_replace_logo": "Перетащите для замены логотипа",
"edit_game_modal_drop_to_replace_hero": "Перетащите для замены обложки"
}, },
"header": { "header": {
"search": "Поиск", "search": "Поиск",

View File

@@ -4,6 +4,7 @@ import { ImageIcon, XIcon } from "@primer/octicons-react";
import { Modal, TextField, Button } from "@renderer/components"; import { Modal, TextField, Button } from "@renderer/components";
import { useToast } from "@renderer/hooks"; import { useToast } from "@renderer/hooks";
import { generateRandomGradient } from "@renderer/helpers";
import type { LibraryGame, Game, ShopDetailsWithAssets } from "@types"; import type { LibraryGame, Game, ShopDetailsWithAssets } from "@types";
import "./edit-game-modal.scss"; import "./edit-game-modal.scss";
@@ -44,6 +45,11 @@ export function EditGameModal({
logo: "", logo: "",
hero: "", hero: "",
}); });
const [removedAssets, setRemovedAssets] = useState({
icon: false,
logo: false,
hero: false,
});
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,
@@ -158,6 +164,21 @@ export function EditGameModal({
return defaultUrls[assetType]; return defaultUrls[assetType];
}; };
const getOriginalAssetUrl = (assetType: AssetType): string | null => {
if (!game || !isCustomGame(game)) return null;
switch (assetType) {
case "icon":
return game.iconUrl;
case "logo":
return game.logoImageUrl;
case "hero":
return game.libraryHeroImageUrl;
default:
return null;
}
};
const handleSelectAsset = async (assetType: AssetType) => { const handleSelectAsset = async (assetType: AssetType) => {
const { filePaths } = await window.electron.showOpenDialog({ const { filePaths } = await window.electron.showOpenDialog({
properties: ["openFile"], properties: ["openFile"],
@@ -183,6 +204,8 @@ export function EditGameModal({
...prev, ...prev,
[assetType]: originalPath, [assetType]: originalPath,
})); }));
// Clear the removed flag when a new asset is selected
setRemovedAssets((prev) => ({ ...prev, [assetType]: false }));
} 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);
@@ -191,14 +214,25 @@ export function EditGameModal({
...prev, ...prev,
[assetType]: originalPath, [assetType]: originalPath,
})); }));
// Clear the removed flag when a new asset is selected
setRemovedAssets((prev) => ({ ...prev, [assetType]: false }));
} }
} }
}; };
const handleRestoreDefault = (assetType: AssetType) => { const handleRestoreDefault = (assetType: AssetType) => {
setAssetPath(assetType, ""); if (game && isCustomGame(game)) {
setAssetDisplayPath(assetType, ""); // For custom games, mark asset as removed and clear paths
setOriginalAssetPaths((prev) => ({ ...prev, [assetType]: "" })); setRemovedAssets((prev) => ({ ...prev, [assetType]: true }));
setAssetPath(assetType, "");
setAssetDisplayPath(assetType, "");
setOriginalAssetPaths((prev) => ({ ...prev, [assetType]: "" }));
} else {
// For non-custom games, clear custom assets (restore to shop defaults)
setAssetPath(assetType, "");
setAssetDisplayPath(assetType, "");
setOriginalAssetPaths((prev) => ({ ...prev, [assetType]: "" }));
}
}; };
const getOriginalTitle = (): string => { const getOriginalTitle = (): string => {
@@ -330,13 +364,38 @@ 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; // For custom games, check if asset was explicitly removed
const logoImageUrl = assetPaths.logo let iconUrl;
? `local:${assetPaths.logo}` if (removedAssets.icon) {
: game.logoImageUrl; iconUrl = null;
const libraryHeroImageUrl = assetPaths.hero } else if (assetPaths.icon) {
? `local:${assetPaths.hero}` iconUrl = `local:${assetPaths.icon}`;
: game.libraryHeroImageUrl; } else {
iconUrl = game.iconUrl;
}
let logoImageUrl;
if (removedAssets.logo) {
logoImageUrl = null;
} else if (assetPaths.logo) {
logoImageUrl = `local:${assetPaths.logo}`;
} else {
logoImageUrl = game.logoImageUrl;
}
// For hero image, if removed, restore to the original gradient or keep the original
let libraryHeroImageUrl;
if (removedAssets.hero) {
// If the original hero was a gradient (data URL), keep it, otherwise generate a new one
const originalHero = game.libraryHeroImageUrl;
libraryHeroImageUrl = originalHero?.startsWith("data:image/svg+xml")
? originalHero
: generateRandomGradient();
} else {
libraryHeroImageUrl = assetPaths.hero
? `local:${assetPaths.hero}`
: game.libraryHeroImageUrl;
}
return { iconUrl, logoImageUrl, libraryHeroImageUrl }; return { iconUrl, logoImageUrl, libraryHeroImageUrl };
}; };
@@ -418,6 +477,13 @@ export function EditGameModal({
(game: LibraryGame | Game) => { (game: LibraryGame | Game) => {
setGameName(game.title || ""); setGameName(game.title || "");
// Reset removed assets state
setRemovedAssets({
icon: false,
logo: false,
hero: false,
});
if (isCustomGame(game)) { if (isCustomGame(game)) {
setCustomGameAssets(game); setCustomGameAssets(game);
// Clear default URLs for custom games // Clear default URLs for custom games
@@ -481,17 +547,19 @@ export function EditGameModal({
<ImageIcon /> <ImageIcon />
{t("edit_game_modal_browse")} {t("edit_game_modal_browse")}
</Button> </Button>
{game && !isCustomGame(game) && assetPath && ( {game &&
<Button (assetPath ||
type="button" (isCustomGame(game) && getOriginalAssetUrl(assetType))) && (
theme="outline" <Button
onClick={() => handleRestoreDefault(assetType)} type="button"
disabled={isUpdating} theme="outline"
title={`Remove ${assetType}`} onClick={() => handleRestoreDefault(assetType)}
> disabled={isUpdating}
<XIcon /> title={`Remove ${assetType}`}
</Button> >
)} <XIcon />
</Button>
)}
</div> </div>
} }
/> />
@@ -519,7 +587,7 @@ export function EditGameModal({
/> />
{isDragOver && ( {isDragOver && (
<div className="edit-game-modal__drop-overlay"> <div className="edit-game-modal__drop-overlay">
<span>Drop to replace {assetType}</span> <span>{t(`edit_game_modal_drop_to_replace_${assetType}`)}</span>
</div> </div>
)} )}
</button> </button>
@@ -542,7 +610,7 @@ export function EditGameModal({
> >
<div className="edit-game-modal__drop-zone-content"> <div className="edit-game-modal__drop-zone-content">
<ImageIcon /> <ImageIcon />
<span>Drop {assetType} image here</span> <span>{t(`edit_game_modal_drop_${assetType}_image_here`)}</span>
</div> </div>
</button> </button>
)} )}