diff --git a/.env.example b/.env.example index 991a06ff..b12e9517 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,3 @@ MAIN_VITE_API_URL=API_URL MAIN_VITE_AUTH_URL=AUTH_URL MAIN_VITE_STEAMGRIDDB_API_KEY=YOUR_API_KEY -RENDERER_VITE_INTERCOM_APP_ID=YOUR_APP_ID diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 21e184fe..0811967b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,6 +2,9 @@ name: Build on: pull_request +env: + AWS_REGION: us-east-1 + jobs: build: strategy: @@ -45,6 +48,7 @@ jobs: MAIN_VITE_CHECKOUT_URL: ${{ vars.MAIN_VITE_STAGING_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.RENDERER_VITE_EXTERNAL_RESOURCES_URL }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Build Windows @@ -56,6 +60,7 @@ jobs: MAIN_VITE_CHECKOUT_URL: ${{ vars.MAIN_VITE_STAGING_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.RENDERER_VITE_EXTERNAL_RESOURCES_URL }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Create artifact diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ff97c937..b91cc743 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -47,6 +47,7 @@ jobs: 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.RENDERER_VITE_EXTERNAL_RESOURCES_URL }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Build Windows if: matrix.os == 'windows-latest' @@ -57,6 +58,7 @@ jobs: 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.RENDERER_VITE_EXTERNAL_RESOURCES_URL }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Create artifact uses: actions/upload-artifact@v4 diff --git a/.python-version b/.python-version new file mode 100644 index 00000000..94329086 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.9.20 diff --git a/package.json b/package.json index 457f2ff8..3eb7bae4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hydralauncher", - "version": "3.0.5", + "version": "3.0.8", "description": "Hydra", "main": "./out/main/index.js", "author": "Los Broxas", @@ -36,7 +36,6 @@ "@electron-toolkit/utils": "^3.0.0", "@fontsource/noto-sans": "^5.0.22", "@hookform/resolvers": "^3.9.0", - "@intercom/messenger-js-sdk": "^0.0.14", "@primer/octicons-react": "^19.9.0", "@reduxjs/toolkit": "^2.2.3", "@vanilla-extract/css": "^1.14.2", diff --git a/src/locales/bg/translation.json b/src/locales/bg/translation.json new file mode 100644 index 00000000..a1a5306f --- /dev/null +++ b/src/locales/bg/translation.json @@ -0,0 +1,381 @@ +{ + "language_name": "Български", + "app": { + "successfully_signed_in": "Успешно вписване" + }, + "home": { + "featured": "Препоръчани", + "surprise_me": "Изненадай ме", + "no_results": "Не са намерени резултати", + "start_typing": "Търсене...", + "hot": "Актуално сега", + "weekly": "📅 Най-доброто от седмицата", + "achievements": "🏆 Игри, които да победите" + }, + "sidebar": { + "catalogue": "Каталог", + "downloads": "Изтегляния", + "settings": "Настройки", + "my_library": "Моята библиотека", + "downloading_metadata": "{{title}} (Сваляне на метаданни…)", + "paused": "{{title}} (Пауза)", + "downloading": "{{title}} ({{percentage}} - Изтегляне…)", + "filter": "Търсене по име", + "home": "Начало", + "queued": "{{title}} (Опашка)", + "game_has_no_executable": "Играта няма избран изпълним файл", + "sign_in": "Вписване", + "friends": "Приятели", + "need_help": "Имате нужда от помощ??" + }, + "header": { + "search": "Търсене", + "home": "Начало", + "catalogue": "Каталог", + "downloads": "Изтегляния", + "search_results": "Резултати от търсене", + "settings": "Настройки", + "version_available_install": "Версия {{version}} е налична. Кликни тук, за да рестартирате и инсталирате.", + "version_available_download": "Версия {{version}} е налична. Кликни тук за изтегляне." + }, + "bottom_panel": { + "no_downloads_in_progress": "Няма изтегляния в ход", + "downloading_metadata": "Сваляне на {{title}} метадата…", + "downloading": "Изтегляне на {{title}}… ({{percentage}} готово) - Остават {{eta}} - {{speed}}", + "calculating_eta": "Изтегляне на {{title}}… ({{percentage}} готово) - Изчисляване на оставащо време…", + "checking_files": "Проверка на {{title}} файловете… ({{percentage}} готово)" + }, + "catalogue": { + "next_page": "Следваща страница", + "previous_page": "Предишна страница" + }, + "game_details": { + "open_download_options": "Варианти за изтегляне", + "download_options_zero": "Няма варианти за изтегляне", + "download_options_one": "{{count}} варианти за изтегляне", + "download_options_other": "{{count}} варианти за изтегляне", + "updated_at": "Обновено на {{updated_at}}", + "install": "Инсталирай", + "resume": "Продължи", + "pause": "Пауза", + "cancel": "Отказ", + "remove": "Премахни", + "space_left_on_disk": "{{space}} място на диска", + "eta": "Заклчение {{eta}}", + "calculating_eta": "Калкулиране на оставащо време…", + "downloading_metadata": "Изтегляне на метадата…", + "filter": "Филтрирай repacks", + "requirements": "Системни изисквания", + "minimum": "Минимални", + "recommended": "Препоръчителни", + "paused": "Паузирано", + "release_date": "Издадено на {{date}}", + "publisher": "Публикувано от {{publisher}}", + "hours": "часове", + "minutes": "минути", + "amount_hours": "{{amount}} часа", + "amount_minutes": "{{amount}} минути", + "accuracy": "{{accuracy}}% точност", + "add_to_library": "Добави в библиотеката", + "remove_from_library": "Премахни от библиотеката", + "no_downloads": "Няма налични изтегляния", + "play_time": "Игрално време {{amount}}", + "last_time_played": "Последно пускане {{period}}", + "not_played_yet": "Не сте играли {{title}} все още", + "next_suggestion": "Следващо предложение", + "play": "Пускане", + "deleting": "Изтриване на инсталация…", + "close": "Затвори", + "playing_now": "Играй сега", + "change": "Промяна", + "repacks_modal_description": "Избери repack който искаш да изтеглиш", + "select_folder_hint": "За да промените стандартната папка отидете в <0>Настройки", + "download_now": "Изтегли сега", + "no_shop_details": "Не може да се извлекат данни за магазина.", + "download_options": "Опции за сваляне", + "download_path": "Път за сваляне", + "previous_screenshot": "Предишна снимка", + "next_screenshot": "Следваща снимка", + "screenshot": "Снимка {{number}}", + "open_screenshot": "Отвори снимки {{number}}", + "download_settings": "Настройки за сваляне", + "downloader": "Downloader", + "select_executable": "Избери", + "no_executable_selected": "Няма избран стартиращ файл", + "open_folder": "Отвори папка", + "open_download_location": "Виж свалените файлове", + "create_shortcut": "Пряк път на Десктопа", + "remove_files": "Премахни файловете", + "remove_from_library_title": "Сигурен ли си?", + "remove_from_library_description": "Това ще премахне {{game}} от Библиотеката", + "options": "Опции", + "executable_section_title": "Стартиращ файл", + "executable_section_description": "Пътят на файла, който ще се изпълни, когато се щракне върху \"Пускане\"", + "downloads_secion_title": "Свалени", + "downloads_section_description": "Вижте актуализации или други версии на тази игра", + "danger_zone_section_title": "Опасна зона", + "danger_zone_section_description": "Премахнете тази игра от библиотеката си или от файловете, изтеглени от Hydra", + "download_in_progress": "Изтегляне в ход", + "download_paused": "Изтеглянето е паузирано", + "last_downloaded_option": "Опция от последно изтегляне", + "create_shortcut_success": "Прекият път е създаден успешно", + "create_shortcut_error": "Грешка при създаването на пряк път", + "nsfw_content_title": "Тази игра съдържа неподходящо съдържание", + "nsfw_content_description": "{{title}} съдържа съдържание, което може да не е подходящо за всички възрасти. Сигурни ли сте, че искате да продължите?", + "allow_nsfw_content": "Продължи", + "refuse_nsfw_content": "Назад", + "stats": "Статистики", + "download_count": "Сваляния", + "player_count": "Активни играчи", + "download_error": "Тази опция за изтегляне не е налична", + "download": "Свали", + "executable_path_in_use": "Изпълнимият файл вече се използва от \"{{game}}\"", + "warning": "Внимание:", + "hydra_needs_to_remain_open": "за това изтегляне, Hydra трябва да остане отворена, когато е завършено. Ако Hydra се затвори преди завършването, ще загубите напредъка си..", + "achievements": "Постижения", + "achievements_count": "Постижения {{unlockedCount}}/{{achievementsCount}}", + "cloud_save": "Запазване в облака", + "cloud_save_description": "Запазете напредъка си в облака и продължете да играете на всяко устройство", + "backups": "Резервни копия", + "install_backup": "Инсталирай", + "delete_backup": "Изтрий", + "create_backup": "Ново копие", + "last_backup_date": "Последно копие от {{date}}", + "no_backup_preview": "Не бяха намерени запазени игри за това заглавие", + "restoring_backup": "Възстановяване на резервно копие ({{progress}} готово)…", + "uploading_backup": "Качване на резервно копие…", + "no_backups": "Все още не сте създали резервни копия за тази игра", + "backup_uploaded": "Качено резервно копие", + "backup_deleted": "Изтрито резервно копие", + "backup_restored": "Възстановен бекъп", + "see_all_achievements": "Вижте всички постижения", + "sign_in_to_see_achievements": "Влезте, за да видите постиженията", + "mapping_method_automatic": "Автоматично", + "mapping_method_manual": "Ръчно", + "mapping_method_label": "Метод на картографиране", + "files_automatically_mapped": "Автоматично картографиране на файлове", + "no_backups_created": "Не са създадени резервни копия за тази игра", + "manage_files": "Управление на файлове", + "loading_save_preview": "Търсене на запазени игри…", + "wine_prefix": "Wine Префикс", + "wine_prefix_description": "Wine prefix използван за тази игра", + "no_download_option_info": "Няма налични данни", + "backup_deletion_failed": "Неуспешно изтриване на резервно копие", + "max_number_of_artifacts_reached": "Достигнат максимален брой резервни копия за тази игра", + "achievements_not_sync": "Постиженията не са синхронизирани", + "manage_files_description": "Управлявайте кои файлове ще бъдат архивирани и възстановени", + "select_folder": "Избери папка", + "backup_from": "Резервно копие от {{date}}", + "custom_backup_location_set": "Задаване на персонализирано местоположение за архивиране" + }, + "activation": { + "title": "Активирай Hydra", + "installation_id": "Идентификатор на инсталацията:", + "enter_activation_code": "Въведете кода за активиране", + "message": "Ако не знаете къде да попитате за това, значи не трябва да го имате..", + "activate": "Активирай", + "loading": "Зареждане…" + }, + "downloads": { + "resume": "Продължи", + "pause": "Пауза", + "eta": "Conclusion {{eta}}", + "paused": "Паузирано", + "verifying": "Проверка…", + "completed": "Готово", + "removed": "Не е изтеглен", + "cancel": "Отказ", + "filter": "Филтриране на изтеглени игри", + "remove": "Премахни", + "downloading_metadata": "Изтегляне на метаданни…", + "deleting": "Изтриване на инсталатора…", + "delete": "Премахване на инсталатора", + "delete_modal_title": "Сигурени ли сте?", + "delete_modal_description": "Това ще премахне всички инсталационни файлове от компютъра ви.", + "install": "Инсталирай", + "download_in_progress": "В процес на изпълнение", + "queued_downloads": "Изтеглени файлове в опашката", + "downloads_completed": "Приключени", + "queued": "В опашка", + "no_downloads_title": "Толкова е празно", + "no_downloads_description": "Все още не сте изтеглили нищо с Hydra, но никога не е късно да започнете...", + "checking_files": "Проверка на файлове…" + }, + "settings": { + "downloads_path": "Инсталационен път", + "change": "Актуализиране", + "notifications": "Известия", + "enable_download_notifications": "Когато изтеглянето е завършено", + "enable_repack_list_notifications": "Когато се добави нов repack", + "real_debrid_api_token_label": "Real-Debrid API токен", + "quit_app_instead_hiding": "Не скривайте Hydra при затваряне", + "launch_with_system": "Стартиране на Hydra при стартиране на системата", + "general": "Общ", + "behavior": "Поведение", + "download_sources": "Източници за изтегляне", + "language": "Език", + "real_debrid_api_token": "API Токен", + "enable_real_debrid": "Включи Real-Debrid", + "real_debrid_description": "Real-Debrid е неограничен даунлоудър, който ви позволява бързо да изтегляте файлове, ограничени само от скоростта на интернет..", + "real_debrid_invalid_token": "Невалиден API токен", + "real_debrid_api_token_hint": "Вземете своя API токен <0>тук", + "real_debrid_free_account_error": "Акаунтът \"{{username}}\" е безплатен акаунт. Моля абонирай се за Real-Debrid", + "real_debrid_linked_message": "Акаунтът \"{{username}}\" е свързан", + "save_changes": "Запази промените", + "changes_saved": "Промените са успешно запазни", + "download_sources_description": "Hydra ще извлича връзките за изтегляне от тези източници. URL адресът на източника трябва да е директна връзка към .json файл, съдържащ връзките за изтегляне.", + "validate_download_source": "Валидиране", + "remove_download_source": "Премахни", + "add_download_source": "Добави източник", + "download_count_zero": "Няма опции за сваляне", + "download_count_one": "{{countFormatted}} опции за сваляне", + "download_count_other": "{{countFormatted}} опции за сваляне", + "download_source_url": "URL адрес на източника за изтегляне", + "add_download_source_description": "Вмъкнете URL адреса на файла .json", + "download_source_up_to_date": "Актуален", + "download_source_errored": "Сгрешен", + "sync_download_sources": "Синхронизирай източниците", + "removed_download_source": "Източника за сваляне е премахнат", + "added_download_source": "Добавен източник за сваляне", + "download_sources_synced": "Всички източници за сваляне са синхронизирани", + "insert_valid_json_url": "Добавете ваиден JSON линк", + "found_download_option_zero": "Няма намерени опции за сваляне", + "found_download_option_one": "Намерени {{countFormatted}} опции за сваляне", + "found_download_option_other": "Намерени {{countFormatted}} опции за сваляне", + "import": "Внеси", + "public": "Публичен", + "private": "Личен", + "friends_only": "Само за приятели", + "privacy": "Поверителност", + "profile_visibility": "Видимост на профила", + "profile_visibility_description": "Изберете кой може да вижда вашия профил и библиотека", + "required_field": "Това поле е задължително", + "source_already_exists": "Този източник вече е добавен", + "must_be_valid_url": "Източникът трябва да е валиден URL адрес.", + "blocked_users": "Блокирани потребители", + "user_unblocked": "Потребителят е бил деблокиран", + "enable_achievement_notifications": "Когато е отключено постижение", + "launch_minimized": "Стартиране на Hydra минимизирано", + "disable_nsfw_alert": "Деактивиране на предупреждението NSFW" + }, + "notifications": { + "download_complete": "Изтеглянето е завършено", + "game_ready_to_install": "{{title}} е готово за инсталиране", + "repack_list_updated": "Repack лист е обновен", + "repack_count_one": "{{count}} repack е добавен", + "repack_count_other": "{{count}} repacks добавени", + "new_update_available": "Версия {{version}} е налична", + "restart_to_install_update": "Рестартирайте Hydra, за да инсталирате актуализацията", + "notification_achievement_unlocked_title": "Отключено постижение за {{game}}", + "notification_achievement_unlocked_body": "{{achievement}} и други {{count}} са отклщчени" + }, + "system_tray": { + "open": "Отвори Hydra", + "quit": "Изход" + }, + "game_card": { + "no_downloads": "Няма налични изтегляния" + }, + "binary_not_found_modal": { + "title": "Не инсталирани програми", + "description": "Wine или Lutris изпълними файлове не бяха открити на вашата система", + "instructions": "Проверете правилния начин за инсталиране на някоя от тях на вашата дистрибуция на Linux, за да може играта да работи нормално" + }, + "modal": { + "close": "Бутон за затваряне" + }, + "forms": { + "toggle_password_visibility": "Превключване на видимостта на паролата" + }, + "user_profile": { + "amount_hours": "{{amount}} часове", + "amount_minutes": "{{amount}} минути", + "last_time_played": "Последно играно {{period}}", + "activity": "Скорошна активност", + "library": "Библиотека", + "total_play_time": "Общо време за игра: {{amount}}", + "no_recent_activity_title": "Хмм… няма нищо тук", + "no_recent_activity_description": "Не сте играли игри напоследък. Време е да промените това.!", + "display_name": "Показване на името", + "saving": "Запазване", + "save": "Запис", + "edit_profile": "Редактиране на профила", + "saved_successfully": "Запазено успешно", + "try_again": "Моля, опитайте пак", + "sign_out_modal_title": "Сигурни ли сте?", + "cancel": "Отказ", + "successfully_signed_out": "Успешно се отписахте", + "sign_out": "Отписване", + "playing_for": "В игра от {{amount}}", + "sign_out_modal_text": "Вашата библиотека е свързана с текущата ви сметка. Когато се отпишете, библиотеката ви вече няма да е видима и напредъкът няма да бъде запазен. Продължете с отписването?", + "add_friends": "Добави приятели", + "add": "Добави", + "friend_code": "Приятелски код", + "see_profile": "Виж профила", + "sending": "Изпращане", + "friend_request_sent": "Изпратена покана за приятелство", + "friends": "Приятели", + "friends_list": "Списък с приятели", + "user_not_found": "Не е намерен потребител", + "block_user": "Блокирай потребител", + "add_friend": "Добави приятел", + "request_sent": "Изпратена покана", + "request_received": "Получена покана", + "accept_request": "Приеми поканата", + "ignore_request": "Игнирирай поканата", + "cancel_request": "Откажи поканата", + "undo_friendship": "Отмяна на приятелството", + "request_accepted": "Поканата е приета", + "user_blocked_successfully": "Потребителят е блокиран успешно", + "user_block_modal_text": "Това ще блокира {{displayName}}", + "blocked_users": "Блокирани потребители", + "unblock": "Отблокирай", + "no_friends_added": "Не сте добавили приятели", + "pending": "Чакащи", + "no_pending_invites": "Нямате чакащи покани", + "no_blocked_users": "Нямате блокирани потребители", + "friend_code_copied": "Приятелския код е копиран", + "undo_friendship_modal_text": "Това ще отмени приятелството ви с {{displayName}}", + "privacy_hint": "За да настроите кой може да вижда това, отидете в <0>Настройки", + "locked_profile": "Този профил е личен", + "image_process_failure": "Грешка при обработката на изображението", + "required_field": "Това поле е задължително", + "displayname_min_length": "Името трябва да е дълго поне 3 символа", + "displayname_max_length": "Името трябва да е с дължина не повече от 50 символа.", + "report_profile": "Докладвай този профил", + "report_reason": "Защо докладвате този профил?", + "report_description": "Допълнителна информация", + "report_description_placeholder": "Допълнителна информация", + "report": "Докладвай", + "report_reason_hate": "Омразна реч", + "report_reason_sexual_content": "Сексуално съдържание", + "report_reason_violence": "Насилия", + "report_reason_spam": "Спам", + "report_reason_other": "Друго", + "profile_reported": "Профилът е докладван", + "your_friend_code": "Вашия приятелски код:", + "upload_banner": "Качи банер", + "uploading_banner": "Качване на банер…", + "background_image_updated": "Обновено фоново изображение" + }, + "achievement": { + "achievement_unlocked": "Постижението е отключено", + "user_achievements": "Постиженията на {{displayName}} ", + "your_achievements": "Вашите Постижения", + "unlocked_at": "Отключено на:", + "subscription_needed": "Необходим е абонамент за Hydra Cloud, за да видите това съдържание", + "new_achievements_unlocked": "Отключени {{achievementCount}} нови постижения от {{gameCount}} игра", + "achievement_progress": "{{unlockedCount}}/{{totalCount}} постижения", + "achievements_unlocked_for_game": "Отключени {{achievementCount}} нови постижения за {{gameTitle}}" + }, + "tour": { + "subscription_tour_title": "Hydra Cloud Абонамент", + "subscribe_now": "Абонирай се сега", + "cloud_saving": "Запазване в облака", + "cloud_achievements": "Запазете постиженията си в облака", + "animated_profile_picture": "Анимирана профилна снимка", + "premium_support": "Премиум поддръжка", + "show_and_compare_achievements": "Показвайте и сравнявайте постиженията си с тези на други потребители", + "animated_profile_banner": "Анимиран профилен банер" + } +} diff --git a/src/locales/cs/translation.json b/src/locales/cs/translation.json index b5b602c5..d839fa46 100644 --- a/src/locales/cs/translation.json +++ b/src/locales/cs/translation.json @@ -6,7 +6,11 @@ "home": { "featured": "Doporučené", "surprise_me": "Překvap mě", - "no_results": "Výsledek nenalezen" + "no_results": "Výsledek nenalezen", + "start_typing": "Začni psát pro vyhledávání...", + "hot": "Teď populární", + "weekly": "📅 Nejlepší hry týdne", + "achievements": "🏆 Hry k překonání" }, "sidebar": { "catalogue": "Katalog", @@ -20,7 +24,9 @@ "home": "Domov", "queued": "{{title}} (V řadě)", "game_has_no_executable": "Hra nemá zvolen žádný spustitelný soubor", - "sign_in": "Přihlásit se" + "sign_in": "Přihlásit se", + "friends": "Přátelé", + "need_help": "Potřebujete pomoc?" }, "header": { "search": "Vyhledat hry", @@ -113,7 +119,54 @@ "download_paused": "Stahování pozastaveno", "last_downloaded_option": "Poslední stažená možnost", "create_shortcut_success": "Zástupce vytvořen úspěšně", - "create_shortcut_error": "Chyba při pokusu vytvořit zástupce" + "create_shortcut_error": "Chyba při pokusu vytvořit zástupce", + "nsfw_content_title": "Tahle hra obsahuje nevhodný obsah", + "nsfw_content_description": "{{title}} obsahuje obsah, který by nemusel být vhodný pro všechny věkové skupiny. Jste si jisti, že chcete pokračovat?", + "allow_nsfw_content": "Pokračovat", + "refuse_nsfw_content": "Jít zpět", + "stats": "Statistiky", + "download_count": "Stažení", + "player_count": "Aktivní hráči", + "download_error": "Tahle možnost stažení není dostupná", + "download": "Stáhnout", + "executable_path_in_use": "Spustitelný soubor již používá \"{{game}}\"", + "warning": "Varování", + "hydra_needs_to_remain_open": "Pro tohle stažení, musí Hydra zůstat otevřená až do konce stahování. Pokud Hydru zavřete dříve, postup stahování bude ztracen.", + "achievements": "Achievementy", + "achievements_count": "Achievementy {{unlockedCount}}/{{achievementsCount}}", + "cloud_save": "Uložení v cloudu", + "cloud_save_description": "Uložte si svůj postup v cloud a pokračujte v hraní na jakémkoliv zářízení", + "backups": "Zálohy", + "install_backup": "Nainstalovat", + "delete_backup": "Smazat", + "create_backup": "Vytvořit zálohu", + "last_backup_date": "Poslední záloha vytvořena {{date}}", + "no_backup_preview": "Žádné zálohy nebyly nalezeny pro tuhle hru", + "restoring_backup": "Obnovuji zálohu ({{progress}} hotovo)...", + "uploading_backup": "Nahrávání zálohy...", + "no_backups": "Nemáte zatím vytvořeny žádné zálohy pro tuto hru", + "backup_uploaded": "Záloha nahrána", + "backup_deleted": "Záloha odstraněna", + "backup_restored": "Záloha obnovena", + "see_all_achievements": "Zobrazit všechny achievementy", + "sign_in_to_see_achievements": "Musíte se přihlásit pro zobrazení achievementů", + "mapping_method_automatic": "Automaticky", + "mapping_method_manual": "Manuálně", + "mapping_method_label": "Metoda mapování", + "files_automatically_mapped": "Soubory automaticky zmapovány", + "no_backups_created": "Žádné zálohy nebyly vytvořeny pro tuto hru", + "manage_files": "Spravovat soubory", + "loading_save_preview": "Hledání uložených her...", + "wine_prefix": "Wine Prefix", + "wine_prefix_description": "Wine Prefix použit pro spuštění této hry", + "no_download_option_info": "Žádné informace nejsou dostupny", + "backup_deletion_failed": "Nepovedlo se odstranit zálohu", + "max_number_of_artifacts_reached": "Dosáhli jste maximálního počtu záloh pro tuto hru", + "achievements_not_sync": "Vaše achievementy nejsou synchronizovány", + "manage_files_description": "Spravovat, které soubory budou zálohovány a obnoveny", + "select_folder": "Vybrat složku", + "backup_from": "Zálohy z {{date}}", + "custom_backup_location_set": "Vlastní umístění záloh nastaveno" }, "activation": { "title": "Aktivovat hydru", @@ -189,7 +242,21 @@ "found_download_option_zero": "Nenalezena žádná možnost stahování", "found_download_option_one": "Nalezena {{countFormatted}} možnost stahování", "found_download_option_other": "Nalezeny {{countFormatted}} možnosti stahování", - "import": "Importovat" + "import": "Importovat", + "public": "Veřejné", + "private": "Soukromé", + "friends_only": "Pouze přátelé", + "privacy": "Soukromí", + "profile_visibility": "Viditelnost profilu", + "profile_visibility_description": "Vyberte si, kdo může vidět váš profil a knihovnu", + "required_field": "Toto pole je povinné", + "source_already_exists": "Tento zdroj byl již přidán", + "must_be_valid_url": "Zdroj musí být platký odkaz URL", + "blocked_users": "Zablokovaní uživatelé", + "user_unblocked": "Uživatel byl odblokován", + "enable_achievement_notifications": "Když je odemknut achievement", + "launch_minimized": "Spustit v minimalizovaném režimu", + "disable_nsfw_alert": "Deaktivovat upozornění na nevhodný obsah" }, "notifications": { "download_complete": "Stahování dokončeno", @@ -198,7 +265,9 @@ "repack_count_one": "{{count}} repack přidán", "repack_count_other": "{{count}} repacky přidány", "new_update_available": "Version {{version}} je dostupná", - "restart_to_install_update": "Restartuj Hydru pro aktualizaci" + "restart_to_install_update": "Restartuj Hydru pro aktualizaci", + "notification_achievement_unlocked_title": "Achievement pro {{game}} byl odemknut", + "notification_achievement_unlocked_body": "{{achievement}} a dalších {{count}} byly odemknuty" }, "system_tray": { "open": "Otevřít Hydru", @@ -266,6 +335,47 @@ "no_pending_invites": "Nemáte žádné příchozí žádosti", "no_blocked_users": "Nemáte nikoho zablokovaného", "friend_code_copied": "Kód přítele zkopírován", - "undo_friendship_modal_text": "Tímto zrušíte své přátelství s {{displayName}}" + "undo_friendship_modal_text": "Tímto zrušíte své přátelství s {{displayName}}", + "privacy_hint": "Pro změnu toho, kdo tohle může vidět, jděte do <0>Nastavení", + "locked_profile": "Tento profil je soukromý", + "image_process_failure": "Nastala chyba při zpracování obrázku", + "required_field": "Toto pole je povinné", + "displayname_min_length": "Uživatelské jméno musí být minimálně 3 znaky dlouhé", + "displayname_max_length": "Uživatelské jméno musí být maximálně 50 znaků dlouhé", + "report_profile": "Nahlásit profil", + "report_reason": "Proč nahlašujete tento profil?", + "report_description": "Přídavné informace", + "report_description_placeholder": "Přídavné informace", + "report": "Nahlásit", + "report_reason_hate": "Nenávistné projevy", + "report_reason_sexual_content": "Sexuální obsah", + "report_reason_violence": "Násilí", + "report_reason_spam": "Spam", + "report_reason_other": "Ostatní", + "profile_reported": "Profil nahlášen", + "your_friend_code": "Tvůj kód přítele:", + "upload_banner": "Nahrát banner profilu", + "uploading_banner": "Nahrávání banneru", + "background_image_updated": "Obrázek pozadí byl změněn" + }, + "achievement": { + "achievement_unlocked": "Achievement odemčen", + "user_achievements": "Achievementy uživatele {{displayName}}", + "your_achievements": "Vaše achievementy", + "unlocked_at": "Odemčeno:", + "subscription_needed": "Je vyžadováno předplatné Hydra Cloud pro zobrazení tohoto obsahu", + "new_achievements_unlocked": "Odemčeno {{achievementCount}} nových achievementů z {{gameCount}} her", + "achievement_progress": "{{unlockedCount}}/{{totalCount}} achievementů", + "achievements_unlocked_for_game": "Odemčeno {{achievementCount}} nových achievementů pro {{gameTitle}}" + }, + "tour": { + "subscription_tour_title": "Předplatné Hydra Cloud", + "subscribe_now": "Připojit se", + "cloud_saving": "Ukládání v cloudu", + "cloud_achievements": "Ukládejte vaše achievementy do cloudu", + "animated_profile_picture": "Animované profilové obrázky", + "premium_support": "Prémiová podpora", + "show_and_compare_achievements": "Zobraz a porovnej achievementy s ostatními uživateli", + "animated_profile_banner": "Animovaný banner na profilu" } } diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index fa47e507..940e3185 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -105,6 +105,7 @@ "open_folder": "Open folder", "open_download_location": "See downloaded files", "create_shortcut": "Create desktop shortcut", + "clear": "Clear", "remove_files": "Remove files", "remove_from_library_title": "Are you sure?", "remove_from_library_description": "This will remove {{game}} from your library", @@ -166,7 +167,8 @@ "manage_files_description": "Manage which files will be backed up and restored", "select_folder": "Select folder", "backup_from": "Backup from {{date}}", - "custom_backup_location_set": "Custom backup location set" + "custom_backup_location_set": "Custom backup location set", + "no_directory_selected": "No directory selected" }, "activation": { "title": "Activate Hydra", diff --git a/src/locales/es/translation.json b/src/locales/es/translation.json index 2830eb0c..ab34be1f 100644 --- a/src/locales/es/translation.json +++ b/src/locales/es/translation.json @@ -100,7 +100,7 @@ "open_screenshot": "Abrir captura {{number}}", "download_settings": "Ajustes de descarga", "downloader": "Método de descarga", - "select_executable": "Seleccionar ejecutable", + "select_executable": "Seleccionar", "no_executable_selected": "No se seleccionó un ejecutable", "open_folder": "Abrir carpeta", "open_download_location": "Ver archivos descargados", @@ -166,7 +166,9 @@ "manage_files_description": "Gestiona los archivos que serán respaldados y restaurados", "select_folder": "Seleccionar carpeta", "backup_from": "Copia de seguridad de {{date}}", - "custom_backup_location_set": "Se configuró la carpeta de copia de seguridad" + "custom_backup_location_set": "Se configuró la carpeta de copia de seguridad", + "clear": "Limpiar", + "no_directory_selected": "No se seleccionó un directório" }, "activation": { "title": "Activar Hydra", @@ -254,7 +256,9 @@ "must_be_valid_url": "La fuente debe ser una URL válida.", "blocked_users": "Usuarios bloqueados", "user_unblocked": "El usuario ha sido desbloqueado", - "enable_achievement_notifications": "Cuando un logro se desbloquea" + "enable_achievement_notifications": "Cuando un logro se desbloquea", + "launch_minimized": "Iniciar Hydra minimizado", + "disable_nsfw_alert": "Desactivar alerta NSFW" }, "notifications": { "download_complete": "Descarga completada", @@ -361,8 +365,10 @@ "user_achievements": "Logros de {{displayName}}", "your_achievements": "Tus Logros", "unlocked_at": "Desbloqueado el:", - "subscription_needed": "Se necesita una suscripción a Hydra Cloud se necesita para ver este contenido", - "new_achievements_unlocked": "Desbloqueados {{achievementCount}} nuevos logros de {{gameCount}} juegos" + "subscription_needed": "Se necesita una suscripción a Hydra Cloud necesita para ver este contenido", + "new_achievements_unlocked": "Desbloqueados {{achievementCount}} nuevos logros de {{gameCount}} juegos", + "achievement_progress": "{{unlockedCount}}/{{totalCount}} logros", + "achievements_unlocked_for_game": "Se han desbloqueado {{achievementCount}} nuevos logros de {{gameTitle}}" }, "tour": { "subscription_tour_title": "Suscripción Hydra Cloud", diff --git a/src/locales/index.ts b/src/locales/index.ts index 26d15b61..c576c3c4 100644 --- a/src/locales/index.ts +++ b/src/locales/index.ts @@ -24,6 +24,7 @@ import kk from "./kk/translation.json"; import cs from "./cs/translation.json"; import nb from "./nb/translation.json"; import et from "./et/translation.json"; +import bg from "./bg/translation.json"; export default { "pt-BR": ptBR, @@ -48,6 +49,7 @@ export default { fa, ro, ca, + bg, kk, cs, nb, diff --git a/src/locales/pt-BR/translation.json b/src/locales/pt-BR/translation.json index 43c18a48..e724cdc3 100644 --- a/src/locales/pt-BR/translation.json +++ b/src/locales/pt-BR/translation.json @@ -162,7 +162,9 @@ "backup_from": "Backup de {{date}}", "custom_backup_location_set": "Localização customizada selecionada", "select_folder": "Selecione a pasta", - "manage_files_description": "Gerencie quais arquivos serão feitos backup" + "manage_files_description": "Gerencie quais arquivos serão feitos backup", + "clear": "Limpar", + "no_directory_selected": "Nenhum diretório selecionado" }, "activation": { "title": "Ativação", diff --git a/src/main/constants.ts b/src/main/constants.ts index 2b92b719..b98b5935 100644 --- a/src/main/constants.ts +++ b/src/main/constants.ts @@ -5,12 +5,12 @@ export const LUDUSAVI_MANIFEST_URL = "https://cdn.losbroxas.org/manifest.yaml"; export const defaultDownloadsPath = app.getPath("downloads"); +export const isStaging = import.meta.env.MAIN_VITE_API_URL.includes("staging"); + export const databaseDirectory = path.join(app.getPath("appData"), "hydra"); export const databasePath = path.join( databaseDirectory, - import.meta.env.MAIN_VITE_API_URL.includes("staging") - ? "hydra_test.db" - : "hydra.db" + isStaging ? "hydra_test.db" : "hydra.db" ); export const logsPath = path.join(app.getPath("appData"), "hydra", "logs"); @@ -25,4 +25,4 @@ export const achievementSoundPath = app.isPackaged export const backupsPath = path.join(app.getPath("userData"), "Backups"); -export const appVersion = app.getVersion(); +export const appVersion = app.getVersion() + (isStaging ? "-staging" : ""); diff --git a/src/main/events/catalogue/get-how-long-to-beat.ts b/src/main/events/catalogue/get-how-long-to-beat.ts index 01966afc..2a1492ef 100644 --- a/src/main/events/catalogue/get-how-long-to-beat.ts +++ b/src/main/events/catalogue/get-how-long-to-beat.ts @@ -1,23 +1,21 @@ -import type { HowLongToBeatCategory } from "@types"; -import { getHowLongToBeatGame, searchHowLongToBeat } from "@main/services"; +import type { GameShop, HowLongToBeatCategory } from "@types"; import { registerEvent } from "../register-event"; -import { formatName } from "@shared"; +import { HydraApi } from "@main/services"; const getHowLongToBeat = async ( _event: Electron.IpcMainInvokeEvent, - title: string + objectId: string, + shop: GameShop ): Promise => { - const response = await searchHowLongToBeat(title); - - const game = response.data.find((game) => { - return formatName(game.game_name) === formatName(title); + const params = new URLSearchParams({ + objectId, + shop, }); - if (!game) return null; - const howLongToBeat = await getHowLongToBeatGame(String(game.game_id)); - - return howLongToBeat; + return HydraApi.get(`/games/how-long-to-beat?${params.toString()}`, null, { + needsAuth: false, + }); }; registerEvent("getHowLongToBeat", getHowLongToBeat); diff --git a/src/main/events/index.ts b/src/main/events/index.ts index 932b80e4..eff62531 100644 --- a/src/main/events/index.ts +++ b/src/main/events/index.ts @@ -1,4 +1,4 @@ -import { appVersion, defaultDownloadsPath } from "@main/constants"; +import { appVersion, defaultDownloadsPath, isStaging } from "@main/constants"; import { ipcMain } from "electron"; import "./catalogue/get-catalogue"; @@ -72,5 +72,6 @@ import "./misc/show-item-in-folder"; ipcMain.handle("ping", () => "pong"); ipcMain.handle("getVersion", () => appVersion); +ipcMain.handle("isStaging", () => isStaging); ipcMain.handle("isPortableVersion", () => isPortableVersion()); ipcMain.handle("getDefaultDownloadsPath", () => defaultDownloadsPath); diff --git a/src/main/events/library/close-game.ts b/src/main/events/library/close-game.ts index 9c431d06..48cb681b 100644 --- a/src/main/events/library/close-game.ts +++ b/src/main/events/library/close-game.ts @@ -24,7 +24,11 @@ const closeGame = async ( if (!game) return; const gameProcess = processes.find((runningProcess) => { - return runningProcess.exe === game.executablePath; + if (process.platform === "linux") { + return runningProcess.name === game.executablePath?.split("/").at(-1); + } else { + return runningProcess.exe === game.executablePath; + } }); if (gameProcess) { diff --git a/src/main/events/library/select-game-wine-prefix.ts b/src/main/events/library/select-game-wine-prefix.ts index a75a3cb0..d9f01c08 100644 --- a/src/main/events/library/select-game-wine-prefix.ts +++ b/src/main/events/library/select-game-wine-prefix.ts @@ -5,9 +5,9 @@ import { registerEvent } from "../register-event"; const selectGameWinePrefix = async ( _event: Electron.IpcMainInvokeEvent, id: number, - winePrefixPath: string + winePrefixPath: string | null ) => { - return gameRepository.update({ id }, { winePrefixPath }); + return gameRepository.update({ id }, { winePrefixPath: winePrefixPath }); }; registerEvent("selectGameWinePrefix", selectGameWinePrefix); diff --git a/src/main/events/library/update-executable-path.ts b/src/main/events/library/update-executable-path.ts index 9be36ab1..aee80771 100644 --- a/src/main/events/library/update-executable-path.ts +++ b/src/main/events/library/update-executable-path.ts @@ -6,14 +6,18 @@ import { parseExecutablePath } from "../helpers/parse-executable-path"; const updateExecutablePath = async ( _event: Electron.IpcMainInvokeEvent, id: number, - executablePath: string + executablePath: string | null ) => { + const parsedPath = executablePath + ? parseExecutablePath(executablePath) + : null; + return gameRepository.update( { id, }, { - executablePath: parseExecutablePath(executablePath), + executablePath: parsedPath, } ); }; diff --git a/src/main/events/misc/open-checkout.ts b/src/main/events/misc/open-checkout.ts index ba48f03b..5d087be0 100644 --- a/src/main/events/misc/open-checkout.ts +++ b/src/main/events/misc/open-checkout.ts @@ -1,10 +1,16 @@ import { shell } from "electron"; import { registerEvent } from "../register-event"; -import { userAuthRepository } from "@main/repository"; +import { + userAuthRepository, + userPreferencesRepository, +} from "@main/repository"; import { HydraApi } from "@main/services"; const openCheckout = async (_event: Electron.IpcMainInvokeEvent) => { - const userAuth = await userAuthRepository.findOne({ where: { id: 1 } }); + const [userAuth, userPreferences] = await Promise.all([ + userAuthRepository.findOne({ where: { id: 1 } }), + userPreferencesRepository.findOne({ where: { id: 1 } }), + ]); if (!userAuth) { return; @@ -16,6 +22,7 @@ const openCheckout = async (_event: Electron.IpcMainInvokeEvent) => { const params = new URLSearchParams({ token: paymentToken, + lng: userPreferences?.language || "en", }); shell.openExternal( diff --git a/src/main/events/torrenting/start-game-download.ts b/src/main/events/torrenting/start-game-download.ts index 17099450..ce16e97b 100644 --- a/src/main/events/torrenting/start-game-download.ts +++ b/src/main/events/torrenting/start-game-download.ts @@ -1,5 +1,4 @@ import { registerEvent } from "../register-event"; -import parseTorrent from "parse-torrent"; import type { StartGameDownloadPayload } from "@types"; import { DownloadManager, HydraApi, logger } from "@main/services"; @@ -9,7 +8,6 @@ import { createGame } from "@main/services/library-sync"; import { steamUrlBuilder } from "@shared"; import { dataSource } from "@main/data-source"; import { DownloadQueue, Game } from "@main/entity"; -import { HydraAnalytics } from "@main/services/hydra-analytics"; const startGameDownload = async ( _event: Electron.IpcMainInvokeEvent, @@ -91,17 +89,6 @@ const startGameDownload = async ( logger.error("Failed to create game download", err); }); - if (uri.startsWith("magnet:")) { - try { - const { infoHash } = await parseTorrent(payload.uri); - if (infoHash) { - HydraAnalytics.postDownload(infoHash).catch(() => {}); - } - } catch (err) { - logger.error("Failed to parse torrent", err); - } - } - await DownloadManager.cancelDownload(updatedGame!.id); await DownloadManager.startDownload(updatedGame!); diff --git a/src/main/services/download/python-instance.ts b/src/main/services/download/python-instance.ts index f59b20b8..146c6553 100644 --- a/src/main/services/download/python-instance.ts +++ b/src/main/services/download/python-instance.ts @@ -174,8 +174,10 @@ export class PythonInstance { .then((response) => response.data); } - private static async handleRpcError(_error: unknown) { - await this.rpc.get("/healthcheck").catch(() => { + private static async handleRpcError(error: unknown) { + logger.error(error); + + return this.rpc.get("/healthcheck").catch(() => { logger.error( "RPC healthcheck failed. Killing process and starting again" ); diff --git a/src/main/services/download/types.ts b/src/main/services/download/types.ts index fd8009a2..3ddaff39 100644 --- a/src/main/services/download/types.ts +++ b/src/main/services/download/types.ts @@ -35,4 +35,5 @@ export interface LibtorrentPayload { export interface ProcessPayload { exe: string; pid: number; + name: string; } diff --git a/src/main/services/how-long-to-beat.ts b/src/main/services/how-long-to-beat.ts deleted file mode 100644 index 1e5f3279..00000000 --- a/src/main/services/how-long-to-beat.ts +++ /dev/null @@ -1,108 +0,0 @@ -import axios from "axios"; -import { requestWebPage } from "@main/helpers"; -import type { - HowLongToBeatCategory, - HowLongToBeatSearchResponse, -} from "@types"; -import { formatName } from "@shared"; -import { logger } from "./logger"; -import UserAgent from "user-agents"; - -const state = { - apiKey: null as string | null, -}; - -const getHowLongToBeatSearchApiKey = async () => { - const userAgent = new UserAgent(); - - const document = await requestWebPage("https://howlongtobeat.com/"); - const scripts = Array.from(document.querySelectorAll("script")); - - const appScript = scripts.find((script) => - script.src.startsWith("/_next/static/chunks/pages/_app") - ); - - if (!appScript) return null; - - const response = await axios.get( - `https://howlongtobeat.com${appScript.src}`, - { - headers: { - "User-Agent": userAgent.toString(), - }, - } - ); - - const results = /fetch\("\/api\/search\/"\.concat\("(.*?)"\)/gm.exec( - response.data - ); - - if (!results) return null; - - return results[1]; -}; - -export const searchHowLongToBeat = async (gameName: string) => { - state.apiKey = state.apiKey ?? (await getHowLongToBeatSearchApiKey()); - if (!state.apiKey) return { data: [] }; - - const userAgent = new UserAgent(); - - const response = await axios - .post( - `https://howlongtobeat.com/api/search/${state.apiKey}`, - { - searchType: "games", - searchTerms: formatName(gameName).split(" "), - searchPage: 1, - size: 20, - }, - { - headers: { - "User-Agent": userAgent.toString(), - Referer: "https://howlongtobeat.com/", - }, - } - ) - .catch((error) => { - logger.error("Error searching HowLongToBeat:", error?.response?.status); - return { data: { data: [] } }; - }); - - return response.data as HowLongToBeatSearchResponse; -}; - -const parseListItems = ($lis: Element[]) => { - return $lis.map(($li) => { - const title = $li.querySelector("h4")?.textContent; - const [, accuracyClassName] = Array.from(($li as HTMLElement).classList); - - const accuracy = accuracyClassName.split("time_").at(1); - - return { - title: title ?? "", - duration: $li.querySelector("h5")?.textContent ?? "", - accuracy: accuracy ?? "", - }; - }); -}; - -export const getHowLongToBeatGame = async ( - id: string -): Promise => { - const document = await requestWebPage(`https://howlongtobeat.com/game/${id}`); - - const $ul = document.querySelector(".shadow_shadow ul"); - if (!$ul) return []; - - const $lis = Array.from($ul.children); - - const [$firstLi] = $lis; - - if ($firstLi.tagName === "DIV") { - const $pcData = $lis.find(($li) => $li.textContent?.includes("PC")); - return parseListItems(Array.from($pcData?.querySelectorAll("li") ?? [])); - } - - return parseListItems($lis); -}; diff --git a/src/main/services/hydra-analytics.ts b/src/main/services/hydra-analytics.ts deleted file mode 100644 index f4a6b24c..00000000 --- a/src/main/services/hydra-analytics.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { userSubscriptionRepository } from "@main/repository"; -import axios from "axios"; -import { appVersion } from "@main/constants"; - -export class HydraAnalytics { - private static instance = axios.create({ - baseURL: import.meta.env.MAIN_VITE_ANALYTICS_API_URL, - headers: { "User-Agent": `Hydra Launcher v${appVersion}` }, - }); - - private static async hasActiveSubscription() { - const userSubscription = await userSubscriptionRepository.findOne({ - where: { id: 1 }, - }); - - return ( - userSubscription?.expiresAt && userSubscription.expiresAt > new Date() - ); - } - - static async postDownload(hash: string) { - const hasSubscription = await this.hasActiveSubscription(); - - return this.instance - .post("/track", { - event: "download", - attributes: { - hash, - hasSubscription, - }, - }) - .then((response) => response.data); - } -} diff --git a/src/main/services/hydra-api.ts b/src/main/services/hydra-api.ts index e3e772e8..bac1486a 100644 --- a/src/main/services/hydra-api.ts +++ b/src/main/services/hydra-api.ts @@ -12,6 +12,7 @@ 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"; interface HydraApiOptions { needsAuth?: boolean; @@ -45,10 +46,8 @@ export class HydraApi { } private static hasActiveSubscription() { - return ( - this.userAuth.subscription?.expiresAt && - this.userAuth.subscription.expiresAt > new Date() - ); + const expiresAt = this.userAuth.subscription?.expiresAt; + return expiresAt && (isFuture(expiresAt) || isToday(expiresAt)); } static async handleExternalAuth(uri: string) { @@ -112,6 +111,8 @@ export class HydraApi { expirationTimestamp: 0, subscription: null, }; + + this.post("/auth/logout", {}, { needsAuth: false }).catch(() => {}); } static async setupApi() { @@ -152,21 +153,26 @@ export class HydraApi { (error) => { logger.error(" ---- RESPONSE ERROR -----"); const { config } = error; + const data = JSON.parse(config.data); + logger.error( config.method, config.baseURL, config.url, - config.headers, - config.data + omit(config.headers, ["accessToken", "refreshToken"]), + Array.isArray(data) + ? data + : omit(data, ["accessToken", "refreshToken"]) ); if (error.response) { logger.error( - "Response", + "Response error:", error.response.status, error.response.data ); } else if (error.request) { - logger.error("Request", error.request); + const errorData = error.toJSON(); + logger.error("Request error:", errorData.message); } else { logger.error("Error", error.message); } diff --git a/src/main/services/index.ts b/src/main/services/index.ts index 27abf579..498159c9 100644 --- a/src/main/services/index.ts +++ b/src/main/services/index.ts @@ -4,7 +4,6 @@ export * from "./steam-250"; export * from "./steam-grid"; export * from "./window-manager"; export * from "./download"; -export * from "./how-long-to-beat"; export * from "./process-watcher"; export * from "./main-loop"; export * from "./hydra-api"; diff --git a/src/main/services/process-watcher.ts b/src/main/services/process-watcher.ts index 51b39bbe..fd9bb148 100644 --- a/src/main/services/process-watcher.ts +++ b/src/main/services/process-watcher.ts @@ -14,6 +14,20 @@ export const gamesPlaytime = new Map< const TICKS_TO_UPDATE_API = 120; let currentTick = 1; +const getSystemProcessSet = async () => { + const processes = await PythonInstance.getProcessList(); + + if (process.platform === "linux") + return new Set(processes.map((process) => process.name)); + return new Set(processes.map((process) => process.exe)); +}; + +const getExecutable = (game: Game) => { + if (process.platform === "linux") + return game.executablePath?.split("/").at(-1); + return game.executablePath; +}; + export const watchProcesses = async () => { const games = await gameRepository.find({ where: { @@ -23,14 +37,15 @@ export const watchProcesses = async () => { }); if (games.length === 0) return; - const processes = await PythonInstance.getProcessList(); - const processSet = new Set(processes.map((process) => process.exe)); + const processSet = await getSystemProcessSet(); for (const game of games) { - const executablePath = game.executablePath!; + const executable = getExecutable(game); - const gameProcess = processSet.has(executablePath); + if (!executable) continue; + + const gameProcess = processSet.has(executable); if (gameProcess) { if (gamesPlaytime.has(game.id)) { diff --git a/src/main/services/user/get-user-data.ts b/src/main/services/user/get-user-data.ts index fd4e5e1d..ff012881 100644 --- a/src/main/services/user/get-user-data.ts +++ b/src/main/services/user/get-user-data.ts @@ -44,7 +44,7 @@ export const getUserData = () => { if (err instanceof UserNotLoggedInError) { return null; } - logger.error("Failed to get logged user", err); + logger.error("Failed to get logged user"); const loggedUser = await userAuthRepository.findOne({ where: { id: 1 }, relations: { subscription: true }, diff --git a/src/main/services/window-manager.ts b/src/main/services/window-manager.ts index 4f65ef2a..426d7afe 100644 --- a/src/main/services/window-manager.ts +++ b/src/main/services/window-manager.ts @@ -85,7 +85,11 @@ export class WindowManager { return callback(details); } - if (details.url.includes("intercom.io")) { + if (details.url.includes("featurebase")) { + return callback(details); + } + + if (details.url.includes("chatwoot")) { return callback(details); } @@ -191,7 +195,7 @@ export class WindowManager { this.mainWindow?.focus(); } - public static createSystemTray(language: string) { + public static async createSystemTray(language: string) { let tray: Tray; if (process.platform === "darwin") { @@ -259,6 +263,7 @@ export class WindowManager { }, ]); + tray.setContextMenu(contextMenu); return contextMenu; }; @@ -270,6 +275,8 @@ export class WindowManager { tray.setToolTip("Hydra"); if (process.platform !== "darwin") { + await updateSystemTray(); + tray.addListener("click", () => { if (this.mainWindow) { if ( diff --git a/src/preload/index.ts b/src/preload/index.ts index d8d142ca..f9d19644 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -42,8 +42,8 @@ contextBridge.exposeInMainWorld("electron", { getGameShopDetails: (objectId: string, shop: GameShop, language: string) => ipcRenderer.invoke("getGameShopDetails", objectId, shop, language), getRandomGame: () => ipcRenderer.invoke("getRandomGame"), - getHowLongToBeat: (title: string) => - ipcRenderer.invoke("getHowLongToBeat", title), + getHowLongToBeat: (objectId: string, shop: GameShop) => + ipcRenderer.invoke("getHowLongToBeat", objectId, shop), getGames: (take?: number, skip?: number) => ipcRenderer.invoke("getGames", take, skip), searchGameRepacks: (query: string) => @@ -87,9 +87,9 @@ contextBridge.exposeInMainWorld("electron", { ipcRenderer.invoke("addGameToLibrary", objectId, title, shop), createGameShortcut: (id: number) => ipcRenderer.invoke("createGameShortcut", id), - updateExecutablePath: (id: number, executablePath: string) => + updateExecutablePath: (id: number, executablePath: string | null) => ipcRenderer.invoke("updateExecutablePath", id, executablePath), - selectGameWinePrefix: (id: number, winePrefixPath: string) => + selectGameWinePrefix: (id: number, winePrefixPath: string | null) => ipcRenderer.invoke("selectGameWinePrefix", id, winePrefixPath), verifyExecutablePathInUse: (executablePath: string) => ipcRenderer.invoke("verifyExecutablePathInUse", executablePath), @@ -198,6 +198,7 @@ contextBridge.exposeInMainWorld("electron", { ping: () => ipcRenderer.invoke("ping"), getVersion: () => ipcRenderer.invoke("getVersion"), getDefaultDownloadsPath: () => ipcRenderer.invoke("getDefaultDownloadsPath"), + isStaging: () => ipcRenderer.invoke("isStaging"), isPortableVersion: () => ipcRenderer.invoke("isPortableVersion"), openExternal: (src: string) => ipcRenderer.invoke("openExternal", src), openCheckout: () => ipcRenderer.invoke("openCheckout"), diff --git a/src/renderer/index.html b/src/renderer/index.html index bfc3a206..5d62f4c5 100644 --- a/src/renderer/index.html +++ b/src/renderer/index.html @@ -6,7 +6,7 @@ Hydra diff --git a/src/renderer/src/app.tsx b/src/renderer/src/app.tsx index 2237cb8a..3b96bb44 100644 --- a/src/renderer/src/app.tsx +++ b/src/renderer/src/app.tsx @@ -2,9 +2,6 @@ import { useCallback, useContext, useEffect, useRef } from "react"; import { Sidebar, BottomPanel, Header, Toast } from "@renderer/components"; -import "./app.scss"; -import Intercom from "@intercom/messenger-js-sdk"; - import { useAppDispatch, useAppSelector, @@ -30,19 +27,13 @@ import { UserFriendModal } from "./pages/shared-modals/user-friend-modal"; import { downloadSourcesWorker } from "./workers"; import { repacksContext } from "./context"; import { logger } from "./logger"; -import { insertCustomStyles } from "./helpers"; + +import "./app.scss"; export interface AppProps { children: React.ReactNode; } -Intercom({ - app_id: import.meta.env.RENDERER_VITE_INTERCOM_APP_ID, -}); - -const customStyles = window.localStorage.getItem("customStyles"); -insertCustomStyles(customStyles || ""); - export function App() { const contentRef = useRef(null); const { updateLibrary, library } = useLibrary(); @@ -123,12 +114,21 @@ export function App() { dispatch(setProfileBackground(profileBackground)); } - fetchUserDetails().then((response) => { - if (response) { - updateUserDetails(response); - syncFriendRequests(); - } - }); + fetchUserDetails() + .then((response) => { + if (response) { + updateUserDetails(response); + syncFriendRequests(); + } + }) + .finally(() => { + if (document.getElementById("external-resources")) return; + + const $script = document.createElement("script"); + $script.id = "external-resources"; + $script.src = `${import.meta.env.RENDERER_VITE_EXTERNAL_RESOURCES_URL}?t=${Date.now()}`; + document.head.appendChild($script); + }); }, [fetchUserDetails, syncFriendRequests, updateUserDetails, dispatch]); const onSignIn = useCallback(() => { @@ -218,9 +218,7 @@ export function App() { useEffect(() => { new MutationObserver(() => { - const modal = document.body.querySelector( - "[role=dialog]:not([data-intercom-frame='true'])" - ); + const modal = document.body.querySelector("[data-hydra-dialog]"); dispatch(toggleDraggingDisabled(Boolean(modal))); }).observe(document.body, { diff --git a/src/renderer/src/assets/lottie/downloading.json b/src/renderer/src/assets/lottie/downloading.json deleted file mode 100644 index 1ef705de..00000000 --- a/src/renderer/src/assets/lottie/downloading.json +++ /dev/null @@ -1,843 +0,0 @@ -{ - "v": "4.8.0", - "meta": { "g": "LottieFiles AE 3.5.6", "a": "", "k": "", "d": "", "tc": "" }, - "fr": 60, - "ip": 0, - "op": 120, - "w": 714, - "h": 678, - "nm": "Pre-comp 1", - "ddd": 0, - "assets": [ - { - "id": "comp_0", - "layers": [ - { - "ddd": 0, - "ind": 1, - "ty": 4, - "nm": "centro", - "sr": 1, - "ks": { - "o": { "a": 0, "k": 100, "ix": 11 }, - "r": { "a": 0, "k": 0, "ix": 10 }, - "p": { - "a": 1, - "k": [ - { - "i": { "x": 0.214, "y": 1 }, - "o": { "x": 0.462, "y": 0 }, - "t": 0, - "s": [450, 907, 0], - "to": [0, 0, 0], - "ti": [0, 0, 0] - }, - { "t": 30, "s": [450, 1513, 0] } - ], - "ix": 2 - }, - "a": { "a": 0, "k": [-348, -169, 0], "ix": 1 }, - "s": { "a": 0, "k": [100, 100, 100], "ix": 6 } - }, - "ao": 0, - "shapes": [ - { - "ty": "gr", - "it": [ - { - "ind": 0, - "ty": "sh", - "ix": 1, - "ks": { - "a": 0, - "k": { - "i": [ - [0, 0], - [0, 0] - ], - "o": [ - [0, 0], - [0, 0] - ], - "v": [ - [-348, -420], - [-348, -30] - ], - "c": false - }, - "ix": 2 - }, - "nm": "Path 1", - "mn": "ADBE Vector Shape - Group", - "hd": false - }, - { - "ty": "st", - "c": { - "a": 0, - "k": [0.854901960784, 0.858823529412, 0.882352941176, 1], - "ix": 3 - }, - "o": { "a": 0, "k": 100, "ix": 4 }, - "w": { "a": 0, "k": 77, "ix": 5 }, - "lc": 2, - "lj": 1, - "ml": 4, - "bm": 0, - "nm": "Stroke 1", - "mn": "ADBE Vector Graphic - Stroke", - "hd": false - }, - { - "ty": "tr", - "p": { "a": 0, "k": [-348, -164], "ix": 2 }, - "a": { "a": 0, "k": [-348, -156], "ix": 1 }, - "s": { "a": 0, "k": [100, 100], "ix": 3 }, - "r": { "a": 0, "k": 0, "ix": 6 }, - "o": { "a": 0, "k": 100, "ix": 7 }, - "sk": { "a": 0, "k": 0, "ix": 4 }, - "sa": { "a": 0, "k": 0, "ix": 5 }, - "nm": "Transform" - } - ], - "nm": "Shape 1", - "np": 3, - "cix": 2, - "bm": 0, - "ix": 1, - "mn": "ADBE Vector Group", - "hd": false - } - ], - "ip": 0, - "op": 120, - "st": 0, - "bm": 0 - }, - { - "ddd": 0, - "ind": 2, - "ty": 4, - "nm": "esquerdo", - "parent": 1, - "sr": 1, - "ks": { - "o": { "a": 0, "k": 100, "ix": 11 }, - "r": { - "a": 1, - "k": [ - { - "i": { "x": [0.298], "y": [1] }, - "o": { "x": [0.448], "y": [0] }, - "t": 6, - "s": [43.5] - }, - { "t": 36, "s": [-1] } - ], - "ix": 10 - }, - "p": { "a": 0, "k": [-348.39, -36.55, 0], "ix": 2 }, - "a": { "a": 0, "k": [-2, 84, 0], "ix": 1 }, - "s": { "a": 0, "k": [100, 100, 100], "ix": 6 } - }, - "ao": 0, - "shapes": [ - { - "ty": "gr", - "it": [ - { - "ind": 0, - "ty": "sh", - "ix": 1, - "ks": { - "a": 0, - "k": { - "i": [ - [0, 0], - [0, 0] - ], - "o": [ - [0, 0], - [0, 0] - ], - "v": [ - [-178, -102], - [-2, 84] - ], - "c": false - }, - "ix": 2 - }, - "nm": "Path 1", - "mn": "ADBE Vector Shape - Group", - "hd": false - }, - { - "ty": "st", - "c": { - "a": 0, - "k": [0.854901960784, 0.858823529412, 0.882352941176, 1], - "ix": 3 - }, - "o": { "a": 0, "k": 100, "ix": 4 }, - "w": { "a": 0, "k": 77, "ix": 5 }, - "lc": 2, - "lj": 1, - "ml": 4, - "bm": 0, - "nm": "Stroke 1", - "mn": "ADBE Vector Graphic - Stroke", - "hd": false - }, - { - "ty": "tr", - "p": { "a": 0, "k": [0, 0], "ix": 2 }, - "a": { "a": 0, "k": [0, 0], "ix": 1 }, - "s": { "a": 0, "k": [100, 100], "ix": 3 }, - "r": { "a": 0, "k": 0, "ix": 6 }, - "o": { "a": 0, "k": 100, "ix": 7 }, - "sk": { "a": 0, "k": 0, "ix": 4 }, - "sa": { "a": 0, "k": 0, "ix": 5 }, - "nm": "Transform" - } - ], - "nm": "Shape 1", - "np": 3, - "cix": 2, - "bm": 0, - "ix": 1, - "mn": "ADBE Vector Group", - "hd": false - }, - { - "ty": "tm", - "s": { "a": 0, "k": 8, "ix": 1 }, - "e": { "a": 0, "k": 100, "ix": 2 }, - "o": { "a": 0, "k": 0, "ix": 3 }, - "m": 1, - "ix": 2, - "nm": "Trim Paths 1", - "mn": "ADBE Vector Filter - Trim", - "hd": false - } - ], - "ip": 0, - "op": 120, - "st": 0, - "bm": 0 - }, - { - "ddd": 0, - "ind": 3, - "ty": 4, - "nm": "direito", - "parent": 1, - "sr": 1, - "ks": { - "o": { "a": 0, "k": 100, "ix": 11 }, - "r": { - "a": 1, - "k": [ - { - "i": { "x": [0.265], "y": [1] }, - "o": { "x": [0.53], "y": [0] }, - "t": 6, - "s": [-43.5] - }, - { "t": 36, "s": [1] } - ], - "ix": 10 - }, - "p": { "a": 0, "k": [-348.39, -36.55, 0], "ix": 2 }, - "a": { "a": 0, "k": [-2, 84, 0], "ix": 1 }, - "s": { "a": 0, "k": [-100, 100, 100], "ix": 6 } - }, - "ao": 0, - "shapes": [ - { - "ty": "gr", - "it": [ - { - "ind": 0, - "ty": "sh", - "ix": 1, - "ks": { - "a": 0, - "k": { - "i": [ - [0, 0], - [0, 0] - ], - "o": [ - [0, 0], - [0, 0] - ], - "v": [ - [-178, -102], - [-2, 84] - ], - "c": false - }, - "ix": 2 - }, - "nm": "Path 1", - "mn": "ADBE Vector Shape - Group", - "hd": false - }, - { - "ty": "tm", - "s": { "a": 0, "k": 8, "ix": 1 }, - "e": { "a": 0, "k": 100, "ix": 2 }, - "o": { "a": 0, "k": 0, "ix": 3 }, - "m": 1, - "ix": 2, - "nm": "Trim Paths 1", - "mn": "ADBE Vector Filter - Trim", - "hd": false - }, - { - "ty": "st", - "c": { - "a": 0, - "k": [0.854901960784, 0.858823529412, 0.882352941176, 1], - "ix": 3 - }, - "o": { "a": 0, "k": 100, "ix": 4 }, - "w": { "a": 0, "k": 77, "ix": 5 }, - "lc": 2, - "lj": 1, - "ml": 4, - "bm": 0, - "nm": "Stroke 1", - "mn": "ADBE Vector Graphic - Stroke", - "hd": false - }, - { - "ty": "tr", - "p": { "a": 0, "k": [0, 0], "ix": 2 }, - "a": { "a": 0, "k": [0, 0], "ix": 1 }, - "s": { "a": 0, "k": [100, 100], "ix": 3 }, - "r": { "a": 0, "k": 0, "ix": 6 }, - "o": { "a": 0, "k": 100, "ix": 7 }, - "sk": { "a": 0, "k": 0, "ix": 4 }, - "sa": { "a": 0, "k": 0, "ix": 5 }, - "nm": "Transform" - } - ], - "nm": "Shape 1", - "np": 4, - "cix": 2, - "bm": 0, - "ix": 1, - "mn": "ADBE Vector Group", - "hd": false - } - ], - "ip": 0, - "op": 120, - "st": 0, - "bm": 0 - } - ] - }, - { - "id": "comp_1", - "layers": [ - { - "ddd": 0, - "ind": 1, - "ty": 4, - "nm": "centro", - "sr": 1, - "ks": { - "o": { "a": 0, "k": 100, "ix": 11 }, - "r": { "a": 0, "k": 0, "ix": 10 }, - "p": { - "a": 1, - "k": [ - { - "i": { "x": 0.569, "y": 1 }, - "o": { "x": 0.809, "y": 0 }, - "t": 0, - "s": [450, 391, 0], - "to": [0, 0, 0], - "ti": [0, 0, 0] - }, - { "t": 30, "s": [450, 997, 0] } - ], - "ix": 2 - }, - "a": { "a": 0, "k": [-348, -169, 0], "ix": 1 }, - "s": { "a": 0, "k": [100, 100, 100], "ix": 6 } - }, - "ao": 0, - "shapes": [ - { - "ty": "gr", - "it": [ - { - "ind": 0, - "ty": "sh", - "ix": 1, - "ks": { - "a": 0, - "k": { - "i": [ - [0, 0], - [0, 0] - ], - "o": [ - [0, 0], - [0, 0] - ], - "v": [ - [-348, -420], - [-348, -30] - ], - "c": false - }, - "ix": 2 - }, - "nm": "Path 1", - "mn": "ADBE Vector Shape - Group", - "hd": false - }, - { - "ty": "st", - "c": { - "a": 0, - "k": [0.854901960784, 0.858823529412, 0.882352941176, 1], - "ix": 3 - }, - "o": { "a": 0, "k": 100, "ix": 4 }, - "w": { "a": 0, "k": 77, "ix": 5 }, - "lc": 2, - "lj": 1, - "ml": 4, - "bm": 0, - "nm": "Stroke 1", - "mn": "ADBE Vector Graphic - Stroke", - "hd": false - }, - { - "ty": "tr", - "p": { "a": 0, "k": [-348, -164], "ix": 2 }, - "a": { "a": 0, "k": [-348, -156], "ix": 1 }, - "s": { "a": 0, "k": [100, 100], "ix": 3 }, - "r": { "a": 0, "k": 0, "ix": 6 }, - "o": { "a": 0, "k": 100, "ix": 7 }, - "sk": { "a": 0, "k": 0, "ix": 4 }, - "sa": { "a": 0, "k": 0, "ix": 5 }, - "nm": "Transform" - } - ], - "nm": "Shape 1", - "np": 3, - "cix": 2, - "bm": 0, - "ix": 1, - "mn": "ADBE Vector Group", - "hd": false - } - ], - "ip": 0, - "op": 120, - "st": 0, - "bm": 0 - }, - { - "ddd": 0, - "ind": 2, - "ty": 4, - "nm": "esquerdo", - "parent": 1, - "sr": 1, - "ks": { - "o": { "a": 0, "k": 100, "ix": 11 }, - "r": { - "a": 1, - "k": [ - { - "i": { "x": [0.552], "y": [1] }, - "o": { "x": [0.702], "y": [0] }, - "t": 0, - "s": [-1] - }, - { "t": 30, "s": [43.5] } - ], - "ix": 10 - }, - "p": { "a": 0, "k": [-348.39, -36.55, 0], "ix": 2 }, - "a": { "a": 0, "k": [-2, 84, 0], "ix": 1 }, - "s": { "a": 0, "k": [100, 100, 100], "ix": 6 } - }, - "ao": 0, - "shapes": [ - { - "ty": "gr", - "it": [ - { - "ind": 0, - "ty": "sh", - "ix": 1, - "ks": { - "a": 0, - "k": { - "i": [ - [0, 0], - [0, 0] - ], - "o": [ - [0, 0], - [0, 0] - ], - "v": [ - [-178, -102], - [-2, 84] - ], - "c": false - }, - "ix": 2 - }, - "nm": "Path 1", - "mn": "ADBE Vector Shape - Group", - "hd": false - }, - { - "ty": "st", - "c": { - "a": 0, - "k": [0.854901960784, 0.858823529412, 0.882352941176, 1], - "ix": 3 - }, - "o": { "a": 0, "k": 100, "ix": 4 }, - "w": { "a": 0, "k": 77, "ix": 5 }, - "lc": 2, - "lj": 1, - "ml": 4, - "bm": 0, - "nm": "Stroke 1", - "mn": "ADBE Vector Graphic - Stroke", - "hd": false - }, - { - "ty": "tr", - "p": { "a": 0, "k": [0, 0], "ix": 2 }, - "a": { "a": 0, "k": [0, 0], "ix": 1 }, - "s": { "a": 0, "k": [100, 100], "ix": 3 }, - "r": { "a": 0, "k": 0, "ix": 6 }, - "o": { "a": 0, "k": 100, "ix": 7 }, - "sk": { "a": 0, "k": 0, "ix": 4 }, - "sa": { "a": 0, "k": 0, "ix": 5 }, - "nm": "Transform" - } - ], - "nm": "Shape 1", - "np": 3, - "cix": 2, - "bm": 0, - "ix": 1, - "mn": "ADBE Vector Group", - "hd": false - }, - { - "ty": "tm", - "s": { "a": 0, "k": 8, "ix": 1 }, - "e": { "a": 0, "k": 100, "ix": 2 }, - "o": { "a": 0, "k": 0, "ix": 3 }, - "m": 1, - "ix": 2, - "nm": "Trim Paths 1", - "mn": "ADBE Vector Filter - Trim", - "hd": false - } - ], - "ip": 0, - "op": 120, - "st": 0, - "bm": 0 - }, - { - "ddd": 0, - "ind": 3, - "ty": 4, - "nm": "direito", - "parent": 1, - "sr": 1, - "ks": { - "o": { "a": 0, "k": 100, "ix": 11 }, - "r": { - "a": 1, - "k": [ - { - "i": { "x": [0.47], "y": [1] }, - "o": { "x": [0.735], "y": [0] }, - "t": 0, - "s": [1] - }, - { "t": 30, "s": [-43.5] } - ], - "ix": 10 - }, - "p": { "a": 0, "k": [-348.39, -36.55, 0], "ix": 2 }, - "a": { "a": 0, "k": [-2, 84, 0], "ix": 1 }, - "s": { "a": 0, "k": [-100, 100, 100], "ix": 6 } - }, - "ao": 0, - "shapes": [ - { - "ty": "gr", - "it": [ - { - "ind": 0, - "ty": "sh", - "ix": 1, - "ks": { - "a": 0, - "k": { - "i": [ - [0, 0], - [0, 0] - ], - "o": [ - [0, 0], - [0, 0] - ], - "v": [ - [-178, -102], - [-2, 84] - ], - "c": false - }, - "ix": 2 - }, - "nm": "Path 1", - "mn": "ADBE Vector Shape - Group", - "hd": false - }, - { - "ty": "tm", - "s": { "a": 0, "k": 8, "ix": 1 }, - "e": { "a": 0, "k": 100, "ix": 2 }, - "o": { "a": 0, "k": 0, "ix": 3 }, - "m": 1, - "ix": 2, - "nm": "Trim Paths 1", - "mn": "ADBE Vector Filter - Trim", - "hd": false - }, - { - "ty": "st", - "c": { - "a": 0, - "k": [0.854901960784, 0.858823529412, 0.882352941176, 1], - "ix": 3 - }, - "o": { "a": 0, "k": 100, "ix": 4 }, - "w": { "a": 0, "k": 77, "ix": 5 }, - "lc": 2, - "lj": 1, - "ml": 4, - "bm": 0, - "nm": "Stroke 1", - "mn": "ADBE Vector Graphic - Stroke", - "hd": false - }, - { - "ty": "tr", - "p": { "a": 0, "k": [0, 0], "ix": 2 }, - "a": { "a": 0, "k": [0, 0], "ix": 1 }, - "s": { "a": 0, "k": [100, 100], "ix": 3 }, - "r": { "a": 0, "k": 0, "ix": 6 }, - "o": { "a": 0, "k": 100, "ix": 7 }, - "sk": { "a": 0, "k": 0, "ix": 4 }, - "sa": { "a": 0, "k": 0, "ix": 5 }, - "nm": "Transform" - } - ], - "nm": "Shape 1", - "np": 4, - "cix": 2, - "bm": 0, - "ix": 1, - "mn": "ADBE Vector Group", - "hd": false - } - ], - "ip": 0, - "op": 120, - "st": 0, - "bm": 0 - } - ] - } - ], - "layers": [ - { - "ddd": 0, - "ind": 1, - "ty": 0, - "nm": "seta 2", - "refId": "comp_0", - "sr": 1, - "ks": { - "o": { "a": 0, "k": 100, "ix": 11 }, - "r": { "a": 0, "k": 0, "ix": 10 }, - "p": { "a": 0, "k": [357, -247, 0], "ix": 2 }, - "a": { "a": 0, "k": [450, 960, 0], "ix": 1 }, - "s": { "a": 0, "k": [100, 100, 100], "ix": 6 } - }, - "ao": 0, - "w": 900, - "h": 1920, - "ip": 30, - "op": 120, - "st": 30, - "bm": 0 - }, - { - "ddd": 0, - "ind": 2, - "ty": 0, - "nm": "seta", - "refId": "comp_1", - "sr": 1, - "ks": { - "o": { "a": 0, "k": 100, "ix": 11 }, - "r": { "a": 0, "k": 0, "ix": 10 }, - "p": { "a": 0, "k": [357, 258, 0], "ix": 2 }, - "a": { "a": 0, "k": [450, 345, 0], "ix": 1 }, - "s": { "a": 0, "k": [100, 100, 100], "ix": 6 } - }, - "ao": 0, - "w": 900, - "h": 690, - "ip": 0, - "op": 120, - "st": 0, - "bm": 0 - }, - { - "ddd": 0, - "ind": 3, - "ty": 4, - "nm": "base Outlines", - "sr": 1, - "ks": { - "o": { "a": 0, "k": 100, "ix": 11 }, - "r": { "a": 0, "k": 0, "ix": 10 }, - "p": { "a": 0, "k": [357, 548.713, 0], "ix": 2 }, - "a": { "a": 0, "k": [357.81, 129.934, 0], "ix": 1 }, - "s": { "a": 0, "k": [100, 100, 100], "ix": 6 } - }, - "ao": 0, - "shapes": [ - { - "ty": "gr", - "it": [ - { - "ind": 0, - "ty": "sh", - "ix": 1, - "ks": { - "a": 0, - "k": { - "i": [ - [0, 0], - [0, 50.043], - [0, 0], - [-21.158, 0], - [0, -21.447], - [0, 0], - [-7.049, 0], - [0, 0], - [0, 7.149], - [0, 0], - [-21.158, 0], - [0, -21.447], - [0, 0], - [49.368, 0] - ], - "o": [ - [-49.369, 0], - [0, 0], - [0, -21.447], - [21.158, 0], - [0, 0], - [0, 7.145], - [0, 0], - [7.053, 0], - [0, 0], - [0, -21.447], - [21.158, 0], - [0, 0], - [0, 50.043], - [0, 0] - ], - "v": [ - [-268.169, 129.445], - [-357.559, 38.834], - [-357.559, -90.61], - [-319.249, -129.445], - [-280.939, -90.61], - [-280.939, 38.834], - [-268.169, 51.778], - [268.169, 51.778], - [280.939, 38.834], - [280.939, -90.61], - [319.249, -129.445], - [357.559, -90.61], - [357.559, 38.834], - [268.169, 129.445] - ], - "c": true - }, - "ix": 2 - }, - "nm": "Path 1", - "mn": "ADBE Vector Shape - Group", - "hd": false - }, - { - "ty": "fl", - "c": { - "a": 0, - "k": [0.865977448108, 0.86824388691, 0.890449075138, 1], - "ix": 4 - }, - "o": { "a": 0, "k": 100, "ix": 5 }, - "r": 1, - "bm": 0, - "nm": "Fill 1", - "mn": "ADBE Vector Graphic - Fill", - "hd": false - }, - { - "ty": "tr", - "p": { "a": 0, "k": [357.809, 129.695], "ix": 2 }, - "a": { "a": 0, "k": [0, 0], "ix": 1 }, - "s": { "a": 0, "k": [100, 100], "ix": 3 }, - "r": { "a": 0, "k": 0, "ix": 6 }, - "o": { "a": 0, "k": 100, "ix": 7 }, - "sk": { "a": 0, "k": 0, "ix": 4 }, - "sa": { "a": 0, "k": 0, "ix": 5 }, - "nm": "Transform" - } - ], - "nm": "Group 1", - "np": 2, - "cix": 2, - "bm": 0, - "ix": 1, - "mn": "ADBE Vector Group", - "hd": false - } - ], - "ip": 0, - "op": 120, - "st": 0, - "bm": 0 - } - ], - "markers": [] -} diff --git a/src/renderer/src/components/bottom-panel/bottom-panel.scss b/src/renderer/src/components/bottom-panel/bottom-panel.scss index 35a1a0ee..5103e916 100644 --- a/src/renderer/src/components/bottom-panel/bottom-panel.scss +++ b/src/renderer/src/components/bottom-panel/bottom-panel.scss @@ -1,7 +1,7 @@ @use "../../scss/globals.scss"; .bottom-panel { - width: "100%"; + width: 100%; border-top: solid 1px globals.$border-color; background-color: globals.$background-color; padding: calc(globals.$spacing-unit / 2) calc(globals.$spacing-unit * 2); diff --git a/src/renderer/src/components/modal/modal.tsx b/src/renderer/src/components/modal/modal.tsx index 3eceedb3..eeb51047 100644 --- a/src/renderer/src/components/modal/modal.tsx +++ b/src/renderer/src/components/modal/modal.tsx @@ -111,6 +111,7 @@ export function Modal({ aria-labelledby={title} aria-describedby={description} ref={modalContentRef} + data-hydra-dialog >
diff --git a/src/renderer/src/components/sidebar/sidebar.css.ts b/src/renderer/src/components/sidebar/sidebar.css.ts deleted file mode 100644 index 4fd4b981..00000000 --- a/src/renderer/src/components/sidebar/sidebar.css.ts +++ /dev/null @@ -1,152 +0,0 @@ -import { style } from "@vanilla-extract/css"; -import { recipe } from "@vanilla-extract/recipes"; - -import { SPACING_UNIT, vars } from "../../theme.css"; - -export const sidebar = recipe({ - base: { - backgroundColor: vars.color.darkBackground, - color: vars.color.muted, - flexDirection: "column", - display: "flex", - transition: "opacity ease 0.2s", - borderRight: `solid 1px ${vars.color.border}`, - position: "relative", - overflow: "hidden", - justifyContent: "space-between", - }, - variants: { - resizing: { - true: { - opacity: vars.opacity.active, - pointerEvents: "none", - }, - }, - darwin: { - true: { - paddingTop: `${SPACING_UNIT * 6}px`, - }, - false: { - paddingTop: `${SPACING_UNIT}px`, - }, - }, - }, -}); - -export const content = style({ - display: "flex", - flexDirection: "column", - padding: `${SPACING_UNIT * 2}px`, - gap: `${SPACING_UNIT * 2}px`, - width: "100%", - overflow: "auto", -}); - -export const handle = style({ - width: "5px", - height: "100%", - cursor: "col-resize", - position: "absolute", - right: "0", -}); - -export const menu = style({ - listStyle: "none", - padding: "0", - margin: "0", - gap: `${SPACING_UNIT / 2}px`, - display: "flex", - flexDirection: "column", - overflow: "hidden", -}); - -export const menuItem = recipe({ - base: { - transition: "all ease 0.1s", - cursor: "pointer", - textWrap: "nowrap", - display: "flex", - color: vars.color.muted, - borderRadius: "4px", - ":hover": { - backgroundColor: "rgba(255, 255, 255, 0.15)", - }, - }, - variants: { - active: { - true: { - backgroundColor: "rgba(255, 255, 255, 0.1)", - }, - }, - muted: { - true: { - opacity: vars.opacity.disabled, - ":hover": { - opacity: "1", - }, - }, - }, - }, -}); - -export const menuItemButton = style({ - color: "inherit", - display: "flex", - alignItems: "center", - gap: `${SPACING_UNIT}px`, - cursor: "pointer", - overflow: "hidden", - width: "100%", - padding: `9px ${SPACING_UNIT}px`, -}); - -export const menuItemButtonLabel = style({ - textOverflow: "ellipsis", - overflow: "hidden", -}); - -export const gameIcon = style({ - width: "20px", - height: "20px", - minWidth: "20px", - minHeight: "20px", - borderRadius: "4px", - backgroundSize: "cover", -}); - -export const sectionTitle = style({ - textTransform: "uppercase", - fontWeight: "bold", -}); - -export const section = style({ - gap: `${SPACING_UNIT * 2}px`, - display: "flex", - flexDirection: "column", - paddingBottom: `${SPACING_UNIT}px`, -}); - -export const helpButton = style({ - color: vars.color.muted, - padding: `${SPACING_UNIT}px ${SPACING_UNIT * 2}px`, - gap: "9px", - display: "flex", - alignItems: "center", - cursor: "pointer", - borderTop: `solid 1px ${vars.color.border}`, - transition: "background-color ease 0.1s", - ":hover": { - backgroundColor: "rgba(255, 255, 255, 0.15)", - }, -}); - -export const helpButtonIcon = style({ - background: "linear-gradient(0deg, #16B195 50%, #3E62C0 100%)", - width: "24px", - height: "24px", - display: "flex", - alignItems: "center", - justifyContent: "center", - color: "#fff", - borderRadius: "50%", -}); diff --git a/src/renderer/src/components/sidebar/sidebar.tsx b/src/renderer/src/components/sidebar/sidebar.tsx index a1f8166b..213c0211 100644 --- a/src/renderer/src/components/sidebar/sidebar.tsx +++ b/src/renderer/src/components/sidebar/sidebar.tsx @@ -24,8 +24,6 @@ import { sortBy } from "lodash-es"; import cn from "classnames"; import { CommentDiscussionIcon } from "@primer/octicons-react"; -import { show, update } from "@intercom/messenger-js-sdk"; - const SIDEBAR_MIN_WIDTH = 200; const SIDEBAR_INITIAL_WIDTH = 250; const SIDEBAR_MAX_WIDTH = 450; @@ -52,19 +50,7 @@ export function Sidebar() { return sortBy(library, (game) => game.title); }, [library]); - const { userDetails, hasActiveSubscription } = useUserDetails(); - - useEffect(() => { - if (userDetails) { - update({ - name: userDetails.displayName, - Username: userDetails.username, - Email: userDetails.email, - "Subscription expiration date": userDetails?.subscription?.expiresAt, - "Payment status": userDetails?.subscription?.status, - }); - } - }, [userDetails, hasActiveSubscription]); + const { hasActiveSubscription } = useUserDetails(); const { lastPacket, progress } = useDownload(); @@ -267,7 +253,11 @@ export function Sidebar() {
{hasActiveSubscription && ( - + <> + + {game.executablePath && ( + + )} + } /> @@ -186,14 +203,24 @@ export function GameOptionsModal({ disabled placeholder={t("no_directory_selected")} rightContent={ - + <> + + {game.winePrefixPath && ( + + )} + } />
diff --git a/src/renderer/src/pages/game-details/sidebar/sidebar.tsx b/src/renderer/src/pages/game-details/sidebar/sidebar.tsx index c4ad640b..86f24f5a 100644 --- a/src/renderer/src/pages/game-details/sidebar/sidebar.tsx +++ b/src/renderer/src/pages/game-details/sidebar/sidebar.tsx @@ -97,8 +97,10 @@ export function Sidebar() { }); } else { try { - const howLongToBeat = - await window.electron.getHowLongToBeat(gameTitle); + const howLongToBeat = await window.electron.getHowLongToBeat( + objectId, + shop + ); if (howLongToBeat) { howLongToBeatEntriesTable.add({ diff --git a/src/renderer/src/pages/profile/profile-content/profile-content.tsx b/src/renderer/src/pages/profile/profile-content/profile-content.tsx index d6b55490..26975647 100644 --- a/src/renderer/src/pages/profile/profile-content/profile-content.tsx +++ b/src/renderer/src/pages/profile/profile-content/profile-content.tsx @@ -45,22 +45,25 @@ export function ProfileContent() { return userProfile?.relation?.status === "ACCEPTED"; }, [userProfile]); - const buildUserGameDetailsPath = (game: UserGame) => { - if (!userProfile?.hasActiveSubscription || game.achievementCount === 0) { - return buildGameDetailsPath({ - ...game, - objectId: game.objectId, - }); - } + const buildUserGameDetailsPath = useCallback( + (game: UserGame) => { + if (!userProfile?.hasActiveSubscription || game.achievementCount === 0) { + return buildGameDetailsPath({ + ...game, + objectId: game.objectId, + }); + } - const userParams = userProfile - ? { - userId: userProfile.id, - } - : undefined; + const userParams = userProfile + ? { + userId: userProfile.id, + } + : undefined; - return buildGameAchievementPath({ ...game }, userParams); - }; + return buildGameAchievementPath({ ...game }, userParams); + }, + [userProfile] + ); const formatPlayTime = useCallback( (playTimeInSeconds = 0) => { @@ -259,6 +262,7 @@ export function ProfileContent() { userStats, numberFormatter, t, + buildUserGameDetailsPath, formatPlayTime, navigate, ]); diff --git a/src/renderer/src/pages/settings/settings-general.tsx b/src/renderer/src/pages/settings/settings-general.tsx index 2f53b6ab..3aa3044e 100644 --- a/src/renderer/src/pages/settings/settings-general.tsx +++ b/src/renderer/src/pages/settings/settings-general.tsx @@ -11,7 +11,6 @@ import { changeLanguage } from "i18next"; import languageResources from "@locales"; import { orderBy } from "lodash-es"; import { settingsContext } from "@renderer/context"; -import { insertCustomStyles } from "@renderer/helpers"; interface LanguageOption { option: string; @@ -114,19 +113,6 @@ export function SettingsGeneral() { } } - const handleSaveStylesClick = () => { - const existingStyles = document.getElementById("custom-styles"); - if (existingStyles) { - existingStyles.remove(); - } - - const css = document.querySelector("textarea")?.value; - if (css) { - insertCustomStyles(css); - window.localStorage.setItem("customStyles", css); - } - }; - return ( <> - -