mirror of
https://github.com/hydralauncher/hydra.git
synced 2026-01-11 22:06:17 +00:00
Compare commits
116 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9b8a0af8e9 | ||
|
|
31319b0811 | ||
|
|
9976812bf7 | ||
|
|
6b4d422b65 | ||
|
|
ec9db3a48f | ||
|
|
72cde368f5 | ||
|
|
bd77aee3c2 | ||
|
|
dafdda1bc7 | ||
|
|
6c6a238c5f | ||
|
|
0df18022ed | ||
|
|
6626368613 | ||
|
|
cff728bbbe | ||
|
|
3961b53d0e | ||
|
|
3f08e87eda | ||
|
|
b84c34a69c | ||
|
|
c2e1a092c9 | ||
|
|
fe03894054 | ||
|
|
893695eda0 | ||
|
|
28727600fe | ||
|
|
d892ed4f34 | ||
|
|
ea618febe2 | ||
|
|
358eac061f | ||
|
|
33f9e5729b | ||
|
|
2a7feba5dc | ||
|
|
1390f99895 | ||
|
|
daf8ee6ba2 | ||
|
|
652fe69141 | ||
|
|
61b2710219 | ||
|
|
0e67e21223 | ||
|
|
f12784b6e7 | ||
|
|
8795359c0b | ||
|
|
bcc2127a6e | ||
|
|
766c5760d7 | ||
|
|
3c8db34456 | ||
|
|
433ebf55ac | ||
|
|
b68bed97dd | ||
|
|
2a8600a841 | ||
|
|
3fd48eb784 | ||
|
|
aa19241b2b | ||
|
|
a16a75ff39 | ||
|
|
e4d97e9775 | ||
|
|
5688aaf0e8 | ||
|
|
ad5eb22553 | ||
|
|
1c3a5f4800 | ||
|
|
d8a7275512 | ||
|
|
489dd710fa | ||
|
|
0db93c7dad | ||
|
|
d9fe3c709e | ||
|
|
03a3d734f1 | ||
|
|
106d66f1a2 | ||
|
|
3ef6472825 | ||
|
|
df77d754ae | ||
|
|
6a8da74bcf | ||
|
|
1aa3bb1ad2 | ||
|
|
fdcf244f4e | ||
|
|
d8a4eaaa66 | ||
|
|
1c71c20b48 | ||
|
|
aefc5eca49 | ||
|
|
5f1b83a0d9 | ||
|
|
85efc23c25 | ||
|
|
b344a1850a | ||
|
|
497f5e7742 | ||
|
|
199b0d5b19 | ||
|
|
2554dc4c69 | ||
|
|
c4c401e054 | ||
|
|
864ff0070f | ||
|
|
da8c40d5dc | ||
|
|
a32fdf3385 | ||
|
|
c9c1750afb | ||
|
|
857063d2c7 | ||
|
|
ed699a8dee | ||
|
|
a9ec1a62bf | ||
|
|
778e921594 | ||
|
|
206886c091 | ||
|
|
af2896efc3 | ||
|
|
c7735362e0 | ||
|
|
886e176b08 | ||
|
|
f522a7c9ef | ||
|
|
69f4ce821f | ||
|
|
8bfd6e5547 | ||
|
|
1cbde684e7 | ||
|
|
e5f7e9addc | ||
|
|
6832c11d06 | ||
|
|
740cc2ec7c | ||
|
|
e12c6daa16 | ||
|
|
2ee8a64945 | ||
|
|
a54edd8b4a | ||
|
|
f7b6a91621 | ||
|
|
96dbfe8593 | ||
|
|
8513f83169 | ||
|
|
b116e29dc0 | ||
|
|
52b291fb24 | ||
|
|
dddc5bfd96 | ||
|
|
282bb45869 | ||
|
|
c982ebe33a | ||
|
|
08935ceabd | ||
|
|
0e7a0d1afa | ||
|
|
38dc8bdea5 | ||
|
|
c33aa3c874 | ||
|
|
afa34c74d7 | ||
|
|
cda9b8fb42 | ||
|
|
026abd7909 | ||
|
|
bc31a28eee | ||
|
|
f1624441b4 | ||
|
|
358abde963 | ||
|
|
c65fc15d99 | ||
|
|
b32226273e | ||
|
|
2e7a2a05a5 | ||
|
|
ab70ff10be | ||
|
|
6a429f9f39 | ||
|
|
b03c69b185 | ||
|
|
3fe77f4961 | ||
|
|
79be151de0 | ||
|
|
e804b92f30 | ||
|
|
cf31b36961 | ||
|
|
3156baf78e |
@@ -1,2 +1,4 @@
|
||||
MAIN_VITE_API_URL=API_URL
|
||||
MAIN_VITE_AUTH_URL=AUTH_URL
|
||||
RENDERER_VITE_REAL_DEBRID_REFERRAL_ID=
|
||||
RENDERER_VITE_TORBOX_REFERRAL_CODE=
|
||||
|
||||
8
.github/workflows/build.yml
vendored
8
.github/workflows/build.yml
vendored
@@ -47,12 +47,13 @@ jobs:
|
||||
MAIN_VITE_API_URL: ${{ vars.MAIN_VITE_STAGING_API_URL }}
|
||||
MAIN_VITE_AUTH_URL: ${{ vars.MAIN_VITE_STAGING_AUTH_URL }}
|
||||
MAIN_VITE_CHECKOUT_URL: ${{ vars.MAIN_VITE_STAGING_CHECKOUT_URL }}
|
||||
RENDERER_VITE_INTERCOM_APP_ID: ${{ vars.RENDERER_VITE_INTERCOM_APP_ID }}
|
||||
RENDERER_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.EXTERNAL_RESOURCES_URL }}
|
||||
MAIN_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.EXTERNAL_RESOURCES_URL }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
RENDERER_VITE_SENTRY_DSN: ${{ vars.SENTRY_DSN }}
|
||||
RENDERER_VITE_REAL_DEBRID_REFERRAL_ID: ${{ vars.RENDERER_VITE_REAL_DEBRID_REFERRAL_ID }}
|
||||
RENDERER_VITE_TORBOX_REFERRAL_CODE: ${{ vars.RENDERER_VITE_TORBOX_REFERRAL_CODE }}
|
||||
|
||||
- name: Build Windows
|
||||
if: matrix.os == 'windows-latest'
|
||||
@@ -61,14 +62,15 @@ jobs:
|
||||
MAIN_VITE_API_URL: ${{ vars.MAIN_VITE_STAGING_API_URL }}
|
||||
MAIN_VITE_AUTH_URL: ${{ vars.MAIN_VITE_STAGING_AUTH_URL }}
|
||||
MAIN_VITE_CHECKOUT_URL: ${{ vars.MAIN_VITE_STAGING_CHECKOUT_URL }}
|
||||
RENDERER_VITE_INTERCOM_APP_ID: ${{ vars.RENDERER_VITE_INTERCOM_APP_ID }}
|
||||
RENDERER_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.EXTERNAL_RESOURCES_URL }}
|
||||
MAIN_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.EXTERNAL_RESOURCES_URL }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
RENDERER_VITE_SENTRY_DSN: ${{ vars.SENTRY_DSN }}
|
||||
RENDERER_VITE_REAL_DEBRID_REFERRAL_ID: ${{ vars.RENDERER_VITE_REAL_DEBRID_REFERRAL_ID }}
|
||||
RENDERER_VITE_TORBOX_REFERRAL_CODE: ${{ vars.RENDERER_VITE_TORBOX_REFERRAL_CODE }}
|
||||
|
||||
- name: Test Upload build
|
||||
- name: Upload build
|
||||
env:
|
||||
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
|
||||
S3_ENDPOINT: ${{ secrets.S3_ENDPOINT }}
|
||||
|
||||
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@@ -49,13 +49,13 @@ jobs:
|
||||
MAIN_VITE_API_URL: ${{ vars.MAIN_VITE_API_URL }}
|
||||
MAIN_VITE_AUTH_URL: ${{ vars.MAIN_VITE_AUTH_URL }}
|
||||
MAIN_VITE_CHECKOUT_URL: ${{ vars.MAIN_VITE_CHECKOUT_URL }}
|
||||
MAIN_VITE_ANALYTICS_API_URL: ${{ vars.MAIN_VITE_ANALYTICS_API_URL }}
|
||||
RENDERER_VITE_INTERCOM_APP_ID: ${{ vars.RENDERER_VITE_INTERCOM_APP_ID }}
|
||||
RENDERER_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.EXTERNAL_RESOURCES_URL }}
|
||||
MAIN_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.EXTERNAL_RESOURCES_URL }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
RENDERER_VITE_SENTRY_DSN: ${{ vars.SENTRY_DSN }}
|
||||
RENDERER_VITE_REAL_DEBRID_REFERRAL_ID: ${{ vars.RENDERER_VITE_REAL_DEBRID_REFERRAL_ID }}
|
||||
RENDERER_VITE_TORBOX_REFERRAL_CODE: ${{ vars.RENDERER_VITE_TORBOX_REFERRAL_CODE }}
|
||||
|
||||
- name: Build Windows
|
||||
if: matrix.os == 'windows-latest'
|
||||
@@ -64,13 +64,13 @@ jobs:
|
||||
MAIN_VITE_API_URL: ${{ vars.MAIN_VITE_API_URL }}
|
||||
MAIN_VITE_AUTH_URL: ${{ vars.MAIN_VITE_AUTH_URL }}
|
||||
MAIN_VITE_CHECKOUT_URL: ${{ vars.MAIN_VITE_CHECKOUT_URL }}
|
||||
MAIN_VITE_ANALYTICS_API_URL: ${{ vars.MAIN_VITE_ANALYTICS_API_URL }}
|
||||
RENDERER_VITE_INTERCOM_APP_ID: ${{ vars.RENDERER_VITE_INTERCOM_APP_ID }}
|
||||
RENDERER_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.EXTERNAL_RESOURCES_URL }}
|
||||
MAIN_VITE_EXTERNAL_RESOURCES_URL: ${{ vars.EXTERNAL_RESOURCES_URL }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
RENDERER_VITE_SENTRY_DSN: ${{ vars.SENTRY_DSN }}
|
||||
RENDERER_VITE_REAL_DEBRID_REFERRAL_ID: ${{ vars.RENDERER_VITE_REAL_DEBRID_REFERRAL_ID }}
|
||||
RENDERER_VITE_TORBOX_REFERRAL_CODE: ${{ vars.RENDERER_VITE_TORBOX_REFERRAL_CODE }}
|
||||
|
||||
- name: Create artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
|
||||
13
.github/workflows/trigger-lp.yml
vendored
Normal file
13
.github/workflows/trigger-lp.yml
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
name: Trigger Landing Page Build
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Trigger Landing Page build
|
||||
run: curl --location --request POST '${{ secrets.LP_TRIGGER_DEPLOY_URL }}'
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "hydralauncher",
|
||||
"version": "3.2.2",
|
||||
"version": "3.3.1",
|
||||
"description": "Hydra",
|
||||
"main": "./out/main/index.js",
|
||||
"author": "Los Broxas",
|
||||
@@ -44,6 +44,7 @@
|
||||
"@sentry/vite-plugin": "^2.22.7",
|
||||
"auto-launch": "^5.0.6",
|
||||
"axios": "^1.7.9",
|
||||
"axios-cookiejar-support": "^5.0.5",
|
||||
"better-sqlite3": "^11.7.0",
|
||||
"classic-level": "^2.0.0",
|
||||
"classnames": "^2.5.1",
|
||||
@@ -71,9 +72,11 @@
|
||||
"react-loading-skeleton": "^3.4.0",
|
||||
"react-redux": "^9.1.1",
|
||||
"react-router-dom": "^6.22.3",
|
||||
"react-tooltip": "^5.28.0",
|
||||
"sound-play": "^1.1.0",
|
||||
"sudo-prompt": "^9.2.1",
|
||||
"tar": "^7.4.3",
|
||||
"tough-cookie": "^5.1.1",
|
||||
"user-agents": "^1.1.387",
|
||||
"yaml": "^2.6.1",
|
||||
"yup": "^1.5.0",
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -255,6 +255,12 @@
|
||||
"download_sources_description": "سيقوم Hydra بجلب روابط التنزيل من هذه المصادر. يجب أن يكون عنوان URL للمصدر رابطًا مباشرًا لملف .json يحتوي على روابط التنزيل.",
|
||||
"validate_download_source": "تحقق",
|
||||
"remove_download_source": "إزالة",
|
||||
"removed_download_sources": "تمت إزالة مصادر التنزيل",
|
||||
"cancel_button_confirmation_delete_all_sources": "لا",
|
||||
"confirm_button_confirmation_delete_all_sources": "نعم، احذف كل شيء",
|
||||
"description_confirmation_delete_all_sources": "سوف تقوم بحذف جميع مصادر التنزيل",
|
||||
"title_confirmation_delete_all_sources": "احذف جميع مصادر التنزيل",
|
||||
"button_delete_all_sources": "قم بإزالة جميع مصادر التنزيل",
|
||||
"add_download_source": "إضافة مصدر",
|
||||
"download_count_zero": "لا توجد خيارات تنزيل",
|
||||
"download_count_one": "{{countFormatted}} خيار تنزيل",
|
||||
|
||||
@@ -107,6 +107,12 @@
|
||||
"notifications": "Апавяшчэнні",
|
||||
"enable_download_notifications": "Па сканчэнні сцягванні",
|
||||
"enable_repack_list_notifications": "Пры даданні новага рэпака",
|
||||
"cancel_button_confirmation_delete_all_sources": "Няма",
|
||||
"confirm_button_confirmation_delete_all_sources": "Так,выдаліць усё",
|
||||
"description_confirmation_delete_all_sources": "Вы выдаліце ўсе крыніцы сцягвання",
|
||||
"title_confirmation_delete_all_sources": "Выдаліць усе крыніцы сцягвання",
|
||||
"removed_download_sources": "Крыніцы сцягвання выдалены",
|
||||
"button_delete_all_sources": "Премахнете всички източници на изтегляне",
|
||||
"behavior": "Паводзіны",
|
||||
"quit_app_instead_hiding": "Закрываць праграму замест таго, каб хаваць яе ў трэй",
|
||||
"launch_with_system": "Запускаць праграму пры запуску сыстэмы"
|
||||
|
||||
@@ -253,6 +253,12 @@
|
||||
"download_source_errored": "Сгрешен",
|
||||
"sync_download_sources": "Синхронизирай източниците",
|
||||
"removed_download_source": "Източника за сваляне е премахнат",
|
||||
"cancel_button_confirmation_delete_all_sources": "не",
|
||||
"confirm_button_confirmation_delete_all_sources": "Да, удалить все",
|
||||
"description_confirmation_delete_all_sources": "Вы удалите все источники загрузки",
|
||||
"title_confirmation_delete_all_sources": "Удалить все источники загрузки",
|
||||
"removed_download_sources": "Шрифты удалены",
|
||||
"button_delete_all_sources": "Удалить все источники загрузки",
|
||||
"added_download_source": "Добавен източник за сваляне",
|
||||
"download_sources_synced": "Всички източници за сваляне са синхронизирани",
|
||||
"insert_valid_json_url": "Добавете ваиден JSON линк",
|
||||
|
||||
@@ -175,6 +175,12 @@
|
||||
"download_sources_description": "Hydra buscarà els enllaços de descàrrega d'aquestes fonts. L'URL d'origen ha de ser un enllaç directe a un fitxer .json que contingui els enllaços de descàrrega.",
|
||||
"validate_download_source": "Valida",
|
||||
"remove_download_source": "Elimina",
|
||||
"cancel_button_confirmation_delete_all_sources": "No",
|
||||
"confirm_button_confirmation_delete_all_sources": "Sí, esborra-ho tot",
|
||||
"description_confirmation_delete_all_sources": "Eliminareu totes les fonts de descàrrega",
|
||||
"title_confirmation_delete_all_sources": "Suprimeix totes les fonts de baixada",
|
||||
"removed_download_sources": "S'han eliminat les fonts de descàrrega",
|
||||
"button_delete_all_sources": "Elimina totes les fonts de baixada",
|
||||
"add_download_source": "Afegeix font",
|
||||
"download_count_zero": "No hi ha baixades a la llista",
|
||||
"download_count_one": "{{countFormatted}} a la llista de baixades",
|
||||
|
||||
@@ -237,6 +237,12 @@
|
||||
"download_source_errored": "Chyba",
|
||||
"sync_download_sources": "Synchronizovat zdroje",
|
||||
"removed_download_source": "Zdroj odebrán",
|
||||
"cancel_button_confirmation_delete_all_sources": "Žádný",
|
||||
"confirm_button_confirmation_delete_all_sources": "Ano, smazat vše",
|
||||
"description_confirmation_delete_all_sources": "Smažete všechny zdroje stahování",
|
||||
"title_confirmation_delete_all_sources": "Odstraňte všechny zdroje stahování",
|
||||
"removed_download_sources": "Zdroje stahování byly odstraněny",
|
||||
"button_delete_all_sources": "Odstraňte všechny zdroje stahování",
|
||||
"added_download_source": "Zdroj přidán",
|
||||
"download_sources_synced": "Všechny zdroje jsou synchronizovány",
|
||||
"insert_valid_json_url": "Zadej platnou JSON adresu",
|
||||
|
||||
@@ -201,6 +201,12 @@
|
||||
"download_source_errored": "Fejlede",
|
||||
"sync_download_sources": "Synkronisér kilder",
|
||||
"removed_download_source": "Download kilde fjernet",
|
||||
"cancel_button_confirmation_delete_all_sources": "Ingen",
|
||||
"confirm_button_confirmation_delete_all_sources": "Ja, slet alt",
|
||||
"description_confirmation_delete_all_sources": "Du vil slette alle downloadkilder",
|
||||
"title_confirmation_delete_all_sources": "Slet alle downloadkilder",
|
||||
"removed_download_sources": "Download kilder fjernet",
|
||||
"button_delete_all_sources": "Fjern alle downloadkilder",
|
||||
"added_download_source": "Tilføjede download kilde",
|
||||
"download_sources_synced": "Alle download kilder er synkroniserede",
|
||||
"insert_valid_json_url": "Indsæt en gyldig JSON url",
|
||||
|
||||
@@ -185,6 +185,12 @@
|
||||
"download_source_errored": "Fehlgeschlagen",
|
||||
"sync_download_sources": "Quellen synchronisieren",
|
||||
"removed_download_source": "Download-Quelle entfernt",
|
||||
"cancel_button_confirmation_delete_all_sources": "Nein",
|
||||
"confirm_button_confirmation_delete_all_sources": "Ja, alles löschen",
|
||||
"description_confirmation_delete_all_sources": "Du löschen alle Downloadquellen",
|
||||
"title_confirmation_delete_all_sources": "Löschen du alle Downloadquellen",
|
||||
"removed_download_sources": "Download-Quellen entfernt",
|
||||
"button_delete_all_sources": "Entfernen Sie alle Downloadquellen",
|
||||
"added_download_source": "Download-Quelle hinzugefügt",
|
||||
"download_sources_synced": "Alle Download-Quellen sind synchronisiert",
|
||||
"insert_valid_json_url": "Füge eine gültige JSON URL ein",
|
||||
|
||||
@@ -178,6 +178,8 @@
|
||||
"manage_files_description": "Manage which files will be backed up and restored",
|
||||
"select_folder": "Select folder",
|
||||
"backup_from": "Backup from {{date}}",
|
||||
"automatic_backup_from": "Automatic backup from {{date}}",
|
||||
"enable_automatic_cloud_sync": "Enable automatic cloud sync",
|
||||
"custom_backup_location_set": "Custom backup location set",
|
||||
"no_directory_selected": "No directory selected",
|
||||
"no_write_permission": "Cannot download into this directory. Click here to learn more.",
|
||||
@@ -265,6 +267,12 @@
|
||||
"download_source_errored": "Errored",
|
||||
"sync_download_sources": "Sync sources",
|
||||
"removed_download_source": "Download source removed",
|
||||
"removed_download_sources": "Download sources removed",
|
||||
"cancel_button_confirmation_delete_all_sources": "No",
|
||||
"confirm_button_confirmation_delete_all_sources": "Yes, delete everything",
|
||||
"title_confirmation_delete_all_sources": "Delete all download sources",
|
||||
"description_confirmation_delete_all_sources": "You will delete all download sources",
|
||||
"button_delete_all_sources": "Remove all",
|
||||
"added_download_source": "Added download source",
|
||||
"download_sources_synced": "All download sources are synced",
|
||||
"insert_valid_json_url": "Insert a valid JSON url",
|
||||
@@ -327,12 +335,16 @@
|
||||
"enable_torbox": "Enable Torbox",
|
||||
"torbox_description": "TorBox is your premium seedbox service rivaling even the best servers on the market.",
|
||||
"torbox_account_linked": "TorBox account linked",
|
||||
"create_real_debrid_account": "Click here if you don't have a Real-Debrid account yet",
|
||||
"create_torbox_account": "Click here if you don't have a TorBox account yet",
|
||||
"real_debrid_account_linked": "Real-Debrid account linked",
|
||||
"name_min_length": "Theme name must be at least 3 characters long",
|
||||
"import_theme": "Import theme",
|
||||
"import_theme_description": "You will import {{theme}} from the theme store",
|
||||
"error_importing_theme": "Error importing theme",
|
||||
"theme_imported": "Theme imported successfully"
|
||||
"theme_imported": "Theme imported successfully",
|
||||
"enable_friend_request_notifications": "When a friend request is received",
|
||||
"enable_auto_install": "Download updates automatically"
|
||||
},
|
||||
"notifications": {
|
||||
"download_complete": "Download complete",
|
||||
@@ -343,13 +355,17 @@
|
||||
"new_update_available": "Version {{version}} available",
|
||||
"restart_to_install_update": "Restart Hydra to install the update",
|
||||
"notification_achievement_unlocked_title": "Achievement unlocked for {{game}}",
|
||||
"notification_achievement_unlocked_body": "{{achievement}} and other {{count}} were unlocked"
|
||||
"notification_achievement_unlocked_body": "{{achievement}} and other {{count}} were unlocked",
|
||||
"new_friend_request_description": "You have received a new friend request",
|
||||
"new_friend_request_title": "New friend request"
|
||||
},
|
||||
"system_tray": {
|
||||
"open": "Open Hydra",
|
||||
"quit": "Quit"
|
||||
},
|
||||
"game_card": {
|
||||
"available_one": "Available",
|
||||
"available_other": "Available",
|
||||
"no_downloads": "No downloads available"
|
||||
},
|
||||
"binary_not_found_modal": {
|
||||
@@ -444,9 +460,6 @@
|
||||
"show_achievements_on_profile": "Show your achievements on your profile",
|
||||
"show_points_on_profile": "Show your earned points on your profile"
|
||||
},
|
||||
"badge": {
|
||||
"badge_description_theme_creator": "Awarded to those who created a custom theme"
|
||||
},
|
||||
"achievement": {
|
||||
"achievement_unlocked": "Achievement unlocked",
|
||||
"user_achievements": "{{displayName}}'s Achievements",
|
||||
|
||||
@@ -174,6 +174,8 @@
|
||||
"manage_files_description": "Gestiona los archivos que serán respaldados y restaurados",
|
||||
"select_folder": "Seleccionar carpeta",
|
||||
"backup_from": "Copia de seguridad de {{date}}",
|
||||
"automatic_backup_from": "Copia de seguridad automática de {{date}}",
|
||||
"enable_automatic_cloud_sync": "Habilitar sincronización automática en la nube",
|
||||
"custom_backup_location_set": "Se configuró la carpeta de copia de seguridad",
|
||||
"clear": "Limpiar",
|
||||
"no_directory_selected": "No se seleccionó un directorio",
|
||||
@@ -185,7 +187,13 @@
|
||||
"reset_achievements_description": "Esto reiniciará todos los logros de {{game}}",
|
||||
"reset_achievements_title": "¿Estás seguro?",
|
||||
"reset_achievements_success": "Logros reiniciados exitosamente",
|
||||
"reset_achievements_error": "Se produjo un error al reiniciar los logros"
|
||||
"reset_achievements_error": "Se produjo un error al reiniciar los logros",
|
||||
"download_error_gofile_quota_exceeded": "Has excedido la cuota mensual de Gofile. Por favor espera a que se reinicie la cuota.",
|
||||
"download_error_real_debrid_account_not_authorized": "Tu cuenta de Real-Debrid no está autorizada para nueva descargas. Por favor, revisa los ajustes de tu cuenta e intenta de nuevo.",
|
||||
"download_error_not_cached_in_real_debrid": "Esta descarga no está disponible en Real-Debrid y el estado de descarga del sondeo de Real-Debrid aún no está disponible.",
|
||||
"download_error_not_cached_in_torbox": "Esta descarga no está disponible en Torbox y el estado de descarga del sondeo aún no está disponible.",
|
||||
"game_added_to_favorites": "Juego añadido a favoritos",
|
||||
"game_removed_from_favorites": "Juego removido de favoritos"
|
||||
},
|
||||
"activation": {
|
||||
"title": "Activar Hydra",
|
||||
@@ -259,6 +267,12 @@
|
||||
"download_source_errored": "Error",
|
||||
"sync_download_sources": "Sincronizar fuentes",
|
||||
"removed_download_source": "Fuente de descarga eliminada",
|
||||
"cancel_button_confirmation_delete_all_sources": "No",
|
||||
"confirm_button_confirmation_delete_all_sources": "Sí, eliminar todo",
|
||||
"description_confirmation_delete_all_sources": "Eliminarás todas las fuentes de descarga",
|
||||
"title_confirmation_delete_all_sources": "Eliminar todas las fuentes de descarga",
|
||||
"removed_download_sources": "Fuentes de descarga eliminadas",
|
||||
"button_delete_all_sources": "Eliminar todas las fuentes de descarga",
|
||||
"added_download_source": "Fuente de descarga añadida",
|
||||
"download_sources_synced": "Todas las fuentes de descargas están actualizadas.",
|
||||
"insert_valid_json_url": "Introduce una URL JSON válida",
|
||||
@@ -290,14 +304,45 @@
|
||||
"no_email_account": "No has configurado un correo aún",
|
||||
"no_subscription": "Disfruta Hydra de la mejor manera",
|
||||
"no_users_blocked": "No tienes usuarios bloqueados",
|
||||
"notifications": "Notificaciones",
|
||||
"renew_subscription": "Renovar Hydra Cloud",
|
||||
"subscription_active_until": "Tu Hydra Cloud está activa hasta {{date}}",
|
||||
"subscription_expired_at": "Tú suscripción expiró el {{date}}",
|
||||
"subscription_renew_cancelled": "Está desactivada la renovación automática",
|
||||
"subscription_renews_on": "Tú suscripción se renueva el {{date}}",
|
||||
"update_email": "Actualizar correo",
|
||||
"update_password": "Actualizar contraseña"
|
||||
"update_password": "Actualizar contraseña",
|
||||
"appearance": "Apariencia",
|
||||
"become_subscriber": "Sé Hydra Cloud",
|
||||
"cancel": "Cancelar",
|
||||
"clear_themes": "Limpiar",
|
||||
"create_theme": "Crear",
|
||||
"create_theme_modal_description": "Crea un nuevo tema para personalizar la apariencia de Hydra",
|
||||
"create_theme_modal_title": "Crear tema personalizado",
|
||||
"delete_all_themes": "Eliminar todos los temas",
|
||||
"delete_all_themes_description": "Esto eliminará todos tus temas personalizados",
|
||||
"delete_theme": "Eliminar tema",
|
||||
"delete_theme_description": "Esto eliminará el tema {{theme}}",
|
||||
"edit_theme": "Editar tema",
|
||||
"editor_tab_code": "Código",
|
||||
"editor_tab_info": "Info",
|
||||
"editor_tab_save": "Guardar",
|
||||
"enable_torbox": "Habilitar Torbox",
|
||||
"error_importing_theme": "Error al importar el tema",
|
||||
"import_theme": "Importar tema",
|
||||
"import_theme_description": "Vas a importar el tema {{theme}} desde la tienda de temas",
|
||||
"insert_theme_name": "Introducí el nombre del tema",
|
||||
"name_min_length": "El tema tiene que tener 3 carácteres de largo mínimo",
|
||||
"no_themes": "Parece que no tenés ningún tema aún, pero no te preocupes, presiona acá para crear tu primer tema.",
|
||||
"real_debrid_account_linked": "Cuenta de Real-Debrid vinculada",
|
||||
"set_theme": "Establecer tema",
|
||||
"theme_imported": "Tema importado exitosamente",
|
||||
"theme_name": "Nombre",
|
||||
"torbox_account_linked": "Cuenta de TorBox vinculada",
|
||||
"torbox_description": "TorBox es tu servicio premium de seedbox que rivaliza incluso a los mejores servidores del mercado.",
|
||||
"unset_theme": "Desactivar tema",
|
||||
"web_store": "Tienda Web",
|
||||
"enable_friend_request_notifications": "Cuando se recibe una solicitud de amistad",
|
||||
"enable_auto_install": "Descargar actualizaciones automáticamente"
|
||||
},
|
||||
"notifications": {
|
||||
"download_complete": "Descarga completada",
|
||||
@@ -308,13 +353,17 @@
|
||||
"new_update_available": "Version {{version}} disponible",
|
||||
"restart_to_install_update": "Reinicia Hydra para instalar la actualización",
|
||||
"notification_achievement_unlocked_title": "Logro desbloqueado de {{game}}",
|
||||
"notification_achievement_unlocked_body": "{{achievement}} y otros {{count}} fueron desbloqueados"
|
||||
"notification_achievement_unlocked_body": "{{achievement}} y otros {{count}} fueron desbloqueados",
|
||||
"new_friend_request_title": "Nueva solicitud de amistad",
|
||||
"new_friend_request_description": "Has recibido una nueva solicitud de amistad"
|
||||
},
|
||||
"system_tray": {
|
||||
"open": "Abrir Hydra",
|
||||
"quit": "Salir"
|
||||
},
|
||||
"game_card": {
|
||||
"available_one": "Disponible",
|
||||
"available_other": "Disponibles",
|
||||
"no_downloads": "No hay descargas disponibles"
|
||||
},
|
||||
"binary_not_found_modal": {
|
||||
|
||||
@@ -236,6 +236,12 @@
|
||||
"download_source_errored": "Vigane",
|
||||
"sync_download_sources": "Sünkroniseeri allikad",
|
||||
"removed_download_source": "Allalaadimise allikas eemaldatud",
|
||||
"cancel_button_confirmation_delete_all_sources": "Ei",
|
||||
"confirm_button_confirmation_delete_all_sources": "Jah, kustuta kõik",
|
||||
"description_confirmation_delete_all_sources": "Kustutate kõik allalaadimisallikad",
|
||||
"title_confirmation_delete_all_sources": "Kustutage kõik allalaadimisallikad",
|
||||
"removed_download_sources": "Allalaadimise allikas eemaldati",
|
||||
"button_delete_all_sources": "Eemaldage kõik allalaadimisallikad",
|
||||
"added_download_source": "Allalaadimise allikas lisatud",
|
||||
"download_sources_synced": "Kõik allalaadimise allikad on sünkroniseeritud",
|
||||
"insert_valid_json_url": "Sisesta kehtiv JSON url",
|
||||
|
||||
@@ -111,6 +111,12 @@
|
||||
"launch_with_system": "زمانی که سیستم روشن میشود، هایدرا را باز کن",
|
||||
"general": "کلی",
|
||||
"behavior": "رفتار",
|
||||
"cancel_button_confirmation_delete_all_sources": "خیر",
|
||||
"confirm_button_confirmation_delete_all_sources": "ہاں، سب کچھ حذف کر دیں۔",
|
||||
"description_confirmation_delete_all_sources": "آپ ڈاؤن لوڈ کے تمام ذرائع کو حذف کر دیں گے۔",
|
||||
"title_confirmation_delete_all_sources": "تمام منابع دانلود را حذف کنید",
|
||||
"removed_download_sources": "منابع دانلود حذف شد",
|
||||
"button_delete_all_sources": "تمام منابع دانلود را حذف کنید",
|
||||
"enable_real_debrid": "فعالسازی Real-Debrid",
|
||||
"debrid_api_token_hint": "کلید API خود را از <ب0>اینجا</0> بگیرید.",
|
||||
"save_changes": "ذخیره تغییرات"
|
||||
|
||||
@@ -99,7 +99,13 @@
|
||||
"notifications": "Notifications",
|
||||
"enable_download_notifications": "Quand un téléchargement est terminé",
|
||||
"enable_repack_list_notifications": "Quand un nouveau repack est ajouté",
|
||||
"language": "Langue"
|
||||
"language": "Langue",
|
||||
"cancel_button_confirmation_delete_all_sources": "Non",
|
||||
"confirm_button_confirmation_delete_all_sources": "Oui, tout supprimer",
|
||||
"description_confirmation_delete_all_sources": "Vous supprimerez toutes les sources de téléchargement",
|
||||
"title_confirmation_delete_all_sources": "Supprimer toutes les sources de téléchargement",
|
||||
"removed_download_sources": "Sources de téléchargement supprimées",
|
||||
"button_delete_all_sources": "Supprimer toutes les sources de téléchargement"
|
||||
},
|
||||
"notifications": {
|
||||
"download_complete": "Téléchargement terminé",
|
||||
|
||||
@@ -106,6 +106,12 @@
|
||||
"change": "Frissítés",
|
||||
"notifications": "Értesítések",
|
||||
"enable_download_notifications": "Amikor egy letöltés befejeződik",
|
||||
"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"
|
||||
},
|
||||
"notifications": {
|
||||
|
||||
@@ -175,6 +175,12 @@
|
||||
"download_sources_description": "Hydra akan mencari link unduhan dari sini. URL harus menuju file .json dengan link unduhan.",
|
||||
"validate_download_source": "Validasi",
|
||||
"remove_download_source": "Hapus",
|
||||
"cancel_button_confirmation_delete_all_sources": "TIDAK",
|
||||
"confirm_button_confirmation_delete_all_sources": "Ya, hapus semuanya",
|
||||
"description_confirmation_delete_all_sources": "Anda akan menghapus semua sumber unduhan",
|
||||
"title_confirmation_delete_all_sources": "Hapus semua sumber unduhan",
|
||||
"removed_download_sources": "Font dihapus",
|
||||
"button_delete_all_sources": "Hapus semua sumber unduhan",
|
||||
"add_download_source": "Tambahkan sumber",
|
||||
"download_count_zero": "Tidak ada unduhan dalam daftar",
|
||||
"download_count_one": "{{countFormatted}} unduhan dalam daftar",
|
||||
|
||||
@@ -120,6 +120,12 @@
|
||||
"general": "Generale",
|
||||
"behavior": "Comportamento",
|
||||
"enable_real_debrid": "Abilita Real Debrid",
|
||||
"cancel_button_confirmation_delete_all_sources": "NO",
|
||||
"confirm_button_confirmation_delete_all_sources": "Sì, cancella tutto",
|
||||
"description_confirmation_delete_all_sources": "Eliminerai tutte le fonti di download",
|
||||
"title_confirmation_delete_all_sources": "Elimina tutte le fonti di download",
|
||||
"removed_download_sources": "Fonti di download rimosse",
|
||||
"button_delete_all_sources": "Rimuovi tutte le fonti di download",
|
||||
"debrid_api_token_hint": "Puoi trovare la tua chiave API <0>here</0>",
|
||||
"save_changes": "Salva modifiche"
|
||||
},
|
||||
|
||||
@@ -173,6 +173,12 @@
|
||||
"download_sources_description": "Hydra осы көздерден жүктеу сілтемелерін алады. URL-да жүктеу сілтемелері бар .json файлына тікелей сілтеме болуы керек.",
|
||||
"validate_download_source": "Тексеру",
|
||||
"remove_download_source": "Жою",
|
||||
"cancel_button_confirmation_delete_all_sources": "Жоқ",
|
||||
"confirm_button_confirmation_delete_all_sources": "Иә, бәрін жойыңыз",
|
||||
"description_confirmation_delete_all_sources": "Барлық жүктеу көздерін жоясыз",
|
||||
"title_confirmation_delete_all_sources": "Барлық жүктеу көздерін жойыңыз",
|
||||
"removed_download_sources": "Қаріптер жойылды",
|
||||
"button_delete_all_sources": "Барлық жүктеу көздерін жойыңыз",
|
||||
"add_download_source": "Жүктеу көзін қосу",
|
||||
"download_count_zero": "Жүктеулер тізімінде жоқ",
|
||||
"download_count_one": "{{countFormatted}} жүктеу тізімде",
|
||||
|
||||
@@ -111,6 +111,12 @@
|
||||
"launch_with_system": "컴퓨터가 시작되었을 때 Hydra 실행",
|
||||
"general": "일반",
|
||||
"behavior": "행동",
|
||||
"cancel_button_confirmation_delete_all_sources": "아니요",
|
||||
"confirm_button_confirmation_delete_all_sources": "네, 모두 삭제합니다",
|
||||
"description_confirmation_delete_all_sources": "모든 다운로드 소스를 삭제합니다.",
|
||||
"title_confirmation_delete_all_sources": "모든 다운로드 소스 삭제",
|
||||
"removed_download_sources": "제거된 글꼴",
|
||||
"button_delete_all_sources": "모든 다운로드 소스 제거",
|
||||
"enable_real_debrid": "Real-Debrid 활성화",
|
||||
"debrid_api_token_hint": "API 키를 <0>이곳</0>에서 얻으세요.",
|
||||
"save_changes": "변경 사항 저장"
|
||||
|
||||
@@ -201,6 +201,12 @@
|
||||
"download_source_errored": "Mislyktes",
|
||||
"sync_download_sources": "Synkroniser kilder",
|
||||
"removed_download_source": "Nedlastingskilde fjernet",
|
||||
"cancel_button_confirmation_delete_all_sources": "Ingen",
|
||||
"confirm_button_confirmation_delete_all_sources": "Ja, slett alt",
|
||||
"description_confirmation_delete_all_sources": "Du vil slette alle nedlastingskilder",
|
||||
"title_confirmation_delete_all_sources": "Slett alle nedlastingskilder",
|
||||
"removed_download_sources": "Fonter fjernet",
|
||||
"button_delete_all_sources": "Fjern alle nedlastingskilder",
|
||||
"added_download_source": "La til Nedlastingskilde",
|
||||
"download_sources_synced": "Alle nedlastingskilder er synkroniserte",
|
||||
"insert_valid_json_url": "Innsett en gyldig JSON url",
|
||||
|
||||
@@ -112,6 +112,12 @@
|
||||
"launch_with_system": "Start Hydra bij het opstarten van het systeem",
|
||||
"general": "Algemeen",
|
||||
"behavior": "Gedrag",
|
||||
"cancel_button_confirmation_delete_all_sources": "Nee",
|
||||
"confirm_button_confirmation_delete_all_sources": "Ja, verwijder alles",
|
||||
"description_confirmation_delete_all_sources": "Je verwijdert alle downloadbronnen",
|
||||
"title_confirmation_delete_all_sources": "Verwijder alle downloadbronnen",
|
||||
"removed_download_sources": "Downloadbronnen verwijderd",
|
||||
"button_delete_all_sources": "Verwijder alle downloadbronnen",
|
||||
"enable_real_debrid": "Enable Real-Debrid",
|
||||
"debrid_api_token_hint": "U kunt uw API-sleutel <0>hier</0> verkrijgen.",
|
||||
"save_changes": "Wijzigingen opslaan"
|
||||
|
||||
@@ -120,6 +120,11 @@
|
||||
"general": "Ogólne",
|
||||
"behavior": "Zachowania",
|
||||
"language": "Język",
|
||||
"cancel_button_confirmation_delete_all_sources": "NIE",
|
||||
"confirm_button_confirmation_delete_all_sources": "Tak, usuń wszystko",
|
||||
"description_confirmation_delete_all_sources": "Usuniesz wszystkie źródła pobierania",
|
||||
"title_confirmation_delete_all_sources": "Usuń wszystkie źródła pobierania",
|
||||
"button_delete_all_sources": "Usuń wszystkie źródła pobierania",
|
||||
"enable_real_debrid": "Włącz Real-Debrid",
|
||||
"debrid_api_token_hint": "Możesz uzyskać swój klucz API <0>tutaj</0>",
|
||||
"save_changes": "Zapisz zmiany"
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
},
|
||||
"header": {
|
||||
"search": "Buscar jogos",
|
||||
|
||||
"catalogue": "Catálogo",
|
||||
"downloads": "Downloads",
|
||||
"search_results": "Resultados da busca",
|
||||
@@ -165,6 +164,8 @@
|
||||
"max_number_of_artifacts_reached": "Número máximo de backups atingido para este jogo",
|
||||
"achievements_not_sync": "Veja como exibir suas conquistas no perfil",
|
||||
"backup_from": "Backup de {{date}}",
|
||||
"automatic_backup_from": "Backup automático de {{date}}",
|
||||
"enable_automatic_cloud_sync": "Habilitar sincronização automática na nuvem",
|
||||
"custom_backup_location_set": "Localização customizada selecionada",
|
||||
"select_folder": "Selecione a pasta",
|
||||
"manage_files_description": "Gerencie quais arquivos serão feitos backup",
|
||||
@@ -255,6 +256,12 @@
|
||||
"download_source_errored": "Falhou",
|
||||
"sync_download_sources": "Sincronizar",
|
||||
"removed_download_source": "Fonte removida",
|
||||
"removed_download_sources": "Fontes removidas",
|
||||
"cancel_button_confirmation_delete_all_sources": "Não",
|
||||
"confirm_button_confirmation_delete_all_sources": "Sim, excluir tudo",
|
||||
"title_confirmation_delete_all_sources": "Remover todas as fontes de download",
|
||||
"description_confirmation_delete_all_sources": "Você irá remover todas as fontes de download. Deseja prosseguir?",
|
||||
"button_delete_all_sources": "Remover todas",
|
||||
"added_download_source": "Fonte adicionada",
|
||||
"download_sources_synced": "As fontes foram sincronizadas",
|
||||
"insert_valid_json_url": "Insira a url de um JSON válido",
|
||||
@@ -315,12 +322,16 @@
|
||||
"enable_torbox": "Habilitar Torbox",
|
||||
"torbox_description": "TorBox é o seu serviço de seedbox premium que rivaliza até com os melhores servidores do mercado.",
|
||||
"torbox_account_linked": "Conta do TorBox vinculada",
|
||||
"create_real_debrid_account": "Clique aqui se você ainda não tem uma conta do Real-Debrid",
|
||||
"create_torbox_account": "Clique aqui se você ainda não tem uma conta do TorBox",
|
||||
"real_debrid_account_linked": "Conta Real-Debrid associada",
|
||||
"name_min_length": "O nome do tema deve ter pelo menos 3 caracteres",
|
||||
"import_theme": "Importar tema",
|
||||
"import_theme_description": "Você irá importar {{theme}} da loja de temas",
|
||||
"error_importing_theme": "Erro ao importar tema",
|
||||
"theme_imported": "Tema importado com sucesso"
|
||||
"theme_imported": "Tema importado com sucesso",
|
||||
"enable_friend_request_notifications": "Quando um pedido de amizade é recebido",
|
||||
"enable_auto_install": "Baixar atualizações automaticamente"
|
||||
},
|
||||
"notifications": {
|
||||
"download_complete": "Download concluído",
|
||||
@@ -329,13 +340,17 @@
|
||||
"repack_count_one": "{{count}} novo repack",
|
||||
"repack_count_other": "{{count}} novos repacks",
|
||||
"new_update_available": "Versão {{version}} disponível",
|
||||
"restart_to_install_update": "Reinicie o Hydra para instalar a nova versão"
|
||||
"restart_to_install_update": "Reinicie o Hydra para instalar a nova versão",
|
||||
"new_friend_request_title": "Novo pedido de amizade",
|
||||
"new_friend_request_description": "Você recebeu um novo pedido de amizade"
|
||||
},
|
||||
"system_tray": {
|
||||
"open": "Abrir Hydra",
|
||||
"quit": "Fechar"
|
||||
},
|
||||
"game_card": {
|
||||
"available_one": "Disponível",
|
||||
"available_other": "Disponíveis",
|
||||
"no_downloads": "Sem downloads disponíveis"
|
||||
},
|
||||
"binary_not_found_modal": {
|
||||
@@ -440,9 +455,6 @@
|
||||
"show_achievements_on_profile": "Exiba suas conquistas no perfil",
|
||||
"show_points_on_profile": "Exiba seus pontos ganhos no perfil"
|
||||
},
|
||||
"badge": {
|
||||
"badge_description_theme_creator": "Concedido àqueles que criaram um tema customizado"
|
||||
},
|
||||
"achievement": {
|
||||
"achievement_unlocked": "Conquista desbloqueada",
|
||||
"your_achievements": "Suas Conquistas",
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
},
|
||||
"header": {
|
||||
"search": "Procurar jogos",
|
||||
|
||||
"catalogue": "Catálogo",
|
||||
"downloads": "Transferências",
|
||||
"search_results": "Resultados da pesquisa",
|
||||
@@ -103,7 +102,7 @@
|
||||
"open_download_location": "Ver ficheiros transferidos",
|
||||
"create_shortcut": "Criar atalho no ambiente de trabalho",
|
||||
"remove_files": "Remover ficheiros",
|
||||
"options": "Gerir",
|
||||
"options": "Opções",
|
||||
"remove_from_library_description": "Isto vai remover {{game}} da tua biblioteca",
|
||||
"remove_from_library_title": "Tens a certeza?",
|
||||
"executable_section_title": "Executável",
|
||||
@@ -159,7 +158,27 @@
|
||||
"no_download_option_info": "Sem informações disponíveis",
|
||||
"backup_deletion_failed": "Falha ao apagar o backup",
|
||||
"max_number_of_artifacts_reached": "Número máximo de backups atingido para este jogo",
|
||||
"achievements_not_sync": "As tuas conquistas não estão sincronizadas"
|
||||
"achievements_not_sync": "As tuas conquistas não estão sincronizadas",
|
||||
"backup_from": "Backup de {{date}}",
|
||||
"automatic_backup_from": "Backup automático de {{date}}",
|
||||
"enable_automatic_cloud_sync": "Ativar sincronização automática na nuvem",
|
||||
"custom_backup_location_set": "Localização customizada selecionada",
|
||||
"select_folder": "Selecione a pasta",
|
||||
"manage_files_description": "Gerencie quais arquivos serão feitos backup",
|
||||
"clear": "Limpar",
|
||||
"no_directory_selected": "Nenhum diretório selecionado",
|
||||
"reset_achievements": "Repor conquistas",
|
||||
"reset_achievements_description": "Isto irá apagar todas as conquistas de {{game}}",
|
||||
"reset_achievements_title": "Tem certeza?",
|
||||
"reset_achievements_success": "Conquistas repostas com sucesso",
|
||||
"reset_achievements_error": "Falha ao repor conquistas",
|
||||
"no_write_permission": "Não é possível descarregar neste diretório. Clique aqui para saber mais.",
|
||||
"download_error_gofile_quota_exceeded": "Você excedeu sua cota mensal do Gofile. Por favor, aguarde o reset da cota.",
|
||||
"download_error_real_debrid_account_not_authorized": "A sua conta do Real-Debrid não está autorizada a fazer novos downloads. Por favor, verifique a sua assinatura e tente novamente.",
|
||||
"download_error_not_cached_in_real_debrid": "Este download não está disponível no Real-Debrid e a verificação do status do download não está disponível.",
|
||||
"download_error_not_cached_in_torbox": "Este download não está disponível no Torbox e a verificação do status do download não está disponível.",
|
||||
"game_removed_from_favorites": "Jogo removido dos favoritos",
|
||||
"game_added_to_favorites": "Jogo adicionado aos favoritos"
|
||||
},
|
||||
"activation": {
|
||||
"title": "Ativação",
|
||||
@@ -192,7 +211,11 @@
|
||||
"queued": "Na fila",
|
||||
"no_downloads_title": "Nada por aqui…",
|
||||
"no_downloads_description": "Ainda não descarregaste nada pelo Hydra, mas nunca é tarde para começar.",
|
||||
"checking_files": "A verificar ficheiros…"
|
||||
"checking_files": "A verificar ficheiros…",
|
||||
"seeding": "A semear",
|
||||
"stop_seeding": "Parar de semear",
|
||||
"resume_seeding": "Semear",
|
||||
"options": "Opções"
|
||||
},
|
||||
"settings": {
|
||||
"downloads_path": "Local das transferências",
|
||||
@@ -232,6 +255,12 @@
|
||||
"download_source_errored": "Falhou",
|
||||
"sync_download_sources": "Sincronizar",
|
||||
"removed_download_source": "Fonte removida",
|
||||
"cancel_button_confirmation_delete_all_sources": "Não",
|
||||
"confirm_button_confirmation_delete_all_sources": "Sim, apague tudo",
|
||||
"description_confirmation_delete_all_sources": "Irá remover todas as fontes de download",
|
||||
"title_confirmation_delete_all_sources": "Remover todas as fontes de download",
|
||||
"removed_download_sources": "Fontes de download removidas",
|
||||
"button_delete_all_sources": "Remover todas",
|
||||
"added_download_source": "Fonte adicionada",
|
||||
"download_sources_synced": "As fontes foram sincronizadas",
|
||||
"insert_valid_json_url": "Insere o URL de um JSON válido",
|
||||
@@ -250,7 +279,57 @@
|
||||
"must_be_valid_url": "A fonte deve ser um URL válido",
|
||||
"blocked_users": "Utilizadores bloqueados",
|
||||
"user_unblocked": "Utilizador desbloqueado",
|
||||
"enable_achievement_notifications": "Quando uma conquista é desbloqueada"
|
||||
"enable_achievement_notifications": "Quando uma conquista é desbloqueada",
|
||||
"enable_friend_request_notifications": "Quando um pedido de amizade é recebido",
|
||||
"launch_minimized": "Iniciar Hydra minimizado",
|
||||
"disable_nsfw_alert": "Desativar alertas NSFW",
|
||||
"seed_after_download_complete": "Semear após concluir o download",
|
||||
"show_hidden_achievement_description": "Mostrar descrição de conquistas ocultas antes de as desbloquear",
|
||||
"account": "Conta",
|
||||
"no_users_blocked": "Não tem utilizadores bloqueados",
|
||||
"subscription_active_until": "O teu Hydra Cloud está ativo até {{date}}",
|
||||
"manage_subscription": "Gerir subscrições",
|
||||
"update_email": "Atualizar email",
|
||||
"update_password": "Atualizar password",
|
||||
"current_email": "Email atual:",
|
||||
"no_email_account": "Ainda não adicionou nenhum email à sua conta",
|
||||
"account_data_updated_successfully": "Dados da conta atualizados com sucesso",
|
||||
"renew_subscription": "Renovar Hydra Cloud",
|
||||
"subscription_expired_at": "A sua subscrição expirou a {{date}}",
|
||||
"no_subscription": "Aproveite o Hydra da melhor forma possível",
|
||||
"become_subscriber": "Subscreva o Hydra Cloud",
|
||||
"subscription_renew_cancelled": "A renovação automática está desativada",
|
||||
"subscription_renews_on": "Sua assinatura renova dia {{date}}",
|
||||
"bill_sent_until": "A próxima cobrança será enviada até esse dia",
|
||||
"no_themes": "Parece que ainda não tem nenhum tema. Não se preocupe, clique aqui para criar a sua primeira obra de arte.",
|
||||
"editor_tab_code": "Código",
|
||||
"editor_tab_info": "Info",
|
||||
"editor_tab_save": "Guardar",
|
||||
"web_store": "Loja de temas",
|
||||
"clear_themes": "Limpar",
|
||||
"create_theme": "Criar",
|
||||
"create_theme_modal_title": "Criar tema customizado",
|
||||
"create_theme_modal_description": "Criar um novo tema para customizar a aparência do Hydra",
|
||||
"theme_name": "Nome",
|
||||
"insert_theme_name": "Insira o nome do tema",
|
||||
"set_theme": "Definir tema",
|
||||
"unset_theme": "Redefinir tema",
|
||||
"delete_theme": "Apagar tema",
|
||||
"edit_theme": "Editar tema",
|
||||
"delete_all_themes": "Apagar todos os temas",
|
||||
"delete_all_themes_description": "Isto irá apagar todos os seus temas",
|
||||
"delete_theme_description": "Isto irá apagar o tema {{theme}}",
|
||||
"cancel": "Cancelar",
|
||||
"appearance": "Aparência",
|
||||
"enable_torbox": "Ativar Torbox",
|
||||
"torbox_description": "TorBox é um serviço de seedbox premium sendo um dos melhores servidores do mercado.",
|
||||
"torbox_account_linked": "Conta do TorBox associada",
|
||||
"real_debrid_account_linked": "Conta Real-Debrid associada",
|
||||
"name_min_length": "O nome do tema deve ter pelo menos 3 caracteres",
|
||||
"import_theme": "Importar tema",
|
||||
"import_theme_description": "Irá importar {{theme}} da loja de temas",
|
||||
"error_importing_theme": "Erro ao importar tema",
|
||||
"theme_imported": "Tema importado com sucesso"
|
||||
},
|
||||
"notifications": {
|
||||
"download_complete": "Transferência concluída",
|
||||
@@ -259,13 +338,17 @@
|
||||
"repack_count_one": "{{count}} novo repack",
|
||||
"repack_count_other": "{{count}} novos repacks",
|
||||
"new_update_available": "Versão {{version}} disponível",
|
||||
"restart_to_install_update": "Reinicia o Hydra para instalar a nova versão"
|
||||
"restart_to_install_update": "Reinicia o Hydra para instalar a nova versão",
|
||||
"new_friend_request_title": "Novo pedido de amizade",
|
||||
"new_friend_request_description": "Recebeste um novo pedido de amizade"
|
||||
},
|
||||
"system_tray": {
|
||||
"open": "Abrir o Hydra",
|
||||
"quit": "Sair"
|
||||
},
|
||||
"game_card": {
|
||||
"available_one": "Disponível",
|
||||
"available_other": "Disponíveis",
|
||||
"no_downloads": "Sem downloads disponíveis"
|
||||
},
|
||||
"binary_not_found_modal": {
|
||||
@@ -275,7 +358,16 @@
|
||||
},
|
||||
"catalogue": {
|
||||
"next_page": "Página seguinte",
|
||||
"previous_page": "Página anterior"
|
||||
"previous_page": "Página anterior",
|
||||
"search": "Filtrar…",
|
||||
"developers": "Desenvolvedores",
|
||||
"genres": "Géneros",
|
||||
"tags": "Marcadores",
|
||||
"publishers": "Distribuidoras",
|
||||
"download_sources": "Fontes de download",
|
||||
"result_count": "{{resultCount}} resultados",
|
||||
"filter_count": "{{filterCount}} disponíveis",
|
||||
"clear_filters": "Limpar {{filterCount}} selecionados"
|
||||
},
|
||||
"modal": {
|
||||
"close": "Botão de fechar"
|
||||
@@ -352,7 +444,17 @@
|
||||
"profile_reported": "Perfil denunciado",
|
||||
"your_friend_code": "O teu código de amigo:",
|
||||
"upload_banner": "Fazer upload do banner",
|
||||
"uploading_banner": "A fazer upload do banner…"
|
||||
"uploading_banner": "A fazer upload do banner…",
|
||||
"background_image_updated": "Imagem de fundo salva",
|
||||
"stats": "Estatísticas",
|
||||
"achievements": "conquistas",
|
||||
"games": "Jogos",
|
||||
"ranking_updated_weekly": "O ranking é atualizado semanalmente",
|
||||
"playing": "A jogar {{game}}",
|
||||
"achievements_unlocked": "Conquistas desbloqueadas",
|
||||
"earned_points": "Pontos ganhos",
|
||||
"show_achievements_on_profile": "Mostre as suas conquistas no perfil",
|
||||
"show_points_on_profile": "Mostre os seus pontos ganhos no perfil"
|
||||
},
|
||||
"achievement": {
|
||||
"achievement_unlocked": "Conquista desbloqueada",
|
||||
@@ -360,15 +462,25 @@
|
||||
"user_achievements": "Conquistas de {{displayName}}",
|
||||
"unlocked_at": "Desbloqueada em: {{date}}",
|
||||
"subscription_needed": "Precisas de uma subscrição Hydra Cloud para visualizar este conteúdo",
|
||||
"new_achievements_unlocked": "{{achievementCount}} novas conquistas de {{gameCount}} jogos"
|
||||
"new_achievements_unlocked": "{{achievementCount}} novas conquistas de {{gameCount}} jogos",
|
||||
"achievement_progress": "{{unlockedCount}}/{{totalCount}} conquistas",
|
||||
"achievements_unlocked_for_game": "Desbloqueadas {{achievementCount}} novas conquistas em {{gameTitle}}",
|
||||
"hidden_achievement_tooltip": "Esta é uma conquista oculta",
|
||||
"achievement_earn_points": "Ganhe {{points}} pontos com essa conquista",
|
||||
"earned_points": "Pontos ganhos:",
|
||||
"available_points": "Pontos disponíveis:",
|
||||
"how_to_earn_achievements_points": "Como desbloquear pontos nas conquistas?"
|
||||
},
|
||||
"hydra_cloud": {
|
||||
"subscription_tour_title": "Subscrição Hydra Cloud",
|
||||
"subscribe_now": "Subscreve agora",
|
||||
"cloud_achievements": "Gravação de conquistas na nuvem",
|
||||
"animated_profile_picture": "Fotos de perfil animadas",
|
||||
"premium_support": "Apoio Premium",
|
||||
"premium_support": "Suporte Premium",
|
||||
"show_and_compare_achievements": "Mostra e compara as tuas conquistas com as de outros utilizadores",
|
||||
"animated_profile_banner": "Banner animado no perfil"
|
||||
"animated_profile_banner": "Banner animado no perfil",
|
||||
"cloud_saving": "Progresso dos jogos na nuvem",
|
||||
"hydra_cloud_feature_found": "Descubriste uma funcionalidade Hydra Cloud!",
|
||||
"learn_more": "Saber mais"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,6 +127,12 @@
|
||||
"behavior": "Comportament",
|
||||
"language": "Limbă",
|
||||
"api_token": "Token API",
|
||||
"cancel_button_confirmation_delete_all_sources": "Nu",
|
||||
"confirm_button_confirmation_delete_all_sources": "Da, șterge totul",
|
||||
"description_confirmation_delete_all_sources": "Veți șterge toate sursele de descărcare",
|
||||
"title_confirmation_delete_all_sources": "Ștergeți toate sursele de descărcare",
|
||||
"removed_download_sources": "Sursele de descărcare au fost eliminate",
|
||||
"button_delete_all_sources": "Eliminați toate sursele de descărcare",
|
||||
"enable_real_debrid": "Activează Real-Debrid",
|
||||
"real_debrid_description": "Real-Debrid este un descărcător fără restricții care îți permite să descarci fișiere instantaneu și la cea mai bună viteză a internetului tău.",
|
||||
"debrid_invalid_token": "Token API invalid",
|
||||
|
||||
@@ -178,6 +178,8 @@
|
||||
"manage_files_description": "Управляйте файлами, которые будут сохраняться и восстанавливаться",
|
||||
"select_folder": "Выбрать папку",
|
||||
"backup_from": "Резервная копия от {{date}}",
|
||||
"automatic_backup_from": "Автоматическая резервная копия от {{date}}",
|
||||
"enable_automatic_cloud_sync": "Включить автоматическую синхронизацию в облаке",
|
||||
"custom_backup_location_set": "Установлено настраиваемое местоположение резервной копии",
|
||||
"no_directory_selected": "Не выбран каталог",
|
||||
"no_write_permission": "Невозможно загрузить в эту директорию. Нажмите здесь, чтобы узнать больше.",
|
||||
@@ -266,6 +268,12 @@
|
||||
"download_source_errored": "Ошибка",
|
||||
"sync_download_sources": "Обновить источники",
|
||||
"removed_download_source": "Источник загрузок удален",
|
||||
"cancel_button_confirmation_delete_all_sources": "Нет",
|
||||
"confirm_button_confirmation_delete_all_sources": "Да, удалить все",
|
||||
"description_confirmation_delete_all_sources": "Вы удалите все источники загрузки",
|
||||
"title_confirmation_delete_all_sources": "Удалить все источники загрузки",
|
||||
"removed_download_sources": "Шрифты удалены",
|
||||
"button_delete_all_sources": "Удалить все источники загрузки",
|
||||
"added_download_source": "Источник загрузок добавлен",
|
||||
"download_sources_synced": "Все источники загрузок синхронизированы",
|
||||
"insert_valid_json_url": "Вставьте действительный URL JSON-файла",
|
||||
@@ -330,7 +338,9 @@
|
||||
"import_theme": "Импортировать тему",
|
||||
"import_theme_description": "Вы импортируете {{theme}} из магазина тем",
|
||||
"error_importing_theme": "Ошибка при импорте темы",
|
||||
"theme_imported": "Тема успешно импортирована"
|
||||
"theme_imported": "Тема успешно импортирована",
|
||||
"enable_friend_request_notifications": "При получении запроса на добавление в друзья",
|
||||
"enable_auto_install": "Загружать обновления автоматически"
|
||||
},
|
||||
"notifications": {
|
||||
"download_complete": "Загрузка завершена",
|
||||
@@ -341,13 +351,17 @@
|
||||
"new_update_available": "Доступна новая версия {{version}}",
|
||||
"restart_to_install_update": "Перезапустите Hydra для установки обновления",
|
||||
"notification_achievement_unlocked_title": "Достижение разблокировано для {{game}}",
|
||||
"notification_achievement_unlocked_body": "были разблокированы {{achievement}} и другие {{count}}"
|
||||
"notification_achievement_unlocked_body": "были разблокированы {{achievement}} и другие {{count}}",
|
||||
"new_friend_request_title": "Новый запрос на добавление в друзья",
|
||||
"new_friend_request_description": "Вы получили новый запрос на добавление в друзья"
|
||||
},
|
||||
"system_tray": {
|
||||
"open": "Открыть Hydra",
|
||||
"quit": "Выйти"
|
||||
},
|
||||
"game_card": {
|
||||
"available_one": "Доступный",
|
||||
"available_other": "Доступный",
|
||||
"no_downloads": "Нет доступных источников"
|
||||
},
|
||||
"binary_not_found_modal": {
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
"catalogue": "Katalog",
|
||||
"downloads": "İndirilenler",
|
||||
"settings": "Ayarlar",
|
||||
"my_library": "Kütüphanem",
|
||||
"my_library": "Kütüphane",
|
||||
"downloading_metadata": "{{title}} (Meta verileri indiriliyor…)",
|
||||
"paused": "{{title}} (Durduruldu)",
|
||||
"downloading": "{{title}} ({{percentage}} - İndiriliyor…)",
|
||||
@@ -36,13 +36,13 @@
|
||||
"downloads": "İndirilenler",
|
||||
"search_results": "Arama sonuçları",
|
||||
"settings": "Ayarlar",
|
||||
"version_available_install": "Sürüm {{version}} mevcut. Yüklemek ve yeniden başlatmak için buraya tıklayın.",
|
||||
"version_available_download": "Sürüm {{version}} mevcut. İndirmek için buraya tıklayın."
|
||||
"version_available_install": "{{version}} sürümü mevcut. Yüklemek ve yeniden başlatmak için buraya tıklayın.",
|
||||
"version_available_download": "{{version}} sürümü mevcut. İndirmek için buraya tıklayın."
|
||||
},
|
||||
"bottom_panel": {
|
||||
"no_downloads_in_progress": "Devam eden indirme yok",
|
||||
"downloading_metadata": "{{title}} meta verileri indiriliyor…",
|
||||
"downloading": "{{title}} indiriliyor… ({{percentage}} tamamlandı) - Tamamlama: {{eta}} - Hız: {{speed}}",
|
||||
"downloading": "{{title}} indiriliyor… ({{percentage}} tamamlandı) - Tamamlanma: {{eta}} - Hız: {{speed}}",
|
||||
"calculating_eta": "{{title}} indiriliyor… ({{percentage}} tamamlandı) - Kalan süre hesaplanıyor…",
|
||||
"checking_files": "{{title}} dosyaları kontrol ediliyor… ({{percentage}} tamamlandı)"
|
||||
},
|
||||
@@ -116,14 +116,14 @@
|
||||
"clear": "Temizle",
|
||||
"remove_files": "Dosyaları kaldır",
|
||||
"remove_from_library_title": "Emin misiniz?",
|
||||
"remove_from_library_description": "Bu işlem {{game}} oyununu kütüphanenizden kaldıracaktır",
|
||||
"remove_from_library_description": "Bu işlem sonrasında {{game}} oyunu kütüphanenizden kaldıracaktır",
|
||||
"options": "Seçenekler",
|
||||
"executable_section_title": "Çalıştırılabilir dosya",
|
||||
"executable_section_description": "\"Oyna\" tıklandığında çalıştırılacak dosyanın yolu",
|
||||
"executable_section_description": "\"Oyna\" butonuna tıklandığında çalıştırılacak dosyanın yolu",
|
||||
"downloads_secion_title": "İndirmeler",
|
||||
"downloads_section_description": "Bu oyun için güncellemeleri veya diğer sürümleri kontrol edin",
|
||||
"danger_zone_section_title": "Tehlike bölgesi",
|
||||
"danger_zone_section_description": "Bu oyunu kütüphanenizden veya Hydra tarafından indirilen dosyaları kaldırın",
|
||||
"danger_zone_section_description": "Bu oyunu kütüphanenizden kaldırın veya Hydra tarafından indirilen dosyaları silin.",
|
||||
"download_in_progress": "İndirme devam ediyor",
|
||||
"download_paused": "İndirme durduruldu",
|
||||
"last_downloaded_option": "Son indirilen seçenek",
|
||||
@@ -141,8 +141,8 @@
|
||||
"executable_path_in_use": "\"{{game}}\" tarafından kullanılan çalıştırılabilir dosya",
|
||||
"warning": "Uyarı:",
|
||||
"hydra_needs_to_remain_open": "Bu indirmenin tamamlanması için Hydra açık kalmalıdır. Eğer Hydra kapanırsa, ilerleme kaydedilmez.",
|
||||
"achievements": "Başarılar",
|
||||
"achievements_count": "Başarılar {{unlockedCount}}/{{achievementsCount}}",
|
||||
"achievements": "Başarımlar",
|
||||
"achievements_count": "Başarımlar {{unlockedCount}}/{{achievementsCount}}",
|
||||
"cloud_save": "Bulut kaydı",
|
||||
"cloud_save_description": "İlerlemenizi buluta kaydedin ve herhangi bir cihazda oynamaya devam edin",
|
||||
"backups": "Yedekler",
|
||||
@@ -150,15 +150,15 @@
|
||||
"delete_backup": "Sil",
|
||||
"create_backup": "Yeni yedek oluştur",
|
||||
"last_backup_date": "{{date}} tarihindeki son yedek",
|
||||
"no_backup_preview": "Bu oyun için kayıtlı oyun bulunamadı",
|
||||
"no_backup_preview": "Bu oyun için bir kayıt dosyası bulunamadı",
|
||||
"restoring_backup": "Yedek geri yükleniyor ({{progress}} tamamlandı)…",
|
||||
"uploading_backup": "Yedek yükleniyor…",
|
||||
"no_backups": "Bu oyun için henüz bir yedek oluşturmadınız",
|
||||
"backup_uploaded": "Yedek yüklendi",
|
||||
"backup_deleted": "Yedek silindi",
|
||||
"backup_restored": "Yedek geri yüklendi",
|
||||
"see_all_achievements": "Tüm başarıları gör",
|
||||
"sign_in_to_see_achievements": "Başarıları görmek için giriş yapın",
|
||||
"see_all_achievements": "Tüm başarımları gör",
|
||||
"sign_in_to_see_achievements": "Başarımları görmek için oturum açın",
|
||||
"mapping_method_automatic": "Otomatik",
|
||||
"mapping_method_manual": "Manuel",
|
||||
"mapping_method_label": "Eşleme yöntemi",
|
||||
@@ -169,29 +169,37 @@
|
||||
"wine_prefix": "Wine Prefix",
|
||||
"wine_prefix_description": "Bu oyunu çalıştırmak için kullanılan Wine Prefix",
|
||||
"launch_options": "Başlatma Seçenekleri",
|
||||
"launch_options_description": "İleri düzey kullanıcılar, başlatma seçeneklerine değişiklikler girebilir (deneysel özellik)",
|
||||
"launch_options_description": "İleri düzey kullanıcılar, başlatma seçeneklerine parametreler girebilir (deneysel özellik)",
|
||||
"launch_options_placeholder": "Belirtilen bir parametre yok",
|
||||
"no_download_option_info": "Bilgi mevcut değil",
|
||||
"backup_deletion_failed": "Yedek silinemedi",
|
||||
"max_number_of_artifacts_reached": "Bu oyun için maksimum yedek sayısına ulaşıldı",
|
||||
"achievements_not_sync": "Başarılarınızı senkronize etmeyi öğrenin",
|
||||
"achievements_not_sync": "Başarımlarınızı senkronize etmeyi öğrenin",
|
||||
"manage_files_description": "Hangi dosyaların yedeklenip geri yükleneceğini yönetin",
|
||||
"select_folder": "Klasör seç",
|
||||
"backup_from": "{{date}} tarihinden yedek",
|
||||
"automatic_backup_from": "{{date}} tarihinden otomatik kayıt",
|
||||
"enable_automatic_cloud_sync": "Otomatik bulut kaydı senkronizasyonunu aktifleştir",
|
||||
"custom_backup_location_set": "Özel yedekleme konumu ayarlandı",
|
||||
"no_directory_selected": "Bir dizin seçilmedi",
|
||||
"no_write_permission": "Bu dizine indirme yapılamaz. Daha fazla bilgi için buraya tıklayın.",
|
||||
"reset_achievements": "Başarıları sıfırla",
|
||||
"reset_achievements_description": "Bu işlem {{game}} için tüm başarıları sıfırlar",
|
||||
"reset_achievements": "Başarımları sıfırla",
|
||||
"reset_achievements_description": "Bu işlem {{game}} için tüm başarımları sıfırlar",
|
||||
"reset_achievements_title": "Emin misiniz?",
|
||||
"reset_achievements_success": "Başarılar başarıyla sıfırlandı",
|
||||
"reset_achievements_error": "Başarılar sıfırlanamadı"
|
||||
"reset_achievements_success": "Başarımlar başarıyla sıfırlandı",
|
||||
"reset_achievements_error": "Başarımlar sıfırlanamadı",
|
||||
"download_error_gofile_quota_exceeded": "Gofile aylık kotanızı doldurdunuz. Kotanın yenilenmesini bekleyin.",
|
||||
"download_error_real_debrid_account_not_authorized": "Real-Debrid hesabınız yeni indirme işlemleri yapmak için yetkilendirilmemiş. Lütfen hesap ayarlarınızı kontrol edip tekrar deneyin.",
|
||||
"download_error_not_cached_in_real_debrid": "Bu indirme Real-Debrid üzerinde mevcut değil ve Real-Debrid'den indirme durumu henüz sorgulanamıyor.",
|
||||
"download_error_not_cached_in_torbox": "Bu indirme Torbox'ta mevcut değil ve Torbox'tan indirme durumu henüz sorgulanamıyor.",
|
||||
"game_removed_from_favorites": "Oyun favorilerden silindi",
|
||||
"game_added_to_favorites": "Oyun favorilere eklendi"
|
||||
},
|
||||
"activation": {
|
||||
"title": "Hydra'yı Aktive Et",
|
||||
"installation_id": "Kurulum Kimliği:",
|
||||
"enter_activation_code": "Aktivasyon kodunuzu girin",
|
||||
"message": "Bunu nereden soracağınızı bilmiyorsanız, bu sizin için olmamalı.",
|
||||
"message": "Bunu nasıl edineceğini bilmiyorsan, buna sahip olmamalısın.",
|
||||
"activate": "Aktive Et",
|
||||
"loading": "Yükleniyor…"
|
||||
},
|
||||
@@ -206,7 +214,7 @@
|
||||
"cancel": "İptal Et",
|
||||
"filter": "İndirilen oyunları filtrele",
|
||||
"remove": "Kaldır",
|
||||
"downloading_metadata": "Metadata indiriliyor…",
|
||||
"downloading_metadata": "Meta verileri indiriliyor…",
|
||||
"deleting": "Yükleyici siliniyor…",
|
||||
"delete": "Yükleyiciyi kaldır",
|
||||
"delete_modal_title": "Emin misiniz?",
|
||||
@@ -231,7 +239,7 @@
|
||||
"enable_download_notifications": "Bir indirme tamamlandığında",
|
||||
"enable_repack_list_notifications": "Yeni bir repack eklendiğinde",
|
||||
"real_debrid_api_token_label": "Real-Debrid API anahtarı",
|
||||
"quit_app_instead_hiding": "Hydra'yı kapatırken gizlemeyin",
|
||||
"quit_app_instead_hiding": "Hydra'yı kapatınca sistem tepsisine gitmesin",
|
||||
"launch_with_system": "Hydra'yı sistem başlatıldığında çalıştır",
|
||||
"general": "Genel",
|
||||
"behavior": "Davranış",
|
||||
@@ -250,6 +258,12 @@
|
||||
"validate_download_source": "Doğrula",
|
||||
"remove_download_source": "Kaldır",
|
||||
"add_download_source": "Kaynak ekle",
|
||||
"cancel_button_confirmation_delete_all_sources": "Hayır",
|
||||
"confirm_button_confirmation_delete_all_sources": "Evet, her şeyi sil",
|
||||
"description_confirmation_delete_all_sources": "Tüm indirme kaynaklarını sileceksiniz",
|
||||
"title_confirmation_delete_all_sources": "Tüm indirme kaynaklarını sil",
|
||||
"removed_download_sources": "Yazı tipleri kaldırıldı",
|
||||
"button_delete_all_sources": "Tüm indirme kaynaklarını kaldır",
|
||||
"download_count_zero": "İndirme seçeneği yok",
|
||||
"download_count_one": "{{countFormatted}} indirme seçeneği",
|
||||
"download_count_other": "{{countFormatted}} indirme seçeneği",
|
||||
@@ -277,11 +291,56 @@
|
||||
"must_be_valid_url": "Kaynak geçerli bir URL olmalıdır",
|
||||
"blocked_users": "Engellenen kullanıcılar",
|
||||
"user_unblocked": "Kullanıcının engeli kaldırıldı",
|
||||
"enable_achievement_notifications": "Bir başarı kilidi açıldığında",
|
||||
"enable_achievement_notifications": "Bir başarım kilidi açıldığında",
|
||||
"launch_minimized": "Hydra'yı küçültülmüş başlat",
|
||||
"disable_nsfw_alert": "NSFW uyarısını devre dışı bırak",
|
||||
"seed_after_download_complete": "İndirme tamamlandıktan sonra paylaş",
|
||||
"show_hidden_achievement_description": "Gizli başarı açıklamalarını kilitlenmeden önce göster"
|
||||
"show_hidden_achievement_description": "Gizli başarım açıklamalarını kilitlenmeden önce göster",
|
||||
"account": "Hesap",
|
||||
"no_users_blocked": "Hiçbir kullanıcıyı engellemediniz",
|
||||
"subscription_active_until": "Hydra Cloud'unuz {{date}} tarihine kadar aktif",
|
||||
"manage_subscription": "Aboneliği yönet",
|
||||
"update_email": "E-posta'yı güncelle",
|
||||
"update_password": "Şifreyi güncelle",
|
||||
"current_email": "Aktif e-posta'nız",
|
||||
"no_email_account": "Henüz ayarlanmış bir e-postanız yok",
|
||||
"account_data_updated_successfully": "Hesap bilgileri başarıyla güncellendi",
|
||||
"renew_subscription": "Hydra Cloud'u yenile",
|
||||
"subscription_expired_at": "Aboneliğiniz {{date}} tarihinde sona erdi",
|
||||
"no_subscription": "Hydra'yı en iyi şekilde deneyimleyin",
|
||||
"become_subscriber": "Hydra Cloud'lu ol",
|
||||
"subscription_renew_cancelled": "Otomatik yenileme devre dışı",
|
||||
"subscription_renews_on": "Aboneliğiniz {{date}} tarihinde yenilenecek",
|
||||
"bill_sent_until": "Bir sonraki faturanız bu tarihe kadar gönderilecek",
|
||||
"no_themes": "Henüz bir temanız yok gibi görünüyor, ama endişelenmeyin, ilk şaheserinizi oluşturmak için buraya tıklayın.",
|
||||
"editor_tab_code": "Kod",
|
||||
"editor_tab_info": "Bilgi",
|
||||
"editor_tab_save": "Kaydet",
|
||||
"web_store": "İnternet mağazası",
|
||||
"clear_themes": "Temizle",
|
||||
"create_theme": "Oluştur",
|
||||
"create_theme_modal_title": "Tema oluştur",
|
||||
"create_theme_modal_description": "Hydra'nın görünümünü özelleştirmek için yeni bir tema oluştur",
|
||||
"theme_name": "İsim",
|
||||
"insert_theme_name": "Tema ismini gir",
|
||||
"set_theme": "Temayı seç",
|
||||
"unset_theme": "Tema seçimini kaldır",
|
||||
"delete_theme": "Temayı sil",
|
||||
"edit_theme": "Temayı düzenle",
|
||||
"delete_all_themes": "Tüm temaları sil",
|
||||
"delete_all_themes_description": "Bu tüm temalarınızı silecektir",
|
||||
"delete_theme_description": "Bu {{theme}} temasını silecektir",
|
||||
"cancel": "İptal",
|
||||
"appearance": "Görünüm",
|
||||
"enable_torbox": "Torbox'u etkinleştir",
|
||||
"torbox_description": "TorBox, piyasadaki en iyi sunucularla bile rekabet edebilen premium seedbox hizmetinizdir.",
|
||||
"torbox_account_linked": "TorBox hesabı bağlando",
|
||||
"real_debrid_account_linked": "Real-Debrid hesabı bağlando",
|
||||
"name_min_length": "Tema ismi en az 3 karakter uzunluğunda olmalıdır",
|
||||
"import_theme": "Temayı içe aktar",
|
||||
"import_theme_description": "{{theme}} teması, tema mağazasından içeri aktarılacak",
|
||||
"error_importing_theme": "Temayı içe aktarmada bir sorun oluştu",
|
||||
"theme_imported": "Tema başarıyla içe aktarıldı"
|
||||
},
|
||||
"notifications": {
|
||||
"download_complete": "İndirme tamamlandı",
|
||||
@@ -289,17 +348,19 @@
|
||||
"repack_list_updated": "Repack listesi güncellendi",
|
||||
"repack_count_one": "{{count}} repack eklendi",
|
||||
"repack_count_other": "{{count}} repack eklendi",
|
||||
"new_update_available": "Sürüm {{version}} mevcut",
|
||||
"new_update_available": "{{version}} sürümü mevcut",
|
||||
"restart_to_install_update": "Güncellemeyi yüklemek için Hydra'yı yeniden başlatın",
|
||||
"notification_achievement_unlocked_title": "{{game}} için başarı kilidi açıldı",
|
||||
"notification_achievement_unlocked_body": "{{achievement}} ve diğer {{count}} başarılar açıldı"
|
||||
"notification_achievement_unlocked_title": "{{game}} için başarım kilidi açıldı",
|
||||
"notification_achievement_unlocked_body": "{{achievement}} ve diğer {{count}} başarım açıldı"
|
||||
},
|
||||
"system_tray": {
|
||||
"open": "Hydra'yı Aç",
|
||||
"quit": "Çık"
|
||||
},
|
||||
"game_card": {
|
||||
"no_downloads": "İndirilebilir içerik bulunmuyor"
|
||||
"no_downloads": "İndirilebilir içerik bulunmuyor",
|
||||
"available_one": "Mevcut",
|
||||
"available_other": "Mevcut"
|
||||
},
|
||||
"binary_not_found_modal": {
|
||||
"title": "Programlar Yüklü Değil",
|
||||
@@ -332,7 +393,7 @@
|
||||
"successfully_signed_out": "Başarıyla çıkış yapıldı",
|
||||
"sign_out": "Çıkış yap",
|
||||
"playing_for": "{{amount}} oynanıyor",
|
||||
"sign_out_modal_text": "Kütüphaneniz mevcut hesabınıza bağlı. Çıkış yaptığınızda kütüphaneniz görünür olmayacak ve herhangi bir ilerleme kaydedilmeyecek. Çıkışa devam etmek istiyor musunuz?",
|
||||
"sign_out_modal_text": "Kütüphaneniz mevcut hesabınıza bağlı. Oturumu kapattığınızda kütüphaneniz görünür olmayacak ve herhangi bir ilerleme kaydedilmeyecek. Oturumu kapatmaya devam etmek istiyor musunuz?",
|
||||
"add_friends": "Arkadaş Ekle",
|
||||
"add": "Ekle",
|
||||
"friend_code": "Arkadaş kodu",
|
||||
@@ -383,39 +444,39 @@
|
||||
"uploading_banner": "Afiş yükleniyor…",
|
||||
"background_image_updated": "Arka plan görüntüsü güncellendi",
|
||||
"stats": "İstatistikler",
|
||||
"achievements": "Başarılar",
|
||||
"achievements": "Başarımlar",
|
||||
"games": "Oyunlar",
|
||||
"top_percentile": "En üst {{percentile}}%",
|
||||
"ranking_updated_weekly": "Sıralama haftalık olarak güncellenir",
|
||||
"playing": "{{game}} oynanıyor",
|
||||
"achievements_unlocked": "Başarılar açıldı",
|
||||
"achievements_unlocked": "Başarımlar açıldı",
|
||||
"earned_points": "Kazanılan puanlar",
|
||||
"show_achievements_on_profile": "Başarılarınızı profilinizde gösterin",
|
||||
"show_achievements_on_profile": "Başarımlarınızı profilinizde gösterin",
|
||||
"show_points_on_profile": "Kazandığınız puanları profilinizde gösterin"
|
||||
},
|
||||
"achievement": {
|
||||
"achievement_unlocked": "Başarı açıldı",
|
||||
"user_achievements": "{{displayName}}'in Başarıları",
|
||||
"your_achievements": "Başarılarınız",
|
||||
"achievement_unlocked": "Başarım açıldı",
|
||||
"user_achievements": "{{displayName}} oyununun Başarımları",
|
||||
"your_achievements": "Başarımlarınız",
|
||||
"unlocked_at": "Açılma zamanı: {{date}}",
|
||||
"subscription_needed": "Bu içeriği görmek için bir Hydra Cloud aboneliği gereklidir",
|
||||
"new_achievements_unlocked": "{{gameCount}} oyundan {{achievementCount}} yeni başarı açıldı",
|
||||
"achievement_progress": "{{unlockedCount}}/{{totalCount}} başarı",
|
||||
"achievements_unlocked_for_game": "{{gameTitle}} oyunu için {{achievementCount}} yeni başarı açıldı",
|
||||
"hidden_achievement_tooltip": "Bu gizli bir başarıdır",
|
||||
"achievement_earn_points": "Bu başarı ile {{points}} puan kazanın",
|
||||
"new_achievements_unlocked": "{{gameCount}} oyundan {{achievementCount}} yeni başarım açıldı",
|
||||
"achievement_progress": "{{unlockedCount}}/{{totalCount}} başarım",
|
||||
"achievements_unlocked_for_game": "{{gameTitle}} oyunu için {{achievementCount}} yeni başarım açıldı",
|
||||
"hidden_achievement_tooltip": "Bu gizli bir başarımdır",
|
||||
"achievement_earn_points": "Bu başarım ile {{points}} puan kazanın",
|
||||
"earned_points": "Kazanılan puanlar:",
|
||||
"available_points": "Mevcut puanlar:",
|
||||
"how_to_earn_achievements_points": "Başarı puanları nasıl kazanılır?"
|
||||
"how_to_earn_achievements_points": "Başarım puanları nasıl kazanılır?"
|
||||
},
|
||||
"hydra_cloud": {
|
||||
"subscription_tour_title": "Hydra Cloud Aboneliği",
|
||||
"subscribe_now": "Şimdi abone olun",
|
||||
"cloud_saving": "Bulut kaydetme",
|
||||
"cloud_achievements": "Başarılarınızı buluta kaydedin",
|
||||
"cloud_achievements": "Başarımlarınızı buluta kaydedin",
|
||||
"animated_profile_picture": "Animasyonlu profil resimleri",
|
||||
"premium_support": "Premium Destek",
|
||||
"show_and_compare_achievements": "Başarılarınızı diğer kullanıcılarla karşılaştırın ve gösterin",
|
||||
"show_and_compare_achievements": "Başarımlarınızı diğer kullanıcılarla karşılaştırın ve gösterin",
|
||||
"animated_profile_banner": "Animasyonlu profil afişi",
|
||||
"hydra_cloud": "Hydra Cloud",
|
||||
"hydra_cloud_feature_found": "Bir Hydra Cloud özelliği keşfettiniz!",
|
||||
|
||||
@@ -176,6 +176,12 @@
|
||||
"import": "Імпортувати",
|
||||
"insert_valid_json_url": "Вставте дійсний URL JSON-файлу",
|
||||
"language": "Мова",
|
||||
"cancel_button_confirmation_delete_all_sources": "немає",
|
||||
"confirm_button_confirmation_delete_all_sources": "Так, видалити все",
|
||||
"description_confirmation_delete_all_sources": "Ви видалите всі джерела завантаження",
|
||||
"title_confirmation_delete_all_sources": "Видалити всі джерела завантаження",
|
||||
"removed_download_sources": "Джерела завантажень видалено",
|
||||
"button_delete_all_sources": "Видаліть усі джерела завантаження",
|
||||
"api_token": "API-токен",
|
||||
"debrid_api_token_hint": "API токен можливо отримати <0>тут</0>",
|
||||
"real_debrid_api_token_label": "Real-Debrid API-токен",
|
||||
|
||||
@@ -226,6 +226,12 @@
|
||||
"download_sources_description": "Hydra 将从这些源获取下载链接。源 URL 必须是直接链接到包含下载链接的 .json 文件。",
|
||||
"validate_download_source": "验证",
|
||||
"remove_download_source": "移除",
|
||||
"cancel_button_confirmation_delete_all_sources": "不",
|
||||
"confirm_button_confirmation_delete_all_sources": "是的,删除所有内容",
|
||||
"description_confirmation_delete_all_sources": "您将删除所有下载源",
|
||||
"title_confirmation_delete_all_sources": "删除所有下载源",
|
||||
"removed_download_sources": "已删除字体",
|
||||
"button_delete_all_sources": "删除所有下载源",
|
||||
"add_download_source": "添加源",
|
||||
"download_count_zero": "列表中无下载",
|
||||
"download_count_one": "列表中有 {{countFormatted}} 个下载",
|
||||
|
||||
@@ -31,3 +31,5 @@ export const achievementSoundPath = app.isPackaged
|
||||
export const backupsPath = path.join(app.getPath("userData"), "Backups");
|
||||
|
||||
export const appVersion = app.getVersion() + (isStaging ? "-staging" : "");
|
||||
|
||||
export const MAIN_LOOP_INTERVAL = 1500;
|
||||
|
||||
@@ -4,11 +4,15 @@ import updater from "electron-updater";
|
||||
|
||||
const { autoUpdater } = updater;
|
||||
|
||||
const restartAndInstallUpdate = async (_event: Electron.IpcMainInvokeEvent) => {
|
||||
export const restartAndInstallUpdate = () => {
|
||||
autoUpdater.removeAllListeners();
|
||||
if (app.isPackaged) {
|
||||
autoUpdater.quitAndInstall(false);
|
||||
}
|
||||
};
|
||||
|
||||
registerEvent("restartAndInstallUpdate", restartAndInstallUpdate);
|
||||
const restartAndInstallUpdateEvent = async (
|
||||
_event: Electron.IpcMainInvokeEvent
|
||||
) => restartAndInstallUpdate();
|
||||
|
||||
registerEvent("restartAndInstallUpdate", restartAndInstallUpdateEvent);
|
||||
|
||||
@@ -1,44 +1,8 @@
|
||||
import { HydraApi, logger, Ludusavi, WindowManager } from "@main/services";
|
||||
import { CloudSync } from "@main/services";
|
||||
import { registerEvent } from "../register-event";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import * as tar from "tar";
|
||||
import crypto from "node:crypto";
|
||||
import type { GameShop } from "@types";
|
||||
import axios from "axios";
|
||||
import os from "node:os";
|
||||
import { backupsPath } from "@main/constants";
|
||||
import { app } from "electron";
|
||||
import { normalizePath } from "@main/helpers";
|
||||
import { gamesSublevel, levelKeys } from "@main/level";
|
||||
|
||||
const bundleBackup = async (
|
||||
shop: GameShop,
|
||||
objectId: string,
|
||||
winePrefix: string | null
|
||||
) => {
|
||||
const backupPath = path.join(backupsPath, `${shop}-${objectId}`);
|
||||
|
||||
// Remove existing backup
|
||||
if (fs.existsSync(backupPath)) {
|
||||
fs.rmSync(backupPath, { recursive: true });
|
||||
}
|
||||
|
||||
await Ludusavi.backupGame(shop, objectId, backupPath, winePrefix);
|
||||
|
||||
const tarLocation = path.join(backupsPath, `${crypto.randomUUID()}.tar`);
|
||||
|
||||
await tar.create(
|
||||
{
|
||||
gzip: false,
|
||||
file: tarLocation,
|
||||
cwd: backupPath,
|
||||
},
|
||||
["."]
|
||||
);
|
||||
|
||||
return tarLocation;
|
||||
};
|
||||
import { t } from "i18next";
|
||||
import { format } from "date-fns";
|
||||
|
||||
const uploadSaveGame = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
@@ -46,61 +10,15 @@ const uploadSaveGame = async (
|
||||
shop: GameShop,
|
||||
downloadOptionTitle: string | null
|
||||
) => {
|
||||
const game = await gamesSublevel.get(levelKeys.game(shop, objectId));
|
||||
|
||||
const bundleLocation = await bundleBackup(
|
||||
shop,
|
||||
return CloudSync.uploadSaveGame(
|
||||
objectId,
|
||||
game?.winePrefixPath ?? null
|
||||
shop,
|
||||
downloadOptionTitle,
|
||||
t("backup_from", {
|
||||
ns: "game_details",
|
||||
date: format(new Date(), "dd/MM/yyyy"),
|
||||
})
|
||||
);
|
||||
|
||||
fs.stat(bundleLocation, async (err, stat) => {
|
||||
if (err) {
|
||||
logger.error("Failed to get zip file stats", err);
|
||||
throw err;
|
||||
}
|
||||
|
||||
const { uploadUrl } = await HydraApi.post<{
|
||||
id: string;
|
||||
uploadUrl: string;
|
||||
}>("/profile/games/artifacts", {
|
||||
artifactLengthInBytes: stat.size,
|
||||
shop,
|
||||
objectId,
|
||||
hostname: os.hostname(),
|
||||
homeDir: normalizePath(app.getPath("home")),
|
||||
downloadOptionTitle,
|
||||
platform: os.platform(),
|
||||
});
|
||||
|
||||
fs.readFile(bundleLocation, async (err, fileBuffer) => {
|
||||
if (err) {
|
||||
logger.error("Failed to read zip file", err);
|
||||
throw err;
|
||||
}
|
||||
|
||||
await axios.put(uploadUrl, fileBuffer, {
|
||||
headers: {
|
||||
"Content-Type": "application/tar",
|
||||
},
|
||||
onUploadProgress: (progressEvent) => {
|
||||
logger.log(progressEvent);
|
||||
},
|
||||
});
|
||||
|
||||
WindowManager.mainWindow?.webContents.send(
|
||||
`on-upload-complete-${objectId}-${shop}`,
|
||||
true
|
||||
);
|
||||
|
||||
fs.rm(bundleLocation, (err) => {
|
||||
if (err) {
|
||||
logger.error("Failed to remove tar file", err);
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
registerEvent("uploadSaveGame", uploadSaveGame);
|
||||
|
||||
@@ -31,11 +31,13 @@ import "./library/remove-game";
|
||||
import "./library/remove-game-from-library";
|
||||
import "./library/select-game-wine-prefix";
|
||||
import "./library/reset-game-achievements";
|
||||
import "./library/toggle-automatic-cloud-sync";
|
||||
import "./misc/open-checkout";
|
||||
import "./misc/open-external";
|
||||
import "./misc/show-open-dialog";
|
||||
import "./misc/get-features";
|
||||
import "./misc/show-item-in-folder";
|
||||
import "./misc/get-badges";
|
||||
import "./torrenting/cancel-game-download";
|
||||
import "./torrenting/pause-game-download";
|
||||
import "./torrenting/resume-game-download";
|
||||
@@ -58,6 +60,7 @@ import "./user/get-blocked-users";
|
||||
import "./user/block-user";
|
||||
import "./user/unblock-user";
|
||||
import "./user/get-user-friends";
|
||||
import "./user/get-auth";
|
||||
import "./user/get-user-stats";
|
||||
import "./user/report-user";
|
||||
import "./user/get-unlocked-achievements";
|
||||
|
||||
23
src/main/events/library/toggle-automatic-cloud-sync.ts
Normal file
23
src/main/events/library/toggle-automatic-cloud-sync.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { registerEvent } from "../register-event";
|
||||
import { levelKeys, gamesSublevel } from "@main/level";
|
||||
import type { GameShop } from "@types";
|
||||
|
||||
const toggleAutomaticCloudSync = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
shop: GameShop,
|
||||
objectId: string,
|
||||
automaticCloudSync: boolean
|
||||
) => {
|
||||
const gameKey = levelKeys.game(shop, objectId);
|
||||
|
||||
const game = await gamesSublevel.get(gameKey);
|
||||
|
||||
if (!game) return;
|
||||
|
||||
await gamesSublevel.put(gameKey, {
|
||||
...game,
|
||||
automaticCloudSync,
|
||||
});
|
||||
};
|
||||
|
||||
registerEvent("toggleAutomaticCloudSync", toggleAutomaticCloudSync);
|
||||
22
src/main/events/misc/get-badges.ts
Normal file
22
src/main/events/misc/get-badges.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Badge } from "@types";
|
||||
import { registerEvent } from "../register-event";
|
||||
import { HydraApi } from "@main/services";
|
||||
import { db, levelKeys } from "@main/level";
|
||||
|
||||
const getBadges = async (_event: Electron.IpcMainInvokeEvent) => {
|
||||
const language = await db
|
||||
.get<string, string>(levelKeys.language, {
|
||||
valueEncoding: "utf-8",
|
||||
})
|
||||
.then((language) => language || "en");
|
||||
|
||||
const params = new URLSearchParams({
|
||||
locale: language,
|
||||
});
|
||||
|
||||
return HydraApi.get<Badge[]>(`/badges?${params.toString()}`, null, {
|
||||
needsAuth: false,
|
||||
});
|
||||
};
|
||||
|
||||
registerEvent("getBadges", getBadges);
|
||||
@@ -1,17 +1,59 @@
|
||||
import { MAIN_LOOP_INTERVAL } from "@main/constants";
|
||||
import { registerEvent } from "../register-event";
|
||||
import { HydraApi } from "@main/services";
|
||||
import { HydraApi, WindowManager } from "@main/services";
|
||||
import { publishNewFriendRequestNotification } from "@main/services/notifications";
|
||||
import { UserNotLoggedInError } from "@shared";
|
||||
import type { FriendRequestSync } from "@types";
|
||||
|
||||
const syncFriendRequests = async (_event: Electron.IpcMainInvokeEvent) => {
|
||||
return HydraApi.get<FriendRequestSync>(`/profile/friend-requests/sync`).catch(
|
||||
(err) => {
|
||||
if (err instanceof UserNotLoggedInError) {
|
||||
return { friendRequests: [] };
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
);
|
||||
interface SyncState {
|
||||
friendRequestCount: number | null;
|
||||
tick: number;
|
||||
}
|
||||
|
||||
const ticksToUpdate = (2 * 60 * 1000) / MAIN_LOOP_INTERVAL; // 2 minutes
|
||||
|
||||
const syncState: SyncState = {
|
||||
friendRequestCount: null,
|
||||
tick: 0,
|
||||
};
|
||||
|
||||
registerEvent("syncFriendRequests", syncFriendRequests);
|
||||
const syncFriendRequests = async () => {
|
||||
return HydraApi.get<FriendRequestSync>(`/profile/friend-requests/sync`)
|
||||
.then((res) => {
|
||||
if (
|
||||
syncState.friendRequestCount != null &&
|
||||
syncState.friendRequestCount < res.friendRequestCount
|
||||
) {
|
||||
publishNewFriendRequestNotification();
|
||||
}
|
||||
|
||||
syncState.friendRequestCount = res.friendRequestCount;
|
||||
|
||||
WindowManager.mainWindow?.webContents.send(
|
||||
"on-sync-friend-requests",
|
||||
res
|
||||
);
|
||||
|
||||
return res;
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err instanceof UserNotLoggedInError) {
|
||||
return { friendRequestCount: 0 } as FriendRequestSync;
|
||||
}
|
||||
throw err;
|
||||
});
|
||||
};
|
||||
|
||||
const syncFriendRequestsEvent = async (_event: Electron.IpcMainInvokeEvent) => {
|
||||
return syncFriendRequests();
|
||||
};
|
||||
|
||||
export const watchFriendRequests = async () => {
|
||||
if (syncState.tick % ticksToUpdate === 0) {
|
||||
await syncFriendRequests();
|
||||
}
|
||||
|
||||
syncState.tick++;
|
||||
};
|
||||
|
||||
registerEvent("syncFriendRequests", syncFriendRequestsEvent);
|
||||
|
||||
11
src/main/events/user/get-auth.ts
Normal file
11
src/main/events/user/get-auth.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { db, levelKeys } from "@main/level";
|
||||
import type { Auth } from "@types";
|
||||
|
||||
import { registerEvent } from "../register-event";
|
||||
|
||||
const getAuth = async (_event: Electron.IpcMainInvokeEvent) =>
|
||||
db.get<string, Auth>(levelKeys.auth, {
|
||||
valueEncoding: "json",
|
||||
});
|
||||
|
||||
registerEvent("getAuth", getAuth);
|
||||
@@ -14,4 +14,5 @@ export const levelKeys = {
|
||||
userPreferences: "userPreferences",
|
||||
language: "language",
|
||||
sqliteMigrationDone: "sqliteMigrationDone",
|
||||
screenState: "screenState",
|
||||
};
|
||||
|
||||
@@ -62,8 +62,6 @@ export const loadState = async () => {
|
||||
game.uri !== null
|
||||
);
|
||||
|
||||
console.log("downloadsToSeed", downloadsToSeed);
|
||||
|
||||
await DownloadManager.startRPC(nextItemOnQueue, downloadsToSeed);
|
||||
|
||||
startMainLoop();
|
||||
@@ -132,7 +130,9 @@ const migrateFromSqlite = async () => {
|
||||
);
|
||||
|
||||
if (rest.language) {
|
||||
await db.put(levelKeys.language, rest.language);
|
||||
await db.put<string, string>(levelKeys.language, rest.language, {
|
||||
valueEncoding: "utf-8",
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
111
src/main/services/cloud-sync.ts
Normal file
111
src/main/services/cloud-sync.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
import { levelKeys, gamesSublevel, db } from "@main/level";
|
||||
import { app } from "electron";
|
||||
import path from "node:path";
|
||||
import * as tar from "tar";
|
||||
import crypto from "node:crypto";
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import type { GameShop, User } from "@types";
|
||||
import { backupsPath } from "@main/constants";
|
||||
import { HydraApi } from "./hydra-api";
|
||||
import { normalizePath } from "@main/helpers";
|
||||
import { logger } from "./logger";
|
||||
import { WindowManager } from "./window-manager";
|
||||
import axios from "axios";
|
||||
import { Ludusavi } from "./ludusavi";
|
||||
import { SubscriptionRequiredError } from "@shared";
|
||||
|
||||
export class CloudSync {
|
||||
private static async bundleBackup(
|
||||
shop: GameShop,
|
||||
objectId: string,
|
||||
winePrefix: string | null
|
||||
) {
|
||||
const backupPath = path.join(backupsPath, `${shop}-${objectId}`);
|
||||
|
||||
// Remove existing backup
|
||||
if (fs.existsSync(backupPath)) {
|
||||
fs.rmSync(backupPath, { recursive: true });
|
||||
}
|
||||
|
||||
await Ludusavi.backupGame(shop, objectId, backupPath, winePrefix);
|
||||
|
||||
const tarLocation = path.join(backupsPath, `${crypto.randomUUID()}.tar`);
|
||||
|
||||
await tar.create(
|
||||
{
|
||||
gzip: false,
|
||||
file: tarLocation,
|
||||
cwd: backupPath,
|
||||
},
|
||||
["."]
|
||||
);
|
||||
|
||||
return tarLocation;
|
||||
}
|
||||
|
||||
public static async uploadSaveGame(
|
||||
objectId: string,
|
||||
shop: GameShop,
|
||||
downloadOptionTitle: string | null,
|
||||
label?: string
|
||||
) {
|
||||
const hasActiveSubscription = await db
|
||||
.get<string, User>(levelKeys.user, { valueEncoding: "json" })
|
||||
.then((user) => {
|
||||
const expiresAt = new Date(user?.subscription?.expiresAt ?? 0);
|
||||
return expiresAt > new Date();
|
||||
});
|
||||
|
||||
if (!hasActiveSubscription) {
|
||||
throw new SubscriptionRequiredError();
|
||||
}
|
||||
|
||||
const game = await gamesSublevel.get(levelKeys.game(shop, objectId));
|
||||
|
||||
const bundleLocation = await this.bundleBackup(
|
||||
shop,
|
||||
objectId,
|
||||
game?.winePrefixPath ?? null
|
||||
);
|
||||
|
||||
const stat = await fs.promises.stat(bundleLocation);
|
||||
|
||||
const { uploadUrl } = await HydraApi.post<{
|
||||
id: string;
|
||||
uploadUrl: string;
|
||||
}>("/profile/games/artifacts", {
|
||||
artifactLengthInBytes: stat.size,
|
||||
shop,
|
||||
objectId,
|
||||
hostname: os.hostname(),
|
||||
homeDir: normalizePath(app.getPath("home")),
|
||||
downloadOptionTitle,
|
||||
platform: os.platform(),
|
||||
label,
|
||||
});
|
||||
|
||||
const fileBuffer = await fs.promises.readFile(bundleLocation);
|
||||
|
||||
await axios.put(uploadUrl, fileBuffer, {
|
||||
headers: {
|
||||
"Content-Type": "application/tar",
|
||||
},
|
||||
onUploadProgress: (progressEvent) => {
|
||||
logger.log(progressEvent);
|
||||
},
|
||||
});
|
||||
|
||||
WindowManager.mainWindow?.webContents.send(
|
||||
`on-upload-complete-${objectId}-${shop}`,
|
||||
true
|
||||
);
|
||||
|
||||
fs.rm(bundleLocation, (err) => {
|
||||
if (err) {
|
||||
logger.error("Failed to remove tar file", err);
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,13 @@ import { Downloader, DownloadError } from "@shared";
|
||||
import { WindowManager } from "../window-manager";
|
||||
import { publishDownloadCompleteNotification } from "../notifications";
|
||||
import type { Download, DownloadProgress, UserPreferences } from "@types";
|
||||
import { GofileApi, QiwiApi, DatanodesApi, MediafireApi } from "../hosters";
|
||||
import {
|
||||
GofileApi,
|
||||
QiwiApi,
|
||||
DatanodesApi,
|
||||
MediafireApi,
|
||||
PixelDrainApi,
|
||||
} from "../hosters";
|
||||
import { PythonRPC } from "../python-rpc";
|
||||
import {
|
||||
LibtorrentPayload,
|
||||
@@ -283,11 +289,12 @@ export class DownloadManager {
|
||||
}
|
||||
case Downloader.PixelDrain: {
|
||||
const id = download.uri.split("/").pop();
|
||||
const downloadUrl = await PixelDrainApi.getDownloadUrl(id!);
|
||||
|
||||
return {
|
||||
action: "start",
|
||||
game_id: downloadId,
|
||||
url: `https://cdn.pd5-gamedriveorg.workers.dev/api/file/${id}`,
|
||||
url: downloadUrl,
|
||||
save_path: download.downloadPath,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import type {
|
||||
TorBoxAddTorrentRequest,
|
||||
TorBoxRequestLinkRequest,
|
||||
} from "@types";
|
||||
import { appVersion } from "@main/constants";
|
||||
|
||||
export class TorBoxClient {
|
||||
private static instance: AxiosInstance;
|
||||
@@ -18,6 +19,7 @@ export class TorBoxClient {
|
||||
baseURL: this.baseURL,
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiToken}`,
|
||||
"User-Agent": `Hydra/${appVersion}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,47 +1,71 @@
|
||||
import axios, { AxiosResponse } from "axios";
|
||||
import { wrapper } from "axios-cookiejar-support";
|
||||
import { CookieJar } from "tough-cookie";
|
||||
|
||||
export class DatanodesApi {
|
||||
private static readonly session = axios.create({});
|
||||
private static readonly jar = new CookieJar();
|
||||
|
||||
private static readonly session = wrapper(
|
||||
axios.create({
|
||||
jar: DatanodesApi.jar,
|
||||
withCredentials: true,
|
||||
})
|
||||
);
|
||||
|
||||
public static async getDownloadUrl(downloadUrl: string): Promise<string> {
|
||||
const parsedUrl = new URL(downloadUrl);
|
||||
const pathSegments = parsedUrl.pathname.split("/");
|
||||
try {
|
||||
const parsedUrl = new URL(downloadUrl);
|
||||
const pathSegments = parsedUrl.pathname.split("/").filter(Boolean);
|
||||
const fileCode = pathSegments[0];
|
||||
|
||||
const fileCode = decodeURIComponent(pathSegments[1]);
|
||||
const fileName = decodeURIComponent(pathSegments[pathSegments.length - 1]);
|
||||
await this.jar.setCookie("lang=english;", "https://datanodes.to");
|
||||
|
||||
const payload = new URLSearchParams({
|
||||
op: "download2",
|
||||
id: fileCode,
|
||||
rand: "",
|
||||
referer: "https://datanodes.to/download",
|
||||
method_free: "Free Download >>",
|
||||
method_premium: "",
|
||||
adblock_detected: "",
|
||||
});
|
||||
const payload = new URLSearchParams({
|
||||
op: "download2",
|
||||
id: fileCode,
|
||||
method_free: "Free Download >>",
|
||||
dl: "1",
|
||||
});
|
||||
|
||||
const response: AxiosResponse = await this.session.post(
|
||||
"https://datanodes.to/download",
|
||||
payload,
|
||||
{
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
Cookie: `lang=english; file_name=${fileName}; file_code=${fileCode};`,
|
||||
Host: "datanodes.to",
|
||||
Origin: "https://datanodes.to",
|
||||
Referer: "https://datanodes.to/download",
|
||||
"User-Agent":
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36",
|
||||
},
|
||||
maxRedirects: 0,
|
||||
validateStatus: (status: number) => status === 302 || status < 400,
|
||||
const response: AxiosResponse = await this.session.post(
|
||||
"https://datanodes.to/download",
|
||||
payload,
|
||||
{
|
||||
headers: {
|
||||
"User-Agent":
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:135.0) Gecko/20100101 Firefox/135.0",
|
||||
Referer: "https://datanodes.to/download",
|
||||
Origin: "https://datanodes.to",
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
maxRedirects: 0,
|
||||
validateStatus: (status: number) => status === 302 || status < 400,
|
||||
}
|
||||
);
|
||||
|
||||
if (response.status === 302) {
|
||||
return response.headers["location"];
|
||||
}
|
||||
);
|
||||
|
||||
if (response.status === 302) {
|
||||
return response.headers["location"];
|
||||
if (typeof response.data === "object" && response.data.url) {
|
||||
return decodeURIComponent(response.data.url);
|
||||
}
|
||||
|
||||
const htmlContent = String(response.data);
|
||||
if (!htmlContent) {
|
||||
throw new Error("Empty response received");
|
||||
}
|
||||
|
||||
const downloadLinkRegex = /href=["'](https:\/\/[^"']+)["']/;
|
||||
const downloadLinkMatch = downloadLinkRegex.exec(htmlContent);
|
||||
if (downloadLinkMatch) {
|
||||
return downloadLinkMatch[1];
|
||||
}
|
||||
|
||||
throw new Error("Failed to get the download link");
|
||||
} catch (error) {
|
||||
console.error("Error fetching download URL:", error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,3 +2,4 @@ export * from "./gofile";
|
||||
export * from "./qiwi";
|
||||
export * from "./datanodes";
|
||||
export * from "./mediafire";
|
||||
export * from "./pixeldrain";
|
||||
|
||||
42
src/main/services/hosters/pixeldrain.ts
Normal file
42
src/main/services/hosters/pixeldrain.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import axios from "axios";
|
||||
|
||||
export class PixelDrainApi {
|
||||
private static readonly browserHeaders = {
|
||||
"User-Agent":
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36",
|
||||
Accept:
|
||||
"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
|
||||
"Accept-Language": "en-US,en;q=0.9",
|
||||
"Accept-Encoding": "gzip, deflate, br",
|
||||
DNT: "1",
|
||||
Connection: "keep-alive",
|
||||
"Sec-Fetch-Dest": "document",
|
||||
"Sec-Fetch-Mode": "navigate",
|
||||
"Sec-Fetch-Site": "none",
|
||||
"Sec-Fetch-User": "?1",
|
||||
};
|
||||
|
||||
public static async getDownloadUrl(fileId: string): Promise<string> {
|
||||
try {
|
||||
const response = await axios.get(`https://pd.cybar.xyz/${fileId}`, {
|
||||
headers: this.browserHeaders,
|
||||
maxRedirects: 0,
|
||||
validateStatus: (status) =>
|
||||
status === 301 || status === 302 || status === 200,
|
||||
});
|
||||
|
||||
if (
|
||||
response.headers.location ||
|
||||
response.status === 301 ||
|
||||
response.status === 302
|
||||
) {
|
||||
return response.headers.location;
|
||||
}
|
||||
|
||||
throw new Error(`No redirect URL found (status: ${response.status})`);
|
||||
} catch (error) {
|
||||
console.error("Error fetching PixelDrain URL:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import { UserNotLoggedInError, SubscriptionRequiredError } from "@shared";
|
||||
import { omit } from "lodash-es";
|
||||
import { appVersion } from "@main/constants";
|
||||
import { getUserData } from "./user/get-user-data";
|
||||
import { isFuture, isToday } from "date-fns";
|
||||
import { db } from "@main/level";
|
||||
import { levelKeys } from "@main/level/sublevels";
|
||||
import type { Auth, User } from "@types";
|
||||
@@ -47,8 +46,8 @@ export class HydraApi {
|
||||
}
|
||||
|
||||
private static hasActiveSubscription() {
|
||||
const expiresAt = this.userAuth.subscription?.expiresAt;
|
||||
return expiresAt && (isFuture(expiresAt) || isToday(expiresAt));
|
||||
const expiresAt = new Date(this.userAuth.subscription?.expiresAt ?? 0);
|
||||
return expiresAt > new Date();
|
||||
}
|
||||
|
||||
static async handleExternalAuth(uri: string) {
|
||||
|
||||
@@ -7,3 +7,4 @@ export * from "./process-watcher";
|
||||
export * from "./main-loop";
|
||||
export * from "./hydra-api";
|
||||
export * from "./ludusavi";
|
||||
export * from "./cloud-sync";
|
||||
|
||||
@@ -3,18 +3,21 @@ import { DownloadManager } from "./download";
|
||||
import { watchProcesses } from "./process-watcher";
|
||||
import { AchievementWatcherManager } from "./achievements/achievement-watcher-manager";
|
||||
import { UpdateManager } from "./update-manager";
|
||||
import { watchFriendRequests } from "@main/events/profile/sync-friend-requests";
|
||||
import { MAIN_LOOP_INTERVAL } from "@main/constants";
|
||||
|
||||
export const startMainLoop = async () => {
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
await Promise.allSettled([
|
||||
watchProcesses(),
|
||||
watchFriendRequests(),
|
||||
DownloadManager.watchDownloads(),
|
||||
AchievementWatcherManager.watchAchievements(),
|
||||
DownloadManager.getSeedStatus(),
|
||||
UpdateManager.checkForUpdatePeriodically(),
|
||||
]);
|
||||
|
||||
await sleep(1500);
|
||||
await sleep(MAIN_LOOP_INTERVAL);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -12,6 +12,7 @@ import { logger } from "../logger";
|
||||
import { WindowManager } from "../window-manager";
|
||||
import type { Game, UserPreferences } from "@types";
|
||||
import { db, levelKeys } from "@main/level";
|
||||
import { restartAndInstallUpdate } from "@main/events/autoupdater/restart-and-install-update";
|
||||
|
||||
async function downloadImage(url: string | null) {
|
||||
if (!url) return undefined;
|
||||
@@ -72,10 +73,33 @@ export const publishNotificationUpdateReadyToInstall = async (
|
||||
ns: "notifications",
|
||||
}),
|
||||
icon: trayIcon,
|
||||
}).show();
|
||||
})
|
||||
.on("click", () => {
|
||||
restartAndInstallUpdate();
|
||||
})
|
||||
.show();
|
||||
};
|
||||
|
||||
export const publishNewFriendRequestNotification = async () => {};
|
||||
export const publishNewFriendRequestNotification = async () => {
|
||||
const userPreferences = await db.get<string, UserPreferences | null>(
|
||||
levelKeys.userPreferences,
|
||||
{
|
||||
valueEncoding: "json",
|
||||
}
|
||||
);
|
||||
|
||||
if (!userPreferences?.friendRequestNotificationsEnabled) return;
|
||||
|
||||
new Notification({
|
||||
title: t("new_friend_request_title", {
|
||||
ns: "notifications",
|
||||
}),
|
||||
body: t("new_friend_request_description", {
|
||||
ns: "notifications",
|
||||
}),
|
||||
icon: trayIcon,
|
||||
}).show();
|
||||
};
|
||||
|
||||
export const publishCombinedNewAchievementNotification = async (
|
||||
achievementCount,
|
||||
|
||||
@@ -6,6 +6,9 @@ import axios from "axios";
|
||||
import { exec } from "child_process";
|
||||
import { ProcessPayload } from "./download/types";
|
||||
import { gamesSublevel, levelKeys } from "@main/level";
|
||||
import { t } from "i18next";
|
||||
import { CloudSync } from "./cloud-sync";
|
||||
import { format } from "date-fns";
|
||||
|
||||
const commands = {
|
||||
findWineDir: `lsof -c wine 2>/dev/null | grep '/drive_c/windows$' | head -n 1 | awk '{for(i=9;i<=NF;i++) printf "%s ", $i; print ""}'`,
|
||||
@@ -225,6 +228,18 @@ function onOpenGame(game: Game) {
|
||||
|
||||
if (game.remoteId) {
|
||||
updateGamePlaytime(game, 0, new Date()).catch(() => {});
|
||||
|
||||
if (game.automaticCloudSync) {
|
||||
CloudSync.uploadSaveGame(
|
||||
game.objectId,
|
||||
game.shop,
|
||||
null,
|
||||
t("automatic_backup_from", {
|
||||
ns: "game_details",
|
||||
date: format(new Date(), "dd/MM/yyyy"),
|
||||
})
|
||||
);
|
||||
}
|
||||
} else {
|
||||
createGame({ ...game, lastTimePlayed: new Date() }).catch(() => {});
|
||||
}
|
||||
@@ -287,6 +302,18 @@ const onCloseGame = (game: Game) => {
|
||||
performance.now() - gamePlaytime.lastSyncTick,
|
||||
game.lastTimePlayed!
|
||||
).catch(() => {});
|
||||
|
||||
if (game.automaticCloudSync) {
|
||||
CloudSync.uploadSaveGame(
|
||||
game.objectId,
|
||||
game.shop,
|
||||
null,
|
||||
t("automatic_backup_from", {
|
||||
ns: "game_details",
|
||||
date: format(new Date(), "dd/MM/yyyy"),
|
||||
})
|
||||
);
|
||||
}
|
||||
} else {
|
||||
createGame(game).catch(() => {});
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import updater, { UpdateInfo } from "electron-updater";
|
||||
import { logger, WindowManager } from "@main/services";
|
||||
import { AppUpdaterEvent } from "@types";
|
||||
import { AppUpdaterEvent, UserPreferences } from "@types";
|
||||
import { app } from "electron";
|
||||
import { publishNotificationUpdateReadyToInstall } from "@main/services/notifications";
|
||||
|
||||
const isAutoInstallAvailable =
|
||||
process.platform !== "darwin" && process.env.PORTABLE_EXECUTABLE_FILE == null;
|
||||
import { db, levelKeys } from "@main/level";
|
||||
import { MAIN_LOOP_INTERVAL } from "@main/constants";
|
||||
|
||||
const { autoUpdater } = updater;
|
||||
const sendEventsForDebug = false;
|
||||
const ticksToUpdate = (50 * 60 * 1000) / MAIN_LOOP_INTERVAL; // 50 minutes
|
||||
|
||||
export class UpdateManager {
|
||||
private static hasNotified = false;
|
||||
@@ -16,7 +16,7 @@ export class UpdateManager {
|
||||
private static checkTick = 0;
|
||||
|
||||
private static mockValuesForDebug() {
|
||||
this.sendEvent({ type: "update-available", info: { version: "1.3.0" } });
|
||||
this.sendEvent({ type: "update-available", info: { version: "3.3.1" } });
|
||||
this.sendEvent({ type: "update-downloaded" });
|
||||
}
|
||||
|
||||
@@ -24,7 +24,27 @@ export class UpdateManager {
|
||||
WindowManager.mainWindow?.webContents.send("autoUpdaterEvent", event);
|
||||
}
|
||||
|
||||
public static checkForUpdates() {
|
||||
private static async isAutoInstallEnabled() {
|
||||
if (process.platform === "darwin") return false;
|
||||
if (process.platform === "win32") {
|
||||
return process.env.PORTABLE_EXECUTABLE_FILE == null;
|
||||
}
|
||||
|
||||
if (process.platform === "linux") {
|
||||
const userPreferences = await db.get<string, UserPreferences | null>(
|
||||
levelKeys.userPreferences,
|
||||
{
|
||||
valueEncoding: "json",
|
||||
}
|
||||
);
|
||||
|
||||
return userPreferences?.enableAutoInstall === true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static async checkForUpdates() {
|
||||
autoUpdater
|
||||
.once("update-available", (info: UpdateInfo) => {
|
||||
this.sendEvent({ type: "update-available", info });
|
||||
@@ -39,6 +59,8 @@ export class UpdateManager {
|
||||
}
|
||||
});
|
||||
|
||||
const isAutoInstallAvailable = await this.isAutoInstallEnabled();
|
||||
|
||||
if (app.isPackaged) {
|
||||
autoUpdater.autoDownload = isAutoInstallAvailable;
|
||||
autoUpdater.checkForUpdates().then((result) => {
|
||||
@@ -52,7 +74,7 @@ export class UpdateManager {
|
||||
}
|
||||
|
||||
public static checkForUpdatePeriodically() {
|
||||
if (this.checkTick % 2000 == 0) {
|
||||
if (this.checkTick % ticksToUpdate == 0) {
|
||||
this.checkForUpdates();
|
||||
}
|
||||
this.checkTick++;
|
||||
|
||||
@@ -17,7 +17,7 @@ import { HydraApi } from "./hydra-api";
|
||||
import UserAgent from "user-agents";
|
||||
import { db, gamesSublevel, levelKeys } from "@main/level";
|
||||
import { slice, sortBy } from "lodash-es";
|
||||
import type { UserPreferences } from "@types";
|
||||
import type { ScreenState, UserPreferences } from "@types";
|
||||
import { AuthPage } from "@shared";
|
||||
import { isStaging } from "@main/constants";
|
||||
|
||||
@@ -26,27 +26,8 @@ export class WindowManager {
|
||||
|
||||
private static readonly editorWindows: Map<string, BrowserWindow> = new Map();
|
||||
|
||||
private static loadMainWindowURL(hash = "") {
|
||||
// HMR for renderer base on electron-vite cli.
|
||||
// Load the remote URL for development or the local html file for production.
|
||||
if (is.dev && process.env["ELECTRON_RENDERER_URL"]) {
|
||||
this.mainWindow?.loadURL(
|
||||
`${process.env["ELECTRON_RENDERER_URL"]}#/${hash}`
|
||||
);
|
||||
} else {
|
||||
this.mainWindow?.loadFile(
|
||||
path.join(__dirname, "../renderer/index.html"),
|
||||
{
|
||||
hash,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static createMainWindow() {
|
||||
if (this.mainWindow) return;
|
||||
|
||||
this.mainWindow = new BrowserWindow({
|
||||
private static initialConfigInitializationMainWindow: Electron.BrowserWindowConstructorOptions =
|
||||
{
|
||||
width: 1200,
|
||||
height: 720,
|
||||
minWidth: 1024,
|
||||
@@ -65,7 +46,65 @@ export class WindowManager {
|
||||
sandbox: false,
|
||||
},
|
||||
show: false,
|
||||
};
|
||||
|
||||
private static loadMainWindowURL(hash = "") {
|
||||
// HMR for renderer base on electron-vite cli.
|
||||
// Load the remote URL for development or the local html file for production.
|
||||
if (is.dev && process.env["ELECTRON_RENDERER_URL"]) {
|
||||
this.mainWindow?.loadURL(
|
||||
`${process.env["ELECTRON_RENDERER_URL"]}#/${hash}`
|
||||
);
|
||||
} else {
|
||||
this.mainWindow?.loadFile(
|
||||
path.join(__dirname, "../renderer/index.html"),
|
||||
{
|
||||
hash,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static async saveScreenConfig(configScreenWhenClosed: ScreenState) {
|
||||
await db.put(levelKeys.screenState, configScreenWhenClosed, {
|
||||
valueEncoding: "json",
|
||||
});
|
||||
}
|
||||
|
||||
private static async loadScreenConfig() {
|
||||
const data = await db.get<string, ScreenState | undefined>(
|
||||
levelKeys.screenState,
|
||||
{
|
||||
valueEncoding: "json",
|
||||
}
|
||||
);
|
||||
return data ?? { isMaximized: false, height: 720, width: 1200 };
|
||||
}
|
||||
|
||||
private static updateInitialConfig(
|
||||
newConfig: Partial<Electron.BrowserWindowConstructorOptions>
|
||||
) {
|
||||
this.initialConfigInitializationMainWindow = {
|
||||
...this.initialConfigInitializationMainWindow,
|
||||
...newConfig,
|
||||
};
|
||||
}
|
||||
|
||||
public static async createMainWindow() {
|
||||
if (this.mainWindow) return;
|
||||
|
||||
const { isMaximized = false, ...configWithoutMaximized } =
|
||||
await this.loadScreenConfig();
|
||||
|
||||
this.updateInitialConfig(configWithoutMaximized);
|
||||
|
||||
this.mainWindow = new BrowserWindow(
|
||||
this.initialConfigInitializationMainWindow
|
||||
);
|
||||
|
||||
if (isMaximized) {
|
||||
this.mainWindow.maximize();
|
||||
}
|
||||
|
||||
this.mainWindow.webContents.session.webRequest.onBeforeSendHeaders(
|
||||
(details, callback) => {
|
||||
@@ -143,9 +182,26 @@ export class WindowManager {
|
||||
}
|
||||
);
|
||||
|
||||
if (this.mainWindow) {
|
||||
const lastBounds = this.mainWindow.getBounds();
|
||||
const isMaximized = this.mainWindow.isMaximized() ?? false;
|
||||
const screenConfig = isMaximized
|
||||
? {
|
||||
x: undefined,
|
||||
y: undefined,
|
||||
height: this.initialConfigInitializationMainWindow.height ?? 720,
|
||||
width: this.initialConfigInitializationMainWindow.width ?? 1200,
|
||||
isMaximized: true,
|
||||
}
|
||||
: { ...lastBounds, isMaximized };
|
||||
|
||||
await this.saveScreenConfig(screenConfig);
|
||||
}
|
||||
|
||||
if (userPreferences?.preferQuitInsteadOfHiding) {
|
||||
app.quit();
|
||||
}
|
||||
|
||||
WindowManager.mainWindow?.setProgressBar(-1);
|
||||
WindowManager.mainWindow = null;
|
||||
});
|
||||
|
||||
@@ -15,6 +15,7 @@ import type {
|
||||
SeedingStatus,
|
||||
GameAchievement,
|
||||
Theme,
|
||||
FriendRequestSync,
|
||||
} from "@types";
|
||||
import type { AuthPage, CatalogueCategory } from "@shared";
|
||||
import type { AxiosProgressEvent } from "axios";
|
||||
@@ -101,6 +102,17 @@ contextBridge.exposeInMainWorld("electron", {
|
||||
ipcRenderer.invoke("putDownloadSource", objectIds),
|
||||
|
||||
/* Library */
|
||||
toggleAutomaticCloudSync: (
|
||||
shop: GameShop,
|
||||
objectId: string,
|
||||
automaticCloudSync: boolean
|
||||
) =>
|
||||
ipcRenderer.invoke(
|
||||
"toggleAutomaticCloudSync",
|
||||
shop,
|
||||
objectId,
|
||||
automaticCloudSync
|
||||
),
|
||||
addGameToLibrary: (shop: GameShop, objectId: string, title: string) =>
|
||||
ipcRenderer.invoke("addGameToLibrary", shop, objectId, title),
|
||||
createGameShortcut: (shop: GameShop, objectId: string) =>
|
||||
@@ -266,6 +278,7 @@ contextBridge.exposeInMainWorld("electron", {
|
||||
showItemInFolder: (path: string) =>
|
||||
ipcRenderer.invoke("showItemInFolder", path),
|
||||
getFeatures: () => ipcRenderer.invoke("getFeatures"),
|
||||
getBadges: () => ipcRenderer.invoke("getBadges"),
|
||||
platform: process.platform,
|
||||
|
||||
/* Auto update */
|
||||
@@ -294,6 +307,15 @@ contextBridge.exposeInMainWorld("electron", {
|
||||
ipcRenderer.invoke("processProfileImage", imagePath),
|
||||
getFriendRequests: () => ipcRenderer.invoke("getFriendRequests"),
|
||||
syncFriendRequests: () => ipcRenderer.invoke("syncFriendRequests"),
|
||||
onSyncFriendRequests: (cb: (friendRequests: FriendRequestSync) => void) => {
|
||||
const listener = (
|
||||
_event: Electron.IpcRendererEvent,
|
||||
friendRequests: FriendRequestSync
|
||||
) => cb(friendRequests);
|
||||
ipcRenderer.on("on-sync-friend-requests", listener);
|
||||
return () =>
|
||||
ipcRenderer.removeListener("on-sync-friend-requests", listener);
|
||||
},
|
||||
updateFriendRequest: (userId: string, action: FriendRequestAction) =>
|
||||
ipcRenderer.invoke("updateFriendRequest", userId, action),
|
||||
sendFriendRequest: (userId: string) =>
|
||||
@@ -325,6 +347,7 @@ contextBridge.exposeInMainWorld("electron", {
|
||||
ipcRenderer.invoke("getUnlockedAchievements", objectId, shop),
|
||||
|
||||
/* Auth */
|
||||
getAuth: () => ipcRenderer.invoke("getAuth"),
|
||||
signOut: () => ipcRenderer.invoke("signOut"),
|
||||
openAuthWindow: (page: AuthPage) =>
|
||||
ipcRenderer.invoke("openAuthWindow", page),
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
setUserDetails,
|
||||
setProfileBackground,
|
||||
setGameRunning,
|
||||
setFriendRequestCount,
|
||||
} from "@renderer/features";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { UserFriendModal } from "./pages/shared-modals/user-friend-modal";
|
||||
@@ -51,7 +52,6 @@ export function App() {
|
||||
isFriendsModalVisible,
|
||||
friendRequetsModalTab,
|
||||
friendModalUserId,
|
||||
syncFriendRequests,
|
||||
hideFriendsModal,
|
||||
fetchUserDetails,
|
||||
updateUserDetails,
|
||||
@@ -123,7 +123,7 @@ export function App() {
|
||||
.then((response) => {
|
||||
if (response) {
|
||||
updateUserDetails(response);
|
||||
syncFriendRequests();
|
||||
window.electron.syncFriendRequests();
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
@@ -134,23 +134,27 @@ export function App() {
|
||||
$script.src = `${import.meta.env.RENDERER_VITE_EXTERNAL_RESOURCES_URL}/bundle.js?t=${Date.now()}`;
|
||||
document.head.appendChild($script);
|
||||
});
|
||||
}, [fetchUserDetails, syncFriendRequests, updateUserDetails, dispatch]);
|
||||
}, [fetchUserDetails, updateUserDetails, dispatch]);
|
||||
|
||||
const onSignIn = useCallback(() => {
|
||||
fetchUserDetails().then((response) => {
|
||||
if (response) {
|
||||
updateUserDetails(response);
|
||||
syncFriendRequests();
|
||||
window.electron.syncFriendRequests();
|
||||
showSuccessToast(t("successfully_signed_in"));
|
||||
}
|
||||
});
|
||||
}, [
|
||||
fetchUserDetails,
|
||||
syncFriendRequests,
|
||||
t,
|
||||
showSuccessToast,
|
||||
updateUserDetails,
|
||||
]);
|
||||
}, [fetchUserDetails, t, showSuccessToast, updateUserDetails]);
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribe = window.electron.onSyncFriendRequests((result) => {
|
||||
dispatch(setFriendRequestCount(result.friendRequestCount));
|
||||
});
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
}, [dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribe = window.electron.onGamesRunning((gamesRunning) => {
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
<svg width="240" height="246" viewBox="0 0 240 246" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M117.681 8.44054C120.27 4.75592 125.73 4.75592 128.319 8.44054L149.273 38.2669C151.08 40.8399 154.301 42.0121 157.339 41.203L192.563 31.8236C196.914 30.6649 201.097 34.1747 200.712 38.6612L197.591 74.9784C197.322 78.1113 199.036 81.0795 201.884 82.4128L234.895 97.8691C238.974 99.7785 239.922 105.156 236.743 108.345L211.008 134.16C208.788 136.387 208.193 139.762 209.517 142.614L224.871 175.674C226.767 179.758 224.037 184.486 219.552 184.886L183.245 188.119C180.113 188.398 177.487 190.601 176.669 193.637L167.18 228.832C166.007 233.179 160.876 235.047 157.184 232.47L127.292 211.609C124.714 209.809 121.286 209.809 118.708 211.609L88.8163 232.47C85.1236 235.047 79.9927 233.179 78.8204 228.832L69.3314 193.637C68.5129 190.601 65.8873 188.398 62.7553 188.119L26.4479 184.886C21.9627 184.486 19.2326 179.758 21.1293 175.674L36.4827 142.614C37.8072 139.762 37.212 136.387 34.992 134.16L9.25738 108.345C6.07823 105.156 7.02639 99.7785 11.1045 97.8691L44.1164 82.4128C46.9642 81.0795 48.6778 78.1113 48.4087 74.9784L45.2883 38.6611C44.9028 34.1747 49.0856 30.6649 53.437 31.8236L88.6606 41.203C91.6992 42.0121 94.9198 40.8399 96.7274 38.2669L117.681 8.44054Z" fill="url(#paint0_linear_1378_2496)"/>
|
||||
<path d="M117.681 8.44054C120.27 4.75592 125.73 4.75592 128.319 8.44054L149.273 38.2669C151.08 40.8399 154.301 42.0121 157.339 41.203L192.563 31.8236C196.914 30.6649 201.097 34.1747 200.712 38.6612L197.591 74.9784C197.322 78.1113 199.036 81.0795 201.884 82.4128L234.895 97.8691C238.974 99.7785 239.922 105.156 236.743 108.345L211.008 134.16C208.788 136.387 208.193 139.762 209.517 142.614L224.871 175.674C226.767 179.758 224.037 184.486 219.552 184.886L183.245 188.119C180.113 188.398 177.487 190.601 176.669 193.637L167.18 228.832C166.007 233.179 160.876 235.047 157.184 232.47L127.292 211.609C124.714 209.809 121.286 209.809 118.708 211.609L88.8163 232.47C85.1236 235.047 79.9927 233.179 78.8204 228.832L69.3314 193.637C68.5129 190.601 65.8873 188.398 62.7553 188.119L26.4479 184.886C21.9627 184.486 19.2326 179.758 21.1293 175.674L36.4827 142.614C37.8072 139.762 37.212 136.387 34.992 134.16L9.25738 108.345C6.07823 105.156 7.02639 99.7785 11.1045 97.8691L44.1164 82.4128C46.9642 81.0795 48.6778 78.1113 48.4087 74.9784L45.2883 38.6611C44.9028 34.1747 49.0856 30.6649 53.437 31.8236L88.6606 41.203C91.6992 42.0121 94.9198 40.8399 96.7274 38.2669L117.681 8.44054Z" fill="url(#paint1_linear_1378_2496)"/>
|
||||
<path d="M117.681 8.44054C120.27 4.75592 125.73 4.75592 128.319 8.44054L149.273 38.2669C151.08 40.8399 154.301 42.0121 157.339 41.203L192.563 31.8236C196.914 30.6649 201.097 34.1747 200.712 38.6612L197.591 74.9784C197.322 78.1113 199.036 81.0795 201.884 82.4128L234.895 97.8691C238.974 99.7785 239.922 105.156 236.743 108.345L211.008 134.16C208.788 136.387 208.193 139.762 209.517 142.614L224.871 175.674C226.767 179.758 224.037 184.486 219.552 184.886L183.245 188.119C180.113 188.398 177.487 190.601 176.669 193.637L167.18 228.832C166.007 233.179 160.876 235.047 157.184 232.47L127.292 211.609C124.714 209.809 121.286 209.809 118.708 211.609L88.8163 232.47C85.1236 235.047 79.9927 233.179 78.8204 228.832L69.3314 193.637C68.5129 190.601 65.8873 188.398 62.7553 188.119L26.4479 184.886C21.9627 184.486 19.2326 179.758 21.1293 175.674L36.4827 142.614C37.8072 139.762 37.212 136.387 34.992 134.16L9.25738 108.345C6.07823 105.156 7.02639 99.7785 11.1045 97.8691L44.1164 82.4128C46.9642 81.0795 48.6778 78.1113 48.4087 74.9784L45.2883 38.6611C44.9028 34.1747 49.0856 30.6649 53.437 31.8236L88.6606 41.203C91.6992 42.0121 94.9198 40.8399 96.7274 38.2669L117.681 8.44054Z" stroke="url(#paint2_linear_1378_2496)"/>
|
||||
<g opacity="0.9">
|
||||
<g style="mix-blend-mode:overlay">
|
||||
<path d="M113.207 107.103L113.873 107.307L114.295 106.754C120.652 98.4238 129.53 87.9999 139.582 79.6678C149.661 71.3136 160.772 65.1865 171.609 65.1865C172.421 65.1865 173.2 65.5092 173.775 66.0835C174.349 66.6578 174.672 67.4368 174.672 68.249C174.672 79.0868 168.546 90.1972 160.192 100.276C151.861 110.328 141.437 119.207 133.105 125.563L132.551 125.985L132.755 126.651C134.104 131.057 134.402 135.718 133.623 140.26C132.844 144.802 131.011 149.098 128.271 152.803C125.531 156.508 121.96 159.519 117.845 161.594C113.73 163.668 109.186 164.749 104.578 164.749H65.9851C65.3296 164.748 64.6916 164.538 64.1649 164.147C63.6381 163.757 63.2505 163.208 63.059 162.581C62.8674 161.954 62.8821 161.283 63.1008 160.665C63.3195 160.047 63.7307 159.515 64.274 159.149L64.2813 159.144C64.8739 158.736 75.1093 151.439 75.1093 135.28C75.1093 130.672 76.19 126.128 78.2646 122.013C80.3393 117.898 83.35 114.328 87.055 111.587C90.76 108.847 95.056 107.014 99.598 106.235C104.14 105.457 108.801 105.754 113.207 107.103ZM120.25 109.05L119.585 109.911L120.499 110.501C124.047 112.792 127.067 115.811 129.357 119.359L129.948 120.275L130.809 119.608C133.07 117.857 135.204 116.141 137.212 114.46L137.904 113.881L137.399 113.133C134.556 108.926 130.933 105.302 126.725 102.459L125.979 101.955L125.4 102.645C123.714 104.65 121.997 106.785 120.25 109.05ZM141.909 108.878L142.566 109.805L143.402 109.036C161.044 92.7992 166.529 80.4502 168.029 72.9963L168.328 71.5152L166.848 71.8195C159.403 73.3505 147.056 78.8076 130.817 96.4511L130.048 97.2872L130.975 97.9442C135.21 100.946 138.907 104.643 141.909 108.878Z" fill="black"/>
|
||||
<path d="M113.207 107.103L113.873 107.307L114.295 106.754C120.652 98.4238 129.53 87.9999 139.582 79.6678C149.661 71.3136 160.772 65.1865 171.609 65.1865C172.421 65.1865 173.2 65.5092 173.775 66.0835C174.349 66.6578 174.672 67.4368 174.672 68.249C174.672 79.0868 168.546 90.1972 160.192 100.276C151.861 110.328 141.437 119.207 133.105 125.563L132.551 125.985L132.755 126.651C134.104 131.057 134.402 135.718 133.623 140.26C132.844 144.802 131.011 149.098 128.271 152.803C125.531 156.508 121.96 159.519 117.845 161.594C113.73 163.668 109.186 164.749 104.578 164.749H65.9851C65.3296 164.748 64.6916 164.538 64.1649 164.147C63.6381 163.757 63.2505 163.208 63.059 162.581C62.8674 161.954 62.8821 161.283 63.1008 160.665C63.3195 160.047 63.7307 159.515 64.274 159.149L64.2813 159.144C64.8739 158.736 75.1093 151.439 75.1093 135.28C75.1093 130.672 76.19 126.128 78.2646 122.013C80.3393 117.898 83.35 114.328 87.055 111.587C90.76 108.847 95.056 107.014 99.598 106.235C104.14 105.457 108.801 105.754 113.207 107.103ZM120.25 109.05L119.585 109.911L120.499 110.501C124.047 112.792 127.067 115.811 129.357 119.359L129.948 120.275L130.809 119.608C133.07 117.857 135.204 116.141 137.212 114.46L137.904 113.881L137.399 113.133C134.556 108.926 130.933 105.302 126.725 102.459L125.979 101.955L125.4 102.645C123.714 104.65 121.997 106.785 120.25 109.05ZM141.909 108.878L142.566 109.805L143.402 109.036C161.044 92.7992 166.529 80.4502 168.029 72.9963L168.328 71.5152L166.848 71.8195C159.403 73.3505 147.056 78.8076 130.817 96.4511L130.048 97.2872L130.975 97.9442C135.21 100.946 138.907 104.643 141.909 108.878Z" stroke="url(#paint3_linear_1378_2496)" stroke-width="2"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_1378_2496" x1="5.63736e-07" y1="12.92" x2="246" y2="233.08" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#0CF1CA"/>
|
||||
<stop offset="1" stop-color="#1DCCEB"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_1378_2496" x1="19.8951" y1="-3.50306e-06" x2="226.105" y2="246" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#0DDEBB"/>
|
||||
<stop offset="1" stop-color="#052520"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear_1378_2496" x1="-1.9947e-06" y1="18.0561" x2="246" y2="227.944" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white" stop-opacity="0.7"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0.1"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint3_linear_1378_2496" x1="61.9253" y1="71.6411" x2="164.664" y2="169.814" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white"/>
|
||||
<stop offset="1" stop-color="white" stop-opacity="0.2"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 7.8 KiB |
@@ -10,6 +10,7 @@
|
||||
cursor: pointer;
|
||||
color: globals.$muted-color;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
&__image {
|
||||
height: 100%;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { PersonIcon } from "@primer/octicons-react";
|
||||
import cn from "classnames";
|
||||
|
||||
import "./avatar.scss";
|
||||
|
||||
@@ -14,11 +15,18 @@ export interface AvatarProps
|
||||
src?: string | null;
|
||||
}
|
||||
|
||||
export function Avatar({ size, alt, src, ...props }: AvatarProps) {
|
||||
export function Avatar({ size, alt, src, className, ...props }: AvatarProps) {
|
||||
return (
|
||||
<div className="profile-avatar" style={{ width: size, height: size }}>
|
||||
{src ? (
|
||||
<img className="profile-avatar__image" alt={alt} src={src} {...props} />
|
||||
<img
|
||||
className={cn("profile-avatar__image", className)}
|
||||
alt={alt}
|
||||
src={src}
|
||||
width={size}
|
||||
height={size}
|
||||
{...props}
|
||||
/>
|
||||
) : (
|
||||
<PersonIcon size={size * 0.7} />
|
||||
)}
|
||||
|
||||
@@ -7,7 +7,7 @@ export interface CheckboxFieldProps
|
||||
React.InputHTMLAttributes<HTMLInputElement>,
|
||||
HTMLInputElement
|
||||
> {
|
||||
label: string;
|
||||
label: string | React.ReactNode;
|
||||
}
|
||||
|
||||
export function CheckboxField({ label, ...props }: CheckboxFieldProps) {
|
||||
|
||||
@@ -10,6 +10,8 @@ export interface ConfirmationModalProps extends Omit<ModalProps, "children"> {
|
||||
|
||||
onConfirm: () => void;
|
||||
onCancel?: () => void;
|
||||
|
||||
buttonsIsDisabled?: boolean;
|
||||
}
|
||||
|
||||
export function ConfirmationModal({
|
||||
@@ -18,6 +20,7 @@ export function ConfirmationModal({
|
||||
descriptionText,
|
||||
onConfirm,
|
||||
onCancel,
|
||||
buttonsIsDisabled = false,
|
||||
...props
|
||||
}: ConfirmationModalProps) {
|
||||
const handleCancelClick = () => {
|
||||
@@ -38,7 +41,11 @@ export function ConfirmationModal({
|
||||
<Button theme="outline" onClick={handleCancelClick}>
|
||||
{cancelButtonLabel}
|
||||
</Button>
|
||||
<Button theme="danger" onClick={onConfirm}>
|
||||
<Button
|
||||
theme="danger"
|
||||
disabled={buttonsIsDisabled}
|
||||
onClick={onConfirm}
|
||||
>
|
||||
{confirmButtonLabel}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -7,7 +7,7 @@ import "./game-card.scss";
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Badge } from "../badge/badge";
|
||||
import { useCallback, useState } from "react";
|
||||
import { useCallback, useState, useMemo } from "react";
|
||||
import { useFormat, useRepacks } from "@renderer/hooks";
|
||||
import { steamUrlBuilder } from "@shared";
|
||||
|
||||
@@ -45,6 +45,15 @@ export function GameCard({ game, ...props }: GameCardProps) {
|
||||
|
||||
const { numberFormatter } = useFormat();
|
||||
|
||||
const firstThreeRepackers = useMemo(
|
||||
() => uniqueRepackers.slice(0, 3),
|
||||
[uniqueRepackers]
|
||||
);
|
||||
const remainingCount = useMemo(
|
||||
() => uniqueRepackers.length - 3,
|
||||
[uniqueRepackers]
|
||||
);
|
||||
|
||||
return (
|
||||
<button
|
||||
{...props}
|
||||
@@ -68,15 +77,24 @@ export function GameCard({ game, ...props }: GameCardProps) {
|
||||
|
||||
{uniqueRepackers.length > 0 ? (
|
||||
<ul className="game-card__download-options">
|
||||
{uniqueRepackers.map((repacker) => (
|
||||
{firstThreeRepackers.map((repacker) => (
|
||||
<li key={repacker}>
|
||||
<Badge>{repacker}</Badge>
|
||||
</li>
|
||||
))}
|
||||
{remainingCount > 0 && (
|
||||
<li>
|
||||
<Badge>
|
||||
+{remainingCount}{" "}
|
||||
{t("game_card:available", { count: remainingCount })}
|
||||
</Badge>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
) : (
|
||||
<p className="game-card__no-download-label">{t("no_downloads")}</p>
|
||||
)}
|
||||
|
||||
<div className="game-card__specifics">
|
||||
<div className="game-card__specifics-item">
|
||||
<DownloadIcon />
|
||||
@@ -84,11 +102,10 @@ export function GameCard({ game, ...props }: GameCardProps) {
|
||||
{stats ? numberFormatter.format(stats.downloadCount) : "…"}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="game-card__specifics-item">
|
||||
<PeopleIcon />
|
||||
<span>
|
||||
{stats ? numberFormatter.format(stats?.playerCount) : "…"}
|
||||
{stats ? numberFormatter.format(stats.playerCount) : "…"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { PeopleIcon } from "@primer/octicons-react";
|
||||
import { useAppSelector, useUserDetails } from "@renderer/hooks";
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { UserFriendModalTab } from "@renderer/pages/shared-modals/user-friend-modal";
|
||||
import SteamLogo from "@renderer/assets/steam-logo.svg?react";
|
||||
@@ -9,19 +9,13 @@ import { Avatar } from "../avatar/avatar";
|
||||
import { AuthPage } from "@shared";
|
||||
import "./sidebar-profile.scss";
|
||||
|
||||
const LONG_POLLING_INTERVAL = 120_000;
|
||||
|
||||
export function SidebarProfile() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { t } = useTranslation("sidebar");
|
||||
|
||||
const {
|
||||
userDetails,
|
||||
friendRequestCount,
|
||||
showFriendsModal,
|
||||
syncFriendRequests,
|
||||
} = useUserDetails();
|
||||
const { userDetails, friendRequestCount, showFriendsModal } =
|
||||
useUserDetails();
|
||||
|
||||
const { gameRunning } = useAppSelector((state) => state.gameRunning);
|
||||
|
||||
@@ -34,16 +28,6 @@ export function SidebarProfile() {
|
||||
navigate(`/profile/${userDetails.id}`);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const pollingInterval = setInterval(() => {
|
||||
syncFriendRequests();
|
||||
}, LONG_POLLING_INTERVAL);
|
||||
|
||||
return () => {
|
||||
clearInterval(pollingInterval);
|
||||
};
|
||||
}, [syncFriendRequests]);
|
||||
|
||||
const friendsButton = useMemo(() => {
|
||||
if (!userDetails) return null;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { darkenColor } from "@renderer/helpers";
|
||||
import { useAppSelector, useToast } from "@renderer/hooks";
|
||||
import type { UserProfile, UserStats } from "@types";
|
||||
import type { Badge, UserProfile, UserStats } from "@types";
|
||||
import { average } from "color.js";
|
||||
|
||||
import { createContext, useCallback, useEffect, useState } from "react";
|
||||
@@ -16,6 +16,7 @@ export interface UserProfileContext {
|
||||
getUserProfile: () => Promise<void>;
|
||||
setSelectedBackgroundImage: React.Dispatch<React.SetStateAction<string>>;
|
||||
backgroundImage: string;
|
||||
badges: Badge[];
|
||||
}
|
||||
|
||||
export const DEFAULT_USER_PROFILE_BACKGROUND = "#151515B3";
|
||||
@@ -28,6 +29,7 @@ export const userProfileContext = createContext<UserProfileContext>({
|
||||
getUserProfile: async () => {},
|
||||
setSelectedBackgroundImage: () => {},
|
||||
backgroundImage: "",
|
||||
badges: [],
|
||||
});
|
||||
|
||||
const { Provider } = userProfileContext;
|
||||
@@ -47,6 +49,7 @@ export function UserProfileContextProvider({
|
||||
const [userStats, setUserStats] = useState<UserStats | null>(null);
|
||||
|
||||
const [userProfile, setUserProfile] = useState<UserProfile | null>(null);
|
||||
const [badges, setBadges] = useState<Badge[]>([]);
|
||||
const [heroBackground, setHeroBackground] = useState(
|
||||
DEFAULT_USER_PROFILE_BACKGROUND
|
||||
);
|
||||
@@ -101,12 +104,18 @@ export function UserProfileContextProvider({
|
||||
});
|
||||
}, [navigate, getUserStats, showErrorToast, userId, t]);
|
||||
|
||||
const getBadges = useCallback(async () => {
|
||||
const badges = await window.electron.getBadges();
|
||||
setBadges(badges);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setUserProfile(null);
|
||||
setHeroBackground(DEFAULT_USER_PROFILE_BACKGROUND);
|
||||
|
||||
getUserProfile();
|
||||
}, [getUserProfile]);
|
||||
getBadges();
|
||||
}, [getUserProfile, getBadges]);
|
||||
|
||||
return (
|
||||
<Provider
|
||||
@@ -118,6 +127,7 @@ export function UserProfileContextProvider({
|
||||
setSelectedBackgroundImage,
|
||||
backgroundImage: getBackgroundImageUrl(),
|
||||
userStats,
|
||||
badges,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
14
src/renderer/src/declaration.d.ts
vendored
14
src/renderer/src/declaration.d.ts
vendored
@@ -30,6 +30,8 @@ import type {
|
||||
GameRunning,
|
||||
TorBoxUser,
|
||||
Theme,
|
||||
Badge,
|
||||
Auth,
|
||||
} from "@types";
|
||||
import type { AxiosProgressEvent } from "axios";
|
||||
import type disk from "diskusage";
|
||||
@@ -86,6 +88,11 @@ declare global {
|
||||
getDevelopers: () => Promise<string[]>;
|
||||
|
||||
/* Library */
|
||||
toggleAutomaticCloudSync: (
|
||||
shop: GameShop,
|
||||
objectId: string,
|
||||
automaticCloudSync: boolean
|
||||
) => Promise<void>;
|
||||
addGameToLibrary: (
|
||||
shop: GameShop,
|
||||
objectId: string,
|
||||
@@ -217,6 +224,7 @@ declare global {
|
||||
) => Promise<Electron.OpenDialogReturnValue>;
|
||||
showItemInFolder: (path: string) => Promise<void>;
|
||||
getFeatures: () => Promise<string[]>;
|
||||
getBadges: () => Promise<Badge[]>;
|
||||
platform: NodeJS.Platform;
|
||||
|
||||
/* Auto update */
|
||||
@@ -227,6 +235,7 @@ declare global {
|
||||
restartAndInstallUpdate: () => Promise<void>;
|
||||
|
||||
/* Auth */
|
||||
getAuth: () => Promise<Auth | null>;
|
||||
signOut: () => Promise<void>;
|
||||
openAuthWindow: (page: AuthPage) => Promise<void>;
|
||||
getSessionHash: () => Promise<string | null>;
|
||||
@@ -271,7 +280,10 @@ declare global {
|
||||
path: string
|
||||
) => Promise<{ imagePath: string; mimeType: string }>;
|
||||
getFriendRequests: () => Promise<FriendRequest[]>;
|
||||
syncFriendRequests: () => Promise<FriendRequestSync>;
|
||||
syncFriendRequests: () => Promise<void>;
|
||||
onSyncFriendRequests: (
|
||||
cb: (friendRequests: FriendRequestSync) => void
|
||||
) => () => Electron.IpcRenderer;
|
||||
updateFriendRequest: (
|
||||
userId: string,
|
||||
action: FriendRequestAction
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
setFriendRequests,
|
||||
setFriendsModalVisible,
|
||||
setFriendsModalHidden,
|
||||
setFriendRequestCount,
|
||||
} from "@renderer/features";
|
||||
import type {
|
||||
FriendRequestAction,
|
||||
@@ -15,7 +14,6 @@ import type {
|
||||
} from "@types";
|
||||
import * as Sentry from "@sentry/react";
|
||||
import { UserFriendModalTab } from "@renderer/pages/shared-modals/user-friend-modal";
|
||||
import { isFuture, isToday } from "date-fns";
|
||||
|
||||
export function useUserDetails() {
|
||||
const dispatch = useAppDispatch();
|
||||
@@ -89,24 +87,15 @@ export function useUserDetails() {
|
||||
]
|
||||
);
|
||||
|
||||
const syncFriendRequests = useCallback(async () => {
|
||||
return window.electron
|
||||
.syncFriendRequests()
|
||||
.then((sync) => {
|
||||
dispatch(setFriendRequestCount(sync.friendRequestCount));
|
||||
})
|
||||
.catch(() => {});
|
||||
}, [dispatch]);
|
||||
|
||||
const fetchFriendRequests = useCallback(async () => {
|
||||
return window.electron
|
||||
.getFriendRequests()
|
||||
.then((friendRequests) => {
|
||||
syncFriendRequests();
|
||||
window.electron.syncFriendRequests();
|
||||
dispatch(setFriendRequests(friendRequests));
|
||||
})
|
||||
.catch(() => {});
|
||||
}, [dispatch, syncFriendRequests]);
|
||||
}, [dispatch]);
|
||||
|
||||
const showFriendsModal = useCallback(
|
||||
(initialTab: UserFriendModalTab, userId: string) => {
|
||||
@@ -146,8 +135,8 @@ export function useUserDetails() {
|
||||
const unblockUser = (userId: string) => window.electron.unblockUser(userId);
|
||||
|
||||
const hasActiveSubscription = useMemo(() => {
|
||||
const expiresAt = userDetails?.subscription?.expiresAt;
|
||||
return expiresAt && (isFuture(expiresAt) || isToday(expiresAt));
|
||||
const expiresAt = new Date(userDetails?.subscription?.expiresAt ?? 0);
|
||||
return expiresAt > new Date();
|
||||
}, [userDetails]);
|
||||
|
||||
return {
|
||||
@@ -168,7 +157,6 @@ export function useUserDetails() {
|
||||
patchUser,
|
||||
sendFriendRequest,
|
||||
fetchFriendRequests,
|
||||
syncFriendRequests,
|
||||
updateFriendRequestState,
|
||||
blockUser,
|
||||
unblockUser,
|
||||
|
||||
@@ -11,6 +11,7 @@ import "@fontsource/noto-sans/500.css";
|
||||
import "@fontsource/noto-sans/700.css";
|
||||
|
||||
import "react-loading-skeleton/dist/skeleton.css";
|
||||
import "react-tooltip/dist/react-tooltip.css";
|
||||
|
||||
import { App } from "./app";
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ $logo-max-width: 200px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
gap: globals.$spacing-unit / 2;
|
||||
gap: calc(globals.$spacing-unit / 2);
|
||||
color: globals.$body-color;
|
||||
cursor: pointer;
|
||||
|
||||
|
||||
@@ -203,9 +203,10 @@ export function CloudSyncModal({ visible, onClose }: CloudSyncModalProps) {
|
||||
<div className="cloud-sync-modal__artifact-info">
|
||||
<div className="cloud-sync-modal__artifact-header">
|
||||
<h3>
|
||||
{t("backup_from", {
|
||||
date: format(artifact.createdAt, "dd/MM/yyyy"),
|
||||
})}
|
||||
{artifact.label ??
|
||||
t("backup_from", {
|
||||
date: format(artifact.createdAt, "dd/MM/yyyy"),
|
||||
})}
|
||||
</h3>
|
||||
<small>{formatBytes(artifact.artifactLengthInBytes)}</small>
|
||||
</div>
|
||||
|
||||
@@ -23,6 +23,20 @@
|
||||
}
|
||||
}
|
||||
|
||||
&__cloud-sync-label {
|
||||
display: flex;
|
||||
gap: globals.$spacing-unit;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&__cloud-sync-hydra-cloud {
|
||||
background: linear-gradient(270deg, #16b195 50%, #3e62c0 100%);
|
||||
color: #fff;
|
||||
padding: 0 globals.$spacing-unit;
|
||||
border-radius: 4px;
|
||||
font-size: globals.$small-font-size;
|
||||
}
|
||||
|
||||
&__row {
|
||||
display: flex;
|
||||
gap: globals.$spacing-unit;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useContext, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Button, Modal, TextField } from "@renderer/components";
|
||||
import { Button, CheckboxField, Modal, TextField } from "@renderer/components";
|
||||
import type { LibraryGame } from "@types";
|
||||
import { gameDetailsContext } from "@renderer/context";
|
||||
import { DeleteGameModal } from "@renderer/pages/downloads/delete-game-modal";
|
||||
@@ -34,12 +34,17 @@ export function GameOptionsModal({
|
||||
achievements,
|
||||
} = useContext(gameDetailsContext);
|
||||
|
||||
const { hasActiveSubscription } = useUserDetails();
|
||||
|
||||
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||
const [showRemoveGameModal, setShowRemoveGameModal] = useState(false);
|
||||
const [launchOptions, setLaunchOptions] = useState(game.launchOptions ?? "");
|
||||
const [showResetAchievementsModal, setShowResetAchievementsModal] =
|
||||
useState(false);
|
||||
const [isDeletingAchievements, setIsDeletingAchievements] = useState(false);
|
||||
const [automaticCloudSync, setAutomaticCloudSync] = useState(
|
||||
game.automaticCloudSync ?? false
|
||||
);
|
||||
|
||||
const {
|
||||
removeGameInstaller,
|
||||
@@ -183,6 +188,20 @@ export function GameOptionsModal({
|
||||
}
|
||||
};
|
||||
|
||||
const handleToggleAutomaticCloudSync = async (
|
||||
event: React.ChangeEvent<HTMLInputElement>
|
||||
) => {
|
||||
setAutomaticCloudSync(event.target.checked);
|
||||
|
||||
await window.electron.toggleAutomaticCloudSync(
|
||||
game.shop,
|
||||
game.objectId,
|
||||
event.target.checked
|
||||
);
|
||||
|
||||
updateGame();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<DeleteGameModal
|
||||
@@ -266,6 +285,20 @@ export function GameOptionsModal({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CheckboxField
|
||||
label={
|
||||
<div className="game-options-modal__cloud-sync-label">
|
||||
{t("enable_automatic_cloud_sync")}
|
||||
<span className="game-options-modal__cloud-sync-hydra-cloud">
|
||||
Hydra Cloud
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
checked={automaticCloudSync}
|
||||
disabled={!hasActiveSubscription || !game.executablePath}
|
||||
onChange={handleToggleAutomaticCloudSync}
|
||||
/>
|
||||
|
||||
{shouldShowWinePrefixConfiguration && (
|
||||
<div className="game-options-modal__wine-prefix">
|
||||
<div className="game-options-modal__header">
|
||||
|
||||
@@ -27,6 +27,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
&__badges {
|
||||
display: flex;
|
||||
gap: calc(globals.$spacing-unit / 2);
|
||||
}
|
||||
|
||||
&__user-information {
|
||||
display: flex;
|
||||
padding: calc(globals.$spacing-unit * 7) calc(globals.$spacing-unit * 3);
|
||||
@@ -82,12 +87,6 @@
|
||||
text-shadow: 0 0 5px rgb(0 0 0 / 40%);
|
||||
}
|
||||
|
||||
&__display-name-badges-container {
|
||||
display: flex;
|
||||
gap: globals.$spacing-unit;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&__current-game {
|
||||
&-wrapper {
|
||||
display: flex;
|
||||
|
||||
@@ -24,8 +24,8 @@ import type { FriendRequestAction } from "@types";
|
||||
import { EditProfileModal } from "../edit-profile-modal/edit-profile-modal";
|
||||
import Skeleton from "react-loading-skeleton";
|
||||
import { UploadBackgroundImageButton } from "../upload-background-image-button/upload-background-image-button";
|
||||
import { Tooltip } from "react-tooltip";
|
||||
import "./profile-hero.scss";
|
||||
import { UserBadges } from "./user-badges";
|
||||
|
||||
type FriendAction =
|
||||
| FriendRequestAction
|
||||
@@ -35,8 +35,14 @@ export function ProfileHero() {
|
||||
const [showEditProfileModal, setShowEditProfileModal] = useState(false);
|
||||
const [isPerformingAction, setIsPerformingAction] = useState(false);
|
||||
|
||||
const { isMe, getUserProfile, userProfile, heroBackground, backgroundImage } =
|
||||
useContext(userProfileContext);
|
||||
const {
|
||||
isMe,
|
||||
badges,
|
||||
getUserProfile,
|
||||
userProfile,
|
||||
heroBackground,
|
||||
backgroundImage,
|
||||
} = useContext(userProfileContext);
|
||||
const {
|
||||
signOut,
|
||||
updateFriendRequestState,
|
||||
@@ -261,14 +267,6 @@ export function ProfileHero() {
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* <ConfirmationModal
|
||||
visible
|
||||
title={t("sign_out_modal_title")}
|
||||
descriptionText={t("sign_out_modal_text")}
|
||||
confirmButtonLabel={t("sign_out")}
|
||||
cancelButtonLabel={t("cancel")}
|
||||
/> */}
|
||||
|
||||
<EditProfileModal
|
||||
visible={showEditProfileModal}
|
||||
onClose={() => setShowEditProfileModal(false)}
|
||||
@@ -312,7 +310,29 @@ export function ProfileHero() {
|
||||
<h2 className="profile-hero__display-name">
|
||||
{userProfile?.displayName}
|
||||
</h2>
|
||||
<UserBadges />
|
||||
|
||||
<div className="profile-hero__badges">
|
||||
{userProfile.badges.map((badgeName) => {
|
||||
const badge = badges.find((b) => b.name === badgeName);
|
||||
|
||||
if (!badge) return null;
|
||||
|
||||
return (
|
||||
<img
|
||||
key={badge.name}
|
||||
src={badge.badge.url}
|
||||
alt={badge.name}
|
||||
width={24}
|
||||
height={24}
|
||||
data-tooltip-place="top"
|
||||
data-tooltip-content={badge.description}
|
||||
data-tooltip-id="badge-name"
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
<Tooltip id="badge-name" />
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<Skeleton width={150} height={28} />
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
import BadgeThemeCreator from "@renderer/assets/icons/badge-theme-creator.svg?react";
|
||||
import "./profile-hero.scss";
|
||||
import { useContext } from "react";
|
||||
import { userProfileContext } from "@renderer/context";
|
||||
import { UserBadge } from "@types";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export function UserBadges() {
|
||||
const { t } = useTranslation("badge");
|
||||
const { userProfile } = useContext(userProfileContext);
|
||||
|
||||
if (!userProfile?.badges?.length) return null;
|
||||
|
||||
const getBadgeIcon = (badge: UserBadge) => {
|
||||
if (badge === "THEME_CREATOR") {
|
||||
return <BadgeThemeCreator width={24} height={24} />;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="profile-hero__display-name-badges-container">
|
||||
{userProfile.badges.map((badge) => {
|
||||
const badgeIcon = getBadgeIcon(badge);
|
||||
|
||||
if (!badgeIcon) return null;
|
||||
return (
|
||||
<div
|
||||
className={`badge__${badge.toLowerCase()}`}
|
||||
key={badge}
|
||||
title={t(`badge_description_${badge.toLowerCase()}`)}
|
||||
>
|
||||
{badgeIcon}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -20,6 +20,7 @@ export function SettingsBehavior() {
|
||||
runAtStartup: false,
|
||||
startMinimized: false,
|
||||
disableNsfwAlert: false,
|
||||
enableAutoInstall: false,
|
||||
seedAfterDownloadComplete: false,
|
||||
showHiddenAchievementsDescription: false,
|
||||
});
|
||||
@@ -34,6 +35,7 @@ export function SettingsBehavior() {
|
||||
runAtStartup: userPreferences.runAtStartup ?? false,
|
||||
startMinimized: userPreferences.startMinimized ?? false,
|
||||
disableNsfwAlert: userPreferences.disableNsfwAlert ?? false,
|
||||
enableAutoInstall: userPreferences.enableAutoInstall ?? false,
|
||||
seedAfterDownloadComplete:
|
||||
userPreferences.seedAfterDownloadComplete ?? false,
|
||||
showHiddenAchievementsDescription:
|
||||
@@ -99,6 +101,16 @@ export function SettingsBehavior() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{window.electron.platform === "linux" && (
|
||||
<CheckboxField
|
||||
label={t("enable_auto_install")}
|
||||
checked={form.enableAutoInstall}
|
||||
onChange={() =>
|
||||
handleChange({ enableAutoInstall: !form.enableAutoInstall })
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
<CheckboxField
|
||||
label={t("disable_nsfw_alert")}
|
||||
checked={form.disableNsfwAlert}
|
||||
|
||||
@@ -53,4 +53,9 @@
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__buttons-container {
|
||||
display: flex;
|
||||
gap: globals.$spacing-unit;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,20 @@
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
|
||||
import { TextField, Button, Badge } from "@renderer/components";
|
||||
import {
|
||||
TextField,
|
||||
Button,
|
||||
Badge,
|
||||
ConfirmationModal,
|
||||
} from "@renderer/components";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import type { DownloadSource } from "@types";
|
||||
import { NoEntryIcon, PlusCircleIcon, SyncIcon } from "@primer/octicons-react";
|
||||
import {
|
||||
NoEntryIcon,
|
||||
PlusCircleIcon,
|
||||
SyncIcon,
|
||||
TrashIcon,
|
||||
} from "@primer/octicons-react";
|
||||
import { AddDownloadSourceModal } from "./add-download-source-modal";
|
||||
import { useAppDispatch, useRepacks, useToast } from "@renderer/hooks";
|
||||
import { DownloadSourceStatus } from "@shared";
|
||||
@@ -16,6 +26,10 @@ import { setFilters, clearFilters } from "@renderer/features";
|
||||
import "./settings-download-sources.scss";
|
||||
|
||||
export function SettingsDownloadSources() {
|
||||
const [
|
||||
showConfirmationDeleteAllSourcesModal,
|
||||
setShowConfirmationDeleteAllSourcesModal,
|
||||
] = useState(false);
|
||||
const [showAddDownloadSourceModal, setShowAddDownloadSourceModal] =
|
||||
useState(false);
|
||||
const [downloadSources, setDownloadSources] = useState<DownloadSource[]>([]);
|
||||
@@ -23,6 +37,7 @@ export function SettingsDownloadSources() {
|
||||
useState(false);
|
||||
const [isRemovingDownloadSource, setIsRemovingDownloadSource] =
|
||||
useState(false);
|
||||
const [isFetchingSources, setIsFetchingSources] = useState(true);
|
||||
|
||||
const { sourceUrl, clearSourceUrl } = useContext(settingsContext);
|
||||
|
||||
@@ -41,6 +56,9 @@ export function SettingsDownloadSources() {
|
||||
.sortBy("createdAt")
|
||||
.then((sources) => {
|
||||
setDownloadSources(sources.reverse());
|
||||
})
|
||||
.finally(() => {
|
||||
setIsFetchingSources(false);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -68,6 +86,25 @@ export function SettingsDownloadSources() {
|
||||
};
|
||||
};
|
||||
|
||||
const handleRemoveAllDownloadSources = () => {
|
||||
setIsRemovingDownloadSource(true);
|
||||
|
||||
const id = crypto.randomUUID();
|
||||
const channel = new BroadcastChannel(`download_sources:delete_all:${id}`);
|
||||
|
||||
downloadSourcesWorker.postMessage(["DELETE_ALL_DOWNLOAD_SOURCES", id]);
|
||||
|
||||
channel.onmessage = () => {
|
||||
showSuccessToast(t("removed_download_sources"));
|
||||
|
||||
getDownloadSources();
|
||||
setIsRemovingDownloadSource(false);
|
||||
setShowConfirmationDeleteAllSourcesModal(false);
|
||||
channel.close();
|
||||
updateRepacks();
|
||||
};
|
||||
};
|
||||
|
||||
const handleAddDownloadSource = async () => {
|
||||
await getDownloadSources();
|
||||
showSuccessToast(t("added_download_source"));
|
||||
@@ -115,6 +152,17 @@ export function SettingsDownloadSources() {
|
||||
onClose={handleModalClose}
|
||||
onAddDownloadSource={handleAddDownloadSource}
|
||||
/>
|
||||
<ConfirmationModal
|
||||
cancelButtonLabel={t("cancel_button_confirmation_delete_all_sources")}
|
||||
confirmButtonLabel={t("confirm_button_confirmation_delete_all_sources")}
|
||||
descriptionText={t("description_confirmation_delete_all_sources")}
|
||||
clickOutsideToClose={false}
|
||||
onConfirm={handleRemoveAllDownloadSources}
|
||||
visible={showConfirmationDeleteAllSourcesModal}
|
||||
title={t("title_confirmation_delete_all_sources")}
|
||||
onClose={() => setShowConfirmationDeleteAllSourcesModal(false)}
|
||||
buttonsIsDisabled={isRemovingDownloadSource}
|
||||
/>
|
||||
|
||||
<p>{t("download_sources_description")}</p>
|
||||
|
||||
@@ -125,7 +173,8 @@ export function SettingsDownloadSources() {
|
||||
disabled={
|
||||
!downloadSources.length ||
|
||||
isSyncingDownloadSources ||
|
||||
isRemovingDownloadSource
|
||||
isRemovingDownloadSource ||
|
||||
isFetchingSources
|
||||
}
|
||||
onClick={syncDownloadSources}
|
||||
>
|
||||
@@ -133,15 +182,36 @@ export function SettingsDownloadSources() {
|
||||
{t("sync_download_sources")}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
type="button"
|
||||
theme="outline"
|
||||
onClick={() => setShowAddDownloadSourceModal(true)}
|
||||
disabled={isSyncingDownloadSources}
|
||||
>
|
||||
<PlusCircleIcon />
|
||||
{t("add_download_source")}
|
||||
</Button>
|
||||
<div className="settings-download-sources__buttons-container">
|
||||
<Button
|
||||
type="button"
|
||||
theme="danger"
|
||||
onClick={() => setShowConfirmationDeleteAllSourcesModal(true)}
|
||||
disabled={
|
||||
isRemovingDownloadSource ||
|
||||
isSyncingDownloadSources ||
|
||||
!downloadSources.length ||
|
||||
isFetchingSources
|
||||
}
|
||||
>
|
||||
<TrashIcon />
|
||||
{t("button_delete_all_sources")}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
type="button"
|
||||
theme="outline"
|
||||
onClick={() => setShowAddDownloadSourceModal(true)}
|
||||
disabled={
|
||||
isSyncingDownloadSources ||
|
||||
isFetchingSources ||
|
||||
isRemovingDownloadSource
|
||||
}
|
||||
>
|
||||
<PlusCircleIcon />
|
||||
{t("add_download_source")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul className="settings-download-sources__list">
|
||||
|
||||
@@ -32,6 +32,7 @@ export function SettingsGeneral() {
|
||||
downloadNotificationsEnabled: false,
|
||||
repackUpdatesNotificationsEnabled: false,
|
||||
achievementNotificationsEnabled: false,
|
||||
friendRequestNotificationsEnabled: false,
|
||||
language: "",
|
||||
|
||||
customStyles: window.localStorage.getItem("customStyles") || "",
|
||||
@@ -82,6 +83,8 @@ export function SettingsGeneral() {
|
||||
userPreferences.repackUpdatesNotificationsEnabled ?? false,
|
||||
achievementNotificationsEnabled:
|
||||
userPreferences.achievementNotificationsEnabled ?? false,
|
||||
friendRequestNotificationsEnabled:
|
||||
userPreferences.friendRequestNotificationsEnabled ?? false,
|
||||
language: language ?? "en",
|
||||
}));
|
||||
}
|
||||
@@ -171,6 +174,17 @@ export function SettingsGeneral() {
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
||||
<CheckboxField
|
||||
label={t("enable_friend_request_notifications")}
|
||||
checked={form.friendRequestNotificationsEnabled}
|
||||
onChange={() =>
|
||||
handleChange({
|
||||
friendRequestNotificationsEnabled:
|
||||
!form.friendRequestNotificationsEnabled,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,12 +7,22 @@
|
||||
gap: globals.$spacing-unit;
|
||||
}
|
||||
|
||||
&__description {
|
||||
margin-bottom: calc(globals.$spacing-unit * 2);
|
||||
}
|
||||
|
||||
&__submit-button {
|
||||
align-self: flex-end;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&__description-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: globals.$spacing-unit;
|
||||
margin-bottom: calc(globals.$spacing-unit * 2);
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
&__create-account {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: globals.$spacing-unit;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,14 @@ import "./settings-real-debrid.scss";
|
||||
import { useAppSelector, useToast } from "@renderer/hooks";
|
||||
|
||||
import { settingsContext } from "@renderer/context";
|
||||
import { LinkExternalIcon } from "@primer/octicons-react";
|
||||
|
||||
const realDebridReferralId = import.meta.env
|
||||
.RENDERER_VITE_REAL_DEBRID_REFERRAL_ID;
|
||||
|
||||
const REAL_DEBRID_URL = realDebridReferralId
|
||||
? `https://real-debrid.com/?id=${realDebridReferralId}`
|
||||
: "https://real-debrid.com";
|
||||
const REAL_DEBRID_API_TOKEN_URL = "https://real-debrid.com/apitoken";
|
||||
|
||||
export function SettingsRealDebrid() {
|
||||
@@ -74,24 +81,43 @@ export function SettingsRealDebrid() {
|
||||
}
|
||||
};
|
||||
|
||||
const toggleRealDebrid = () => {
|
||||
const updatedValue = !form.useRealDebrid;
|
||||
|
||||
setForm((prev) => ({
|
||||
...prev,
|
||||
useRealDebrid: updatedValue,
|
||||
}));
|
||||
|
||||
if (!updatedValue) {
|
||||
updateUserPreferences({
|
||||
realDebridApiToken: null,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const isButtonDisabled =
|
||||
(form.useRealDebrid && !form.realDebridApiToken) || isLoading;
|
||||
|
||||
return (
|
||||
<form className="settings-real-debrid__form" onSubmit={handleFormSubmit}>
|
||||
<p className="settings-real-debrid__description">
|
||||
{t("real_debrid_description")}
|
||||
</p>
|
||||
<div className="settings-real-debrid__description-container">
|
||||
<p className="settings-real-debrid__description">
|
||||
{t("real_debrid_description")}
|
||||
</p>
|
||||
<Link
|
||||
to={REAL_DEBRID_URL}
|
||||
className="settings-real-debrid__create-account"
|
||||
>
|
||||
<LinkExternalIcon />
|
||||
{t("create_real_debrid_account")}
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<CheckboxField
|
||||
label={t("enable_real_debrid")}
|
||||
checked={form.useRealDebrid}
|
||||
onChange={() => {
|
||||
setForm((prev) => ({
|
||||
...prev,
|
||||
useRealDebrid: !form.useRealDebrid,
|
||||
}));
|
||||
}}
|
||||
onChange={toggleRealDebrid}
|
||||
/>
|
||||
|
||||
{form.useRealDebrid && (
|
||||
|
||||
@@ -7,12 +7,22 @@
|
||||
gap: globals.$spacing-unit;
|
||||
}
|
||||
|
||||
&__description {
|
||||
margin-bottom: calc(globals.$spacing-unit * 2);
|
||||
}
|
||||
|
||||
&__submit-button {
|
||||
align-self: flex-end;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&__description-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: globals.$spacing-unit;
|
||||
margin-bottom: calc(globals.$spacing-unit * 2);
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
&__create-account {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: globals.$spacing-unit;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,13 @@ import "./settings-torbox.scss";
|
||||
import { useAppSelector, useToast } from "@renderer/hooks";
|
||||
|
||||
import { settingsContext } from "@renderer/context";
|
||||
import { LinkExternalIcon } from "@primer/octicons-react";
|
||||
|
||||
const torBoxReferralCode = import.meta.env.RENDERER_VITE_TORBOX_REFERRAL_CODE;
|
||||
|
||||
const TORBOX_URL = torBoxReferralCode
|
||||
? `https://torbox.app/subscription?referral=${torBoxReferralCode}`
|
||||
: "https://torbox.app";
|
||||
const TORBOX_API_TOKEN_URL = "https://torbox.app/settings";
|
||||
|
||||
export function SettingsTorbox() {
|
||||
@@ -69,19 +75,37 @@ export function SettingsTorbox() {
|
||||
const isButtonDisabled =
|
||||
(form.useTorBox && !form.torBoxApiToken) || isLoading;
|
||||
|
||||
const toggleTorBox = () => {
|
||||
const updatedValue = !form.useTorBox;
|
||||
|
||||
setForm((prev) => ({
|
||||
...prev,
|
||||
useTorBox: updatedValue,
|
||||
}));
|
||||
|
||||
if (!updatedValue) {
|
||||
updateUserPreferences({
|
||||
torBoxApiToken: null,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<form className="settings-torbox__form" onSubmit={handleFormSubmit}>
|
||||
<p className="settings-torbox__description">{t("torbox_description")}</p>
|
||||
<div className="settings-torbox__description-container">
|
||||
<p className="settings-torbox__description">
|
||||
{t("torbox_description")}
|
||||
</p>
|
||||
<Link to={TORBOX_URL} className="settings-torbox__create-account">
|
||||
<LinkExternalIcon />
|
||||
{t("create_torbox_account")}
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<CheckboxField
|
||||
label={t("enable_torbox")}
|
||||
checked={form.useTorBox}
|
||||
onChange={() =>
|
||||
setForm((prev) => ({
|
||||
...prev,
|
||||
useTorBox: !form.useTorBox,
|
||||
}))
|
||||
}
|
||||
onChange={toggleTorBox}
|
||||
/>
|
||||
|
||||
{form.useTorBox && (
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
@use "../../scss/globals.scss";
|
||||
@use "sass:color";
|
||||
|
||||
.theme-editor {
|
||||
display: flex;
|
||||
@@ -58,7 +59,10 @@
|
||||
gap: 8px;
|
||||
|
||||
.active {
|
||||
background-color: darken(globals.$dark-background-color, 2%);
|
||||
background-color: color.adjust(
|
||||
globals.$dark-background-color,
|
||||
$lightness: -2%
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
2
src/renderer/src/vite-env.d.ts
vendored
2
src/renderer/src/vite-env.d.ts
vendored
@@ -4,6 +4,8 @@
|
||||
interface ImportMetaEnv {
|
||||
readonly RENDERER_VITE_EXTERNAL_RESOURCES_URL: string;
|
||||
readonly RENDERER_VITE_SENTRY_DSN: string;
|
||||
readonly RENDERER_VITE_REAL_DEBRID_REFERRAL_ID: string;
|
||||
readonly RENDERER_VITE_TORBOX_REFERRAL_CODE: string;
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
|
||||
@@ -23,7 +23,8 @@ type Payload =
|
||||
| ["IMPORT_DOWNLOAD_SOURCE", string]
|
||||
| ["DELETE_DOWNLOAD_SOURCE", number]
|
||||
| ["VALIDATE_DOWNLOAD_SOURCE", string]
|
||||
| ["SYNC_DOWNLOAD_SOURCES", string];
|
||||
| ["SYNC_DOWNLOAD_SOURCES", string]
|
||||
| ["DELETE_ALL_DOWNLOAD_SOURCES", string];
|
||||
|
||||
export type SteamGamesByLetter = Record<string, { id: string; name: string }[]>;
|
||||
|
||||
@@ -114,6 +115,13 @@ const deleteDownloadSource = async (id: number) => {
|
||||
});
|
||||
};
|
||||
|
||||
const deleteAllDowloadSources = async () => {
|
||||
await db.transaction("rw", repacksTable, downloadSourcesTable, async () => {
|
||||
await repacksTable.clear();
|
||||
await downloadSourcesTable.clear();
|
||||
});
|
||||
};
|
||||
|
||||
self.onmessage = async (event: MessageEvent<Payload>) => {
|
||||
const [type, data] = event.data;
|
||||
|
||||
@@ -132,6 +140,14 @@ self.onmessage = async (event: MessageEvent<Payload>) => {
|
||||
});
|
||||
}
|
||||
|
||||
if (type === "DELETE_ALL_DOWNLOAD_SOURCES") {
|
||||
await deleteAllDowloadSources();
|
||||
|
||||
const channel = new BroadcastChannel(`download_sources:delete_all:${data}`);
|
||||
|
||||
channel.postMessage(true);
|
||||
}
|
||||
|
||||
if (type === "DELETE_DOWNLOAD_SOURCE") {
|
||||
await deleteDownloadSource(data);
|
||||
|
||||
|
||||
@@ -130,7 +130,13 @@ export interface UserProfileCurrentGame extends Omit<GameRunning, "objectId"> {
|
||||
|
||||
export type ProfileVisibility = "PUBLIC" | "PRIVATE" | "FRIENDS";
|
||||
|
||||
export type UserBadge = "THEME_CREATOR";
|
||||
export interface Badge {
|
||||
name: string;
|
||||
description: string;
|
||||
badge: {
|
||||
url: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface UserDetails {
|
||||
id: string;
|
||||
@@ -166,7 +172,7 @@ export interface UserProfile {
|
||||
quirks: {
|
||||
backupsPerGameLimit: number;
|
||||
};
|
||||
badges: UserBadge[];
|
||||
badges: string[];
|
||||
}
|
||||
|
||||
export interface UpdateProfileRequest {
|
||||
@@ -247,6 +253,7 @@ export interface GameArtifact {
|
||||
updatedAt: string;
|
||||
hostname: string;
|
||||
downloadCount: number;
|
||||
label?: string;
|
||||
}
|
||||
|
||||
export interface ComparedAchievements {
|
||||
|
||||
@@ -43,6 +43,7 @@ export interface Game {
|
||||
executablePath?: string | null;
|
||||
launchOptions?: string | null;
|
||||
favorite?: boolean;
|
||||
automaticCloudSync?: boolean;
|
||||
}
|
||||
|
||||
export interface Download {
|
||||
@@ -75,9 +76,19 @@ export interface UserPreferences {
|
||||
runAtStartup?: boolean;
|
||||
startMinimized?: boolean;
|
||||
disableNsfwAlert?: boolean;
|
||||
enableAutoInstall?: boolean;
|
||||
seedAfterDownloadComplete?: boolean;
|
||||
showHiddenAchievementsDescription?: boolean;
|
||||
downloadNotificationsEnabled?: boolean;
|
||||
repackUpdatesNotificationsEnabled?: boolean;
|
||||
achievementNotificationsEnabled?: boolean;
|
||||
friendRequestNotificationsEnabled?: boolean;
|
||||
}
|
||||
|
||||
export interface ScreenState {
|
||||
x?: number;
|
||||
y?: number;
|
||||
height: number;
|
||||
width: number;
|
||||
isMaximized: boolean;
|
||||
}
|
||||
|
||||
61
yarn.lock
61
yarn.lock
@@ -1663,6 +1663,14 @@
|
||||
"@floating-ui/core" "^1.6.0"
|
||||
"@floating-ui/utils" "^0.2.8"
|
||||
|
||||
"@floating-ui/dom@^1.6.1":
|
||||
version "1.6.13"
|
||||
resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.13.tgz#a8a938532aea27a95121ec16e667a7cbe8c59e34"
|
||||
integrity sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==
|
||||
dependencies:
|
||||
"@floating-ui/core" "^1.6.0"
|
||||
"@floating-ui/utils" "^0.2.9"
|
||||
|
||||
"@floating-ui/react-dom@^2.0.0":
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.1.2.tgz#a1349bbf6a0e5cb5ded55d023766f20a4d439a31"
|
||||
@@ -1675,6 +1683,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.8.tgz#21a907684723bbbaa5f0974cf7730bd797eb8e62"
|
||||
integrity sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==
|
||||
|
||||
"@floating-ui/utils@^0.2.9":
|
||||
version "0.2.9"
|
||||
resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.9.tgz#50dea3616bc8191fb8e112283b49eaff03e78429"
|
||||
integrity sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==
|
||||
|
||||
"@fontsource/noto-sans@^5.1.0":
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@fontsource/noto-sans/-/noto-sans-5.1.0.tgz#54a5edd1b2b8c8e17bec6a85d4ee3a53b4b89c1f"
|
||||
@@ -3559,6 +3572,11 @@ agent-base@^7.0.2, agent-base@^7.1.0:
|
||||
dependencies:
|
||||
debug "^4.3.4"
|
||||
|
||||
agent-base@^7.1.3:
|
||||
version "7.1.3"
|
||||
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.3.tgz#29435eb821bc4194633a5b89e5bc4703bafc25a1"
|
||||
integrity sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==
|
||||
|
||||
agentkeepalive@^4.2.1:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.5.0.tgz#2673ad1389b3c418c5a20c5d7364f93ca04be923"
|
||||
@@ -3895,6 +3913,13 @@ axe-core@^4.10.0:
|
||||
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.10.0.tgz#d9e56ab0147278272739a000880196cdfe113b59"
|
||||
integrity sha512-Mr2ZakwQ7XUAjp7pAwQWRhhK8mQQ6JAaNWSjmjxil0R8BPioMtQsTLOolGYkji1rcL++3dCqZA3zWqpT+9Ew6g==
|
||||
|
||||
axios-cookiejar-support@^5.0.5:
|
||||
version "5.0.5"
|
||||
resolved "https://registry.yarnpkg.com/axios-cookiejar-support/-/axios-cookiejar-support-5.0.5.tgz#6030e17b438a64e3967a5ca190ee4202481c0ecf"
|
||||
integrity sha512-jJG+p7JnOYxkVrYkCDKBrLqUmcpwHZTNQrEcIEKr5qe7YVTyPAD9nCsi1cO5LDmQpQApfS430czO+oceI3g/3g==
|
||||
dependencies:
|
||||
http-cookie-agent "^6.0.8"
|
||||
|
||||
axios@^1.7.9:
|
||||
version "1.7.9"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.9.tgz#d7d071380c132a24accda1b2cfc1535b79ec650a"
|
||||
@@ -4279,7 +4304,7 @@ classic-level@^2.0.0:
|
||||
napi-macros "^2.2.2"
|
||||
node-gyp-build "^4.3.0"
|
||||
|
||||
classnames@^2.2.1, classnames@^2.2.6, classnames@^2.5.1:
|
||||
classnames@^2.2.1, classnames@^2.2.6, classnames@^2.3.0, classnames@^2.5.1:
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b"
|
||||
integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==
|
||||
@@ -6134,6 +6159,13 @@ http-cache-semantics@^4.0.0, http-cache-semantics@^4.1.0:
|
||||
resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a"
|
||||
integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==
|
||||
|
||||
http-cookie-agent@^6.0.8:
|
||||
version "6.0.8"
|
||||
resolved "https://registry.yarnpkg.com/http-cookie-agent/-/http-cookie-agent-6.0.8.tgz#f2635638f4172c7de0c482396ea7313e9731a62b"
|
||||
integrity sha512-qnYh3yLSr2jBsTYkw11elq+T361uKAJaZ2dR4cfYZChw1dt9uL5t3zSUwehoqqVb4oldk1BpkXKm2oat8zV+oA==
|
||||
dependencies:
|
||||
agent-base "^7.1.3"
|
||||
|
||||
http-proxy-agent@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43"
|
||||
@@ -8093,6 +8125,14 @@ react-style-singleton@^2.2.1:
|
||||
invariant "^2.2.4"
|
||||
tslib "^2.0.0"
|
||||
|
||||
react-tooltip@^5.28.0:
|
||||
version "5.28.0"
|
||||
resolved "https://registry.yarnpkg.com/react-tooltip/-/react-tooltip-5.28.0.tgz#c7b5343ab2d740a428494a3d8315515af1f26f46"
|
||||
integrity sha512-R5cO3JPPXk6FRbBHMO0rI9nkUG/JKfalBSQfZedZYzmqaZQgq7GLzF8vcCWx6IhUCKg0yPqJhXIzmIO5ff15xg==
|
||||
dependencies:
|
||||
"@floating-ui/dom" "^1.6.1"
|
||||
classnames "^2.3.0"
|
||||
|
||||
react@^18.2.0:
|
||||
version "18.3.1"
|
||||
resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891"
|
||||
@@ -9127,6 +9167,18 @@ tinyexec@^0.3.0:
|
||||
resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-0.3.1.tgz#0ab0daf93b43e2c211212396bdb836b468c97c98"
|
||||
integrity sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==
|
||||
|
||||
tldts-core@^6.1.78:
|
||||
version "6.1.78"
|
||||
resolved "https://registry.yarnpkg.com/tldts-core/-/tldts-core-6.1.78.tgz#47b477d9742870daa01dbd5ff9a598a48379728c"
|
||||
integrity sha512-jS0svNsB99jR6AJBmfmEWuKIgz91Haya91Z43PATaeHJ24BkMoNRb/jlaD37VYjb0mYf6gRL/HOnvS1zEnYBiw==
|
||||
|
||||
tldts@^6.1.32:
|
||||
version "6.1.78"
|
||||
resolved "https://registry.yarnpkg.com/tldts/-/tldts-6.1.78.tgz#ee94576653a60d421ff94162c4e9060f2e62467b"
|
||||
integrity sha512-fSgYrW0ITH0SR/CqKMXIruYIPpNu5aDgUp22UhYoSrnUQwc7SBqifEBFNce7AAcygUPBo6a/gbtcguWdmko4RQ==
|
||||
dependencies:
|
||||
tldts-core "^6.1.78"
|
||||
|
||||
tmp-promise@^3.0.2:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/tmp-promise/-/tmp-promise-3.0.3.tgz#60a1a1cc98c988674fcbfd23b6e3367bdeac4ce7"
|
||||
@@ -9174,6 +9226,13 @@ tough-cookie@^4.1.4:
|
||||
universalify "^0.2.0"
|
||||
url-parse "^1.5.3"
|
||||
|
||||
tough-cookie@^5.1.1:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-5.1.1.tgz#4641c1fdbf024927e29c5532edb7b6e5377ea1f2"
|
||||
integrity sha512-Ek7HndSVkp10hmHP9V4qZO1u+pn1RU5sI0Fw+jCU3lyvuMZcgqsNgc6CmJJZyByK4Vm/qotGRJlfgAX8q+4JiA==
|
||||
dependencies:
|
||||
tldts "^6.1.32"
|
||||
|
||||
tr46@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/tr46/-/tr46-5.0.0.tgz#3b46d583613ec7283020d79019f1335723801cec"
|
||||
|
||||
Reference in New Issue
Block a user