From 953b7b3ad80dbecdabaa8efd969bc3bc529aa0d3 Mon Sep 17 00:00:00 2001 From: Gustavo Francisco Date: Mon, 20 Jan 2025 16:07:59 -0300 Subject: [PATCH 1/3] feat: updated profile game sorting --- .env.example | 2 +- .gitignore | 1 + docs/README.pt-BR.md | 4 + src/locales/en/translation.json | 18 +- src/locales/pt-BR/translation.json | 20 +- src/locales/ru/translation.json | 27 +- src/locales/tr/translation.json | 420 +++++++++++++++--- src/main/events/auth/open-auth-window.ts | 23 +- .../services/download/download-manager.ts | 12 +- src/main/services/hosters/datanodes.ts | 47 ++ src/main/services/hosters/index.ts | 1 + src/main/services/hydra-api.ts | 66 +-- src/main/services/window-manager.ts | 18 +- src/preload/index.ts | 10 +- .../components/sidebar/sidebar-profile.tsx | 5 +- src/renderer/src/constants.ts | 1 + src/renderer/src/declaration.d.ts | 5 +- .../game-details/game-details-content.tsx | 6 +- .../sidebar-section/sidebar-section.tsx | 13 +- .../profile-content/profile-content.css.ts | 40 ++ .../profile-content/profile-content.tsx | 79 +++- ...privacy.css.ts => settings-account.css.ts} | 10 +- .../src/pages/settings/settings-account.tsx | 291 ++++++++++++ .../src/pages/settings/settings-privacy.tsx | 139 ------ src/renderer/src/pages/settings/settings.tsx | 6 +- src/shared/constants.ts | 7 + src/shared/index.ts | 1 + tsconfig.web.json | 2 +- 28 files changed, 996 insertions(+), 278 deletions(-) create mode 100644 src/main/services/hosters/datanodes.ts rename src/renderer/src/pages/settings/{settings-privacy.css.ts => settings-account.css.ts} (80%) create mode 100644 src/renderer/src/pages/settings/settings-account.tsx delete mode 100644 src/renderer/src/pages/settings/settings-privacy.tsx diff --git a/.env.example b/.env.example index 3ef399f73..90a1a76bb 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,2 @@ MAIN_VITE_API_URL=API_URL -MAIN_VITE_AUTH_URL=AUTH_URL +MAIN_VITE_AUTH_URL=AUTH_URL \ No newline at end of file diff --git a/.gitignore b/.gitignore index 36e620fc8..69fd157cb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .vscode/ +.vs/ node_modules/ __pycache__ dist diff --git a/docs/README.pt-BR.md b/docs/README.pt-BR.md index f9ba9d66f..ca9428544 100644 --- a/docs/README.pt-BR.md +++ b/docs/README.pt-BR.md @@ -125,6 +125,10 @@ cd hydra yarn ``` +### Instale OpenSSL 1.1 + +[OpenSSL 1.1](https://slproweb.com/download/Win64OpenSSL-1_1_1w.exe) é exigido pelo libtorrent em ambientes Windows. + ### Instale Python 3.9 Certifique-se de ter o Python 3.9 instalado em sua máquina. Você pode baixá-lo e instalá-lo em [python.org](https://www.python.org/downloads/release/python-3913/). diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 4e3dcb37c..1baf46141 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -280,7 +280,23 @@ "launch_minimized": "Launch Hydra minimized", "disable_nsfw_alert": "Disable NSFW alert", "seed_after_download_complete": "Seed after download complete", - "show_hidden_achievement_description": "Show hidden achievements description before unlocking them" + "show_hidden_achievement_description": "Show hidden achievements description before unlocking them", + "account": "Account", + "no_users_blocked": "You have no blocked users", + "subscription_active_until": "Your Hydra Cloud is active until {{date}}", + "manage_subscription": "Manage subscription", + "update_email": "Update email", + "update_password": "Update password", + "current_email": "Current email:", + "no_email_account": "You have not set an email yet", + "account_data_updated_successfully": "Account data updated successfully", + "renew_subscription": "Renew Hydra Cloud", + "subscription_expired_at": "Your subscription expired at {{date}}", + "no_subscription": "Enjoy Hydra in the best possible way", + "become_subscriber": "Be Hydra Cloud", + "subscription_renew_cancelled": "Automatic renewal is disabled", + "subscription_renews_on": "Your subscription renews on {{date}}", + "bill_sent_until": "Your next bill will be sent until this day" }, "notifications": { "download_complete": "Download complete", diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index 2a80084f3..9e1021fca 100644 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -268,7 +268,23 @@ "launch_minimized": "Iniciar o Hydra minimizado", "disable_nsfw_alert": "Desativar alerta de conteúdo inapropriado", "seed_after_download_complete": "Semear após a conclusão do download", - "show_hidden_achievement_description": "Mostrar descrição de conquistas ocultas antes de debloqueá-las" + "show_hidden_achievement_description": "Mostrar descrição de conquistas ocultas antes de debloqueá-las", + "account": "Conta", + "no_users_blocked": "Você não bloqueou nenhum usuário", + "subscription_active_until": "Sua assinatura Hydra Cloud ficará ativa até {{date}}", + "manage_subscription": "Gerenciar assinatura", + "update_email": "Atualizar email", + "update_password": "Atualizar senha", + "current_email": "Email atual:", + "no_email_account": "Você ainda não adicionou um email a sua conta", + "account_data_updated_successfully": "Dados da conta atualizados com sucesso", + "renew_subscription": "Renovar Hydra Cloud", + "subscription_expired_at": "Sua assinatura expirou em {{date}}", + "no_subscription": "Aproveite o Hydra da melhor forma possível", + "become_subscriber": "Seja Hydra Cloud", + "subscription_renew_cancelled": "A renovação automática está desativada", + "subscription_renews_on": "Sua assinatura renova dia {{date}}", + "bill_sent_until": "Sua próxima cobrança será enviada até esse dia" }, "notifications": { "download_complete": "Download concluído", @@ -397,7 +413,7 @@ "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": "Está é uma conquista oculta", + "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:", diff --git a/src/locales/ru/translation.json b/src/locales/ru/translation.json index 741f5e16f..f96ef4957 100644 --- a/src/locales/ru/translation.json +++ b/src/locales/ru/translation.json @@ -167,6 +167,9 @@ "loading_save_preview": "Поиск сохранений…", "wine_prefix": "Префикс Wine", "wine_prefix_description": "Префикс Wine, используемый для запуска этой игры", + "launch_options": "Параметры запуска", + "launch_options_description": "Опытные пользователи могут внести изменения в параметры запуска", + "launch_options_placeholder": "Параметр не указан ", "no_download_option_info": "Информация недоступна", "backup_deletion_failed": "Не удалось удалить резервную копию", "max_number_of_artifacts_reached": "Достигнуто максимальное количество резервных копий для этой игры", @@ -175,7 +178,11 @@ "select_folder": "Выбрать папку", "backup_from": "Резервная копия от {{date}}", "custom_backup_location_set": "Установлено настраиваемое местоположение резервной копии", - "no_directory_selected": "Не выбран каталог" + "no_directory_selected": "Не выбран каталог", + "no_write_permission": "Невозможно загрузить в эту директорию. Нажмите здесь, чтобы узнать больше.", + "reset_achievements_title": "Вы уверены?", + "reset_achievements_success": "Достижения успешно сброшены", + "reset_achievements_error": "Не удалось сбросить достижения" }, "activation": { "title": "Активировать Hydra", @@ -271,7 +278,23 @@ "source_already_exists": "Этот источник уже добавлен", "user_unblocked": "Пользователь разблокирован", "seed_after_download_complete": "Раздавать после завершения загрузки", - "show_hidden_achievement_description": "Показывать описание скрытых достижений перед их получением" + "show_hidden_achievement_description": "Показывать описание скрытых достижений перед их получением", + "account": "Аккаунт", + "no_users_blocked": "У вас нет заблокированных пользователей", + "subscription_active_until": "Ваша подписка на Hydra Cloud активна до {{date}}", + "manage_subscription": "Управлять подпиской", + "update_email": "Обновить электронную почту", + "update_password": "Обновить пароль", + "current_email": "Текущий email:", + "no_email_account": "Вы еще не установили электронную почту", + "account_data_updated_successfully": "Данные учетной записи успешно обновлены", + "renew_subscription": "Обновить подписку Hydra Cloud", + "subscription_expired_at": "Срок действия вашей подписки истек в {{date}}", + "no_subscription": "Наслаждайтесь Hydra по максимуму", + "become_subscriber": "Станьте обладателем Hydra Cloud", + "subscription_renew_cancelled": "Автоматическое продление отключено", + "subscription_renews_on": "Ваша подписка продлевается на {{date}}", + "bill_sent_until": "Ваш следующий счет будет отправлен до этого дня" }, "notifications": { "download_complete": "Загрузка завершена", diff --git a/src/locales/tr/translation.json b/src/locales/tr/translation.json index 6757fdf51..6fa89c033 100644 --- a/src/locales/tr/translation.json +++ b/src/locales/tr/translation.json @@ -1,131 +1,423 @@ { "language_name": "Türkçe", + "app": { + "successfully_signed_in": "Başarıyla giriş yapıldı" + }, "home": { - "featured": "Öne çıkan", - "surprise_me": "Şaşırt beni", - "no_results": "Sonuç bulunamadı" + "featured": "Öne Çıkanlar", + "surprise_me": "Beni Şaşırt", + "no_results": "Sonuç bulunamadı", + "start_typing": "Aramak için yazmaya başlayın...", + "hot": "Şu anda popüler", + "weekly": "📅 Haftanın en iyi oyunları", + "achievements": "🏆 Tamamlanacak oyunlar" }, "sidebar": { "catalogue": "Katalog", - "downloads": "İndirmeler", + "downloads": "İndirilenler", "settings": "Ayarlar", - "my_library": "Kütüphane", - "downloading_metadata": "{{title}} (Metadata indiriliyor…)", - "paused": "{{title}} (Duraklatıldı)", + "my_library": "Kütüphanem", + "downloading_metadata": "{{title}} (Meta verileri indiriliyor…)", + "paused": "{{title}} (Durduruldu)", "downloading": "{{title}} ({{percentage}} - İndiriliyor…)", "filter": "Kütüphaneyi filtrele", - "home": "Ana menü" + "home": "Ana Sayfa", + "queued": "{{title}} (Sırada)", + "game_has_no_executable": "Oyun için bir çalıştırılabilir dosya seçilmedi", + "sign_in": "Giriş yap", + "friends": "Arkadaşlar", + "need_help": "Yardıma mı ihtiyacınız var?" }, "header": { - "search": "Ara", - "home": "Ana menü", + "search": "Oyunları ara", + "home": "Ana Sayfa", "catalogue": "Katalog", - "downloads": "İndirmeler", + "downloads": "İndirilenler", "search_results": "Arama sonuçları", - "settings": "Ayarlar" + "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." }, "bottom_panel": { - "no_downloads_in_progress": "İndirilen bir şey yok", - "downloading_metadata": "{{title}} metadatası indiriliyor…", - "downloading": "{{title}} indiriliyor… ({{percentage}} tamamlandı) - Bitiş {{eta}} - {{speed}}" + "no_downloads_in_progress": "Devam eden indirme yok", + "downloading_metadata": "{{title}} meta verileri indiriliyor…", + "downloading": "{{title}} indiriliyor… ({{percentage}} tamamlandı) - Tamamlama: {{eta}} - Hız: {{speed}}", + "calculating_eta": "{{title}} indiriliyor… ({{percentage}} tamamlandı) - Kalan süre hesaplanıyor…", + "checking_files": "{{title}} dosyaları kontrol ediliyor… ({{percentage}} tamamlandı)" }, "catalogue": { - "next_page": "Sonraki sayfa", - "previous_page": "Önceki sayfa" + "search": "Filtrele…", + "developers": "Geliştiriciler", + "genres": "Türler", + "tags": "Etiketler", + "publishers": "Yayıncılar", + "download_sources": "İndirme kaynakları", + "result_count": "{{resultCount}} sonuç", + "filter_count": "{{filterCount}} mevcut", + "clear_filters": "{{filterCount}} seçili filtreyi temizle" }, "game_details": { "open_download_options": "İndirme seçeneklerini aç", "download_options_zero": "İndirme seçeneği yok", "download_options_one": "{{count}} indirme seçeneği", "download_options_other": "{{count}} indirme seçeneği", - "updated_at": "{{updated_at}} güncellendi", - "install": "İndir", + "updated_at": "{{updated_at}} tarihinde güncellendi", + "install": "Yükle", "resume": "Devam et", - "pause": "Duraklat", + "pause": "Durdur", "cancel": "İptal et", - "remove": "Sil", - "space_left_on_disk": "Diskte {{space}} yer kaldı", - "eta": "Bitiş {{eta}}", - "downloading_metadata": "Metadata indiriliyor…", - "filter": "Repackleri filtrele", + "remove": "Kaldır", + "space_left_on_disk": "Diskte {{space}} boş alan kaldı", + "eta": "{{eta}} tahmini bitiş", + "calculating_eta": "Kalan süre hesaplanıyor…", + "downloading_metadata": "Meta veriler indiriliyor…", + "filter": "Paketleri filtrele", "requirements": "Sistem gereksinimleri", "minimum": "Minimum", "recommended": "Önerilen", - "release_date": "{{date}} tarihinde çıktı", - "publisher": "{{publisher}} tarihinde yayınlandı", - "hours": "saatler", - "minutes": "dakikalar", + "paused": "Durduruldu", + "release_date": "{{date}} tarihinde yayımlandı", + "publisher": "{{publisher}} tarafından yayımlandı", + "hours": "saat", + "minutes": "dakika", "amount_hours": "{{amount}} saat", "amount_minutes": "{{amount}} dakika", - "accuracy": "%{{accuracy}} doğruluk", + "accuracy": "{{accuracy}}% doğruluk", "add_to_library": "Kütüphaneye ekle", "remove_from_library": "Kütüphaneden kaldır", - "no_downloads": "İndirme yok", - "play_time": "{{amount}} oynandı", - "last_time_played": "Son oynanan {{period}}", - "not_played_yet": "Bu {{title}} hiç oynanmadı", - "next_suggestion": "Sıradaki öneri", + "no_downloads": "İndirilebilir içerik yok", + "play_time": "{{amount}} süre oynandı", + "last_time_played": "Son oynama {{period}} önce", + "not_played_yet": "{{title}} henüz oynanmadı", + "next_suggestion": "Sonraki öneri", "play": "Oyna", - "deleting": "Installer siliniyor…", + "deleting": "Yükleyici siliniyor…", "close": "Kapat", - "playing_now": "Şimdi oynanıyor", + "playing_now": "Şu anda oynanıyor", "change": "Değiştir", - "repacks_modal_description": "İndirmek istediğiiniz repacki seçin", - "select_folder_hint": "Varsayılan klasörü değiştirmek için ulaşmanız gereken ayar", - "download_now": "Şimdi" + "repacks_modal_description": "İndirmek istediğiniz paketi seçin", + "select_folder_hint": "Varsayılan klasörü değiştirmek için <0>Ayarlar bölümüne gidin", + "download_now": "Şimdi indir", + "no_shop_details": "Mağaza bilgileri alınamadı.", + "download_options": "İndirme seçenekleri", + "download_path": "İndirme yolu", + "previous_screenshot": "Önceki ekran görüntüsü", + "next_screenshot": "Sonraki ekran görüntüsü", + "screenshot": "{{number}} ekran görüntüsü", + "open_screenshot": "{{number}} ekran görüntüsünü aç", + "download_settings": "İndirme ayarları", + "downloader": "İndirici", + "select_executable": "Seç", + "no_executable_selected": "Hiçbir çalıştırılabilir dosya seçilmedi", + "open_folder": "Klasörü aç", + "open_download_location": "İndirilen dosyaları gör", + "create_shortcut": "Masaüstü kısayolu oluştur", + "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", + "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", + "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", + "download_in_progress": "İndirme devam ediyor", + "download_paused": "İndirme durduruldu", + "last_downloaded_option": "Son indirilen seçenek", + "create_shortcut_success": "Kısayol başarıyla oluşturuldu", + "create_shortcut_error": "Kısayol oluşturulurken hata oluştu", + "nsfw_content_title": "Bu oyun uygunsuz içerik içeriyor", + "nsfw_content_description": "{{title}} her yaş için uygun olmayabilecek içeriklere sahiptir. Devam etmek istediğinizden emin misiniz?", + "allow_nsfw_content": "Devam et", + "refuse_nsfw_content": "Geri dön", + "stats": "İstatistikler", + "download_count": "İndirme sayısı", + "player_count": "Aktif oyuncular", + "download_error": "Bu indirme seçeneği mevcut değil", + "download": "İndir", + "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}}", + "cloud_save": "Bulut kaydı", + "cloud_save_description": "İlerlemenizi buluta kaydedin ve herhangi bir cihazda oynamaya devam edin", + "backups": "Yedekler", + "install_backup": "Yükle", + "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ı", + "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", + "mapping_method_automatic": "Otomatik", + "mapping_method_manual": "Manuel", + "mapping_method_label": "Eşleme yöntemi", + "files_automatically_mapped": "Dosyalar otomatik olarak eşlendi", + "no_backups_created": "Bu oyun için yedek oluşturulmadı", + "manage_files": "Dosyaları yönet", + "loading_save_preview": "Kayıtlı oyunlar aranıyor…", + "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_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", + "manage_files_description": "Hangi dosyaların yedeklenip geri yükleneceğini yönetin", + "select_folder": "Klasör seç", + "backup_from": "{{date}} tarihinden yedek", + "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_title": "Emin misiniz?", + "reset_achievements_success": "Başarılar başarıyla sıfırlandı", + "reset_achievements_error": "Başarılar sıfırlanamadı" }, "activation": { - "title": "Hydra'yı aktif et", - "installation_id": "Kurulum ID'si:", - "enter_activation_code": "Aktifleştirme kodunuzu girin", - "message": "Bunu nerede soracağınızı bilmiyorsanız, buna sahip olmamanız gerekiyor.", - "activate": "Aktif et", + "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ı.", + "activate": "Aktive Et", "loading": "Yükleniyor…" }, "downloads": { - "resume": "Devam et", + "resume": "Devam Et", "pause": "Duraklat", - "eta": "Bitiş {{eta}}", + "eta": "Tamamlama {{eta}}", "paused": "Duraklatıldı", "verifying": "Doğrulanıyor…", "completed": "Tamamlandı", - "cancel": "İptal et", - "filter": "Yüklü oyunları filtrele", + "removed": "İndirilmedi", + "cancel": "İptal Et", + "filter": "İndirilen oyunları filtrele", "remove": "Kaldır", "downloading_metadata": "Metadata indiriliyor…", - "deleting": "Installer siliniyor…", - "delete": "Installer'ı sil", + "deleting": "Yükleyici siliniyor…", + "delete": "Yükleyiciyi kaldır", "delete_modal_title": "Emin misiniz?", - "delete_modal_description": "Bu bilgisayarınızdan tüm kurulum dosyalarını silecek", - "install": "Kur" + "delete_modal_description": "Bu işlem, tüm kurulum dosyalarını bilgisayarınızdan kaldıracaktır", + "install": "Kur", + "download_in_progress": "Devam ediyor", + "queued_downloads": "Sıradaki indirmeler", + "downloads_completed": "Tamamlananlar", + "queued": "Sırada", + "no_downloads_title": "Bomboş", + "no_downloads_description": "Henüz Hydra ile hiçbir şey indirmediniz, ancak başlamak için asla geç değil.", + "checking_files": "Dosyalar kontrol ediliyor…", + "seeding": "Paylaşılıyor", + "stop_seeding": "Paylaşımı durdur", + "resume_seeding": "Paylaşımı sürdür", + "options": "Yönet" }, "settings": { "downloads_path": "İndirme yolu", "change": "Güncelle", "notifications": "Bildirimler", - "enable_download_notifications": "Bir indirme bittiğinde", - "enable_repack_list_notifications": "Yeni bir repack eklendiğinde" + "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", + "launch_with_system": "Hydra'yı sistem başlatıldığında çalıştır", + "general": "Genel", + "behavior": "Davranış", + "download_sources": "İndirme kaynakları", + "language": "Dil", + "real_debrid_api_token": "API Anahtarı", + "enable_real_debrid": "Real-Debrid'i Etkinleştir", + "real_debrid_description": "Real-Debrid, yalnızca internet hızınızla sınırlı olarak hızlı dosya indirmenizi sağlayan sınırsız bir indirici.", + "real_debrid_invalid_token": "Geçersiz API anahtarı", + "real_debrid_api_token_hint": "API anahtarınızı <0>buradan alabilirsiniz", + "real_debrid_free_account_error": "\"{{username}}\" hesabı ücretsiz bir hesaptır. Lütfen Real-Debrid abonesi olun", + "real_debrid_linked_message": "\"{{username}}\" hesabı bağlandı", + "save_changes": "Değişiklikleri Kaydet", + "changes_saved": "Değişiklikler başarıyla kaydedildi", + "download_sources_description": "Hydra, indirme bağlantılarını bu kaynaklardan alacak. Kaynak URL, indirme bağlantılarını içeren bir .json dosyasına doğrudan bir bağlantı olmalıdır.", + "validate_download_source": "Doğrula", + "remove_download_source": "Kaldır", + "add_download_source": "Kaynak ekle", + "download_count_zero": "İndirme seçeneği yok", + "download_count_one": "{{countFormatted}} indirme seçeneği", + "download_count_other": "{{countFormatted}} indirme seçeneği", + "download_source_url": "İndirme kaynağı URL'si", + "add_download_source_description": ".json dosyasının URL'sini girin", + "download_source_up_to_date": "Güncel", + "download_source_errored": "Hatalı", + "sync_download_sources": "Kaynakları senkronize et", + "removed_download_source": "İndirme kaynağı kaldırıldı", + "added_download_source": "İndirme kaynağı eklendi", + "download_sources_synced": "Tüm indirme kaynakları senkronize edildi", + "insert_valid_json_url": "Geçerli bir JSON URL'si girin", + "found_download_option_zero": "Hiçbir indirme seçeneği bulunamadı", + "found_download_option_one": "{{countFormatted}} indirme seçeneği bulundu", + "found_download_option_other": "{{countFormatted}} indirme seçeneği bulundu", + "import": "İçe aktar", + "public": "Herkese açık", + "private": "Gizli", + "friends_only": "Sadece arkadaşlar", + "privacy": "Gizlilik", + "profile_visibility": "Profil görünürlüğü", + "profile_visibility_description": "Profilinizi ve kütüphanenizi kimlerin görebileceğini seçin", + "required_field": "Bu alan gereklidir", + "source_already_exists": "Bu kaynak zaten eklenmiş", + "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", + "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" }, "notifications": { "download_complete": "İndirme tamamlandı", - "game_ready_to_install": "{{title}} kuruluma hazır", + "game_ready_to_install": "{{title}} kurulmaya hazır", "repack_list_updated": "Repack listesi güncellendi", - "repack_count_one": "{{count}} yeni repack eklendi", - "repack_count_other": "{{count}} yeni repack eklendi" + "repack_count_one": "{{count}} repack eklendi", + "repack_count_other": "{{count}} repack eklendi", + "new_update_available": "Sürüm {{version}} 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ı" }, "system_tray": { - "open": "Hydra'yı aç", + "open": "Hydra'yı Aç", "quit": "Çık" }, "game_card": { - "no_downloads": "İndirme mevcut değil" + "no_downloads": "İndirilebilir içerik bulunmuyor" }, "binary_not_found_modal": { - "title": "Programlar yüklü değil", - "description": "Sisteminizde Wine veya Lutris çalıştırılabiliri bulunamadı", - "instructions": "Oyunları düzgün şekilde çalıştırmak için Linux distronuza bunlardan birini nasıl yükleyebileceğinize bakın" + "title": "Programlar Yüklü Değil", + "description": "Wine veya Lutris çalıştırılabilir dosyaları sisteminizde bulunamadı", + "instructions": "Oyunun normal çalışabilmesi için bunlardan herhangi birini Linux dağıtımınıza uygun şekilde nasıl kuracağınızı kontrol edin" }, "modal": { - "close": "Kapat tuşu" + "close": "Kapat düğmesi" + }, + "forms": { + "toggle_password_visibility": "Şifre görünürlüğünü değiştir" + }, + "user_profile": { + "amount_hours": "{{amount}} saat", + "amount_minutes": "{{amount}} dakika", + "last_time_played": "Son oynanma {{period}}", + "activity": "Son Etkinlik", + "library": "Kütüphane", + "total_play_time": "Toplam oynama süresi", + "no_recent_activity_title": "Hmmm… burada bir şey yok", + "no_recent_activity_description": "Son zamanlarda hiç oyun oynamamışsınız. Bunu değiştirmenin zamanı geldi!", + "display_name": "Görünen isim", + "saving": "Kaydediliyor", + "save": "Kaydet", + "edit_profile": "Profili Düzenle", + "saved_successfully": "Başarıyla kaydedildi", + "try_again": "Lütfen tekrar deneyin", + "sign_out_modal_title": "Emin misiniz?", + "cancel": "İptal", + "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?", + "add_friends": "Arkadaş Ekle", + "add": "Ekle", + "friend_code": "Arkadaş kodu", + "see_profile": "Profili gör", + "sending": "Gönderiliyor", + "friend_request_sent": "Arkadaşlık isteği gönderildi", + "friends": "Arkadaşlar", + "friends_list": "Arkadaş listesi", + "user_not_found": "Kullanıcı bulunamadı", + "block_user": "Kullanıcıyı engelle", + "add_friend": "Arkadaş ekle", + "request_sent": "İstek gönderildi", + "request_received": "İstek alındı", + "accept_request": "İsteği kabul et", + "ignore_request": "İsteği yok say", + "cancel_request": "İsteği iptal et", + "undo_friendship": "Arkadaşlığı sonlandır", + "request_accepted": "İstek kabul edildi", + "user_blocked_successfully": "Kullanıcı başarıyla engellendi", + "user_block_modal_text": "Bu işlem {{displayName}} adlı kullanıcıyı engelleyecek", + "blocked_users": "Engellenen kullanıcılar", + "unblock": "Engeli kaldır", + "no_friends_added": "Hiç arkadaş eklemediniz", + "pending": "Bekliyor", + "no_pending_invites": "Bekleyen davetiniz yok", + "no_blocked_users": "Engellenmiş kullanıcı yok", + "friend_code_copied": "Arkadaş kodu kopyalandı", + "undo_friendship_modal_text": "Bu işlem {{displayName}} ile arkadaşlığınızı sonlandıracak", + "privacy_hint": "Bunu kimin görebileceğini ayarlamak için <0>Ayarlar bölümüne gidin", + "locked_profile": "Bu profil gizli", + "image_process_failure": "Görüntü işleme başarısız oldu", + "required_field": "Bu alan gerekli", + "displayname_min_length": "Görünen isim en az 3 karakter uzunluğunda olmalıdır", + "displayname_max_length": "Görünen isim en fazla 50 karakter uzunluğunda olabilir", + "report_profile": "Bu profili bildir", + "report_reason": "Bu profili neden bildiriyorsunuz?", + "report_description": "Ek bilgi", + "report_description_placeholder": "Ek bilgi", + "report": "Bildir", + "report_reason_hate": "Nefret söylemi", + "report_reason_sexual_content": "Cinsel içerik", + "report_reason_violence": "Şiddet", + "report_reason_spam": "Spam", + "report_reason_other": "Diğer", + "profile_reported": "Profil bildirildi", + "your_friend_code": "Arkadaş kodunuz:", + "upload_banner": "Afiş yükle", + "uploading_banner": "Afiş yükleniyor…", + "background_image_updated": "Arka plan görüntüsü güncellendi", + "stats": "İstatistikler", + "achievements": "Başarılar", + "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ı", + "earned_points": "Kazanılan puanlar", + "show_achievements_on_profile": "Başarıları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", + "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", + "earned_points": "Kazanılan puanlar:", + "available_points": "Mevcut puanlar:", + "how_to_earn_achievements_points": "Başarı 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", + "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", + "animated_profile_banner": "Animasyonlu profil afişi", + "hydra_cloud": "Hydra Cloud", + "hydra_cloud_feature_found": "Bir Hydra Cloud özelliği keşfettiniz!", + "learn_more": "Daha Fazla Bilgi Edinin" } } diff --git a/src/main/events/auth/open-auth-window.ts b/src/main/events/auth/open-auth-window.ts index e93a5a42e..0f5ec3718 100644 --- a/src/main/events/auth/open-auth-window.ts +++ b/src/main/events/auth/open-auth-window.ts @@ -1,7 +1,24 @@ +import i18next from "i18next"; import { registerEvent } from "../register-event"; -import { WindowManager } from "@main/services"; +import { HydraApi, WindowManager } from "@main/services"; +import { AuthPage } from "@shared"; -const openAuthWindow = async (_event: Electron.IpcMainInvokeEvent) => - WindowManager.openAuthWindow(); +const openAuthWindow = async ( + _event: Electron.IpcMainInvokeEvent, + page: AuthPage +) => { + const searchParams = new URLSearchParams({ + lng: i18next.language, + }); + + if ([AuthPage.UpdateEmail, AuthPage.UpdatePassword].includes(page)) { + const { accessToken } = await HydraApi.refreshToken().catch(() => { + return { accessToken: "" }; + }); + searchParams.set("token", accessToken); + } + + WindowManager.openAuthWindow(page, searchParams); +}; registerEvent("openAuthWindow", openAuthWindow); diff --git a/src/main/services/download/download-manager.ts b/src/main/services/download/download-manager.ts index 0d9f5cbb7..134a74e60 100644 --- a/src/main/services/download/download-manager.ts +++ b/src/main/services/download/download-manager.ts @@ -8,7 +8,7 @@ import { } from "@main/repository"; import { publishDownloadCompleteNotification } from "../notifications"; import type { DownloadProgress } from "@types"; -import { GofileApi, QiwiApi } from "../hosters"; +import { GofileApi, QiwiApi, DatanodesApi } from "../hosters"; import { PythonRPC } from "../python-rpc"; import { LibtorrentPayload, @@ -277,6 +277,16 @@ export class DownloadManager { save_path: game.downloadPath!, }; } + case Downloader.Datanodes: { + const downloadUrl = await DatanodesApi.getDownloadUrl(game.uri!); + + return { + action: "start", + game_id: game.id, + url: downloadUrl, + save_path: game.downloadPath!, + }; + } case Downloader.Torrent: return { action: "start", diff --git a/src/main/services/hosters/datanodes.ts b/src/main/services/hosters/datanodes.ts new file mode 100644 index 000000000..ae1444180 --- /dev/null +++ b/src/main/services/hosters/datanodes.ts @@ -0,0 +1,47 @@ +import axios, { AxiosResponse } from "axios"; + +export class DatanodesApi { + private static readonly session = axios.create({}); + + public static async getDownloadUrl(downloadUrl: string): Promise { + const parsedUrl = new URL(downloadUrl); + const pathSegments = parsedUrl.pathname.split("/"); + + const fileCode = decodeURIComponent(pathSegments[1]); + const fileName = decodeURIComponent(pathSegments[pathSegments.length - 1]); + + const payload = new URLSearchParams({ + op: "download2", + id: fileCode, + rand: "", + referer: "https://datanodes.to/download", + method_free: "Free Download >>", + method_premium: "", + adblock_detected: "", + }); + + 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, + } + ); + + if (response.status === 302) { + return response.headers["location"]; + } + + return ""; + } +} diff --git a/src/main/services/hosters/index.ts b/src/main/services/hosters/index.ts index 4c5b18035..8cff7bd25 100644 --- a/src/main/services/hosters/index.ts +++ b/src/main/services/hosters/index.ts @@ -1,2 +1,3 @@ export * from "./gofile"; export * from "./qiwi"; +export * from "./datanodes"; diff --git a/src/main/services/hydra-api.ts b/src/main/services/hydra-api.ts index 63dd9b16f..16bbc21ff 100644 --- a/src/main/services/hydra-api.ts +++ b/src/main/services/hydra-api.ts @@ -215,38 +215,42 @@ export class HydraApi { } } - private static async revalidateAccessTokenIfExpired() { - const now = new Date(); + public static async refreshToken() { + const { accessToken, expiresIn } = await this.instance + .post<{ accessToken: string; expiresIn: number }>(`/auth/refresh`, { + refreshToken: this.userAuth.refreshToken, + }) + .then((response) => response.data); + + const tokenExpirationTimestamp = + Date.now() + + this.secondsToMilliseconds(expiresIn) - + this.EXPIRATION_OFFSET_IN_MS; - if (this.userAuth.expirationTimestamp < now.getTime()) { + this.userAuth.authToken = accessToken; + this.userAuth.expirationTimestamp = tokenExpirationTimestamp; + + logger.log( + "Token refreshed. New expiration:", + this.userAuth.expirationTimestamp + ); + + userAuthRepository.upsert( + { + id: 1, + accessToken, + tokenExpirationTimestamp, + }, + ["id"] + ); + + return { accessToken, expiresIn }; + } + + private static async revalidateAccessTokenIfExpired() { + if (this.userAuth.expirationTimestamp < Date.now()) { try { - const response = await this.instance.post(`/auth/refresh`, { - refreshToken: this.userAuth.refreshToken, - }); - - const { accessToken, expiresIn } = response.data; - - const tokenExpirationTimestamp = - now.getTime() + - this.secondsToMilliseconds(expiresIn) - - this.EXPIRATION_OFFSET_IN_MS; - - this.userAuth.authToken = accessToken; - this.userAuth.expirationTimestamp = tokenExpirationTimestamp; - - logger.log( - "Token refreshed. New expiration:", - this.userAuth.expirationTimestamp - ); - - userAuthRepository.upsert( - { - id: 1, - accessToken, - tokenExpirationTimestamp, - }, - ["id"] - ); + await this.refreshToken(); } catch (err) { this.handleUnauthorizedError(err); } @@ -261,7 +265,7 @@ export class HydraApi { }; } - private static handleUnauthorizedError = (err) => { + private static readonly handleUnauthorizedError = (err) => { if (err instanceof AxiosError && err.response?.status === 401) { logger.error( "401 - Current credentials:", diff --git a/src/main/services/window-manager.ts b/src/main/services/window-manager.ts index a7cfcee2a..f7e82f072 100644 --- a/src/main/services/window-manager.ts +++ b/src/main/services/window-manager.ts @@ -9,7 +9,7 @@ import { shell, } from "electron"; import { is } from "@electron-toolkit/utils"; -import i18next, { t } from "i18next"; +import { t } from "i18next"; import path from "node:path"; import icon from "@resources/icon.png?asset"; import trayIcon from "@resources/tray-icon.png?asset"; @@ -17,6 +17,7 @@ import { gameRepository, userPreferencesRepository } from "@main/repository"; import { IsNull, Not } from "typeorm"; import { HydraApi } from "./hydra-api"; import UserAgent from "user-agents"; +import { AuthPage } from "@shared"; export class WindowManager { public static mainWindow: Electron.BrowserWindow | null = null; @@ -142,7 +143,7 @@ export class WindowManager { }); } - public static openAuthWindow() { + public static openAuthWindow(page: AuthPage, searchParams: URLSearchParams) { if (this.mainWindow) { const authWindow = new BrowserWindow({ width: 600, @@ -164,12 +165,8 @@ export class WindowManager { if (!app.isPackaged) authWindow.webContents.openDevTools(); - const searchParams = new URLSearchParams({ - lng: i18next.language, - }); - authWindow.loadURL( - `${import.meta.env.MAIN_VITE_AUTH_URL}/?${searchParams.toString()}` + `${import.meta.env.MAIN_VITE_AUTH_URL}${page}?${searchParams.toString()}` ); authWindow.once("ready-to-show", () => { @@ -181,6 +178,13 @@ export class WindowManager { authWindow.close(); HydraApi.handleExternalAuth(url); + return; + } + + if (url.startsWith("hydralauncher://update-account")) { + authWindow.close(); + + WindowManager.mainWindow?.webContents.send("on-account-updated"); } }); } diff --git a/src/preload/index.ts b/src/preload/index.ts index 316397d22..5f9bc02c1 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -15,7 +15,7 @@ import type { SeedingStatus, GameAchievement, } from "@types"; -import type { CatalogueCategory } from "@shared"; +import type { AuthPage, CatalogueCategory } from "@shared"; import type { AxiosProgressEvent } from "axios"; contextBridge.exposeInMainWorld("electron", { @@ -291,13 +291,19 @@ contextBridge.exposeInMainWorld("electron", { /* Auth */ signOut: () => ipcRenderer.invoke("signOut"), - openAuthWindow: () => ipcRenderer.invoke("openAuthWindow"), + openAuthWindow: (page: AuthPage) => + ipcRenderer.invoke("openAuthWindow", page), getSessionHash: () => ipcRenderer.invoke("getSessionHash"), onSignIn: (cb: () => void) => { const listener = (_event: Electron.IpcRendererEvent) => cb(); ipcRenderer.on("on-signin", listener); return () => ipcRenderer.removeListener("on-signin", listener); }, + onAccountUpdated: (cb: () => void) => { + const listener = (_event: Electron.IpcRendererEvent) => cb(); + ipcRenderer.on("on-account-updated", listener); + return () => ipcRenderer.removeListener("on-account-updated", listener); + }, onSignOut: (cb: () => void) => { const listener = (_event: Electron.IpcRendererEvent) => cb(); ipcRenderer.on("on-signout", listener); diff --git a/src/renderer/src/components/sidebar/sidebar-profile.tsx b/src/renderer/src/components/sidebar/sidebar-profile.tsx index 49e56ab78..3897ac545 100644 --- a/src/renderer/src/components/sidebar/sidebar-profile.tsx +++ b/src/renderer/src/components/sidebar/sidebar-profile.tsx @@ -7,6 +7,7 @@ import { useTranslation } from "react-i18next"; import { UserFriendModalTab } from "@renderer/pages/shared-modals/user-friend-modal"; import SteamLogo from "@renderer/assets/steam-logo.svg?react"; import { Avatar } from "../avatar/avatar"; +import { AuthPage } from "@shared"; const LONG_POLLING_INTERVAL = 120_000; @@ -26,11 +27,11 @@ export function SidebarProfile() { const handleProfileClick = () => { if (userDetails === null) { - window.electron.openAuthWindow(); + window.electron.openAuthWindow(AuthPage.SignIn); return; } - navigate(`/profile/${userDetails!.id}`); + navigate(`/profile/${userDetails.id}`); }; useEffect(() => { diff --git a/src/renderer/src/constants.ts b/src/renderer/src/constants.ts index 745418379..d0797caf2 100644 --- a/src/renderer/src/constants.ts +++ b/src/renderer/src/constants.ts @@ -8,6 +8,7 @@ export const DOWNLOADER_NAME = { [Downloader.Gofile]: "Gofile", [Downloader.PixelDrain]: "PixelDrain", [Downloader.Qiwi]: "Qiwi", + [Downloader.Datanodes]: "Datanodes", }; export const MAX_MINUTES_TO_SHOW_IN_PLAYTIME = 120; diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index 88f3297f1..31c99df52 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -1,4 +1,4 @@ -import type { CatalogueCategory } from "@shared"; +import type { AuthPage, CatalogueCategory } from "@shared"; import type { AppUpdaterEvent, Game, @@ -208,9 +208,10 @@ declare global { /* Auth */ signOut: () => Promise; - openAuthWindow: () => Promise; + openAuthWindow: (page: AuthPage) => Promise; getSessionHash: () => Promise; onSignIn: (cb: () => void) => () => Electron.IpcRenderer; + onAccountUpdated: (cb: () => void) => () => Electron.IpcRenderer; onSignOut: (cb: () => void) => () => Electron.IpcRenderer; /* User */ diff --git a/src/renderer/src/pages/game-details/game-details-content.tsx b/src/renderer/src/pages/game-details/game-details-content.tsx index 12495231e..bd138e81c 100644 --- a/src/renderer/src/pages/game-details/game-details-content.tsx +++ b/src/renderer/src/pages/game-details/game-details-content.tsx @@ -10,7 +10,7 @@ import { Sidebar } from "./sidebar/sidebar"; import * as styles from "./game-details.css"; import { useTranslation } from "react-i18next"; import { cloudSyncContext, gameDetailsContext } from "@renderer/context"; -import { steamUrlBuilder } from "@shared"; +import { AuthPage, steamUrlBuilder } from "@shared"; import cloudIconAnimated from "@renderer/assets/icons/cloud-animated.gif"; import { useUserDetails } from "@renderer/hooks"; @@ -69,7 +69,7 @@ export function GameDetailsContent() { }); const backgroundColor = output - ? (new Color(output).darken(0.7).toString() as string) + ? new Color(output).darken(0.7).toString() : ""; setGameColor(backgroundColor); @@ -101,7 +101,7 @@ export function GameDetailsContent() { const handleCloudSaveButtonClick = () => { if (!userDetails) { - window.electron.openAuthWindow(); + window.electron.openAuthWindow(AuthPage.SignIn); return; } diff --git a/src/renderer/src/pages/game-details/sidebar-section/sidebar-section.tsx b/src/renderer/src/pages/game-details/sidebar-section/sidebar-section.tsx index da9d078f0..e24f677be 100644 --- a/src/renderer/src/pages/game-details/sidebar-section/sidebar-section.tsx +++ b/src/renderer/src/pages/game-details/sidebar-section/sidebar-section.tsx @@ -1,5 +1,5 @@ import { ChevronDownIcon } from "@primer/octicons-react"; -import { useRef, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import * as styles from "./sidebar-section.css"; @@ -11,6 +11,15 @@ export interface SidebarSectionProps { export function SidebarSection({ title, children }: SidebarSectionProps) { const content = useRef(null); const [isOpen, setIsOpen] = useState(true); + const [height, setHeight] = useState(0); + + useEffect(() => { + if (content.current && content.current.scrollHeight !== height) { + setHeight(isOpen ? content.current.scrollHeight : 0); + } else if (!isOpen) { + setHeight(0); + } + }, [isOpen, children, height]); return (
@@ -26,7 +35,7 @@ export function SidebarSection({ title, children }: SidebarSectionProps) {
{ dispatch(setHeaderTitle("")); @@ -72,6 +80,25 @@ export function ProfileContent() { return userProfile?.relation?.status === "ACCEPTED"; }, [userProfile]); + const sortGames = (games) => { + if (sortOption === "playtime") { + return sortBy(games, (game) => -game.playTimeInSeconds); + } else if (sortOption === "achievements") { + return sortBy(games, (game) => { + return game.achievementCount > 0 + ? -(game.unlockedAchievementCount / game.achievementCount) + : 0; + }); + } else if (sortOption === "lastPlayed") { + return sortBy(games, (game) => { + return game.lastTimePlayed + ? -new Date(game.lastTimePlayed).getTime() + : 0; + }); + } + return games; + }; + const content = useMemo(() => { if (!userProfile) return null; @@ -87,6 +114,8 @@ export function ProfileContent() { const shouldShowRightContent = hasGames || userProfile.friends.length > 0; + const sortedGames = sortGames(userProfile.libraryGames || []); // Ordena os jogos conforme o critério + return (
+
+
+ +
+
+ +
+ +
+ +
+
+
    - {userProfile?.libraryGames?.map((game) => ( + {sortedGames.map((game) => ( (); + + const { + userDetails, + hasActiveSubscription, + patchUser, + fetchUserDetails, + updateUserDetails, + unblockUser, + } = useUserDetails(); + + useEffect(() => { + if (userDetails?.profileVisibility) { + setValue("profileVisibility", userDetails.profileVisibility); + } + }, [userDetails, setValue]); + + useEffect(() => { + const unsubscribe = window.electron.onAccountUpdated(() => { + fetchUserDetails().then((response) => { + if (response) { + updateUserDetails(response); + } + }); + showSuccessToast(t("account_data_updated_successfully")); + }); + + return () => { + unsubscribe(); + }; + }, [fetchUserDetails, updateUserDetails]); + + const visibilityOptions = [ + { value: "PUBLIC", label: t("public") }, + { value: "FRIENDS", label: t("friends_only") }, + { value: "PRIVATE", label: t("private") }, + ]; + + const onSubmit = async (values: FormValues) => { + await patchUser(values); + showSuccessToast(t("changes_saved")); + }; + + const handleUnblockClick = useCallback( + (id: string) => { + setIsUnblocking(true); + + unblockUser(id) + .then(() => { + fetchBlockedUsers(); + showSuccessToast(t("user_unblocked")); + }) + .finally(() => { + setIsUnblocking(false); + }); + }, + [unblockUser, fetchBlockedUsers, t, showSuccessToast] + ); + + const getHydraCloudSectionContent = () => { + const hasSubscribedBefore = Boolean(userDetails?.subscription?.expiresAt); + const isRenewalActive = userDetails?.subscription?.status === "active"; + + if (!hasSubscribedBefore) { + return { + description: {t("no_subscription")}, + callToAction: t("become_subscriber"), + }; + } + + if (hasActiveSubscription) { + return { + description: isRenewalActive ? ( + <> + + {t("subscription_renews_on", { + date: formatDate(userDetails.subscription!.expiresAt!), + })} + + {t("bill_sent_until")} + + ) : ( + <> + {t("subscription_renew_cancelled")} + + {t("subscription_active_until", { + date: formatDate(userDetails!.subscription!.expiresAt!), + })} + + + ), + callToAction: t("manage_subscription"), + }; + } + + return { + description: ( + + {t("subscription_expired_at", { + date: formatDate(userDetails!.subscription!.expiresAt!), + })} + + ), + callToAction: t("renew_subscription"), + }; + }; + + if (!userDetails) return null; + + return ( +
    + { + const handleChange = ( + event: React.ChangeEvent + ) => { + field.onChange(event); + handleSubmit(onSubmit)(); + }; + + return ( +
    + ({ + key: visiblity.value, + value: visiblity.value, + label: visiblity.label, + }))} + disabled={isSubmitting} + /> + + {t("profile_visibility_description")} +
    + ); + }} + /> + +
    +

    {t("current_email")}

    +

    {userDetails?.email ?? t("no_email_account")}

    + +
    + + + +
    +
    + +
    +

    Hydra Cloud

    +
    + {getHydraCloudSectionContent().description} +
    + + +
    + +
    +

    {t("blocked_users")}

    + + {blockedUsers.length > 0 ? ( +
      + {blockedUsers.map((user) => { + return ( +
    • +
      + + {user.displayName} +
      + + +
    • + ); + })} +
    + ) : ( + {t("no_users_blocked")} + )} +
    + + ); +} diff --git a/src/renderer/src/pages/settings/settings-privacy.tsx b/src/renderer/src/pages/settings/settings-privacy.tsx deleted file mode 100644 index b93d1d07d..000000000 --- a/src/renderer/src/pages/settings/settings-privacy.tsx +++ /dev/null @@ -1,139 +0,0 @@ -import { SelectField } from "@renderer/components"; -import { SPACING_UNIT } from "@renderer/theme.css"; -import { Controller, useForm } from "react-hook-form"; -import { useTranslation } from "react-i18next"; - -import * as styles from "./settings-privacy.css"; -import { useToast, useUserDetails } from "@renderer/hooks"; -import { useCallback, useContext, useEffect, useState } from "react"; -import { XCircleFillIcon } from "@primer/octicons-react"; -import { settingsContext } from "@renderer/context"; - -interface FormValues { - profileVisibility: "PUBLIC" | "FRIENDS" | "PRIVATE"; -} - -export function SettingsPrivacy() { - const { t } = useTranslation("settings"); - - const [isUnblocking, setIsUnblocking] = useState(false); - - const { showSuccessToast } = useToast(); - - const { blockedUsers, fetchBlockedUsers } = useContext(settingsContext); - - const { - control, - formState: { isSubmitting }, - setValue, - handleSubmit, - } = useForm(); - - const { patchUser, userDetails } = useUserDetails(); - - const { unblockUser } = useUserDetails(); - - useEffect(() => { - if (userDetails?.profileVisibility) { - setValue("profileVisibility", userDetails.profileVisibility); - } - }, [userDetails, setValue]); - - const visibilityOptions = [ - { value: "PUBLIC", label: t("public") }, - { value: "FRIENDS", label: t("friends_only") }, - { value: "PRIVATE", label: t("private") }, - ]; - - const onSubmit = async (values: FormValues) => { - await patchUser(values); - showSuccessToast(t("changes_saved")); - }; - - const handleUnblockClick = useCallback( - (id: string) => { - setIsUnblocking(true); - - unblockUser(id) - .then(() => { - fetchBlockedUsers(); - showSuccessToast(t("user_unblocked")); - }) - .finally(() => { - setIsUnblocking(false); - }); - }, - [unblockUser, fetchBlockedUsers, t, showSuccessToast] - ); - - return ( -
    - { - const handleChange = ( - event: React.ChangeEvent - ) => { - field.onChange(event); - handleSubmit(onSubmit)(); - }; - - return ( - <> - ({ - key: visiblity.value, - value: visiblity.value, - label: visiblity.label, - }))} - disabled={isSubmitting} - /> - - {t("profile_visibility_description")} - - ); - }} - /> - -

    - {t("blocked_users")} -

    - -
      - {blockedUsers.map((user) => { - return ( -
    • -
      - {user.displayName} - {user.displayName} -
      - - -
    • - ); - })} -
    - - ); -} diff --git a/src/renderer/src/pages/settings/settings.tsx b/src/renderer/src/pages/settings/settings.tsx index dffdfbaeb..5fba6c5df 100644 --- a/src/renderer/src/pages/settings/settings.tsx +++ b/src/renderer/src/pages/settings/settings.tsx @@ -11,7 +11,7 @@ import { SettingsContextConsumer, SettingsContextProvider, } from "@renderer/context"; -import { SettingsPrivacy } from "./settings-privacy"; +import { SettingsAccount } from "./settings-account"; import { useUserDetails } from "@renderer/hooks"; import { useMemo } from "react"; @@ -28,7 +28,7 @@ export default function Settings() { "Real-Debrid", ]; - if (userDetails) return [...categories, t("privacy")]; + if (userDetails) return [...categories, t("account")]; return categories; }, [userDetails, t]); @@ -53,7 +53,7 @@ export default function Settings() { return ; } - return ; + return ; }; return ( diff --git a/src/shared/constants.ts b/src/shared/constants.ts index 2d313abb9..f2bcc7939 100644 --- a/src/shared/constants.ts +++ b/src/shared/constants.ts @@ -4,6 +4,7 @@ export enum Downloader { Gofile, PixelDrain, Qiwi, + Datanodes, } export enum DownloadSourceStatus { @@ -41,3 +42,9 @@ export enum Cracker { rle = "RLE", razor1911 = "RAZOR1911", } + +export enum AuthPage { + SignIn = "/", + UpdateEmail = "/update-email", + UpdatePassword = "/update-password", +} diff --git a/src/shared/index.ts b/src/shared/index.ts index 858683919..7d612a170 100644 --- a/src/shared/index.ts +++ b/src/shared/index.ts @@ -87,6 +87,7 @@ export const getDownloadersForUri = (uri: string) => { if (uri.startsWith("https://pixeldrain.com")) return [Downloader.PixelDrain]; if (uri.startsWith("https://qiwi.gg")) return [Downloader.Qiwi]; + if (uri.startsWith("https://datanodes.to")) return [Downloader.Datanodes]; if (realDebridHosts.some((host) => uri.startsWith(host))) return [Downloader.RealDebrid]; diff --git a/tsconfig.web.json b/tsconfig.web.json index ca29bd89c..836ebdeee 100644 --- a/tsconfig.web.json +++ b/tsconfig.web.json @@ -21,4 +21,4 @@ "@shared": ["src/shared/index.ts"] } } -} +} \ No newline at end of file From 6577597ba60cf3e6093bb5b717dc4562aa639248 Mon Sep 17 00:00:00 2001 From: Gustavo Francisco <121983090+ciscosweater@users.noreply.github.com> Date: Mon, 20 Jan 2025 16:13:21 -0300 Subject: [PATCH 2/3] Update .gitignore --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 69fd157cb..36e620fc8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ .vscode/ -.vs/ node_modules/ __pycache__ dist From 438c2d96b0b58c045dc0c8219a0535052b69473b Mon Sep 17 00:00:00 2001 From: Gustavo Francisco Date: Mon, 20 Jan 2025 16:17:54 -0300 Subject: [PATCH 3/3] fix: profile content css --- .../src/pages/profile/profile-content/profile-content.css.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/renderer/src/pages/profile/profile-content/profile-content.css.ts b/src/renderer/src/pages/profile/profile-content/profile-content.css.ts index f425a7bd1..185ad85ba 100644 --- a/src/renderer/src/pages/profile/profile-content/profile-content.css.ts +++ b/src/renderer/src/pages/profile/profile-content/profile-content.css.ts @@ -225,6 +225,7 @@ export const link = style({ color: vars.color.body, ":hover": { textDecoration: "underline", + cursor: "pointer", }, });