Compare commits

...

28 Commits

Author SHA1 Message Date
Zamitto
51c4e4f5b3 chore: bump version 2025-10-31 13:07:06 -03:00
Zamitto
c71f5947ba feat: use new ep to track game playtime 2025-10-31 10:20:11 -03:00
Zamitto
1af69465c1 Merge pull request #1839 from hydralauncher/fix/custom-games-requests
fix: requests for custom games
2025-10-31 07:08:51 -03:00
Moyase
a83a96f214 Merge pull request #1840 from hydralauncher/feat/catalogue-manual-pagination
fix: removed ability to enter non-number symbols to pagination
2025-10-31 00:46:39 +02:00
Moyasee
aadbda770b fix: linting issue, marked props as read-only 2025-10-31 00:19:49 +02:00
Moyasee
bd059cc7fa feat: update cursorrules 2025-10-30 23:45:29 +02:00
Moyasee
bbbf861594 fix: deleted comments 2025-10-30 23:36:41 +02:00
Moyasee
80e0adcd49 fix: removed ability to enter non-number symbols to pagination 2025-10-30 23:33:07 +02:00
Zamitto
aadf648a2b chore: unnecessary casting 2025-10-30 07:58:43 -03:00
Zamitto
87dbd548d0 Merge branch 'release/v3.7.2' into fix/custom-games-requests 2025-10-30 07:58:31 -03:00
Zamitto
459bf73121 fix: request download-sources on custom game
Some checks failed
Build Renderer / build (push) Has been cancelled
Release / build (ubuntu-latest) (push) Has been cancelled
Release / build (windows-2022) (push) Has been cancelled
2025-10-30 07:36:23 -03:00
Zamitto
a2ef0f304d fix: playtime count and custom games request on process watcher 2025-10-30 07:35:49 -03:00
Zamitto
b04561986e Merge pull request #1838 from hydralauncher/main
Some checks failed
Build Renderer / build (push) Has been cancelled
Release / build (ubuntu-latest) (push) Has been cancelled
Release / build (windows-2022) (push) Has been cancelled
sync release 3.7.2
2025-10-29 23:07:16 -03:00
Zamitto
1bd88e6c6e Merge pull request #1837 from Stormm232/main
Updating Hungarian Translation
2025-10-29 23:06:22 -03:00
Kiwo.2
4ff8dc4fa7 Fix with Prettier 2025-10-30 02:32:18 +01:00
Kiwo.2
dcc671f999 Mistake Correction 2025-10-30 02:15:35 +01:00
Kiwo.2
6e76111e23 Missing Comma Fix 2025-10-30 02:10:02 +01:00
Kiwo.2
3fce26f1f7 Update to 3.7.2 2025-10-30 01:55:15 +01:00
Kiwo.2
90c5ccb796 Update to 3.7.2 2025-10-30 01:40:36 +01:00
Kiwo.2
41092c2dd4 Update to 3.7.2 2025-10-30 01:35:54 +01:00
Kiwo.2
6383b728bc Fix to the translation 2025-10-30 01:26:51 +01:00
Kiwo.2
4dd28bbbf1 Hungarian Translation 3.7.2 2025-10-30 01:12:29 +01:00
Zamitto
21074322fa Merge pull request #1836 from Wkeynhk/patch-6
Some checks failed
Build Renderer / build (push) Has been cancelled
Release / build (ubuntu-latest) (push) Has been cancelled
Release / build (windows-2022) (push) Has been cancelled
Update Russian Translation
2025-10-29 20:32:30 -03:00
Wkeynhk
0e7e53478a Update translation.json 2025-10-30 00:47:30 +03:00
Zamitto
65e49550ad chore: fix aur package 2025-10-29 18:10:27 -03:00
Zamitto
0990951183 chore: fix aur package 2025-10-29 18:06:46 -03:00
Zamitto
53c162f0e4 feat: add i18n 2025-10-29 17:55:55 -03:00
Kiwo.2
8203399eda Matching to Latest Update 2025-10-12 23:27:20 +02:00
15 changed files with 167 additions and 71 deletions

View File

@@ -27,3 +27,11 @@
- Follow TypeScript strict mode conventions - Follow TypeScript strict mode conventions
- Use async/await instead of promises when possible - Use async/await instead of promises when possible
- Prefer named exports over default exports for utilities and services - Prefer named exports over default exports for utilities and services
## Comments
- Keep comments concise and purposeful; avoid verbose explanations.
- Focus on the "why" or non-obvious context, not restating the code.
- Prefer self-explanatory naming and structure over excessive comments.
- Do not comment every line or obvious behavior; remove stale comments.
- Use docblocks only where they add value (public APIs, complex logic).

View File

@@ -98,6 +98,7 @@ jobs:
# Update pkgver in PKGBUILD # Update pkgver in PKGBUILD
cd hydra-launcher-bin cd hydra-launcher-bin
NEW_VERSION="${{ steps.get-version.outputs.version }}" NEW_VERSION="${{ steps.get-version.outputs.version }}"
NEW_VERSION="${NEW_VERSION#v}"
echo "Updating PKGBUILD pkgver to $NEW_VERSION" echo "Updating PKGBUILD pkgver to $NEW_VERSION"
@@ -137,6 +138,9 @@ jobs:
COMMIT_MSG="v${{ steps.get-version.outputs.version }}" COMMIT_MSG="v${{ steps.get-version.outputs.version }}"
git commit -m "$COMMIT_MSG" git commit -m "$COMMIT_MSG"
export GIT_SSH_COMMAND="ssh -i ~/.ssh/id_rsa -F ~/.ssh/config -o UserKnownHostsFile=$SSH_PATH/known_hosts"
git push origin master git push origin master
echo "Successfully updated AUR package to version ${{ steps.get-version.outputs.version }}" echo "Successfully updated AUR package to version ${{ steps.get-version.outputs.version }}"
fi fi

