Compare commits

...

36 Commits

Author SHA1 Message Date
Zamitto
613898b32d Merge pull request #985 from hydralauncher/fix/migration-to-fix-missing-columns
Some checks failed
Release / build (ubuntu-latest) (push) Has been cancelled
Release / build (windows-latest) (push) Has been cancelled
fix: migration to fix missing columns
2024-09-18 07:35:27 -03:00
Zamitto
2bc98317e9 fix: conditional to check if hydra is updating 2024-09-18 07:07:29 -03:00
Zamitto
2e87ae8486 fix: new line 2024-09-18 06:50:20 -03:00
Zamitto
df5f34119f feat: add script to remove hydra folders on uninstall 2024-09-17 23:02:12 -03:00
Zamitto
2271368199 chore: bump version 2024-09-17 21:56:50 -03:00
Zamitto
e139423b52 feat: add axios user agent header with Hydra version 2024-09-17 21:56:46 -03:00
Zamitto
aa2ecfad96 feat: add migration to add missing columns from installations before 2.0 2024-09-17 21:49:30 -03:00
Zamitto
b76441a763 Merge pull request #984 from alexhostrup/danish-translation
Some checks are pending
Release / build (ubuntu-latest) (push) Waiting to run
Release / build (windows-latest) (push) Waiting to run
Updated Danish translation to match English translation
2024-09-17 18:23:07 -03:00
Alexander Hostrup
2d160ba8ee Merge branch 'main' into danish-translation 2024-09-17 22:35:06 +02:00
Zamitto
5863b15b12 doc: fix log location description 2024-09-17 17:32:05 -03:00
Zamitto
bc175e635f doc: update bug report template 2024-09-17 17:30:28 -03:00
Alexander Hostrup
f7f89cb778 Updated Danish translation to match English translation 2024-09-17 22:19:53 +02:00
Chubby Granny Chaser
16c45692da Merge pull request #982 from hydralauncher/feature/adding-flame-animation
Some checks are pending
Release / build (ubuntu-latest) (push) Waiting to run
Release / build (windows-latest) (push) Waiting to run
chore: bump version
2024-09-17 16:53:06 +01:00
Chubby Granny Chaser
30aa3f5470 chore: bump version 2024-09-17 16:50:26 +01:00
Chubby Granny Chaser
ef16732c0a Merge pull request #981 from hydralauncher/feature/adding-flame-animation
feat: adding flame animation
2024-09-17 16:42:24 +01:00
Chubby Granny Chaser
84c472a3fa chore: bump version 2024-09-17 16:37:25 +01:00
Chubby Granny Chaser
2610f8b17b Merge branch 'feature/adding-flame-animation' of github.com:hydralauncher/hydra into feature/adding-flame-animation 2024-09-17 16:32:11 +01:00
Chubby Granny Chaser
705b12019f Merge branch 'main' of github.com:hydralauncher/hydra into feature/adding-flame-animation 2024-09-17 16:31:44 +01:00
Zamitto
39be8fdf53 Merge branch 'main' into feature/adding-flame-animation 2024-09-17 12:28:59 -03:00
Chubby Granny Chaser
cc7c3455fa feat: adding flame animation 2024-09-17 16:28:59 +01:00
Chubby Granny Chaser
d1f4bc7207 feat: adding flame animation 2024-09-17 16:26:12 +01:00
Chubby Granny Chaser
aa4ca25653 feat: adding flame animation 2024-09-17 16:25:19 +01:00
Zamitto
405ea0a824 fix: playtime count 2024-09-17 11:10:44 -03:00
Zamitto
43bc0cb08f Merge pull request #977 from hydralauncher/feat/polling-from-sync
Some checks are pending
Release / build (ubuntu-latest) (push) Waiting to run
Release / build (windows-latest) (push) Waiting to run
feat: polling from sync
2024-09-16 16:46:29 -03:00
Zamitto
3c200aa2eb fix: rename variable 2024-09-16 14:48:15 -03:00
Zamitto
cc5967814b fix: adjust when calling /games/download 2024-09-16 14:07:00 -03:00
Zamitto
ec16efed2f feat: create use details functions 2024-09-16 14:03:54 -03:00
Zamitto
09d0e5b4ef Merge pull request #976 from hydralauncher/feat/update-typing-to-match-get-me
Some checks are pending
Release / build (ubuntu-latest) (push) Waiting to run
Release / build (windows-latest) (push) Waiting to run
feat: update typing to match get me
2024-09-16 13:36:26 -03:00
Zamitto
5b18aba2b8 feat: omit username and tokens in logs 2024-09-16 13:09:50 -03:00
Zamitto
192008c76c fix: not showing repacks and stats if game details request fails 2024-09-16 12:56:29 -03:00
Zamitto
1de3a9836c feat: update typing to match get me endpoint 2024-09-16 11:22:41 -03:00
Zamitto
ee02811aea chore: remove space in version string
Some checks are pending
Release / build (ubuntu-latest) (push) Waiting to run
Release / build (windows-latest) (push) Waiting to run
2024-09-15 12:21:41 -03:00
Zamitto
c21ebe1ce2 fix: database migration 2024-09-15 12:09:51 -03:00
Zamitto
214e39adda chore: version
Some checks are pending
Release / build (ubuntu-latest) (push) Waiting to run
Release / build (windows-latest) (push) Waiting to run
2024-09-15 01:17:16 -03:00
Zamitto
8258127616 chore: version 2024-09-15 01:07:46 -03:00
Zamitto
f9906bfe95 fix: message and migration 2024-09-15 01:00:44 -03:00
37 changed files with 2241 additions and 196 deletions

View File

@@ -1,6 +1,6 @@
name: Bug Report
description: Create a report to help us improve. Write in English, please.
title: "[BUG] "
title: "[BUG] Write a title for your bug"
labels: ["bug"]
body:
- type: markdown
@@ -28,11 +28,21 @@ body:
description: A clear and concise description of what you expected to happen.
validations:
required: false
- type: textarea
id: additional-info
attributes:
label: Additional information and data
description: |
If possible, add screenshots and upload your logs file here.
Logs location on Windows: "%appdata%/hydra"
Logs location on Linux: "~/.config/hydra/"
validations:
required: false
- type: textarea
id: screenshots
attributes:
label: Screenshots
description: If applicable, add screenshots to help explain your problem.
description: If possible, add screenshots to help explain your problem.
validations:
required: false
- type: input
@@ -49,13 +59,6 @@ body:
description: Please provide the version of Hydra you are using.
validations:
required: true
- type: textarea
id: additional-info
attributes:
label: Additional Information
description: Please provide any additional information and context about your problem.
validations:
required: false
- type: checkboxes
id: terms
attributes:

6
build/installer.nsh Normal file
View File

