From 0d19328798aa4eb429c4776addde0e4ae7699df7 Mon Sep 17 00:00:00 2001 From: caduHD4 Date: Sat, 20 Sep 2025 22:50:16 -0300 Subject: [PATCH 01/15] feat: add context menu for game items - Added ContextMenu and GameContextMenu with actions (play, favorite, manage). - Integrated useGameActions hook and updated SidebarGameItem for right-click menu. - Enhanced game details and RepacksModal (filters, styling, disabled states). --- src/locales/en/translation.json | 10 + src/locales/pt-BR/translation.json | 10 + .../confirm-modal/confirm-modal.scss | 11 + .../confirm-modal/confirm-modal.tsx | 48 ++++ .../components/context-menu/context-menu.scss | 154 +++++++++++ .../components/context-menu/context-menu.tsx | 241 ++++++++++++++++++ .../game-context-menu/game-context-menu.tsx | 219 ++++++++++++++++ .../game-context-menu/use-game-actions.ts | 222 ++++++++++++++++ src/renderer/src/components/index.ts | 3 + .../components/sidebar/sidebar-game-item.tsx | 84 ++++-- .../game-details/game-details.context.tsx | 59 +++++ .../src/context/settings/settings.context.tsx | 8 + .../game-details/hero/hero-panel-actions.scss | 4 + .../game-details/hero/hero-panel-actions.tsx | 32 ++- .../game-details/modals/repacks-modal.scss | 57 +++++ .../game-details/modals/repacks-modal.tsx | 149 ++++++++--- 16 files changed, 1246 insertions(+), 65 deletions(-) create mode 100644 src/renderer/src/components/confirm-modal/confirm-modal.scss create mode 100644 src/renderer/src/components/confirm-modal/confirm-modal.tsx create mode 100644 src/renderer/src/components/context-menu/context-menu.scss create mode 100644 src/renderer/src/components/context-menu/context-menu.tsx create mode 100644 src/renderer/src/components/game-context-menu/game-context-menu.tsx create mode 100644 src/renderer/src/components/game-context-menu/use-game-actions.ts diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 9f8de8f8..945f4cc8 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -102,6 +102,7 @@ "playing_now": "Playing now", "change": "Change", "repacks_modal_description": "Choose the repack you want to download", + "no_repacks_found": "No sources found for this game", "select_folder_hint": "To change the default folder, go to the <0>Settings", "download_now": "Download now", "no_shop_details": "Could not retrieve shop details.", @@ -123,6 +124,8 @@ "remove_from_library_title": "Are you sure?", "remove_from_library_description": "This will remove {{game}} from your library", "options": "Options", + "properties": "Properties", + "filter_by_source": "Filter by source:", "executable_section_title": "Executable", "executable_section_description": "Path of the file that will be executed when \"Play\" is clicked", "downloads_section_title": "Downloads", @@ -136,6 +139,13 @@ "create_shortcut_success": "Shortcut created successfully", "you_might_need_to_restart_steam": "You might need to restart Steam to see the changes", "create_shortcut_error": "Error creating shortcut", + "add_to_favorites": "Add to favorites", + "remove_from_favorites": "Remove from favorites", + "failed_update_favorites": "Failed to update favorites", + "game_removed_from_library": "Game removed from library", + "failed_remove_from_library": "Failed to remove from library", + "files_removed_success": "Files removed successfully", + "failed_remove_files": "Failed to remove files", "nsfw_content_title": "This game contains inappropriate content", "nsfw_content_description": "{{title}} contains content that may not be suitable for all ages. Are you sure you want to continue?", "allow_nsfw_content": "Continue", diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index 22f5b533..b1bd01fa 100644 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -90,6 +90,7 @@ "playing_now": "Jogando agora", "change": "Explorar", "repacks_modal_description": "Escolha o repack do jogo que deseja baixar", + "no_repacks_found": "Nenhuma fonte encontrada para este jogo", "select_folder_hint": "Para trocar o diretório padrão, acesse a <0>Tela de Ajustes", "download_now": "Iniciar download", "no_shop_details": "Não foi possível obter os detalhes da loja.", @@ -108,6 +109,8 @@ "create_shortcut": "Criar atalho na área de trabalho", "remove_files": "Remover arquivos", "options": "Gerenciar", + "properties": "Propriedades", + "filter_by_source": "Filtrar por fonte:", "remove_from_library_description": "Isso irá remover {{game}} da sua biblioteca", "remove_from_library_title": "Tem certeza?", "executable_section_title": "Executável", @@ -190,6 +193,13 @@ "download_error_not_cached_on_hydra": "Este download não está disponível no Nimbus.", "game_removed_from_favorites": "Jogo removido dos favoritos", "game_added_to_favorites": "Jogo adicionado aos favoritos", + "add_to_favorites": "Adicionar aos favoritos", + "remove_from_favorites": "Remover dos favoritos", + "failed_update_favorites": "Falha ao atualizar favoritos", + "game_removed_from_library": "Jogo removido da biblioteca", + "failed_remove_from_library": "Falha ao remover da biblioteca", + "files_removed_success": "Arquivos removidos com sucesso", + "failed_remove_files": "Falha ao remover arquivos", "automatically_extract_downloaded_files": "Extrair automaticamente os arquivos baixados", "create_start_menu_shortcut": "Criar atalho no Menu Iniciar", "invalid_wine_prefix_path": "Caminho do prefixo Wine inválido", diff --git a/src/renderer/src/components/confirm-modal/confirm-modal.scss b/src/renderer/src/components/confirm-modal/confirm-modal.scss new file mode 100644 index 00000000..e5bda187 --- /dev/null +++ b/src/renderer/src/components/confirm-modal/confirm-modal.scss @@ -0,0 +1,11 @@ +@use "../../scss/globals.scss"; + +.confirm-modal { + &__actions { + display: flex; + width: 100%; + justify-content: flex-end; + align-items: center; + gap: globals.$spacing-unit; + } +} diff --git a/src/renderer/src/components/confirm-modal/confirm-modal.tsx b/src/renderer/src/components/confirm-modal/confirm-modal.tsx new file mode 100644 index 00000000..d210c035 --- /dev/null +++ b/src/renderer/src/components/confirm-modal/confirm-modal.tsx @@ -0,0 +1,48 @@ +import { useTranslation } from "react-i18next"; +import { Button, Modal } from "@renderer/components"; +import "./confirm-modal.scss"; + +export interface ConfirmModalProps { + visible: boolean; + title: string; + description?: string; + onClose: () => void; + onConfirm: () => Promise | void; + confirmLabel?: string; + cancelLabel?: string; + confirmTheme?: "primary" | "outline" | "danger"; + confirmDisabled?: boolean; +} + +export function ConfirmModal({ + visible, + title, + description, + onClose, + onConfirm, + confirmLabel, + cancelLabel, + confirmTheme = "outline", + confirmDisabled = false, +}: ConfirmModalProps) { + const { t } = useTranslation(); + + const handleConfirm = async () => { + await onConfirm(); + onClose(); + }; + + return ( + +
+ + + +
+
+ ); +} diff --git a/src/renderer/src/components/context-menu/context-menu.scss b/src/renderer/src/components/context-menu/context-menu.scss new file mode 100644 index 00000000..2c066440 --- /dev/null +++ b/src/renderer/src/components/context-menu/context-menu.scss @@ -0,0 +1,154 @@ +@use "../../scss/globals.scss"; + +.context-menu { + position: fixed; + z-index: 1000; + background-color: globals.$background-color; + border: 1px solid globals.$border-color; + border-radius: 6px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4); + padding: 4px 0; + min-width: 180px; + backdrop-filter: blur(8px); + + &__list { + list-style: none; + margin: 0; + padding: 0; + } + + &__item-container { + position: relative; + padding-right: 8px; + } + + &__item { + width: 100%; + display: flex; + align-items: center; + gap: calc(globals.$spacing-unit * 1.5); + padding: 8px 12px; + background: transparent; + border: none; + color: globals.$body-color; + cursor: pointer; + font-size: globals.$body-font-size; + text-align: left; + transition: background-color 0.15s ease; + + &:hover:not(&--disabled) { + background-color: rgba(255, 255, 255, 0.1); + } + + &:active:not(&--disabled) { + background-color: rgba(255, 255, 255, 0.15); + } + + &--disabled { + color: globals.$muted-color; + cursor: not-allowed; + opacity: 0.6; + } + + &--danger { + color: globals.$danger-color; + + &:hover:not(.context-menu__item--disabled) { + background-color: rgba(128, 29, 30, 0.1); + } + + .context-menu__item-icon { + color: globals.$danger-color; + } + } + + &--has-submenu { + position: relative; + + &:hover { + background-color: rgba(255, 255, 255, 0.1); + } + } + + &--active { + background-color: rgba(255, 255, 255, 0.1); + } + } + + &__item-icon { + display: flex; + align-items: center; + justify-content: center; + width: 16px; + height: 16px; + flex-shrink: 0; + } + + &__item-label { + flex: 1; + white-space: nowrap; + } + + &__item-arrow { + font-size: 10px; + color: globals.$muted-color; + margin-left: auto; + } + + &__submenu { + position: absolute; + left: calc(100% - 2px); + top: 0; + background-color: globals.$background-color; + border: 1px solid globals.$border-color; + border-radius: 6px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4); + padding: 4px 0; + min-width: 160px; + backdrop-filter: blur(8px); + margin-left: 0; + z-index: 1200; + + pointer-events: auto; + + max-height: 60vh; + overflow-y: auto; + } + + &__content { + border-top: 1px solid globals.$border-color; + padding: 8px 12px; + margin-top: 4px; + } + + &__item + &__item { + border-top: 1px solid transparent; + } + + &__item--danger:first-of-type { + border-top: 1px solid globals.$border-color; + margin-top: 4px; + } + + &__separator { + height: 1px; + background: globals.$border-color; + margin: 6px 8px; + border-radius: 1px; + } +} + +.context-menu { + animation: contextMenuFadeIn 0.15s ease-out; +} + +@keyframes contextMenuFadeIn { + from { + opacity: 0; + transform: scale(0.95) translateY(-4px); + } + to { + opacity: 1; + transform: scale(1) translateY(0); + } +} \ No newline at end of file diff --git a/src/renderer/src/components/context-menu/context-menu.tsx b/src/renderer/src/components/context-menu/context-menu.tsx new file mode 100644 index 00000000..f1c2950e --- /dev/null +++ b/src/renderer/src/components/context-menu/context-menu.tsx @@ -0,0 +1,241 @@ +import React, { useEffect, useRef, useState } from "react"; +import { createPortal } from "react-dom"; +import cn from "classnames"; +import "./context-menu.scss"; + +export interface ContextMenuItemData { + id: string; + label: string; + icon?: React.ReactNode; + onClick?: () => void; + disabled?: boolean; + danger?: boolean; + separator?: boolean; + submenu?: ContextMenuItemData[]; +} + +export interface ContextMenuProps { + items: ContextMenuItemData[]; + visible: boolean; + position: { x: number; y: number }; + onClose: () => void; + children?: React.ReactNode; +} + +export function ContextMenu({ + items, + visible, + position, + onClose, + children, +}: ContextMenuProps) { + const menuRef = useRef(null); + const [adjustedPosition, setAdjustedPosition] = useState(position); + const [activeSubmenu, setActiveSubmenu] = useState(null); + const submenuCloseTimeout = useRef(null); + const itemRefs = useRef>({}); + const [submenuStyles, setSubmenuStyles] = useState>({}); + + useEffect(() => { + if (!visible) return; + + const handleClickOutside = (event: MouseEvent) => { + if (menuRef.current && !menuRef.current.contains(event.target as Node)) { + onClose(); + } + }; + + const handleEscape = (event: KeyboardEvent) => { + if (event.key === "Escape") { + onClose(); + } + }; + + document.addEventListener("mousedown", handleClickOutside); + document.addEventListener("keydown", handleEscape); + + return () => { + document.removeEventListener("mousedown", handleClickOutside); + document.removeEventListener("keydown", handleEscape); + }; + }, [visible, onClose]); + + useEffect(() => { + if (!visible || !menuRef.current) return; + + const rect = menuRef.current.getBoundingClientRect(); + const viewportWidth = window.innerWidth; + const viewportHeight = window.innerHeight; + + let adjustedX = position.x; + let adjustedY = position.y; + + if (position.x + rect.width > viewportWidth) { + adjustedX = viewportWidth - rect.width - 10; + } + + if (position.y + rect.height > viewportHeight) { + adjustedY = viewportHeight - rect.height - 10; + } + + setAdjustedPosition({ x: adjustedX, y: adjustedY }); + }, [visible, position]); + + useEffect(() => { + if (!visible) { + setActiveSubmenu(null); + } + }, [visible]); + + const handleItemClick = (item: ContextMenuItemData) => { + if (item.disabled) return; + + if (item.submenu) { + setActiveSubmenu(activeSubmenu === item.id ? null : item.id); + return; + } + + if (item.onClick) { + item.onClick(); + onClose(); + } + }; + + const handleSubmenuMouseEnter = (itemId: string) => { + if (submenuCloseTimeout.current) { + window.clearTimeout(submenuCloseTimeout.current); + submenuCloseTimeout.current = null; + } + setActiveSubmenu(itemId); + }; + + const handleSubmenuMouseLeave = () => { + if (submenuCloseTimeout.current) { + window.clearTimeout(submenuCloseTimeout.current); + } + submenuCloseTimeout.current = window.setTimeout(() => { + setActiveSubmenu(null); + submenuCloseTimeout.current = null; + }, 120); + }; + + useEffect(() => { + if (!activeSubmenu) return; + + const parentEl = itemRefs.current[activeSubmenu]; + if (!parentEl) return; + + const submenuEl = parentEl.querySelector(".context-menu__submenu") as HTMLElement | null; + if (!submenuEl) return; + + const parentRect = parentEl.getBoundingClientRect(); + const submenuWidth = submenuEl.offsetWidth; + const submenuHeight = submenuEl.offsetHeight; + const viewportWidth = window.innerWidth; + const viewportHeight = window.innerHeight; + + const styles: React.CSSProperties = {}; + + if (parentRect.right + submenuWidth > viewportWidth - 8) { + styles.left = "auto"; + styles.right = "calc(100% - 2px)"; + } else { + styles.left = "calc(100% - 2px)"; + styles.right = undefined; + } + + const overflowBottom = parentRect.top + submenuHeight - viewportHeight; + if (overflowBottom > 0) { + const topAdjust = Math.min(overflowBottom + 8, parentRect.top - 8); + styles.top = `${-topAdjust}px`; + } else { + styles.top = undefined; + } + + setSubmenuStyles((prev) => ({ ...prev, [activeSubmenu]: styles })); + }, [activeSubmenu]); + + if (!visible) return null; + + const menuContent = ( +
+
    + {items.map((item) => ( +
  • (itemRefs.current[item.id] = el)} + className="context-menu__item-container" + onMouseEnter={() => item.submenu && handleSubmenuMouseEnter(item.id)} + onMouseLeave={() => item.submenu && handleSubmenuMouseLeave()} + > + {item.separator &&
    } + + + {item.submenu && activeSubmenu === item.id && ( +
    handleSubmenuMouseEnter(item.id)} + onMouseLeave={() => handleSubmenuMouseLeave()} + > +
      + {item.submenu.map((subItem) => ( +
    • + {subItem.separator &&
      } + +
    • + ))} +
    +
    + )} +
  • + ))} +