View File

@@ -1,6 +1,6 @@
{ {
"name": "hydralauncher", "name": "hydralauncher",
"version": "3.7.2", "version": "3.7.3",
"description": "Hydra", "description": "Hydra",
"main": "./out/main/index.js", "main": "./out/main/index.js",
"author": "Los Broxas", "author": "Los Broxas",

View File

@@ -541,7 +541,9 @@
"notification_preview": "Probar notificación de logro", "notification_preview": "Probar notificación de logro",
"debrid": "Debrid", "debrid": "Debrid",
"debrid_description": "Los servicios Debrid son descargadores premium sin restricciones que te dejan descargar más rápido archivos alojados en servicios de alojamiento siendo que la única limitación es tu velocidad de internet.", "debrid_description": "Los servicios Debrid son descargadores premium sin restricciones que te dejan descargar más rápido archivos alojados en servicios de alojamiento siendo que la única limitación es tu velocidad de internet.",
"enable_friend_start_game_notifications": "Cuando un amigo está jugando un juego" "enable_friend_start_game_notifications": "Cuando un amigo está jugando un juego",
"autoplay_trailers_on_game_page": "Reproducir trailers automáticamente en la página del juego",
"hide_to_tray_on_game_start": "Ocultar Hydra en la bandeja al iniciar un juego"
}, },
"notifications": { "notifications": {
"download_complete": "Descarga completada", "download_complete": "Descarga completada",

View File

@@ -8,7 +8,7 @@
"no_results": "Nincs találat", "no_results": "Nincs találat",
"start_typing": "Kereséshez gépelj...", "start_typing": "Kereséshez gépelj...",
"hot": "Most felkapott", "hot": "Most felkapott",
"weekly": "📅 A hét felkapott játékai", "weekly": "📅 A hét felkapottjai",
"achievements": "🏆 Achievement támogatott" "achievements": "🏆 Achievement támogatott"
}, },
"sidebar": { "sidebar": {
@@ -26,7 +26,7 @@
"sign_in": "Bejelentkezés", "sign_in": "Bejelentkezés",
"friends": "Barátok", "friends": "Barátok",
"need_help": "Elakadtál?", "need_help": "Elakadtál?",
"favorites": "Kedvenc játékok", "favorites": "Kedvenc Játékaim",
"playable_button_title": "Csak az azonnal játszható játékokat mutasd", "playable_button_title": "Csak az azonnal játszható játékokat mutasd",
"add_custom_game_tooltip": "Saját játék hozzáadása", "add_custom_game_tooltip": "Saját játék hozzáadása",
"show_playable_only_tooltip": "Csak játszható játék mutatása", "show_playable_only_tooltip": "Csak játszható játék mutatása",
@@ -224,7 +224,7 @@
"show_less": "Mutass kevesebbet", "show_less": "Mutass kevesebbet",
"reviews": "Vélemények", "reviews": "Vélemények",
"leave_a_review": "Hagyd itt a véleményed", "leave_a_review": "Hagyd itt a véleményed",
"write_review_placeholder": "Oszd meg a gondolataid a játékról...", "write_review_placeholder": "Oszd meg gondolatod a játékról...",
"sort_newest": "Legújabb", "sort_newest": "Legújabb",
"no_reviews_yet": "Még nem lett vélemény megosztva", "no_reviews_yet": "Még nem lett vélemény megosztva",
"be_first_to_review": "Légy az első, aki megossza a véleményét a játékról!", "be_first_to_review": "Légy az első, aki megossza a véleményét a játékról!",
@@ -252,7 +252,7 @@
"you_seemed_to_enjoy_this_game": "Úgy látszik élvezted ezt a játékot", "you_seemed_to_enjoy_this_game": "Úgy látszik élvezted ezt a játékot",
"would_you_recommend_this_game": "Szeretnél véleményt írni erről a játékról?", "would_you_recommend_this_game": "Szeretnél véleményt írni erről a játékról?",
"yes": "Igen", "yes": "Igen",
"maybe_later": "Talán Később", "maybe_later": "Talán később",
"cloud_save": "Mentés felhőben", "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", "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", "backups": "Biztonsági másolatok",
@@ -356,13 +356,18 @@
"delete_review_modal_title": "Biztos vagy abban hogy törölni szeretnéd a véleményed?", "delete_review_modal_title": "Biztos vagy abban hogy törölni szeretnéd a véleményed?",
"delete_review_modal_description": "Ez a lépés nem vonható vissza.", "delete_review_modal_description": "Ez a lépés nem vonható vissza.",
"delete_review_modal_delete_button": "Törlés", "delete_review_modal_delete_button": "Törlés",
"delete_review_modal_cancel_button": "Mégse" "delete_review_modal_cancel_button": "Mégse",
"vote_failed": "A szavazatod nem regisztrálódott. Kérlek próbáld újra.",
"show_original": "Eredeti megjelenítése",
"show_translation": "Fordítás megjelenítése",
"show_original_translated_from": "Eredeti megjelenítése (fordítva: {{language}})",
"hide_original": "Eredeti elrejtése"
}, },
"activation": { "activation": {
"title": "Hydra Aktiválása", "title": "Hydra Aktiválása",
"installation_id": "Telepítési Azonosító:", "installation_id": "Telepítési Azonosító:",
"enter_activation_code": "Írd be az aktiválási kódod", "enter_activation_code": "Írd be az aktiválási kódod",
"message": "Ha nem tudod hol kérdezz efelől, akkor nem kéne ilyened legyen.", "message": "Ha nem tudod merre kérdezz efelől, akkor nem kéne ilyened legyen.",
"activate": "Aktiválás", "activate": "Aktiválás",
"loading": "Töltés…" "loading": "Töltés…"
}, },
@@ -386,7 +391,7 @@
"download_in_progress": "Folyamatban lévő", "download_in_progress": "Folyamatban lévő",
"queued_downloads": "Várakozósoron lévő letöltések", "queued_downloads": "Várakozósoron lévő letöltések",
"downloads_completed": "Befejezett", "downloads_completed": "Befejezett",
"queued": "Várakozási sorban", "queued": "Várakozásban",
"no_downloads_title": "Oly üres..", "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.", "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…", "checking_files": "Fájlok ellenőrzése…",
@@ -419,20 +424,30 @@
"debrid_linked_message": "Fiók összekapcsolva: \"{{username}}\" ", "debrid_linked_message": "Fiók összekapcsolva: \"{{username}}\" ",
"save_changes": "Változtatások mentése", "save_changes": "Változtatások mentése",
"changes_saved": "Változtatások sikeresen mentve", "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.", "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", "validate_download_source": "Érvényesítés",
"remove_download_source": "Eltávolítás", "remove_download_source": "Eltávolítás",
"add_download_source": "Forrás hozáadása", "add_download_source": "Forrás hozáadása",
"adding": "Hozzáadás…",
"failed_add_download_source": "Letöltési forrás hozzáadása sikertelen. Kérlek próbáld újra.",
"download_source_already_exists": "Ez a letöltési forrás URL már létezik.",
"download_count_zero": "Nincs letöltési opció", "download_count_zero": "Nincs letöltési opció",
"download_count_one": "{{countFormatted}} letöltési opció", "download_count_one": "{{countFormatted}} letöltési opció",
"download_count_other": "{{countFormatted}} letöltési opció", "download_count_other": "{{countFormatted}} letöltési opció",
"download_source_url": "URL forrás:", "download_source_url": "URL Forrás:",
"add_download_source_description": "Helyezd be a .json fájl URL-jét", "add_download_source_description": "Helyezd be a .json fájl URL-jét",
"download_source_up_to_date": "Naprakész", "download_source_up_to_date": "Naprakész",
"download_source_errored": "Hiba történt", "download_source_errored": "Hiba történt",
"download_source_pending_matching": "Frissítés hamarosan",
"download_source_matched": "Naprakész",
"download_source_matching": "Frissítés..",
"download_source_failed": "Hiba",
"download_source_no_information": "Nincs elérhető információ",
"sync_download_sources": "Források szinkronizálása", "sync_download_sources": "Források szinkronizálása",
"removed_download_source": "Letöltési forrás eltávolítva", "removed_download_source": "Letöltési forrás eltávolítva",
"removed_download_sources": "Letöltési források eltávolítva", "removed_download_sources": "Letöltési források eltávolítva",
"removed_all_download_sources": "Összes letöltési forrás eltávolítva",
"download_sources_synced_successfully": "Az összes letöltési forrás szinkronizálva",
"cancel_button_confirmation_delete_all_sources": "Nem", "cancel_button_confirmation_delete_all_sources": "Nem",
"confirm_button_confirmation_delete_all_sources": "Igen, törölj mindent", "confirm_button_confirmation_delete_all_sources": "Igen, törölj mindent",
"title_confirmation_delete_all_sources": "Az összes letöltési forrás törlése", "title_confirmation_delete_all_sources": "Az összes letöltési forrás törlése",
@@ -445,6 +460,7 @@
"found_download_option_one": "{{countFormatted}} Letöltési opció találva", "found_download_option_one": "{{countFormatted}} Letöltési opció találva",
"found_download_option_other": "{{countFormatted}} Letöltési opciók találva", "found_download_option_other": "{{countFormatted}} Letöltési opciók találva",
"import": "Importálás", "import": "Importálás",
"importing": "Importálás...",
"public": "Publikus", "public": "Publikus",
"private": "Privát", "private": "Privát",
"friends_only": "Csak barátok", "friends_only": "Csak barátok",
@@ -462,6 +478,7 @@
"seed_after_download_complete": "Letöltés utáni seedelés", "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", "show_hidden_achievement_description": "Rejtett achievementek leírásának megjelenítése feloldás előtt",
"account": "Fiók", "account": "Fiók",
"hydra_cloud": "Hydra Cloud",
"no_users_blocked": "Nincsenek letiltott felhasználóid", "no_users_blocked": "Nincsenek letiltott felhasználóid",
"subscription_active_until": "Hydra Cloud előfizetésed aktív, eddig: {{date}}", "subscription_active_until": "Hydra Cloud előfizetésed aktív, eddig: {{date}}",
"manage_subscription": "Előfizetés kezelése", "manage_subscription": "Előfizetés kezelése",
@@ -498,14 +515,14 @@
"cancel": "Mégsem", "cancel": "Mégsem",
"appearance": "Megjelenés", "appearance": "Megjelenés",
"debrid": "Debrid", "debrid": "Debrid",
"debrid_description": "A Debrid szolgáltatások prémium szolgáltatások amelyek lehetővé teszik, hogy gyorsan letölts különböző fájltároló szolgáltatásokon tárolt fájlokat, csak az internet sebességed szab határt.", "debrid_description": "A Debrid szolgáltatások prémium szolgáltatások amelyek lehetővé teszik, hogy gyorsan letölts különböző fájltároló szolgáltatásokon tárolt fájlokat, és csak az internet sebességed szab határt.",
"enable_torbox": "TorBox bekapcsolása", "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_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", "torbox_account_linked": "TorBox fiók összekapcsolva",
"create_real_debrid_account": "Kattints ide ha még nincs Real-Debrid fiókod", "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", "create_torbox_account": "Kattints ide ha még nincs TorBox fiókod",
"real_debrid_account_linked": "Real-Debrid fiók összekapcsolva", "real_debrid_account_linked": "Real-Debrid fiók összekapcsolva",
"name_min_length": "A téma neve legalább 3 karakter hosszú legyen", "name_min_length": "A téma neve legalább 3 karakter hosszú kell legyen",
"import_theme": "Téma importálása", "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}}", "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", "error_importing_theme": "Hiba lépett fel a téma importálása közben",
@@ -535,7 +552,9 @@
"hidden": "Rejtett", "hidden": "Rejtett",
"test_notification": "Értesítés tesztelése", "test_notification": "Értesítés tesztelése",
"notification_preview": "Achievement Értesítés Előnézete", "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" "enable_friend_start_game_notifications": "Amikor egy barátod elkezd játszani egy játékot",
"autoplay_trailers_on_game_page": "Játékelőzetes automatikus lejátszása a játék oldalán",
"hide_to_tray_on_game_start": "Hydra elrejtése játék elindításakor a tálcára"
}, },
"notifications": { "notifications": {
"download_complete": "Letöltés befejezve", "download_complete": "Letöltés befejezve",
@@ -563,10 +582,10 @@
"available_one": "Elérhető", "available_one": "Elérhető",
"available_other": "Elérhető", "available_other": "Elérhető",
"no_downloads": "Nincs elérhető letöltés", "no_downloads": "Nincs elérhető letöltés",
"calculating": "Feldolgozás" "calculating": "Számítás alatt.."
}, },
"binary_not_found_modal": { "binary_not_found_modal": {
"title": "A programok nincsenek telepítve", "title": "Hiányzó programok",
"description": "Wine vagy Lutris futtatható fájlok nem találhatók a rendszereden", "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ódra, hogy a játék megfelelően fusson" "instructions": "Ellenőrízd hogy melyiket kell helyesen telepíteni a Linux disztribúciódra, hogy a játék megfelelően fusson"
}, },
@@ -585,6 +604,7 @@
"activity": "Legutóbbi tevékenység", "activity": "Legutóbbi tevékenység",
"library": "Könyvtár", "library": "Könyvtár",
"pinned": "Kitűzve", "pinned": "Kitűzve",
"sort_by": "Rendezés:",
"achievements_earned": "Elért achievementek", "achievements_earned": "Elért achievementek",
"played_recently": "Nemrég játszva", "played_recently": "Nemrég játszva",
"playtime": "Játszottidő", "playtime": "Játszottidő",
@@ -654,7 +674,7 @@
"uploading_banner": "Borítókép feltöltése…", "uploading_banner": "Borítókép feltöltése…",
"background_image_updated": "Borítókép frissítve", "background_image_updated": "Borítókép frissítve",
"stats": "Statisztikák", "stats": "Statisztikák",
"achievements": "achievementek", "achievements": "achievement",
"games": "Játékok", "games": "Játékok",
"top_percentile": "Top {{percentile}}%", "top_percentile": "Top {{percentile}}%",
"ranking_updated_weekly": "A rangsor hetente frissül.", "ranking_updated_weekly": "A rangsor hetente frissül.",
@@ -669,7 +689,7 @@
"game_added_to_pinned": "Játék hozzáadva a kitűzöttekhez", "game_added_to_pinned": "Játék hozzáadva a kitűzöttekhez",
"karma": "Karma", "karma": "Karma",
"karma_count": "karma", "karma_count": "karma",
"karma_description": "Pozitív értékelésekre kapott pontok alapján" "karma_description": "Pozitív értékelésekkel szerzett pontok"
}, },
"achievement": { "achievement": {
"achievement_unlocked": "Achievement feloldva", "achievement_unlocked": "Achievement feloldva",
@@ -678,7 +698,7 @@
"unlocked_at": "Feloldva: {{date}}", "unlocked_at": "Feloldva: {{date}}",
"subscription_needed": "A tartalom megtekintéséhez Hydra Cloud előfizetés szükséges", "subscription_needed": "A tartalom megtekintéséhez Hydra Cloud előfizetés szükséges",
"new_achievements_unlocked": "{{achievementCount}} új achievement feloldva {{gameCount}} játékban", "new_achievements_unlocked": "{{achievementCount}} új achievement feloldva {{gameCount}} játékban",
"achievement_progress": "{{unlockedCount}}/{{totalCount}} achievementek", "achievement_progress": "{{unlockedCount}}/{{totalCount}} achievement",
"achievements_unlocked_for_game": "{{achievementCount}} új achievement feloldva itt: {{gameTitle}}", "achievements_unlocked_for_game": "{{achievementCount}} új achievement feloldva itt: {{gameTitle}}",
"hidden_achievement_tooltip": "Ez egy rejtett achievement", "hidden_achievement_tooltip": "Ez egy rejtett achievement",
"achievement_earn_points": "Szerezz be {{points}} pontot ezzel az achievement-el", "achievement_earn_points": "Szerezz be {{points}} pontot ezzel az achievement-el",

View File

@@ -538,7 +538,9 @@
"hidden": "Oculta", "hidden": "Oculta",
"test_notification": "Testar notificação", "test_notification": "Testar notificação",
"notification_preview": "Prévia da Notificação de Conquistas", "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",
"autoplay_trailers_on_game_page": "Reproduzir trailers automaticamente na página do jogo",
"hide_to_tray_on_game_start": "Ocultar o Hydra na bandeja ao iniciar um jogo"
}, },
"notifications": { "notifications": {
"download_complete": "Download concluído", "download_complete": "Download concluído",

View File

@@ -212,6 +212,7 @@
"stats": "Статистика", "stats": "Статистика",
"download_count": "Загрузки", "download_count": "Загрузки",
"player_count": "Активные игроки", "player_count": "Активные игроки",
"rating_count": "Оценка",
"download_error": "Этот вариант загрузки недоступен", "download_error": "Этот вариант загрузки недоступен",
"download": "Скачать", "download": "Скачать",
"executable_path_in_use": "Исполняемый файл уже используется \"{{game}}\"", "executable_path_in_use": "Исполняемый файл уже используется \"{{game}}\"",
@@ -252,17 +253,6 @@
"would_you_recommend_this_game": "Хотите оставить отзыв об этой игре?", "would_you_recommend_this_game": "Хотите оставить отзыв об этой игре?",
"yes": "Да", "yes": "Да",
"maybe_later": "Возможно позже", "maybe_later": "Возможно позже",
"rating_count": "Оценка",
"delete_review": "Удалить отзыв",
"remove_review": "Удалить отзыв",
"delete_review_modal_title": "Вы уверены, что хотите удалить свой отзыв?",
"delete_review_modal_description": "Это действие нельзя отменить.",
"delete_review_modal_delete_button": "Удалить",
"delete_review_modal_cancel_button": "Отмена",
"show_original": "Показать оригинал",
"show_translation": "Показать перевод",
"show_original_translated_from": "Показать оригинал (переведено с {{language}})",
"hide_original": "Скрыть оригинал",
"cloud_save": "Облачное сохранение", "cloud_save": "Облачное сохранение",
"cloud_save_description": "Сохраняйте ваш прогресс в облаке и продолжайте играть на любом устройстве", "cloud_save_description": "Сохраняйте ваш прогресс в облаке и продолжайте играть на любом устройстве",
"backups": "Резервные копии", "backups": "Резервные копии",
@@ -360,7 +350,18 @@
"caption": "Субтитры", "caption": "Субтитры",
"audio": "Аудио", "audio": "Аудио",
"filter_by_source": "Фильтр по источнику", "filter_by_source": "Фильтр по источнику",
"no_repacks_found": "Источники для этой игры не найдены" "no_repacks_found": "Источники для этой игры не найдены",
"delete_review": "Удалить отзыв",
"remove_review": "Удалить отзыв",
"delete_review_modal_title": "Вы уверены, что хотите удалить свой отзыв?",
"delete_review_modal_description": "Это действие нельзя отменить.",
"delete_review_modal_delete_button": "Удалить",
"delete_review_modal_cancel_button": "Отмена",
"vote_failed": "Не удалось зарегистрировать ваш голос. Пожалуйста, попробуйте снова.",
"show_original": "Показать оригинал",
"show_translation": "Показать перевод",
"show_original_translated_from": "Показать оригинал (переведено с {{language}})",
"hide_original": "Скрыть оригинал"
}, },
"activation": { "activation": {
"title": "Активировать Hydra", "title": "Активировать Hydra",
@@ -427,6 +428,9 @@
"validate_download_source": "Проверить", "validate_download_source": "Проверить",
"remove_download_source": "Удалить", "remove_download_source": "Удалить",
"add_download_source": "Добавить источник", "add_download_source": "Добавить источник",
"adding": "Добавление…",
"failed_add_download_source": "Не удалось добавить источник. Пожалуйста, попробуйте снова.",
"download_source_already_exists": "Этот URL источника уже существует.",
"download_count_zero": "В списке нет загрузок", "download_count_zero": "В списке нет загрузок",
"download_count_one": "{{countFormatted}} загрузка в списке", "download_count_one": "{{countFormatted}} загрузка в списке",
"download_count_other": "{{countFormatted}} загрузок в списке", "download_count_other": "{{countFormatted}} загрузок в списке",
@@ -434,9 +438,16 @@
"add_download_source_description": "Вставьте ссылку на .json-файл", "add_download_source_description": "Вставьте ссылку на .json-файл",
"download_source_up_to_date": "Обновлён", "download_source_up_to_date": "Обновлён",
"download_source_errored": "Ошибка", "download_source_errored": "Ошибка",
"download_source_pending_matching": "Скоро обновится",
"download_source_matched": "Обновлен",
"download_source_matching": "Обновление",
"download_source_failed": "Ошибка",
"download_source_no_information": "Информация отсутствует",
"sync_download_sources": "Обновить источники", "sync_download_sources": "Обновить источники",
"removed_download_source": "Источник удален", "removed_download_source": "Источник удален",
"removed_download_sources": "Источники удалены", "removed_download_sources": "Источники удалены",
"removed_all_download_sources": "Все источники удалены",
"download_sources_synced_successfully": "Все источники синхронизированы",
"cancel_button_confirmation_delete_all_sources": "Нет", "cancel_button_confirmation_delete_all_sources": "Нет",
"confirm_button_confirmation_delete_all_sources": "Да, удалить все", "confirm_button_confirmation_delete_all_sources": "Да, удалить все",
"title_confirmation_delete_all_sources": "Удалить все источники", "title_confirmation_delete_all_sources": "Удалить все источники",
@@ -467,6 +478,7 @@
"seed_after_download_complete": "Раздавать после завершения загрузки", "seed_after_download_complete": "Раздавать после завершения загрузки",
"show_hidden_achievement_description": "Показывать описание скрытых достижений перед их получением", "show_hidden_achievement_description": "Показывать описание скрытых достижений перед их получением",
"account": "Аккаунт", "account": "Аккаунт",
"hydra_cloud": "Hydra Cloud",
"no_users_blocked": "У вас нет заблокированных пользователей", "no_users_blocked": "У вас нет заблокированных пользователей",
"subscription_active_until": "Ваша подписка на Hydra Cloud активна до {{date}}", "subscription_active_until": "Ваша подписка на Hydra Cloud активна до {{date}}",
"manage_subscription": "Управлять подпиской", "manage_subscription": "Управлять подпиской",
@@ -540,7 +552,9 @@
"hidden": "Скрытый", "hidden": "Скрытый",
"test_notification": "Тестовое уведомление", "test_notification": "Тестовое уведомление",
"notification_preview": "Предварительный просмотр уведомления о достижении", "notification_preview": "Предварительный просмотр уведомления о достижении",
"enable_friend_start_game_notifications": "Когда друг начинает играть в игру" "enable_friend_start_game_notifications": "Когда друг начинает играть в игру",
"autoplay_trailers_on_game_page": "Автоматически начинать воспроизведение трейлеров на странице игры",
"hide_to_tray_on_game_start": "Скрывать Hydra в трей при запуске игры"
}, },
"notifications": { "notifications": {
"download_complete": "Загрузка завершена", "download_complete": "Загрузка завершена",
@@ -590,6 +604,7 @@
"activity": "Недавняя активность", "activity": "Недавняя активность",
"library": "Библиотека", "library": "Библиотека",
"pinned": "Закрепленные", "pinned": "Закрепленные",
"sort_by": "Сортировать по:",
"achievements_earned": "Заработанные достижения", "achievements_earned": "Заработанные достижения",
"played_recently": "Недавно сыгранные", "played_recently": "Недавно сыгранные",
"playtime": "Время игры", "playtime": "Время игры",

View File

@@ -167,6 +167,8 @@ export class AchievementWatcherManager {
shop: GameShop, shop: GameShop,
objectId: string objectId: string
) { ) {
if (shop === "custom") return;
const gameKey = levelKeys.game(shop, objectId); const gameKey = levelKeys.game(shop, objectId);
if (this.alreadySyncedGames.get(gameKey)) return; if (this.alreadySyncedGames.get(gameKey)) return;

View File

@@ -3,6 +3,10 @@ import { HydraApi } from "../hydra-api";
import { gamesSublevel, levelKeys } from "@main/level"; import { gamesSublevel, levelKeys } from "@main/level";
export const createGame = async (game: Game) => { export const createGame = async (game: Game) => {
if (game.shop === "custom") {
return;
}
return HydraApi.post(`/profile/games`, { return HydraApi.post(`/profile/games`, {
objectId: game.objectId, objectId: game.objectId,
playTimeInMilliseconds: Math.trunc(game.playTimeInMilliseconds ?? 0), playTimeInMilliseconds: Math.trunc(game.playTimeInMilliseconds ?? 0),

View File

@@ -1,12 +1,16 @@
import type { Game } from "@types"; import type { Game } from "@types";
import { HydraApi } from "../hydra-api"; import { HydraApi } from "../hydra-api";
export const updateGamePlaytime = async ( export const trackGamePlaytime = async (
game: Game, game: Game,
deltaInMillis: number, deltaInMillis: number,
lastTimePlayed: Date lastTimePlayed: Date
) => { ) => {
return HydraApi.put(`/profile/games/${game.remoteId}`, { if (game.shop === "custom") {
return;
}
return HydraApi.put(`/profile/games/${game.shop}/${game.objectId}`, {
playTimeDeltaInSeconds: Math.trunc(deltaInMillis / 1000), playTimeDeltaInSeconds: Math.trunc(deltaInMillis / 1000),
lastTimePlayed, lastTimePlayed,
}); });

View File

@@ -1,5 +1,5 @@
import { WindowManager } from "./window-manager"; import { WindowManager } from "./window-manager";
import { createGame, updateGamePlaytime } from "./library-sync"; import { createGame, trackGamePlaytime } from "./library-sync";
import type { Game, GameRunning, UserPreferences } from "@types"; import type { Game, GameRunning, UserPreferences } from "@types";
import { PythonRPC } from "./python-rpc"; import { PythonRPC } from "./python-rpc";
import axios from "axios"; import axios from "axios";
@@ -198,11 +198,6 @@ export const watchProcesses = async () => {
function onOpenGame(game: Game) { function onOpenGame(game: Game) {
const now = performance.now(); const now = performance.now();
AchievementWatcherManager.firstSyncWithRemoteIfNeeded(
game.shop,
game.objectId
);
gamesPlaytime.set(levelKeys.game(game.shop, game.objectId), { gamesPlaytime.set(levelKeys.game(game.shop, game.objectId), {
lastTick: now, lastTick: now,
firstTick: now, firstTick: now,
@@ -220,8 +215,15 @@ function onOpenGame(game: Game) {
}) })
.catch(() => {}); .catch(() => {});
if (game.shop === "custom") return;
AchievementWatcherManager.firstSyncWithRemoteIfNeeded(
game.shop,
game.objectId
);
if (game.remoteId) { if (game.remoteId) {
updateGamePlaytime( trackGamePlaytime(
game, game,
game.unsyncedDeltaPlayTimeInMilliseconds ?? 0, game.unsyncedDeltaPlayTimeInMilliseconds ?? 0,
new Date() new Date()
@@ -255,43 +257,46 @@ function onTickGame(game: Game) {
const delta = now - gamePlaytime.lastTick; const delta = now - gamePlaytime.lastTick;
gamesSublevel.put(levelKeys.game(game.shop, game.objectId), { const updatedGame: Game = {
...game, ...game,
playTimeInMilliseconds: (game.playTimeInMilliseconds ?? 0) + delta, playTimeInMilliseconds: (game.playTimeInMilliseconds ?? 0) + delta,
lastTimePlayed: new Date(), lastTimePlayed: new Date(),
}); };
gamesSublevel.put(levelKeys.game(game.shop, game.objectId), updatedGame);
gamesPlaytime.set(levelKeys.game(game.shop, game.objectId), { gamesPlaytime.set(levelKeys.game(game.shop, game.objectId), {
...gamePlaytime, ...gamePlaytime,
lastTick: now, lastTick: now,
}); });
if (currentTick % TICKS_TO_UPDATE_API === 0) { if (currentTick % TICKS_TO_UPDATE_API === 0 && game.shop !== "custom") {
const deltaToSync = const deltaToSync =
now - now -
gamePlaytime.lastSyncTick + gamePlaytime.lastSyncTick +
(game.unsyncedDeltaPlayTimeInMilliseconds ?? 0); (game.unsyncedDeltaPlayTimeInMilliseconds ?? 0);
const gamePromise = game.remoteId const gamePromise = game.remoteId
? updateGamePlaytime(game, deltaToSync, game.lastTimePlayed!) ? trackGamePlaytime(game, deltaToSync, game.lastTimePlayed!)
: createGame(game); : createGame(game);
gamePromise gamePromise
.then(() => { .then(() => {
gamesSublevel.put(levelKeys.game(game.shop, game.objectId), { gamesSublevel.put(levelKeys.game(game.shop, game.objectId), {
...game, ...updatedGame,
unsyncedDeltaPlayTimeInMilliseconds: 0, unsyncedDeltaPlayTimeInMilliseconds: 0,
}); });
}) })
.catch(() => { .catch(() => {
gamesSublevel.put(levelKeys.game(game.shop, game.objectId), { gamesSublevel.put(levelKeys.game(game.shop, game.objectId), {
...game, ...updatedGame,
unsyncedDeltaPlayTimeInMilliseconds: deltaToSync, unsyncedDeltaPlayTimeInMilliseconds: deltaToSync,
}); });
}) })
.finally(() => { .finally(() => {
gamesPlaytime.set(levelKeys.game(game.shop, game.objectId), { gamesPlaytime.set(levelKeys.game(game.shop, game.objectId), {
...gamePlaytime, ...gamePlaytime,
lastTick: now,
lastSyncTick: now, lastSyncTick: now,
}); });
}); });
@@ -299,11 +304,24 @@ function onTickGame(game: Game) {
} }
const onCloseGame = (game: Game) => { const onCloseGame = (game: Game) => {
const now = performance.now();
const gamePlaytime = gamesPlaytime.get( const gamePlaytime = gamesPlaytime.get(
levelKeys.game(game.shop, game.objectId) levelKeys.game(game.shop, game.objectId)
)!; )!;
gamesPlaytime.delete(levelKeys.game(game.shop, game.objectId)); gamesPlaytime.delete(levelKeys.game(game.shop, game.objectId));
const delta = now - gamePlaytime.lastTick;
const updatedGame: Game = {
...game,
playTimeInMilliseconds: (game.playTimeInMilliseconds ?? 0) + delta,
lastTimePlayed: new Date(),
};
gamesSublevel.put(levelKeys.game(game.shop, game.objectId), updatedGame);
if (game.shop === "custom") return;
if (game.remoteId) { if (game.remoteId) {
if (game.automaticCloudSync) { if (game.automaticCloudSync) {
CloudSync.uploadSaveGame( CloudSync.uploadSaveGame(
@@ -315,20 +333,20 @@ const onCloseGame = (game: Game) => {
} }
const deltaToSync = const deltaToSync =
performance.now() - now -
gamePlaytime.lastSyncTick + gamePlaytime.lastSyncTick +
(game.unsyncedDeltaPlayTimeInMilliseconds ?? 0); (game.unsyncedDeltaPlayTimeInMilliseconds ?? 0);
return updateGamePlaytime(game, deltaToSync, game.lastTimePlayed!) return trackGamePlaytime(game, deltaToSync, game.lastTimePlayed!)
.then(() => { .then(() => {
return gamesSublevel.put(levelKeys.game(game.shop, game.objectId), { return gamesSublevel.put(levelKeys.game(game.shop, game.objectId), {
...game, ...updatedGame,
unsyncedDeltaPlayTimeInMilliseconds: 0, unsyncedDeltaPlayTimeInMilliseconds: 0,
}); });
}) })
.catch(() => { .catch(() => {
return gamesSublevel.put(levelKeys.game(game.shop, game.objectId), { return gamesSublevel.put(levelKeys.game(game.shop, game.objectId), {
...game, ...updatedGame,
unsyncedDeltaPlayTimeInMilliseconds: deltaToSync, unsyncedDeltaPlayTimeInMilliseconds: deltaToSync,
}); });
}); });

View File

@@ -289,12 +289,6 @@ export class WindowManager {
} }
} }
private static loadNotificationWindowURL() {
if (this.notificationWindow) {
this.loadWindowURL(this.notificationWindow, "achievement-notification");
}
}
private static readonly NOTIFICATION_WINDOW_WIDTH = 360; private static readonly NOTIFICATION_WINDOW_WIDTH = 360;
private static readonly NOTIFICATION_WINDOW_HEIGHT = 140; private static readonly NOTIFICATION_WINDOW_HEIGHT = 140;
@@ -387,7 +381,7 @@ export class WindowManager {
this.notificationWindow.setIgnoreMouseEvents(true); this.notificationWindow.setIgnoreMouseEvents(true);
this.notificationWindow.setAlwaysOnTop(true, "screen-saver", 1); this.notificationWindow.setAlwaysOnTop(true, "screen-saver", 1);
this.loadNotificationWindowURL(); this.loadWindowURL(this.notificationWindow, "achievement-notification");
if (!app.isPackaged || isStaging) { if (!app.isPackaged || isStaging) {
this.notificationWindow.webContents.openDevTools(); this.notificationWindow.webContents.openDevTools();

View File

@@ -293,6 +293,8 @@ export function GameDetailsContextProvider({
}, [objectId, shop, userDetails]); }, [objectId, shop, userDetails]);
useEffect(() => { useEffect(() => {
if (shop === "custom") return;
const fetchDownloadSources = async () => { const fetchDownloadSources = async () => {
try { try {
const sources = await window.electron.getDownloadSources(); const sources = await window.electron.getDownloadSources();

View File

@@ -29,9 +29,11 @@ function JumpControl({
return isOpen ? ( return isOpen ? (
<input <input
ref={inputRef} ref={inputRef}
type="number" type="text"
min={1} min={1}
max={totalPages} max={totalPages}
inputMode="numeric"
pattern="[0-9]*"
className="pagination__page-input" className="pagination__page-input"
value={value} value={value}
onChange={onChange} onChange={onChange}
@@ -56,7 +58,7 @@ export function Pagination({
page, page,
totalPages, totalPages,
onPageChange, onPageChange,
}: PaginationProps) { }: Readonly<PaginationProps>) {
const { formatNumber } = useFormat(); const { formatNumber } = useFormat();
const [isJumpOpen, setIsJumpOpen] = useState(false); const [isJumpOpen, setIsJumpOpen] = useState(false);
@@ -87,13 +89,15 @@ export function Pagination({
} }
const onJumpChange = (e: ChangeEvent<HTMLInputElement>) => { const onJumpChange = (e: ChangeEvent<HTMLInputElement>) => {
const val = e.target.value; const raw = e.target.value;
if (val === "") { const digitsOnly = raw.replaceAll(/\D+/g, "");
if (digitsOnly === "") {
setJumpValue(""); setJumpValue("");
return; return;
} }
const num = Number(val); const num = Number.parseInt(digitsOnly, 10);
if (Number.isNaN(num)) { if (Number.isNaN(num)) {
setJumpValue("");
return; return;
} }
if (num < 1) { if (num < 1) {
@@ -104,19 +108,36 @@ export function Pagination({
setJumpValue(String(totalPages)); setJumpValue(String(totalPages));
return; return;
} }
setJumpValue(val); setJumpValue(String(num));
}; };
const onJumpKeyDown = (e: KeyboardEvent<HTMLInputElement>) => { const onJumpKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
const controlKeys = [
"Backspace",
"Delete",
"Tab",
"ArrowLeft",
"ArrowRight",
"Home",
"End",
];
if (controlKeys.includes(e.key) || e.ctrlKey || e.metaKey) {
return;
}
if (e.key === "Enter") { if (e.key === "Enter") {
if (jumpValue.trim() === "") return; const sanitized = jumpValue.replaceAll(/\D+/g, "");
const parsed = Number(jumpValue); if (sanitized.trim() === "") return;
const parsed = Number.parseInt(sanitized, 10);
if (Number.isNaN(parsed)) return; if (Number.isNaN(parsed)) return;
const target = Math.max(1, Math.min(totalPages, parsed)); const target = Math.max(1, Math.min(totalPages, parsed));
onPageChange(target); onPageChange(target);
setIsJumpOpen(false); setIsJumpOpen(false);
} else if (e.key === "Escape") { } else if (e.key === "Escape") {
setIsJumpOpen(false); setIsJumpOpen(false);
} else if (!/^\d$/.test(e.key)) {
e.preventDefault();
} }
}; };

View File

@@ -89,7 +89,7 @@ export function SettingsDownloadSources() {
try { try {
await window.electron.removeDownloadSource(false, downloadSource.id); await window.electron.removeDownloadSource(false, downloadSource.id);
const sources = await window.electron.getDownloadSources(); const sources = await window.electron.getDownloadSources();
setDownloadSources(sources as DownloadSource[]); setDownloadSources(sources);
showSuccessToast(t("removed_download_source")); showSuccessToast(t("removed_download_source"));
} catch (error) { } catch (error) {
logger.error("Failed to remove download source:", error); logger.error("Failed to remove download source:", error);
@@ -104,7 +104,7 @@ export function SettingsDownloadSources() {
try { try {
await window.electron.removeDownloadSource(true); await window.electron.removeDownloadSource(true);
const sources = await window.electron.getDownloadSources(); const sources = await window.electron.getDownloadSources();
setDownloadSources(sources as DownloadSource[]); setDownloadSources(sources);
showSuccessToast(t("removed_all_download_sources")); showSuccessToast(t("removed_all_download_sources"));
} catch (error) { } catch (error) {
logger.error("Failed to remove all download sources:", error); logger.error("Failed to remove all download sources:", error);
@@ -117,7 +117,7 @@ export function SettingsDownloadSources() {
const handleAddDownloadSource = async () => { const handleAddDownloadSource = async () => {
try { try {
const sources = await window.electron.getDownloadSources(); const sources = await window.electron.getDownloadSources();
setDownloadSources(sources as DownloadSource[]); setDownloadSources(sources);
} catch (error) { } catch (error) {
logger.error("Failed to refresh download sources:", error); logger.error("Failed to refresh download sources:", error);
} }
@@ -128,7 +128,7 @@ export function SettingsDownloadSources() {
try { try {
await window.electron.syncDownloadSources(); await window.electron.syncDownloadSources();
const sources = await window.electron.getDownloadSources(); const sources = await window.electron.getDownloadSources();
setDownloadSources(sources as DownloadSource[]); setDownloadSources(sources);
showSuccessToast(t("download_sources_synced_successfully")); showSuccessToast(t("download_sources_synced_successfully"));
} finally { } finally {