@@ -0,0 +1,6 @@
!macro customUnInstall
${ifNot} ${isUpdated}
RMDir /r "$APPDATA\${APP_PACKAGE_NAME}"
RMDir /r "$APPDATA\hydra"
${endIf}
!macroend

View File

@@ -27,6 +27,7 @@ nsis:
createDesktopShortcut: always
oneClick: false
allowToChangeInstallationDirectory: true
include: installer.nsh
portable:
artifactName: ${name}-${version}-portable.${ext}
mac:

View File

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

View File

@@ -1,10 +1,16 @@
{
"language_name": "Dansk",
"app": {
"successfully_signed_in": "Loggede ind successfuldt"
},
"home": {
"featured": "Anbefalet",
"trending": "Trender",
"surprise_me": "Overrask mig",
"no_results": "Ingen resultater fundet"
"no_results": "Ingen resultater fundet",
"start_typing": "Begynd at skrive for at søge...",
"hot": "Populært lige nu",
"weekly": "📅 Mest populære spil denne uge"
},
"sidebar": {
"catalogue": "Katalog",
@@ -12,27 +18,35 @@
"settings": "Indstillinger",
"my_library": "Mit bibliotek",
"downloading_metadata": "{{title}} (Downloader metadata…)",
"paused": "{{title}} (Paused)",
"downloading": "{{title}} ({{percentage}} - Downloading…)",
"filter": "Filtrer bibliotek",
"home": "Hjem"
"paused": "{{title}} (Sat på pause)",
"downloading": "{{title}} ({{percentage}} - Downloader…)",
"filter": "Filtrér bibliotek",
"home": "Hjem",
"queued": "{{title}} (I køen)",
"game_has_no_executable": "Spillet har ikke nogen eksekverbar fil valgt",
"sign_in": "Log ind",
"friends": "Venner"
},
"header": {
"search": "Søg spil",
"search": "Søg efter spil",
"home": "Hjem",
"catalogue": "Katalog",
"downloads": "Downloads",
"search_results": "Søge resultater",
"settings": "Indstillinger"
"settings": "Indstillinger",
"version_available_install": "Version {{version}} tilgængelig. Klik her for at genstarte og installere.",
"version_available_download": "Version {{version}} tilgængelig. Klik her for at downloade."
},
"bottom_panel": {
"no_downloads_in_progress": "Ingen downloads igang",
"downloading_metadata": "Downloader {{title}} metadata…",
"downloading": "Downloader {{title}}… ({{percentage}} færdig) - Konklusion {{eta}} - {{speed}}"
"downloading": "Downloader {{title}}… ({{percentage}} færdig) - Fuldt downloadet {{eta}} - {{speed}}",
"calculating_eta": "Downloader {{title}}… ({{percentage}} færdig) - Udregner resterende tid…",
"checking_files": "Checker {{title}} filer… ({{percentage}} færdig)"
},
"catalogue": {
"next_page": "Næste side",
"previous_page": "Tidligere side"
"previous_page": "Forrige side"
},
"game_details": {
"open_download_options": "Åben download muligheder",
@@ -47,11 +61,13 @@
"remove": "Fjern",
"space_left_on_disk": "{{space}} tilbage på harddisken",
"eta": "Konklusion {{eta}}",
"calculating_eta": "Udregner resterende tid…",
"downloading_metadata": "Downloader metadata…",
"filter": "Filtrer repacks",
"filter": "Filtrér repacks",
"requirements": "System behov",
"minimum": "Mindste",
"recommended": "Anbefalet",
"paused": "Sat på pause",
"release_date": "Offentliggjort den {{date}}",
"publisher": "Udgivet af {{publisher}}",
"hours": "timer",
@@ -70,10 +86,51 @@
"deleting": "Sletter installatør…",
"close": "Luk",
"playing_now": "Spiller nu",
"change": "Ændré",
"change": "Ændre",
"repacks_modal_description": "Vælg den repack du vil downloade",
"select_folder_hint": "For at ændre standard mappen, gå til <0>Instillingerne</0>",
"download_now": "Download nu"
"download_now": "Download nu",
"no_shop_details": "Kunne ikke modtage butiks detaljerne.",
"download_options": "Download muligheder",
"download_path": "Download sti",
"previous_screenshot": "Forrige skærmbillede",
"next_screenshot": "Næste skærmbillede",
"screenshot": "Skærmbillede {{number}}",
"open_screenshot": "Åben skærmbillede {{number}}",
"download_settings": "Download indstillinger",
"downloader": "Downloader",
"select_executable": "Vælg",
"no_executable_selected": "Ingen eksekverbar fil valgt",
"open_folder": "Åben mappe",
"open_download_location": "Se downloadede filer",
"create_shortcut": "Lav skrivebords genvej",
"remove_files": "Fjern filer",
"remove_from_library_title": "Er du sikker?",
"remove_from_library_description": "Dette vil fjerne {{game}} fra dit bibliotek",
"options": "Valgmuligheder",
"executable_section_title": "Eksekverbar fil",
"executable_section_description": "Sti til filen som skal bruges når \"Spil\" bliver klikket",
"downloads_secion_title": "Downloads",
"downloads_section_description": "Undersøg opdateringer eller andre versioner af dette spil",
"danger_zone_section_title": "Farezonen",
"danger_zone_section_description": "Fjern dette spil fra dit bibliotek eller filerne der er blevet downloadet af Hydra",
"download_in_progress": "Download undervejs",
"download_paused": "Download sat på pause",
"last_downloaded_option": "Sidste download mulighed",
"create_shortcut_success": "Genvej lavet successfuldt",
"create_shortcut_error": "Fejl under skabelsen af genvej",
"nsfw_content_title": "Dette spil indeholder upassende indhold",
"nsfw_content_description": "{{title}} indeholder indhold der ikke egner sig til alle aldre. Er du sikker på at du vil fortsætte?",
"allow_nsfw_content": "Fortsæt",
"refuse_nsfw_content": "Gå tilbage",
"stats": "Statistik",
"download_count": "Downloads",
"player_count": "Aktive spillere",
"download_error": "Denne download mulighed er ikke tilgængelig",
"download": "Download",
"executable_path_in_use": "Eksekverbar allerede i brug af \"{{game}}\"",
"warning": "Advarsel:",
"hydra_needs_to_remain_open": "Hydra skal forblive åbent for at denne download kan gennemføres. I tilfælde af at Hydra lukker før downloaden er færdig, mister du dit fremskridt."
},
"activation": {
"title": "Aktivér Hydra",
@@ -81,45 +138,95 @@
"enter_activation_code": "Indtast din aktiverings kode",
"message": "Hvis du ikke ved hvor du skal spørge om dette, burde du ikke have dette.",
"activate": "Aktivér",
"loading": "Loader…"
"loading": "Indlæser…"
},
"downloads": {
"resume": "Fortsæt",
"pause": "Pause",
"eta": "Konklusion {{eta}}",
"paused": "Pauset",
"paused": "Sat på pause",
"verifying": "Verificerer…",
"completed": "Færdigt",
"removed": "Ikke downloadet",
"cancel": "Annullér",
"filter": "Filtrer downloadet spil",
"filter": "Filtrér downloadet spil",
"remove": "Fjern",
"downloading_metadata": "Downloader metadata…",
"deleting": "Sletter installatør…",
"delete": "Fjern installatør",
"delete_modal_title": "Er du sikker?",
"delete_modal_description": "Dette vil fjerne alle installations filerne fra din computer",
"install": "Installér"
"install": "Installér",
"download_in_progress": "Undervejs",
"queued_downloads": "Downloadkø",
"downloads_completed": "Gennemførte",
"queued": "I kø",
"no_downloads_title": "Rimelig tomt",
"no_downloads_description": "Du har ikke downloadet noget med Hydra endnu, men det er aldrig for sent at begynde.",
"checking_files": "Undersøger filer…"
},
"settings": {
"downloads_path": "Downloads sti",
"change": "Opdatering",
"change": "Opdatér",
"notifications": "Notifikationer",
"enable_download_notifications": "Når et download bliver færdigt",
"enable_download_notifications": "Når en download bliver færdigt",
"enable_repack_list_notifications": "Når en ny repack bliver tilføjet",
"real_debrid_api_token_label": "Real-Debrid API nøgle",
"quit_app_instead_hiding": "Afslut Hydra instedet for at minimere til processlinjen",
"launch_with_system": "Åben Hydra ved start af systemet",
"general": "Generelt",
"behavior": "Opførsel",
"download_sources": "Download kilder",
"language": "Sprog",
"real_debrid_api_token": "API nøgle",
"enable_real_debrid": "Slå Real-Debrid til",
"real_debrid_description": "Real-Debrid er en ubegrænset downloader der gør det muligt for dig at downloade filer med det samme og med den bedste udnyttelse af din internet hastighed.",
"real_debrid_invalid_token": "Ugyldig API nøgle",
"real_debrid_api_token_hint": "Du kan få din API nøgle <0>her</0>",
"save_changes": "Gem ændringer"
"real_debrid_free_account_error": "Brugeren \"{{username}}\" er en gratis bruger. Venligst abbonér på Real-Debrid",
"real_debrid_linked_message": "Brugeren \"{{username}}\" er forbundet",
"save_changes": "Gem ændringer",
"changes_saved": "Ændringer gemt successfuldt",
"download_sources_description": "Hydra vil hente download links fra disse kilder. Kilde URLen skal være et direkte link til en .json fil der indeholder download linkene.",
"validate_download_source": "Validér",
"remove_download_source": "Fjern",
"add_download_source": "Tilføj kilde",
"download_count_zero": "Ingen download muligheder",
"download_count_one": "{{countFormatted}} download mulighed",
"download_count_other": "{{countFormatted}} download muligheder",
"download_source_url": "Download kilde URL",
"add_download_source_description": "Indsæt URLen der indeholder .json filen",
"download_source_up_to_date": "Op til dato",
"download_source_errored": "Fejlede",
"sync_download_sources": "Synkronisér kilder",
"removed_download_source": "Download kilde fjernet",
"added_download_source": "Tilføjede download kilde",
"download_sources_synced": "Alle download kilder er synkroniserede",
"insert_valid_json_url": "Indsæt en gyldig JSON url",
"found_download_option_zero": "Ingen download mulighed fundet",
"found_download_option_one": "Fandt {{countFormatted}} download mulighed",
"found_download_option_other": "Fandt {{countFormatted}} download mulighed",
"import": "Importér",
"public": "Offentlig",
"private": "Privat",
"friends_only": "Kun blandt venner",
"privacy": "Privatliv",
"profile_visibility": "Synlighed af profil",
"profile_visibility_description": "Vælg hvem der kan se din profil og dit bibliotek",
"required_field": "Dette felt er påkrævet",
"source_already_exists": "Denne kilde er allerede blevet tilføjet",
"must_be_valid_url": "Kilden skal være en gyldig URL",
"blocked_users": "Blokerede brugere",
"user_unblocked": "Brugeren er blevet afblokeret"
},
"notifications": {
"download_complete": "Download færdig",
"game_ready_to_install": "{{title}} er klar til at installeret",
"repack_list_updated": "Repack liste opdateret",
"repack_count_one": "{{count}} repack tilføjet",
"repack_count_other": "{{count}} repacks tilføjet"
"repack_count_other": "{{count}} repacks tilføjet",
"new_update_available": "Version {{version}} tilgængelig",
"restart_to_install_update": "Genstart Hydra for at installere opdateringen"
},
"system_tray": {
"open": "Åben Hydra",
@@ -130,10 +237,80 @@
},
"binary_not_found_modal": {
"title": "Programmer ikke installeret",
"description": "Wine eller Lutris eksekverbare blev ikke fundet på dit system",
"description": "Wine eller Lutris eksekverbar blev ikke fundet på dit system",
"instructions": "Tjek den korrekte måde at installere nogle af dem, på din Linux distribution, så spillet kan køre normalt"
},
"modal": {
"close": "Luk knap"
},
"forms": {
"toggle_password_visibility": "Skift synlighed af kodeord"
},
"user_profile": {
"amount_hours": "{{amount}} timer",
"amount_minutes": "{{amount}} minuter",
"last_time_played": "Sidst spillet {{period}}",
"activity": "Seneste aktivitet",
"library": "Bibliotek",
"total_play_time": "Samlet spiltid: {{amount}}",
"no_recent_activity_title": "Hmmm… ikke noget her",
"no_recent_activity_description": "Du har ikke spillet nogen spil for nyligt. Dét er det på tide at lave om på!",
"display_name": "Brugernavn",
"saving": "Gemmer",
"save": "Gem",
"edit_profile": "Redigér Profil",
"saved_successfully": "Gemt successfuldt",
"try_again": "Venligst, prøv igen",
"sign_out_modal_title": "Er du sikker?",
"cancel": "Annullér",
"successfully_signed_out": "Loggede ud successfuldt",
"sign_out": "Log ud",
"playing_for": "Spiller i {{amount}}",
"sign_out_modal_text": "Dit bibliotek er koblet sammen med din nuværende bruger. Når du logger ud er dit bibliotek ikke synligt længere, og nogen som helst form for fremskridt bliver ikke gemt. Vil du fortsætte med at logge ud?",
"add_friends": "Tilføj venner",
"add": "Tilføj",
"friend_code": "Venne kode",
"see_profile": "Se profil",
"sending": "Sender",
"friend_request_sent": "Venne anmodning sendt",
"friends": "Venner",
"friends_list": "Venne liste",
"user_not_found": "Bruger ikke fundet",
"block_user": "Blokér bruger",
"add_friend": "Tilføj ven",
"request_sent": "Anmodning sendt",
"request_received": "Anmodning modtaget",
"accept_request": "Acceptér anmodning",
"ignore_request": "Ignorér anmodning",
"cancel_request": "Annullér anmodning",
"undo_friendship": "Fortryd venskab",
"request_accepted": "Anmodning accepteret",
"user_blocked_successfully": "Bruger blokeret successfuldt",
"user_block_modal_text": "Dette blokerer {{displayName}}",
"blocked_users": "Blokerede brugere",
"unblock": "Afblokér",
"no_friends_added": "Du har stadig ikke tilføjet nogen venner",
"pending": "Afventer",
"no_pending_invites": "Du har ingen afventende invitationer",
"no_blocked_users": "Du har ingen blokerede brugere",
"friend_code_copied": "Venne kode kopieret",
"undo_friendship_modal_text": "Dette vil fortryde dit venskab med {{displayName}}",
"privacy_hint": "For at justere hvem der kan se dette, gå til <0>Indstillingerne</0>",
"locked_profile": "Denne profil er privat",
"image_process_failure": "Fejlede under håndteringen af billedet",
"required_field": "Dette felt er påkrævet",
"displayname_min_length": "Brugernavnet skal være mindst 3 karakterer langt",
"displayname_max_length": "Brugernavnet skal være højest 50 karakterer langt",
"report_profile": "Rapportér denne profil",
"report_reason": "Hvorfor rapportérer du denne profil?",
"report_description": "Yderligere information",
"report_description_placeholder": "Yderligere information",
"report": "Rapportér",
"report_reason_hate": "Hadefuld tale",
"report_reason_sexual_content": "Seksuelt indhold",
"report_reason_violence": "Vold",
"report_reason_spam": "Spam",
"report_reason_other": "Andet",
"profile_reported": "Profil rapporteret"
}
}