+ {children &&
{children}
} +
+ ); + + return createPortal(menuContent, document.body); +} \ No newline at end of file diff --git a/src/renderer/src/components/game-context-menu/game-context-menu.tsx b/src/renderer/src/components/game-context-menu/game-context-menu.tsx new file mode 100644 index 00000000..8b179ea5 --- /dev/null +++ b/src/renderer/src/components/game-context-menu/game-context-menu.tsx @@ -0,0 +1,219 @@ +import { useTranslation } from "react-i18next"; +import { useState } from "react"; +import { + PlayIcon, + DownloadIcon, + HeartIcon, + HeartFillIcon, + GearIcon, + PencilIcon, + FileDirectoryIcon, + LinkIcon, + TrashIcon, + XIcon, +} from "@primer/octicons-react"; +import SteamLogo from "@renderer/assets/steam-logo.svg?react"; +import { LibraryGame } from "@types"; +import { ContextMenu, ContextMenuItemData, ContextMenuProps } from ".."; +import { ConfirmModal } from "@renderer/components/confirm-modal/confirm-modal"; +import { useGameActions } from ".."; + +interface GameContextMenuProps extends Omit { + game: LibraryGame; +} + +export function GameContextMenu({ + game, + visible, + position, + onClose, +}: GameContextMenuProps) { + const { t } = useTranslation("game_details"); + const [showConfirmRemoveLibrary, setShowConfirmRemoveLibrary] = useState(false); + const [showConfirmRemoveFiles, setShowConfirmRemoveFiles] = useState(false); + const { + canPlay, + isDeleting, + isGameDownloading, + hasRepacks, + shouldShowCreateStartMenuShortcut, + handlePlayGame, + handleToggleFavorite, + handleCreateShortcut, + handleCreateSteamShortcut, + handleOpenFolder, + handleOpenDownloadOptions, + handleOpenDownloadLocation, + handleRemoveFromLibrary, + handleRemoveFiles, + handleOpenGameOptions, + } = useGameActions(game); + + const items: ContextMenuItemData[] = [ + { + id: "play", + label: canPlay ? t("play") : t("download"), + icon: canPlay ? : , + onClick: handlePlayGame, + disabled: isDeleting, + }, + { + id: "favorite", + label: game.favorite ? t("remove_from_favorites") : t("add_to_favorites"), + icon: game.favorite ? : , + onClick: handleToggleFavorite, + disabled: isDeleting, + }, + ...(game.executablePath + ? [ + { + id: "shortcuts", + label: t("create_shortcut"), + icon: , + disabled: isDeleting, + submenu: [ + { + id: "desktop-shortcut", + label: t("create_shortcut"), + icon: , + onClick: () => handleCreateShortcut("desktop"), + disabled: isDeleting, + }, + { + id: "steam-shortcut", + label: t("create_steam_shortcut"), + icon: , + onClick: handleCreateSteamShortcut, + disabled: isDeleting, + }, + ...(shouldShowCreateStartMenuShortcut + ? [ + { + id: "start-menu-shortcut", + label: t("create_start_menu_shortcut"), + icon: , + onClick: () => handleCreateShortcut("start_menu"), + disabled: isDeleting, + }, + ] + : []), + ], + }, + ] + : []), + + { + id: "manage", + label: t("options"), + icon: , + disabled: isDeleting, + submenu: [ + ...(game.executablePath + ? [ + { + id: "open-folder", + label: t("open_folder"), + icon: , + onClick: handleOpenFolder, + disabled: isDeleting, + }, + ] + : []), + ...(game.executablePath + ? [ + { + id: "download-options", + label: t("open_download_options"), + icon: , + onClick: handleOpenDownloadOptions, + disabled: isDeleting || isGameDownloading || !hasRepacks, + }, + ] + : []), + ...(game.download?.downloadPath + ? [ + { + id: "download-location", + label: t("open_download_location"), + icon: , + onClick: handleOpenDownloadLocation, + disabled: isDeleting, + }, + ] + : []), + + { + id: "remove-library", + label: t("remove_from_library"), + icon: , + onClick: () => setShowConfirmRemoveLibrary(true), + disabled: isDeleting, + danger: true, + }, + ...(game.download?.downloadPath + ? [ + { + id: "remove-files", + label: t("remove_files"), + icon: , + onClick: () => setShowConfirmRemoveFiles(true), + disabled: isDeleting || isGameDownloading, + danger: true, + }, + ] + : []), + ], + }, + { + id: "properties", + label: t("properties"), + separator: true, + icon: , + onClick: () => handleOpenGameOptions(), + disabled: isDeleting, + }, + ]; + + return ( + <> + + + { + setShowConfirmRemoveLibrary(false); + onClose(); + }} + onConfirm={async () => { + await handleRemoveFromLibrary(); + }} + confirmLabel={t("remove")} + cancelLabel={t("cancel")} + confirmTheme="danger" + /> + + { + setShowConfirmRemoveFiles(false); + onClose(); + }} + onConfirm={async () => { + await handleRemoveFiles(); + }} + confirmLabel={t("remove")} + cancelLabel={t("cancel")} + confirmTheme="danger" + /> + + ); +} diff --git a/src/renderer/src/components/game-context-menu/use-game-actions.ts b/src/renderer/src/components/game-context-menu/use-game-actions.ts new file mode 100644 index 00000000..2c865126 --- /dev/null +++ b/src/renderer/src/components/game-context-menu/use-game-actions.ts @@ -0,0 +1,222 @@ +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { LibraryGame, ShortcutLocation } from "@types"; +import { useDownload, useLibrary, useToast } from "@renderer/hooks"; +import { useNavigate, useLocation } from "react-router-dom"; +import { buildGameDetailsPath } from "@renderer/helpers"; +import { logger } from "@renderer/logger"; + +export function useGameActions(game: LibraryGame) { + const { t } = useTranslation("game_details"); + const { showSuccessToast, showErrorToast } = useToast(); + const { updateLibrary } = useLibrary(); + const navigate = useNavigate(); + const location = useLocation(); + const { + removeGameInstaller, + removeGameFromLibrary, + isGameDeleting, + lastPacket, + cancelDownload + } = useDownload(); + + const [creatingSteamShortcut, setCreatingSteamShortcut] = useState(false); + + const canPlay = Boolean(game.executablePath); + const isDeleting = isGameDeleting(game.id); + const isGameDownloading = game.download?.status === "active" && lastPacket?.gameId === game.id; + const hasRepacks = true; + const shouldShowCreateStartMenuShortcut = window.electron.platform === "win32"; + + const handlePlayGame = async () => { + if (!canPlay) { + const path = buildGameDetailsPath({ + ...game, + objectId: game.objectId, + }); + if (location.pathname === path) { + try { + window.dispatchEvent( + new CustomEvent("hydra:openRepacks", { detail: { objectId: game.objectId } }) + ); + } catch (e) {} + } else { + navigate(path, { state: { openRepacks: true } }); + + try { + window.dispatchEvent( + new CustomEvent("hydra:openRepacks", { detail: { objectId: game.objectId } }) + ); + } catch (e) {} + } + return; + } + + try { + await window.electron.openGame( + game.shop, + game.objectId, + game.executablePath!, + game.launchOptions + ); + } catch (error) { + showErrorToast("Failed to start game"); + logger.error("Failed to start game", error); + } + }; + + const handleToggleFavorite = async () => { + try { + if (game.favorite) { + await window.electron.removeGameFromFavorites(game.shop, game.objectId); + showSuccessToast(t("game_removed_from_favorites")); + } else { + await window.electron.addGameToFavorites(game.shop, game.objectId); + showSuccessToast(t("game_added_to_favorites")); + } + updateLibrary(); + try { + window.dispatchEvent(new CustomEvent("hydra:game-favorite-toggled", { detail: { shop: game.shop, objectId: game.objectId } })); + } catch (e) {} + } catch (error) { + showErrorToast(t("failed_update_favorites")); + logger.error("Failed to toggle favorite", error); + } + }; + + const handleCreateShortcut = async (location: ShortcutLocation) => { + try { + const success = await window.electron.createGameShortcut( + game.shop, + game.objectId, + location + ); + + if (success) { + showSuccessToast(t("create_shortcut_success")); + } else { + showErrorToast(t("create_shortcut_error")); + } + } catch (error) { + showErrorToast(t("create_shortcut_error")); + logger.error("Failed to create shortcut", error); + } + }; + + const handleCreateSteamShortcut = async () => { + try { + setCreatingSteamShortcut(true); + await window.electron.createSteamShortcut(game.shop, game.objectId); + + showSuccessToast( + t("create_shortcut_success"), + t("you_might_need_to_restart_steam") + ); + } catch (error) { + logger.error("Failed to create Steam shortcut", error); + showErrorToast(t("create_shortcut_error")); + } finally { + setCreatingSteamShortcut(false); + } + }; + + const handleOpenFolder = async () => { + try { + await window.electron.openGameExecutablePath(game.shop, game.objectId); + } catch (error) { + showErrorToast("Failed to open folder"); + logger.error("Failed to open folder", error); + } + }; + + const handleOpenDownloadOptions = () => { + const path = buildGameDetailsPath({ + ...game, + objectId: game.objectId, + }); + navigate(path, { state: { openRepacks: true } }); + + try { + window.dispatchEvent( + new CustomEvent("hydra:openRepacks", { detail: { objectId: game.objectId } }) + ); + } catch (e) { + } + }; + + const handleOpenGameOptions = () => { + const path = buildGameDetailsPath({ + ...game, + objectId: game.objectId, + }); + + navigate(path, { state: { openGameOptions: true } }); + + try { + window.dispatchEvent( + new CustomEvent("hydra:openGameOptions", { detail: { objectId: game.objectId } }) + ); + } catch (e) { + } + }; + + const handleOpenDownloadLocation = async () => { + try { + await window.electron.openGameInstallerPath(game.shop, game.objectId); + } catch (error) { + showErrorToast("Failed to open download location"); + logger.error("Failed to open download location", error); + } + }; + + const handleRemoveFromLibrary = async () => { + try { + if (isGameDownloading) { + await cancelDownload(game.shop, game.objectId); + } + + await removeGameFromLibrary(game.shop, game.objectId); + updateLibrary(); + showSuccessToast(t("game_removed_from_library")); + try { + window.dispatchEvent(new CustomEvent("hydra:game-removed-from-library", { detail: { shop: game.shop, objectId: game.objectId } })); + } catch (e) {} + } catch (error) { + showErrorToast(t("failed_remove_from_library")); + logger.error("Failed to remove from library", error); + } + }; + + const handleRemoveFiles = async () => { + try { + await removeGameInstaller(game.shop, game.objectId); + updateLibrary(); + showSuccessToast(t("files_removed_success")); + try { + window.dispatchEvent(new CustomEvent("hydra:game-files-removed", { detail: { shop: game.shop, objectId: game.objectId } })); + } catch (e) {} + } catch (error) { + showErrorToast(t("failed_remove_files")); + logger.error("Failed to remove files", error); + } + }; + + return { + canPlay, + isDeleting, + isGameDownloading, + hasRepacks, + shouldShowCreateStartMenuShortcut, + creatingSteamShortcut, + handlePlayGame, + handleToggleFavorite, + handleCreateShortcut, + handleCreateSteamShortcut, + handleOpenFolder, + handleOpenDownloadOptions, + handleOpenDownloadLocation, + handleRemoveFromLibrary, + handleRemoveFiles, + handleOpenGameOptions, + }; +} \ No newline at end of file diff --git a/src/renderer/src/components/index.ts b/src/renderer/src/components/index.ts index 8373e0dc..9970be42 100644 --- a/src/renderer/src/components/index.ts +++ b/src/renderer/src/components/index.ts @@ -15,3 +15,6 @@ export * from "./badge/badge"; export * from "./confirmation-modal/confirmation-modal"; export * from "./suspense-wrapper/suspense-wrapper"; export * from "./debrid-badge/debrid-badge"; +export * from "./context-menu/context-menu"; +export * from "./game-context-menu/game-context-menu"; +export * from "./game-context-menu/use-game-actions"; diff --git a/src/renderer/src/components/sidebar/sidebar-game-item.tsx b/src/renderer/src/components/sidebar/sidebar-game-item.tsx index 0672f847..37ef91c3 100644 --- a/src/renderer/src/components/sidebar/sidebar-game-item.tsx +++ b/src/renderer/src/components/sidebar/sidebar-game-item.tsx @@ -2,6 +2,8 @@ import SteamLogo from "@renderer/assets/steam-logo.svg?react"; import { LibraryGame } from "@types"; import cn from "classnames"; import { useLocation } from "react-router-dom"; +import { useState } from "react"; +import { GameContextMenu } from ".."; interface SidebarGameItemProps { game: LibraryGame; @@ -15,36 +17,64 @@ export function SidebarGameItem({ getGameTitle, }: Readonly) { const location = useLocation(); + const [contextMenu, setContextMenu] = useState<{ + visible: boolean; + position: { x: number; y: number }; + }>({ visible: false, position: { x: 0, y: 0 } }); + + const handleContextMenu = (event: React.MouseEvent) => { + event.preventDefault(); + event.stopPropagation(); + + setContextMenu({ + visible: true, + position: { x: event.clientX, y: event.clientY }, + }); + }; + + const handleCloseContextMenu = () => { + setContextMenu({ visible: false, position: { x: 0, y: 0 } }); + }; return ( -
  • - -
  • + + {getGameTitle(game)} + + + + + + ); } diff --git a/src/renderer/src/context/game-details/game-details.context.tsx b/src/renderer/src/context/game-details/game-details.context.tsx index ce2923b2..edfc503b 100644 --- a/src/renderer/src/context/game-details/game-details.context.tsx +++ b/src/renderer/src/context/game-details/game-details.context.tsx @@ -26,6 +26,7 @@ import type { } from "@types"; import { useTranslation } from "react-i18next"; +import { useLocation } from "react-router-dom"; import { GameDetailsContext } from "./game-details.context.types"; import { SteamContentDescriptor } from "@shared"; @@ -94,6 +95,7 @@ export function GameDetailsContextProvider({ }, [getRepacksForObjectId, objectId]); const { i18n } = useTranslation("game_details"); + const location = useLocation(); const dispatch = useAppDispatch(); @@ -201,6 +203,16 @@ export function GameDetailsContextProvider({ dispatch(setHeaderTitle(gameTitle)); }, [objectId, gameTitle, dispatch]); + useEffect(() => { + const state: any = (location && (location.state as any)) || {}; + if (state.openRepacks) { + setShowRepacksModal(true); + try { + window.history.replaceState({}, document.title, location.pathname); + } catch (_e) {} + } + }, [location]); + useEffect(() => { const unsubscribe = window.electron.onGamesRunning((gamesIds) => { const updatedIsGameRunning = @@ -219,6 +231,53 @@ export function GameDetailsContextProvider({ }; }, [game?.id, isGameRunning, updateGame]); + useEffect(() => { + const handler = (ev: Event) => { + try { + const detail = (ev as CustomEvent).detail || {}; + if (detail.objectId && detail.objectId === objectId) { + setShowRepacksModal(true); + } + } catch (e) { + } + }; + + window.addEventListener("hydra:openRepacks", handler as EventListener); + + return () => { + window.removeEventListener("hydra:openRepacks", handler as EventListener); + }; + }, [objectId]); + + useEffect(() => { + const handler = (ev: Event) => { + try { + const detail = (ev as CustomEvent).detail || {}; + if (detail.objectId && detail.objectId === objectId) { + setShowGameOptionsModal(true); + } + } catch (e) { + } + }; + + window.addEventListener("hydra:openGameOptions", handler as EventListener); + + return () => { + window.removeEventListener("hydra:openGameOptions", handler as EventListener); + }; + }, [objectId]); + + useEffect(() => { + const state: any = (location && (location.state as any)) || {}; + if (state.openGameOptions) { + setShowGameOptionsModal(true); + + try { + window.history.replaceState({}, document.title, location.pathname); + } catch (_e) {} + } + }, [location]); + const lastDownloadedOption = useMemo(() => { if (game?.download) { const repack = repacks.find((repack) => diff --git a/src/renderer/src/context/settings/settings.context.tsx b/src/renderer/src/context/settings/settings.context.tsx index 61a3fdb7..5c79f38d 100644 --- a/src/renderer/src/context/settings/settings.context.tsx +++ b/src/renderer/src/context/settings/settings.context.tsx @@ -63,6 +63,7 @@ export function SettingsContextProvider({ const [searchParams] = useSearchParams(); const defaultSourceUrl = searchParams.get("urls"); + const defaultTab = searchParams.get("tab"); const defaultAppearanceTheme = searchParams.get("theme"); const defaultAppearanceAuthorId = searchParams.get("authorId"); const defaultAppearanceAuthorName = searchParams.get("authorName"); @@ -77,6 +78,13 @@ export function SettingsContextProvider({ } }, [defaultSourceUrl]); + useEffect(() => { + if (defaultTab) { + const idx = Number(defaultTab); + if (!Number.isNaN(idx)) setCurrentCategoryIndex(idx); + } + }, [defaultTab]); + useEffect(() => { if (appearance.theme) setCurrentCategoryIndex(3); }, [appearance.theme]); diff --git a/src/renderer/src/pages/game-details/hero/hero-panel-actions.scss b/src/renderer/src/pages/game-details/hero/hero-panel-actions.scss index 1f258ced..f8313370 100644 --- a/src/renderer/src/pages/game-details/hero/hero-panel-actions.scss +++ b/src/renderer/src/pages/game-details/hero/hero-panel-actions.scss @@ -3,6 +3,10 @@ .hero-panel-actions { &__action { border: solid 1px globals.$muted-color; + + &--disabled { + opacity: 0.5; + } } &__container { diff --git a/src/renderer/src/pages/game-details/hero/hero-panel-actions.tsx b/src/renderer/src/pages/game-details/hero/hero-panel-actions.tsx index a3b75d2e..8b86af79 100644 --- a/src/renderer/src/pages/game-details/hero/hero-panel-actions.tsx +++ b/src/renderer/src/pages/game-details/hero/hero-panel-actions.tsx @@ -13,6 +13,7 @@ import { useTranslation } from "react-i18next"; import { gameDetailsContext } from "@renderer/context"; import "./hero-panel-actions.scss"; +import { useEffect } from "react"; export function HeroPanelActions() { const [toggleLibraryGameDisabled, setToggleLibraryGameDisabled] = @@ -44,6 +45,33 @@ export function HeroPanelActions() { const { t } = useTranslation("game_details"); + useEffect(() => { + const onFavoriteToggled = () => { + updateLibrary(); + updateGame(); + }; + + const onGameRemoved = () => { + updateLibrary(); + updateGame(); + }; + + const onFilesRemoved = () => { + updateLibrary(); + updateGame(); + }; + + window.addEventListener("hydra:game-favorite-toggled", onFavoriteToggled as EventListener); + window.addEventListener("hydra:game-removed-from-library", onGameRemoved as EventListener); + window.addEventListener("hydra:game-files-removed", onFilesRemoved as EventListener); + + return () => { + window.removeEventListener("hydra:game-favorite-toggled", onFavoriteToggled as EventListener); + window.removeEventListener("hydra:game-removed-from-library", onGameRemoved as EventListener); + window.removeEventListener("hydra:game-files-removed", onFilesRemoved as EventListener); + }; + }, [updateLibrary, updateGame]); + const addGameToLibrary = async () => { setToggleLibraryGameDisabled(true); @@ -166,8 +194,8 @@ export function HeroPanelActions() { + + + + ) : ( + filteredRepacks.map((repack) => { + const isLastDownloadedOption = checkIfLastDownloadedOption(repack); - return ( - - ); - })} + {hashesInDebrid[getHashFromMagnet(repack.uris[0]) ?? ""] && ( + + )} + + ); + }) + )} From ca6331e45b46e1648b781ffc0da86d5c4f2544b8 Mon Sep 17 00:00:00 2001 From: caduHD4 Date: Sun, 21 Sep 2025 01:48:22 -0300 Subject: [PATCH 02/15] fix: improve context menu shortcut styling --- src/locales/en/translation.json | 1 + src/locales/pt-BR/translation.json | 1 + .../components/context-menu/context-menu.scss | 5 ++ .../components/context-menu/context-menu.tsx | 4 +- .../game-context-menu/game-context-menu.tsx | 3 +- .../game-details/modals/repacks-modal.scss | 63 +++++++++++++------ .../game-details/modals/repacks-modal.tsx | 23 ++++--- 7 files changed, 71 insertions(+), 29 deletions(-) diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 945f4cc8..c79741c3 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -119,6 +119,7 @@ "open_folder": "Open folder", "open_download_location": "See downloaded files", "create_shortcut": "Create desktop shortcut", + "create_shortcut_simple": "Create shortcut", "clear": "Clear", "remove_files": "Remove files", "remove_from_library_title": "Are you sure?", diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index b1bd01fa..076b18fa 100644 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -107,6 +107,7 @@ "open_folder": "Abrir pasta", "open_download_location": "Ver arquivos baixados", "create_shortcut": "Criar atalho na área de trabalho", + "create_shortcut_simple": "Criar atalho", "remove_files": "Remover arquivos", "options": "Gerenciar", "properties": "Propriedades", diff --git a/src/renderer/src/components/context-menu/context-menu.scss b/src/renderer/src/components/context-menu/context-menu.scss index 2c066440..e8ac6593 100644 --- a/src/renderer/src/components/context-menu/context-menu.scss +++ b/src/renderer/src/components/context-menu/context-menu.scss @@ -130,6 +130,11 @@ margin-top: 4px; } + &--game-not-installed &__submenu &__item--danger:first-of-type { + border-top: none; + margin-top: 0; + } + &__separator { height: 1px; background: globals.$border-color; diff --git a/src/renderer/src/components/context-menu/context-menu.tsx b/src/renderer/src/components/context-menu/context-menu.tsx index f1c2950e..ebbc6b70 100644 --- a/src/renderer/src/components/context-menu/context-menu.tsx +++ b/src/renderer/src/components/context-menu/context-menu.tsx @@ -20,6 +20,7 @@ export interface ContextMenuProps { position: { x: number; y: number }; onClose: () => void; children?: React.ReactNode; + className?: string; } export function ContextMenu({ @@ -28,6 +29,7 @@ export function ContextMenu({ position, onClose, children, + className, }: ContextMenuProps) { const menuRef = useRef(null); const [adjustedPosition, setAdjustedPosition] = useState(position); @@ -160,7 +162,7 @@ export function ContextMenu({ const menuContent = (
    , disabled: isDeleting, submenu: [ @@ -181,6 +181,7 @@ export function GameContextMenu({ visible={visible} position={position} onClose={onClose} + className={!game.executablePath ? "context-menu--game-not-installed" : undefined} />

    {t("filter_by_source")}

    - {downloadSources.map((source) => ( -
    - toggleFingerprint(source.fingerprint)} - /> -
    - ))} +
    + {downloadSources.map((source) => ( +
    + toggleFingerprint(source.fingerprint)} + /> +
    + ))} +
    From 3ff8ed735e386854e44c06b7733a76b844970d74 Mon Sep 17 00:00:00 2001 From: caduHD4 Date: Sun, 21 Sep 2025 16:22:04 -0300 Subject: [PATCH 03/15] feat: add new translations for properties and favorites in multiple languages --- src/locales/ca/translation.json | 10 ++++++++++ src/locales/cs/translation.json | 10 ++++++++++ src/locales/da/translation.json | 10 ++++++++++ src/locales/de/translation.json | 12 +++++++++++- src/locales/es/translation.json | 11 ++++++++++- src/locales/et/translation.json | 12 +++++++++++- src/locales/fa/translation.json | 12 +++++++++++- src/locales/fr/translation.json | 10 ++++++++++ src/locales/hu/translation.json | 10 ++++++++++ src/locales/id/translation.json | 11 ++++++++++- src/locales/it/translation.json | 12 +++++++++++- src/locales/kk/translation.json | 12 +++++++++++- src/locales/ko/translation.json | 10 ++++++++++ src/locales/nb/translation.json | 12 +++++++++++- src/locales/nl/translation.json | 10 ++++++++++ src/locales/pl/translation.json | 10 ++++++++++ src/locales/pt-PT/translation.json | 10 ++++++++++ src/locales/ro/translation.json | 10 ++++++++++ src/locales/ru/translation.json | 10 ++++++++++ src/locales/sv/translation.json | 10 ++++++++++ src/locales/tr/translation.json | 10 ++++++++++ src/locales/uk/translation.json | 10 ++++++++++ src/locales/uz/translation.json | 10 ++++++++++ src/locales/zh/translation.json | 10 ++++++++++ 24 files changed, 246 insertions(+), 8 deletions(-) diff --git a/src/locales/ca/translation.json b/src/locales/ca/translation.json index aa69001f..e397e9f1 100644 --- a/src/locales/ca/translation.json +++ b/src/locales/ca/translation.json @@ -50,6 +50,16 @@ "download_options_zero": "No hi ha opcions de baixada", "download_options_one": "{{count}} opció de baixada", "download_options_other": "{{count}} opcions de baixada", + "no_repacks_found": "No s'han trobat fonts per a aquest joc", + "properties": "Propietats", + "filter_by_source": "Filtra per font:", + "add_to_favorites": "Afegeix als favorits", + "remove_from_favorites": "Elimina dels favorits", + "failed_update_favorites": "No s'han pogut actualitzar els favorits", + "game_removed_from_library": "El joc s'ha eliminat de la biblioteca", + "failed_remove_from_library": "No s'ha pogut eliminar de la biblioteca", + "files_removed_success": "Fitxers eliminats correctament", + "failed_remove_files": "No s'han pogut eliminar els fitxers", "updated_at": "Actualitzat: {{updated_at}}", "install": "Instal·la", "resume": "Reprèn", diff --git a/src/locales/cs/translation.json b/src/locales/cs/translation.json index 9b501b54..57d0c25d 100644 --- a/src/locales/cs/translation.json +++ b/src/locales/cs/translation.json @@ -75,6 +75,16 @@ "eta": "Odhadovaný čas: {{eta}}", "calculating_eta": "Počítám zbývající čas…", "downloading_metadata": "Stahuji metadata…", + "properties": "Vlastnosti", + "filter_by_source": "Filtrovat podle zdroje:", + "add_to_favorites": "Přidat do oblíbených", + "remove_from_favorites": "Odebrat z oblíbených", + "failed_update_favorites": "Nepodařilo se aktualizovat oblíbené", + "game_removed_from_library": "Hra byla odebrána z knihovny", + "failed_remove_from_library": "Nepodařilo se odebrat z knihovny", + "files_removed_success": "Soubory byly úspěšně odebrány", + "failed_remove_files": "Nepodařilo se odebrat soubory", + "no_repacks_found": "Nebyly nalezeny žádné zdroje pro tuto hru", "filter": "Filtrovat repacky", "requirements": "Systémové požadavky", "minimum": "Minimální", diff --git a/src/locales/da/translation.json b/src/locales/da/translation.json index 618f085c..14306368 100644 --- a/src/locales/da/translation.json +++ b/src/locales/da/translation.json @@ -54,6 +54,16 @@ "download_options_zero": "Ingen download mulighed", "download_options_one": "{{count}} download mulighed", "download_options_other": "{{count}} download muligheder", + "properties": "Egenskaber", + "filter_by_source": "Filtrér efter kilde:", + "add_to_favorites": "Tilføj til favoritter", + "remove_from_favorites": "Fjern fra favoritter", + "failed_update_favorites": "Kunne ikke opdatere favoritter", + "game_removed_from_library": "Spillet er fjernet fra biblioteket", + "failed_remove_from_library": "Kunne ikke fjerne fra biblioteket", + "files_removed_success": "Filer fjernet succesfuldt", + "failed_remove_files": "Kunne ikke fjerne filer", + "no_repacks_found": "Ingen kilder fundet til dette spil", "updated_at": "Opdateret {{updated_at}}", "install": "Installér", "resume": "Fortsæt", diff --git a/src/locales/de/translation.json b/src/locales/de/translation.json index 5101f459..fa52322c 100644 --- a/src/locales/de/translation.json +++ b/src/locales/de/translation.json @@ -207,7 +207,17 @@ "create_start_menu_shortcut": "Startmenü-Verknüpfung erstellen", "invalid_wine_prefix_path": "Ungültiger Wine-Präfix-Pfad", "invalid_wine_prefix_path_description": "Der Pfad zum Wine-Präfix ist ungültig. Bitte überprüfe den Pfad und versuche es erneut.", - "missing_wine_prefix": "Wine-Präfix ist erforderlich, um eine Sicherung unter Linux zu erstellen" + "missing_wine_prefix": "Wine-Präfix ist erforderlich, um eine Sicherung unter Linux zu erstellen", + "properties": "Eigenschaften", + "filter_by_source": "Filtern nach Quelle:", + "add_to_favorites": "Zu Favoriten hinzufügen", + "remove_from_favorites": "Aus Favoriten entfernen", + "failed_update_favorites": "Favoriten konnten nicht aktualisiert werden", + "game_removed_from_library": "Spiel aus Bibliothek entfernt", + "failed_remove_from_library": "Entfernen aus Bibliothek fehlgeschlagen", + "files_removed_success": "Dateien erfolgreich entfernt", + "failed_remove_files": "Dateien konnten nicht entfernt werden", + "no_repacks_found": "Keine Quellen für dieses Spiel gefunden" }, "activation": { "title": "Hydra aktivieren", diff --git a/src/locales/es/translation.json b/src/locales/es/translation.json index 7f54925a..a36e32c0 100644 --- a/src/locales/es/translation.json +++ b/src/locales/es/translation.json @@ -172,7 +172,16 @@ "files_automatically_mapped": "Archivos mapeados automáticamente", "no_backups_created": "Sin copias de seguridad creadas para este juego", "manage_files": "Gestionar archivos", - "loading_save_preview": "Buscando datos de guardados de juegos…", + "properties": "Propiedades", + "filter_by_source": "Filtrar por fuente:", + "add_to_favorites": "Agregar a favoritos", + "remove_from_favorites": "Eliminar de favoritos", + "failed_update_favorites": "No se pudo actualizar favoritos", + "game_removed_from_library": "Juego eliminado de la biblioteca", + "failed_remove_from_library": "No se pudo eliminar de la biblioteca", + "files_removed_success": "Archivos eliminados exitosamente", + "failed_remove_files": "No se pudieron eliminar los archivos", + "no_repacks_found": "No se encontraron fuentes para este juego", "wine_prefix": "Prefijo de Wine", "wine_prefix_description": "El prefijo de Wine usado para ejecutar este juego", "no_download_option_info": "Sin información disponible", diff --git a/src/locales/et/translation.json b/src/locales/et/translation.json index 119e1aab..23f9bc40 100644 --- a/src/locales/et/translation.json +++ b/src/locales/et/translation.json @@ -166,7 +166,17 @@ "manage_files_description": "Hallake, millised failid varundatakse ja taastatakse", "select_folder": "Vali kaust", "backup_from": "Varundamine kuupäevast {{date}}", - "custom_backup_location_set": "Kohandatud varundamise asukoht määratud" + "custom_backup_location_set": "Kohandatud varundamise asukoht määratud", + "properties": "Omadused", + "filter_by_source": "Filtreeri allika järgi:", + "add_to_favorites": "Lisa lemmikutesse", + "remove_from_favorites": "Eemalda lemmikutest", + "failed_update_favorites": "Lemmikute uuendamine ebaõnnestus", + "game_removed_from_library": "Mäng eemaldati kogust", + "failed_remove_from_library": "Eemaldamine kogust ebaõnnestus", + "files_removed_success": "Failid edukalt eemaldatud", + "failed_remove_files": "Failide eemaldamine ebaõnnestus", + "no_repacks_found": "Sellele mängule ei leitud allikaid" }, "activation": { "title": "Aktiveeri Hydra", diff --git a/src/locales/fa/translation.json b/src/locales/fa/translation.json index be18263a..ae1b085e 100644 --- a/src/locales/fa/translation.json +++ b/src/locales/fa/translation.json @@ -74,7 +74,17 @@ "change": "تغییر", "repacks_modal_description": "ریپک مورد نظر برای دانلود را انتخاب کنید", "select_folder_hint": "برای تغییر پوشه‌ی پیش‌فرض به <0>Settings بروید", - "download_now": "الان دانلود کن" + "download_now": "الان دانلود کن", + "no_repacks_found": "هیچ منبعی برای این بازی پیدا نشد", + "properties": "ویژگی‌ها", + "filter_by_source": "فیلتر بر اساس منبع:", + "add_to_favorites": "افزودن به علاقه‌مندی‌ها", + "remove_from_favorites": "حذف از علاقه‌مندی‌ها", + "failed_update_favorites": "به‌روزرسانی علاقه‌مندی‌ها ناموفق بود", + "game_removed_from_library": "بازی از کتابخانه حذف شد", + "failed_remove_from_library": "حذف از کتابخانه ناموفق بود", + "files_removed_success": "فایل‌ها با موفقیت حذف شدند", + "failed_remove_files": "حذف فایل‌ها ناموفق بود" }, "activation": { "title": "فعال کردن هایدرا", diff --git a/src/locales/fr/translation.json b/src/locales/fr/translation.json index 1c129a64..d19b6c27 100644 --- a/src/locales/fr/translation.json +++ b/src/locales/fr/translation.json @@ -65,6 +65,16 @@ "open_download_options": "Ouvrir les options de téléchargement", "download_options_zero": "Aucune option de téléchargement", "download_options_one": "{{count}} option de téléchargement", + "properties": "Propriétés", + "filter_by_source": "Filtrer par source :", + "add_to_favorites": "Ajouter aux favoris", + "remove_from_favorites": "Retirer des favoris", + "failed_update_favorites": "Échec de la mise à jour des favoris", + "game_removed_from_library": "Jeu retiré de la bibliothèque", + "failed_remove_from_library": "Échec du retrait de la bibliothèque", + "files_removed_success": "Fichiers supprimés avec succès", + "failed_remove_files": "Échec de la suppression des fichiers", + "no_repacks_found": "Aucune source trouvée pour ce jeu", "download_options_other": "{{count}} options de téléchargement", "updated_at": "Mis à jour le {{updated_at}}", "install": "Installer", diff --git a/src/locales/hu/translation.json b/src/locales/hu/translation.json index 0cea87b0..4b9a53eb 100644 --- a/src/locales/hu/translation.json +++ b/src/locales/hu/translation.json @@ -39,6 +39,16 @@ "open_download_options": "Letöltési lehetőségek", "download_options_zero": "Nincs letöltési lehetőség", "download_options_one": "{{count}} letöltési lehetőség", + "no_repacks_found": "Nem található forrás ehhez a játékhoz", + "properties": "Tulajdonságok", + "filter_by_source": "Szűrés forrás szerint:", + "add_to_favorites": "Hozzáadás a kedvencekhez", + "remove_from_favorites": "Eltávolítás a kedvencekből", + "failed_update_favorites": "Nem sikerült frissíteni a kedvenceket", + "game_removed_from_library": "A játék eltávolítva a könyvtárból", + "failed_remove_from_library": "Nem sikerült eltávolítani a könyvtárból", + "files_removed_success": "Fájlok sikeresen eltávolítva", + "failed_remove_files": "Nem sikerült eltávolítani a fájlokat", "download_options_other": "{{count}} letöltési lehetőség", "updated_at": "Frissítve: {{updated_at}}", "install": "Letöltés", diff --git a/src/locales/id/translation.json b/src/locales/id/translation.json index 4fa347fc..0d0eeb49 100644 --- a/src/locales/id/translation.json +++ b/src/locales/id/translation.json @@ -102,7 +102,16 @@ "open_download_location": "Lihat file yang diunduh", "create_shortcut": "Buat pintasan desktop", "remove_files": "Hapus file", - "remove_from_library_title": "Apa kamu yakin?", + "properties": "Properti", + "filter_by_source": "Filter berdasarkan sumber:", + "add_to_favorites": "Tambahkan ke favorit", + "remove_from_favorites": "Hapus dari favorit", + "failed_update_favorites": "Gagal memperbarui favorit", + "game_removed_from_library": "Game dihapus dari perpustakaan", + "failed_remove_from_library": "Gagal menghapus dari perpustakaan", + "files_removed_success": "File berhasil dihapus", + "failed_remove_files": "Gagal menghapus file", + "no_repacks_found": "Tidak ada sumber untuk game ini", "remove_from_library_description": "Ini akan menghapus {{game}} dari perpustakaan kamu", "options": "Opsi", "executable_section_title": "Eksekusi", diff --git a/src/locales/it/translation.json b/src/locales/it/translation.json index b23d1244..f51450c7 100644 --- a/src/locales/it/translation.json +++ b/src/locales/it/translation.json @@ -82,7 +82,17 @@ "previous_screenshot": "Screenshot precedente", "next_screenshot": "Screenshot successivo", "screenshot": "Screenshot {{number}}", - "open_screenshot": "Apri screenshot {{number}}" + "open_screenshot": "Apri screenshot {{number}}", + "no_repacks_found": "Nessuna fonte trovata per questo gioco", + "properties": "Proprietà", + "filter_by_source": "Filtra per fonte:", + "add_to_favorites": "Aggiungi ai preferiti", + "remove_from_favorites": "Rimuovi dai preferiti", + "failed_update_favorites": "Impossibile aggiornare i preferiti", + "game_removed_from_library": "Gioco rimosso dalla libreria", + "failed_remove_from_library": "Impossibile rimuovere dalla libreria", + "files_removed_success": "File rimossi con successo", + "failed_remove_files": "Impossibile rimuovere i file" }, "activation": { "title": "Attiva Hydra", diff --git a/src/locales/kk/translation.json b/src/locales/kk/translation.json index bfb009a7..48b59e65 100644 --- a/src/locales/kk/translation.json +++ b/src/locales/kk/translation.json @@ -114,7 +114,17 @@ "download_paused": "Жүктеу тоқтатылды", "last_downloaded_option": "Соңғы жүктеу нұсқасы", "create_shortcut_success": "Жарлық жасалды", - "create_shortcut_error": "Жарлық жасау мүмкін болмады" + "create_shortcut_error": "Жарлық жасау мүмкін болмады", + "no_repacks_found": "Бұл ойын үшін дереккөздер табылмады", + "properties": "Қасиеттер", + "filter_by_source": "Дереккөз бойынша сүзу:", + "add_to_favorites": "Таңдаулыларға қосу", + "remove_from_favorites": "Таңдаулылардан жою", + "failed_update_favorites": "Таңдаулыларды жаңарту сәтсіз аяқталды", + "game_removed_from_library": "Ойын кітапханадан жойылды", + "failed_remove_from_library": "Кітапханадан жою сәтсіз аяқталды", + "files_removed_success": "Файлдар сәтті жойылды", + "failed_remove_files": "Файлдарды жою сәтсіз аяқталды" }, "activation": { "title": "Hydra-ны белсендіру", diff --git a/src/locales/ko/translation.json b/src/locales/ko/translation.json index 9ec389b1..488f42d0 100644 --- a/src/locales/ko/translation.json +++ b/src/locales/ko/translation.json @@ -41,6 +41,16 @@ "download_options_one": "{{count}}개의 다운로드 선택지가 존재함", "download_options_other": "{{count}}개의 다운로드 선택지들이 존재함", "updated_at": "{{updated_at}}에 업데이트 됨", + "properties": "속성", + "filter_by_source": "소스별 필터:", + "add_to_favorites": "즐겨찾기에 추가", + "remove_from_favorites": "즐겨찾기에서 제거", + "failed_update_favorites": "즐겨찾기 업데이트 실패", + "game_removed_from_library": "게임이 라이브러리에서 제거되었습니다", + "failed_remove_from_library": "라이브러리에서 제거 실패", + "files_removed_success": "파일이 성공적으로 제거되었습니다", + "failed_remove_files": "파일 제거 실패", + "no_repacks_found": "이 게임에 대한 소스를 찾을 수 없습니다", "install": "설치", "resume": "재개", "pause": "일시 정지", diff --git a/src/locales/nb/translation.json b/src/locales/nb/translation.json index 8898ec7b..7684f2ca 100644 --- a/src/locales/nb/translation.json +++ b/src/locales/nb/translation.json @@ -131,7 +131,17 @@ "download": "Last ned", "executable_path_in_use": "Kjørbar fil blir allerede brukt av \"{{game}}\"", "warning": "Advarsel:", - "hydra_needs_to_remain_open": "Hydra skal forbli åpent for at denne nedlastingen kan gjennomføres. I tilfelle av at Hydra lukker før nedlastingen er ferdig, mister du fremskrittet ditt." + "hydra_needs_to_remain_open": "Hydra skal forbli åpent for at denne nedlastingen kan gjennomføres. I tilfelle av at Hydra lukker før nedlastingen er ferdig, mister du fremskrittet ditt.", + "properties": "Egenskaper", + "filter_by_source": "Filtrer etter kilde:", + "add_to_favorites": "Legg til i favoritter", + "remove_from_favorites": "Fjern fra favoritter", + "failed_update_favorites": "Kunne ikke oppdatere favoritter", + "game_removed_from_library": "Spillet er fjernet fra biblioteket", + "failed_remove_from_library": "Kunne ikke fjerne fra biblioteket", + "files_removed_success": "Filer fjernet", + "failed_remove_files": "Kunne ikke fjerne filer", + "no_repacks_found": "Ingen kilder funnet for dette spillet" }, "activation": { "title": "Aktivér Hydra", diff --git a/src/locales/nl/translation.json b/src/locales/nl/translation.json index 72d20c74..1df74891 100644 --- a/src/locales/nl/translation.json +++ b/src/locales/nl/translation.json @@ -49,6 +49,16 @@ "space_left_on_disk": "{{space}} Over op schijf", "eta": "Conclusie {{eta}}", "downloading_metadata": "Downloading metadata…", + "properties": "Eigenschappen", + "filter_by_source": "Filteren op bron:", + "add_to_favorites": "Toevoegen aan favorieten", + "remove_from_favorites": "Verwijderen uit favorieten", + "failed_update_favorites": "Favorieten bijwerken mislukt", + "game_removed_from_library": "Spel verwijderd uit bibliotheek", + "failed_remove_from_library": "Verwijderen uit bibliotheek mislukt", + "files_removed_success": "Bestanden succesvol verwijderd", + "failed_remove_files": "Verwijderen van bestanden mislukt", + "no_repacks_found": "Geen bronnen gevonden voor deze game", "filter": "Filter repacks", "requirements": "Systeem vereisten", "minimum": "Minimaal", diff --git a/src/locales/pl/translation.json b/src/locales/pl/translation.json index 86751b0e..29c44d4a 100644 --- a/src/locales/pl/translation.json +++ b/src/locales/pl/translation.json @@ -41,6 +41,16 @@ "download_options_zero": "Brak opcji pobierania", "download_options_one": "{{count}} opcja pobierania", "download_options_other": "{{count}} opcji pobierania", + "properties": "Właściwości", + "filter_by_source": "Filtruj według źródła:", + "add_to_favorites": "Dodaj do ulubionych", + "remove_from_favorites": "Usuń z ulubionych", + "failed_update_favorites": "Nie udało się zaktualizować ulubionych", + "game_removed_from_library": "Gra została usunięta z biblioteki", + "failed_remove_from_library": "Nie udało się usunąć z biblioteki", + "files_removed_success": "Pliki zostały pomyślnie usunięte", + "failed_remove_files": "Nie udało się usunąć plików", + "no_repacks_found": "Nie znaleziono źródeł dla tej gry", "updated_at": "Zaktualizowano {{updated_at}}", "install": "Instaluj", "resume": "Wznów", diff --git a/src/locales/pt-PT/translation.json b/src/locales/pt-PT/translation.json index 6c32b35b..0f6eb393 100644 --- a/src/locales/pt-PT/translation.json +++ b/src/locales/pt-PT/translation.json @@ -49,6 +49,16 @@ "open_download_options": "Ver opções de transferência", "download_options_zero": "Sem opções de transferência", "download_options_one": "{{count}} opção de transferência", + "properties": "Propriedades", + "filter_by_source": "Filtrar por fonte:", + "add_to_favorites": "Adicionar aos favoritos", + "remove_from_favorites": "Remover dos favoritos", + "failed_update_favorites": "Falha ao atualizar favoritos", + "game_removed_from_library": "Jogo removido da biblioteca", + "failed_remove_from_library": "Falha ao remover da biblioteca", + "files_removed_success": "Ficheiros removidos com sucesso", + "failed_remove_files": "Falha ao remover ficheiros", + "no_repacks_found": "Não foram encontradas fontes para este jogo", "download_options_other": "{{count}} opções de transferência", "updated_at": "Atualizado a {{updated_at}}", "resume": "Continuar", diff --git a/src/locales/ro/translation.json b/src/locales/ro/translation.json index c5a81881..cc02450a 100644 --- a/src/locales/ro/translation.json +++ b/src/locales/ro/translation.json @@ -51,6 +51,16 @@ "eta": "Concluzie {{eta}}", "calculating_eta": "Calculare timp rămas...", "downloading_metadata": "Se descarcă metadata...", + "properties": "Proprietăți", + "filter_by_source": "Filtrează după sursă:", + "add_to_favorites": "Adaugă la favorite", + "remove_from_favorites": "Elimină din favorite", + "failed_update_favorites": "Actualizarea favoritelor a eșuat", + "game_removed_from_library": "Jocul a fost eliminat din bibliotecă", + "failed_remove_from_library": "Eliminarea din bibliotecă a eșuat", + "files_removed_success": "Fișiere eliminate cu succes", + "failed_remove_files": "Eliminarea fișierelor a eșuat", + "no_repacks_found": "Nu s-au găsit surse pentru acest joc", "filter": "Filtrează repack-urile", "requirements": "Cerințe de sistem", "minimum": "Minim", diff --git a/src/locales/ru/translation.json b/src/locales/ru/translation.json index e576e5d9..97d62e1a 100644 --- a/src/locales/ru/translation.json +++ b/src/locales/ru/translation.json @@ -65,6 +65,16 @@ "open_download_options": "Открыть источники", "download_options_zero": "Нет источников", "download_options_one": "{{count}} источник", + "properties": "Свойства", + "filter_by_source": "Фильтровать по источнику:", + "add_to_favorites": "Добавить в избранное", + "remove_from_favorites": "Удалить из избранного", + "failed_update_favorites": "Не удалось обновить избранное", + "game_removed_from_library": "Игра удалена из библиотеки", + "failed_remove_from_library": "Не удалось удалить из библиотеки", + "files_removed_success": "Файлы успешно удалены", + "failed_remove_files": "Не удалось удалить файлы", + "no_repacks_found": "Не найдено источников для этой игры", "download_options_other": "{{count}} источников", "updated_at": "Обновлено {{updated_at}}", "install": "Установить", diff --git a/src/locales/sv/translation.json b/src/locales/sv/translation.json index 0972effa..8cbdfaf1 100644 --- a/src/locales/sv/translation.json +++ b/src/locales/sv/translation.json @@ -64,6 +64,16 @@ "open_download_options": "Öppna nedladdningsalternativ", "download_options_zero": "Inget nedladdningsalternativ", "download_options_one": "{{count}} nedladdningsalternativ", + "no_repacks_found": "Inga källor hittades för detta spel", + "properties": "Egenskaper", + "filter_by_source": "Filtrera efter källa:", + "add_to_favorites": "Lägg till i favoriter", + "remove_from_favorites": "Ta bort från favoriter", + "failed_update_favorites": "Misslyckades med att uppdatera favoriter", + "game_removed_from_library": "Spelet har tagits bort från biblioteket", + "failed_remove_from_library": "Misslyckades med att ta bort från biblioteket", + "files_removed_success": "Filer har tagits bort", + "failed_remove_files": "Misslyckades med att ta bort filer", "download_options_other": "{{count}} nedladdningsalternativ", "updated_at": "Uppdaterad {{updated_at}}", "install": "Installera", diff --git a/src/locales/tr/translation.json b/src/locales/tr/translation.json index c3fe2081..218ffa39 100644 --- a/src/locales/tr/translation.json +++ b/src/locales/tr/translation.json @@ -64,6 +64,16 @@ "open_download_options": "İndirme seçeneklerini aç", "download_options_zero": "İndirme seçeneği yok", "download_options_one": "{{count}} indirme seçeneği", + "properties": "Özellikler", + "filter_by_source": "Kaynağa göre filtrele:", + "add_to_favorites": "Favorilere ekle", + "remove_from_favorites": "Favorilerden kaldır", + "failed_update_favorites": "Favoriler güncellenemedi", + "game_removed_from_library": "Oyun kütüphaneden kaldırıldı", + "failed_remove_from_library": "Kütüphaneden kaldırma başarısız oldu", + "files_removed_success": "Dosyalar başarıyla kaldırıldı", + "failed_remove_files": "Dosyalar kaldırılamadı", + "no_repacks_found": "Bu oyun için kaynak bulunamadı", "download_options_other": "{{count}} indirme seçeneği", "updated_at": "{{updated_at}} tarihinde güncellendi", "install": "Yükle", diff --git a/src/locales/uk/translation.json b/src/locales/uk/translation.json index 48a3972d..624138ee 100644 --- a/src/locales/uk/translation.json +++ b/src/locales/uk/translation.json @@ -68,6 +68,16 @@ "download_options_other": "{{count}} варіантів завантаження", "updated_at": "Оновлено {{updated_at}}", "install": "Встановити", + "properties": "Властивості", + "filter_by_source": "Фільтрувати за джерелом:", + "add_to_favorites": "Додати до обраного", + "remove_from_favorites": "Видалити з обраного", + "failed_update_favorites": "Не вдалося оновити обране", + "game_removed_from_library": "Гру видалено з бібліотеки", + "failed_remove_from_library": "Не вдалося видалити з бібліотеки", + "files_removed_success": "Файли успішно видалено", + "failed_remove_files": "Не вдалося видалити файли", + "no_repacks_found": "Не знайдено джерел для цієї гри", "resume": "Відновити", "pause": "Призупинити", "cancel": "Скасувати", diff --git a/src/locales/uz/translation.json b/src/locales/uz/translation.json index d20a9677..a81fea55 100644 --- a/src/locales/uz/translation.json +++ b/src/locales/uz/translation.json @@ -64,6 +64,16 @@ "play_time": "O'ynalgan vaqt {{amount}}", "last_time_played": "Oxirgi ishga tushirilgan {{period}}", "not_played_yet": "Siz hali {{title}}ni o'ynamagansiz", + "properties": "Xususiyatlar", + "filter_by_source": "Manba bo'yicha filtrlash:", + "add_to_favorites": "Sevimlilarga qo'shish", + "remove_from_favorites": "Sevimlilardan olib tashlash", + "failed_update_favorites": "Sevimlilarni yangilash muvaffaqiyatsiz tugadi", + "game_removed_from_library": "O'yin kutubxonadan olib tashlandi", + "failed_remove_from_library": "Kutubxonadan olib tashlash muvaffaqiyatsiz tugadi", + "files_removed_success": "Fayllar muvaffaqiyatli olib tashlandi", + "failed_remove_files": "Fayllarni olib tashlash muvaffaqiyatsiz tugadi", + "no_repacks_found": "Bu o'yin uchun manbalar topilmadi", "next_suggestion": "Keyingi taklif", "play": "O'ynash", "deleting": "O'rnatuvchi o'chirilmoqda…", diff --git a/src/locales/zh/translation.json b/src/locales/zh/translation.json index 0323d991..97c143b9 100644 --- a/src/locales/zh/translation.json +++ b/src/locales/zh/translation.json @@ -72,6 +72,16 @@ "install": "安装", "resume": "恢复", "pause": "暂停", + "properties": "属性", + "filter_by_source": "按来源筛选:", + "add_to_favorites": "添加到收藏夹", + "remove_from_favorites": "从收藏夹移除", + "failed_update_favorites": "收藏夹更新失败", + "game_removed_from_library": "游戏已从库中移除", + "failed_remove_from_library": "从库中移除失败", + "files_removed_success": "文件已成功移除", + "failed_remove_files": "文件移除失败", + "no_repacks_found": "未找到该游戏的资源", "cancel": "取消", "remove": "移除", "space_left_on_disk": "磁盘剩余空间{{space}}", From f9673b1a5a9d1b1461b014f19c89a544cb275228 Mon Sep 17 00:00:00 2001 From: caduHD4 Date: Sun, 21 Sep 2025 18:14:35 -0300 Subject: [PATCH 04/15] fix: adjusting lint formatting --- .../game-context-menu/game-context-menu.tsx | 23 ++++-- .../game-context-menu/use-game-actions.ts | 76 ++++++++++++++----- .../game-details/game-details.context.tsx | 23 ++++-- 3 files changed, 89 insertions(+), 33 deletions(-) diff --git a/src/renderer/src/components/game-context-menu/game-context-menu.tsx b/src/renderer/src/components/game-context-menu/game-context-menu.tsx index f772e46e..bb341a3a 100644 --- a/src/renderer/src/components/game-context-menu/game-context-menu.tsx +++ b/src/renderer/src/components/game-context-menu/game-context-menu.tsx @@ -18,7 +18,7 @@ import { ContextMenu, ContextMenuItemData, ContextMenuProps } from ".."; import { ConfirmModal } from "@renderer/components/confirm-modal/confirm-modal"; import { useGameActions } from ".."; -interface GameContextMenuProps extends Omit { +interface GameContextMenuProps extends Omit { game: LibraryGame; } @@ -29,7 +29,8 @@ export function GameContextMenu({ onClose, }: GameContextMenuProps) { const { t } = useTranslation("game_details"); - const [showConfirmRemoveLibrary, setShowConfirmRemoveLibrary] = useState(false); + const [showConfirmRemoveLibrary, setShowConfirmRemoveLibrary] = + useState(false); const [showConfirmRemoveFiles, setShowConfirmRemoveFiles] = useState(false); const { canPlay, @@ -54,14 +55,22 @@ export function GameContextMenu({ id: "play", label: canPlay ? t("play") : t("download"), icon: canPlay ? : , - onClick: handlePlayGame, + onClick: () => { + void handlePlayGame(); + }, disabled: isDeleting, }, { id: "favorite", label: game.favorite ? t("remove_from_favorites") : t("add_to_favorites"), - icon: game.favorite ? : , - onClick: handleToggleFavorite, + icon: game.favorite ? ( + + ) : ( + + ), + onClick: () => { + void handleToggleFavorite(); + }, disabled: isDeleting, }, ...(game.executablePath @@ -181,7 +190,9 @@ export function GameContextMenu({ visible={visible} position={position} onClose={onClose} - className={!game.executablePath ? "context-menu--game-not-installed" : undefined} + className={ + !game.executablePath ? "context-menu--game-not-installed" : undefined + } /> { if (!canPlay) { @@ -37,17 +39,25 @@ export function useGameActions(game: LibraryGame) { if (location.pathname === path) { try { window.dispatchEvent( - new CustomEvent("hydra:openRepacks", { detail: { objectId: game.objectId } }) + new CustomEvent("hydra:openRepacks", { + detail: { objectId: game.objectId }, + }) ); - } catch (e) {} + } catch (e) { + void e; + } } else { navigate(path, { state: { openRepacks: true } }); try { window.dispatchEvent( - new CustomEvent("hydra:openRepacks", { detail: { objectId: game.objectId } }) + new CustomEvent("hydra:openRepacks", { + detail: { objectId: game.objectId }, + }) ); - } catch (e) {} + } catch (e) { + void e; + } } return; } @@ -76,10 +86,16 @@ export function useGameActions(game: LibraryGame) { } updateLibrary(); try { - window.dispatchEvent(new CustomEvent("hydra:game-favorite-toggled", { detail: { shop: game.shop, objectId: game.objectId } })); - } catch (e) {} + window.dispatchEvent( + new CustomEvent("hydra:game-favorite-toggled", { + detail: { shop: game.shop, objectId: game.objectId }, + }) + ); + } catch (e) { + void e; + } } catch (error) { - showErrorToast(t("failed_update_favorites")); + showErrorToast(t("failed_update_favorites")); logger.error("Failed to toggle favorite", error); } }; @@ -91,7 +107,7 @@ export function useGameActions(game: LibraryGame) { game.objectId, location ); - + if (success) { showSuccessToast(t("create_shortcut_success")); } else { @@ -138,9 +154,12 @@ export function useGameActions(game: LibraryGame) { try { window.dispatchEvent( - new CustomEvent("hydra:openRepacks", { detail: { objectId: game.objectId } }) + new CustomEvent("hydra:openRepacks", { + detail: { objectId: game.objectId }, + }) ); } catch (e) { + void e; } }; @@ -154,9 +173,12 @@ export function useGameActions(game: LibraryGame) { try { window.dispatchEvent( - new CustomEvent("hydra:openGameOptions", { detail: { objectId: game.objectId } }) + new CustomEvent("hydra:openGameOptions", { + detail: { objectId: game.objectId }, + }) ); } catch (e) { + void e; } }; @@ -179,8 +201,14 @@ export function useGameActions(game: LibraryGame) { updateLibrary(); showSuccessToast(t("game_removed_from_library")); try { - window.dispatchEvent(new CustomEvent("hydra:game-removed-from-library", { detail: { shop: game.shop, objectId: game.objectId } })); - } catch (e) {} + window.dispatchEvent( + new CustomEvent("hydra:game-removed-from-library", { + detail: { shop: game.shop, objectId: game.objectId }, + }) + ); + } catch (e) { + void e; + } } catch (error) { showErrorToast(t("failed_remove_from_library")); logger.error("Failed to remove from library", error); @@ -193,8 +221,14 @@ export function useGameActions(game: LibraryGame) { updateLibrary(); showSuccessToast(t("files_removed_success")); try { - window.dispatchEvent(new CustomEvent("hydra:game-files-removed", { detail: { shop: game.shop, objectId: game.objectId } })); - } catch (e) {} + window.dispatchEvent( + new CustomEvent("hydra:game-files-removed", { + detail: { shop: game.shop, objectId: game.objectId }, + }) + ); + } catch (e) { + void e; + } } catch (error) { showErrorToast(t("failed_remove_files")); logger.error("Failed to remove files", error); @@ -219,4 +253,4 @@ export function useGameActions(game: LibraryGame) { handleRemoveFiles, handleOpenGameOptions, }; -} \ No newline at end of file +} diff --git a/src/renderer/src/context/game-details/game-details.context.tsx b/src/renderer/src/context/game-details/game-details.context.tsx index edfc503b..d83d9494 100644 --- a/src/renderer/src/context/game-details/game-details.context.tsx +++ b/src/renderer/src/context/game-details/game-details.context.tsx @@ -182,7 +182,7 @@ export function GameDetailsContextProvider({ if (abortController.signal.aborted) return; setAchievements(achievements); }) - .catch(() => {}); + .catch(() => void 0); } }, [ updateGame, @@ -204,12 +204,15 @@ export function GameDetailsContextProvider({ }, [objectId, gameTitle, dispatch]); useEffect(() => { - const state: any = (location && (location.state as any)) || {}; + const state = + (location && (location.state as Record)) || {}; if (state.openRepacks) { setShowRepacksModal(true); try { window.history.replaceState({}, document.title, location.pathname); - } catch (_e) {} + } catch (_e) { + void _e; + } } }, [location]); @@ -239,6 +242,7 @@ export function GameDetailsContextProvider({ setShowRepacksModal(true); } } catch (e) { + void e; } }; @@ -257,24 +261,31 @@ export function GameDetailsContextProvider({ setShowGameOptionsModal(true); } } catch (e) { + void e; } }; window.addEventListener("hydra:openGameOptions", handler as EventListener); return () => { - window.removeEventListener("hydra:openGameOptions", handler as EventListener); + window.removeEventListener( + "hydra:openGameOptions", + handler as EventListener + ); }; }, [objectId]); useEffect(() => { - const state: any = (location && (location.state as any)) || {}; + const state = + (location && (location.state as Record)) || {}; if (state.openGameOptions) { setShowGameOptionsModal(true); try { window.history.replaceState({}, document.title, location.pathname); - } catch (_e) {} + } catch (_e) { + void _e; + } } }, [location]); From d00fe8dedcb5720334f223cb500a5fe92ae7148f Mon Sep 17 00:00:00 2001 From: caduHD4 Date: Sun, 21 Sep 2025 18:26:43 -0300 Subject: [PATCH 05/15] fix: more lint --- src/locales/en/translation.json | 20 +++++++-------- src/locales/pt-BR/translation.json | 20 +++++++-------- .../components/context-menu/context-menu.tsx | 25 +++++++++++++------ 3 files changed, 38 insertions(+), 27 deletions(-) diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index c79741c3..1ed6bff8 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -102,7 +102,7 @@ "playing_now": "Playing now", "change": "Change", "repacks_modal_description": "Choose the repack you want to download", - "no_repacks_found": "No sources found for this game", + "no_repacks_found": "No sources found for this game", "select_folder_hint": "To change the default folder, go to the <0>Settings", "download_now": "Download now", "no_shop_details": "Could not retrieve shop details.", @@ -125,8 +125,8 @@ "remove_from_library_title": "Are you sure?", "remove_from_library_description": "This will remove {{game}} from your library", "options": "Options", - "properties": "Properties", - "filter_by_source": "Filter by source:", + "properties": "Properties", + "filter_by_source": "Filter by source:", "executable_section_title": "Executable", "executable_section_description": "Path of the file that will be executed when \"Play\" is clicked", "downloads_section_title": "Downloads", @@ -140,13 +140,13 @@ "create_shortcut_success": "Shortcut created successfully", "you_might_need_to_restart_steam": "You might need to restart Steam to see the changes", "create_shortcut_error": "Error creating shortcut", - "add_to_favorites": "Add to favorites", - "remove_from_favorites": "Remove from favorites", - "failed_update_favorites": "Failed to update favorites", - "game_removed_from_library": "Game removed from library", - "failed_remove_from_library": "Failed to remove from library", - "files_removed_success": "Files removed successfully", - "failed_remove_files": "Failed to remove files", + "add_to_favorites": "Add to favorites", + "remove_from_favorites": "Remove from favorites", + "failed_update_favorites": "Failed to update favorites", + "game_removed_from_library": "Game removed from library", + "failed_remove_from_library": "Failed to remove from library", + "files_removed_success": "Files removed successfully", + "failed_remove_files": "Failed to remove files", "nsfw_content_title": "This game contains inappropriate content", "nsfw_content_description": "{{title}} contains content that may not be suitable for all ages. Are you sure you want to continue?", "allow_nsfw_content": "Continue", diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index 076b18fa..e8917d44 100644 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -90,7 +90,7 @@ "playing_now": "Jogando agora", "change": "Explorar", "repacks_modal_description": "Escolha o repack do jogo que deseja baixar", - "no_repacks_found": "Nenhuma fonte encontrada para este jogo", + "no_repacks_found": "Nenhuma fonte encontrada para este jogo", "select_folder_hint": "Para trocar o diretório padrão, acesse a <0>Tela de Ajustes", "download_now": "Iniciar download", "no_shop_details": "Não foi possível obter os detalhes da loja.", @@ -110,8 +110,8 @@ "create_shortcut_simple": "Criar atalho", "remove_files": "Remover arquivos", "options": "Gerenciar", - "properties": "Propriedades", - "filter_by_source": "Filtrar por fonte:", + "properties": "Propriedades", + "filter_by_source": "Filtrar por fonte:", "remove_from_library_description": "Isso irá remover {{game}} da sua biblioteca", "remove_from_library_title": "Tem certeza?", "executable_section_title": "Executável", @@ -194,13 +194,13 @@ "download_error_not_cached_on_hydra": "Este download não está disponível no Nimbus.", "game_removed_from_favorites": "Jogo removido dos favoritos", "game_added_to_favorites": "Jogo adicionado aos favoritos", - "add_to_favorites": "Adicionar aos favoritos", - "remove_from_favorites": "Remover dos favoritos", - "failed_update_favorites": "Falha ao atualizar favoritos", - "game_removed_from_library": "Jogo removido da biblioteca", - "failed_remove_from_library": "Falha ao remover da biblioteca", - "files_removed_success": "Arquivos removidos com sucesso", - "failed_remove_files": "Falha ao remover arquivos", + "add_to_favorites": "Adicionar aos favoritos", + "remove_from_favorites": "Remover dos favoritos", + "failed_update_favorites": "Falha ao atualizar favoritos", + "game_removed_from_library": "Jogo removido da biblioteca", + "failed_remove_from_library": "Falha ao remover da biblioteca", + "files_removed_success": "Arquivos removidos com sucesso", + "failed_remove_files": "Falha ao remover arquivos", "automatically_extract_downloaded_files": "Extrair automaticamente os arquivos baixados", "create_start_menu_shortcut": "Criar atalho no Menu Iniciar", "invalid_wine_prefix_path": "Caminho do prefixo Wine inválido", diff --git a/src/renderer/src/components/context-menu/context-menu.tsx b/src/renderer/src/components/context-menu/context-menu.tsx index ebbc6b70..cb9e0347 100644 --- a/src/renderer/src/components/context-menu/context-menu.tsx +++ b/src/renderer/src/components/context-menu/context-menu.tsx @@ -36,7 +36,9 @@ export function ContextMenu({ const [activeSubmenu, setActiveSubmenu] = useState(null); const submenuCloseTimeout = useRef(null); const itemRefs = useRef>({}); - const [submenuStyles, setSubmenuStyles] = useState>({}); + const [submenuStyles, setSubmenuStyles] = useState< + Record + >({}); useEffect(() => { if (!visible) return; @@ -127,7 +129,9 @@ export function ContextMenu({ const parentEl = itemRefs.current[activeSubmenu]; if (!parentEl) return; - const submenuEl = parentEl.querySelector(".context-menu__submenu") as HTMLElement | null; + const submenuEl = parentEl.querySelector( + ".context-menu__submenu" + ) as HTMLElement | null; if (!submenuEl) return; const parentRect = parentEl.getBoundingClientRect(); @@ -174,7 +178,9 @@ export function ContextMenu({ key={item.id} ref={(el) => (itemRefs.current[item.id] = el)} className="context-menu__item-container" - onMouseEnter={() => item.submenu && handleSubmenuMouseEnter(item.id)} + onMouseEnter={() => + item.submenu && handleSubmenuMouseEnter(item.id) + } onMouseLeave={() => item.submenu && handleSubmenuMouseLeave()} > {item.separator &&
    } @@ -198,7 +204,7 @@ export function ContextMenu({ )} - {item.submenu && activeSubmenu === item.id && ( + {item.submenu && activeSubmenu === item.id && (
      {item.submenu.map((subItem) => ( -
    • - {subItem.separator &&
      } +
    • + {subItem.separator && ( +
      + )}
      -
      -

      {t("filter_by_source")}

      +
      {downloadSources.map((source) => (
      Date: Tue, 23 Sep 2025 23:49:29 -0300 Subject: [PATCH 08/15] Refactor SCSS for repacks modal layout and styles --- .../game-details/modals/repacks-modal.scss | 38 +++++++++++++++++-- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/src/renderer/src/pages/game-details/modals/repacks-modal.scss b/src/renderer/src/pages/game-details/modals/repacks-modal.scss index 7ffdb973..f2de554a 100644 --- a/src/renderer/src/pages/game-details/modals/repacks-modal.scss +++ b/src/renderer/src/pages/game-details/modals/repacks-modal.scss @@ -7,6 +7,22 @@ &__filter-top { margin-bottom: 1rem; + display: flex; + flex-direction: column; + gap: 0.5rem; + } + + &__filter-toggle { + align-self: flex-start; + display: flex; + align-items: center; + gap: 0.5rem; + font-size: globals.$small-font-size; + font-weight: 600; + color: var(--color-text-secondary); + padding: 0.5rem 0.75rem; + border-radius: 6px; + transition: background-color 0.2s ease; } &__repacks { @@ -67,14 +83,23 @@ } &__download-sources { - padding: 0.75rem; + padding: 0; background-color: var(--color-background-light); border-radius: 8px; margin-bottom: 1rem; margin-top: calc(globals.$spacing-unit * 0.5); + max-height: 0; + overflow: hidden; + transition: max-height 0.3s ease, padding 0.3s ease; + + &--open { + padding: 0.75rem; + max-height: 250px; + } } &__filter-label { + display: none; font-size: globals.$small-font-size; font-weight: 600; margin-bottom: 0.75rem; @@ -84,15 +109,17 @@ &__source-grid { display: grid; - grid-template-columns: repeat(3, 1fr); + grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 0.5rem; max-height: 200px; overflow-y: auto; + overflow-x: hidden; align-items: start; + padding-right: 0.25rem; } &__source-item { - padding: 0.35rem 0.75rem; + padding: 0.35rem 0.5rem; background: var(--color-surface, rgba(0, 0, 0, 0.03)); border: 1px solid var(--color-border); border-radius: 6px; @@ -100,11 +127,12 @@ align-items: center; min-height: 38px; box-sizing: border-box; + width: 100%; } - // Ajustes para o label do checkbox &__source-item :global(.checkbox-field) { width: 100%; + min-width: 0; } &__source-item :global(.checkbox-field__label) { @@ -112,5 +140,7 @@ overflow: hidden; text-overflow: ellipsis; display: block; + font-size: 0.85rem; + width: 100%; } } From 5e217bf0866384227503233b9d2ffc88df808b11 Mon Sep 17 00:00:00 2001 From: Carlos Eduardo Mariano Garcia Pereira <69264602+caduHD4@users.noreply.github.com> Date: Tue, 23 Sep 2025 23:53:06 -0300 Subject: [PATCH 09/15] Fix: Enhance repacks modal with filter toggle feature Added filter toggle functionality with icons to repacks modal. --- .../pages/game-details/modals/repacks-modal.tsx | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/renderer/src/pages/game-details/modals/repacks-modal.tsx b/src/renderer/src/pages/game-details/modals/repacks-modal.tsx index e5c7a7cb..32e330c6 100644 --- a/src/renderer/src/pages/game-details/modals/repacks-modal.tsx +++ b/src/renderer/src/pages/game-details/modals/repacks-modal.tsx @@ -1,7 +1,7 @@ import { useContext, useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router-dom"; -import { PlusCircleIcon } from "@primer/octicons-react"; +import { PlusCircleIcon, ChevronDownIcon, ChevronUpIcon } from "@primer/octicons-react"; import { Badge, @@ -159,6 +159,8 @@ export function RepacksModal({ return repack.uris.some((uri) => uri.includes(game.download!.uri)); }; + const [isFilterDrawerOpen, setIsFilterDrawerOpen] = useState(false); + return ( <>
      +
      -
      -

      {t("filter_by_source")}

      +
      {downloadSources.map((source) => (
      Date: Wed, 24 Sep 2025 00:01:49 -0300 Subject: [PATCH 10/15] fix: lint --- src/renderer/src/pages/game-details/modals/repacks-modal.scss | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/renderer/src/pages/game-details/modals/repacks-modal.scss b/src/renderer/src/pages/game-details/modals/repacks-modal.scss index f2de554a..17a0b8c6 100644 --- a/src/renderer/src/pages/game-details/modals/repacks-modal.scss +++ b/src/renderer/src/pages/game-details/modals/repacks-modal.scss @@ -90,7 +90,9 @@ margin-top: calc(globals.$spacing-unit * 0.5); max-height: 0; overflow: hidden; - transition: max-height 0.3s ease, padding 0.3s ease; + transition: + max-height 0.3s ease, + padding 0.3s ease; &--open { padding: 0.75rem; From 688ae215d76e252de2c67d220d6281dcc6dbe8f3 Mon Sep 17 00:00:00 2001 From: caduHD4 Date: Sun, 28 Sep 2025 02:04:31 -0300 Subject: [PATCH 11/15] Refactor: Remove translation keys (keeping pt-br and en) and clean up repacks modal styles and logic --- src/locales/ca/translation.json | 10 -- src/locales/cs/translation.json | 10 -- src/locales/da/translation.json | 10 -- src/locales/de/translation.json | 12 +- src/locales/en/translation.json | 2 - src/locales/es/translation.json | 10 -- src/locales/et/translation.json | 12 +- src/locales/fa/translation.json | 12 +- src/locales/fr/translation.json | 10 -- src/locales/hu/translation.json | 10 -- src/locales/id/translation.json | 10 -- src/locales/it/translation.json | 12 +- src/locales/kk/translation.json | 12 +- src/locales/ko/translation.json | 10 -- src/locales/nb/translation.json | 12 +- src/locales/nl/translation.json | 10 -- src/locales/pl/translation.json | 10 -- src/locales/pt-BR/translation.json | 2 - src/locales/pt-PT/translation.json | 10 -- src/locales/ro/translation.json | 10 -- src/locales/ru/translation.json | 10 -- src/locales/sv/translation.json | 10 -- src/locales/tr/translation.json | 10 -- src/locales/uk/translation.json | 10 -- src/locales/uz/translation.json | 10 -- src/locales/zh/translation.json | 10 -- .../game-details/modals/repacks-modal.scss | 140 +-------------- .../game-details/modals/repacks-modal.tsx | 166 ++++-------------- 28 files changed, 45 insertions(+), 517 deletions(-) diff --git a/src/locales/ca/translation.json b/src/locales/ca/translation.json index e397e9f1..aa69001f 100644 --- a/src/locales/ca/translation.json +++ b/src/locales/ca/translation.json @@ -50,16 +50,6 @@ "download_options_zero": "No hi ha opcions de baixada", "download_options_one": "{{count}} opció de baixada", "download_options_other": "{{count}} opcions de baixada", - "no_repacks_found": "No s'han trobat fonts per a aquest joc", - "properties": "Propietats", - "filter_by_source": "Filtra per font:", - "add_to_favorites": "Afegeix als favorits", - "remove_from_favorites": "Elimina dels favorits", - "failed_update_favorites": "No s'han pogut actualitzar els favorits", - "game_removed_from_library": "El joc s'ha eliminat de la biblioteca", - "failed_remove_from_library": "No s'ha pogut eliminar de la biblioteca", - "files_removed_success": "Fitxers eliminats correctament", - "failed_remove_files": "No s'han pogut eliminar els fitxers", "updated_at": "Actualitzat: {{updated_at}}", "install": "Instal·la", "resume": "Reprèn", diff --git a/src/locales/cs/translation.json b/src/locales/cs/translation.json index 57d0c25d..9b501b54 100644 --- a/src/locales/cs/translation.json +++ b/src/locales/cs/translation.json @@ -75,16 +75,6 @@ "eta": "Odhadovaný čas: {{eta}}", "calculating_eta": "Počítám zbývající čas…", "downloading_metadata": "Stahuji metadata…", - "properties": "Vlastnosti", - "filter_by_source": "Filtrovat podle zdroje:", - "add_to_favorites": "Přidat do oblíbených", - "remove_from_favorites": "Odebrat z oblíbených", - "failed_update_favorites": "Nepodařilo se aktualizovat oblíbené", - "game_removed_from_library": "Hra byla odebrána z knihovny", - "failed_remove_from_library": "Nepodařilo se odebrat z knihovny", - "files_removed_success": "Soubory byly úspěšně odebrány", - "failed_remove_files": "Nepodařilo se odebrat soubory", - "no_repacks_found": "Nebyly nalezeny žádné zdroje pro tuto hru", "filter": "Filtrovat repacky", "requirements": "Systémové požadavky", "minimum": "Minimální", diff --git a/src/locales/da/translation.json b/src/locales/da/translation.json index 14306368..618f085c 100644 --- a/src/locales/da/translation.json +++ b/src/locales/da/translation.json @@ -54,16 +54,6 @@ "download_options_zero": "Ingen download mulighed", "download_options_one": "{{count}} download mulighed", "download_options_other": "{{count}} download muligheder", - "properties": "Egenskaber", - "filter_by_source": "Filtrér efter kilde:", - "add_to_favorites": "Tilføj til favoritter", - "remove_from_favorites": "Fjern fra favoritter", - "failed_update_favorites": "Kunne ikke opdatere favoritter", - "game_removed_from_library": "Spillet er fjernet fra biblioteket", - "failed_remove_from_library": "Kunne ikke fjerne fra biblioteket", - "files_removed_success": "Filer fjernet succesfuldt", - "failed_remove_files": "Kunne ikke fjerne filer", - "no_repacks_found": "Ingen kilder fundet til dette spil", "updated_at": "Opdateret {{updated_at}}", "install": "Installér", "resume": "Fortsæt", diff --git a/src/locales/de/translation.json b/src/locales/de/translation.json index fa52322c..5101f459 100644 --- a/src/locales/de/translation.json +++ b/src/locales/de/translation.json @@ -207,17 +207,7 @@ "create_start_menu_shortcut": "Startmenü-Verknüpfung erstellen", "invalid_wine_prefix_path": "Ungültiger Wine-Präfix-Pfad", "invalid_wine_prefix_path_description": "Der Pfad zum Wine-Präfix ist ungültig. Bitte überprüfe den Pfad und versuche es erneut.", - "missing_wine_prefix": "Wine-Präfix ist erforderlich, um eine Sicherung unter Linux zu erstellen", - "properties": "Eigenschaften", - "filter_by_source": "Filtern nach Quelle:", - "add_to_favorites": "Zu Favoriten hinzufügen", - "remove_from_favorites": "Aus Favoriten entfernen", - "failed_update_favorites": "Favoriten konnten nicht aktualisiert werden", - "game_removed_from_library": "Spiel aus Bibliothek entfernt", - "failed_remove_from_library": "Entfernen aus Bibliothek fehlgeschlagen", - "files_removed_success": "Dateien erfolgreich entfernt", - "failed_remove_files": "Dateien konnten nicht entfernt werden", - "no_repacks_found": "Keine Quellen für dieses Spiel gefunden" + "missing_wine_prefix": "Wine-Präfix ist erforderlich, um eine Sicherung unter Linux zu erstellen" }, "activation": { "title": "Hydra aktivieren", diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 57c11d8a..baf216ee 100755 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -102,7 +102,6 @@ "playing_now": "Playing now", "change": "Change", "repacks_modal_description": "Choose the repack you want to download", - "no_repacks_found": "No sources found for this game", "select_folder_hint": "To change the default folder, go to the <0>Settings", "download_now": "Download now", "no_shop_details": "Could not retrieve shop details.", @@ -126,7 +125,6 @@ "remove_from_library_description": "This will remove {{game}} from your library", "options": "Options", "properties": "Properties", - "filter_by_source": "Filter by source:", "executable_section_title": "Executable", "executable_section_description": "Path of the file that will be executed when \"Play\" is clicked", "downloads_section_title": "Downloads", diff --git a/src/locales/es/translation.json b/src/locales/es/translation.json index a36e32c0..7c9ef59c 100644 --- a/src/locales/es/translation.json +++ b/src/locales/es/translation.json @@ -172,16 +172,6 @@ "files_automatically_mapped": "Archivos mapeados automáticamente", "no_backups_created": "Sin copias de seguridad creadas para este juego", "manage_files": "Gestionar archivos", - "properties": "Propiedades", - "filter_by_source": "Filtrar por fuente:", - "add_to_favorites": "Agregar a favoritos", - "remove_from_favorites": "Eliminar de favoritos", - "failed_update_favorites": "No se pudo actualizar favoritos", - "game_removed_from_library": "Juego eliminado de la biblioteca", - "failed_remove_from_library": "No se pudo eliminar de la biblioteca", - "files_removed_success": "Archivos eliminados exitosamente", - "failed_remove_files": "No se pudieron eliminar los archivos", - "no_repacks_found": "No se encontraron fuentes para este juego", "wine_prefix": "Prefijo de Wine", "wine_prefix_description": "El prefijo de Wine usado para ejecutar este juego", "no_download_option_info": "Sin información disponible", diff --git a/src/locales/et/translation.json b/src/locales/et/translation.json index 23f9bc40..119e1aab 100644 --- a/src/locales/et/translation.json +++ b/src/locales/et/translation.json @@ -166,17 +166,7 @@ "manage_files_description": "Hallake, millised failid varundatakse ja taastatakse", "select_folder": "Vali kaust", "backup_from": "Varundamine kuupäevast {{date}}", - "custom_backup_location_set": "Kohandatud varundamise asukoht määratud", - "properties": "Omadused", - "filter_by_source": "Filtreeri allika järgi:", - "add_to_favorites": "Lisa lemmikutesse", - "remove_from_favorites": "Eemalda lemmikutest", - "failed_update_favorites": "Lemmikute uuendamine ebaõnnestus", - "game_removed_from_library": "Mäng eemaldati kogust", - "failed_remove_from_library": "Eemaldamine kogust ebaõnnestus", - "files_removed_success": "Failid edukalt eemaldatud", - "failed_remove_files": "Failide eemaldamine ebaõnnestus", - "no_repacks_found": "Sellele mängule ei leitud allikaid" + "custom_backup_location_set": "Kohandatud varundamise asukoht määratud" }, "activation": { "title": "Aktiveeri Hydra", diff --git a/src/locales/fa/translation.json b/src/locales/fa/translation.json index ae1b085e..be18263a 100644 --- a/src/locales/fa/translation.json +++ b/src/locales/fa/translation.json @@ -74,17 +74,7 @@ "change": "تغییر", "repacks_modal_description": "ریپک مورد نظر برای دانلود را انتخاب کنید", "select_folder_hint": "برای تغییر پوشه‌ی پیش‌فرض به <0>Settings بروید", - "download_now": "الان دانلود کن", - "no_repacks_found": "هیچ منبعی برای این بازی پیدا نشد", - "properties": "ویژگی‌ها", - "filter_by_source": "فیلتر بر اساس منبع:", - "add_to_favorites": "افزودن به علاقه‌مندی‌ها", - "remove_from_favorites": "حذف از علاقه‌مندی‌ها", - "failed_update_favorites": "به‌روزرسانی علاقه‌مندی‌ها ناموفق بود", - "game_removed_from_library": "بازی از کتابخانه حذف شد", - "failed_remove_from_library": "حذف از کتابخانه ناموفق بود", - "files_removed_success": "فایل‌ها با موفقیت حذف شدند", - "failed_remove_files": "حذف فایل‌ها ناموفق بود" + "download_now": "الان دانلود کن" }, "activation": { "title": "فعال کردن هایدرا", diff --git a/src/locales/fr/translation.json b/src/locales/fr/translation.json index d19b6c27..1c129a64 100644 --- a/src/locales/fr/translation.json +++ b/src/locales/fr/translation.json @@ -65,16 +65,6 @@ "open_download_options": "Ouvrir les options de téléchargement", "download_options_zero": "Aucune option de téléchargement", "download_options_one": "{{count}} option de téléchargement", - "properties": "Propriétés", - "filter_by_source": "Filtrer par source :", - "add_to_favorites": "Ajouter aux favoris", - "remove_from_favorites": "Retirer des favoris", - "failed_update_favorites": "Échec de la mise à jour des favoris", - "game_removed_from_library": "Jeu retiré de la bibliothèque", - "failed_remove_from_library": "Échec du retrait de la bibliothèque", - "files_removed_success": "Fichiers supprimés avec succès", - "failed_remove_files": "Échec de la suppression des fichiers", - "no_repacks_found": "Aucune source trouvée pour ce jeu", "download_options_other": "{{count}} options de téléchargement", "updated_at": "Mis à jour le {{updated_at}}", "install": "Installer", diff --git a/src/locales/hu/translation.json b/src/locales/hu/translation.json index 4b9a53eb..0cea87b0 100644 --- a/src/locales/hu/translation.json +++ b/src/locales/hu/translation.json @@ -39,16 +39,6 @@ "open_download_options": "Letöltési lehetőségek", "download_options_zero": "Nincs letöltési lehetőség", "download_options_one": "{{count}} letöltési lehetőség", - "no_repacks_found": "Nem található forrás ehhez a játékhoz", - "properties": "Tulajdonságok", - "filter_by_source": "Szűrés forrás szerint:", - "add_to_favorites": "Hozzáadás a kedvencekhez", - "remove_from_favorites": "Eltávolítás a kedvencekből", - "failed_update_favorites": "Nem sikerült frissíteni a kedvenceket", - "game_removed_from_library": "A játék eltávolítva a könyvtárból", - "failed_remove_from_library": "Nem sikerült eltávolítani a könyvtárból", - "files_removed_success": "Fájlok sikeresen eltávolítva", - "failed_remove_files": "Nem sikerült eltávolítani a fájlokat", "download_options_other": "{{count}} letöltési lehetőség", "updated_at": "Frissítve: {{updated_at}}", "install": "Letöltés", diff --git a/src/locales/id/translation.json b/src/locales/id/translation.json index 0d0eeb49..d6d591b6 100644 --- a/src/locales/id/translation.json +++ b/src/locales/id/translation.json @@ -102,16 +102,6 @@ "open_download_location": "Lihat file yang diunduh", "create_shortcut": "Buat pintasan desktop", "remove_files": "Hapus file", - "properties": "Properti", - "filter_by_source": "Filter berdasarkan sumber:", - "add_to_favorites": "Tambahkan ke favorit", - "remove_from_favorites": "Hapus dari favorit", - "failed_update_favorites": "Gagal memperbarui favorit", - "game_removed_from_library": "Game dihapus dari perpustakaan", - "failed_remove_from_library": "Gagal menghapus dari perpustakaan", - "files_removed_success": "File berhasil dihapus", - "failed_remove_files": "Gagal menghapus file", - "no_repacks_found": "Tidak ada sumber untuk game ini", "remove_from_library_description": "Ini akan menghapus {{game}} dari perpustakaan kamu", "options": "Opsi", "executable_section_title": "Eksekusi", diff --git a/src/locales/it/translation.json b/src/locales/it/translation.json index f51450c7..b23d1244 100644 --- a/src/locales/it/translation.json +++ b/src/locales/it/translation.json @@ -82,17 +82,7 @@ "previous_screenshot": "Screenshot precedente", "next_screenshot": "Screenshot successivo", "screenshot": "Screenshot {{number}}", - "open_screenshot": "Apri screenshot {{number}}", - "no_repacks_found": "Nessuna fonte trovata per questo gioco", - "properties": "Proprietà", - "filter_by_source": "Filtra per fonte:", - "add_to_favorites": "Aggiungi ai preferiti", - "remove_from_favorites": "Rimuovi dai preferiti", - "failed_update_favorites": "Impossibile aggiornare i preferiti", - "game_removed_from_library": "Gioco rimosso dalla libreria", - "failed_remove_from_library": "Impossibile rimuovere dalla libreria", - "files_removed_success": "File rimossi con successo", - "failed_remove_files": "Impossibile rimuovere i file" + "open_screenshot": "Apri screenshot {{number}}" }, "activation": { "title": "Attiva Hydra", diff --git a/src/locales/kk/translation.json b/src/locales/kk/translation.json index 48b59e65..bfb009a7 100644 --- a/src/locales/kk/translation.json +++ b/src/locales/kk/translation.json @@ -114,17 +114,7 @@ "download_paused": "Жүктеу тоқтатылды", "last_downloaded_option": "Соңғы жүктеу нұсқасы", "create_shortcut_success": "Жарлық жасалды", - "create_shortcut_error": "Жарлық жасау мүмкін болмады", - "no_repacks_found": "Бұл ойын үшін дереккөздер табылмады", - "properties": "Қасиеттер", - "filter_by_source": "Дереккөз бойынша сүзу:", - "add_to_favorites": "Таңдаулыларға қосу", - "remove_from_favorites": "Таңдаулылардан жою", - "failed_update_favorites": "Таңдаулыларды жаңарту сәтсіз аяқталды", - "game_removed_from_library": "Ойын кітапханадан жойылды", - "failed_remove_from_library": "Кітапханадан жою сәтсіз аяқталды", - "files_removed_success": "Файлдар сәтті жойылды", - "failed_remove_files": "Файлдарды жою сәтсіз аяқталды" + "create_shortcut_error": "Жарлық жасау мүмкін болмады" }, "activation": { "title": "Hydra-ны белсендіру", diff --git a/src/locales/ko/translation.json b/src/locales/ko/translation.json index 488f42d0..9ec389b1 100644 --- a/src/locales/ko/translation.json +++ b/src/locales/ko/translation.json @@ -41,16 +41,6 @@ "download_options_one": "{{count}}개의 다운로드 선택지가 존재함", "download_options_other": "{{count}}개의 다운로드 선택지들이 존재함", "updated_at": "{{updated_at}}에 업데이트 됨", - "properties": "속성", - "filter_by_source": "소스별 필터:", - "add_to_favorites": "즐겨찾기에 추가", - "remove_from_favorites": "즐겨찾기에서 제거", - "failed_update_favorites": "즐겨찾기 업데이트 실패", - "game_removed_from_library": "게임이 라이브러리에서 제거되었습니다", - "failed_remove_from_library": "라이브러리에서 제거 실패", - "files_removed_success": "파일이 성공적으로 제거되었습니다", - "failed_remove_files": "파일 제거 실패", - "no_repacks_found": "이 게임에 대한 소스를 찾을 수 없습니다", "install": "설치", "resume": "재개", "pause": "일시 정지", diff --git a/src/locales/nb/translation.json b/src/locales/nb/translation.json index 7684f2ca..8898ec7b 100644 --- a/src/locales/nb/translation.json +++ b/src/locales/nb/translation.json @@ -131,17 +131,7 @@ "download": "Last ned", "executable_path_in_use": "Kjørbar fil blir allerede brukt av \"{{game}}\"", "warning": "Advarsel:", - "hydra_needs_to_remain_open": "Hydra skal forbli åpent for at denne nedlastingen kan gjennomføres. I tilfelle av at Hydra lukker før nedlastingen er ferdig, mister du fremskrittet ditt.", - "properties": "Egenskaper", - "filter_by_source": "Filtrer etter kilde:", - "add_to_favorites": "Legg til i favoritter", - "remove_from_favorites": "Fjern fra favoritter", - "failed_update_favorites": "Kunne ikke oppdatere favoritter", - "game_removed_from_library": "Spillet er fjernet fra biblioteket", - "failed_remove_from_library": "Kunne ikke fjerne fra biblioteket", - "files_removed_success": "Filer fjernet", - "failed_remove_files": "Kunne ikke fjerne filer", - "no_repacks_found": "Ingen kilder funnet for dette spillet" + "hydra_needs_to_remain_open": "Hydra skal forbli åpent for at denne nedlastingen kan gjennomføres. I tilfelle av at Hydra lukker før nedlastingen er ferdig, mister du fremskrittet ditt." }, "activation": { "title": "Aktivér Hydra", diff --git a/src/locales/nl/translation.json b/src/locales/nl/translation.json index 1df74891..72d20c74 100644 --- a/src/locales/nl/translation.json +++ b/src/locales/nl/translation.json @@ -49,16 +49,6 @@ "space_left_on_disk": "{{space}} Over op schijf", "eta": "Conclusie {{eta}}", "downloading_metadata": "Downloading metadata…", - "properties": "Eigenschappen", - "filter_by_source": "Filteren op bron:", - "add_to_favorites": "Toevoegen aan favorieten", - "remove_from_favorites": "Verwijderen uit favorieten", - "failed_update_favorites": "Favorieten bijwerken mislukt", - "game_removed_from_library": "Spel verwijderd uit bibliotheek", - "failed_remove_from_library": "Verwijderen uit bibliotheek mislukt", - "files_removed_success": "Bestanden succesvol verwijderd", - "failed_remove_files": "Verwijderen van bestanden mislukt", - "no_repacks_found": "Geen bronnen gevonden voor deze game", "filter": "Filter repacks", "requirements": "Systeem vereisten", "minimum": "Minimaal", diff --git a/src/locales/pl/translation.json b/src/locales/pl/translation.json index 29c44d4a..86751b0e 100644 --- a/src/locales/pl/translation.json +++ b/src/locales/pl/translation.json @@ -41,16 +41,6 @@ "download_options_zero": "Brak opcji pobierania", "download_options_one": "{{count}} opcja pobierania", "download_options_other": "{{count}} opcji pobierania", - "properties": "Właściwości", - "filter_by_source": "Filtruj według źródła:", - "add_to_favorites": "Dodaj do ulubionych", - "remove_from_favorites": "Usuń z ulubionych", - "failed_update_favorites": "Nie udało się zaktualizować ulubionych", - "game_removed_from_library": "Gra została usunięta z biblioteki", - "failed_remove_from_library": "Nie udało się usunąć z biblioteki", - "files_removed_success": "Pliki zostały pomyślnie usunięte", - "failed_remove_files": "Nie udało się usunąć plików", - "no_repacks_found": "Nie znaleziono źródeł dla tej gry", "updated_at": "Zaktualizowano {{updated_at}}", "install": "Instaluj", "resume": "Wznów", diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index 08228c2a..0c4f7d89 100755 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -90,7 +90,6 @@ "playing_now": "Jogando agora", "change": "Explorar", "repacks_modal_description": "Escolha o repack do jogo que deseja baixar", - "no_repacks_found": "Nenhuma fonte encontrada para este jogo", "select_folder_hint": "Para trocar o diretório padrão, acesse a <0>Tela de Ajustes", "download_now": "Iniciar download", "no_shop_details": "Não foi possível obter os detalhes da loja.", @@ -111,7 +110,6 @@ "remove_files": "Remover arquivos", "options": "Gerenciar", "properties": "Propriedades", - "filter_by_source": "Filtrar por fonte:", "remove_from_library_description": "Isso irá remover {{game}} da sua biblioteca", "remove_from_library_title": "Tem certeza?", "executable_section_title": "Executável", diff --git a/src/locales/pt-PT/translation.json b/src/locales/pt-PT/translation.json index 0f6eb393..6c32b35b 100644 --- a/src/locales/pt-PT/translation.json +++ b/src/locales/pt-PT/translation.json @@ -49,16 +49,6 @@ "open_download_options": "Ver opções de transferência", "download_options_zero": "Sem opções de transferência", "download_options_one": "{{count}} opção de transferência", - "properties": "Propriedades", - "filter_by_source": "Filtrar por fonte:", - "add_to_favorites": "Adicionar aos favoritos", - "remove_from_favorites": "Remover dos favoritos", - "failed_update_favorites": "Falha ao atualizar favoritos", - "game_removed_from_library": "Jogo removido da biblioteca", - "failed_remove_from_library": "Falha ao remover da biblioteca", - "files_removed_success": "Ficheiros removidos com sucesso", - "failed_remove_files": "Falha ao remover ficheiros", - "no_repacks_found": "Não foram encontradas fontes para este jogo", "download_options_other": "{{count}} opções de transferência", "updated_at": "Atualizado a {{updated_at}}", "resume": "Continuar", diff --git a/src/locales/ro/translation.json b/src/locales/ro/translation.json index cc02450a..c5a81881 100644 --- a/src/locales/ro/translation.json +++ b/src/locales/ro/translation.json @@ -51,16 +51,6 @@ "eta": "Concluzie {{eta}}", "calculating_eta": "Calculare timp rămas...", "downloading_metadata": "Se descarcă metadata...", - "properties": "Proprietăți", - "filter_by_source": "Filtrează după sursă:", - "add_to_favorites": "Adaugă la favorite", - "remove_from_favorites": "Elimină din favorite", - "failed_update_favorites": "Actualizarea favoritelor a eșuat", - "game_removed_from_library": "Jocul a fost eliminat din bibliotecă", - "failed_remove_from_library": "Eliminarea din bibliotecă a eșuat", - "files_removed_success": "Fișiere eliminate cu succes", - "failed_remove_files": "Eliminarea fișierelor a eșuat", - "no_repacks_found": "Nu s-au găsit surse pentru acest joc", "filter": "Filtrează repack-urile", "requirements": "Cerințe de sistem", "minimum": "Minim", diff --git a/src/locales/ru/translation.json b/src/locales/ru/translation.json index b854d5a5..b1714804 100644 --- a/src/locales/ru/translation.json +++ b/src/locales/ru/translation.json @@ -66,16 +66,6 @@ "open_download_options": "Открыть источники", "download_options_zero": "Нет источников", "download_options_one": "{{count}} источник", - "properties": "Свойства", - "filter_by_source": "Фильтровать по источнику:", - "add_to_favorites": "Добавить в избранное", - "remove_from_favorites": "Удалить из избранного", - "failed_update_favorites": "Не удалось обновить избранное", - "game_removed_from_library": "Игра удалена из библиотеки", - "failed_remove_from_library": "Не удалось удалить из библиотеки", - "files_removed_success": "Файлы успешно удалены", - "failed_remove_files": "Не удалось удалить файлы", - "no_repacks_found": "Не найдено источников для этой игры", "download_options_other": "{{count}} источников", "updated_at": "Обновлено {{updated_at}}", "install": "Установить", diff --git a/src/locales/sv/translation.json b/src/locales/sv/translation.json index 8cbdfaf1..0972effa 100644 --- a/src/locales/sv/translation.json +++ b/src/locales/sv/translation.json @@ -64,16 +64,6 @@ "open_download_options": "Öppna nedladdningsalternativ", "download_options_zero": "Inget nedladdningsalternativ", "download_options_one": "{{count}} nedladdningsalternativ", - "no_repacks_found": "Inga källor hittades för detta spel", - "properties": "Egenskaper", - "filter_by_source": "Filtrera efter källa:", - "add_to_favorites": "Lägg till i favoriter", - "remove_from_favorites": "Ta bort från favoriter", - "failed_update_favorites": "Misslyckades med att uppdatera favoriter", - "game_removed_from_library": "Spelet har tagits bort från biblioteket", - "failed_remove_from_library": "Misslyckades med att ta bort från biblioteket", - "files_removed_success": "Filer har tagits bort", - "failed_remove_files": "Misslyckades med att ta bort filer", "download_options_other": "{{count}} nedladdningsalternativ", "updated_at": "Uppdaterad {{updated_at}}", "install": "Installera", diff --git a/src/locales/tr/translation.json b/src/locales/tr/translation.json index 218ffa39..c3fe2081 100644 --- a/src/locales/tr/translation.json +++ b/src/locales/tr/translation.json @@ -64,16 +64,6 @@ "open_download_options": "İndirme seçeneklerini aç", "download_options_zero": "İndirme seçeneği yok", "download_options_one": "{{count}} indirme seçeneği", - "properties": "Özellikler", - "filter_by_source": "Kaynağa göre filtrele:", - "add_to_favorites": "Favorilere ekle", - "remove_from_favorites": "Favorilerden kaldır", - "failed_update_favorites": "Favoriler güncellenemedi", - "game_removed_from_library": "Oyun kütüphaneden kaldırıldı", - "failed_remove_from_library": "Kütüphaneden kaldırma başarısız oldu", - "files_removed_success": "Dosyalar başarıyla kaldırıldı", - "failed_remove_files": "Dosyalar kaldırılamadı", - "no_repacks_found": "Bu oyun için kaynak bulunamadı", "download_options_other": "{{count}} indirme seçeneği", "updated_at": "{{updated_at}} tarihinde güncellendi", "install": "Yükle", diff --git a/src/locales/uk/translation.json b/src/locales/uk/translation.json index 624138ee..48a3972d 100644 --- a/src/locales/uk/translation.json +++ b/src/locales/uk/translation.json @@ -68,16 +68,6 @@ "download_options_other": "{{count}} варіантів завантаження", "updated_at": "Оновлено {{updated_at}}", "install": "Встановити", - "properties": "Властивості", - "filter_by_source": "Фільтрувати за джерелом:", - "add_to_favorites": "Додати до обраного", - "remove_from_favorites": "Видалити з обраного", - "failed_update_favorites": "Не вдалося оновити обране", - "game_removed_from_library": "Гру видалено з бібліотеки", - "failed_remove_from_library": "Не вдалося видалити з бібліотеки", - "files_removed_success": "Файли успішно видалено", - "failed_remove_files": "Не вдалося видалити файли", - "no_repacks_found": "Не знайдено джерел для цієї гри", "resume": "Відновити", "pause": "Призупинити", "cancel": "Скасувати", diff --git a/src/locales/uz/translation.json b/src/locales/uz/translation.json index a81fea55..d20a9677 100644 --- a/src/locales/uz/translation.json +++ b/src/locales/uz/translation.json @@ -64,16 +64,6 @@ "play_time": "O'ynalgan vaqt {{amount}}", "last_time_played": "Oxirgi ishga tushirilgan {{period}}", "not_played_yet": "Siz hali {{title}}ni o'ynamagansiz", - "properties": "Xususiyatlar", - "filter_by_source": "Manba bo'yicha filtrlash:", - "add_to_favorites": "Sevimlilarga qo'shish", - "remove_from_favorites": "Sevimlilardan olib tashlash", - "failed_update_favorites": "Sevimlilarni yangilash muvaffaqiyatsiz tugadi", - "game_removed_from_library": "O'yin kutubxonadan olib tashlandi", - "failed_remove_from_library": "Kutubxonadan olib tashlash muvaffaqiyatsiz tugadi", - "files_removed_success": "Fayllar muvaffaqiyatli olib tashlandi", - "failed_remove_files": "Fayllarni olib tashlash muvaffaqiyatsiz tugadi", - "no_repacks_found": "Bu o'yin uchun manbalar topilmadi", "next_suggestion": "Keyingi taklif", "play": "O'ynash", "deleting": "O'rnatuvchi o'chirilmoqda…", diff --git a/src/locales/zh/translation.json b/src/locales/zh/translation.json index 97c143b9..0323d991 100644 --- a/src/locales/zh/translation.json +++ b/src/locales/zh/translation.json @@ -72,16 +72,6 @@ "install": "安装", "resume": "恢复", "pause": "暂停", - "properties": "属性", - "filter_by_source": "按来源筛选:", - "add_to_favorites": "添加到收藏夹", - "remove_from_favorites": "从收藏夹移除", - "failed_update_favorites": "收藏夹更新失败", - "game_removed_from_library": "游戏已从库中移除", - "failed_remove_from_library": "从库中移除失败", - "files_removed_success": "文件已成功移除", - "failed_remove_files": "文件移除失败", - "no_repacks_found": "未找到该游戏的资源", "cancel": "取消", "remove": "移除", "space_left_on_disk": "磁盘剩余空间{{space}}", diff --git a/src/renderer/src/pages/game-details/modals/repacks-modal.scss b/src/renderer/src/pages/game-details/modals/repacks-modal.scss index 392bd7cf..8bec90bd 100644 --- a/src/renderer/src/pages/game-details/modals/repacks-modal.scss +++ b/src/renderer/src/pages/game-details/modals/repacks-modal.scss @@ -2,43 +2,7 @@ .repacks-modal { &__filter-container { - margin-bottom: 1rem; - } - - &__filter-top { - margin-bottom: 1rem; - display: flex; - flex-direction: column; - gap: 0.5rem; - } - - &__filter-toggle { - align-self: flex-start; - display: flex; - align-items: center; - gap: 0.5rem; - font-size: globals.$small-font-size; - font-weight: 600; - color: var(--color-text-secondary); - padding: 0.5rem 0.75rem; - border-radius: 6px; - transition: background-color 0.2s ease; - display: flex; - flex-direction: column; - gap: 0.5rem; - } - - &__filter-toggle { - align-self: flex-start; - display: flex; - align-items: center; - gap: 0.5rem; - font-size: globals.$small-font-size; - font-weight: 600; - color: var(--color-text-secondary); - padding: 0.5rem 0.75rem; - border-radius: 6px; - transition: background-color 0.2s ease; + margin-bottom: calc(globals.$spacing-unit * 2); } &__repacks { @@ -65,104 +29,4 @@ &__repack-info { font-size: globals.$small-font-size; } - - &__no-results { - width: 100%; - padding: calc(globals.$spacing-unit * 4) 0; - text-align: center; - color: globals.$muted-color; - font-size: globals.$small-font-size; - display: flex; - align-items: center; - justify-content: center; - } - - &__no-results-content { - display: flex; - flex-direction: column; - align-items: center; - gap: calc(globals.$spacing-unit * 1.5); - max-width: 480px; - width: 100%; - } - - &__no-results-text { - color: globals.$muted-color; - font-size: globals.$small-font-size; - text-align: center; - } - - &__no-results-button { - display: flex; - justify-content: center; - width: 100%; - } - - &__download-sources { - padding: 0; - background-color: var(--color-background-light); - border-radius: 8px; - margin-bottom: 1rem; - margin-top: calc(globals.$spacing-unit * 0.5); - max-height: 0; - overflow: hidden; - transition: - max-height 0.3s ease, - padding 0.3s ease; - - &--open { - padding: 0.75rem; - max-height: 250px; - } - } - - &__filter-label { - display: none; - font-size: globals.$small-font-size; - font-weight: 600; - margin-bottom: 0.75rem; - color: var(--color-text-secondary); - width: 100%; - } - - &__source-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); - grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); - gap: 0.5rem; - max-height: 200px; - overflow-y: auto; - overflow-x: hidden; - overflow-x: hidden; - align-items: start; - padding-right: 0.25rem; /* Espaço para a barra de rolagem */ - } - - &__source-item { - padding: 0.35rem 0.5rem; - padding: 0.35rem 0.5rem; - background: var(--color-surface, rgba(0, 0, 0, 0.03)); - border: 1px solid var(--color-border); - border-radius: 6px; - display: flex; - align-items: center; - min-height: 38px; - box-sizing: border-box; - width: 100%; - width: 100%; - } - - &__source-item :global(.checkbox-field) { - width: 100%; - min-width: 0; - } - - &__source-item :global(.checkbox-field__label) { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - display: block; - font-size: 0.85rem; - width: 100%; - } -} +} \ No newline at end of file diff --git a/src/renderer/src/pages/game-details/modals/repacks-modal.tsx b/src/renderer/src/pages/game-details/modals/repacks-modal.tsx index 32e330c6..ad45a73f 100644 --- a/src/renderer/src/pages/game-details/modals/repacks-modal.tsx +++ b/src/renderer/src/pages/game-details/modals/repacks-modal.tsx @@ -1,7 +1,5 @@ import { useContext, useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; -import { useNavigate } from "react-router-dom"; -import { PlusCircleIcon, ChevronDownIcon, ChevronUpIcon } from "@primer/octicons-react"; import { Badge, @@ -9,10 +7,7 @@ import { DebridBadge, Modal, TextField, - CheckboxField, } from "@renderer/components"; -import { downloadSourcesTable } from "@renderer/dexie"; -import type { DownloadSource } from "@types"; import type { GameRepack } from "@types"; import { DownloadSettingsModal } from "./download-settings-modal"; @@ -41,13 +36,6 @@ export function RepacksModal({ const [filteredRepacks, setFilteredRepacks] = useState([]); const [repack, setRepack] = useState(null); const [showSelectFolderModal, setShowSelectFolderModal] = useState(false); - const [downloadSources, setDownloadSources] = useState( - [] - ); - const [selectedFingerprints, setSelectedFingerprints] = useState( - [] - ); - const [filterTerm, setFilterTerm] = useState(""); const [hashesInDebrid, setHashesInDebrid] = useState>( {} @@ -58,7 +46,6 @@ export function RepacksModal({ const { t } = useTranslation("game_details"); const { formatDate } = useDate(); - const navigate = useNavigate(); const getHashFromMagnet = (magnet: string) => { if (!magnet || typeof magnet !== "string") { @@ -103,39 +90,8 @@ export function RepacksModal({ }, [repacks, hashesInDebrid]); useEffect(() => { - downloadSourcesTable.toArray().then((sources) => { - const uniqueRepackers = new Set(sortedRepacks.map((r) => r.repacker)); - const filteredSources = sources.filter( - (s) => s.name && uniqueRepackers.has(s.name) && !!s.fingerprint - ); - setDownloadSources(filteredSources); - }); - }, [sortedRepacks]); - - useEffect(() => { - const term = filterTerm.trim().toLowerCase(); - - const byTerm = sortedRepacks.filter((repack) => { - if (!term) return true; - const lowerTitle = repack.title.toLowerCase(); - const lowerRepacker = repack.repacker.toLowerCase(); - return ( - lowerTitle.includes(term) || lowerRepacker.includes(term) - ); - }); - - const bySource = byTerm.filter((repack) => { - if (selectedFingerprints.length === 0) return true; - - return downloadSources.some( - (src) => - selectedFingerprints.includes(src.fingerprint) && - src.name === repack.repacker - ); - }); - - setFilteredRepacks(bySource); - }, [sortedRepacks, filterTerm, selectedFingerprints, downloadSources]); + setFilteredRepacks(sortedRepacks); + }, [sortedRepacks, visible, game]); const handleRepackClick = (repack: GameRepack) => { setRepack(repack); @@ -143,14 +99,17 @@ export function RepacksModal({ }; const handleFilter: React.ChangeEventHandler = (event) => { - setFilterTerm(event.target.value); - }; + const term = event.target.value.toLocaleLowerCase(); - const toggleFingerprint = (fingerprint: string) => { - setSelectedFingerprints((prev) => - prev.includes(fingerprint) - ? prev.filter((f) => f !== fingerprint) - : [...prev, fingerprint] + setFilteredRepacks( + sortedRepacks.filter((repack) => { + const lowerCaseTitle = repack.title.toLowerCase(); + const lowerCaseRepacker = repack.repacker.toLowerCase(); + + return [lowerCaseTitle, lowerCaseRepacker].some((value) => + value.includes(term) + ); + }) ); }; @@ -159,8 +118,6 @@ export function RepacksModal({ return repack.uris.some((uri) => uri.includes(game.download!.uri)); }; - const [isFilterDrawerOpen, setIsFilterDrawerOpen] = useState(false); - return ( <>
      -
      - - -
      - -
      -
      - {downloadSources.map((source) => ( -
      - toggleFingerprint(source.fingerprint)} - /> -
      - ))} -
      -
      +
      - {filteredRepacks.length === 0 ? ( -
      -
      -
      {t("no_repacks_found")}
      -
      - -
      -
      -
      - ) : ( - filteredRepacks.map((repack) => { - const isLastDownloadedOption = checkIfLastDownloadedOption(repack); + {filteredRepacks.map((repack) => { + const isLastDownloadedOption = checkIfLastDownloadedOption(repack); - return ( - - ); - }) - )} + {hashesInDebrid[getHashFromMagnet(repack.uris[0]) ?? ""] && ( + + )} + + ); + })}
      ); -} +} \ No newline at end of file From 6c34e5516f93f6e97ef7d4b16c17dbd232ad8f75 Mon Sep 17 00:00:00 2001 From: caduHD4 Date: Sun, 28 Sep 2025 02:33:05 -0300 Subject: [PATCH 12/15] fix: add newline at the end of repacks-modal.scss --- src/renderer/src/pages/game-details/modals/repacks-modal.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/src/pages/game-details/modals/repacks-modal.scss b/src/renderer/src/pages/game-details/modals/repacks-modal.scss index 8bec90bd..2eb2512d 100644 --- a/src/renderer/src/pages/game-details/modals/repacks-modal.scss +++ b/src/renderer/src/pages/game-details/modals/repacks-modal.scss @@ -29,4 +29,4 @@ &__repack-info { font-size: globals.$small-font-size; } -} \ No newline at end of file +} From aacf9abc6aef60f99b2ef82f51011a7e8e039ac3 Mon Sep 17 00:00:00 2001 From: ItsYeBoi20 Date: Wed, 1 Oct 2025 05:04:34 +0200 Subject: [PATCH 13/15] Update PR #1452 to latest HydraLauncher and fix conflicts --- python_rpc/http_multi_link_downloader.py | 151 +++++++++ python_rpc/main.py | 58 +++- src/locales/en/translation.json | 73 +--- src/locales/ro/translation.json | 13 +- src/main/events/index.ts | 8 +- .../authenticate-all-debrid.ts | 17 + src/main/main.ts | 5 + src/main/services/download/all-debrid.ts | 315 ++++++++++++++++++ .../services/download/download-manager.ts | 23 ++ src/main/services/download/helpers.ts | 21 +- src/main/services/download/index.ts | 1 + src/main/services/python-rpc.ts | 6 +- src/preload/index.ts | 48 +-- src/renderer/src/constants.ts | 1 + src/renderer/src/declaration.d.ts | 43 +-- .../src/pages/downloads/download-group.tsx | 22 +- .../game-details/hero/hero-panel-actions.tsx | 2 +- .../modals/download-settings-modal.tsx | 5 + .../pages/settings/settings-all-debrid.scss | 12 + .../pages/settings/settings-all-debrid.tsx | 129 +++++++ src/renderer/src/pages/settings/settings.tsx | 6 + src/shared/constants.ts | 2 + src/shared/index.ts | 1 + src/types/download.types.ts | 8 + src/types/level.types.ts | 13 +- tsconfig.web.json | 1 + 26 files changed, 805 insertions(+), 179 deletions(-) create mode 100644 python_rpc/http_multi_link_downloader.py create mode 100644 src/main/events/user-preferences/authenticate-all-debrid.ts create mode 100644 src/main/services/download/all-debrid.ts create mode 100644 src/renderer/src/pages/settings/settings-all-debrid.scss create mode 100644 src/renderer/src/pages/settings/settings-all-debrid.tsx diff --git a/python_rpc/http_multi_link_downloader.py b/python_rpc/http_multi_link_downloader.py new file mode 100644 index 00000000..3968d77c --- /dev/null +++ b/python_rpc/http_multi_link_downloader.py @@ -0,0 +1,151 @@ +import aria2p +from aria2p.client import ClientException as DownloadNotFound + +class HttpMultiLinkDownloader: + def __init__(self): + self.downloads = [] + self.completed_downloads = [] + self.total_size = None + self.aria2 = aria2p.API( + aria2p.Client( + host="http://localhost", + port=6800, + secret="" + ) + ) + + def start_download(self, urls: list[str], save_path: str, header: str = None, out: str = None, total_size: int = None): + """Add multiple URLs to download queue with same options""" + options = {"dir": save_path} + if header: + options["header"] = header + if out: + options["out"] = out + + # Clear any existing downloads first + self.cancel_download() + self.completed_downloads = [] + self.total_size = total_size + + for url in urls: + try: + added_downloads = self.aria2.add(url, options=options) + self.downloads.extend(added_downloads) + except Exception as e: + print(f"Error adding download for URL {url}: {str(e)}") + + def pause_download(self): + """Pause all active downloads""" + if self.downloads: + try: + self.aria2.pause(self.downloads) + except Exception as e: + print(f"Error pausing downloads: {str(e)}") + + def cancel_download(self): + """Cancel and remove all downloads""" + if self.downloads: + try: + # First try to stop the downloads + self.aria2.remove(self.downloads) + except Exception as e: + print(f"Error removing downloads: {str(e)}") + finally: + # Clear the downloads list regardless of success/failure + self.downloads = [] + self.completed_downloads = [] + + def get_download_status(self): + """Get status for all tracked downloads, auto-remove completed/failed ones""" + if not self.downloads and not self.completed_downloads: + return [] + + total_completed = 0 + current_download_speed = 0 + active_downloads = [] + to_remove = [] + + # First calculate sizes from completed downloads + for completed in self.completed_downloads: + total_completed += completed['size'] + + # Then check active downloads + for download in self.downloads: + try: + current_download = self.aria2.get_download(download.gid) + + # Skip downloads that are not properly initialized + if not current_download or not current_download.files: + to_remove.append(download) + continue + + # Add to completed size and speed calculations + total_completed += current_download.completed_length + current_download_speed += current_download.download_speed + + # If download is complete, move it to completed_downloads + if current_download.status == 'complete': + self.completed_downloads.append({ + 'name': current_download.name, + 'size': current_download.total_length + }) + to_remove.append(download) + else: + active_downloads.append({ + 'name': current_download.name, + 'size': current_download.total_length, + 'completed': current_download.completed_length, + 'speed': current_download.download_speed + }) + + except DownloadNotFound: + to_remove.append(download) + continue + except Exception as e: + print(f"Error getting download status: {str(e)}") + continue + + # Clean up completed/removed downloads from active list + for download in to_remove: + try: + if download in self.downloads: + self.downloads.remove(download) + except ValueError: + pass + + # Return aggregate status + if self.total_size or active_downloads or self.completed_downloads: + # Use the first active download's name as the folder name, or completed if none active + folder_name = None + if active_downloads: + folder_name = active_downloads[0]['name'] + elif self.completed_downloads: + folder_name = self.completed_downloads[0]['name'] + + if folder_name and '/' in folder_name: + folder_name = folder_name.split('/')[0] + + # Use provided total size if available, otherwise sum from downloads + total_size = self.total_size + if not total_size: + total_size = sum(d['size'] for d in active_downloads) + sum(d['size'] for d in self.completed_downloads) + + # Calculate completion status based on total downloaded vs total size + is_complete = len(active_downloads) == 0 and total_completed >= (total_size * 0.99) # Allow 1% margin for size differences + + # If all downloads are complete, clear the completed_downloads list to prevent status updates + if is_complete: + self.completed_downloads = [] + + return [{ + 'folderName': folder_name, + 'fileSize': total_size, + 'progress': total_completed / total_size if total_size > 0 else 0, + 'downloadSpeed': current_download_speed, + 'numPeers': 0, + 'numSeeds': 0, + 'status': 'complete' if is_complete else 'active', + 'bytesDownloaded': total_completed, + }] + + return [] \ No newline at end of file diff --git a/python_rpc/main.py b/python_rpc/main.py index 152f8ffb..36170025 100644 --- a/python_rpc/main.py +++ b/python_rpc/main.py @@ -3,6 +3,7 @@ import sys, json, urllib.parse, psutil from torrent_downloader import TorrentDownloader from http_downloader import HttpDownloader from profile_image_processor import ProfileImageProcessor +from http_multi_link_downloader import HttpMultiLinkDownloader import libtorrent as lt app = Flask(__name__) @@ -24,7 +25,15 @@ if start_download_payload: initial_download = json.loads(urllib.parse.unquote(start_download_payload)) downloading_game_id = initial_download['game_id'] - if initial_download['url'].startswith('magnet'): + if isinstance(initial_download['url'], list): + # Handle multiple URLs using HttpMultiLinkDownloader + http_multi_downloader = HttpMultiLinkDownloader() + downloads[initial_download['game_id']] = http_multi_downloader + try: + http_multi_downloader.start_download(initial_download['url'], initial_download['save_path'], initial_download.get('header'), initial_download.get("out")) + except Exception as e: + print("Error starting multi-link download", e) + elif initial_download['url'].startswith('magnet'): torrent_downloader = TorrentDownloader(torrent_session) downloads[initial_download['game_id']] = torrent_downloader try: @@ -62,12 +71,23 @@ def status(): return auth_error downloader = downloads.get(downloading_game_id) - if downloader: - status = downloads.get(downloading_game_id).get_download_status() - return jsonify(status), 200 - else: + if not downloader: return jsonify(None) + status = downloader.get_download_status() + if not status: + return jsonify(None) + + if isinstance(status, list): + if not status: # Empty list + return jsonify(None) + + # For multi-link downloader, use the aggregated status + # The status will already be aggregated by the HttpMultiLinkDownloader + return jsonify(status[0]), 200 + + return jsonify(status), 200 + @app.route("/seed-status", methods=["GET"]) def seed_status(): auth_error = validate_rpc_password() @@ -81,10 +101,24 @@ def seed_status(): continue response = downloader.get_download_status() - if response is None: + if not response: continue - if response.get('status') == 5: + if isinstance(response, list): + # For multi-link downloader, check if all files are complete + if response and all(item['status'] == 'complete' for item in response): + seed_status.append({ + 'gameId': game_id, + 'status': 'complete', + 'folderName': response[0]['folderName'], + 'fileSize': sum(item['fileSize'] for item in response), + 'bytesDownloaded': sum(item['bytesDownloaded'] for item in response), + 'downloadSpeed': 0, + 'numPeers': 0, + 'numSeeds': 0, + 'progress': 1.0 + }) + elif response.get('status') == 5: # Original torrent seeding check seed_status.append({ 'gameId': game_id, **response, @@ -143,7 +177,15 @@ def action(): existing_downloader = downloads.get(game_id) - if url.startswith('magnet'): + if isinstance(url, list): + # Handle multiple URLs using HttpMultiLinkDownloader + if existing_downloader and isinstance(existing_downloader, HttpMultiLinkDownloader): + existing_downloader.start_download(url, data['save_path'], data.get('header'), data.get('out')) + else: + http_multi_downloader = HttpMultiLinkDownloader() + downloads[game_id] = http_multi_downloader + http_multi_downloader.start_download(url, data['save_path'], data.get('header'), data.get('out')) + elif url.startswith('magnet'): if existing_downloader and isinstance(existing_downloader, TorrentDownloader): existing_downloader.start_download(url, data['save_path']) else: diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index bcf8cf54..bf0cbb73 100755 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -4,6 +4,7 @@ "successfully_signed_in": "Successfully signed in" }, "home": { + "featured": "Featured", "surprise_me": "Surprise me", "no_results": "No results found", "start_typing": "Starting typing to search...", @@ -27,56 +28,7 @@ "friends": "Friends", "need_help": "Need help?", "favorites": "Favorites", - "playable_button_title": "Show only games you can play now", - "add_custom_game_tooltip": "Add Custom Game", - "show_playable_only_tooltip": "Show Playable Only", - "custom_game_modal": "Add Custom Game", - "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_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_success": "Custom game added successfully", - "custom_game_modal_failed": "Failed to add custom game", - "custom_game_modal_executable": "Executable", - "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", - "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", - "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" + "playable_button_title": "Show only games you can play now" }, "header": { "search": "Search games", @@ -279,7 +231,6 @@ "backup_unfrozen": "Backup unpinned", "backup_freeze_failed": "Failed to freeze backup", "backup_freeze_failed_description": "You must leave at least one free slot for automatic backups", - "edit_game_modal_button": "Customize game assets", "game_details": "Game Details", "currency_symbol": "$", "currency_country": "us", @@ -290,11 +241,10 @@ "keyshop_price": "Keyshop price", "historical_retail": "Historical retail", "historical_keyshop": "Historical keyshop", + "supported_languages": "Supported languages", "language": "Language", "caption": "Caption", - "audio": "Audio", - "filter_by_source": "Filter by source", - "no_repacks_found": "No sources found for this game" + "audio": "Audio" }, "activation": { "title": "Activate Hydra", @@ -332,6 +282,7 @@ "stop_seeding": "Stop seeding", "resume_seeding": "Resume seeding", "options": "Manage", + "alldebrid_size_not_supported": "Download info for AllDebrid is not supported yet", "extract": "Extract files", "extracting": "Extracting files…" }, @@ -340,6 +291,7 @@ "change": "Update", "notifications": "Notifications", "enable_download_notifications": "When a download is complete", + "gg_deals_api_key_description": "gg deals api key. Used to show the lowest price. (https://gg.deals/api/)", "enable_repack_list_notifications": "When a new repack is added", "real_debrid_api_token_label": "Real-Debrid API token", "quit_app_instead_hiding": "Don't hide Hydra when closing", @@ -441,6 +393,17 @@ "create_real_debrid_account": "Click here if you don't have a Real-Debrid account yet", "create_torbox_account": "Click here if you don't have a TorBox account yet", "real_debrid_account_linked": "Real-Debrid account linked", + "enable_all_debrid": "Enable All-Debrid", + "all_debrid_description": "All-Debrid is an unrestricted downloader that allows you to quickly download files from various sources.", + "all_debrid_free_account_error": "The account \"{{username}}\" is a free account. Please subscribe to All-Debrid", + "all_debrid_account_linked": "All-Debrid account linked successfully", + "alldebrid_missing_key": "Please provide an API key", + "alldebrid_invalid_key": "Invalid API key", + "alldebrid_blocked": "Your API key is geo-blocked or IP-blocked", + "alldebrid_banned": "This account has been banned", + "alldebrid_unknown_error": "An unknown error occurred", + "alldebrid_invalid_response": "Invalid response from All-Debrid", + "alldebrid_network_error": "Network error. Please check your connection", "name_min_length": "Theme name must be at least 3 characters long", "import_theme": "Import theme", "import_theme_description": "You will import {{theme}} from the theme store", @@ -514,8 +477,6 @@ "user_profile": { "amount_hours": "{{amount}} hours", "amount_minutes": "{{amount}} minutes", - "amount_hours_short": "{{amount}}h", - "amount_minutes_short": "{{amount}}m", "last_time_played": "Last played {{period}}", "activity": "Recent Activity", "library": "Library", diff --git a/src/locales/ro/translation.json b/src/locales/ro/translation.json index 8ed6fd39..2c4e68a4 100644 --- a/src/locales/ro/translation.json +++ b/src/locales/ro/translation.json @@ -1,6 +1,7 @@ { "language_name": "Română", "home": { + "featured": "Recomandate", "surprise_me": "Surprinde-mă", "no_results": "Niciun rezultat găsit" }, @@ -18,6 +19,7 @@ }, "header": { "search": "Caută jocuri", + "home": "Acasă", "catalogue": "Catalog", "downloads": "Descărcări", @@ -30,7 +32,10 @@ "downloading": "Se descarcă {{title}}... ({{percentage}} complet) - Concluzie {{eta}} - {{speed}}", "calculating_eta": "Se descarcă {{title}}... ({{percentage}} complet) - Calculare timp rămas..." }, - "catalogue": {}, + "catalogue": { + "next_page": "Pagina următoare", + "previous_page": "Pagina anterioară" + }, "game_details": { "open_download_options": "Deschide opțiunile de descărcare", "download_options_zero": "Nicio opțiune de descărcare", @@ -135,7 +140,11 @@ "real_debrid_free_account_error": "Contul \"{{username}}\" este un cont gratuit. Te rugăm să te abonezi la Real-Debrid", "debrid_linked_message": "Contul \"{{username}}\" a fost legat", "save_changes": "Salvează modificările", - "changes_saved": "Modificările au fost salvate cu succes" + "changes_saved": "Modificările au fost salvate cu succes", + "enable_all_debrid": "Activează All-Debrid", + "all_debrid_description": "All-Debrid este un descărcător fără restricții care îți permite să descarci fișiere din diverse surse.", + "all_debrid_free_account_error": "Contul \"{{username}}\" este un cont gratuit. Te rugăm să te abonezi la All-Debrid", + "all_debrid_account_linked": "Contul All-Debrid a fost conectat cu succes" }, "notifications": { "download_complete": "Descărcare completă", diff --git a/src/main/events/index.ts b/src/main/events/index.ts index d4c461f8..8cb48d47 100644 --- a/src/main/events/index.ts +++ b/src/main/events/index.ts @@ -14,9 +14,6 @@ import "./catalogue/get-developers"; import "./hardware/get-disk-free-space"; import "./hardware/check-folder-write-permission"; import "./library/add-game-to-library"; -import "./library/add-custom-game-to-library"; -import "./library/update-custom-game"; -import "./library/update-game-custom-assets"; import "./library/add-game-to-favorites"; import "./library/remove-game-from-favorites"; import "./library/toggle-game-pin"; @@ -40,9 +37,7 @@ import "./library/reset-game-achievements"; import "./library/change-game-playtime"; import "./library/toggle-automatic-cloud-sync"; import "./library/get-default-wine-prefix-selection-path"; -import "./library/cleanup-unused-assets"; import "./library/create-steam-shortcut"; -import "./library/copy-custom-game-asset"; import "./misc/open-checkout"; import "./misc/open-external"; import "./misc/show-open-dialog"; @@ -51,10 +46,9 @@ 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 "./user-preferences/authenticate-all-debrid"; import "./torrenting/resume-game-download"; import "./torrenting/start-game-download"; import "./torrenting/pause-game-seed"; diff --git a/src/main/events/user-preferences/authenticate-all-debrid.ts b/src/main/events/user-preferences/authenticate-all-debrid.ts new file mode 100644 index 00000000..713db965 --- /dev/null +++ b/src/main/events/user-preferences/authenticate-all-debrid.ts @@ -0,0 +1,17 @@ +import { AllDebridClient } from "@main/services/download/all-debrid"; +import { registerEvent } from "../register-event"; + +const authenticateAllDebrid = async ( + _event: Electron.IpcMainInvokeEvent, + apiKey: string +) => { + AllDebridClient.authorize(apiKey); + const result = await AllDebridClient.getUser(); + if ("error_code" in result) { + return { error_code: result.error_code }; + } + + return result.user; +}; + +registerEvent("authenticateAllDebrid", authenticateAllDebrid); diff --git a/src/main/main.ts b/src/main/main.ts index f96662c3..67391057 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -8,6 +8,7 @@ import { CommonRedistManager, TorBoxClient, RealDebridClient, + AllDebridClient, Aria2, DownloadManager, HydraApi, @@ -37,6 +38,10 @@ export const loadState = async () => { RealDebridClient.authorize(userPreferences.realDebridApiToken); } + if (userPreferences?.allDebridApiKey) { + AllDebridClient.authorize(userPreferences.allDebridApiKey); + } + if (userPreferences?.torBoxApiToken) { TorBoxClient.authorize(userPreferences.torBoxApiToken); } diff --git a/src/main/services/download/all-debrid.ts b/src/main/services/download/all-debrid.ts new file mode 100644 index 00000000..05ee56c6 --- /dev/null +++ b/src/main/services/download/all-debrid.ts @@ -0,0 +1,315 @@ +import axios, { AxiosInstance } from "axios"; +import type { AllDebridUser } from "@types"; +import { logger } from "@main/services"; + +interface AllDebridMagnetStatus { + id: number; + filename: string; + size: number; + status: string; + statusCode: number; + downloaded: number; + uploaded: number; + seeders: number; + downloadSpeed: number; + uploadSpeed: number; + uploadDate: number; + completionDate: number; + links: Array<{ + link: string; + filename: string; + size: number; + }>; +} + +interface AllDebridError { + code: string; + message: string; +} + +interface AllDebridDownloadUrl { + link: string; + size?: number; + filename?: string; +} + +export class AllDebridClient { + private static instance: AxiosInstance; + private static readonly baseURL = "https://api.alldebrid.com/v4"; + + static authorize(apiKey: string) { + logger.info("[AllDebrid] Authorizing with key:", apiKey ? "***" : "empty"); + this.instance = axios.create({ + baseURL: this.baseURL, + params: { + agent: "hydra", + apikey: apiKey, + }, + }); + } + + static async getUser() { + try { + const response = await this.instance.get<{ + status: string; + data?: { user: AllDebridUser }; + error?: AllDebridError; + }>("/user"); + + logger.info("[AllDebrid] API Response:", response.data); + + if (response.data.status === "error") { + const error = response.data.error; + logger.error("[AllDebrid] API Error:", error); + if (error?.code === "AUTH_MISSING_APIKEY") { + return { error_code: "alldebrid_missing_key" }; + } + if (error?.code === "AUTH_BAD_APIKEY") { + return { error_code: "alldebrid_invalid_key" }; + } + if (error?.code === "AUTH_BLOCKED") { + return { error_code: "alldebrid_blocked" }; + } + if (error?.code === "AUTH_USER_BANNED") { + return { error_code: "alldebrid_banned" }; + } + return { error_code: "alldebrid_unknown_error" }; + } + + if (!response.data.data?.user) { + logger.error("[AllDebrid] No user data in response"); + return { error_code: "alldebrid_invalid_response" }; + } + + logger.info( + "[AllDebrid] Successfully got user:", + response.data.data.user.username + ); + return { user: response.data.data.user }; + } catch (error: any) { + logger.error("[AllDebrid] Request Error:", error); + if (error.response?.data?.error) { + return { error_code: "alldebrid_invalid_key" }; + } + return { error_code: "alldebrid_network_error" }; + } + } + + private static async uploadMagnet(magnet: string) { + try { + logger.info("[AllDebrid] Uploading magnet with params:", { magnet }); + + const response = await this.instance.get("/magnet/upload", { + params: { + magnets: [magnet], + }, + }); + + logger.info( + "[AllDebrid] Upload Magnet Raw Response:", + JSON.stringify(response.data, null, 2) + ); + + if (response.data.status === "error") { + throw new Error(response.data.error?.message || "Unknown error"); + } + + const magnetInfo = response.data.data.magnets[0]; + logger.info( + "[AllDebrid] Magnet Info:", + JSON.stringify(magnetInfo, null, 2) + ); + + if (magnetInfo.error) { + throw new Error(magnetInfo.error.message); + } + + return magnetInfo.id; + } catch (error: any) { + logger.error("[AllDebrid] Upload Magnet Error:", error); + throw error; + } + } + + private static async checkMagnetStatus( + magnetId: number + ): Promise { + try { + logger.info("[AllDebrid] Checking magnet status for ID:", magnetId); + + const response = await this.instance.get(`/magnet/status`, { + params: { + id: magnetId, + }, + }); + + logger.info( + "[AllDebrid] Check Magnet Status Raw Response:", + JSON.stringify(response.data, null, 2) + ); + + if (!response.data) { + throw new Error("No response data received"); + } + + if (response.data.status === "error") { + throw new Error(response.data.error?.message || "Unknown error"); + } + + // Verificăm noua structură a răspunsului + const magnetData = response.data.data?.magnets; + if (!magnetData || typeof magnetData !== "object") { + logger.error( + "[AllDebrid] Invalid response structure:", + JSON.stringify(response.data, null, 2) + ); + throw new Error("Invalid magnet status response format"); + } + + // Convertim răspunsul în formatul așteptat + const magnetStatus: AllDebridMagnetStatus = { + id: magnetData.id, + filename: magnetData.filename, + size: magnetData.size, + status: magnetData.status, + statusCode: magnetData.statusCode, + downloaded: magnetData.downloaded, + uploaded: magnetData.uploaded, + seeders: magnetData.seeders, + downloadSpeed: magnetData.downloadSpeed, + uploadSpeed: magnetData.uploadSpeed, + uploadDate: magnetData.uploadDate, + completionDate: magnetData.completionDate, + links: magnetData.links.map((link) => ({ + link: link.link, + filename: link.filename, + size: link.size, + })), + }; + + logger.info( + "[AllDebrid] Magnet Status:", + JSON.stringify(magnetStatus, null, 2) + ); + + return magnetStatus; + } catch (error: any) { + logger.error("[AllDebrid] Check Magnet Status Error:", error); + throw error; + } + } + + private static async unlockLink(link: string) { + try { + const response = await this.instance.get<{ + status: string; + data?: { link: string }; + error?: AllDebridError; + }>("/link/unlock", { + params: { + link, + }, + }); + + if (response.data.status === "error") { + throw new Error(response.data.error?.message || "Unknown error"); + } + + const unlockedLink = response.data.data?.link; + if (!unlockedLink) { + throw new Error("No download link received from AllDebrid"); + } + + return unlockedLink; + } catch (error: any) { + logger.error("[AllDebrid] Unlock Link Error:", error); + throw error; + } + } + + public static async getDownloadUrls( + uri: string + ): Promise { + try { + logger.info("[AllDebrid] Getting download URLs for URI:", uri); + + if (uri.startsWith("magnet:")) { + logger.info("[AllDebrid] Detected magnet link, uploading..."); + // 1. Upload magnet + const magnetId = await this.uploadMagnet(uri); + logger.info("[AllDebrid] Magnet uploaded, ID:", magnetId); + + // 2. Verificăm statusul până când avem link-uri + let retries = 0; + let magnetStatus: AllDebridMagnetStatus; + + do { + magnetStatus = await this.checkMagnetStatus(magnetId); + logger.info( + "[AllDebrid] Magnet status:", + magnetStatus.status, + "statusCode:", + magnetStatus.statusCode + ); + + if (magnetStatus.statusCode === 4) { + // Ready + // Deblocăm fiecare link în parte și aruncăm eroare dacă oricare eșuează + const unlockedLinks = await Promise.all( + magnetStatus.links.map(async (link) => { + try { + const unlockedLink = await this.unlockLink(link.link); + logger.info( + "[AllDebrid] Successfully unlocked link:", + unlockedLink + ); + + return { + link: unlockedLink, + size: link.size, + filename: link.filename, + }; + } catch (error) { + logger.error( + "[AllDebrid] Failed to unlock link:", + link.link, + error + ); + throw new Error("Failed to unlock all links"); + } + }) + ); + + logger.info( + "[AllDebrid] Got unlocked download links:", + unlockedLinks + ); + console.log("[AllDebrid] FINAL LINKS →", unlockedLinks); + return unlockedLinks; + } + + if (retries++ > 30) { + // Maximum 30 de încercări + throw new Error("Timeout waiting for magnet to be ready"); + } + + await new Promise((resolve) => setTimeout(resolve, 2000)); // Așteptăm 2 secunde între verificări + } while (magnetStatus.statusCode !== 4); + } else { + logger.info("[AllDebrid] Regular link, unlocking..."); + // Pentru link-uri normale, doar debridam link-ul + const downloadUrl = await this.unlockLink(uri); + logger.info("[AllDebrid] Got unlocked download URL:", downloadUrl); + return [ + { + link: downloadUrl, + }, + ]; + } + } catch (error: any) { + logger.error("[AllDebrid] Get Download URLs Error:", error); + throw error; + } + return []; // Add default return for TypeScript + } +} diff --git a/src/main/services/download/download-manager.ts b/src/main/services/download/download-manager.ts index 57b3bac2..7c256b51 100644 --- a/src/main/services/download/download-manager.ts +++ b/src/main/services/download/download-manager.ts @@ -17,6 +17,7 @@ import { } from "./types"; import { calculateETA, getDirSize } from "./helpers"; import { RealDebridClient } from "./real-debrid"; +import { AllDebridClient } from "./all-debrid"; import path from "path"; import { logger } from "../logger"; import { db, downloadsSublevel, gamesSublevel, levelKeys } from "@main/level"; @@ -40,6 +41,7 @@ export class DownloadManager { }) : undefined, downloadsToSeed?.map((download) => ({ + action: "seed", game_id: levelKeys.game(download.shop, download.objectId), url: download.uri, save_path: download.downloadPath, @@ -377,6 +379,27 @@ export class DownloadManager { allow_multiple_connections: true, }; } + case Downloader.AllDebrid: { + const downloadUrls = await AllDebridClient.getDownloadUrls( + download.uri + ); + + if (!downloadUrls.length) + throw new Error(DownloadError.NotCachedInAllDebrid); + + const totalSize = downloadUrls.reduce( + (total, url) => total + (url.size || 0), + 0 + ); + + return { + action: "start", + game_id: downloadId, + url: downloadUrls.map((d) => d.link), + save_path: download.downloadPath, + total_size: totalSize, + }; + } case Downloader.TorBox: { const { name, url } = await TorBoxClient.getDownloadInfo(download.uri); diff --git a/src/main/services/download/helpers.ts b/src/main/services/download/helpers.ts index 0856eb16..84db662e 100644 --- a/src/main/services/download/helpers.ts +++ b/src/main/services/download/helpers.ts @@ -17,17 +17,24 @@ export const calculateETA = ( }; export const getDirSize = async (dir: string): Promise => { - const getItemSize = async (filePath: string): Promise => { - const stat = await fs.promises.stat(filePath); + try { + const stat = await fs.promises.stat(dir); - if (stat.isDirectory()) { - return getDirSize(filePath); + // If it's a file, return its size directly + if (!stat.isDirectory()) { + return stat.size; } - return stat.size; - }; + const getItemSize = async (filePath: string): Promise => { + const stat = await fs.promises.stat(filePath); + + if (stat.isDirectory()) { + return getDirSize(filePath); + } + + return stat.size; + }; - try { const files = await fs.promises.readdir(dir); const filePaths = files.map((file) => path.join(dir, file)); const sizes = await Promise.all(filePaths.map(getItemSize)); diff --git a/src/main/services/download/index.ts b/src/main/services/download/index.ts index f4e2eddc..c28d560b 100644 --- a/src/main/services/download/index.ts +++ b/src/main/services/download/index.ts @@ -1,3 +1,4 @@ export * from "./download-manager"; export * from "./real-debrid"; +export * from "./all-debrid"; export * from "./torbox"; diff --git a/src/main/services/python-rpc.ts b/src/main/services/python-rpc.ts index da4f1e71..f3ce9f6c 100644 --- a/src/main/services/python-rpc.ts +++ b/src/main/services/python-rpc.ts @@ -11,9 +11,13 @@ import { app, dialog } from "electron"; import { db, levelKeys } from "@main/level"; interface GamePayload { + action: string; game_id: string; - url: string; + url: string | string[]; save_path: string; + header?: string; + out?: string; + total_size?: number; } const binaryNameByPlatform: Partial> = { diff --git a/src/preload/index.ts b/src/preload/index.ts index 17c1225f..66eedcbf 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -102,6 +102,8 @@ contextBridge.exposeInMainWorld("electron", { ipcRenderer.invoke("autoLaunch", autoLaunchProps), authenticateRealDebrid: (apiToken: string) => ipcRenderer.invoke("authenticateRealDebrid", apiToken), + authenticateAllDebrid: (apiKey: string) => + ipcRenderer.invoke("authenticateAllDebrid", apiKey), authenticateTorBox: (apiToken: string) => ipcRenderer.invoke("authenticateTorBox", apiToken), @@ -128,52 +130,6 @@ contextBridge.exposeInMainWorld("electron", { ), addGameToLibrary: (shop: GameShop, objectId: string, title: string) => ipcRenderer.invoke("addGameToLibrary", shop, objectId, title), - addCustomGameToLibrary: ( - title: string, - executablePath: string, - iconUrl?: string, - logoImageUrl?: string, - libraryHeroImageUrl?: string - ) => - ipcRenderer.invoke( - "addCustomGameToLibrary", - title, - executablePath, - iconUrl, - logoImageUrl, - libraryHeroImageUrl - ), - copyCustomGameAsset: ( - 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: (params: { - shop: GameShop; - objectId: string; - title: string; - iconUrl?: string; - logoImageUrl?: string; - libraryHeroImageUrl?: string; - originalIconPath?: string; - originalLogoPath?: string; - originalHeroPath?: string; - }) => ipcRenderer.invoke("updateCustomGame", params), - updateGameCustomAssets: (params: { - shop: GameShop; - objectId: string; - title: string; - customIconUrl?: string | null; - customLogoImageUrl?: string | null; - customHeroImageUrl?: string | null; - customOriginalIconPath?: string | null; - customOriginalLogoPath?: string | null; - customOriginalHeroPath?: string | null; - }) => ipcRenderer.invoke("updateGameCustomAssets", params), createGameShortcut: ( shop: GameShop, objectId: string, diff --git a/src/renderer/src/constants.ts b/src/renderer/src/constants.ts index e28c7633..5f5661ea 100644 --- a/src/renderer/src/constants.ts +++ b/src/renderer/src/constants.ts @@ -11,6 +11,7 @@ export const DOWNLOADER_NAME = { [Downloader.Datanodes]: "Datanodes", [Downloader.Mediafire]: "Mediafire", [Downloader.TorBox]: "TorBox", + [Downloader.AllDebrid]: "All-Debrid", [Downloader.Hydra]: "Nimbus", }; diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index e6277888..2c942312 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -28,6 +28,7 @@ import type { LibraryGame, GameRunning, TorBoxUser, + AllDebridUser, Theme, Badge, Auth, @@ -112,43 +113,6 @@ declare global { objectId: string, title: string ) => Promise; - addCustomGameToLibrary: ( - title: string, - executablePath: string, - iconUrl?: string, - logoImageUrl?: string, - libraryHeroImageUrl?: string - ) => Promise; - updateCustomGame: (params: { - shop: GameShop; - objectId: string; - title: string; - iconUrl?: string; - logoImageUrl?: string; - libraryHeroImageUrl?: string; - originalIconPath?: string; - originalLogoPath?: string; - originalHeroPath?: string; - }) => Promise; - copyCustomGameAsset: ( - sourcePath: string, - assetType: "icon" | "logo" | "hero" - ) => Promise; - cleanupUnusedAssets: () => Promise<{ - deletedCount: number; - errors: string[]; - }>; - updateGameCustomAssets: (params: { - shop: GameShop; - objectId: string; - title: string; - customIconUrl?: string | null; - customLogoImageUrl?: string | null; - customHeroImageUrl?: string | null; - customOriginalIconPath?: string | null; - customOriginalLogoPath?: string | null; - customOriginalHeroPath?: string | null; - }) => Promise; createGameShortcut: ( shop: GameShop, objectId: string, @@ -212,6 +176,9 @@ declare global { ) => Promise; /* User preferences */ authenticateRealDebrid: (apiToken: string) => Promise; + authenticateAllDebrid: ( + apiKey: string + ) => Promise; authenticateTorBox: (apiToken: string) => Promise; getUserPreferences: () => Promise; updateUserPreferences: ( @@ -310,8 +277,6 @@ declare global { onCommonRedistProgress: ( cb: (value: { log: string; complete: boolean }) => void ) => () => Electron.IpcRenderer; - saveTempFile: (fileName: string, fileData: Uint8Array) => Promise; - deleteTempFile: (filePath: string) => Promise; platform: NodeJS.Platform; /* Auto update */ diff --git a/src/renderer/src/pages/downloads/download-group.tsx b/src/renderer/src/pages/downloads/download-group.tsx index 06e9face..bfa27792 100644 --- a/src/renderer/src/pages/downloads/download-group.tsx +++ b/src/renderer/src/pages/downloads/download-group.tsx @@ -114,6 +114,15 @@ export function DownloadGroup({ return

      {t("deleting")}

      ; } + if (download.downloader === Downloader.AllDebrid) { + return ( + <> +

      {progress}

      +

      {t("alldebrid_size_not_supported")}

      + + ); + } + if (isGameDownloading) { if (lastPacket?.isDownloadingMetadata) { return

      {t("downloading_metadata")}

      ; @@ -181,6 +190,15 @@ export function DownloadGroup({ } if (download.status === "active") { + if ((download.downloader as unknown as string) === "alldebrid") { + return ( + <> +

      {formatDownloadProgress(download.progress)}

      +

      {t("alldebrid_size_not_supported")}

      + + ); + } + return ( <>

      {formatDownloadProgress(download.progress)}

      @@ -275,7 +293,9 @@ export function DownloadGroup({ (download?.downloader === Downloader.RealDebrid && !userPreferences?.realDebridApiToken) || (download?.downloader === Downloader.TorBox && - !userPreferences?.torBoxApiToken); + !userPreferences?.torBoxApiToken) || + (download?.downloader === Downloader.AllDebrid && + !userPreferences?.allDebridApiKey); return [ { diff --git a/src/renderer/src/pages/game-details/hero/hero-panel-actions.tsx b/src/renderer/src/pages/game-details/hero/hero-panel-actions.tsx index d8d98583..307de108 100644 --- a/src/renderer/src/pages/game-details/hero/hero-panel-actions.tsx +++ b/src/renderer/src/pages/game-details/hero/hero-panel-actions.tsx @@ -229,7 +229,7 @@ export function HeroPanelActions() { {game.favorite ? : } - {userDetails && game.shop !== "custom" && ( + {userDetails && ( + } + placeholder="API Key" + hint={ + + + + } + /> + )} + + ); +} diff --git a/src/renderer/src/pages/settings/settings.tsx b/src/renderer/src/pages/settings/settings.tsx index 325c2e17..d609d218 100644 --- a/src/renderer/src/pages/settings/settings.tsx +++ b/src/renderer/src/pages/settings/settings.tsx @@ -1,6 +1,7 @@ import { Button } from "@renderer/components"; import { useTranslation } from "react-i18next"; import { SettingsRealDebrid } from "./settings-real-debrid"; +import { SettingsAllDebrid } from "./settings-all-debrid"; import { SettingsGeneral } from "./settings-general"; import { SettingsBehavior } from "./settings-behavior"; import { SettingsDownloadSources } from "./settings-download-sources"; @@ -42,6 +43,7 @@ export default function Settings() { ] : []), { tabLabel: "Real-Debrid", contentTitle: "Real-Debrid" }, + { tabLabel: "All-Debrid", contentTitle: "All-Debrid" }, ]; if (userDetails) @@ -81,6 +83,10 @@ export default function Settings() { return ; } + if (currentCategoryIndex === 6) { + return ; + } + return ; }; diff --git a/src/shared/constants.ts b/src/shared/constants.ts index 851aec49..352ffe12 100644 --- a/src/shared/constants.ts +++ b/src/shared/constants.ts @@ -1,5 +1,6 @@ export enum Downloader { RealDebrid, + AllDebrid, Torrent, Gofile, PixelDrain, @@ -55,6 +56,7 @@ export enum AuthPage { export enum DownloadError { NotCachedOnRealDebrid = "download_error_not_cached_on_real_debrid", + NotCachedInAllDebrid = "download_error_not_cached_in_alldebrid", NotCachedOnTorBox = "download_error_not_cached_on_torbox", GofileQuotaExceeded = "download_error_gofile_quota_exceeded", RealDebridAccountNotAuthorized = "download_error_real_debrid_account_not_authorized", diff --git a/src/shared/index.ts b/src/shared/index.ts index 2f35692f..000ffd22 100644 --- a/src/shared/index.ts +++ b/src/shared/index.ts @@ -123,6 +123,7 @@ export const getDownloadersForUri = (uri: string) => { Downloader.Hydra, Downloader.TorBox, Downloader.RealDebrid, + Downloader.AllDebrid, ]; } diff --git a/src/types/download.types.ts b/src/types/download.types.ts index 004d8f27..d19a3b83 100644 --- a/src/types/download.types.ts +++ b/src/types/download.types.ts @@ -175,3 +175,11 @@ export interface SeedingStatus { status: DownloadStatus; uploadSpeed: number; } + +/* All-Debrid */ +export interface AllDebridUser { + username: string; + email: string; + isPremium: boolean; + premiumUntil: string; +} diff --git a/src/types/level.types.ts b/src/types/level.types.ts index 8a6c56a0..567523e6 100644 --- a/src/types/level.types.ts +++ b/src/types/level.types.ts @@ -33,17 +33,6 @@ export interface User { export interface Game { title: string; iconUrl: string | null; - libraryHeroImageUrl: string | null; - logoImageUrl: string | null; - customIconUrl?: string | null; - customLogoImageUrl?: 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; unsyncedDeltaPlayTimeInMilliseconds?: number; lastTimePlayed: Date | null; @@ -95,9 +84,11 @@ export type AchievementCustomNotificationPosition = export interface UserPreferences { downloadsPath?: string | null; + ggDealsApiKey?: string | null; language?: string; realDebridApiToken?: string | null; torBoxApiToken?: string | null; + allDebridApiKey?: string | null; preferQuitInsteadOfHiding?: boolean; runAtStartup?: boolean; startMinimized?: boolean; diff --git a/tsconfig.web.json b/tsconfig.web.json index 6dc0c4ab..c80396f3 100644 --- a/tsconfig.web.json +++ b/tsconfig.web.json @@ -8,6 +8,7 @@ "src/locales/index.ts", "src/shared/**/*", "src/stories/**/*", + "src/types/**/*", ".storybook/**/*" ], "compilerOptions": { From cb9b120093a5ced69bcdc2ed0606aa9d99309af4 Mon Sep 17 00:00:00 2001 From: ItsYeBoi20 Date: Wed, 1 Oct 2025 05:43:03 +0200 Subject: [PATCH 14/15] Fix Game type to resolve typecheck errors --- src/types/level.types.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/types/level.types.ts b/src/types/level.types.ts index 567523e6..93925068 100644 --- a/src/types/level.types.ts +++ b/src/types/level.types.ts @@ -33,6 +33,17 @@ export interface User { export interface Game { title: string; iconUrl: string | null; + libraryHeroImageUrl: string | null; + logoImageUrl: string | null; + customIconUrl?: string | null; + customLogoImageUrl?: 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; unsyncedDeltaPlayTimeInMilliseconds?: number; lastTimePlayed: Date | null; From abe2314c38f02664bfb29c94f86838c61353b50b Mon Sep 17 00:00:00 2001 From: ItsYeBoi20 Date: Wed, 1 Oct 2025 06:15:41 +0200 Subject: [PATCH 15/15] Fix Game type to resolve typecheck errors --- src/locales/en/translation.json | 61 +++++++++++++++++-- src/locales/ro/translation.json | 7 +-- src/main/events/index.ts | 9 ++- src/preload/index.ts | 46 ++++++++++++++ src/renderer/src/declaration.d.ts | 41 ++++++++++++- .../game-details/hero/hero-panel-actions.tsx | 2 +- 6 files changed, 152 insertions(+), 14 deletions(-) diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index bf0cbb73..d84fce55 100755 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -4,7 +4,6 @@ "successfully_signed_in": "Successfully signed in" }, "home": { - "featured": "Featured", "surprise_me": "Surprise me", "no_results": "No results found", "start_typing": "Starting typing to search...", @@ -28,7 +27,56 @@ "friends": "Friends", "need_help": "Need help?", "favorites": "Favorites", - "playable_button_title": "Show only games you can play now" + "playable_button_title": "Show only games you can play now", + "add_custom_game_tooltip": "Add Custom Game", + "show_playable_only_tooltip": "Show Playable Only", + "custom_game_modal": "Add Custom Game", + "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_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_success": "Custom game added successfully", + "custom_game_modal_failed": "Failed to add custom game", + "custom_game_modal_executable": "Executable", + "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", + "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", + "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": { "search": "Search games", @@ -231,6 +279,7 @@ "backup_unfrozen": "Backup unpinned", "backup_freeze_failed": "Failed to freeze backup", "backup_freeze_failed_description": "You must leave at least one free slot for automatic backups", + "edit_game_modal_button": "Customize game assets", "game_details": "Game Details", "currency_symbol": "$", "currency_country": "us", @@ -241,10 +290,11 @@ "keyshop_price": "Keyshop price", "historical_retail": "Historical retail", "historical_keyshop": "Historical keyshop", - "supported_languages": "Supported languages", "language": "Language", "caption": "Caption", - "audio": "Audio" + "audio": "Audio", + "filter_by_source": "Filter by source", + "no_repacks_found": "No sources found for this game" }, "activation": { "title": "Activate Hydra", @@ -291,7 +341,6 @@ "change": "Update", "notifications": "Notifications", "enable_download_notifications": "When a download is complete", - "gg_deals_api_key_description": "gg deals api key. Used to show the lowest price. (https://gg.deals/api/)", "enable_repack_list_notifications": "When a new repack is added", "real_debrid_api_token_label": "Real-Debrid API token", "quit_app_instead_hiding": "Don't hide Hydra when closing", @@ -477,6 +526,8 @@ "user_profile": { "amount_hours": "{{amount}} hours", "amount_minutes": "{{amount}} minutes", + "amount_hours_short": "{{amount}}h", + "amount_minutes_short": "{{amount}}m", "last_time_played": "Last played {{period}}", "activity": "Recent Activity", "library": "Library", diff --git a/src/locales/ro/translation.json b/src/locales/ro/translation.json index 2c4e68a4..be02c7b4 100644 --- a/src/locales/ro/translation.json +++ b/src/locales/ro/translation.json @@ -1,7 +1,6 @@ { "language_name": "Română", "home": { - "featured": "Recomandate", "surprise_me": "Surprinde-mă", "no_results": "Niciun rezultat găsit" }, @@ -19,7 +18,6 @@ }, "header": { "search": "Caută jocuri", - "home": "Acasă", "catalogue": "Catalog", "downloads": "Descărcări", @@ -32,10 +30,7 @@ "downloading": "Se descarcă {{title}}... ({{percentage}} complet) - Concluzie {{eta}} - {{speed}}", "calculating_eta": "Se descarcă {{title}}... ({{percentage}} complet) - Calculare timp rămas..." }, - "catalogue": { - "next_page": "Pagina următoare", - "previous_page": "Pagina anterioară" - }, + "catalogue": {}, "game_details": { "open_download_options": "Deschide opțiunile de descărcare", "download_options_zero": "Nicio opțiune de descărcare", diff --git a/src/main/events/index.ts b/src/main/events/index.ts index 8cb48d47..713ce581 100644 --- a/src/main/events/index.ts +++ b/src/main/events/index.ts @@ -14,6 +14,9 @@ import "./catalogue/get-developers"; import "./hardware/get-disk-free-space"; import "./hardware/check-folder-write-permission"; import "./library/add-game-to-library"; +import "./library/add-custom-game-to-library"; +import "./library/update-custom-game"; +import "./library/update-game-custom-assets"; import "./library/add-game-to-favorites"; import "./library/remove-game-from-favorites"; import "./library/toggle-game-pin"; @@ -37,7 +40,9 @@ import "./library/reset-game-achievements"; import "./library/change-game-playtime"; import "./library/toggle-automatic-cloud-sync"; import "./library/get-default-wine-prefix-selection-path"; +import "./library/cleanup-unused-assets"; import "./library/create-steam-shortcut"; +import "./library/copy-custom-game-asset"; import "./misc/open-checkout"; import "./misc/open-external"; import "./misc/show-open-dialog"; @@ -46,9 +51,10 @@ 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 "./user-preferences/authenticate-all-debrid"; import "./torrenting/resume-game-download"; import "./torrenting/start-game-download"; import "./torrenting/pause-game-seed"; @@ -60,6 +66,7 @@ import "./user-preferences/auto-launch"; import "./autoupdater/check-for-updates"; import "./autoupdater/restart-and-install-update"; import "./user-preferences/authenticate-real-debrid"; +import "./user-preferences/authenticate-all-debrid"; import "./user-preferences/authenticate-torbox"; import "./download-sources/put-download-source"; import "./auth/sign-out"; diff --git a/src/preload/index.ts b/src/preload/index.ts index 66eedcbf..cb41e176 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -130,6 +130,52 @@ contextBridge.exposeInMainWorld("electron", { ), addGameToLibrary: (shop: GameShop, objectId: string, title: string) => ipcRenderer.invoke("addGameToLibrary", shop, objectId, title), + addCustomGameToLibrary: ( + title: string, + executablePath: string, + iconUrl?: string, + logoImageUrl?: string, + libraryHeroImageUrl?: string + ) => + ipcRenderer.invoke( + "addCustomGameToLibrary", + title, + executablePath, + iconUrl, + logoImageUrl, + libraryHeroImageUrl + ), + copyCustomGameAsset: ( + 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: (params: { + shop: GameShop; + objectId: string; + title: string; + iconUrl?: string; + logoImageUrl?: string; + libraryHeroImageUrl?: string; + originalIconPath?: string; + originalLogoPath?: string; + originalHeroPath?: string; + }) => ipcRenderer.invoke("updateCustomGame", params), + updateGameCustomAssets: (params: { + shop: GameShop; + objectId: string; + title: string; + customIconUrl?: string | null; + customLogoImageUrl?: string | null; + customHeroImageUrl?: string | null; + customOriginalIconPath?: string | null; + customOriginalLogoPath?: string | null; + customOriginalHeroPath?: string | null; + }) => ipcRenderer.invoke("updateGameCustomAssets", params), createGameShortcut: ( shop: GameShop, objectId: string, diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index 2c942312..ad7a5386 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -9,6 +9,7 @@ import type { UserPreferences, StartGameDownloadPayload, RealDebridUser, + AllDebridUser, UserProfile, FriendRequest, FriendRequestAction, @@ -28,7 +29,6 @@ import type { LibraryGame, GameRunning, TorBoxUser, - AllDebridUser, Theme, Badge, Auth, @@ -113,6 +113,43 @@ declare global { objectId: string, title: string ) => Promise; + addCustomGameToLibrary: ( + title: string, + executablePath: string, + iconUrl?: string, + logoImageUrl?: string, + libraryHeroImageUrl?: string + ) => Promise; + updateCustomGame: (params: { + shop: GameShop; + objectId: string; + title: string; + iconUrl?: string; + logoImageUrl?: string; + libraryHeroImageUrl?: string; + originalIconPath?: string; + originalLogoPath?: string; + originalHeroPath?: string; + }) => Promise; + copyCustomGameAsset: ( + sourcePath: string, + assetType: "icon" | "logo" | "hero" + ) => Promise; + cleanupUnusedAssets: () => Promise<{ + deletedCount: number; + errors: string[]; + }>; + updateGameCustomAssets: (params: { + shop: GameShop; + objectId: string; + title: string; + customIconUrl?: string | null; + customLogoImageUrl?: string | null; + customHeroImageUrl?: string | null; + customOriginalIconPath?: string | null; + customOriginalLogoPath?: string | null; + customOriginalHeroPath?: string | null; + }) => Promise; createGameShortcut: ( shop: GameShop, objectId: string, @@ -277,6 +314,8 @@ declare global { onCommonRedistProgress: ( cb: (value: { log: string; complete: boolean }) => void ) => () => Electron.IpcRenderer; + saveTempFile: (fileName: string, fileData: Uint8Array) => Promise; + deleteTempFile: (filePath: string) => Promise; platform: NodeJS.Platform; /* Auto update */ diff --git a/src/renderer/src/pages/game-details/hero/hero-panel-actions.tsx b/src/renderer/src/pages/game-details/hero/hero-panel-actions.tsx index 307de108..d8d98583 100644 --- a/src/renderer/src/pages/game-details/hero/hero-panel-actions.tsx +++ b/src/renderer/src/pages/game-details/hero/hero-panel-actions.tsx @@ -229,7 +229,7 @@ export function HeroPanelActions() { {game.favorite ? : } - {userDetails && ( + {userDetails && game.shop !== "custom" && (