mirror of
https://github.com/hydralauncher/hydra.git
synced 2026-01-11 13:56:16 +00:00
added drag and drop functionality and recommended resolution to each image
This commit is contained in:
@@ -35,53 +35,42 @@
|
||||
"custom_game_modal_description": "Add a custom game to your library by selecting an executable file",
|
||||
"custom_game_modal_executable_path": "Executable Path",
|
||||
"custom_game_modal_select_executable": "Select executable file",
|
||||
"custom_game_modal_game_name": "Game Name",
|
||||
"custom_game_modal_enter_name": "Enter game name",
|
||||
"custom_game_modal_image": "Game Image",
|
||||
"custom_game_modal_select_image": "Select game image",
|
||||
"custom_game_modal_image_preview": "Game image preview",
|
||||
"custom_game_modal_title": "Title",
|
||||
"custom_game_modal_enter_title": "Enter title",
|
||||
"custom_game_modal_browse": "Browse",
|
||||
"custom_game_modal_cancel": "Cancel",
|
||||
"custom_game_modal_add": "Add Game",
|
||||
"custom_game_modal_adding": "Adding Game...",
|
||||
"custom_game_modal_fill_required": "Please fill in all required fields",
|
||||
"custom_game_modal_success": "Custom game added successfully",
|
||||
"custom_game_modal_failed": "Failed to add custom game",
|
||||
"custom_game_modal_executable": "Executable",
|
||||
"custom_game_modal_image_filter": "Image",
|
||||
"custom_game_modal_icon": "Game Icon",
|
||||
"custom_game_modal_select_icon": "Select game icon",
|
||||
"custom_game_modal_icon_preview": "Game icon preview",
|
||||
"custom_game_modal_logo": "Game Logo",
|
||||
"custom_game_modal_select_logo": "Select game logo",
|
||||
"custom_game_modal_logo_preview": "Game logo preview",
|
||||
"custom_game_modal_hero": "Library Hero Image",
|
||||
"custom_game_modal_select_hero": "Select library hero image",
|
||||
"custom_game_modal_hero_preview": "Library hero image preview",
|
||||
"edit_custom_game_modal": "Edit Custom Game",
|
||||
"edit_custom_game_modal_description": "Edit your custom game details",
|
||||
"edit_custom_game_modal_game_name": "Game Name",
|
||||
"edit_custom_game_modal_enter_name": "Enter game name",
|
||||
"edit_custom_game_modal_image": "Game Image",
|
||||
"edit_custom_game_modal_select_image": "Select game image",
|
||||
"edit_custom_game_modal_browse": "Browse",
|
||||
"edit_custom_game_modal_image_preview": "Game image preview",
|
||||
"edit_custom_game_modal_icon": "Game Icon",
|
||||
"edit_custom_game_modal_select_icon": "Select game icon",
|
||||
"edit_custom_game_modal_icon_preview": "Game icon preview",
|
||||
"edit_custom_game_modal_logo": "Game Logo",
|
||||
"edit_custom_game_modal_select_logo": "Select game logo",
|
||||
"edit_custom_game_modal_logo_preview": "Game logo preview",
|
||||
"edit_custom_game_modal_hero": "Library Hero Image",
|
||||
"edit_custom_game_modal_select_hero": "Select library hero image",
|
||||
"edit_custom_game_modal_hero_preview": "Library hero image preview",
|
||||
"edit_custom_game_modal_cancel": "Cancel",
|
||||
"edit_custom_game_modal_update": "Update Game",
|
||||
"edit_custom_game_modal_updating": "Updating Game...",
|
||||
"edit_custom_game_modal_fill_required": "Please fill in all required fields",
|
||||
"edit_custom_game_modal_success": "Custom game updated successfully",
|
||||
"edit_custom_game_modal_failed": "Failed to update custom game",
|
||||
"edit_custom_game_modal_image_filter": "Image"
|
||||
"edit_game_modal": "Customize Assets",
|
||||
"edit_game_modal_description": "Customize game assets and details",
|
||||
"edit_game_modal_title": "Title",
|
||||
"edit_game_modal_enter_title": "Enter title",
|
||||
"edit_game_modal_image": "Image",
|
||||
"edit_game_modal_select_image": "Select image",
|
||||
"edit_game_modal_browse": "Browse",
|
||||
"edit_game_modal_image_preview": "Image preview",
|
||||
"edit_game_modal_icon": "Icon",
|
||||
"edit_game_modal_select_icon": "Select icon",
|
||||
"edit_game_modal_icon_preview": "Icon preview",
|
||||
"edit_game_modal_logo": "Logo",
|
||||
"edit_game_modal_select_logo": "Select logo",
|
||||
"edit_game_modal_logo_preview": "Logo preview",
|
||||
"edit_game_modal_hero": "Library Hero Image",
|
||||
"edit_game_modal_select_hero": "Select library hero image",
|
||||
"edit_game_modal_hero_preview": "Library hero image preview",
|
||||
"edit_game_modal_cancel": "Cancel",
|
||||
"edit_game_modal_update": "Update",
|
||||
"edit_game_modal_updating": "Updating...",
|
||||
"edit_game_modal_fill_required": "Please fill in all required fields",
|
||||
"edit_game_modal_success": "Assets updated successfully",
|
||||
"edit_game_modal_failed": "Failed to update assets",
|
||||
"edit_game_modal_image_filter": "Image",
|
||||
"edit_game_modal_icon_resolution": "Recommended resolution: 256x256px",
|
||||
"edit_game_modal_logo_resolution": "Recommended resolution: 640x360px",
|
||||
"edit_game_modal_hero_resolution": "Recommended resolution: 1920x620px"
|
||||
},
|
||||
"header": {
|
||||
"search": "Search games",
|
||||
|
||||
@@ -28,58 +28,47 @@
|
||||
"friends": "Друзья",
|
||||
"need_help": "Нужна помощь?",
|
||||
"favorites": "Избранное",
|
||||
"playable_button_title": "Показать только игры, в которые можно играть сейчас",
|
||||
"playable_button_title": "Показать только установленные игры.",
|
||||
"custom_game_modal": "Добавить пользовательскую игру",
|
||||
"custom_game_modal_description": "Добавьте пользовательскую игру в библиотеку, выбрав исполняемый файл",
|
||||
"custom_game_modal_executable_path": "Путь к исполняемому файлу",
|
||||
"custom_game_modal_select_executable": "Выберите исполняемый файл",
|
||||
"custom_game_modal_game_name": "Название игры",
|
||||
"custom_game_modal_enter_name": "Введите название игры",
|
||||
"custom_game_modal_image": "Изображение игры",
|
||||
"custom_game_modal_select_image": "Выберите изображение игры",
|
||||
"custom_game_modal_image_preview": "Предварительный просмотр изображения игры",
|
||||
"custom_game_modal_title": "Название игры",
|
||||
"custom_game_modal_enter_title": "Введите название игры",
|
||||
"custom_game_modal_browse": "Обзор",
|
||||
"custom_game_modal_cancel": "Отмена",
|
||||
"custom_game_modal_add": "Добавить игру",
|
||||
"custom_game_modal_adding": "Добавление игры...",
|
||||
"custom_game_modal_fill_required": "Пожалуйста, заполните все обязательные поля",
|
||||
"custom_game_modal_success": "Пользовательская игра успешно добавлена",
|
||||
"custom_game_modal_failed": "Не удалось добавить пользовательскую игру",
|
||||
"custom_game_modal_executable": "Исполняемый файл",
|
||||
"custom_game_modal_image_filter": "Изображение",
|
||||
"custom_game_modal_icon": "Иконка игры",
|
||||
"custom_game_modal_select_icon": "Выберите иконку игры",
|
||||
"custom_game_modal_icon_preview": "Предпросмотр иконки игры",
|
||||
"custom_game_modal_logo": "Логотип игры",
|
||||
"custom_game_modal_select_logo": "Выберите логотип игры",
|
||||
"custom_game_modal_logo_preview": "Предпросмотр логотипа игры",
|
||||
"custom_game_modal_hero": "Изображение героя библиотеки",
|
||||
"custom_game_modal_select_hero": "Выберите изображение героя библиотеки",
|
||||
"custom_game_modal_hero_preview": "Предпросмотр изображения героя библиотеки",
|
||||
"edit_custom_game_modal": "Редактировать пользовательскую игру",
|
||||
"edit_custom_game_modal_description": "Редактируйте детали вашей пользовательской игры",
|
||||
"edit_custom_game_modal_game_name": "Название игры",
|
||||
"edit_custom_game_modal_enter_name": "Введите название игры",
|
||||
"edit_custom_game_modal_image": "Изображение игры",
|
||||
"edit_custom_game_modal_select_image": "Выберите изображение игры",
|
||||
"edit_custom_game_modal_browse": "Обзор",
|
||||
"edit_custom_game_modal_image_preview": "Предпросмотр изображения игры",
|
||||
"edit_custom_game_modal_icon": "Иконка игры",
|
||||
"edit_custom_game_modal_select_icon": "Выберите иконку игры",
|
||||
"edit_custom_game_modal_icon_preview": "Предпросмотр иконки игры",
|
||||
"edit_custom_game_modal_logo": "Логотип игры",
|
||||
"edit_custom_game_modal_select_logo": "Выберите логотип игры",
|
||||
"edit_custom_game_modal_logo_preview": "Предпросмотр логотипа игры",
|
||||
"edit_custom_game_modal_hero": "Изображение героя библиотеки",
|
||||
"edit_custom_game_modal_select_hero": "Выберите изображение героя библиотеки",
|
||||
"edit_custom_game_modal_hero_preview": "Предпросмотр изображения героя библиотеки",
|
||||
"edit_custom_game_modal_cancel": "Отмена",
|
||||
"edit_custom_game_modal_update": "Обновить игру",
|
||||
"edit_custom_game_modal_updating": "Обновление игры...",
|
||||
"edit_custom_game_modal_fill_required": "Пожалуйста, заполните все обязательные поля",
|
||||
"edit_custom_game_modal_success": "Пользовательская игра успешно обновлена",
|
||||
"edit_custom_game_modal_failed": "Не удалось обновить пользовательскую игру",
|
||||
"edit_custom_game_modal_image_filter": "Изображение"
|
||||
"edit_game_modal": "Настроить ресурсы",
|
||||
"edit_game_modal_description": "Настройте ресурсы и детали игры",
|
||||
"edit_game_modal_title": "Название",
|
||||
"edit_game_modal_enter_title": "Введите название",
|
||||
"edit_game_modal_image": "Изображение",
|
||||
"edit_game_modal_select_image": "Выберите изображение",
|
||||
"edit_game_modal_browse": "Обзор",
|
||||
"edit_game_modal_image_preview": "Предпросмотр изображения",
|
||||
"edit_game_modal_icon": "Иконка",
|
||||
"edit_game_modal_select_icon": "Выберите иконку",
|
||||
"edit_game_modal_icon_preview": "Предпросмотр иконки",
|
||||
"edit_game_modal_logo": "Логотип",
|
||||
"edit_game_modal_select_logo": "Выберите логотип",
|
||||
"edit_game_modal_logo_preview": "Предпросмотр логотипа",
|
||||
"edit_game_modal_hero": "Изображение обложку игры",
|
||||
"edit_game_modal_select_hero": "Выберите обложку игры",
|
||||
"edit_game_modal_hero_preview": "Предпросмотр обложки игры",
|
||||
"edit_game_modal_cancel": "Отмена",
|
||||
"edit_game_modal_update": "Обновить",
|
||||
"edit_game_modal_updating": "Обновление...",
|
||||
"edit_game_modal_fill_required": "Пожалуйста, заполните все обязательные поля",
|
||||
"edit_game_modal_success": "Ресурсы успешно обновлены",
|
||||
"edit_game_modal_failed": "Не удалось обновить ресурсы",
|
||||
"edit_game_modal_image_filter": "Изображение",
|
||||
"edit_game_modal_icon_resolution": "Рекомендуемое разрешение: 256x256px",
|
||||
"edit_game_modal_logo_resolution": "Рекомендуемое разрешение: 640x360px",
|
||||
"edit_game_modal_hero_resolution": "Рекомендуемое разрешение: 1920x620px"
|
||||
},
|
||||
"header": {
|
||||
"search": "Поиск",
|
||||
|
||||
@@ -50,6 +50,8 @@ import "./misc/show-item-in-folder";
|
||||
import "./misc/get-badges";
|
||||
import "./misc/install-common-redist";
|
||||
import "./misc/can-install-common-redist";
|
||||
import "./misc/save-temp-file";
|
||||
import "./misc/delete-temp-file";
|
||||
import "./torrenting/cancel-game-download";
|
||||
import "./torrenting/pause-game-download";
|
||||
import "./torrenting/resume-game-download";
|
||||
|
||||
18
src/main/events/misc/delete-temp-file.ts
Normal file
18
src/main/events/misc/delete-temp-file.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import fs from "node:fs";
|
||||
import { registerEvent } from "../register-event";
|
||||
|
||||
const deleteTempFile = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
filePath: string
|
||||
): Promise<void> => {
|
||||
try {
|
||||
if (fs.existsSync(filePath)) {
|
||||
fs.unlinkSync(filePath);
|
||||
}
|
||||
} catch (error) {
|
||||
// Silently fail - temp files will be cleaned up by OS eventually
|
||||
console.warn(`Failed to delete temp file: ${error}`);
|
||||
}
|
||||
};
|
||||
|
||||
registerEvent("deleteTempFile", deleteTempFile);
|
||||
24
src/main/events/misc/save-temp-file.ts
Normal file
24
src/main/events/misc/save-temp-file.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { app } from "electron";
|
||||
import { registerEvent } from "../register-event";
|
||||
|
||||
const saveTempFile = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
fileName: string,
|
||||
fileData: Uint8Array
|
||||
): Promise<string> => {
|
||||
try {
|
||||
const tempDir = app.getPath("temp");
|
||||
const tempFilePath = path.join(tempDir, `hydra-temp-${Date.now()}-${fileName}`);
|
||||
|
||||
// Write the file data to temp directory
|
||||
fs.writeFileSync(tempFilePath, fileData);
|
||||
|
||||
return tempFilePath;
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to save temp file: ${error}`);
|
||||
}
|
||||
};
|
||||
|
||||
registerEvent("saveTempFile", saveTempFile);
|
||||
@@ -147,6 +147,10 @@ contextBridge.exposeInMainWorld("electron", {
|
||||
sourcePath: string,
|
||||
assetType: "icon" | "logo" | "hero"
|
||||
) => ipcRenderer.invoke("copyCustomGameAsset", sourcePath, assetType),
|
||||
saveTempFile: (fileName: string, fileData: Uint8Array) =>
|
||||
ipcRenderer.invoke("saveTempFile", fileName, fileData),
|
||||
deleteTempFile: (filePath: string) =>
|
||||
ipcRenderer.invoke("deleteTempFile", filePath),
|
||||
cleanupUnusedAssets: () => ipcRenderer.invoke("cleanupUnusedAssets"),
|
||||
updateCustomGame: (
|
||||
shop: GameShop,
|
||||
|
||||
@@ -143,8 +143,8 @@ export function SidebarAddingCustomGameModal({
|
||||
/>
|
||||
|
||||
<TextField
|
||||
label={t("custom_game_modal_game_name")}
|
||||
placeholder={t("custom_game_modal_enter_name")}
|
||||
label={t("custom_game_modal_title")}
|
||||
placeholder={t("custom_game_modal_enter_title")}
|
||||
value={gameName}
|
||||
onChange={handleGameNameChange}
|
||||
theme="dark"
|
||||
|
||||
2
src/renderer/src/declaration.d.ts
vendored
2
src/renderer/src/declaration.d.ts
vendored
@@ -298,6 +298,8 @@ declare global {
|
||||
onCommonRedistProgress: (
|
||||
cb: (value: { log: string; complete: boolean }) => void
|
||||
) => () => Electron.IpcRenderer;
|
||||
saveTempFile: (fileName: string, fileData: Uint8Array) => Promise<string>;
|
||||
deleteTempFile: (filePath: string) => Promise<void>;
|
||||
platform: NodeJS.Platform;
|
||||
|
||||
/* Auto update */
|
||||
|
||||
@@ -170,12 +170,28 @@ export function GameDetailsContent() {
|
||||
style={{ opacity: backdropOpacity }}
|
||||
>
|
||||
<div className="game-details__hero-content">
|
||||
{logoImage && (
|
||||
<img
|
||||
src={logoImage}
|
||||
className="game-details__game-logo"
|
||||
alt={game?.title}
|
||||
/>
|
||||
{isCustomGame ? (
|
||||
// For custom games, show logo image if available, otherwise show game title as text
|
||||
logoImage ? (
|
||||
<img
|
||||
src={logoImage}
|
||||
className="game-details__game-logo"
|
||||
alt={game?.title}
|
||||
/>
|
||||
) : (
|
||||
<div className="game-details__game-logo-text">
|
||||
{game?.title}
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
// For non-custom games, show logo image if available
|
||||
logoImage && (
|
||||
<img
|
||||
src={logoImage}
|
||||
className="game-details__game-logo"
|
||||
alt={game?.title}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
|
||||
<div className="game-details__hero-buttons game-details__hero-buttons--right">
|
||||
|
||||
@@ -121,6 +121,20 @@ $hero-height: 300px;
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
&__game-logo-text {
|
||||
width: 300px;
|
||||
align-self: flex-end;
|
||||
font-size: 2.5rem;
|
||||
font-weight: bold;
|
||||
color: #ffffff;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);
|
||||
text-align: left;
|
||||
line-height: 1.2;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
hyphens: auto;
|
||||
}
|
||||
|
||||
&__hero-image-skeleton {
|
||||
height: 300px;
|
||||
|
||||
|
||||
@@ -18,6 +18,13 @@
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.edit-game-modal__resolution-info {
|
||||
font-size: 12px;
|
||||
color: var(--color-text-secondary);
|
||||
margin-top: -4px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.edit-game-modal__image-preview {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@@ -26,6 +33,86 @@
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 8px;
|
||||
background-color: var(--color-background-secondary);
|
||||
background-image:
|
||||
linear-gradient(45deg, rgba(255, 255, 255, 0.1) 25%, transparent 25%),
|
||||
linear-gradient(-45deg, rgba(255, 255, 255, 0.1) 25%, transparent 25%),
|
||||
linear-gradient(45deg, transparent 75%, rgba(255, 255, 255, 0.1) 75%),
|
||||
linear-gradient(-45deg, transparent 75%, rgba(255, 255, 255, 0.1) 75%);
|
||||
background-size: 16px 16px;
|
||||
background-position: 0 0, 0 8px, 8px -8px, -8px 0px;
|
||||
transition: border-color 0.2s ease, background-color 0.2s ease, transform 0.2s ease;
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.edit-game-modal__drop-zone {
|
||||
min-height: 120px;
|
||||
cursor: pointer;
|
||||
border-style: dashed !important;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--color-primary);
|
||||
background-color: rgba(var(--color-primary-rgb), 0.05);
|
||||
}
|
||||
|
||||
&--active {
|
||||
border-color: var(--color-primary) !important;
|
||||
background-color: rgba(var(--color-primary-rgb), 0.1) !important;
|
||||
transform: scale(1.02);
|
||||
box-shadow: 0 4px 12px rgba(var(--color-primary-rgb), 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
.edit-game-modal__drop-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(var(--color-primary-rgb), 0.9);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 8px;
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
backdrop-filter: blur(2px);
|
||||
animation: fadeIn 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.edit-game-modal__drop-zone-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 14px;
|
||||
|
||||
svg {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
.edit-game-modal__icon-preview {
|
||||
max-width: 200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.edit-game-modal__preview-image {
|
||||
|
||||
@@ -93,7 +93,7 @@ export function EditGameModal({
|
||||
properties: ["openFile"],
|
||||
filters: [
|
||||
{
|
||||
name: t("edit_custom_game_modal_image_filter"),
|
||||
name: t("edit_game_modal_image_filter"),
|
||||
extensions: ["jpg", "jpeg", "png", "gif", "webp"],
|
||||
},
|
||||
],
|
||||
@@ -120,7 +120,7 @@ export function EditGameModal({
|
||||
properties: ["openFile"],
|
||||
filters: [
|
||||
{
|
||||
name: t("edit_custom_game_modal_image_filter"),
|
||||
name: t("edit_game_modal_image_filter"),
|
||||
extensions: ["jpg", "jpeg", "png", "gif", "webp"],
|
||||
},
|
||||
],
|
||||
@@ -147,7 +147,7 @@ export function EditGameModal({
|
||||
properties: ["openFile"],
|
||||
filters: [
|
||||
{
|
||||
name: t("edit_custom_game_modal_image_filter"),
|
||||
name: t("edit_game_modal_image_filter"),
|
||||
extensions: ["jpg", "jpeg", "png", "gif", "webp"],
|
||||
},
|
||||
],
|
||||
@@ -182,6 +182,141 @@ export function EditGameModal({
|
||||
setHeroPath("");
|
||||
};
|
||||
|
||||
// Drag and drop state
|
||||
const [dragOverTarget, setDragOverTarget] = useState<string | null>(null);
|
||||
|
||||
// Drag and drop handlers
|
||||
const handleDragOver = (e: React.DragEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
const handleDragEnter = (e: React.DragEvent, target: string) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setDragOverTarget(target);
|
||||
};
|
||||
|
||||
const handleDragLeave = (e: React.DragEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
// Only clear drag state if we're leaving the drop zone entirely
|
||||
if (!e.currentTarget.contains(e.relatedTarget as Node)) {
|
||||
setDragOverTarget(null);
|
||||
}
|
||||
};
|
||||
|
||||
const validateImageFile = (file: File): boolean => {
|
||||
const validTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp'];
|
||||
return validTypes.includes(file.type);
|
||||
};
|
||||
|
||||
const processDroppedFile = async (file: File, assetType: 'icon' | 'logo' | 'hero') => {
|
||||
setDragOverTarget(null);
|
||||
|
||||
if (!validateImageFile(file)) {
|
||||
showErrorToast('Invalid file type. Please select an image file.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// In Electron, we need to get the file path differently
|
||||
let filePath: string;
|
||||
|
||||
// Try to get the path from the file object (Electron specific)
|
||||
if ('path' in file && typeof (file as any).path === 'string') {
|
||||
filePath = (file as any).path;
|
||||
} else {
|
||||
// Fallback: create a temporary file from the file data
|
||||
const arrayBuffer = await file.arrayBuffer();
|
||||
const uint8Array = new Uint8Array(arrayBuffer);
|
||||
|
||||
// Use a temporary file approach
|
||||
const tempFileName = `temp_${Date.now()}_${file.name}`;
|
||||
const tempPath = await window.electron.saveTempFile?.(tempFileName, uint8Array);
|
||||
|
||||
if (!tempPath) {
|
||||
throw new Error('Unable to process file. Drag and drop may not be fully supported.');
|
||||
}
|
||||
|
||||
filePath = tempPath;
|
||||
}
|
||||
|
||||
// Copy the asset to the app's assets folder using the file path
|
||||
const copiedAssetUrl = await window.electron.copyCustomGameAsset(
|
||||
filePath,
|
||||
assetType
|
||||
);
|
||||
|
||||
const assetPath = copiedAssetUrl.replace("local:", "");
|
||||
|
||||
switch (assetType) {
|
||||
case 'icon':
|
||||
setIconPath(assetPath);
|
||||
break;
|
||||
case 'logo':
|
||||
setLogoPath(assetPath);
|
||||
break;
|
||||
case 'hero':
|
||||
setHeroPath(assetPath);
|
||||
break;
|
||||
}
|
||||
|
||||
showSuccessToast(`${assetType.charAt(0).toUpperCase() + assetType.slice(1)} updated successfully!`);
|
||||
|
||||
// Clean up temporary file if we created one
|
||||
if (!('path' in file) && filePath) {
|
||||
try {
|
||||
await window.electron.deleteTempFile?.(filePath);
|
||||
} catch (cleanupError) {
|
||||
console.warn('Failed to clean up temporary file:', cleanupError);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Failed to process dropped ${assetType}:`, error);
|
||||
showErrorToast(`Failed to process dropped ${assetType}. Please try again.`);
|
||||
}
|
||||
};
|
||||
|
||||
const handleIconDrop = async (e: React.DragEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setDragOverTarget(null);
|
||||
|
||||
if (isUpdating) return;
|
||||
|
||||
const files = Array.from(e.dataTransfer.files);
|
||||
if (files.length > 0) {
|
||||
await processDroppedFile(files[0], 'icon');
|
||||
}
|
||||
};
|
||||
|
||||
const handleLogoDrop = async (e: React.DragEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setDragOverTarget(null);
|
||||
|
||||
if (isUpdating) return;
|
||||
|
||||
const files = Array.from(e.dataTransfer.files);
|
||||
if (files.length > 0) {
|
||||
await processDroppedFile(files[0], 'logo');
|
||||
}
|
||||
};
|
||||
|
||||
const handleHeroDrop = async (e: React.DragEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setDragOverTarget(null);
|
||||
|
||||
if (isUpdating) return;
|
||||
|
||||
const files = Array.from(e.dataTransfer.files);
|
||||
if (files.length > 0) {
|
||||
await processDroppedFile(files[0], 'hero');
|
||||
}
|
||||
};
|
||||
|
||||
// Helper function to prepare custom game assets
|
||||
const prepareCustomGameAssets = (game: LibraryGame | Game) => {
|
||||
const iconUrl = iconPath ? `local:${iconPath}` : game.iconUrl;
|
||||
@@ -234,18 +369,19 @@ export function EditGameModal({
|
||||
|
||||
const handleUpdateGame = async () => {
|
||||
if (!game || !gameName.trim()) {
|
||||
showErrorToast(t("edit_custom_game_modal_fill_required"));
|
||||
showErrorToast(t("edit_game_modal_fill_required"));
|
||||
return;
|
||||
}
|
||||
|
||||
setIsUpdating(true);
|
||||
|
||||
try {
|
||||
const updatedGame = game && isCustomGame(game)
|
||||
? await updateCustomGame(game)
|
||||
: await updateNonCustomGame(game as LibraryGame);
|
||||
const updatedGame =
|
||||
game && isCustomGame(game)
|
||||
? await updateCustomGame(game)
|
||||
: await updateNonCustomGame(game as LibraryGame);
|
||||
|
||||
showSuccessToast(t("edit_custom_game_modal_success"));
|
||||
showSuccessToast(t("edit_game_modal_success"));
|
||||
onGameUpdated(updatedGame);
|
||||
onClose();
|
||||
} catch (error) {
|
||||
@@ -253,7 +389,7 @@ export function EditGameModal({
|
||||
showErrorToast(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: t("edit_custom_game_modal_failed")
|
||||
: t("edit_game_modal_failed")
|
||||
);
|
||||
} finally {
|
||||
setIsUpdating(false);
|
||||
@@ -311,15 +447,15 @@ export function EditGameModal({
|
||||
return (
|
||||
<Modal
|
||||
visible={visible}
|
||||
title={t("edit_custom_game_modal")}
|
||||
description={t("edit_custom_game_modal_description")}
|
||||
title={t("edit_game_modal")}
|
||||
description={t("edit_game_modal_description")}
|
||||
onClose={handleClose}
|
||||
>
|
||||
<div className="edit-game-modal__container">
|
||||
<div className="edit-game-modal__form">
|
||||
<TextField
|
||||
label={t("edit_custom_game_modal_game_name")}
|
||||
placeholder={t("edit_custom_game_modal_enter_name")}
|
||||
label={t("edit_game_modal_title")}
|
||||
placeholder={t("edit_game_modal_enter_title")}
|
||||
value={gameName}
|
||||
onChange={handleGameNameChange}
|
||||
theme="dark"
|
||||
@@ -328,8 +464,8 @@ export function EditGameModal({
|
||||
|
||||
<div className="edit-game-modal__image-section">
|
||||
<TextField
|
||||
label={t("edit_custom_game_modal_icon")}
|
||||
placeholder={t("edit_custom_game_modal_select_icon")}
|
||||
label={t("edit_game_modal_icon")}
|
||||
placeholder={t("edit_game_modal_select_icon")}
|
||||
value={iconPath}
|
||||
readOnly
|
||||
theme="dark"
|
||||
@@ -342,7 +478,7 @@ export function EditGameModal({
|
||||
disabled={isUpdating}
|
||||
>
|
||||
<ImageIcon />
|
||||
{t("edit_custom_game_modal_browse")}
|
||||
{t("edit_game_modal_browse")}
|
||||
</Button>
|
||||
{game && !isCustomGame(game) && iconPath && (
|
||||
<Button
|
||||
@@ -358,22 +494,55 @@ export function EditGameModal({
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<div className="edit-game-modal__resolution-info">
|
||||
{t("edit_game_modal_icon_resolution")}
|
||||
</div>
|
||||
|
||||
{(iconPath || (game && !isCustomGame(game) && defaultIconUrl)) && (
|
||||
<div className="edit-game-modal__image-preview">
|
||||
<div
|
||||
className={`edit-game-modal__image-preview edit-game-modal__icon-preview ${
|
||||
dragOverTarget === 'icon' ? 'edit-game-modal__drop-zone--active' : ''
|
||||
}`}
|
||||
onDragOver={handleDragOver}
|
||||
onDragEnter={(e) => handleDragEnter(e, 'icon')}
|
||||
onDragLeave={handleDragLeave}
|
||||
onDrop={handleIconDrop}
|
||||
>
|
||||
<img
|
||||
src={getIconPreviewUrl()}
|
||||
alt={t("edit_custom_game_modal_icon_preview")}
|
||||
alt={t("edit_game_modal_icon_preview")}
|
||||
className="edit-game-modal__preview-image"
|
||||
/>
|
||||
{dragOverTarget === 'icon' && (
|
||||
<div className="edit-game-modal__drop-overlay">
|
||||
<span>Drop to replace icon</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(!iconPath && !(game && !isCustomGame(game) && defaultIconUrl)) && (
|
||||
<div
|
||||
className={`edit-game-modal__image-preview edit-game-modal__icon-preview edit-game-modal__drop-zone ${
|
||||
dragOverTarget === 'icon' ? 'edit-game-modal__drop-zone--active' : ''
|
||||
}`}
|
||||
onDragOver={handleDragOver}
|
||||
onDragEnter={(e) => handleDragEnter(e, 'icon')}
|
||||
onDragLeave={handleDragLeave}
|
||||
onDrop={handleIconDrop}
|
||||
>
|
||||
<div className="edit-game-modal__drop-zone-content">
|
||||
<ImageIcon />
|
||||
<span>Drop icon image here</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="edit-game-modal__image-section">
|
||||
<TextField
|
||||
label={t("edit_custom_game_modal_logo")}
|
||||
placeholder={t("edit_custom_game_modal_select_logo")}
|
||||
label={t("edit_game_modal_logo")}
|
||||
placeholder={t("edit_game_modal_select_logo")}
|
||||
value={logoPath}
|
||||
readOnly
|
||||
theme="dark"
|
||||
@@ -386,7 +555,7 @@ export function EditGameModal({
|
||||
disabled={isUpdating}
|
||||
>
|
||||
<ImageIcon />
|
||||
{t("edit_custom_game_modal_browse")}
|
||||
{t("edit_game_modal_browse")}
|
||||
</Button>
|
||||
{game && !isCustomGame(game) && logoPath && (
|
||||
<Button
|
||||
@@ -402,22 +571,55 @@ export function EditGameModal({
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<div className="edit-game-modal__resolution-info">
|
||||
{t("edit_game_modal_logo_resolution")}
|
||||
</div>
|
||||
|
||||
{(logoPath || (game && !isCustomGame(game) && defaultLogoUrl)) && (
|
||||
<div className="edit-game-modal__image-preview">
|
||||
<div
|
||||
className={`edit-game-modal__image-preview ${
|
||||
dragOverTarget === 'logo' ? 'edit-game-modal__drop-zone--active' : ''
|
||||
}`}
|
||||
onDragOver={handleDragOver}
|
||||
onDragEnter={(e) => handleDragEnter(e, 'logo')}
|
||||
onDragLeave={handleDragLeave}
|
||||
onDrop={handleLogoDrop}
|
||||
>
|
||||
<img
|
||||
src={getLogoPreviewUrl()}
|
||||
alt={t("edit_custom_game_modal_logo_preview")}
|
||||
alt={t("edit_game_modal_logo_preview")}
|
||||
className="edit-game-modal__preview-image"
|
||||
/>
|
||||
{dragOverTarget === 'logo' && (
|
||||
<div className="edit-game-modal__drop-overlay">
|
||||
<span>Drop to replace logo</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(!logoPath && !(game && !isCustomGame(game) && defaultLogoUrl)) && (
|
||||
<div
|
||||
className={`edit-game-modal__image-preview edit-game-modal__drop-zone ${
|
||||
dragOverTarget === 'logo' ? 'edit-game-modal__drop-zone--active' : ''
|
||||
}`}
|
||||
onDragOver={handleDragOver}
|
||||
onDragEnter={(e) => handleDragEnter(e, 'logo')}
|
||||
onDragLeave={handleDragLeave}
|
||||
onDrop={handleLogoDrop}
|
||||
>
|
||||
<div className="edit-game-modal__drop-zone-content">
|
||||
<ImageIcon />
|
||||
<span>Drop logo image here</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="edit-game-modal__image-section">
|
||||
<TextField
|
||||
label={t("edit_custom_game_modal_hero")}
|
||||
placeholder={t("edit_custom_game_modal_select_hero")}
|
||||
label={t("edit_game_modal_hero")}
|
||||
placeholder={t("edit_game_modal_select_hero")}
|
||||
value={heroPath}
|
||||
readOnly
|
||||
theme="dark"
|
||||
@@ -430,7 +632,7 @@ export function EditGameModal({
|
||||
disabled={isUpdating}
|
||||
>
|
||||
<ImageIcon />
|
||||
{t("edit_custom_game_modal_browse")}
|
||||
{t("edit_game_modal_browse")}
|
||||
</Button>
|
||||
{game && !isCustomGame(game) && heroPath && (
|
||||
<Button
|
||||
@@ -446,14 +648,47 @@ export function EditGameModal({
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<div className="edit-game-modal__resolution-info">
|
||||
{t("edit_game_modal_hero_resolution")}
|
||||
</div>
|
||||
|
||||
{(heroPath || (game && !isCustomGame(game) && defaultHeroUrl)) && (
|
||||
<div className="edit-game-modal__image-preview">
|
||||
<div
|
||||
className={`edit-game-modal__image-preview ${
|
||||
dragOverTarget === 'hero' ? 'edit-game-modal__drop-zone--active' : ''
|
||||
}`}
|
||||
onDragOver={handleDragOver}
|
||||
onDragEnter={(e) => handleDragEnter(e, 'hero')}
|
||||
onDragLeave={handleDragLeave}
|
||||
onDrop={handleHeroDrop}
|
||||
>
|
||||
<img
|
||||
src={getHeroPreviewUrl()}
|
||||
alt={t("edit_custom_game_modal_hero_preview")}
|
||||
alt={t("edit_game_modal_hero_preview")}
|
||||
className="edit-game-modal__preview-image"
|
||||
/>
|
||||
{dragOverTarget === 'hero' && (
|
||||
<div className="edit-game-modal__drop-overlay">
|
||||
<span>Drop to replace hero image</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(!heroPath && !(game && !isCustomGame(game) && defaultHeroUrl)) && (
|
||||
<div
|
||||
className={`edit-game-modal__image-preview edit-game-modal__drop-zone ${
|
||||
dragOverTarget === 'hero' ? 'edit-game-modal__drop-zone--active' : ''
|
||||
}`}
|
||||
onDragOver={handleDragOver}
|
||||
onDragEnter={(e) => handleDragEnter(e, 'hero')}
|
||||
onDragLeave={handleDragLeave}
|
||||
onDrop={handleHeroDrop}
|
||||
>
|
||||
<div className="edit-game-modal__drop-zone-content">
|
||||
<ImageIcon />
|
||||
<span>Drop hero image here</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -466,7 +701,7 @@ export function EditGameModal({
|
||||
onClick={handleClose}
|
||||
disabled={isUpdating}
|
||||
>
|
||||
{t("edit_custom_game_modal_cancel")}
|
||||
{t("edit_game_modal_cancel")}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
@@ -475,8 +710,8 @@ export function EditGameModal({
|
||||
disabled={!isFormValid || isUpdating}
|
||||
>
|
||||
{isUpdating
|
||||
? t("edit_custom_game_modal_updating")
|
||||
: t("edit_custom_game_modal_update")}
|
||||
? t("edit_game_modal_updating")
|
||||
: t("edit_game_modal_update")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user