mirror of
https://github.com/hydralauncher/hydra.git
synced 2026-01-11 13:56:16 +00:00
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:
@@ -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",
|
||||||
|
|||||||
@@ -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": "Поиск",
|
||||||
|
|||||||
@@ -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>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user