View File

@@ -9,7 +9,7 @@
"surprise_me": "Surprise me",
"no_results": "No results found",
"start_typing": "Starting typing to search...",
"hot": "🔥 Hot now",
"hot": "Hot now",
"weekly": "📅 Top games of the week"
},
"sidebar": {

View File

@@ -8,7 +8,7 @@
"trending": "Tendencias",
"surprise_me": "¡Sorpréndeme!",
"no_results": "No se encontraron resultados",
"hot": "🔥 Caliente ahora",
"hot": "Caliente ahora",
"weekly": "📅 Los mejores juegos de la semana",
"start_typing": "Empieza a escribir para buscar..."
},

View File

@@ -6,7 +6,7 @@
"home": {
"featured": "Destaques",
"trending": "Populares",
"hot": "🔥 Populares agora",
"hot": "Populares agora",
"weekly": "📅 Mais baixados da semana",
"surprise_me": "Surpreenda-me",
"no_results": "Nenhum resultado encontrado",

View File

@@ -8,7 +8,7 @@
"trending": "В тренде",
"surprise_me": "Удиви меня",
"no_results": "Ничего не найдено",
"hot": "🔥 Сейчас жарко",
"hot": "Сейчас жарко",
"start_typing": "Начинаю вводить текст для поиска...",
"weekly": "📅 Лучшие игры недели"
},

