mirror of
https://github.com/hydralauncher/hydra.git
synced 2026-01-18 00:33:59 +00:00
Merge branch 'main' into feat/context_game_menu
This commit is contained in:
committed by
GitHub
commit
3058a05ca8
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [windows-latest, ubuntu-latest]
|
||||
os: [windows-2022, ubuntu-latest]
|
||||
fail-fast: false
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
RENDERER_VITE_TORBOX_REFERRAL_CODE: ${{ vars.RENDERER_VITE_TORBOX_REFERRAL_CODE }}
|
||||
|
||||
- name: Build Windows
|
||||
if: matrix.os == 'windows-latest'
|
||||
if: matrix.os == 'windows-2022'
|
||||
run: yarn build:win
|
||||
env:
|
||||
MAIN_VITE_API_URL: ${{ vars.MAIN_VITE_STAGING_API_URL }}
|
||||
|
||||
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [windows-latest, ubuntu-latest]
|
||||
os: [windows-2022, ubuntu-latest]
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
@@ -59,7 +59,7 @@ jobs:
|
||||
RENDERER_VITE_TORBOX_REFERRAL_CODE: ${{ vars.RENDERER_VITE_TORBOX_REFERRAL_CODE }}
|
||||
|
||||
- name: Build Windows
|
||||
if: matrix.os == 'windows-latest'
|
||||
if: matrix.os == 'windows-2022'
|
||||
run: yarn build:win
|
||||
env:
|
||||
MAIN_VITE_API_URL: ${{ vars.MAIN_VITE_API_URL }}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"successfully_signed_in": "تم تسجيل الدخول بنجاح"
|
||||
},
|
||||
"home": {
|
||||
"featured": "مميز",
|
||||
"surprise_me": "مفاجئني",
|
||||
"no_results": "لم يتم العثور على نتائج",
|
||||
"start_typing": "ابدأ بالكتابة للبحث...",
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"language_name": "беларуская мова",
|
||||
"home": {
|
||||
"featured": "Рэкамэндаванае",
|
||||
"surprise_me": "Здзіві мяне",
|
||||
"no_results": "Няма вынікаў"
|
||||
},
|
||||
@@ -17,7 +16,6 @@
|
||||
"home": "Галоўная",
|
||||
"favorites": "Улюбленыя"
|
||||
},
|
||||
|
||||
"header": {
|
||||
"search": "Пошук",
|
||||
"home": "Галоўная",
|
||||
@@ -31,10 +29,7 @@
|
||||
"downloading_metadata": "Сцягванне мэтаданых {{title}}…",
|
||||
"downloading": "Сцягванне {{title}}… ({{percentage}} скончана) - Канчатак {{eta}} - {{speed}}"
|
||||
},
|
||||
"catalogue": {
|
||||
"next_page": "Наступная старонка",
|
||||
"previous_page": "Папярэдняя старонка"
|
||||
},
|
||||
"catalogue": {},
|
||||
"game_details": {
|
||||
"open_download_options": "Адкрыць варыянты сцягвання",
|
||||
"download_options_zero": "Няма варыянтаў сцягвання",
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"successfully_signed_in": "Успешно влизане"
|
||||
},
|
||||
"home": {
|
||||
"featured": "Препоръчани",
|
||||
"surprise_me": "Изненадай ме",
|
||||
"no_results": "Няма намерени резултати",
|
||||
"start_typing": "Започнете да пишете за търсене...",
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"successfully_signed_in": "Has entrat correctament"
|
||||
},
|
||||
"home": {
|
||||
"featured": "Destacats",
|
||||
"surprise_me": "Sorprèn-me",
|
||||
"no_results": "No s'ha trobat res"
|
||||
},
|
||||
@@ -25,7 +24,6 @@
|
||||
},
|
||||
"header": {
|
||||
"search": "Cerca jocs",
|
||||
|
||||
"home": "Inici",
|
||||
"catalogue": "Catàleg",
|
||||
"downloads": "Baixades",
|
||||
@@ -41,10 +39,7 @@
|
||||
"calculating_eta": "Descarregant {{title}}… ({{percentage}} completat) - Calculant el temps restant…",
|
||||
"checking_files": "Comprovant els fitxers de {{title}}… ({{percentage}} completat)"
|
||||
},
|
||||
"catalogue": {
|
||||
"next_page": "Pàgina següent",
|
||||
"previous_page": "Pàgina anterior"
|
||||
},
|
||||
"catalogue": {},
|
||||
"game_details": {
|
||||
"open_download_options": "Obre les opcions de baixada",
|
||||
"download_options_zero": "No hi ha opcions de baixada",
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"successfully_signed_in": "Úspěšně přihlášen"
|
||||
},
|
||||
"home": {
|
||||
"featured": "Doporučené",
|
||||
"surprise_me": "Překvap mě",
|
||||
"no_results": "Výsledek nenalezen",
|
||||
"start_typing": "Začni psát pro vyhledávání...",
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"successfully_signed_in": "Loggede ind successfuldt"
|
||||
},
|
||||
"home": {
|
||||
"featured": "Anbefalet",
|
||||
"surprise_me": "Overrask mig",
|
||||
"no_results": "Ingen resultater fundet",
|
||||
"start_typing": "Begynd at skrive for at søge...",
|
||||
@@ -29,7 +28,6 @@
|
||||
},
|
||||
"header": {
|
||||
"search": "Søg efter spil",
|
||||
|
||||
"home": "Hjem",
|
||||
"catalogue": "Katalog",
|
||||
"downloads": "Downloads",
|
||||
@@ -45,10 +43,7 @@
|
||||
"calculating_eta": "Downloader {{title}}… ({{percentage}} færdig) - Udregner resterende tid…",
|
||||
"checking_files": "Checker {{title}} filer… ({{percentage}} færdig)"
|
||||
},
|
||||
"catalogue": {
|
||||
"next_page": "Næste side",
|
||||
"previous_page": "Forrige side"
|
||||
},
|
||||
"catalogue": {},
|
||||
"game_details": {
|
||||
"open_download_options": "Åben download muligheder",
|
||||
"download_options_zero": "Ingen download mulighed",
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"successfully_signed_in": "Erfolgreich angemeldet"
|
||||
},
|
||||
"home": {
|
||||
"featured": "Empfohlen",
|
||||
"surprise_me": "Überrasche mich",
|
||||
"no_results": "Keine Ergebnisse gefunden",
|
||||
"start_typing": "Tippe, um zu suchen...",
|
||||
@@ -59,9 +58,7 @@
|
||||
"download_sources": "Download-Quellen",
|
||||
"result_count": "{{resultCount}} Ergebnisse",
|
||||
"filter_count": "{{filterCount}} verfügbar",
|
||||
"clear_filters": "{{filterCount}} ausgewählte löschen",
|
||||
"next_page": "Nächste Seite",
|
||||
"previous_page": "Vorherige Seite"
|
||||
"clear_filters": "{{filterCount}} ausgewählte löschen"
|
||||
},
|
||||
"game_details": {
|
||||
"open_download_options": "Download-Optionen öffnen",
|
||||
|
||||
@@ -70,7 +70,13 @@
|
||||
"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_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",
|
||||
@@ -295,7 +301,9 @@
|
||||
"historical_keyshop": "Historical keyshop",
|
||||
"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",
|
||||
@@ -515,6 +523,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",
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"successfully_signed_in": "Edukalt sisse logitud"
|
||||
},
|
||||
"home": {
|
||||
"featured": "Esile toodud",
|
||||
"surprise_me": "Üllata mind",
|
||||
"no_results": "Tulemusi ei leitud",
|
||||
"start_typing": "Alusta otsimiseks kirjutamist...",
|
||||
@@ -45,10 +44,7 @@
|
||||
"calculating_eta": "{{title}} allalaadimine… ({{percentage}} valmis) - Järelejäänud aja arvutamine…",
|
||||
"checking_files": "{{title}} failide kontrollimine… ({{percentage}} valmis)"
|
||||
},
|
||||
"catalogue": {
|
||||
"next_page": "Järgmine leht",
|
||||
"previous_page": "Eelmine leht"
|
||||
},
|
||||
"catalogue": {},
|
||||
"game_details": {
|
||||
"open_download_options": "Ava allalaadimise valikud",
|
||||
"download_options_zero": "Allalaadimise valikuid pole",
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"language_name": "فارسی",
|
||||
"home": {
|
||||
"featured": "پیشنهادی",
|
||||
"surprise_me": "سوپرایزم کن",
|
||||
"no_results": "اتمامای پیدا نشد"
|
||||
},
|
||||
@@ -17,7 +16,6 @@
|
||||
"home": "خانه",
|
||||
"favorites": "علاقهمندیها"
|
||||
},
|
||||
|
||||
"header": {
|
||||
"search": "جستجوی بازیها",
|
||||
"home": "خانه",
|
||||
@@ -31,10 +29,7 @@
|
||||
"downloading_metadata": "درحال دانلود متادیتاهای {{title}}…",
|
||||
"downloading": "در حال دانلود {{title}}… ({{percentage}} تکمیل شده) - اتمام {{eta}} - {{speed}}"
|
||||
},
|
||||
"catalogue": {
|
||||
"next_page": "صفحهی بعدی",
|
||||
"previous_page": "صفحهی قبلی"
|
||||
},
|
||||
"catalogue": {},
|
||||
"game_details": {
|
||||
"open_download_options": "بازکردن آپشنهای دانلود",
|
||||
"download_options_zero": "هیچ آپشن دانلودی وجود ندارد",
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"successfully_signed_in": "Connecté avec succès"
|
||||
},
|
||||
"home": {
|
||||
"featured": "En vedette",
|
||||
"surprise_me": "Surprenez-moi",
|
||||
"no_results": "Aucun résultat trouvé",
|
||||
"start_typing": "Commencez à taper pour rechercher...",
|
||||
|
||||
@@ -1,45 +1,113 @@
|
||||
{
|
||||
"language_name": "Magyar",
|
||||
"app": {
|
||||
"successfully_signed_in": "Sikeresen bejelentkeztél"
|
||||
},
|
||||
"home": {
|
||||
"featured": "Featured",
|
||||
"surprise_me": "Lepj meg",
|
||||
"no_results": "Nem található"
|
||||
"no_results": "Nincs találat",
|
||||
"start_typing": "Kereséshez gépelj...",
|
||||
"hot": "Most felkapott",
|
||||
"weekly": "📅 A hét felkapott játékai",
|
||||
"achievements": "🏆 Achievement támogatott"
|
||||
},
|
||||
"sidebar": {
|
||||
"catalogue": "Katalógus",
|
||||
"downloads": "Letöltések",
|
||||
"settings": "Beállítások",
|
||||
"my_library": "Könyvtáram",
|
||||
"downloading_metadata": "{{title}} (Metadata letöltése…)",
|
||||
"paused": "{{title}} (Szünet)",
|
||||
"downloading_metadata": "{{title}} (metaadatai letöltése…)",
|
||||
"paused": "{{title}} (Szüneteltetve)",
|
||||
"downloading": "{{title}} ({{percentage}} - Letöltés…)",
|
||||
"filter": "Könyvtár szűrése",
|
||||
"home": "Főoldal",
|
||||
"favorites": "Kedvenc játékok"
|
||||
"queued": "A(z) {{title}} (Várakozósorban van)",
|
||||
"game_has_no_executable": "A játékhoz nincs tallózva futtatható fájl",
|
||||
"sign_in": "Bejelentkezés",
|
||||
"friends": "Barátok",
|
||||
"need_help": "Elakadtál?",
|
||||
"favorites": "Kedvenc játékok",
|
||||
"playable_button_title": "Csak az azonnal játszható játékokat mutasd",
|
||||
"add_custom_game_tooltip": "Saját játék hozzáadása",
|
||||
"show_playable_only_tooltip": "Csak játszható játék mutatása",
|
||||
"custom_game_modal": "Saját játék hozzáadása:",
|
||||
"custom_game_modal_description": "Adj meg egy futtatható fájlt",
|
||||
"custom_game_modal_executable_path": "A fájl útvonala",
|
||||
"custom_game_modal_select_executable": "Az útvonal",
|
||||
"custom_game_modal_title": "Játékcím",
|
||||
"custom_game_modal_enter_title": "Játék elnevezése",
|
||||
"custom_game_modal_browse": "Tallózás",
|
||||
"custom_game_modal_cancel": "Mégse",
|
||||
"custom_game_modal_add": "Játék hozzáadása",
|
||||
"custom_game_modal_adding": "Játék hozzáadása...",
|
||||
"custom_game_modal_success": "Saját játék sikeresen hozzáadva",
|
||||
"custom_game_modal_failed": "Saját játék hozzáadása sikertelen",
|
||||
"custom_game_modal_executable": "Futtatható fájl",
|
||||
"edit_game_modal": "Játékmegjelenés",
|
||||
"edit_game_modal_description": "Játékcím és vizuális elemek módosítása",
|
||||
"edit_game_modal_title": "Játékcím",
|
||||
"edit_game_modal_enter_title": "Játék elnevezése",
|
||||
"edit_game_modal_image": "Kép",
|
||||
"edit_game_modal_select_image": "Kép útvonala",
|
||||
"edit_game_modal_browse": "Tallózás",
|
||||
"edit_game_modal_image_preview": "Kép előnézete",
|
||||
"edit_game_modal_icon": "Ikon",
|
||||
"edit_game_modal_select_icon": "Ikon útvonala",
|
||||
"edit_game_modal_icon_preview": "Ikon előnézete",
|
||||
"edit_game_modal_logo": "Logó",
|
||||
"edit_game_modal_select_logo": "Logó útvonala",
|
||||
"edit_game_modal_logo_preview": "Logó előnézete",
|
||||
"edit_game_modal_hero": "Borítókép",
|
||||
"edit_game_modal_select_hero": "Borítókép útvonala",
|
||||
"edit_game_modal_hero_preview": "Borítókép előnézete",
|
||||
"edit_game_modal_cancel": "Mégse",
|
||||
"edit_game_modal_update": "Frissít",
|
||||
"edit_game_modal_updating": "Frissítés...",
|
||||
"edit_game_modal_fill_required": "Kérlek töltsd ki az összes kötelező mezőt",
|
||||
"edit_game_modal_success": "Játék megjelenés frissítése sikeres",
|
||||
"edit_game_modal_failed": "Játék megjelenés frissítése sikertelen",
|
||||
"edit_game_modal_image_filter": "Kép",
|
||||
"edit_game_modal_icon_resolution": "Ajánlott felbontás: 256x256px",
|
||||
"edit_game_modal_logo_resolution": "Ajánlott felbontás: 640x360px",
|
||||
"edit_game_modal_hero_resolution": "Ajánlott felbontás: 1920x620px",
|
||||
"edit_game_modal_assets": "Vizuális elemek:"
|
||||
},
|
||||
"header": {
|
||||
"search": "Keresés",
|
||||
|
||||
"home": "Főoldal",
|
||||
"catalogue": "Katalógus",
|
||||
"downloads": "Letöltések",
|
||||
"search_results": "Keresési eredmények",
|
||||
"settings": "Beállítások"
|
||||
"search_results": "Keresési találatok",
|
||||
"settings": "Beállítások",
|
||||
"version_available_install": "A(z) {{version}} verzió elérhető. Kattints ide az újraindításhoz és telepítéshez.",
|
||||
"version_available_download": "A(z) {{version}} verzió elérhető. A letöltéshez kattints ide."
|
||||
},
|
||||
"bottom_panel": {
|
||||
"no_downloads_in_progress": "Nincsenek folyamatban lévő letöltések",
|
||||
"no_downloads_in_progress": "Nincs folyamatban lévő letöltés",
|
||||
"downloading_metadata": "{{title}} metaadatainak letöltése…",
|
||||
"downloading": "{{title}} letöltése… ({{percentage}} kész) - Befejezés {{eta}} - {{speed}}"
|
||||
"downloading": "{{title}} letöltése… ({{percentage}} kész) - Befejezés {{eta}} - {{speed}}",
|
||||
"calculating_eta": "{{title}} letöltése… ({{percentage}} kész) - Hátralévő idő…",
|
||||
"checking_files": "A(z) {{title}} fájljaiból… ({{percentage}} kész)",
|
||||
"installing_common_redist": "{{log}}…",
|
||||
"installation_complete": "Telepítés befejezve",
|
||||
"installation_complete_message": "A(z) Alapvető segédprogramok sikeresen telepítve"
|
||||
},
|
||||
"catalogue": {
|
||||
"next_page": "Következő olda",
|
||||
"previous_page": "Előző olda"
|
||||
"search": "Szűrés…",
|
||||
"developers": "Fejlesztők",
|
||||
"genres": "Műfajok",
|
||||
"tags": "Címkék",
|
||||
"publishers": "Kiadók",
|
||||
"download_sources": "Letöltési források",
|
||||
"result_count": "{{resultCount}} találatok",
|
||||
"filter_count": "{{filterCount}} elérhető",
|
||||
"clear_filters": "{{filterCount}} kiválaszott szűrő törlése"
|
||||
},
|
||||
"game_details": {
|
||||
"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",
|
||||
"download_options_other": "{{count}} letöltési lehetőség",
|
||||
"open_download_options": "Letöltési opciók megnyitása",
|
||||
"download_options_zero": "Nincs letöltési opció",
|
||||
"download_options_one": "{{count}} letöltési opció",
|
||||
"download_options_other": "{{count}} letöltési opció",
|
||||
"updated_at": "Frissítve: {{updated_at}}",
|
||||
"install": "Letöltés",
|
||||
"resume": "Folytatás",
|
||||
@@ -48,11 +116,13 @@
|
||||
"remove": "Eltávolítás",
|
||||
"space_left_on_disk": "{{space}} szabad hely a lemezen",
|
||||
"eta": "Befejezés {{eta}}",
|
||||
"downloading_metadata": "Metaadatok letöltése…",
|
||||
"calculating_eta": "Hátralevő idő kiszámítása…",
|
||||
"downloading_metadata": "Metaadat letöltése",
|
||||
"filter": "Repackek szűrése",
|
||||
"requirements": "Rendszerkövetelmények",
|
||||
"minimum": "Minimális",
|
||||
"minimum": "Minimum",
|
||||
"recommended": "Ajánlott",
|
||||
"paused": "Szüneteltetve",
|
||||
"release_date": "Megjelenés: {{date}}",
|
||||
"publisher": "Kiadta: {{publisher}}",
|
||||
"hours": "óra",
|
||||
@@ -60,29 +130,171 @@
|
||||
"amount_hours": "{{amount}} óra",
|
||||
"amount_minutes": "{{amount}} perc",
|
||||
"accuracy": "{{accuracy}}% pontosság",
|
||||
"add_to_library": "Hozzáadás a könyvtárhoz",
|
||||
"add_to_library": "Könyvtárba helyezés",
|
||||
"already_in_library": "Már könyvtárban",
|
||||
"remove_from_library": "Eltávolítás a könyvtárból",
|
||||
"no_downloads": "Nincs elérhető letöltés",
|
||||
"play_time": "Játszva: {{amount}}",
|
||||
"last_time_played": "Utoljára játszva {{period}}",
|
||||
"not_played_yet": "{{title}} még nem játszottál",
|
||||
"last_time_played": "Utoljára játszva: {{period}}",
|
||||
"not_played_yet": "Ezzel a játékkal még nem játszottál: {{title}}",
|
||||
"next_suggestion": "Következő javaslat",
|
||||
"play": "Játék",
|
||||
"deleting": "Telepítő törlése…",
|
||||
"close": "Bezárás",
|
||||
"playing_now": "Jelenleg játszva",
|
||||
"playing_now": "Játékban: ",
|
||||
"change": "Változtatás",
|
||||
"repacks_modal_description": "Choose the repack you want to download",
|
||||
"select_folder_hint": "Ahhoz, hogy megváltoztasd a helyet, hozzákell férned a",
|
||||
"download_now": "Töltsd le most"
|
||||
"repacks_modal_description": "Válaszd ki a repacket amit leszeretnél tölteni",
|
||||
"select_folder_hint": "Hogy megváltoztasd a letöltési mappát, menj a <0>Beállítások</0> menüjébe",
|
||||
"download_now": "Letöltés",
|
||||
"no_shop_details": "A bolt adatai nem érhetőek el.",
|
||||
"download_options": "Letöltési opciók",
|
||||
"download_path": "Letöltis hely",
|
||||
"previous_screenshot": "Előző screenshot",
|
||||
"next_screenshot": "Következő screenshot",
|
||||
"screenshot": "Screenshot {{number}}",
|
||||
"open_screenshot": "Screenshot megnyitása {{number}}",
|
||||
"download_settings": "Letöltési beállítások",
|
||||
"downloader": "Letöltési mód",
|
||||
"select_executable": "Tallózás",
|
||||
"no_executable_selected": "Nincs futtatható fájl tallózva",
|
||||
"open_folder": "Mappa megnyitása",
|
||||
"open_download_location": "Letöltött fájlok megtekintése",
|
||||
"create_shortcut": "Asztali parancsikon létrehozása",
|
||||
"clear": "Visszavon",
|
||||
"remove_files": "Fájlok eltávolítása",
|
||||
"remove_from_library_title": "Biztos vagy ebben?",
|
||||
"remove_from_library_description": "Ezzel eltávolítod a játékot {{game}} a könyvtáradból",
|
||||
"options": "Beállítások",
|
||||
"executable_section_title": "Futtatható fájl",
|
||||
"executable_section_description": "A fájl amely futtatásra fog kerülni amikor a \"Játék\" lenyomásra kerül",
|
||||
"downloads_section_title": "Letöltések",
|
||||
"downloads_section_description": "Csekkold le a játék frissítéseit vagy más verzióit",
|
||||
"danger_zone_section_title": "Veszélyzóna",
|
||||
"danger_zone_section_description": "Távolítsd el a játékot könyvtáradból, vagy a fájlokat amit a Hydra töltött le",
|
||||
"download_in_progress": "Letöltés folyamatban",
|
||||
"download_paused": "Letöltés szüneteltetve",
|
||||
"last_downloaded_option": "Utoljára letöltött",
|
||||
"create_steam_shortcut": "Steam parancsikon létrehozása",
|
||||
"create_shortcut_success": "A parancsikon létrehozása sikeres",
|
||||
"you_might_need_to_restart_steam": "Lehetséges hogy újrakell indítsd a Steamet hogy lásd a változást.",
|
||||
"create_shortcut_error": "Hiba lépett fel létrehozás közben",
|
||||
"nsfw_content_title": "Ez a játék nem megfelelő tartalmat tartalmaz.",
|
||||
"nsfw_content_description": "{{title}} tartalmaz tartalmat amely nem megfelelő minden korosztálynak. Biztosan folytatni szeretnéd?",
|
||||
"allow_nsfw_content": "Folytatás",
|
||||
"refuse_nsfw_content": "Vissza",
|
||||
"stats": "Statisztikák",
|
||||
"download_count": "Letöltések",
|
||||
"player_count": "Aktív játékosok",
|
||||
"download_error": "Ez a letöltési opció nem elérhető",
|
||||
"download": "Letöltés",
|
||||
"executable_path_in_use": "Ez a futtatható fájl már használatban van a(z) \"{{game}}\" által",
|
||||
"warning": "Figyelmeztetés:",
|
||||
"hydra_needs_to_remain_open": "ehhez a letöltéshez, a Hydrának muszáj nyitva maradnia hogy letöltődjön. Ha a Hydra bezáródik letöltés előtt, a letöltés elveszik.",
|
||||
"achievements": "Achievementek",
|
||||
"achievements_count": "Achievementek {{unlockedCount}}/{{achievementsCount}}",
|
||||
"cloud_save": "Mentés felhőben",
|
||||
"cloud_save_description": "Mentsd el az előrehaladásod a felhőben, majd folytasd egy másik eszközön",
|
||||
"backups": "Biztonsági másolatok",
|
||||
"install_backup": "Telepít",
|
||||
"delete_backup": "Töröl",
|
||||
"create_backup": "Biztonsági másolat létrehozása",
|
||||
"last_backup_date": "Utolsó biztonsági mentés {{date}}",
|
||||
"no_backup_preview": "Ehhez a címhez nem található mentett játék",
|
||||
"restoring_backup": "Biztonsági mentés helyreállítás: ({{progress}} kész)…",
|
||||
"uploading_backup": "Biztonsági mentés feltöltése…",
|
||||
"no_backups": "Még nem hoztál létre biztonsági másolatot ehhez a játékhoz",
|
||||
"backup_uploaded": "Biztonsági mentés feltöltve",
|
||||
"backup_deleted": "Biztonsági mentés törölve",
|
||||
"backup_restored": "Biztonsági mentés helyreállítva",
|
||||
"see_all_achievements": "Achievementlista megtekintése",
|
||||
"sign_in_to_see_achievements": "Jelentkezz be hogy lásd az achievementjeid",
|
||||
"mapping_method_automatic": "Automatikus",
|
||||
"mapping_method_manual": "Kézi",
|
||||
"mapping_method_label": "Térképezési módszer",
|
||||
"files_automatically_mapped": "Fájlok automatikusan térképezve",
|
||||
"no_backups_created": "Ehhez a játékhoz nincs biztonsági másolat létrehozva",
|
||||
"manage_files": "Fájlok kezelése",
|
||||
"loading_save_preview": "Mentett játék keresése…",
|
||||
"wine_prefix": "Wine Prefix",
|
||||
"wine_prefix_description": "A Wine környezet, amiben a játék fut",
|
||||
"launch_options": "Indítási opciók",
|
||||
"launch_options_description": "Indítási opciók testreszabása haladó felhasználóknak (kísérleti funkció)",
|
||||
"launch_options_placeholder": "Nincs paraméter megadva",
|
||||
"no_download_option_info": "Nincs elérhető információ",
|
||||
"backup_deletion_failed": "Biztonsági mentés törlése sikertelen",
|
||||
"max_number_of_artifacts_reached": "A játék biztonsági mentéseinek száma elérte a határt",
|
||||
"achievements_not_sync": "Tekintsd meg hogyan kell szinkronizálni az achievementjeid",
|
||||
"manage_files_description": "Kezeld mely fájlokról készül biztonsági másolat, és melyek állíthatók vissza",
|
||||
"select_folder": "Mappa tallózása",
|
||||
"backup_from": "Biztonsági másolat: {{date}}",
|
||||
"automatic_backup_from": "Automatikus másolat: {{date}}",
|
||||
"enable_automatic_cloud_sync": "Automatikus felhőalapú szinkronizálás engedélyezése",
|
||||
"custom_backup_location_set": "Egyéni biztonsági mentési hely",
|
||||
"no_directory_selected": "Nincs mappa tallózva",
|
||||
"no_write_permission": "Nem lehet a mappába letölteni. Kattints ide további információért.",
|
||||
"reset_achievements": "Achievementek nullázása",
|
||||
"reset_achievements_description": "Ez az összes achievementet nullázza a {{game}} játékhoz",
|
||||
"reset_achievements_title": "Biztos vagy ebben?",
|
||||
"reset_achievements_success": "Achievementek sikeresen nullázva",
|
||||
"reset_achievements_error": "Achievementek nullázása sikertelen",
|
||||
"download_error_gofile_quota_exceeded": "Túllépted a Gofile havi kvótáját. Kérlek, várd meg amíg a kvóta lejár.",
|
||||
"download_error_real_debrid_account_not_authorized": "A Real-Debrid fiókod nem jogosult új letöltésekre. Kérlek, ellenőrízd a fiókbeállításaidat, majd próbáld újra.",
|
||||
"download_error_not_cached_on_real_debrid": "Ez a letöltés nem érhető el a Real-Debridnél, és lekérdezni letöltési állapotot még nem lehet vele.",
|
||||
"update_playtime_title": "Játékidő frissítése",
|
||||
"update_playtime_description": "Manuálisan frissíteni a Játékidőt a {{game}} játékhoz",
|
||||
"update_playtime": "Játékidő frissítése",
|
||||
"update_playtime_success": "Játékidő sikeresen frissítve",
|
||||
"update_playtime_error": "A Játékidőnek nem sikerült frissülnie",
|
||||
"update_game_playtime": "Játékidő frissítése",
|
||||
"manual_playtime_warning": "Az óráid 'manuálisan frissítve' lesznek megjelölve, és ez nem visszavonható.",
|
||||
"manual_playtime_tooltip": "Ez a játékidő manuálisan lett frissítve",
|
||||
"download_error_not_cached_on_torbox": "This download is not available on TorBox and polling download status from TorBox is not yet available.",
|
||||
"download_error_not_cached_on_hydra": "This download is not available on Nimbus.",
|
||||
"game_removed_from_favorites": "Játék eltávolítva a kedvencek közül",
|
||||
"game_added_to_favorites": "Játék hozzáadva a kedvencekhez",
|
||||
"game_removed_from_pinned": "Játék eltávolítva a kitűzöttek közül",
|
||||
"game_added_to_pinned": "Játék sikeresen kitűzve",
|
||||
"automatically_extract_downloaded_files": "Automatikus kibontása a letöltött fájloknak",
|
||||
"create_start_menu_shortcut": "Start menü parancsikon létrehozása",
|
||||
"invalid_wine_prefix_path": "Érvénytelen Wine prefix elérési útvonal",
|
||||
"invalid_wine_prefix_path_description": "Az út a Wine prefixhez érvénytelen. Ellenőrízd az elérési utat, majd próbáld újra.",
|
||||
"missing_wine_prefix": "Wine prefix szükséges a biztonsági másolat létrehozásához Linux rendszeren.",
|
||||
"artifact_renamed": "Biztonsági mentés sikeresen átnevezve",
|
||||
"rename_artifact": "Biztonsági mentés átnevezése",
|
||||
"rename_artifact_description": "Nevezd át a biztonsági másolatot egy leíróbb névre",
|
||||
"artifact_name_label": "Biztonsági másolat neve",
|
||||
"artifact_name_placeholder": "Adj egy nevet a biztonsági mentésnek",
|
||||
"save_changes": "Változtatások mentése",
|
||||
"required_field": "Ez a mező kötelező",
|
||||
"max_length_field": "Ez a mező kevesebb karakter kell legyen mint {{length}}",
|
||||
"freeze_backup": "Rögzítsd, hogy az automatikus biztonsági mentések ne írják felül",
|
||||
"unfreeze_backup": "Leválaszt",
|
||||
"backup_frozen": "Biztonsági mentés rögzítve",
|
||||
"backup_unfrozen": "Biztonsági mentés leválasztva",
|
||||
"backup_freeze_failed": "Biztonsági mentés rögzítése sikertelen",
|
||||
"backup_freeze_failed_description": "Legalább egy szabad helyet kell hagyni az automatikus biztonsági mentéseknek.",
|
||||
"edit_game_modal_button": "Játékadatok testreszabása",
|
||||
"game_details": "Játék leírása",
|
||||
"currency_symbol": "Ft",
|
||||
"currency_country": "hu",
|
||||
"prices": "Árak",
|
||||
"no_prices_found": "Nincsenek található árak",
|
||||
"view_all_prices": "Összes ár megtekintése",
|
||||
"retail_price": "Bolti ár",
|
||||
"keyshop_price": "Nem hivatalos ár",
|
||||
"historical_retail": "Korábbi bolti ár",
|
||||
"historical_keyshop": "Korábbi nem hivatalos ár",
|
||||
"language": "Nyelv",
|
||||
"caption": "Felirat",
|
||||
"audio": "Hang"
|
||||
},
|
||||
"activation": {
|
||||
"title": "Hydra Aktiválása",
|
||||
"installation_id": "Telepítési ID:",
|
||||
"enter_activation_code": "Add meg az aktiválási kódodat",
|
||||
"message": "Ha nem tudod, hol kérdezd meg ezt, akkor nem is kellene, hogy legyen ilyened.",
|
||||
"title": "Hydra aktiválása",
|
||||
"installation_id": "Telepítési azonosító:",
|
||||
"enter_activation_code": "Írd be az aktiválási kódod",
|
||||
"message": "Ha nem tudod kit kérdezz efelől, akkor nem kéne nálad legyen.",
|
||||
"activate": "Aktiválás",
|
||||
"loading": "Betöltés…"
|
||||
"loading": "Töltés…"
|
||||
},
|
||||
"downloads": {
|
||||
"resume": "Folytatás",
|
||||
@@ -91,46 +303,325 @@
|
||||
"paused": "Szüneteltetve",
|
||||
"verifying": "Ellenőrzés…",
|
||||
"completed": "Befejezve",
|
||||
"removed": "Nincs letöltve",
|
||||
"cancel": "Mégse",
|
||||
"filter": "Letöltött játékok szűrése",
|
||||
"remove": "Eltávolítás",
|
||||
"remove": "Eltávolít",
|
||||
"downloading_metadata": "Metaadatok letöltése…",
|
||||
"deleting": "Telepítő törlése…",
|
||||
"delete": "Telepítő eltávolítása",
|
||||
"delete_modal_title": "Biztos vagy benne?",
|
||||
"delete_modal_description": "Ez eltávolít minden telepítési fájlt a számítógépedről",
|
||||
"install": "Telepítés"
|
||||
"delete_modal_title": "Biztos vagy ebben?",
|
||||
"delete_modal_description": "Ez eltávolítja a telepítési fájlokat a számítógépedről",
|
||||
"install": "Telepít",
|
||||
"download_in_progress": "Folyamatban lévő",
|
||||
"queued_downloads": "Várakozósoron lévő letöltések",
|
||||
"downloads_completed": "Befejezett",
|
||||
"queued": "Várakozási sorban",
|
||||
"no_downloads_title": "Oly üres..",
|
||||
"no_downloads_description": "Még nem töltöttél le semmit a Hydra segítségével, de soha nem késő elkezdeni.",
|
||||
"checking_files": "Fájlok ellenőrzése…",
|
||||
"seeding": "Seedelés",
|
||||
"stop_seeding": "Seedelés leállítása",
|
||||
"resume_seeding": "Seedelés folytatása",
|
||||
"options": "Kezelés",
|
||||
"extract": "Fájlok kibontása",
|
||||
"extracting": "Fájlok kibontása…"
|
||||
},
|
||||
"settings": {
|
||||
"downloads_path": "Letöltések helye",
|
||||
"downloads_path": "Letöltési útvonalak",
|
||||
"change": "Frissítés",
|
||||
"notifications": "Értesítések",
|
||||
"enable_download_notifications": "Amikor egy letöltés befejeződik",
|
||||
"enable_repack_list_notifications": "Amikor új repack kerül feltöltésre",
|
||||
"real_debrid_api_token_label": "Real-Debrid API token",
|
||||
"quit_app_instead_hiding": "Hydra elrejtésének tiltása bezáráskor",
|
||||
"launch_with_system": "Hydra automatikus indítása rendszer indításakor",
|
||||
"general": "Általános",
|
||||
"behavior": "Működés",
|
||||
"download_sources": "Letöltési források",
|
||||
"language": "Nyelv",
|
||||
"api_token": "API Token",
|
||||
"enable_real_debrid": "Real-Debrid Bekapcsolása",
|
||||
"real_debrid_description": "A Real-Debrid egy korlátozásmentes letöltőprogram, lehetővé teszi a fájlok gyors letöltését, és csak az internetkapcsolat sebessége szab határt.",
|
||||
"debrid_invalid_token": "Érvénytelen API token",
|
||||
"debrid_api_token_hint": "Az API tokened <0>itt</0> található",
|
||||
"real_debrid_free_account_error": "Ez a fiók: \"{{username}}\" egy ingyenes fiók. Kérlek iratkozz fel a Real-Debrid-re",
|
||||
"debrid_linked_message": "Fiók összekapcsolva: \"{{username}}\" ",
|
||||
"save_changes": "Változtatások mentése",
|
||||
"changes_saved": "Változtatások sikeresen mentve",
|
||||
"download_sources_description": "A Hydra lefogja tölteni a letöltési linkeket a forrásokból. Az URL forrásnak közvetlen linknek kell lennie egy .json fájlhoz, ami tartalmazza a linkeket.",
|
||||
"validate_download_source": "Érvényesítés",
|
||||
"remove_download_source": "Eltávolítás",
|
||||
"add_download_source": "Forrás hozáadása",
|
||||
"download_count_zero": "Nincs letöltési opció",
|
||||
"download_count_one": "{{countFormatted}} letöltési opció",
|
||||
"download_count_other": "{{countFormatted}} letöltési opció",
|
||||
"download_source_url": "URL forrás:",
|
||||
"add_download_source_description": "Helyezd be a .json fájl URL-jét",
|
||||
"download_source_up_to_date": "Naprakész",
|
||||
"download_source_errored": "Hiba történt",
|
||||
"sync_download_sources": "Források szinkronizálása",
|
||||
"removed_download_source": "Letöltési forrás eltávolítva",
|
||||
"removed_download_sources": "Letöltési források eltávolítva",
|
||||
"cancel_button_confirmation_delete_all_sources": "Nem",
|
||||
"confirm_button_confirmation_delete_all_sources": "Igen, törölj mindent",
|
||||
"description_confirmation_delete_all_sources": "Törölni fog minden letöltési forrást",
|
||||
"title_confirmation_delete_all_sources": "Törölje az összes letöltési forrást",
|
||||
"removed_download_sources": "Betűtípusok eltávolítva",
|
||||
"button_delete_all_sources": "Távolítsa el az összes letöltési forrást",
|
||||
"enable_repack_list_notifications": "Amikor egy új repack hozzáadásra kerül"
|
||||
"title_confirmation_delete_all_sources": "Az összes letöltési forrás törlése",
|
||||
"description_confirmation_delete_all_sources": "Az összes letöltési forrást törölni fogod ezáltal",
|
||||
"button_delete_all_sources": "Összes eltávolítása",
|
||||
"added_download_source": "Letöltési forrás hozzáadva",
|
||||
"download_sources_synced": "Az összes letöltési forrás szinkronizálva",
|
||||
"insert_valid_json_url": "Adj meg egy érvényes JSON url-t",
|
||||
"found_download_option_zero": "Nincs letöltési opció",
|
||||
"found_download_option_one": "{{countFormatted}} Letöltési opció találva",
|
||||
"found_download_option_other": "{{countFormatted}} Letöltési opciók találva",
|
||||
"import": "Importálás",
|
||||
"public": "Publikus",
|
||||
"private": "Privát",
|
||||
"friends_only": "Csak barátok",
|
||||
"privacy": "Adatvédelem",
|
||||
"profile_visibility": "Profil láthatósága",
|
||||
"profile_visibility_description": "Válaszd ki, ki láthatja a profilod és könyvtárad",
|
||||
"required_field": "Ez a mező kötelező",
|
||||
"source_already_exists": "Ez a forrás már használatban",
|
||||
"must_be_valid_url": "A forrás egy érvényes URL kell legyen",
|
||||
"blocked_users": "Letiltott felhasználók",
|
||||
"user_unblocked": "Felhasználó letiltva",
|
||||
"enable_achievement_notifications": "Amikor egy achievement feloldódik",
|
||||
"launch_minimized": "Hydra indítása minimalizálva",
|
||||
"disable_nsfw_alert": "NSFW figyelmeztetés kikapcsolása",
|
||||
"seed_after_download_complete": "Letöltés utáni seedelés",
|
||||
"show_hidden_achievement_description": "Rejtett achievementek leírásának megjelenítése feloldás előtt",
|
||||
"account": "Fiók",
|
||||
"no_users_blocked": "Nincsenek letiltott felhasználóid",
|
||||
"subscription_active_until": "Hydra Cloud előfizetésed aktív, eddig: {{date}}",
|
||||
"manage_subscription": "Előfizetés kezelése",
|
||||
"update_email": "Email változtatása",
|
||||
"update_password": "Jelszó változtatása",
|
||||
"current_email": "Jelenlegi email:",
|
||||
"no_email_account": "Még nincs beállított emailed",
|
||||
"account_data_updated_successfully": "Fiókadatok változtatása sikeres",
|
||||
"renew_subscription": "Hydra Cloud Megújítása",
|
||||
"subscription_expired_at": "Az előfizetésed lejárt, ekkor: {{date}}",
|
||||
"no_subscription": "Élvezd a Hydrát a lehető legjobb módon",
|
||||
"become_subscriber": "Légy Hydra Cloud tag",
|
||||
"subscription_renew_cancelled": "Automatikus megújítás kikapcsolva",
|
||||
"subscription_renews_on": "Az előfizetésed megújul, ekkor: {{date}}",
|
||||
"bill_sent_until": "A következő számlát ezen napon küldjük",
|
||||
"no_themes": "Úgy látom nincs egyetlen témád sem még, de ne aggódj, kattints ide hogy elkészítsd a remekművedet.",
|
||||
"editor_tab_code": "Code",
|
||||
"editor_tab_info": "Info",
|
||||
"editor_tab_save": "Mentés",
|
||||
"web_store": "Webáruház",
|
||||
"clear_themes": "Törlés",
|
||||
"create_theme": "Létrehozás",
|
||||
"create_theme_modal_title": "Egyéni téma létrehozása",
|
||||
"create_theme_modal_description": "Hozz létre egy új témát, hogy testreszabhasd a Hydrát ahogy szeretnéd",
|
||||
"theme_name": "Téma neve",
|
||||
"insert_theme_name": "Adj a témádnak nevet",
|
||||
"set_theme": "Téma beállítása",
|
||||
"unset_theme": "Téma visszavonása",
|
||||
"delete_theme": "Téma törlése",
|
||||
"edit_theme": "Téma szerkesztése",
|
||||
"delete_all_themes": "Összes téma törlése",
|
||||
"delete_all_themes_description": "Ez törölni fogja az összes témádat",
|
||||
"delete_theme_description": "Ez törölni fogja a(z) {{theme}} témát",
|
||||
"cancel": "Mégsem",
|
||||
"appearance": "Megjelenés",
|
||||
"enable_torbox": "TorBox bekapcsolása",
|
||||
"torbox_description": "A TorBox egy olyan premium seedbox szolgáltatás, amely még a piacon elérhető legjobb szerverekkel is felveszi a versenyt.",
|
||||
"torbox_account_linked": "TorBox fiók összekapcsolva",
|
||||
"create_real_debrid_account": "Kattints ide ha még nincs Real-Debrid fiókod",
|
||||
"create_torbox_account": "Kattints ide ha még nincs TorBox fiókod",
|
||||
"real_debrid_account_linked": "Real-Debrid fiók összekapcsolva",
|
||||
"name_min_length": "A téma neve legalább 3 karakter hosszú legyen",
|
||||
"import_theme": "Téma importálása",
|
||||
"import_theme_description": "Ezt a témát fogod importálni a Témaáruház-ból: {{theme}}",
|
||||
"error_importing_theme": "Hiba lépett fel a téma importálása közben",
|
||||
"theme_imported": "Téma sikeresen importálva",
|
||||
"enable_friend_request_notifications": "Amikor ismerősnek jelölnek",
|
||||
"enable_auto_install": "Frissítések letöltése automatikusan",
|
||||
"common_redist": "Alapvető Segédprogramok",
|
||||
"common_redist_description": "Egyes játékok futtatásához alapvető segédprogram fájlok szükségesek. A problémák elkerülése képpen ajánlott telepíteni őket.",
|
||||
"install_common_redist": "Telepítés",
|
||||
"installing_common_redist": "Telepítés alatt…",
|
||||
"show_download_speed_in_megabytes": "Letöltési sebesség megabájt/másodpercben lévő megjelenítése",
|
||||
"extract_files_by_default": "Fájlok kicsomagolása letöltés után",
|
||||
"enable_steam_achievements": "Steam-achievementek utáni keresés engedélyezése",
|
||||
"achievement_custom_notification_position": "Achievement-értesítések egyéni elhelyezése",
|
||||
"top-left": "Bal felső sarok",
|
||||
"top-center": "Felső közép",
|
||||
"top-right": "Jobb felső sarok",
|
||||
"bottom-left": "Bal alsó sarok",
|
||||
"bottom-center": "Alsó közép",
|
||||
"bottom-right": "Jobb alsó sarok",
|
||||
"enable_achievement_custom_notifications": "Egyéni achievement-értesítések bekapcsolása",
|
||||
"alignment": "Igazítás",
|
||||
"variation": "Variáció",
|
||||
"default": "Alapértelmezett",
|
||||
"rare": "Ritka",
|
||||
"platinum": "Platinum",
|
||||
"hidden": "Rejtett",
|
||||
"test_notification": "Értesítés tesztelése",
|
||||
"notification_preview": "Achievement Értesítés Előnézete",
|
||||
"enable_friend_start_game_notifications": "Amikor egy barátod elkezd játszani egy játékot"
|
||||
},
|
||||
"notifications": {
|
||||
"download_complete": "Letöltés befejeződött",
|
||||
"game_ready_to_install": "{{title}} telepítésre kész",
|
||||
"download_complete": "Letöltés befejezve",
|
||||
"game_ready_to_install": "A(z) {{title}} telepítésre kész",
|
||||
"repack_list_updated": "Repack lista frissítve",
|
||||
"repack_count_one": "{{count}} repack hozzáadva",
|
||||
"repack_count_other": "{{count}} repack hozzáadva"
|
||||
"repack_count_other": "{{count}} repack hozzáadva",
|
||||
"new_update_available": "A(z) {{version}} verzió elérhető",
|
||||
"restart_to_install_update": "Indítsd újra a Hydrát a frissítés telepítéséhez",
|
||||
"notification_achievement_unlocked_title": "Achievement feloldva: {{game}}",
|
||||
"notification_achievement_unlocked_body": "{{achievement}} és további {{count}} feloldva",
|
||||
"new_friend_request_description": "{{displayName}} küldött neked egy barátfelkérést",
|
||||
"new_friend_request_title": "Új barátfelkérés",
|
||||
"extraction_complete": "Kicsomagolás befejezve",
|
||||
"game_extracted": "{{title}} sikeresen kicsomagolva",
|
||||
"friend_started_playing_game": "{{displayName}} játszani kezdett",
|
||||
"test_achievement_notification_title": "Ez egy teszt értesítés",
|
||||
"test_achievement_notification_description": "Elég menő, mi?"
|
||||
},
|
||||
"system_tray": {
|
||||
"open": "Hydra megnyitása",
|
||||
"quit": "Kilépés"
|
||||
},
|
||||
"game_card": {
|
||||
"available_one": "Elérhető",
|
||||
"available_other": "Elérhető",
|
||||
"no_downloads": "Nincs elérhető letöltés"
|
||||
},
|
||||
"binary_not_found_modal": {
|
||||
"title": "A programok nincsenek telepítve",
|
||||
"description": "A Wine vagy a Lutris végrehajtható fájljai nem találhatók a rendszereden",
|
||||
"instructions": "Ellenőrizd a megfelelő telepítési módot bármelyiküknek a Linux disztribúciódon, hogy a játék normálisan fusson"
|
||||
"description": "Wine vagy Lutris futtatható fájlok nem találhatók a rendszereden",
|
||||
"instructions": "Ellenőrízd, hogy melyiket kell helyesen telepíteni a Linux disztribúcióra, hogy a játék normálisan fusson"
|
||||
},
|
||||
"modal": {
|
||||
"close": "Bezárás gomb"
|
||||
},
|
||||
"forms": {
|
||||
"toggle_password_visibility": "Jelszó láthatóságának állítása"
|
||||
},
|
||||
"user_profile": {
|
||||
"amount_hours": "{{amount}} óra",
|
||||
"amount_minutes": "{{amount}} perc",
|
||||
"amount_hours_short": "{{amount}}ó",
|
||||
"amount_minutes_short": "{{amount}}p",
|
||||
"last_time_played": "Utoljára játszva {{period}}",
|
||||
"activity": "Legutóbbi tevékenység",
|
||||
"library": "Könyvtár",
|
||||
"pinned": "Kitűzve",
|
||||
"achievements_earned": "Elért achievementek",
|
||||
"played_recently": "Nemrég játszva",
|
||||
"playtime": "Játszottidő",
|
||||
"total_play_time": "Teljes játszottidő",
|
||||
"manual_playtime_tooltip": "Ez a játszottidő manuálisan lett frissítve",
|
||||
"no_recent_activity_title": "Hmmm… itt semmi sincs",
|
||||
"no_recent_activity_description": "Mostanában nem játszottál semmivel. Hát ideje ezt megváltoztatni!",
|
||||
"display_name": "Profilnév",
|
||||
"saving": "Mentésben",
|
||||
"save": "Mentés",
|
||||
"edit_profile": "Profil Szerkesztése",
|
||||
"saved_successfully": "Sikeresen elmentve",
|
||||
"try_again": "Kérlek, próbálkozz újra",
|
||||
"sign_out_modal_title": "Biztos vagy ebben?",
|
||||
"cancel": "Mégsem",
|
||||
"successfully_signed_out": "Sikeresen kijelentkezve",
|
||||
"sign_out": "Kijelentkezés",
|
||||
"playing_for": "Játékban: {{amount}}",
|
||||
"sign_out_modal_text": "A könyvtár a jelenlegi fiókodhoz van csatolva. Kijelentkezéskor a könyvtár többé nem lesz látható, és az eddigi előrehaladás nem lesz mentve. Folytatod a kijelentkezést?",
|
||||
"add_friends": "Barát bejelölés",
|
||||
"add": "Elküld",
|
||||
"friend_code": "Barát kód",
|
||||
"see_profile": "Profil megtekintése",
|
||||
"sending": "Küldés..",
|
||||
"friend_request_sent": "Barátfelkérés elküldve",
|
||||
"friends": "Barátok",
|
||||
"friends_list": "Barát lista",
|
||||
"user_not_found": "Felhasználó nem találva",
|
||||
"block_user": "Felhasználó letiltása",
|
||||
"add_friend": "Barát bejelölése",
|
||||
"request_sent": "Kérés elküldve",
|
||||
"request_received": "Barátfelkérést kaptál",
|
||||
"accept_request": "Kérés elfogadása",
|
||||
"ignore_request": "Kérés ignorálása",
|
||||
"cancel_request": "Kérés visszavonása",
|
||||
"undo_friendship": "Barát eltávolítása",
|
||||
"request_accepted": "Barátfelkérés elfogadva",
|
||||
"user_blocked_successfully": "Felhasználó sikeresen letiltva",
|
||||
"user_block_modal_text": "Ez által letiltod őt: {{displayName}}",
|
||||
"blocked_users": "Letiltott felhasználók",
|
||||
"unblock": "Tiltás feloldása",
|
||||
"no_friends_added": "Nincs bejelölt barátod",
|
||||
"pending": "Függőben",
|
||||
"no_pending_invites": "Nincs függőben lévő barátfelkérésed",
|
||||
"no_blocked_users": "Nincs letiltott felhasználó",
|
||||
"friend_code_copied": "Barát kód kimásolva",
|
||||
"undo_friendship_modal_text": "Ezáltal megszünteted a barátságod vele: {{displayName}}",
|
||||
"privacy_hint": "Hogy beállítsd ki láthassa ezt, menj a <0>Beállítások</0> menüjébe",
|
||||
"locked_profile": "Ez a profil privát",
|
||||
"image_process_failure": "Hiba a kép feldolgozása közben",
|
||||
"required_field": "Ez a mező kötelező",
|
||||
"displayname_min_length": "A megjelenített névnek legalább 3 karakter hosszúnak kell lennie",
|
||||
"displayname_max_length": "A megjelenített név hossza legfeljebb 50 karakter lehet",
|
||||
"report_profile": "Profil bejelentése",
|
||||
"report_reason": "Miért jelented ezt a profilt?",
|
||||
"report_description": "További információ",
|
||||
"report_description_placeholder": "További információ",
|
||||
"report": "Bejelentés",
|
||||
"report_reason_hate": "Gyűlöletbeszéd",
|
||||
"report_reason_sexual_content": "Szexuális tartalom",
|
||||
"report_reason_violence": "Fenyegető",
|
||||
"report_reason_spam": "Spam",
|
||||
"report_reason_other": "Egyéb",
|
||||
"profile_reported": "Profil jelentve",
|
||||
"your_friend_code": "A barát kódod:",
|
||||
"upload_banner": "Borítókép feltöltés",
|
||||
"uploading_banner": "Borítókép feltöltése…",
|
||||
"background_image_updated": "Borítókép frissítve",
|
||||
"stats": "Statisztikák",
|
||||
"achievements": "achievementek",
|
||||
"games": "Játékok",
|
||||
"top_percentile": "Top {{percentile}}%",
|
||||
"ranking_updated_weekly": "A rangsor hetente frissül.",
|
||||
"playing": "Játékban: {{game}}",
|
||||
"achievements_unlocked": "Achievementek feloldva",
|
||||
"earned_points": "Megszerzett pontok",
|
||||
"show_achievements_on_profile": "Mutasd az achievementjeid a profilodon",
|
||||
"show_points_on_profile": "Mutasd a megszerzett pontjaid a profilodon",
|
||||
"error_adding_friend": "Hiba, barátfelkérés sikertelen. Kérlek ellenőrízd a barát kódot",
|
||||
"friend_code_length_error": "A barát kódnak 8 karakterből kell állnia",
|
||||
"game_removed_from_pinned": "Játék eltávolítva a kitűzöttek közül",
|
||||
"game_added_to_pinned": "Játék hozzáadva a kitűzöttekhez"
|
||||
},
|
||||
"achievement": {
|
||||
"achievement_unlocked": "Achievement feloldva",
|
||||
"user_achievements": "{{displayName}} Achievementjei",
|
||||
"your_achievements": "A te Achievementjeid",
|
||||
"unlocked_at": "Feloldva ekkor: {{date}}",
|
||||
"subscription_needed": "A tartalom megtekintéséhez Hydra Cloud előfizetés szükséges",
|
||||
"new_achievements_unlocked": "{{achievementCount}} új achievementet oldottál fel {{gameCount}} játékban",
|
||||
"achievement_progress": "{{unlockedCount}}/{{totalCount}} achievementek",
|
||||
"achievements_unlocked_for_game": "{{achievementCount}} új achievementet oldottál fel a(z) {{gameTitle}} játékban",
|
||||
"hidden_achievement_tooltip": "Ez egy rejtett achievement",
|
||||
"achievement_earn_points": "Szerezz be {{points}} pontot ezzel az achievement-el",
|
||||
"earned_points": "Megszerzett pontok:",
|
||||
"available_points": "Elérhető pontok:",
|
||||
"how_to_earn_achievements_points": "Hogy lehet elérni achievement pontokat?"
|
||||
},
|
||||
"hydra_cloud": {
|
||||
"subscription_tour_title": "Hydra Cloud Előfizetés",
|
||||
"subscribe_now": "Előfizetés",
|
||||
"cloud_saving": "Felhőmentés",
|
||||
"cloud_achievements": "Mentsd az achievementjeid el a felhőben",
|
||||
"animated_profile_picture": "Animált profilkép",
|
||||
"premium_support": "Premium Támogatás",
|
||||
"show_and_compare_achievements": "Jelenítsd és hasonlítsd az elért achievementjeid másokéhoz",
|
||||
"animated_profile_banner": "Animált profil borítókép",
|
||||
"hydra_cloud": "Hydra Cloud",
|
||||
"hydra_cloud_feature_found": "Épp felfedeztél egy Hydra Cloud funkciót!",
|
||||
"learn_more": "Tudj meg többet",
|
||||
"debrid_description": "Akár 4x gyorsabb letöltés a Nimbusszal"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"successfully_signed_in": "Berhasil masuk"
|
||||
},
|
||||
"home": {
|
||||
"featured": "Unggulan",
|
||||
"surprise_me": "Kejutkan saya",
|
||||
"no_results": "Tidak ada hasil ditemukan"
|
||||
},
|
||||
@@ -25,7 +24,6 @@
|
||||
},
|
||||
"header": {
|
||||
"search": "Cari game",
|
||||
|
||||
"home": "Beranda",
|
||||
"catalogue": "Katalog",
|
||||
"downloads": "Unduhan",
|
||||
@@ -41,10 +39,7 @@
|
||||
"calculating_eta": "Mengunduh {{title}}… ({{percentage}} selesai) - Menghitung waktu yang tersisa…",
|
||||
"checking_files": "Memeriksa file {{title}}… ({{percentage}} selesai)"
|
||||
},
|
||||
"catalogue": {
|
||||
"next_page": "Halaman Berikutnya",
|
||||
"previous_page": "Halaman Sebelumnya"
|
||||
},
|
||||
"catalogue": {},
|
||||
"game_details": {
|
||||
"open_download_options": "Buka opsi unduhan",
|
||||
"download_options_zero": "Tidak ada opsi unduhan",
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"language_name": "Italiano",
|
||||
"home": {
|
||||
"featured": "In primo piano",
|
||||
"surprise_me": "Sorprendimi",
|
||||
"no_results": "Nessun risultato trovato"
|
||||
},
|
||||
@@ -20,7 +19,6 @@
|
||||
},
|
||||
"header": {
|
||||
"search": "Cerca",
|
||||
|
||||
"home": "Home",
|
||||
"catalogue": "Catalogo",
|
||||
"downloads": "Download",
|
||||
@@ -32,10 +30,7 @@
|
||||
"downloading_metadata": "Scaricamento metadati di {{title}}…",
|
||||
"downloading": "Download di {{title}}… ({{percentage}} completato) - Conclusione {{eta}} - {{speed}}"
|
||||
},
|
||||
"catalogue": {
|
||||
"next_page": "Pagina successiva",
|
||||
"previous_page": "Pagina precedente"
|
||||
},
|
||||
"catalogue": {},
|
||||
"game_details": {
|
||||
"open_download_options": "Apri opzioni di download",
|
||||
"download_options_zero": "Nessuna opzione di download",
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"successfully_signed_in": "Сәтті кіру"
|
||||
},
|
||||
"home": {
|
||||
"featured": "Ұсынылған",
|
||||
"surprise_me": "Таңқалдыр",
|
||||
"no_results": "Ештеңе табылмады"
|
||||
},
|
||||
@@ -23,7 +22,6 @@
|
||||
"sign_in": "Кіру",
|
||||
"favorites": "Таңдаулылар"
|
||||
},
|
||||
|
||||
"header": {
|
||||
"search": "Іздеу",
|
||||
"home": "Басты бет",
|
||||
@@ -40,10 +38,7 @@
|
||||
"downloading": "Жүктеу {{title}}… ({{percentage}} аяқталды) - Аяқтау {{eta}} - {{speed}}",
|
||||
"calculating_eta": "Жүктеу {{title}}… ({{percentage}} аяқталды) - Қалған уақытты есептеу…"
|
||||
},
|
||||
"catalogue": {
|
||||
"next_page": "Келесі бет",
|
||||
"previous_page": "Алдыңғы бет"
|
||||
},
|
||||
"catalogue": {},
|
||||
"game_details": {
|
||||
"open_download_options": "Жүктеу нұсқаларын ашу",
|
||||
"download_options_zero": "Жүктеу нұсқалары жоқ",
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"language_name": "한국어",
|
||||
"home": {
|
||||
"featured": "추천",
|
||||
"surprise_me": "무작위 추천",
|
||||
"no_results": "결과 없음"
|
||||
},
|
||||
@@ -17,7 +16,6 @@
|
||||
"home": "홈",
|
||||
"favorites": "즐겨찾기"
|
||||
},
|
||||
|
||||
"header": {
|
||||
"search": "게임 검색하기",
|
||||
"home": "홈",
|
||||
@@ -31,10 +29,7 @@
|
||||
"downloading_metadata": "{{title}}의 메타데이터를 다운로드 중…",
|
||||
"downloading": "{{title}}의 파일들을 다운로드 중… ({{percentage}} 완료) - 완료까지 {{eta}} - {{speed}}"
|
||||
},
|
||||
"catalogue": {
|
||||
"next_page": "다음 페이지",
|
||||
"previous_page": "이전 페이지"
|
||||
},
|
||||
"catalogue": {},
|
||||
"game_details": {
|
||||
"open_download_options": "다운로드 선택지 열기",
|
||||
"download_options_zero": "다운로드 선택지 없음",
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"successfully_signed_in": "Logget inn vellykket"
|
||||
},
|
||||
"home": {
|
||||
"featured": "Anbefalinger",
|
||||
"surprise_me": "Overrask meg",
|
||||
"no_results": "Ingen resultater fundet",
|
||||
"start_typing": "Begynn å skrive for å søke...",
|
||||
@@ -29,7 +28,6 @@
|
||||
},
|
||||
"header": {
|
||||
"search": "Søk efter spill",
|
||||
|
||||
"home": "Hjem",
|
||||
"catalogue": "Katalog",
|
||||
"downloads": "Nedlastinger",
|
||||
@@ -45,10 +43,7 @@
|
||||
"calculating_eta": "Laster ned {{title}}… ({{percentage}} ferdig) - Regner ut resterende tid…",
|
||||
"checking_files": "Sjekker {{title}} filer… ({{percentage}} ferdig)"
|
||||
},
|
||||
"catalogue": {
|
||||
"next_page": "Neste side",
|
||||
"previous_page": "Forrige side"
|
||||
},
|
||||
"catalogue": {},
|
||||
"game_details": {
|
||||
"open_download_options": "Åpne nedlastingsmuligheter",
|
||||
"download_options_zero": "Ingen nedlastingsmulighet",
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"language_name": "Nederlands",
|
||||
"home": {
|
||||
"featured": "Uitgelicht",
|
||||
"surprise_me": "Verrasing",
|
||||
"no_results": "Geen resultaten gevonden"
|
||||
},
|
||||
@@ -19,7 +18,6 @@
|
||||
},
|
||||
"header": {
|
||||
"search": "Zoek spellen",
|
||||
|
||||
"home": "Home",
|
||||
"catalogue": "Bibliotheek",
|
||||
"downloads": "Downloads",
|
||||
@@ -31,10 +29,7 @@
|
||||
"downloading_metadata": "Downloading {{title}} metadata…",
|
||||
"downloading": "Downloading {{title}}… ({{percentage}} complete) - Conclusion {{eta}} - {{speed}}"
|
||||
},
|
||||
"catalogue": {
|
||||
"next_page": "Volgende Pagina",
|
||||
"previous_page": "Vorige Pagina"
|
||||
},
|
||||
"catalogue": {},
|
||||
"game_details": {
|
||||
"open_download_options": "Open download Instellingen",
|
||||
"download_options_zero": "Geen download Instellingen",
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"language_name": "Polski",
|
||||
"home": {
|
||||
"featured": "Wyróżnione",
|
||||
"surprise_me": "Zaskocz mnie",
|
||||
"no_results": "Nie znaleziono wyników"
|
||||
},
|
||||
@@ -20,7 +19,6 @@
|
||||
},
|
||||
"header": {
|
||||
"search": "Szukaj",
|
||||
|
||||
"home": "Główna",
|
||||
"catalogue": "Katalog",
|
||||
"downloads": "Pobrane",
|
||||
@@ -32,10 +30,7 @@
|
||||
"downloading_metadata": "Pobieranie {{title}} metadata…",
|
||||
"downloading": "Pobieranie {{title}}… (ukończone w {{percentage}}) - Podsumowanie {{eta}} - {{speed}}"
|
||||
},
|
||||
"catalogue": {
|
||||
"next_page": "Następna strona",
|
||||
"previous_page": "Poprzednia strona"
|
||||
},
|
||||
"catalogue": {},
|
||||
"game_details": {
|
||||
"open_download_options": "Otwórz opcje pobierania",
|
||||
"download_options_zero": "Brak opcji pobierania",
|
||||
|
||||
@@ -26,7 +26,22 @@
|
||||
"sign_in": "Login",
|
||||
"friends": "Amigos",
|
||||
"need_help": "Precisa de ajuda?",
|
||||
"favorites": "Favoritos"
|
||||
"favorites": "Favoritos",
|
||||
"add_custom_game_tooltip": "Adicionar jogo personalizado",
|
||||
"custom_game_modal": "Adicionar jogo personalizado",
|
||||
"edit_game_modal_title": "Título",
|
||||
"playable_button_title": "",
|
||||
"custom_game_modal_add": "Adicionar Jogo",
|
||||
"custom_game_modal_adding": "Adicionando...",
|
||||
"custom_game_modal_browse": "Buscar",
|
||||
"custom_game_modal_cancel": "Cancelar",
|
||||
"edit_game_modal_assets": "Imagens",
|
||||
"edit_game_modal_icon": "Ícone",
|
||||
"edit_game_modal_browse": "Buscar",
|
||||
"edit_game_modal_cancel": "Cancelar",
|
||||
"edit_game_modal_enter_title": "Insira o título",
|
||||
"edit_game_modal_logo": "Logo",
|
||||
"edit_game_modal": "Personalizar detalhes"
|
||||
},
|
||||
"header": {
|
||||
"search": "Buscar jogos",
|
||||
@@ -228,7 +243,20 @@
|
||||
"historical_keyshop": "Preço histórico em keyshops",
|
||||
"language": "Idioma",
|
||||
"caption": "Legenda",
|
||||
"audio": "Áudio"
|
||||
"audio": "Áudio",
|
||||
"filter_by_source": "Filtrar por fonte",
|
||||
"no_repacks_found": "Nenhuma fonte encontrada para este jogo",
|
||||
"edit_game_modal_button": "Alterar detalhes do jogo",
|
||||
"game_added_to_pinned": "Jogo adicionado aos fixados",
|
||||
"game_removed_from_pinned": "Jogo removido dos fixados",
|
||||
"manual_playtime_tooltip": "Este tempo de jogo foi atualizado manualmente",
|
||||
"manual_playtime_warning": "As suas horas de jogo serão marcadas como atualizadas manualmente. Esta ação não pode ser desfeita.",
|
||||
"missing_wine_prefix": "Um prefixo Wine é necessário para criar um backup no Linux",
|
||||
"update_game_playtime": "Modificar tempo de jogo",
|
||||
"update_playtime": "Modificar tempo de jogo",
|
||||
"update_playtime_description": "Atualizar manualmente o tempo de jogo de {{game}}",
|
||||
"update_playtime_error": "Falha ao atualizar tempo de jogo",
|
||||
"update_playtime_title": "Atualizar tempo de jogo"
|
||||
},
|
||||
"activation": {
|
||||
"title": "Ativação",
|
||||
@@ -403,7 +431,8 @@
|
||||
"hidden": "Oculta",
|
||||
"test_notification": "Testar notificação",
|
||||
"notification_preview": "Prévia da Notificação de Conquistas",
|
||||
"enable_friend_start_game_notifications": "Quando um amigo iniciar um jogo"
|
||||
"enable_friend_start_game_notifications": "Quando um amigo iniciar um jogo",
|
||||
"editor_tab_code": "Código"
|
||||
},
|
||||
"notifications": {
|
||||
"download_complete": "Download concluído",
|
||||
@@ -532,7 +561,15 @@
|
||||
"show_achievements_on_profile": "Exiba suas conquistas no perfil",
|
||||
"show_points_on_profile": "Exiba seus pontos ganhos no perfil",
|
||||
"error_adding_friend": "Não foi possível enviar o pedido de amizade. Verifique o código de amizade inserido",
|
||||
"friend_code_length_error": "Código de amigo deve ter 8 caracteres"
|
||||
"friend_code_length_error": "Código de amigo deve ter 8 caracteres",
|
||||
"top_percentile": "Top {{percentile}}%",
|
||||
"playtime": "Tempo de jogo",
|
||||
"played_recently": "Jogado recentemente",
|
||||
"pinned": "Fixado",
|
||||
"amount_minutes_short": "{{amount}}m",
|
||||
"amount_hours_short": "{{amount}}h",
|
||||
"game_added_to_pinned": "Jogo adicionado aos fixados",
|
||||
"achievements_earned": "Conquistas recebidas"
|
||||
},
|
||||
"achievement": {
|
||||
"achievement_unlocked": "Conquista desbloqueada",
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"successfully_signed_in": "Sessão iniciada com sucesso"
|
||||
},
|
||||
"home": {
|
||||
"featured": "Destaques",
|
||||
"hot": "Populares",
|
||||
"weekly": "📅 Mais descarregados esta semana",
|
||||
"achievements": "🏆 Para completar",
|
||||
@@ -26,7 +25,8 @@
|
||||
"game_has_no_executable": "O jogo não tem um executável selecionado",
|
||||
"sign_in": "Iniciar sessão",
|
||||
"friends": "Amigos",
|
||||
"favorites": "Favoritos"
|
||||
"favorites": "Favoritos",
|
||||
"edit_game_modal_cancel": "Cancelar"
|
||||
},
|
||||
"header": {
|
||||
"search": "Procurar jogos",
|
||||
@@ -247,9 +247,6 @@
|
||||
"download_count_zero": "Sem downloads na lista",
|
||||
"download_count_one": "{{countFormatted}} download na lista",
|
||||
"download_count_other": "{{countFormatted}} downloads na lista",
|
||||
"download_options_zero": "Sem downloads disponíveis",
|
||||
"download_options_one": "{{countFormatted}} download disponível",
|
||||
"download_options_other": "{{countFormatted}} downloads disponíveis",
|
||||
"download_source_url": "URL da fonte",
|
||||
"add_download_source_description": "Insere o URL que contém o ficheiro .json",
|
||||
"download_source_up_to_date": "Sincronizada",
|
||||
@@ -359,8 +356,6 @@
|
||||
"instructions": "Verifica a forma correta de instalar algum deles na tua distribuição Linux, para garantir a execução normal do jogo"
|
||||
},
|
||||
"catalogue": {
|
||||
"next_page": "Página seguinte",
|
||||
"previous_page": "Página anterior",
|
||||
"search": "Filtrar…",
|
||||
"developers": "Desenvolvedores",
|
||||
"genres": "Géneros",
|
||||
@@ -427,7 +422,6 @@
|
||||
"friend_code_copied": "Código de amigo copiado",
|
||||
"undo_friendship_modal_text": "Isto vai remover a tua amizade com {{displayName}}",
|
||||
"privacy_hint": "Para controlar quem pode ver o teu perfil, acede às <0>Definições</0>",
|
||||
"profile_locked": "Este perfil é privado",
|
||||
"image_process_failure": "Falha ao processar a imagem",
|
||||
"required_field": "Este campo é obrigatório",
|
||||
"displayname_min_length": "O nome de apresentação deve ter pelo menos 3 caracteres",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -4,14 +4,12 @@
|
||||
"successfully_signed_in": "Успешный вход"
|
||||
},
|
||||
"home": {
|
||||
"featured": "Рекомендации",
|
||||
"surprise_me": "Удиви меня",
|
||||
"no_results": "Ничего не найдено",
|
||||
"hot": "Сейчас популярно",
|
||||
"start_typing": "Начинаю вводить текст...",
|
||||
"weekly": "📅 Лучшие игры недели",
|
||||
"achievements": "🏆 Игры с достижениями",
|
||||
"already_in_library": "Уже в библиотеке"
|
||||
"achievements": "🏆 Игры с достижениями"
|
||||
},
|
||||
"sidebar": {
|
||||
"catalogue": "Каталог",
|
||||
@@ -69,7 +67,14 @@
|
||||
"edit_game_modal_image_filter": "Изображение",
|
||||
"edit_game_modal_icon_resolution": "Рекомендуемое разрешение: 256x256px",
|
||||
"edit_game_modal_logo_resolution": "Рекомендуемое разрешение: 640x360px",
|
||||
"edit_game_modal_hero_resolution": "Рекомендуемое разрешение: 1920x620px"
|
||||
"edit_game_modal_hero_resolution": "Рекомендуемое разрешение: 1920x620px",
|
||||
"edit_game_modal_assets": "Ресурсы",
|
||||
"edit_game_modal_drop_icon_image_here": "Перетащите изображение иконки сюда",
|
||||
"edit_game_modal_drop_logo_image_here": "Перетащите изображение логотипа сюда",
|
||||
"edit_game_modal_drop_hero_image_here": "Перетащите изображение обложки сюда",
|
||||
"edit_game_modal_drop_to_replace_icon": "Перетащите для замены иконки",
|
||||
"edit_game_modal_drop_to_replace_logo": "Перетащите для замены логотипа",
|
||||
"edit_game_modal_drop_to_replace_hero": "Перетащите для замены обложки"
|
||||
},
|
||||
"header": {
|
||||
"search": "Поиск",
|
||||
@@ -486,6 +491,8 @@
|
||||
"user_profile": {
|
||||
"amount_hours": "{{amount}} часов",
|
||||
"amount_minutes": "{{amount}} минут",
|
||||
"amount_hours_short": "{{amount}}ч",
|
||||
"amount_minutes_short": "{{amount}}м",
|
||||
"last_time_played": "Последняя игра {{period}}",
|
||||
"activity": "Недавняя активность",
|
||||
"library": "Библиотека",
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"successfully_signed_in": "Inloggningen lyckades"
|
||||
},
|
||||
"home": {
|
||||
"featured": "Utvalt",
|
||||
"surprise_me": "Överraska mig",
|
||||
"no_results": "Inga resultat hittades",
|
||||
"start_typing": "Börja skriva för att söka...",
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"successfully_signed_in": "Başarıyla giriş yapıldı"
|
||||
},
|
||||
"home": {
|
||||
"featured": "Öne Çıkanlar",
|
||||
"surprise_me": "Beni Şaşırt",
|
||||
"no_results": "Sonuç bulunamadı",
|
||||
"start_typing": "Aramak için yazmaya başlayın...",
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"successfully_signed_in": "Успішний вхід в систему"
|
||||
},
|
||||
"home": {
|
||||
"featured": "Рекомендоване",
|
||||
"surprise_me": "Здивуй мене",
|
||||
"no_results": "Результатів не знайдено",
|
||||
"start_typing": "Почніть набирати текст для пошуку...",
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"successfully_signed_in": "Kirish muvaffaqiyatli amalga oshirildi"
|
||||
},
|
||||
"home": {
|
||||
"featured": "Tavsiya etilgan",
|
||||
"surprise_me": "Hayratda qoldir",
|
||||
"no_results": "Natijalar topilmadi",
|
||||
"hot": "Eng mashhur",
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
"successfully_signed_in": "已成功登录"
|
||||
},
|
||||
"home": {
|
||||
"featured": "特色推荐",
|
||||
"surprise_me": "向我推荐",
|
||||
"no_results": "没有找到结果",
|
||||
"start_typing": "键入以开始搜素...",
|
||||
@@ -51,8 +50,6 @@
|
||||
"installing_common_redist": "{{log}}…"
|
||||
},
|
||||
"catalogue": {
|
||||
"next_page": "下一页",
|
||||
"previous_page": "上一页",
|
||||
"clear_filters": "清除已选的 {{filterCount}} 项",
|
||||
"developers": "开发商",
|
||||
"download_sources": "下载源",
|
||||
|
||||
@@ -21,11 +21,9 @@ const getGameStats = async (
|
||||
return cachedStats;
|
||||
}
|
||||
|
||||
return HydraApi.get<GameStats>(
|
||||
`/games/stats`,
|
||||
{ objectId, shop },
|
||||
{ needsAuth: false }
|
||||
).then(async (data) => {
|
||||
return HydraApi.get<GameStats>(`/games/${shop}/${objectId}/stats`, null, {
|
||||
needsAuth: false,
|
||||
}).then(async (data) => {
|
||||
await gamesStatsCacheSublevel.put(levelKeys.game(shop, objectId), {
|
||||
...data,
|
||||
updatedAt: Date.now(),
|
||||
|
||||
@@ -8,12 +8,7 @@ const getHowLongToBeat = async (
|
||||
objectId: string,
|
||||
shop: GameShop
|
||||
): Promise<HowLongToBeatCategory[] | null> => {
|
||||
const params = new URLSearchParams({
|
||||
objectId,
|
||||
shop,
|
||||
});
|
||||
|
||||
return HydraApi.get(`/games/how-long-to-beat?${params.toString()}`, null, {
|
||||
return HydraApi.get(`/games/${shop}/${objectId}/how-long-to-beat`, null, {
|
||||
needsAuth: false,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -11,7 +11,7 @@ const getTrendingGames = async (_event: Electron.IpcMainInvokeEvent) => {
|
||||
.then((language) => language || "en");
|
||||
|
||||
const trendingGames = await HydraApi.get<TrendingGame[]>(
|
||||
"/games/featured",
|
||||
"/catalogue/featured",
|
||||
{ language },
|
||||
{ needsAuth: false }
|
||||
).catch(() => []);
|
||||
|
||||
@@ -87,7 +87,7 @@ const createSteamShortcut = async (
|
||||
}
|
||||
|
||||
const { assets } = await HydraApi.get<GameStats>(
|
||||
`/games/stats?objectId=${objectId}&shop=${shop}`
|
||||
`/games/${shop}/${objectId}/stats`
|
||||
);
|
||||
|
||||
const steamUserIds = await getSteamUsersIds();
|
||||
|
||||
@@ -1,7 +1,70 @@
|
||||
import { registerEvent } from "../register-event";
|
||||
import { HydraApi } from "@main/services";
|
||||
import { gamesSublevel, levelKeys } from "@main/level";
|
||||
import type { GameShop } from "@types";
|
||||
import { HydraApi, logger } from "@main/services";
|
||||
import { gamesSublevel, gamesShopAssetsSublevel, levelKeys } from "@main/level";
|
||||
import type { GameShop, Game } from "@types";
|
||||
import fs from "node:fs";
|
||||
|
||||
const collectAssetPathsToDelete = (game: Game): string[] => {
|
||||
const assetPathsToDelete: string[] = [];
|
||||
|
||||
const assetUrls =
|
||||
game.shop === "custom"
|
||||
? [game.iconUrl, game.logoImageUrl, game.libraryHeroImageUrl]
|
||||
: [game.customIconUrl, game.customLogoImageUrl, game.customHeroImageUrl];
|
||||
|
||||
for (const url of assetUrls) {
|
||||
if (url?.startsWith("local:")) {
|
||||
assetPathsToDelete.push(url.replace("local:", ""));
|
||||
}
|
||||
}
|
||||
|
||||
return assetPathsToDelete;
|
||||
};
|
||||
|
||||
const updateGameAsDeleted = async (
|
||||
game: Game,
|
||||
gameKey: string
|
||||
): Promise<void> => {
|
||||
const updatedGame = {
|
||||
...game,
|
||||
isDeleted: true,
|
||||
executablePath: null,
|
||||
...(game.shop !== "custom" && {
|
||||
customIconUrl: null,
|
||||
customLogoImageUrl: null,
|
||||
customHeroImageUrl: null,
|
||||
}),
|
||||
};
|
||||
|
||||
await gamesSublevel.put(gameKey, updatedGame);
|
||||
};
|
||||
|
||||
const resetShopAssets = async (gameKey: string): Promise<void> => {
|
||||
const existingAssets = await gamesShopAssetsSublevel.get(gameKey);
|
||||
if (existingAssets) {
|
||||
const resetAssets = {
|
||||
...existingAssets,
|
||||
title: existingAssets.title,
|
||||
};
|
||||
await gamesShopAssetsSublevel.put(gameKey, resetAssets);
|
||||
}
|
||||
};
|
||||
|
||||
const deleteAssetFiles = async (
|
||||
assetPathsToDelete: string[]
|
||||
): Promise<void> => {
|
||||
if (assetPathsToDelete.length === 0) return;
|
||||
|
||||
for (const assetPath of assetPathsToDelete) {
|
||||
try {
|
||||
if (fs.existsSync(assetPath)) {
|
||||
await fs.promises.unlink(assetPath);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn(`Failed to delete asset ${assetPath}:`, error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const removeGameFromLibrary = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
@@ -11,17 +74,21 @@ const removeGameFromLibrary = async (
|
||||
const gameKey = levelKeys.game(shop, objectId);
|
||||
const game = await gamesSublevel.get(gameKey);
|
||||
|
||||
if (game) {
|
||||
await gamesSublevel.put(gameKey, {
|
||||
...game,
|
||||
isDeleted: true,
|
||||
executablePath: null,
|
||||
});
|
||||
if (!game) return;
|
||||
|
||||
if (game?.remoteId) {
|
||||
HydraApi.delete(`/profile/games/${game.remoteId}`).catch(() => {});
|
||||
}
|
||||
const assetPathsToDelete = collectAssetPathsToDelete(game);
|
||||
|
||||
await updateGameAsDeleted(game, gameKey);
|
||||
|
||||
if (game.shop !== "custom") {
|
||||
await resetShopAssets(gameKey);
|
||||
}
|
||||
|
||||
if (game?.remoteId) {
|
||||
HydraApi.delete(`/profile/games/${game.remoteId}`).catch(() => {});
|
||||
}
|
||||
|
||||
await deleteAssetFiles(assetPathsToDelete);
|
||||
};
|
||||
|
||||
registerEvent("removeGameFromLibrary", removeGameFromLibrary);
|
||||
|
||||
@@ -1,16 +1,36 @@
|
||||
import { registerEvent } from "../register-event";
|
||||
import { gamesSublevel, gamesShopAssetsSublevel, levelKeys } from "@main/level";
|
||||
import type { GameShop } from "@types";
|
||||
import fs from "node:fs";
|
||||
import { logger } from "@main/services";
|
||||
|
||||
interface UpdateCustomGameParams {
|
||||
shop: GameShop;
|
||||
objectId: string;
|
||||
title: string;
|
||||
iconUrl?: string;
|
||||
logoImageUrl?: string;
|
||||
libraryHeroImageUrl?: string;
|
||||
originalIconPath?: string;
|
||||
originalLogoPath?: string;
|
||||
originalHeroPath?: string;
|
||||
}
|
||||
|
||||
const updateCustomGame = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
shop: GameShop,
|
||||
objectId: string,
|
||||
title: string,
|
||||
iconUrl?: string,
|
||||
logoImageUrl?: string,
|
||||
libraryHeroImageUrl?: string
|
||||
params: UpdateCustomGameParams
|
||||
) => {
|
||||
const {
|
||||
shop,
|
||||
objectId,
|
||||
title,
|
||||
iconUrl,
|
||||
logoImageUrl,
|
||||
libraryHeroImageUrl,
|
||||
originalIconPath,
|
||||
originalLogoPath,
|
||||
originalHeroPath,
|
||||
} = params;
|
||||
const gameKey = levelKeys.game(shop, objectId);
|
||||
|
||||
const existingGame = await gamesSublevel.get(gameKey);
|
||||
@@ -18,12 +38,29 @@ const updateCustomGame = async (
|
||||
throw new Error("Game not found");
|
||||
}
|
||||
|
||||
const oldAssetPaths: string[] = [];
|
||||
|
||||
const assetPairs = [
|
||||
{ existing: existingGame.iconUrl, new: iconUrl },
|
||||
{ existing: existingGame.logoImageUrl, new: logoImageUrl },
|
||||
{ existing: existingGame.libraryHeroImageUrl, new: libraryHeroImageUrl },
|
||||
];
|
||||
|
||||
for (const { existing, new: newUrl } of assetPairs) {
|
||||
if (existing?.startsWith("local:") && (!newUrl || existing !== newUrl)) {
|
||||
oldAssetPaths.push(existing.replace("local:", ""));
|
||||
}
|
||||
}
|
||||
|
||||
const updatedGame = {
|
||||
...existingGame,
|
||||
title,
|
||||
iconUrl: iconUrl || null,
|
||||
logoImageUrl: logoImageUrl || null,
|
||||
libraryHeroImageUrl: libraryHeroImageUrl || null,
|
||||
originalIconPath: originalIconPath || existingGame.originalIconPath || null,
|
||||
originalLogoPath: originalLogoPath || existingGame.originalLogoPath || null,
|
||||
originalHeroPath: originalHeroPath || existingGame.originalHeroPath || null,
|
||||
};
|
||||
|
||||
await gamesSublevel.put(gameKey, updatedGame);
|
||||
@@ -43,6 +80,18 @@ const updateCustomGame = async (
|
||||
await gamesShopAssetsSublevel.put(gameKey, updatedAssets);
|
||||
}
|
||||
|
||||
if (oldAssetPaths.length > 0) {
|
||||
for (const assetPath of oldAssetPaths) {
|
||||
try {
|
||||
if (fs.existsSync(assetPath)) {
|
||||
await fs.promises.unlink(assetPath);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn(`Failed to delete old asset ${assetPath}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return updatedGame;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,16 +1,131 @@
|
||||
import { registerEvent } from "../register-event";
|
||||
import { gamesSublevel, gamesShopAssetsSublevel, levelKeys } from "@main/level";
|
||||
import type { GameShop } from "@types";
|
||||
import type { GameShop, Game } from "@types";
|
||||
import fs from "node:fs";
|
||||
import { logger } from "@main/services";
|
||||
|
||||
const updateGameCustomAssets = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
shop: GameShop,
|
||||
objectId: string,
|
||||
title: string,
|
||||
const collectOldAssetPaths = (
|
||||
existingGame: Game,
|
||||
customIconUrl?: string | null,
|
||||
customLogoImageUrl?: string | null,
|
||||
customHeroImageUrl?: string | null
|
||||
): string[] => {
|
||||
const oldAssetPaths: string[] = [];
|
||||
|
||||
const assetPairs = [
|
||||
{ existing: existingGame.customIconUrl, new: customIconUrl },
|
||||
{ existing: existingGame.customLogoImageUrl, new: customLogoImageUrl },
|
||||
{ existing: existingGame.customHeroImageUrl, new: customHeroImageUrl },
|
||||
];
|
||||
|
||||
for (const { existing, new: newUrl } of assetPairs) {
|
||||
if (
|
||||
existing &&
|
||||
newUrl !== undefined &&
|
||||
existing !== newUrl &&
|
||||
existing.startsWith("local:")
|
||||
) {
|
||||
oldAssetPaths.push(existing.replace("local:", ""));
|
||||
}
|
||||
}
|
||||
|
||||
return oldAssetPaths;
|
||||
};
|
||||
|
||||
interface UpdateGameDataParams {
|
||||
gameKey: string;
|
||||
existingGame: Game;
|
||||
title: string;
|
||||
customIconUrl?: string | null;
|
||||
customLogoImageUrl?: string | null;
|
||||
customHeroImageUrl?: string | null;
|
||||
customOriginalIconPath?: string | null;
|
||||
customOriginalLogoPath?: string | null;
|
||||
customOriginalHeroPath?: string | null;
|
||||
}
|
||||
|
||||
const updateGameData = async (params: UpdateGameDataParams): Promise<Game> => {
|
||||
const {
|
||||
gameKey,
|
||||
existingGame,
|
||||
title,
|
||||
customIconUrl,
|
||||
customLogoImageUrl,
|
||||
customHeroImageUrl,
|
||||
customOriginalIconPath,
|
||||
customOriginalLogoPath,
|
||||
customOriginalHeroPath,
|
||||
} = params;
|
||||
const updatedGame = {
|
||||
...existingGame,
|
||||
title,
|
||||
...(customIconUrl !== undefined && { customIconUrl }),
|
||||
...(customLogoImageUrl !== undefined && { customLogoImageUrl }),
|
||||
...(customHeroImageUrl !== undefined && { customHeroImageUrl }),
|
||||
...(customOriginalIconPath !== undefined && { customOriginalIconPath }),
|
||||
...(customOriginalLogoPath !== undefined && { customOriginalLogoPath }),
|
||||
...(customOriginalHeroPath !== undefined && { customOriginalHeroPath }),
|
||||
};
|
||||
|
||||
await gamesSublevel.put(gameKey, updatedGame);
|
||||
return updatedGame;
|
||||
};
|
||||
|
||||
const updateShopAssets = async (
|
||||
gameKey: string,
|
||||
title: string
|
||||
): Promise<void> => {
|
||||
const existingAssets = await gamesShopAssetsSublevel.get(gameKey);
|
||||
if (existingAssets) {
|
||||
const updatedAssets = {
|
||||
...existingAssets,
|
||||
title,
|
||||
};
|
||||
await gamesShopAssetsSublevel.put(gameKey, updatedAssets);
|
||||
}
|
||||
};
|
||||
|
||||
const deleteOldAssetFiles = async (oldAssetPaths: string[]): Promise<void> => {
|
||||
if (oldAssetPaths.length === 0) return;
|
||||
|
||||
for (const assetPath of oldAssetPaths) {
|
||||
try {
|
||||
if (fs.existsSync(assetPath)) {
|
||||
await fs.promises.unlink(assetPath);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn(`Failed to delete old custom asset ${assetPath}:`, error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
interface UpdateGameCustomAssetsParams {
|
||||
shop: GameShop;
|
||||
objectId: string;
|
||||
title: string;
|
||||
customIconUrl?: string | null;
|
||||
customLogoImageUrl?: string | null;
|
||||
customHeroImageUrl?: string | null;
|
||||
customOriginalIconPath?: string | null;
|
||||
customOriginalLogoPath?: string | null;
|
||||
customOriginalHeroPath?: string | null;
|
||||
}
|
||||
|
||||
const updateGameCustomAssets = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
params: UpdateGameCustomAssetsParams
|
||||
) => {
|
||||
const {
|
||||
shop,
|
||||
objectId,
|
||||
title,
|
||||
customIconUrl,
|
||||
customLogoImageUrl,
|
||||
customHeroImageUrl,
|
||||
customOriginalIconPath,
|
||||
customOriginalLogoPath,
|
||||
customOriginalHeroPath,
|
||||
} = params;
|
||||
const gameKey = levelKeys.game(shop, objectId);
|
||||
|
||||
const existingGame = await gamesSublevel.get(gameKey);
|
||||
@@ -18,26 +133,28 @@ const updateGameCustomAssets = async (
|
||||
throw new Error("Game not found");
|
||||
}
|
||||
|
||||
const updatedGame = {
|
||||
...existingGame,
|
||||
const oldAssetPaths = collectOldAssetPaths(
|
||||
existingGame,
|
||||
customIconUrl,
|
||||
customLogoImageUrl,
|
||||
customHeroImageUrl
|
||||
);
|
||||
|
||||
const updatedGame = await updateGameData({
|
||||
gameKey,
|
||||
existingGame,
|
||||
title,
|
||||
...(customIconUrl !== undefined && { customIconUrl }),
|
||||
...(customLogoImageUrl !== undefined && { customLogoImageUrl }),
|
||||
...(customHeroImageUrl !== undefined && { customHeroImageUrl }),
|
||||
};
|
||||
customIconUrl,
|
||||
customLogoImageUrl,
|
||||
customHeroImageUrl,
|
||||
customOriginalIconPath,
|
||||
customOriginalLogoPath,
|
||||
customOriginalHeroPath,
|
||||
});
|
||||
|
||||
await gamesSublevel.put(gameKey, updatedGame);
|
||||
await updateShopAssets(gameKey, title);
|
||||
|
||||
// Also update the shop assets for non-custom games
|
||||
const existingAssets = await gamesShopAssetsSublevel.get(gameKey);
|
||||
if (existingAssets) {
|
||||
const updatedAssets = {
|
||||
...existingAssets,
|
||||
title, // Update the title in shop assets as well
|
||||
};
|
||||
|
||||
await gamesShopAssetsSublevel.put(gameKey, updatedAssets);
|
||||
}
|
||||
await deleteOldAssetFiles(oldAssetPaths);
|
||||
|
||||
return updatedGame;
|
||||
};
|
||||
|
||||
@@ -93,14 +93,9 @@ const startGameDownload = async (
|
||||
|
||||
await Promise.all([
|
||||
createGame(updatedGame!).catch(() => {}),
|
||||
HydraApi.post(
|
||||
"/games/download",
|
||||
{
|
||||
objectId,
|
||||
shop,
|
||||
},
|
||||
{ needsAuth: false }
|
||||
).catch(() => {}),
|
||||
HydraApi.post(`/games/${shop}/${objectId}/download`, null, {
|
||||
needsAuth: false,
|
||||
}).catch(() => {}),
|
||||
]);
|
||||
|
||||
return { ok: true };
|
||||
|
||||
@@ -45,10 +45,8 @@ export const getGameAchievementData = async (
|
||||
.then((language) => language || "en");
|
||||
|
||||
return HydraApi.get<SteamAchievement[]>(
|
||||
"/games/achievements",
|
||||
`/games/${shop}/${objectId}/achievements`,
|
||||
{
|
||||
shop,
|
||||
objectId,
|
||||
language,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -10,7 +10,7 @@ export const requestSteam250 = async (path: string) => {
|
||||
const { window } = new JSDOM(response.data);
|
||||
const { document } = window;
|
||||
|
||||
return Array.from(document.querySelectorAll(".appline .title a"))
|
||||
return Array.from(document.querySelectorAll("a[data-title]"))
|
||||
.map(($title) => {
|
||||
const steamGameUrl = ($title as HTMLAnchorElement).href;
|
||||
if (!steamGameUrl) return null;
|
||||
|
||||
@@ -16,9 +16,7 @@ export const friendGameSessionEvent = async (payload: FriendGameSession) => {
|
||||
|
||||
const [friend, gameStats] = await Promise.all([
|
||||
HydraApi.get<UserProfile>(`/users/${payload.friendId}`),
|
||||
HydraApi.get<GameStats>(
|
||||
`/games/stats?objectId=${payload.objectId}&shop=steam`
|
||||
),
|
||||
HydraApi.get<GameStats>(`/games/steam/${payload.objectId}/stats`),
|
||||
]).catch(() => [null, null]);
|
||||
|
||||
if (friend && gameStats) {
|
||||
|
||||
@@ -152,40 +152,28 @@ contextBridge.exposeInMainWorld("electron", {
|
||||
deleteTempFile: (filePath: string) =>
|
||||
ipcRenderer.invoke("deleteTempFile", filePath),
|
||||
cleanupUnusedAssets: () => ipcRenderer.invoke("cleanupUnusedAssets"),
|
||||
updateCustomGame: (
|
||||
shop: GameShop,
|
||||
objectId: string,
|
||||
title: string,
|
||||
iconUrl?: string,
|
||||
logoImageUrl?: string,
|
||||
libraryHeroImageUrl?: string
|
||||
) =>
|
||||
ipcRenderer.invoke(
|
||||
"updateCustomGame",
|
||||
shop,
|
||||
objectId,
|
||||
title,
|
||||
iconUrl,
|
||||
logoImageUrl,
|
||||
libraryHeroImageUrl
|
||||
),
|
||||
updateGameCustomAssets: (
|
||||
shop: GameShop,
|
||||
objectId: string,
|
||||
title: string,
|
||||
customIconUrl?: string | null,
|
||||
customLogoImageUrl?: string | null,
|
||||
customHeroImageUrl?: string | null
|
||||
) =>
|
||||
ipcRenderer.invoke(
|
||||
"updateGameCustomAssets",
|
||||
shop,
|
||||
objectId,
|
||||
title,
|
||||
customIconUrl,
|
||||
customLogoImageUrl,
|
||||
customHeroImageUrl
|
||||
),
|
||||
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,
|
||||
|
||||
38
src/renderer/src/declaration.d.ts
vendored
38
src/renderer/src/declaration.d.ts
vendored
@@ -119,14 +119,17 @@ declare global {
|
||||
logoImageUrl?: string,
|
||||
libraryHeroImageUrl?: string
|
||||
) => Promise<Game>;
|
||||
updateCustomGame: (
|
||||
shop: GameShop,
|
||||
objectId: string,
|
||||
title: string,
|
||||
iconUrl?: string,
|
||||
logoImageUrl?: string,
|
||||
libraryHeroImageUrl?: string
|
||||
) => Promise<Game>;
|
||||
updateCustomGame: (params: {
|
||||
shop: GameShop;
|
||||
objectId: string;
|
||||
title: string;
|
||||
iconUrl?: string;
|
||||
logoImageUrl?: string;
|
||||
libraryHeroImageUrl?: string;
|
||||
originalIconPath?: string;
|
||||
originalLogoPath?: string;
|
||||
originalHeroPath?: string;
|
||||
}) => Promise<Game>;
|
||||
copyCustomGameAsset: (
|
||||
sourcePath: string,
|
||||
assetType: "icon" | "logo" | "hero"
|
||||
@@ -135,14 +138,17 @@ declare global {
|
||||
deletedCount: number;
|
||||
errors: string[];
|
||||
}>;
|
||||
updateGameCustomAssets: (
|
||||
shop: GameShop,
|
||||
objectId: string,
|
||||
title: string,
|
||||
customIconUrl?: string | null,
|
||||
customLogoImageUrl?: string | null,
|
||||
customHeroImageUrl?: string | null
|
||||
) => Promise<Game>;
|
||||
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<Game>;
|
||||
createGameShortcut: (
|
||||
shop: GameShop,
|
||||
objectId: string,
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
@use "../../../scss/globals.scss";
|
||||
|
||||
.description-header {
|
||||
width: calc(100% - calc(globals.$spacing-unit * 2));
|
||||
margin: calc(globals.$spacing-unit * 1) auto;
|
||||
width: 100%;
|
||||
padding: calc(globals.$spacing-unit * 1.5);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -10,8 +9,9 @@
|
||||
background-color: globals.$background-color;
|
||||
height: 72px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.03);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
margin-bottom: calc(globals.$spacing-unit * 1);
|
||||
|
||||
&__info {
|
||||
display: flex;
|
||||
|
||||
@@ -7,6 +7,15 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
max-height: 80vh;
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
max-height: 70vh;
|
||||
}
|
||||
|
||||
@media (min-width: 1280px) {
|
||||
max-height: 60vh;
|
||||
}
|
||||
}
|
||||
|
||||
&__viewport {
|
||||
@@ -16,8 +25,19 @@
|
||||
overflow: hidden;
|
||||
border-radius: 8px;
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
width: 80%;
|
||||
max-height: 400px;
|
||||
}
|
||||
|
||||
@media (min-width: 1280px) {
|
||||
width: 60%;
|
||||
max-height: 500px;
|
||||
}
|
||||
|
||||
@media (min-width: 1536px) {
|
||||
width: 50%;
|
||||
max-height: 600px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,10 +72,18 @@
|
||||
overflow-y: hidden;
|
||||
gap: calc(globals.$spacing-unit / 2);
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
@media (min-width: 1280px) {
|
||||
width: 60%;
|
||||
}
|
||||
|
||||
@media (min-width: 1536px) {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
width: 20%;
|
||||
}
|
||||
@@ -79,6 +107,19 @@
|
||||
border: solid 1px globals.$border-color;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
aspect-ratio: 16/9;
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
width: 15%;
|
||||
}
|
||||
|
||||
@media (min-width: 1280px) {
|
||||
width: 12%;
|
||||
}
|
||||
|
||||
@media (min-width: 1536px) {
|
||||
width: 10%;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
|
||||
@@ -43,6 +43,29 @@ export function GameDetailsContent() {
|
||||
const $images = Array.from(document.querySelectorAll("img"));
|
||||
$images.forEach(($image) => {
|
||||
$image.loading = "lazy";
|
||||
// Remove any inline width/height styles that might cause overflow
|
||||
$image.removeAttribute("width");
|
||||
$image.removeAttribute("height");
|
||||
$image.removeAttribute("style");
|
||||
// Set max-width to prevent overflow
|
||||
$image.style.maxWidth = "100%";
|
||||
$image.style.width = "auto";
|
||||
$image.style.height = "auto";
|
||||
$image.style.boxSizing = "border-box";
|
||||
});
|
||||
|
||||
// Handle videos the same way
|
||||
const $videos = Array.from(document.querySelectorAll("video"));
|
||||
$videos.forEach(($video) => {
|
||||
// Remove any inline width/height styles that might cause overflow
|
||||
$video.removeAttribute("width");
|
||||
$video.removeAttribute("height");
|
||||
$video.removeAttribute("style");
|
||||
// Set max-width to prevent overflow
|
||||
$video.style.maxWidth = "100%";
|
||||
$video.style.width = "auto";
|
||||
$video.style.height = "auto";
|
||||
$video.style.boxSizing = "border-box";
|
||||
});
|
||||
|
||||
return document.body.outerHTML;
|
||||
@@ -168,14 +191,16 @@ export function GameDetailsContent() {
|
||||
{renderGameLogo()}
|
||||
|
||||
<div className="game-details__hero-buttons game-details__hero-buttons--right">
|
||||
<button
|
||||
type="button"
|
||||
className="game-details__edit-custom-game-button"
|
||||
onClick={handleEditGameClick}
|
||||
title={t("edit_game_modal_button")}
|
||||
>
|
||||
<PencilIcon size={16} />
|
||||
</button>
|
||||
{game && (
|
||||
<button
|
||||
type="button"
|
||||
className="game-details__edit-custom-game-button"
|
||||
onClick={handleEditGameClick}
|
||||
title={t("edit_game_modal_button")}
|
||||
>
|
||||
<PencilIcon size={16} />
|
||||
</button>
|
||||
)}
|
||||
|
||||
{game?.shop !== "custom" && (
|
||||
<button
|
||||
@@ -217,13 +242,15 @@ export function GameDetailsContent() {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<EditGameModal
|
||||
visible={showEditGameModal}
|
||||
onClose={() => setShowEditGameModal(false)}
|
||||
game={game}
|
||||
shopDetails={shopDetails}
|
||||
onGameUpdated={handleGameUpdated}
|
||||
/>
|
||||
{game && (
|
||||
<EditGameModal
|
||||
visible={showEditGameModal}
|
||||
onClose={() => setShowEditGameModal(false)}
|
||||
game={game}
|
||||
shopDetails={shopDetails}
|
||||
onGameUpdated={handleGameUpdated}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -182,44 +182,49 @@ $hero-height: 300px;
|
||||
globals.$background-color 50%,
|
||||
globals.$dark-background-color 100%
|
||||
);
|
||||
padding: calc(globals.$spacing-unit * 1.5);
|
||||
gap: calc(globals.$spacing-unit * 1.5);
|
||||
}
|
||||
|
||||
&__description-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 100%;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
&__description {
|
||||
user-select: text;
|
||||
line-height: 22px;
|
||||
font-size: globals.$body-font-size;
|
||||
padding: calc(globals.$spacing-unit * 2) calc(globals.$spacing-unit * 1.5);
|
||||
width: 100%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
|
||||
@media (min-width: 768px) {
|
||||
padding: calc(globals.$spacing-unit * 2.5) calc(globals.$spacing-unit * 2);
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
padding: calc(globals.$spacing-unit * 3) calc(globals.$spacing-unit * 2);
|
||||
}
|
||||
overflow-x: auto;
|
||||
min-height: auto;
|
||||
|
||||
@media (min-width: 1280px) {
|
||||
width: 60%;
|
||||
}
|
||||
|
||||
img {
|
||||
@media (min-width: 1536px) {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
img,
|
||||
video {
|
||||
border-radius: 5px;
|
||||
margin-top: globals.$spacing-unit;
|
||||
margin-bottom: calc(globals.$spacing-unit * 3);
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
object-fit: cover;
|
||||
display: block !important;
|
||||
max-width: 100% !important;
|
||||
width: auto !important;
|
||||
height: auto !important;
|
||||
object-fit: contain !important;
|
||||
box-sizing: border-box !important;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
a {
|
||||
@@ -247,12 +252,17 @@ $hero-height: 300px;
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
padding: calc(globals.$spacing-unit * 3) calc(globals.$spacing-unit * 2);
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
@media (min-width: 1280px) {
|
||||
width: 60%;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
@media (min-width: 1536px) {
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
&__randomizer-button {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
import { useState, useEffect, useCallback, useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ImageIcon, XIcon } from "@primer/octicons-react";
|
||||
|
||||
import { Modal, TextField, Button } from "@renderer/components";
|
||||
import { useToast } from "@renderer/hooks";
|
||||
import { generateRandomGradient } from "@renderer/helpers";
|
||||
import type { LibraryGame, Game, ShopDetailsWithAssets } from "@types";
|
||||
|
||||
import "./edit-game-modal.scss";
|
||||
@@ -29,16 +30,34 @@ export function EditGameModal({
|
||||
const { showSuccessToast, showErrorToast } = useToast();
|
||||
|
||||
const [gameName, setGameName] = useState("");
|
||||
const [iconPath, setIconPath] = useState("");
|
||||
const [logoPath, setLogoPath] = useState("");
|
||||
const [heroPath, setHeroPath] = useState("");
|
||||
const [assetPaths, setAssetPaths] = useState({
|
||||
icon: "",
|
||||
logo: "",
|
||||
hero: "",
|
||||
});
|
||||
const [assetDisplayPaths, setAssetDisplayPaths] = useState({
|
||||
icon: "",
|
||||
logo: "",
|
||||
hero: "",
|
||||
});
|
||||
const [originalAssetPaths, setOriginalAssetPaths] = useState({
|
||||
icon: "",
|
||||
logo: "",
|
||||
hero: "",
|
||||
});
|
||||
const [removedAssets, setRemovedAssets] = useState({
|
||||
icon: false,
|
||||
logo: false,
|
||||
hero: false,
|
||||
});
|
||||
const [defaultUrls, setDefaultUrls] = useState({
|
||||
icon: null as string | null,
|
||||
logo: null as string | null,
|
||||
hero: null as string | null,
|
||||
});
|
||||
const [isUpdating, setIsUpdating] = useState(false);
|
||||
const [selectedAssetType, setSelectedAssetType] = useState<AssetType>("icon");
|
||||
|
||||
const [defaultIconUrl, setDefaultIconUrl] = useState<string | null>(null);
|
||||
const [defaultLogoUrl, setDefaultLogoUrl] = useState<string | null>(null);
|
||||
const [defaultHeroUrl, setDefaultHeroUrl] = useState<string | null>(null);
|
||||
|
||||
const isCustomGame = (game: LibraryGame | Game): boolean => {
|
||||
return game.shop === "custom";
|
||||
};
|
||||
@@ -48,26 +67,58 @@ export function EditGameModal({
|
||||
};
|
||||
|
||||
const setCustomGameAssets = useCallback((game: LibraryGame | Game) => {
|
||||
setIconPath(extractLocalPath(game.iconUrl));
|
||||
setLogoPath(extractLocalPath(game.logoImageUrl));
|
||||
setHeroPath(extractLocalPath(game.libraryHeroImageUrl));
|
||||
setAssetPaths({
|
||||
icon: extractLocalPath(game.iconUrl),
|
||||
logo: extractLocalPath(game.logoImageUrl),
|
||||
hero: extractLocalPath(game.libraryHeroImageUrl),
|
||||
});
|
||||
setAssetDisplayPaths({
|
||||
icon: extractLocalPath(game.iconUrl),
|
||||
logo: extractLocalPath(game.logoImageUrl),
|
||||
hero: extractLocalPath(game.libraryHeroImageUrl),
|
||||
});
|
||||
setOriginalAssetPaths({
|
||||
icon: (game as any).originalIconPath || extractLocalPath(game.iconUrl),
|
||||
logo:
|
||||
(game as any).originalLogoPath || extractLocalPath(game.logoImageUrl),
|
||||
hero:
|
||||
(game as any).originalHeroPath ||
|
||||
extractLocalPath(game.libraryHeroImageUrl),
|
||||
});
|
||||
}, []);
|
||||
|
||||
const setNonCustomGameAssets = useCallback(
|
||||
(game: LibraryGame) => {
|
||||
setIconPath(extractLocalPath(game.customIconUrl));
|
||||
setLogoPath(extractLocalPath(game.customLogoImageUrl));
|
||||
setHeroPath(extractLocalPath(game.customHeroImageUrl));
|
||||
setAssetPaths({
|
||||
icon: extractLocalPath(game.customIconUrl),
|
||||
logo: extractLocalPath(game.customLogoImageUrl),
|
||||
hero: extractLocalPath(game.customHeroImageUrl),
|
||||
});
|
||||
setAssetDisplayPaths({
|
||||
icon: extractLocalPath(game.customIconUrl),
|
||||
logo: extractLocalPath(game.customLogoImageUrl),
|
||||
hero: extractLocalPath(game.customHeroImageUrl),
|
||||
});
|
||||
setOriginalAssetPaths({
|
||||
icon:
|
||||
(game as any).customOriginalIconPath ||
|
||||
extractLocalPath(game.customIconUrl),
|
||||
logo:
|
||||
(game as any).customOriginalLogoPath ||
|
||||
extractLocalPath(game.customLogoImageUrl),
|
||||
hero:
|
||||
(game as any).customOriginalHeroPath ||
|
||||
extractLocalPath(game.customHeroImageUrl),
|
||||
});
|
||||
|
||||
setDefaultIconUrl(shopDetails?.assets?.iconUrl || game.iconUrl || null);
|
||||
setDefaultLogoUrl(
|
||||
shopDetails?.assets?.logoImageUrl || game.logoImageUrl || null
|
||||
);
|
||||
setDefaultHeroUrl(
|
||||
shopDetails?.assets?.libraryHeroImageUrl ||
|
||||
setDefaultUrls({
|
||||
icon: shopDetails?.assets?.iconUrl || game.iconUrl || null,
|
||||
logo: shopDetails?.assets?.logoImageUrl || game.logoImageUrl || null,
|
||||
hero:
|
||||
shopDetails?.assets?.libraryHeroImageUrl ||
|
||||
game.libraryHeroImageUrl ||
|
||||
null
|
||||
);
|
||||
null,
|
||||
});
|
||||
},
|
||||
[shopDetails]
|
||||
);
|
||||
@@ -93,38 +144,38 @@ export function EditGameModal({
|
||||
};
|
||||
|
||||
const getAssetPath = (assetType: AssetType): string => {
|
||||
switch (assetType) {
|
||||
case "icon":
|
||||
return iconPath;
|
||||
case "logo":
|
||||
return logoPath;
|
||||
case "hero":
|
||||
return heroPath;
|
||||
}
|
||||
return assetPaths[assetType];
|
||||
};
|
||||
|
||||
const getAssetDisplayPath = (assetType: AssetType): string => {
|
||||
// Use original path if available, otherwise fall back to display path
|
||||
return originalAssetPaths[assetType] || assetDisplayPaths[assetType];
|
||||
};
|
||||
|
||||
const setAssetPath = (assetType: AssetType, path: string): void => {
|
||||
switch (assetType) {
|
||||
case "icon":
|
||||
setIconPath(path);
|
||||
break;
|
||||
case "logo":
|
||||
setLogoPath(path);
|
||||
break;
|
||||
case "hero":
|
||||
setHeroPath(path);
|
||||
break;
|
||||
}
|
||||
setAssetPaths((prev) => ({ ...prev, [assetType]: path }));
|
||||
};
|
||||
|
||||
const setAssetDisplayPath = (assetType: AssetType, path: string): void => {
|
||||
setAssetDisplayPaths((prev) => ({ ...prev, [assetType]: path }));
|
||||
};
|
||||
|
||||
const getDefaultUrl = (assetType: AssetType): string | null => {
|
||||
return defaultUrls[assetType];
|
||||
};
|
||||
|
||||
const getOriginalAssetUrl = (assetType: AssetType): string | null => {
|
||||
if (!game || !isCustomGame(game)) return null;
|
||||
|
||||
switch (assetType) {
|
||||
case "icon":
|
||||
return defaultIconUrl;
|
||||
return game.iconUrl;
|
||||
case "logo":
|
||||
return defaultLogoUrl;
|
||||
return game.logoImageUrl;
|
||||
case "hero":
|
||||
return defaultHeroUrl;
|
||||
return game.libraryHeroImageUrl;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -140,23 +191,68 @@ export function EditGameModal({
|
||||
});
|
||||
|
||||
if (filePaths && filePaths.length > 0) {
|
||||
const originalPath = filePaths[0];
|
||||
try {
|
||||
const copiedAssetUrl = await window.electron.copyCustomGameAsset(
|
||||
filePaths[0],
|
||||
originalPath,
|
||||
assetType
|
||||
);
|
||||
setAssetPath(assetType, copiedAssetUrl.replace("local:", ""));
|
||||
setAssetDisplayPath(assetType, originalPath);
|
||||
// Store the original path for display purposes
|
||||
setOriginalAssetPaths((prev) => ({
|
||||
...prev,
|
||||
[assetType]: originalPath,
|
||||
}));
|
||||
// Clear the removed flag when a new asset is selected
|
||||
setRemovedAssets((prev) => ({ ...prev, [assetType]: false }));
|
||||
} catch (error) {
|
||||
console.error(`Failed to copy ${assetType} asset:`, error);
|
||||
setAssetPath(assetType, filePaths[0]);
|
||||
setAssetPath(assetType, originalPath);
|
||||
setAssetDisplayPath(assetType, originalPath);
|
||||
setOriginalAssetPaths((prev) => ({
|
||||
...prev,
|
||||
[assetType]: originalPath,
|
||||
}));
|
||||
// Clear the removed flag when a new asset is selected
|
||||
setRemovedAssets((prev) => ({ ...prev, [assetType]: false }));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleRestoreDefault = (assetType: AssetType) => {
|
||||
setAssetPath(assetType, "");
|
||||
if (game && isCustomGame(game)) {
|
||||
// For custom games, mark asset as removed and clear paths
|
||||
setRemovedAssets((prev) => ({ ...prev, [assetType]: true }));
|
||||
setAssetPath(assetType, "");
|
||||
setAssetDisplayPath(assetType, "");
|
||||
setOriginalAssetPaths((prev) => ({ ...prev, [assetType]: "" }));
|
||||
} else {
|
||||
// For non-custom games, clear custom assets (restore to shop defaults)
|
||||
setAssetPath(assetType, "");
|
||||
setAssetDisplayPath(assetType, "");
|
||||
setOriginalAssetPaths((prev) => ({ ...prev, [assetType]: "" }));
|
||||
}
|
||||
};
|
||||
|
||||
const getOriginalTitle = (): string => {
|
||||
if (!game) return "";
|
||||
|
||||
// For non-custom games, the original title is from shopDetails assets
|
||||
return shopDetails?.assets?.title || game.title || "";
|
||||
};
|
||||
|
||||
const handleRestoreDefaultTitle = () => {
|
||||
const originalTitle = getOriginalTitle();
|
||||
setGameName(originalTitle);
|
||||
};
|
||||
|
||||
const isTitleChanged = useMemo((): boolean => {
|
||||
if (!game || isCustomGame(game)) return false;
|
||||
const originalTitle = getOriginalTitle();
|
||||
return gameName.trim() !== originalTitle.trim();
|
||||
}, [game, gameName, shopDetails]);
|
||||
|
||||
const [dragOverTarget, setDragOverTarget] = useState<string | null>(null);
|
||||
|
||||
const handleDragOver = (e: React.DragEvent) => {
|
||||
@@ -232,6 +328,7 @@ export function EditGameModal({
|
||||
|
||||
const assetPath = copiedAssetUrl.replace("local:", "");
|
||||
setAssetPath(assetType, assetPath);
|
||||
setAssetDisplayPath(assetType, filePath);
|
||||
|
||||
showSuccessToast(
|
||||
`${assetType.charAt(0).toUpperCase() + assetType.slice(1)} updated successfully!`
|
||||
@@ -267,11 +364,38 @@ export function EditGameModal({
|
||||
|
||||
// Helper function to prepare custom game assets
|
||||
const prepareCustomGameAssets = (game: LibraryGame | Game) => {
|
||||
const iconUrl = iconPath ? `local:${iconPath}` : game.iconUrl;
|
||||
const logoImageUrl = logoPath ? `local:${logoPath}` : game.logoImageUrl;
|
||||
const libraryHeroImageUrl = heroPath
|
||||
? `local:${heroPath}`
|
||||
: game.libraryHeroImageUrl;
|
||||
// For custom games, check if asset was explicitly removed
|
||||
let iconUrl;
|
||||
if (removedAssets.icon) {
|
||||
iconUrl = null;
|
||||
} else if (assetPaths.icon) {
|
||||
iconUrl = `local:${assetPaths.icon}`;
|
||||
} else {
|
||||
iconUrl = game.iconUrl;
|
||||
}
|
||||
|
||||
let logoImageUrl;
|
||||
if (removedAssets.logo) {
|
||||
logoImageUrl = null;
|
||||
} else if (assetPaths.logo) {
|
||||
logoImageUrl = `local:${assetPaths.logo}`;
|
||||
} else {
|
||||
logoImageUrl = game.logoImageUrl;
|
||||
}
|
||||
|
||||
// For hero image, if removed, restore to the original gradient or keep the original
|
||||
let libraryHeroImageUrl;
|
||||
if (removedAssets.hero) {
|
||||
// If the original hero was a gradient (data URL), keep it, otherwise generate a new one
|
||||
const originalHero = game.libraryHeroImageUrl;
|
||||
libraryHeroImageUrl = originalHero?.startsWith("data:image/svg+xml")
|
||||
? originalHero
|
||||
: generateRandomGradient();
|
||||
} else {
|
||||
libraryHeroImageUrl = assetPaths.hero
|
||||
? `local:${assetPaths.hero}`
|
||||
: game.libraryHeroImageUrl;
|
||||
}
|
||||
|
||||
return { iconUrl, logoImageUrl, libraryHeroImageUrl };
|
||||
};
|
||||
@@ -279,9 +403,9 @@ export function EditGameModal({
|
||||
// Helper function to prepare non-custom game assets
|
||||
const prepareNonCustomGameAssets = () => {
|
||||
return {
|
||||
customIconUrl: iconPath ? `local:${iconPath}` : null,
|
||||
customLogoImageUrl: logoPath ? `local:${logoPath}` : null,
|
||||
customHeroImageUrl: heroPath ? `local:${heroPath}` : null,
|
||||
customIconUrl: assetPaths.icon ? `local:${assetPaths.icon}` : null,
|
||||
customLogoImageUrl: assetPaths.logo ? `local:${assetPaths.logo}` : null,
|
||||
customHeroImageUrl: assetPaths.hero ? `local:${assetPaths.hero}` : null,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -290,14 +414,17 @@ export function EditGameModal({
|
||||
const { iconUrl, logoImageUrl, libraryHeroImageUrl } =
|
||||
prepareCustomGameAssets(game);
|
||||
|
||||
return window.electron.updateCustomGame(
|
||||
game.shop,
|
||||
game.objectId,
|
||||
gameName.trim(),
|
||||
iconUrl || undefined,
|
||||
logoImageUrl || undefined,
|
||||
libraryHeroImageUrl || undefined
|
||||
);
|
||||
return window.electron.updateCustomGame({
|
||||
shop: game.shop,
|
||||
objectId: game.objectId,
|
||||
title: gameName.trim(),
|
||||
iconUrl: iconUrl || undefined,
|
||||
logoImageUrl: logoImageUrl || undefined,
|
||||
libraryHeroImageUrl: libraryHeroImageUrl || undefined,
|
||||
originalIconPath: originalAssetPaths.icon || undefined,
|
||||
originalLogoPath: originalAssetPaths.logo || undefined,
|
||||
originalHeroPath: originalAssetPaths.hero || undefined,
|
||||
});
|
||||
};
|
||||
|
||||
// Helper function to update non-custom game
|
||||
@@ -305,14 +432,17 @@ export function EditGameModal({
|
||||
const { customIconUrl, customLogoImageUrl, customHeroImageUrl } =
|
||||
prepareNonCustomGameAssets();
|
||||
|
||||
return window.electron.updateGameCustomAssets(
|
||||
game.shop,
|
||||
game.objectId,
|
||||
gameName.trim(),
|
||||
return window.electron.updateGameCustomAssets({
|
||||
shop: game.shop,
|
||||
objectId: game.objectId,
|
||||
title: gameName.trim(),
|
||||
customIconUrl,
|
||||
customLogoImageUrl,
|
||||
customHeroImageUrl
|
||||
);
|
||||
customHeroImageUrl,
|
||||
customOriginalIconPath: originalAssetPaths.icon || undefined,
|
||||
customOriginalLogoPath: originalAssetPaths.logo || undefined,
|
||||
customOriginalHeroPath: originalAssetPaths.hero || undefined,
|
||||
});
|
||||
};
|
||||
|
||||
const handleUpdateGame = async () => {
|
||||
@@ -343,19 +473,31 @@ export function EditGameModal({
|
||||
};
|
||||
|
||||
// Helper function to reset form to initial state
|
||||
const resetFormToInitialState = (game: LibraryGame | Game) => {
|
||||
setGameName(game.title || "");
|
||||
const resetFormToInitialState = useCallback(
|
||||
(game: LibraryGame | Game) => {
|
||||
setGameName(game.title || "");
|
||||
|
||||
if (isCustomGame(game)) {
|
||||
setCustomGameAssets(game);
|
||||
// Clear default URLs for custom games
|
||||
setDefaultIconUrl(null);
|
||||
setDefaultLogoUrl(null);
|
||||
setDefaultHeroUrl(null);
|
||||
} else {
|
||||
setNonCustomGameAssets(game as LibraryGame);
|
||||
}
|
||||
};
|
||||
// Reset removed assets state
|
||||
setRemovedAssets({
|
||||
icon: false,
|
||||
logo: false,
|
||||
hero: false,
|
||||
});
|
||||
|
||||
if (isCustomGame(game)) {
|
||||
setCustomGameAssets(game);
|
||||
// Clear default URLs for custom games
|
||||
setDefaultUrls({
|
||||
icon: null,
|
||||
logo: null,
|
||||
hero: null,
|
||||
});
|
||||
} else {
|
||||
setNonCustomGameAssets(game as LibraryGame);
|
||||
}
|
||||
},
|
||||
[setCustomGameAssets, setNonCustomGameAssets]
|
||||
);
|
||||
|
||||
const handleClose = () => {
|
||||
if (!isUpdating && game) {
|
||||
@@ -378,6 +520,7 @@ export function EditGameModal({
|
||||
|
||||
const renderImageSection = (assetType: AssetType) => {
|
||||
const assetPath = getAssetPath(assetType);
|
||||
const assetDisplayPath = getAssetDisplayPath(assetType);
|
||||
const defaultUrl = getDefaultUrl(assetType);
|
||||
const hasImage = assetPath || (game && !isCustomGame(game) && defaultUrl);
|
||||
const isDragOver = dragOverTarget === assetType;
|
||||
@@ -390,7 +533,7 @@ export function EditGameModal({
|
||||
<div className="edit-game-modal__image-section">
|
||||
<TextField
|
||||
placeholder={t(`edit_game_modal_select_${assetType}`)}
|
||||
value={assetPath}
|
||||
value={assetDisplayPath}
|
||||
readOnly
|
||||
theme="dark"
|
||||
rightContent={
|
||||
@@ -404,17 +547,19 @@ export function EditGameModal({
|
||||
<ImageIcon />
|
||||
{t("edit_game_modal_browse")}
|
||||
</Button>
|
||||
{game && !isCustomGame(game) && assetPath && (
|
||||
<Button
|
||||
type="button"
|
||||
theme="outline"
|
||||
onClick={() => handleRestoreDefault(assetType)}
|
||||
disabled={isUpdating}
|
||||
title={`Remove ${assetType}`}
|
||||
>
|
||||
<XIcon />
|
||||
</Button>
|
||||
)}
|
||||
{game &&
|
||||
(assetPath ||
|
||||
(isCustomGame(game) && getOriginalAssetUrl(assetType))) && (
|
||||
<Button
|
||||
type="button"
|
||||
theme="outline"
|
||||
onClick={() => handleRestoreDefault(assetType)}
|
||||
disabled={isUpdating}
|
||||
title={`Remove ${assetType}`}
|
||||
>
|
||||
<XIcon />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
@@ -442,7 +587,7 @@ export function EditGameModal({
|
||||
/>
|
||||
{isDragOver && (
|
||||
<div className="edit-game-modal__drop-overlay">
|
||||
<span>Drop to replace {assetType}</span>
|
||||
<span>{t(`edit_game_modal_drop_to_replace_${assetType}`)}</span>
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
@@ -465,7 +610,7 @@ export function EditGameModal({
|
||||
>
|
||||
<div className="edit-game-modal__drop-zone-content">
|
||||
<ImageIcon />
|
||||
<span>Drop {assetType} image here</span>
|
||||
<span>{t(`edit_game_modal_drop_${assetType}_image_here`)}</span>
|
||||
</div>
|
||||
</button>
|
||||
)}
|
||||
@@ -489,6 +634,19 @@ export function EditGameModal({
|
||||
onChange={handleGameNameChange}
|
||||
theme="dark"
|
||||
disabled={isUpdating}
|
||||
rightContent={
|
||||
isTitleChanged && (
|
||||
<Button
|
||||
type="button"
|
||||
theme="outline"
|
||||
onClick={handleRestoreDefaultTitle}
|
||||
disabled={isUpdating}
|
||||
title="Restore default title"
|
||||
>
|
||||
<XIcon />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
<div className="edit-game-modal__asset-selector">
|
||||
|
||||
@@ -2,12 +2,33 @@
|
||||
|
||||
.repacks-modal {
|
||||
&__filter-container {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
&__filter-top {
|
||||
margin-bottom: calc(globals.$spacing-unit * 2);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: calc(globals.$spacing-unit * 1);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&__filter-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: calc(globals.$spacing-unit * 1);
|
||||
font-size: globals.$small-font-size;
|
||||
font-weight: 600;
|
||||
color: var(--color-text-secondary);
|
||||
padding: calc(globals.$spacing-unit * 1) calc(globals.$spacing-unit * 1.5);
|
||||
border-radius: 6px;
|
||||
transition: background-color 0.2s ease;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&__repacks {
|
||||
display: flex;
|
||||
gap: globals.$spacing-unit;
|
||||
gap: calc(globals.$spacing-unit * 2);
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
@@ -16,7 +37,7 @@
|
||||
text-align: left;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: globals.$spacing-unit;
|
||||
gap: calc(globals.$spacing-unit * 1);
|
||||
color: globals.$body-color;
|
||||
padding: calc(globals.$spacing-unit * 2);
|
||||
}
|
||||
@@ -29,4 +50,106 @@
|
||||
&__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: calc(globals.$spacing-unit * 2);
|
||||
margin-top: calc(globals.$spacing-unit * 1);
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
transition:
|
||||
max-height 0.3s ease,
|
||||
padding 0.3s ease;
|
||||
|
||||
&--open {
|
||||
max-height: 280px;
|
||||
}
|
||||
}
|
||||
|
||||
&__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(3, 1fr);
|
||||
gap: calc(globals.$spacing-unit * 1);
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
align-items: start;
|
||||
padding: calc(globals.$spacing-unit * 0.5) calc(globals.$spacing-unit * 0.5)
|
||||
calc(globals.$spacing-unit * 0.5) 0;
|
||||
}
|
||||
|
||||
&__source-item {
|
||||
padding: calc(globals.$spacing-unit * 0.75) calc(globals.$spacing-unit * 1);
|
||||
background: var(--color-surface, rgba(0, 0, 0, 0.03));
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: calc(globals.$spacing-unit * 5);
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
transition: border-color 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
&__source-item :global(.checkbox-field) {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
&__source-item :global(.checkbox-field__label) {
|
||||
white-space: normal;
|
||||
overflow: visible;
|
||||
text-overflow: unset;
|
||||
display: block;
|
||||
font-size: 0.85rem;
|
||||
width: 100%;
|
||||
word-break: break-word;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
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,
|
||||
@@ -7,7 +13,10 @@ 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";
|
||||
@@ -36,6 +45,11 @@ export function RepacksModal({
|
||||
const [filteredRepacks, setFilteredRepacks] = useState<GameRepack[]>([]);
|
||||
const [repack, setRepack] = useState<GameRepack | null>(null);
|
||||
const [showSelectFolderModal, setShowSelectFolderModal] = useState(false);
|
||||
const [downloadSources, setDownloadSources] = useState<DownloadSource[]>([]);
|
||||
const [selectedFingerprints, setSelectedFingerprints] = useState<string[]>(
|
||||
[]
|
||||
);
|
||||
const [filterTerm, setFilterTerm] = useState("");
|
||||
|
||||
const [hashesInDebrid, setHashesInDebrid] = useState<Record<string, boolean>>(
|
||||
{}
|
||||
@@ -46,6 +60,7 @@ export function RepacksModal({
|
||||
const { t } = useTranslation("game_details");
|
||||
|
||||
const { formatDate } = useDate();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const getHashFromMagnet = (magnet: string) => {
|
||||
if (!magnet || typeof magnet !== "string") {
|
||||
@@ -90,8 +105,37 @@ export function RepacksModal({
|
||||
}, [repacks, hashesInDebrid]);
|
||||
|
||||
useEffect(() => {
|
||||
setFilteredRepacks(sortedRepacks);
|
||||
}, [sortedRepacks, visible, game]);
|
||||
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]);
|
||||
|
||||
const handleRepackClick = (repack: GameRepack) => {
|
||||
setRepack(repack);
|
||||
@@ -99,17 +143,14 @@ export function RepacksModal({
|
||||
};
|
||||
|
||||
const handleFilter: React.ChangeEventHandler<HTMLInputElement> = (event) => {
|
||||
const term = event.target.value.toLocaleLowerCase();
|
||||
setFilterTerm(event.target.value);
|
||||
};
|
||||
|
||||
setFilteredRepacks(
|
||||
sortedRepacks.filter((repack) => {
|
||||
const lowerCaseTitle = repack.title.toLowerCase();
|
||||
const lowerCaseRepacker = repack.repacker.toLowerCase();
|
||||
|
||||
return [lowerCaseTitle, lowerCaseRepacker].some((value) =>
|
||||
value.includes(term)
|
||||
);
|
||||
})
|
||||
const toggleFingerprint = (fingerprint: string) => {
|
||||
setSelectedFingerprints((prev) =>
|
||||
prev.includes(fingerprint)
|
||||
? prev.filter((f) => f !== fingerprint)
|
||||
: [...prev, fingerprint]
|
||||
);
|
||||
};
|
||||
|
||||
@@ -118,6 +159,8 @@ export function RepacksModal({
|
||||
return repack.uris.some((uri) => uri.includes(game.download!.uri));
|
||||
};
|
||||
|
||||
const [isFilterDrawerOpen, setIsFilterDrawerOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DownloadSettingsModal
|
||||
@@ -133,38 +176,103 @@ export function RepacksModal({
|
||||
description={t("repacks_modal_description")}
|
||||
onClose={onClose}
|
||||
>
|
||||
<div className="repacks-modal__filter-container">
|
||||
<TextField placeholder={t("filter")} onChange={handleFilter} />
|
||||
<div
|
||||
className={`repacks-modal__filter-container ${isFilterDrawerOpen ? "repacks-modal__filter-container--drawer-open" : ""}`}
|
||||
>
|
||||
<div className="repacks-modal__filter-top">
|
||||
<TextField placeholder={t("filter")} onChange={handleFilter} />
|
||||
{downloadSources.length > 0 && (
|
||||
<Button
|
||||
type="button"
|
||||
theme="outline"
|
||||
onClick={() => setIsFilterDrawerOpen(!isFilterDrawerOpen)}
|
||||
className="repacks-modal__filter-toggle"
|
||||
>
|
||||
{t("filter_by_source")}
|
||||
{isFilterDrawerOpen ? <ChevronUpIcon /> : <ChevronDownIcon />}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={`repacks-modal__download-sources ${isFilterDrawerOpen ? "repacks-modal__download-sources--open" : ""}`}
|
||||
>
|
||||
<div className="repacks-modal__source-grid">
|
||||
{downloadSources.map((source) => {
|
||||
const label = source.name || source.url;
|
||||
const truncatedLabel =
|
||||
label.length > 16 ? label.substring(0, 16) + "..." : label;
|
||||
return (
|
||||
<div
|
||||
key={source.fingerprint}
|
||||
className="repacks-modal__source-item"
|
||||
>
|
||||
<CheckboxField
|
||||
label={truncatedLabel}
|
||||
checked={selectedFingerprints.includes(
|
||||
source.fingerprint
|
||||
)}
|
||||
onChange={() => toggleFingerprint(source.fingerprint)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="repacks-modal__repacks">
|
||||
{filteredRepacks.map((repack) => {
|
||||
const isLastDownloadedOption = checkIfLastDownloadedOption(repack);
|
||||
{filteredRepacks.length === 0 ? (
|
||||
<div className="repacks-modal__no-results">
|
||||
<div className="repacks-modal__no-results-content">
|
||||
<div className="repacks-modal__no-results-text">
|
||||
{t("no_repacks_found")}
|
||||
</div>
|
||||
<div className="repacks-modal__no-results-button">
|
||||
<Button
|
||||
type="button"
|
||||
theme="primary"
|
||||
onClick={() => {
|
||||
onClose();
|
||||
navigate("/settings?tab=2");
|
||||
}}
|
||||
>
|
||||
<PlusCircleIcon />
|
||||
{t("add_download_source", { ns: "settings" })}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
filteredRepacks.map((repack) => {
|
||||
const isLastDownloadedOption =
|
||||
checkIfLastDownloadedOption(repack);
|
||||
|
||||
return (
|
||||
<Button
|
||||
key={repack.id}
|
||||
theme="dark"
|
||||
onClick={() => handleRepackClick(repack)}
|
||||
className="repacks-modal__repack-button"
|
||||
>
|
||||
<p className="repacks-modal__repack-title">{repack.title}</p>
|
||||
return (
|
||||
<Button
|
||||
key={repack.id}
|
||||
theme="dark"
|
||||
onClick={() => handleRepackClick(repack)}
|
||||
className="repacks-modal__repack-button"
|
||||
>
|
||||
<p className="repacks-modal__repack-title">{repack.title}</p>
|
||||
|
||||
{isLastDownloadedOption && (
|
||||
<Badge>{t("last_downloaded_option")}</Badge>
|
||||
)}
|
||||
{isLastDownloadedOption && (
|
||||
<Badge>{t("last_downloaded_option")}</Badge>
|
||||
)}
|
||||
|
||||
<p className="repacks-modal__repack-info">
|
||||
{repack.fileSize} - {repack.repacker} -{" "}
|
||||
{repack.uploadDate ? formatDate(repack.uploadDate) : ""}
|
||||
</p>
|
||||
<p className="repacks-modal__repack-info">
|
||||
{repack.fileSize} - {repack.repacker} -{" "}
|
||||
{repack.uploadDate ? formatDate(repack.uploadDate) : ""}
|
||||
</p>
|
||||
|
||||
{hashesInDebrid[getHashFromMagnet(repack.uris[0]) ?? ""] && (
|
||||
<DebridBadge />
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
{hashesInDebrid[getHashFromMagnet(repack.uris[0]) ?? ""] && (
|
||||
<DebridBadge />
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
</Modal>
|
||||
</>
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
@use "../../../scss/globals.scss";
|
||||
|
||||
.sidebar-section {
|
||||
background-color: globals.$background-color;
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
|
||||
&__button {
|
||||
padding: calc(globals.$spacing-unit * 2.5) calc(globals.$spacing-unit * 2);
|
||||
display: flex;
|
||||
@@ -15,7 +21,7 @@
|
||||
font-weight: bold;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
&:active {
|
||||
@@ -34,6 +40,7 @@
|
||||
&__content {
|
||||
overflow: hidden;
|
||||
transition: max-height 0.4s cubic-bezier(0, 1, 0, 1);
|
||||
background-color: globals.$dark-background-color;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ export function HowLongToBeatSection({
|
||||
return `${value} ${t(durationTranslation[unit])}`;
|
||||
};
|
||||
|
||||
if (!howLongToBeatData && !isLoading) return null;
|
||||
if (!howLongToBeatData || !isLoading) return null;
|
||||
|
||||
return (
|
||||
<SkeletonTheme baseColor="#1c1c1c" highlightColor="#444">
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
@use "../../../scss/globals.scss";
|
||||
|
||||
.content-sidebar {
|
||||
border-left: solid 1px globals.$border-color;
|
||||
background-color: globals.$dark-background-color;
|
||||
height: 100%;
|
||||
flex-shrink: 0;
|
||||
width: 280px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: calc(globals.$spacing-unit * 1.5);
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
width: 320px;
|
||||
|
||||
@@ -76,6 +76,15 @@
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&__title-flame-icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
&__title {
|
||||
|
||||
@@ -158,7 +158,7 @@ export default function Home() {
|
||||
<img
|
||||
src={flameIconAnimated}
|
||||
alt="Flame animation"
|
||||
className="home__flame-icon"
|
||||
className="home__title-flame-icon"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -5,7 +5,6 @@ import { useAppDispatch, useFormat } from "@renderer/hooks";
|
||||
import { setHeaderTitle } from "@renderer/features";
|
||||
import { TelescopeIcon, ChevronRightIcon } from "@primer/octicons-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { UserGame } from "@types";
|
||||
import { LockedProfile } from "./locked-profile";
|
||||
import { ReportProfile } from "../report-profile/report-profile";
|
||||
import { FriendsBox } from "./friends-box";
|
||||
@@ -17,8 +16,6 @@ import { useSectionCollapse } from "@renderer/hooks/use-section-collapse";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import {
|
||||
sectionVariants,
|
||||
gameCardVariants,
|
||||
gameGridVariants,
|
||||
chevronVariants,
|
||||
GAME_STATS_ANIMATION_DURATION_IN_MS,
|
||||
} from "./profile-animations";
|
||||
@@ -38,8 +35,6 @@ export function ProfileContent() {
|
||||
const [statsIndex, setStatsIndex] = useState(0);
|
||||
const [isAnimationRunning, setIsAnimationRunning] = useState(true);
|
||||
const [sortBy, setSortBy] = useState<SortOption>("playedRecently");
|
||||
const [prevLibraryGames, setPrevLibraryGames] = useState<UserGame[]>([]);
|
||||
const [prevPinnedGames, setPrevPinnedGames] = useState<UserGame[]>([]);
|
||||
const statsAnimation = useRef(-1);
|
||||
const { toggleSection, isPinnedCollapsed } = useSectionCollapse();
|
||||
|
||||
@@ -92,27 +87,6 @@ export function ProfileContent() {
|
||||
|
||||
const { numberFormatter } = useFormat();
|
||||
|
||||
const gamesHaveChanged = (
|
||||
current: UserGame[],
|
||||
previous: UserGame[]
|
||||
): boolean => {
|
||||
if (current.length !== previous.length) return true;
|
||||
return current.some(
|
||||
(game, index) => game.objectId !== previous[index]?.objectId
|
||||
);
|
||||
};
|
||||
|
||||
const shouldAnimateLibrary = gamesHaveChanged(libraryGames, prevLibraryGames);
|
||||
const shouldAnimatePinned = gamesHaveChanged(pinnedGames, prevPinnedGames);
|
||||
|
||||
useEffect(() => {
|
||||
setPrevLibraryGames(libraryGames);
|
||||
}, [libraryGames]);
|
||||
|
||||
useEffect(() => {
|
||||
setPrevPinnedGames(pinnedGames);
|
||||
}, [pinnedGames]);
|
||||
|
||||
const usersAreFriends = useMemo(() => {
|
||||
return userProfile?.relation?.status === "ACCEPTED";
|
||||
}, [userProfile]);
|
||||
@@ -192,57 +166,22 @@ export function ProfileContent() {
|
||||
exit="collapsed"
|
||||
layout
|
||||
>
|
||||
<motion.ul
|
||||
className="profile-content__games-grid"
|
||||
variants={
|
||||
shouldAnimatePinned ? gameGridVariants : undefined
|
||||
}
|
||||
initial={shouldAnimatePinned ? "hidden" : undefined}
|
||||
animate={shouldAnimatePinned ? "visible" : undefined}
|
||||
exit={shouldAnimatePinned ? "exit" : undefined}
|
||||
key={
|
||||
shouldAnimatePinned
|
||||
? `pinned-${sortBy}`
|
||||
: `pinned-static`
|
||||
}
|
||||
>
|
||||
{shouldAnimatePinned ? (
|
||||
<AnimatePresence mode="wait">
|
||||
{pinnedGames?.map((game, index) => (
|
||||
<motion.li
|
||||
key={game.objectId}
|
||||
variants={gameCardVariants}
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
exit="exit"
|
||||
transition={{ delay: index * 0.1 }}
|
||||
style={{ listStyle: "none" }}
|
||||
>
|
||||
<UserLibraryGameCard
|
||||
game={game}
|
||||
statIndex={statsIndex}
|
||||
onMouseEnter={handleOnMouseEnterGameCard}
|
||||
onMouseLeave={handleOnMouseLeaveGameCard}
|
||||
/>
|
||||
</motion.li>
|
||||
))}
|
||||
</AnimatePresence>
|
||||
) : (
|
||||
pinnedGames?.map((game) => (
|
||||
<li
|
||||
key={game.objectId}
|
||||
style={{ listStyle: "none" }}
|
||||
>
|
||||
<UserLibraryGameCard
|
||||
game={game}
|
||||
statIndex={statsIndex}
|
||||
onMouseEnter={handleOnMouseEnterGameCard}
|
||||
onMouseLeave={handleOnMouseLeaveGameCard}
|
||||
/>
|
||||
</li>
|
||||
))
|
||||
)}
|
||||
</motion.ul>
|
||||
<ul className="profile-content__games-grid">
|
||||
{pinnedGames?.map((game) => (
|
||||
<li
|
||||
key={game.objectId}
|
||||
style={{ listStyle: "none" }}
|
||||
>
|
||||
<UserLibraryGameCard
|
||||
game={game}
|
||||
statIndex={statsIndex}
|
||||
onMouseEnter={handleOnMouseEnterGameCard}
|
||||
onMouseLeave={handleOnMouseLeaveGameCard}
|
||||
sortBy={sortBy}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
@@ -262,54 +201,19 @@ export function ProfileContent() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<motion.ul
|
||||
className="profile-content__games-grid"
|
||||
variants={
|
||||
shouldAnimateLibrary ? gameGridVariants : undefined
|
||||
}
|
||||
initial={shouldAnimateLibrary ? "hidden" : undefined}
|
||||
animate={shouldAnimateLibrary ? "visible" : undefined}
|
||||
exit={shouldAnimateLibrary ? "exit" : undefined}
|
||||
key={
|
||||
shouldAnimateLibrary
|
||||
? `library-${sortBy}`
|
||||
: `library-static`
|
||||
}
|
||||
>
|
||||
{shouldAnimateLibrary ? (
|
||||
<AnimatePresence mode="wait">
|
||||
{libraryGames?.map((game, index) => (
|
||||
<motion.li
|
||||
key={game.objectId}
|
||||
variants={gameCardVariants}
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
exit="exit"
|
||||
transition={{ delay: index * 0.1 }}
|
||||
style={{ listStyle: "none" }}
|
||||
>
|
||||
<UserLibraryGameCard
|
||||
game={game}
|
||||
statIndex={statsIndex}
|
||||
onMouseEnter={handleOnMouseEnterGameCard}
|
||||
onMouseLeave={handleOnMouseLeaveGameCard}
|
||||
/>
|
||||
</motion.li>
|
||||
))}
|
||||
</AnimatePresence>
|
||||
) : (
|
||||
libraryGames?.map((game) => (
|
||||
<li key={game.objectId} style={{ listStyle: "none" }}>
|
||||
<UserLibraryGameCard
|
||||
game={game}
|
||||
statIndex={statsIndex}
|
||||
onMouseEnter={handleOnMouseEnterGameCard}
|
||||
onMouseLeave={handleOnMouseLeaveGameCard}
|
||||
/>
|
||||
</li>
|
||||
))
|
||||
)}
|
||||
</motion.ul>
|
||||
<ul className="profile-content__games-grid">
|
||||
{libraryGames?.map((game) => (
|
||||
<li key={game.objectId} style={{ listStyle: "none" }}>
|
||||
<UserLibraryGameCard
|
||||
game={game}
|
||||
statIndex={statsIndex}
|
||||
onMouseEnter={handleOnMouseEnterGameCard}
|
||||
onMouseLeave={handleOnMouseLeaveGameCard}
|
||||
sortBy={sortBy}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -338,8 +242,6 @@ export function ProfileContent() {
|
||||
pinnedGames,
|
||||
isPinnedCollapsed,
|
||||
toggleSection,
|
||||
shouldAnimateLibrary,
|
||||
shouldAnimatePinned,
|
||||
sortBy,
|
||||
]);
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
display: flex;
|
||||
transition: all ease 0.2s;
|
||||
cursor: grab;
|
||||
container-type: inline-size;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.05);
|
||||
@@ -86,32 +87,10 @@
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
gap: 4px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
&__favorite-icon {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
backdrop-filter: blur(8px);
|
||||
-webkit-backdrop-filter: blur(8px);
|
||||
border: solid 1px rgba(255, 255, 255, 0.15);
|
||||
border-radius: 50%;
|
||||
padding: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
transition: all ease 0.2s;
|
||||
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
border-color: rgba(255, 255, 255, 0.25);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
&__pin-button {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
@@ -154,11 +133,25 @@
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
transition: all ease 0.2s;
|
||||
|
||||
&:hover {
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
border-color: rgba(255, 255, 255, 0.25);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
|
||||
&-long {
|
||||
display: inline;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
&-short {
|
||||
display: none;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
// When the card is narrow (less than 180px), show short format
|
||||
@container (max-width: 140px) {
|
||||
&-long {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&-short {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
}
|
||||
&__manual-playtime {
|
||||
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
ClockIcon,
|
||||
TrophyIcon,
|
||||
AlertFillIcon,
|
||||
HeartFillIcon,
|
||||
PinIcon,
|
||||
PinSlashIcon,
|
||||
} from "@primer/octicons-react";
|
||||
@@ -27,6 +26,7 @@ interface UserLibraryGameCardProps {
|
||||
statIndex: number;
|
||||
onMouseEnter: () => void;
|
||||
onMouseLeave: () => void;
|
||||
sortBy?: string;
|
||||
}
|
||||
|
||||
export function UserLibraryGameCard({
|
||||
@@ -34,6 +34,7 @@ export function UserLibraryGameCard({
|
||||
statIndex,
|
||||
onMouseEnter,
|
||||
onMouseLeave,
|
||||
sortBy,
|
||||
}: UserLibraryGameCardProps) {
|
||||
const { userProfile, isMe, getUserLibraryGames } =
|
||||
useContext(userProfileContext);
|
||||
@@ -79,17 +80,22 @@ export function UserLibraryGameCard({
|
||||
};
|
||||
|
||||
const formatPlayTime = useCallback(
|
||||
(playTimeInSeconds = 0) => {
|
||||
(playTimeInSeconds = 0, isShort = false) => {
|
||||
const minutes = playTimeInSeconds / 60;
|
||||
|
||||
if (minutes < MAX_MINUTES_TO_SHOW_IN_PLAYTIME) {
|
||||
return t("amount_minutes", {
|
||||
return t(isShort ? "amount_minutes_short" : "amount_minutes", {
|
||||
amount: minutes.toFixed(0),
|
||||
});
|
||||
}
|
||||
|
||||
const hours = minutes / 60;
|
||||
return t("amount_hours", { amount: numberFormatter.format(hours) });
|
||||
const hoursKey = isShort ? "amount_hours_short" : "amount_hours";
|
||||
const hoursAmount = isShort
|
||||
? Math.floor(hours)
|
||||
: numberFormatter.format(hours);
|
||||
|
||||
return t(hoursKey, { amount: hoursAmount });
|
||||
},
|
||||
[numberFormatter, t]
|
||||
);
|
||||
@@ -104,7 +110,7 @@ export function UserLibraryGameCard({
|
||||
!game.isPinned
|
||||
);
|
||||
|
||||
await getUserLibraryGames();
|
||||
await getUserLibraryGames(sortBy);
|
||||
|
||||
if (game.isPinned) {
|
||||
showSuccessToast(t("game_removed_from_pinned"));
|
||||
@@ -130,33 +136,26 @@ export function UserLibraryGameCard({
|
||||
onClick={() => navigate(buildUserGameDetailsPath(game))}
|
||||
>
|
||||
<div className="user-library-game__overlay">
|
||||
{(game.isFavorite || isMe) && (
|
||||
{isMe && (
|
||||
<div className="user-library-game__actions-container">
|
||||
{game.isFavorite && (
|
||||
<div className="user-library-game__favorite-icon">
|
||||
<HeartFillIcon size={12} />
|
||||
</div>
|
||||
)}
|
||||
{isMe && (
|
||||
<button
|
||||
type="button"
|
||||
className="user-library-game__pin-button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
toggleGamePinned();
|
||||
}}
|
||||
disabled={isPinning}
|
||||
>
|
||||
{game.isPinned ? (
|
||||
<PinSlashIcon size={12} />
|
||||
) : (
|
||||
<PinIcon size={12} />
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
className="user-library-game__pin-button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
toggleGamePinned();
|
||||
}}
|
||||
disabled={isPinning}
|
||||
>
|
||||
{game.isPinned ? (
|
||||
<PinSlashIcon size={12} />
|
||||
) : (
|
||||
<PinIcon size={12} />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<small
|
||||
<div
|
||||
className="user-library-game__playtime"
|
||||
data-tooltip-place="top"
|
||||
data-tooltip-content={
|
||||
@@ -174,8 +173,13 @@ export function UserLibraryGameCard({
|
||||
) : (
|
||||
<ClockIcon size={11} />
|
||||
)}
|
||||
{formatPlayTime(game.playTimeInSeconds)}
|
||||
</small>
|
||||
<span className="user-library-game__playtime-long">
|
||||
{formatPlayTime(game.playTimeInSeconds)}
|
||||
</span>
|
||||
<span className="user-library-game__playtime-short">
|
||||
{formatPlayTime(game.playTimeInSeconds, true)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{userProfile?.hasActiveSubscription &&
|
||||
game.achievementCount > 0 && (
|
||||
|
||||
@@ -38,6 +38,12 @@ export interface Game {
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user