View File

@@ -11,3 +11,5 @@ export const logsPath = path.join(app.getPath("appData"), "hydra", "logs");
export const seedsPath = app.isPackaged
? path.join(process.resourcesPath, "seeds")
: path.join(__dirname, "..", "..", "seeds");
export const appVersion = app.getVersion();

View File

@@ -1,5 +1,5 @@
import { defaultDownloadsPath } from "@main/constants";
import { app, ipcMain } from "electron";
import { appVersion, defaultDownloadsPath } from "@main/constants";
import { ipcMain } from "electron";
import "./catalogue/get-catalogue";
import "./catalogue/get-game-shop-details";
@@ -59,9 +59,10 @@ import "./profile/update-friend-request";
import "./profile/update-profile";
import "./profile/process-profile-image";
import "./profile/send-friend-request";
import "./profile/sync-friend-requests";
import { isPortableVersion } from "@main/helpers";
ipcMain.handle("ping", () => "pong");
ipcMain.handle("getVersion", () => app.getVersion());
ipcMain.handle("getVersion", () => appVersion);
ipcMain.handle("isPortableVersion", () => isPortableVersion());
ipcMain.handle("getDefaultDownloadsPath", () => defaultDownloadsPath);

View File

@@ -1,32 +1,14 @@
import { registerEvent } from "../register-event";
import * as Sentry from "@sentry/electron/main";
import { HydraApi, logger } from "@main/services";
import { UserProfile } from "@types";
import { HydraApi } from "@main/services";
import { ProfileVisibility, UserDetails } from "@types";
import { userAuthRepository } from "@main/repository";
import { steamUrlBuilder, UserNotLoggedInError } from "@shared";
import { steamGamesWorker } from "@main/workers";
const getSteamGame = async (objectId: string) => {
try {
const steamGame = await steamGamesWorker.run(Number(objectId), {
name: "getById",
});
return {
title: steamGame.name,
iconUrl: steamUrlBuilder.icon(objectId, steamGame.clientIcon),
};
} catch (err) {
logger.error("Failed to get Steam game", err);
return null;
}
};
import { UserNotLoggedInError } from "@shared";
const getMe = async (
_event: Electron.IpcMainInvokeEvent
): Promise<UserProfile | null> => {
return HydraApi.get(`/profile/me`)
): Promise<UserDetails | null> => {
return HydraApi.get<UserDetails>(`/profile/me`)
.then(async (me) => {
userAuthRepository.upsert(
{
@@ -38,17 +20,6 @@ const getMe = async (
["id"]
);
if (me.currentGame) {
const steamGame = await getSteamGame(me.currentGame.objectId);
if (steamGame) {
me.currentGame = {
...me.currentGame,
...steamGame,
};
}
}
Sentry.setUser({ id: me.id, username: me.username });
return me;
@@ -61,7 +32,13 @@ const getMe = async (
const loggedUser = await userAuthRepository.findOne({ where: { id: 1 } });
if (loggedUser) {
return { ...loggedUser, id: loggedUser.userId };
return {
...loggedUser,
id: loggedUser.userId,
username: "",
bio: "",
profileVisibility: "PUBLIC" as ProfileVisibility,
};
}
return null;

View File

@@ -0,0 +1,9 @@
import { registerEvent } from "../register-event";
import { HydraApi } from "@main/services";
import { FriendRequestSync } from "@types";
const syncFriendRequests = async (_event: Electron.IpcMainInvokeEvent) => {
return HydraApi.get<FriendRequestSync>(`/profile/friend-requests/sync`);
};
registerEvent("syncFriendRequests", syncFriendRequests);

View File

@@ -2,7 +2,7 @@ import { registerEvent } from "../register-event";
import type { StartGameDownloadPayload } from "@types";
import { getFileBase64 } from "@main/helpers";
import { DownloadManager } from "@main/services";
import { DownloadManager, HydraApi, logger } from "@main/services";
import { Not } from "typeorm";
import { steamGamesWorker } from "@main/workers";
@@ -101,6 +101,17 @@ const startGameDownload = async (
createGame(updatedGame!).catch(() => {});
HydraApi.post(
"/games/download",
{
objectId: updatedGame!.objectID,
shop: updatedGame!.shop,
},
{ needsAuth: false }
).catch((err) => {
logger.error("Failed to create game download", err);
});
await DownloadManager.cancelDownload(updatedGame!.id);
await DownloadManager.startDownload(updatedGame!);

View File

@@ -3,12 +3,21 @@ import { databasePath } from "./constants";
import { Hydra2_0_3 } from "./migrations/20240830143811_Hydra_2_0_3";
import { RepackUris } from "./migrations/20240830143906_RepackUris";
import { UpdateUserLanguage } from "./migrations/20240913213944_update_user_language";
import { EnsureRepackUris } from "./migrations/20240915035339_ensure_repack_uris";
import { app } from "electron";
import { FixMissingColumns } from "./migrations/20240918001920_FixMissingColumns";
export type HydraMigration = Knex.Migration & { name: string };
class MigrationSource implements Knex.MigrationSource<HydraMigration> {
getMigrations(): Promise<HydraMigration[]> {
return Promise.resolve([Hydra2_0_3, RepackUris, UpdateUserLanguage]);
return Promise.resolve([
Hydra2_0_3,
RepackUris,
UpdateUserLanguage,
EnsureRepackUris,
FixMissingColumns,
]);
}
getMigrationName(migration: HydraMigration): string {
return migration.name;
@@ -19,6 +28,7 @@ class MigrationSource implements Knex.MigrationSource<HydraMigration> {
}
export const knexClient = knex({
debug: !app.isPackaged,
client: "better-sqlite3",
connection: {
filename: databasePath,

View File

@@ -4,55 +4,15 @@ import type { Knex } from "knex";
export const RepackUris: HydraMigration = {
name: "RepackUris",
up: async (knex: Knex) => {
await knex.schema.createTable("temporary_repack", (table) => {
const timestamp = new Date().getTime();
table.increments("id").primary();
table
.text("title")
.notNullable()
.unique({ indexName: "repack_title_unique_" + timestamp });
table
.text("magnet")
.notNullable()
.unique({ indexName: "repack_magnet_unique_" + timestamp });
table.text("repacker").notNullable();
table.text("fileSize").notNullable();
table.datetime("uploadDate").notNullable();
table.datetime("createdAt").notNullable().defaultTo(knex.fn.now());
table.datetime("updatedAt").notNullable().defaultTo(knex.fn.now());
table
.integer("downloadSourceId")
.references("download_source.id")
.onDelete("CASCADE");
await knex.schema.alterTable("repack", (table) => {
table.text("uris").notNullable().defaultTo("[]");
});
await knex.raw(
`INSERT INTO "temporary_repack"("id", "title", "magnet", "repacker", "fileSize", "uploadDate", "createdAt", "updatedAt", "downloadSourceId") SELECT "id", "title", "magnet", "repacker", "fileSize", "uploadDate", "createdAt", "updatedAt", "downloadSourceId" FROM "repack"`
);
await knex.schema.dropTable("repack");
await knex.schema.renameTable("temporary_repack", "repack");
},
down: async (knex: Knex) => {
await knex.schema.renameTable("repack", "temporary_repack");
await knex.schema.createTable("repack", (table) => {
table.increments("id").primary();
table.text("title").notNullable().unique();
table.text("magnet").notNullable().unique();
await knex.schema.alterTable("repack", (table) => {
table.integer("page");
table.text("repacker").notNullable();
table.text("fileSize").notNullable();
table.datetime("uploadDate").notNullable();
table.datetime("createdAt").notNullable().defaultTo(knex.fn.now());
table.datetime("updatedAt").notNullable().defaultTo(knex.fn.now());
table
.integer("downloadSourceId")
.references("download_source.id")
.onDelete("CASCADE");
table.dropColumn("uris");
});
await knex.raw(
`INSERT INTO "repack"("id", "title", "magnet", "repacker", "fileSize", "uploadDate", "createdAt", "updatedAt", "downloadSourceId") SELECT "id", "title", "magnet", "repacker", "fileSize", "uploadDate", "createdAt", "updatedAt", "downloadSourceId" FROM "temporary_repack"`
);
await knex.schema.dropTable("temporary_repack");
},
};

View File

@@ -0,0 +1,17 @@
import type { HydraMigration } from "@main/knex-client";
import type { Knex } from "knex";
export const EnsureRepackUris: HydraMigration = {
name: "EnsureRepackUris",
up: async (knex: Knex) => {
await knex.schema.hasColumn("repack", "uris").then(async (exists) => {
if (!exists) {
await knex.schema.table("repack", (table) => {
table.text("uris").notNullable().defaultTo("[]");
});
}
});
},
down: async (_knex: Knex) => {},
};

View File

@@ -0,0 +1,41 @@
import type { HydraMigration } from "@main/knex-client";
import type { Knex } from "knex";
export const FixMissingColumns: HydraMigration = {
name: "FixMissingColumns",
up: async (knex: Knex) => {
const timestamp = new Date().getTime();
await knex.schema
.hasColumn("repack", "downloadSourceId")
.then(async (exists) => {
if (!exists) {
await knex.schema.table("repack", (table) => {
table
.integer("downloadSourceId")
.references("download_source.id")
.onDelete("CASCADE");
});
}
});
await knex.schema.hasColumn("game", "remoteId").then(async (exists) => {
if (!exists) {
await knex.schema.table("game", (table) => {
table
.text("remoteId")
.unique({ indexName: "game_remoteId_unique_" + timestamp });
});
}
});
await knex.schema.hasColumn("game", "uri").then(async (exists) => {
if (!exists) {
await knex.schema.table("game", (table) => {
table.text("uri");
});
}
});
},
down: async (_knex: Knex) => {},
};

View File

@@ -6,6 +6,8 @@ import { uploadGamesBatch } from "./library-sync";
import { clearGamesRemoteIds } from "./library-sync/clear-games-remote-id";
import { logger } from "./logger";
import { UserNotLoggedInError } from "@shared";
import { omit } from "lodash-es";
import { appVersion } from "@main/constants";
interface HydraApiOptions {
needsAuth: boolean;
@@ -79,12 +81,16 @@ export class HydraApi {
static async setupApi() {
this.instance = axios.create({
baseURL: import.meta.env.MAIN_VITE_API_URL,
headers: { "User-Agent": `Hydra Launcher v${appVersion}` },
});
this.instance.interceptors.request.use(
(request) => {
logger.log(" ---- REQUEST -----");
logger.log(request.method, request.url, request.params, request.data);
const data = Array.isArray(request.data)
? request.data
: omit(request.data, ["refreshToken"]);
logger.log(request.method, request.url, request.params, data);
return request;
},
(error) => {
@@ -96,11 +102,14 @@ export class HydraApi {
this.instance.interceptors.response.use(
(response) => {
logger.log(" ---- RESPONSE -----");
const data = Array.isArray(response.data)
? response.data
: omit(response.data, ["username", "accessToken", "refreshToken"]);
logger.log(
response.status,
response.config.method,
response.config.url,
response.data
data
);
return response;
},
@@ -166,7 +175,10 @@ export class HydraApi {
this.userAuth.authToken = accessToken;
this.userAuth.expirationTimestamp = tokenExpirationTimestamp;
logger.log("Token refreshed", this.userAuth);
logger.log(
"Token refreshed. New expiration:",
this.userAuth.expirationTimestamp
);
userAuthRepository.upsert(
{

View File

@@ -1,20 +1,8 @@
import { Game } from "@main/entity";
import { HydraApi } from "../hydra-api";
import { gameRepository } from "@main/repository";
import { logger } from "../logger";
export const createGame = async (game: Game) => {
HydraApi.post(
"/games/download",
{
objectId: game.objectID,
shop: game.shop,
},
{ needsAuth: false }
).catch((err) => {
logger.error("Failed to create game download", err);
});
return HydraApi.post(`/profile/games`, {
objectId: game.objectID,
playTimeInMilliseconds: Math.trunc(game.playTimeInMilliseconds),

View File

@@ -119,7 +119,7 @@ const onCloseGame = (game: Game) => {
if (game.remoteId) {
updateGamePlaytime(
game,
performance.now() - gamePlaytime.firstTick,
performance.now() - gamePlaytime.lastSyncTick,
game.lastTimePlayed!
).catch(() => {});
} else {

View File

@@ -150,6 +150,7 @@ contextBridge.exposeInMainWorld("electron", {
processProfileImage: (imagePath: string) =>
ipcRenderer.invoke("processProfileImage", imagePath),
getFriendRequests: () => ipcRenderer.invoke("getFriendRequests"),
syncFriendRequests: () => ipcRenderer.invoke("syncFriendRequests"),
updateFriendRequest: (userId: string, action: FriendRequestAction) =>
ipcRenderer.invoke("updateFriendRequest", userId, action),
sendFriendRequest: (userId: string) =>

View File

@@ -43,7 +43,7 @@ export function App() {
isFriendsModalVisible,
friendRequetsModalTab,
friendModalUserId,
fetchFriendRequests,
syncFriendRequests,
hideFriendsModal,
} = useUserDetails();
@@ -105,22 +105,22 @@ export function App() {
fetchUserDetails().then((response) => {
if (response) {
updateUserDetails(response);
fetchFriendRequests();
syncFriendRequests();
}
});
}, [fetchUserDetails, fetchFriendRequests, updateUserDetails, dispatch]);
}, [fetchUserDetails, syncFriendRequests, updateUserDetails, dispatch]);
const onSignIn = useCallback(() => {
fetchUserDetails().then((response) => {
if (response) {
updateUserDetails(response);
fetchFriendRequests();
syncFriendRequests();
showSuccessToast(t("successfully_signed_in"));
}
});
}, [
fetchUserDetails,
fetchFriendRequests,
syncFriendRequests,
t,
showSuccessToast,
updateUserDetails,

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,3 @@
import { useRef } from "react";
import Lottie from "lottie-react";
import downloadingAnimation from "@renderer/assets/lottie/downloading.json";
@@ -8,11 +7,8 @@ export interface DownloadIconProps {
}
export function DownloadIcon({ isDownloading }: DownloadIconProps) {
const lottieRef = useRef(null);
return (
<Lottie
lottieRef={lottieRef}
animationData={downloadingAnimation}
loop={isDownloading}
autoplay={isDownloading}

View File

@@ -15,15 +15,15 @@ export function SidebarProfile() {
const { t } = useTranslation("sidebar");
const { userDetails, friendRequests, showFriendsModal, fetchFriendRequests } =
useUserDetails();
const {
userDetails,
friendRequestCount,
showFriendsModal,
syncFriendRequests,
} = useUserDetails();
const { gameRunning } = useAppSelector((state) => state.gameRunning);
const receivedRequests = useMemo(() => {
return friendRequests.filter((request) => request.type === "RECEIVED");
}, [friendRequests]);
const handleProfileClick = () => {
if (userDetails === null) {
window.electron.openAuthWindow();
@@ -35,7 +35,7 @@ export function SidebarProfile() {
useEffect(() => {
pollingInterval.current = setInterval(() => {
fetchFriendRequests();
syncFriendRequests();
}, LONG_POLLING_INTERVAL);
return () => {
@@ -43,7 +43,7 @@ export function SidebarProfile() {
clearInterval(pollingInterval.current);
}
};
}, [fetchFriendRequests]);
}, [syncFriendRequests]);
const friendsButton = useMemo(() => {
if (!userDetails) return null;
@@ -57,16 +57,16 @@ export function SidebarProfile() {
}
title={t("friends")}
>
{receivedRequests.length > 0 && (
{friendRequestCount > 0 && (
<small className={styles.friendsButtonBadge}>
{receivedRequests.length > 99 ? "99+" : receivedRequests.length}
{friendRequestCount > 99 ? "99+" : friendRequestCount}
</small>
)}
<PeopleIcon size={16} />
</button>
);
}, [userDetails, t, receivedRequests, showFriendsModal]);
}, [userDetails, t, friendRequestCount, showFriendsModal]);
return (
<div className={styles.profileContainer}>
@@ -100,6 +100,7 @@ export function SidebarProfile() {
textOverflow: "ellipsis",
whiteSpace: "nowrap",
width: "100%",
textAlign: "left",
}}
>
<small>{gameRunning.title}</small>

View File

@@ -26,7 +26,7 @@ export const sidebar = recipe({
paddingTop: `${SPACING_UNIT * 6}px`,
},
false: {
paddingTop: `${SPACING_UNIT * 2}px`,
paddingTop: `${SPACING_UNIT}px`,
},
},
},

View File

@@ -105,7 +105,7 @@ export function GameDetailsContextProvider({
setShopDetails(appDetailsResult.value);
if (
appDetailsResult.value!.content_descriptors.ids.includes(
appDetailsResult.value?.content_descriptors.ids.includes(
SteamContentDescriptor.AdultOnlySexualContent
)
) {

View File

@@ -23,6 +23,8 @@ import type {
GameStats,
TrendingGame,
UserStats,
UserDetails,
FriendRequestSync,
} from "@types";
import type { DiskSpace } from "check-disk-space";
@@ -153,7 +155,7 @@ declare global {
) => Promise<void>;
/* Profile */
getMe: () => Promise<UserProfile | null>;
getMe: () => Promise<UserDetails | null>;
undoFriendship: (userId: string) => Promise<void>;
updateProfile: (
updateProfile: UpdateProfileRequest
@@ -163,6 +165,7 @@ declare global {
path: string
) => Promise<{ imagePath: string; mimeType: string }>;
getFriendRequests: () => Promise<FriendRequest[]>;
syncFriendRequests: () => Promise<FriendRequestSync>;
updateFriendRequest: (
userId: string,
action: FriendRequestAction

View File

@@ -1,11 +1,12 @@
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
import { UserFriendModalTab } from "@renderer/pages/shared-modals/user-friend-modal";
import type { FriendRequest, UserProfile } from "@types";
import type { FriendRequest, UserDetails } from "@types";
export interface UserDetailsState {
userDetails: UserProfile | null;
userDetails: UserDetails | null;
profileBackground: null | string;
friendRequests: FriendRequest[];
friendRequestCount: number;
isFriendsModalVisible: boolean;
friendRequetsModalTab: UserFriendModalTab | null;
friendModalUserId: string;
@@ -15,6 +16,7 @@ const initialState: UserDetailsState = {
userDetails: null,
profileBackground: null,
friendRequests: [],
friendRequestCount: 0,
isFriendsModalVisible: false,
friendRequetsModalTab: null,
friendModalUserId: "",
@@ -24,7 +26,7 @@ export const userDetailsSlice = createSlice({
name: "user-details",
initialState,
reducers: {
setUserDetails: (state, action: PayloadAction<UserProfile | null>) => {
setUserDetails: (state, action: PayloadAction<UserDetails | null>) => {
state.userDetails = action.payload;
},
setProfileBackground: (state, action: PayloadAction<string | null>) => {
@@ -33,6 +35,9 @@ export const userDetailsSlice = createSlice({
setFriendRequests: (state, action: PayloadAction<FriendRequest[]>) => {
state.friendRequests = action.payload;
},
setFriendRequestCount: (state, action: PayloadAction<number>) => {
state.friendRequestCount = action.payload;
},
setFriendsModalVisible: (
state,
action: PayloadAction<{ initialTab: UserFriendModalTab; userId: string }>
@@ -52,6 +57,7 @@ export const {
setUserDetails,
setProfileBackground,
setFriendRequests,
setFriendRequestCount,
setFriendsModalVisible,
setFriendsModalHidden,
} = userDetailsSlice.actions;

View File

@@ -6,11 +6,12 @@ import {
setFriendRequests,
setFriendsModalVisible,
setFriendsModalHidden,
setFriendRequestCount,
} from "@renderer/features";
import type {
FriendRequestAction,
UpdateProfileRequest,
UserProfile,
UserDetails,
} from "@types";
import { UserFriendModalTab } from "@renderer/pages/shared-modals/user-friend-modal";
@@ -21,6 +22,7 @@ export function useUserDetails() {
userDetails,
profileBackground,
friendRequests,
friendRequestCount,
isFriendsModalVisible,
friendModalUserId,
friendRequetsModalTab,
@@ -40,7 +42,7 @@ export function useUserDetails() {
}, [clearUserDetails]);
const updateUserDetails = useCallback(
async (userDetails: UserProfile) => {
async (userDetails: UserDetails) => {
dispatch(setUserDetails(userDetails));
if (userDetails.profileImageUrl) {
@@ -83,7 +85,10 @@ export function useUserDetails() {
const patchUser = useCallback(
async (values: UpdateProfileRequest) => {
const response = await window.electron.updateProfile(values);
return updateUserDetails(response);
return updateUserDetails({
...response,
username: userDetails?.username || "",
});
},
[updateUserDetails]
);
@@ -92,11 +97,21 @@ export function useUserDetails() {
return window.electron
.getFriendRequests()
.then((friendRequests) => {
syncFriendRequests();
dispatch(setFriendRequests(friendRequests));
})
.catch(() => {});
}, [dispatch]);
const syncFriendRequests = useCallback(async () => {
return window.electron
.syncFriendRequests()
.then((sync) => {
dispatch(setFriendRequestCount(sync.friendRequestCount));
})
.catch(() => {});
}, [dispatch]);
const showFriendsModal = useCallback(
(initialTab: UserFriendModalTab, userId: string) => {
dispatch(setFriendsModalVisible({ initialTab, userId }));
@@ -140,6 +155,7 @@ export function useUserDetails() {
userDetails,
profileBackground,
friendRequests,
friendRequestCount,
friendRequetsModalTab,
isFriendsModalVisible,
friendModalUserId,
@@ -152,6 +168,7 @@ export function useUserDetails() {
patchUser,
sendFriendRequest,
fetchFriendRequests,
syncFriendRequests,
updateFriendRequestState,
blockUser,
unblockUser,

View File

@@ -149,8 +149,7 @@ export const randomizerButton = style({
animationName: slideIn,
animationDuration: "0.2s",
position: "fixed",
/* Bottom panel height + spacing */
bottom: `${26 + SPACING_UNIT * 2}px`,
bottom: `${SPACING_UNIT * 3}px`,
/* Scroll bar + spacing */
right: `${9 + SPACING_UNIT * 2}px`,
boxShadow: "rgba(255, 255, 255, 0.1) 0px 0px 10px 1px",

View File

@@ -160,12 +160,15 @@ export function DownloadSettingsModal({
))}
</div>
{selectedDownloader && selectedDownloader !== Downloader.Torrent && (
<p style={{ marginTop: `${SPACING_UNIT}px` }}>
<span style={{ color: vars.color.warning }}>{t("warning")}</span>{" "}
{t("hydra_needs_to_remain_open")}
</p>
)}
{selectedDownloader != null &&
selectedDownloader !== Downloader.Torrent && (
<p style={{ marginTop: `${SPACING_UNIT}px` }}>
<span style={{ color: vars.color.warning }}>
{t("warning")}
</span>{" "}
{t("hydra_needs_to_remain_open")}
</p>
)}
</div>
<div

View File

@@ -1,4 +1,4 @@
import { useContext, useEffect, useState } from "react";
import { useContext, useState } from "react";
import type { HowLongToBeatCategory, SteamAppDetails } from "@types";
import { useTranslation } from "react-i18next";
import { Button } from "@renderer/components";
@@ -9,7 +9,7 @@ import { useFormat } from "@renderer/hooks";
import { DownloadIcon, PeopleIcon } from "@primer/octicons-react";
export function Sidebar() {
const [_howLongToBeat, setHowLongToBeat] = useState<{
const [_howLongToBeat, _setHowLongToBeat] = useState<{
isLoading: boolean;
data: HowLongToBeatCategory[] | null;
}>({ isLoading: true, data: null });
@@ -17,27 +17,26 @@ export function Sidebar() {
const [activeRequirement, setActiveRequirement] =
useState<keyof SteamAppDetails["pc_requirements"]>("minimum");
const { gameTitle, shopDetails, objectID, stats } =
useContext(gameDetailsContext);
const { gameTitle, shopDetails, stats } = useContext(gameDetailsContext);
const { t } = useTranslation("game_details");
const { numberFormatter } = useFormat();
useEffect(() => {
if (objectID) {
setHowLongToBeat({ isLoading: true, data: null });
// useEffect(() => {
// if (objectID) {
// setHowLongToBeat({ isLoading: true, data: null });
window.electron
.getHowLongToBeat(objectID, "steam", gameTitle)
.then((howLongToBeat) => {
setHowLongToBeat({ isLoading: false, data: howLongToBeat });
})
.catch(() => {
setHowLongToBeat({ isLoading: false, data: null });
});
}
}, [objectID, gameTitle]);
// window.electron
// .getHowLongToBeat(objectID, "steam", gameTitle)
// .then((howLongToBeat) => {
// setHowLongToBeat({ isLoading: false, data: howLongToBeat });
// })
// .catch(() => {
// setHowLongToBeat({ isLoading: false, data: null });
// });
// }
// }, [objectID, gameTitle]);
return (
<aside className={styles.contentSidebar}>

View File

@@ -1,4 +1,4 @@
import { useCallback, useEffect, useState } from "react";
import { useCallback, useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
@@ -8,10 +8,11 @@ import { Button, GameCard, Hero } from "@renderer/components";
import type { Steam250Game, CatalogueEntry } from "@types";
import starsAnimation from "@renderer/assets/lottie/stars.json";
import flameAnimation from "@renderer/assets/lottie/flame.json";
import * as styles from "./home.css";
import { vars } from "@renderer/theme.css";
import Lottie from "lottie-react";
import { SPACING_UNIT, vars } from "@renderer/theme.css";
import Lottie, { type LottieRefCurrentProps } from "lottie-react";
import { buildGameDetailsPath } from "@renderer/helpers";
import { CatalogueCategory } from "@shared";
@@ -19,6 +20,8 @@ export function Home() {
const { t } = useTranslation("home");
const navigate = useNavigate();
const flameAnimationRef = useRef<LottieRefCurrentProps>(null);
const [isLoading, setIsLoading] = useState(false);
const [randomGame, setRandomGame] = useState<Steam250Game | null>(null);
@@ -82,6 +85,18 @@ export function Home() {
const categories = Object.values(CatalogueCategory);
const handleMouseEnterCategory = (category: CatalogueCategory) => {
if (category === CatalogueCategory.Hot) {
flameAnimationRef?.current?.play();
}
};
const handleMouseLeaveCategory = (category: CatalogueCategory) => {
if (category === CatalogueCategory.Hot) {
flameAnimationRef?.current?.stop();
}
};
return (
<SkeletonTheme baseColor={vars.color.background} highlightColor="#444">
<section className={styles.content}>
@@ -100,7 +115,28 @@ export function Home() {
: "outline"
}
onClick={() => handleCategoryClick(category)}
onMouseEnter={() => handleMouseEnterCategory(category)}
onMouseLeave={() => handleMouseLeaveCategory(category)}
>
{category === CatalogueCategory.Hot && (
<div
style={{ width: 16, height: 16, position: "relative" }}
>
<Lottie
lottieRef={flameAnimationRef}
animationData={flameAnimation}
loop
autoplay={false}
style={{
width: 30,
top: -10,
left: -5,
position: "absolute",
}}
/>
</div>
)}
{t(category)}
</Button>
</li>
@@ -116,14 +152,32 @@ export function Home() {
<Lottie
animationData={starsAnimation}
style={{ width: 70, position: "absolute", top: -28, left: -27 }}
loop
loop={Boolean(randomGame)}
/>
</div>
{t("surprise_me")}
</Button>
</section>
<h2>{t(currentCatalogueCategory)}</h2>
<h2 style={{ display: "flex", gap: SPACING_UNIT }}>
{currentCatalogueCategory === CatalogueCategory.Hot && (
<div style={{ width: 24, height: 24, position: "relative" }}>
<Lottie
animationData={flameAnimation}
loop
autoplay
style={{
width: 40,
top: -10,
left: -5,
position: "absolute",
}}
/>
</div>
)}
{t(currentCatalogueCategory)}
</h2>
<section className={styles.cards}>
{isLoading

View File

@@ -155,6 +155,7 @@ export const listItemImage = style({
width: "32px",
height: "32px",
borderRadius: "4px",
objectFit: "cover",
});
export const listItemDetails = style({

View File

@@ -170,6 +170,10 @@ export interface UserBlocks {
blocks: UserFriend[];
}
export interface FriendRequestSync {
friendRequestCount: number;
}
export interface FriendRequest {
id: string;
displayName: string;
@@ -190,23 +194,34 @@ export interface UserProfileCurrentGame extends Omit<GameRunning, "objectID"> {
sessionDurationInSeconds: number;
}
export type ProfileVisibility = "PUBLIC" | "PRIVATE" | "FRIENDS";
export interface UserDetails {
id: string;
username: string;
displayName: string;
profileImageUrl: string | null;
profileVisibility: ProfileVisibility;
bio: string;
}
export interface UserProfile {
id: string;
displayName: string;
profileImageUrl: string | null;
profileVisibility: "PUBLIC" | "PRIVATE" | "FRIENDS";
totalPlayTimeInSeconds: number;
profileVisibility: ProfileVisibility;
libraryGames: UserGame[];
recentGames: UserGame[];
friends: UserFriend[];
totalFriends: number;
relation: UserRelation | null;
currentGame: UserProfileCurrentGame | null;
bio: string;
}
export interface UpdateProfileRequest {
displayName?: string;
profileVisibility?: "PUBLIC" | "PRIVATE" | "FRIENDS";
profileVisibility?: ProfileVisibility;
profileImageUrl?: string | null;
bio?: string;
}