Compare commits

...

52 Commits

Author SHA1 Message Date
Zamitto
e77991ea16 Merge pull request #1009 from hydralauncher/chore/update-api-url
Some checks are pending
Release / build (ubuntu-latest) (push) Waiting to run
Release / build (windows-latest) (push) Waiting to run
chore: update api and auth url
2024-09-22 16:42:19 -03:00
Zamitto
5e55c05bd7 chore: i18n for friend code 2024-09-22 16:30:47 -03:00
Zamitto
81e74f068f fix: import correctly vite env 2024-09-22 16:25:16 -03:00
Zamitto
d5a510175f feat: use env to open auth window 2024-09-22 16:21:01 -03:00
Zamitto
2b2f29da61 chore: add envs to pipeline 2024-09-22 16:20:45 -03:00
Zamitto
79e2eb042a Merge pull request #1001 from hydralauncher/chore/bump-version-and-update-CSP
Some checks failed
Release / build (ubuntu-latest) (push) Has been cancelled
Release / build (windows-latest) (push) Has been cancelled
chore: bump version and change CSP
2024-09-21 10:47:32 -03:00
Zamitto
f9ad26c836 chore: bump version and change CSP 2024-09-21 10:38:53 -03:00
Zamitto
f6fbfe33e0 Merge pull request #988 from alexhostrup/nb-translation
Norwegian bokmål translation and README
2024-09-21 10:38:26 -03:00
Alexander Hostrup
18d6ca630a Merge branch 'main' into nb-translation 2024-09-20 23:39:59 +02:00
Alexander Hostrup
c7afcff0b4 Fixed small issue with language code being wrong 2024-09-20 23:38:20 +02:00
Zamitto
a3bdfe7641 Merge pull request #995 from hydralauncher/fix/game-details-gallery-align
Some checks failed
Release / build (ubuntu-latest) (push) Has been cancelled
Release / build (windows-latest) (push) Has been cancelled
fix: game details gallery align
2024-09-19 20:48:48 -03:00
Zamitto
e7b4f8e1c8 feat: add lazy loading 2024-09-19 15:38:33 -03:00
Zamitto
e526c0f650 feat: adjust align on wider images 2024-09-19 15:38:20 -03:00
Zamitto
9791c311f1 chore: update bug issue template 2024-09-18 15:46:14 -03:00
Alexander Hostrup
b1ea9cbfaa formatting 2024-09-18 19:54:08 +02:00
Alexander Hostrup
74d2ec8238 Added translation and README in Norwegian Bokmål 2024-09-18 19:53:46 +02:00
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
61 changed files with 2776 additions and 209 deletions

View File

@@ -1,4 +1,5 @@
MAIN_VITE_STEAMGRIDDB_API_KEY=YOUR_API_KEY
MAIN_VITE_API_URL=API_URL
MAIN_VITE_AUTH_URL=AUTH_URL
MAIN_VITE_STEAMGRIDDB_API_KEY=YOUR_API_KEY
MAIN_VITE_SENTRY_DSN=YOUR_SENTRY_DSN
SENTRY_AUTH_TOKEN=

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
@@ -29,12 +29,15 @@ body:
validations:
required: false
- type: textarea
id: screenshots
id: additional-info
attributes:
label: Screenshots
description: If applicable, add screenshots to help explain your problem.
label: Additional information and data
description: |
Add screenshots and upload your logs file here.
Logs location on Windows: "%appdata%/hydra"
Logs location on Linux: "~/.config/hydra/"
validations:
required: false
required: true
- type: input
id: OS
attributes:
@@ -49,13 +52,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:

View File

@@ -41,6 +41,7 @@ jobs:
yarn build:linux
env:
MAIN_VITE_API_URL: ${{ vars.MAIN_VITE_API_URL }}
MAIN_VITE_AUTH_URL: ${{ vars.MAIN_VITE_AUTH_URL }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
MAIN_VITE_SENTRY_DSN: ${{ vars.MAIN_VITE_SENTRY_DSN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -50,6 +51,7 @@ jobs:
run: yarn build:win
env:
MAIN_VITE_API_URL: ${{ vars.MAIN_VITE_API_URL }}
MAIN_VITE_AUTH_URL: ${{ vars.MAIN_VITE_AUTH_URL }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
MAIN_VITE_SENTRY_DSN: ${{ vars.MAIN_VITE_SENTRY_DSN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -43,6 +43,7 @@ jobs:
yarn build:linux
env:
MAIN_VITE_API_URL: ${{ vars.MAIN_VITE_API_URL }}
MAIN_VITE_AUTH_URL: ${{ vars.MAIN_VITE_AUTH_URL }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
MAIN_VITE_SENTRY_DSN: ${{ vars.MAIN_VITE_SENTRY_DSN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -52,6 +53,7 @@ jobs:
run: yarn build:win
env:
MAIN_VITE_API_URL: ${{ vars.MAIN_VITE_API_URL }}
MAIN_VITE_AUTH_URL: ${{ vars.MAIN_VITE_AUTH_URL }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
MAIN_VITE_SENTRY_DSN: ${{ vars.MAIN_VITE_SENTRY_DSN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -23,6 +23,7 @@
[![de](https://img.shields.io/badge/lang-de-black)](README.de.md)
[![ita](https://img.shields.io/badge/lang-it-red)](README.it.md)
[![cs](https://img.shields.io/badge/lang-cs-purple)](README.cs.md)
[![nb](https://img.shields.io/badge/lang-nb-blue)](README.nb.md)
![Hydra Catalogue](./docs/screenshot.png)

View File

@@ -23,6 +23,7 @@
[![de](https://img.shields.io/badge/lang-de-black)](README.de.md)
[![ita](https://img.shields.io/badge/lang-it-red)](README.it.md)
[![cs](https://img.shields.io/badge/lang-cs-purple)](README.cs.md)
[![nb](https://img.shields.io/badge/lang-nb-blue)](README.nb.md)
![Hydra Katalog](./docs/screenshot.png)

View File

@@ -23,6 +23,7 @@
[![fr](https://img.shields.io/badge/lang-fr-blue)](README.fr.md)
[![de](https://img.shields.io/badge/lang-de-black)](README.de.md)
[![cs](https://img.shields.io/badge/lang-cs-purple)](README.cs.md)
[![nb](https://img.shields.io/badge/lang-nb-blue)](README.nb.md)
![Hydra Katalog](./docs/screenshot.png)

View File

@@ -23,6 +23,7 @@
[![de](https://img.shields.io/badge/lang-de-black)](README.de.md)
[![ita](https://img.shields.io/badge/lang-it-red)](README.it.md)
[![cs](https://img.shields.io/badge/lang-cs-purple)](README.cs.md)
[![nb](https://img.shields.io/badge/lang-nb-blue)](README.nb.md)
![Hydra Catalogue](./docs/screenshot.png)

View File

@@ -23,6 +23,7 @@
[![de](https://img.shields.io/badge/lang-de-black)](README.de.md)
[![ita](https://img.shields.io/badge/lang-it-red)](README.it.md)
[![cs](https://img.shields.io/badge/lang-cs-purple)](README.cs.md)
[![nb](https://img.shields.io/badge/lang-nb-blue)](README.nb.md)
![Catalogue Hydra](./docs/screenshot.png)

View File

@@ -23,6 +23,7 @@
[![de](https://img.shields.io/badge/lang-de-black)](README.de.md)
[![ita](https://img.shields.io/badge/lang-it-red)](README.it.md)
[![cs](https://img.shields.io/badge/lang-cs-purple)](README.cs.md)
[![nb](https://img.shields.io/badge/lang-nb-blue)](README.nb.md)
![Hydra Catalogue](./docs/screenshot.png)

View File

@@ -23,6 +23,7 @@
[![de](https://img.shields.io/badge/lang-de-black)](README.de.md)
[![ita](https://img.shields.io/badge/lang-it-red)](README.it.md)
[![cs](https://img.shields.io/badge/lang-cs-purple)](README.cs.md)
[![nb](https://img.shields.io/badge/lang-nb-blue)](README.nb.md)
![Hydra Catalogue](./docs/screenshot.png)

187
README.nb.md Normal file
View File

@@ -0,0 +1,187 @@
<br>
<div align="center">
[<img src="./resources/icon.png" width="144"/>](https://hydralauncher.site)
<h1 align="center">Hydra Launcher</h1>
<p align="center">
<strong>Hydra er en spill launcher sin egen innebygt bittorrent klient.</strong>
</p>
[![build](https://img.shields.io/github/actions/workflow/status/hydralauncher/hydra/build.yml)](https://github.com/hydralauncher/hydra/actions)
[![release](https://img.shields.io/github/package-json/v/hydralauncher/hydra)](https://github.com/hydralauncher/hydra/releases)
[![pt-BR](https://img.shields.io/badge/lang-pt--BR-green.svg)](README.pt-BR.md)
[![en](https://img.shields.io/badge/lang-en-red.svg)](README.md)
[![ru](https://img.shields.io/badge/lang-ru-yellow.svg)](README.ru.md)
[![uk-UA](https://img.shields.io/badge/lang-uk--UA-blue)](README.uk-UA.md)
[![be](https://img.shields.io/badge/lang-be-orange)](README.be.md)
[![es](https://img.shields.io/badge/lang-es-red)](README.es.md)
[![fr](https://img.shields.io/badge/lang-fr-blue)](README.fr.md)
[![de](https://img.shields.io/badge/lang-de-black)](README.de.md)
[![ita](https://img.shields.io/badge/lang-it-red)](README.it.md)
[![cs](https://img.shields.io/badge/lang-cs-purple)](README.cs.md)
[![nb](https://img.shields.io/badge/lang-nb-blue)](README.nb.md)
![Hydra Catalogue](./docs/screenshot.png)
</div>
## Innhold
- [Innhold](#innhold)
- [Om](#om)
- [Funksjoner](#funksjoner)
- [Installasjon](#installasjon)
- [Bidra](#-bidra)
- [Bli med i Telegram kanalen vår](#-join-our-telegram)
- [Forke og klone repoet ditt](#fork-and-clone-your-repository)
- [Måter du kan bidra](#ways-you-can-contribute)
- [Prosjekt Struktur](#project-structure)
- [Bygg fra kilden](#build-from-source)
- [Installere Node.js](#install-nodejs)
- [Installere Yarn](#install-yarn)
- [Installere Node-avhengigheter](#install-node-dependencies)
- [Installere Python 3.9](#install-python-39)
- [Installere Python-avhengigheter](#install-python-dependencies)
- [Miljøvariabler](#environment-variables)
- [Kjøre](#running)
- [Bygge](#build)
- [Bygg bittorrent klienten](#build-the-bittorrent-client)
- [Bygg Electron applikationen](#build-the-electron-application)
- [Bidragsytere](#contributors)
- [Lisens](#license)
## Om
**Hydra** er en **Spill Launcher** sin egne innbygte **BitTorrent Klient**.
<br>
Launcheren er skrevet i TypeScript (Electron) og Python, som håndterer torrent systemet ved bruk av libtorrent.
## Funksjoner
- Sin egen innebyggte bittorrent klient
- How Long To Beat (HLTB) integrasjon på spillsiden
- Nedlastingssti tilpasning
- Windows og Linux understøttelse
- Konstant oppdatert
- Og mer ...
## Installasjon
Følg trinnene her under for å innstallere:
1. Last ned den seneste versjonen av Hydra fra [Releases](https://github.com/hydralauncher/hydra/releases/latest) siden.
- Last kun .exe filen ned om du vil installere Hydra på Windows.
- Last kun .deb, .rpm eller .zip ned om du vil installere Hydra på Linux. (kommer an på Linux distroen din)
2. Kjør den nedlastede filen.
3. Nyt Hydra!
## <a name="contributing"> Bidra
### <a name="join-our-telegram"></a> Bli med i Telegram kanalen vår
Vi holder diskusjonene våres i [Telegram](https://t.me/hydralauncher) kanalen.
### Forke og klone repoet ditt
1. Fork repoet [(trykk her for å forke nå)](https://github.com/hydralauncher/hydra/fork)
2. Klon den forkede koden `git clone https://github.com/brukernavnet_ditt/hydra`
3. Lag en ny branch
4. Skyv committene dine
5. Send inn en ny Pull-forespørsel.
### Måter du kan bidra
- Oversetting: Vi har lyst at Hydra skal bli tilgjengelig for så mange som mulig. Hjelp gjerne med å oversette til nye språk eller oppdater og forbedre de som allerede er tilgjengelige i Hydra.
- Code: Hydra is built with Typescript, Electron and a little bit of Python. If you want to contribute, join our [Telegram](https://t.me/hydralauncher)!
- Kode: Hydra er laget med Typescript, Electron og lite gran Pythong. Hvis du har lyst på å bidra, bli med i [Telegram](https://t.me/hydralauncher) kanalen vår!
### Prosjektstruktur
- torrent-client: Vi bruker libtorrent, et Python-bibliotek, til å håndtere torrent nedlastinger.
- src/renderer: UIen til applikasjonen
- src/main: all logikken er her.
## Bygg fra kildekoden
### Installere Node.js
Vær sikker på at du har installert Node.js på maskinen din. Hvis du ikke har det, må du laste ned og installere det fra [nodejs.org](https://nodejs.org/).
### Installere Yarn
Yarn er et pakkehåndteringsverktøy til Node.js. Hvis du ikke allerede har installert Yarn, da kan du gjøre det ved å følge instruksjonene på [yarnpkg.com](https://classic.yarnpkg.com/lang/en/docs/install/).
### Installere Node-avhengigheter
Naviger til prosjektmappen og installer Node-avhengighetene ved bruk av Yarn:
```bash
cd hydra
yarn
```
### Installere Python 3.9
Vær sikker på at du har installert Python 3.9 på maskinen din. Du kan laste ned og installere det på [python.org](https://www.python.org/downloads/release/python-3913/).
### Installere Python-avhengigheter
Installer de nødvendige Python-avhengigheter ved bruk av pip:
```bash
pip install -r requirements.txt
```
## Miljøvariabler
Du trenger en SteamGridDB API nøkkel for å kunne hente spillikonene ved installasjon.
Når du har det, kan du kopiere eller endre navnet på `.env.example` filen til å være `.env` og lagre nøkkelen som `STEAMGRIDDB_API_KEY`.
## Kjøre
Når alt er satt op, kan du kjøre følgende kommando for å start både Electron prosessen og bittorrent klienten.
```bash
yarn dev
```
## Bygge
### Bygge bittorrent klienten
Bygg bittorrent klienten ved å bruke denne kommandoen:
```bash
python torrent-client/setup.py build
```
### Bygge Electron applikasjonen
Bygg Electron applikasjonen ved å bruke denne kommandoen:
På Windows:
```bash
yarn build:win
```
På Linux:
```bash
yarn build:linux
```
## Bidragsytere
<a href="https://github.com/hydralauncher/hydra/graphs/contributors">
<img src="https://contrib.rocks/image?repo=hydralauncher/hydra" />
</a>
## Lisens
Hydra bruker [MIT Lisensen](LICENSE).

View File

@@ -23,6 +23,7 @@
[![de](https://img.shields.io/badge/lang-de-black)](README.de.md)
[![ita](https://img.shields.io/badge/lang-it-red)](README.it.md)
[![cs](https://img.shields.io/badge/lang-cs-purple)](README.cs.md)
[![nb](https://img.shields.io/badge/lang-nb-blue)](README.nb.md)
![Hydra Catalogue](./docs/screenshot.png)

View File

@@ -23,6 +23,7 @@
[![de](https://img.shields.io/badge/lang-de-black)](README.de.md)
[![ita](https://img.shields.io/badge/lang-it-red)](README.it.md)
[![cs](https://img.shields.io/badge/lang-cs-purple)](README.cs.md)
[![nb](https://img.shields.io/badge/lang-nb-blue)](README.nb.md)
![Hydra Catalogue](./docs/screenshot.png)

View File

@@ -23,6 +23,7 @@
[![de](https://img.shields.io/badge/lang-de-black)](README.de.md)
[![ita](https://img.shields.io/badge/lang-it-red)](README.it.md)
[![cs](https://img.shields.io/badge/lang-cs-purple)](README.cs.md)
[![nb](https://img.shields.io/badge/lang-nb-blue)](README.nb.md)
![Hydra Catalogue](./docs/screenshot.png)

View File

@@ -23,6 +23,7 @@
[![de](https://img.shields.io/badge/lang-de-black)](README.de.md)
[![ita](https://img.shields.io/badge/lang-it-red)](README.it.md)
[![cs](https://img.shields.io/badge/lang-cs-purple)](README.cs.md)
[![nb](https://img.shields.io/badge/lang-nb-blue)](README.nb.md)
![Hydra Catalogue](./docs/screenshot.png)

7
build/installer.nsh Normal file
View File

@@ -0,0 +1,7 @@
!macro customUnInstall
${ifNot} ${isUpdated}
RMDir /r "$APPDATA\${APP_PACKAGE_NAME}"
RMDir /r "$APPDATA\hydra"
RMDir /r "$LOCALAPPDATA\hydralauncher-updater"
${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.7",
"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": {
@@ -311,6 +311,7 @@
"report_reason_violence": "Violence",
"report_reason_spam": "Spam",
"report_reason_other": "Other",
"profile_reported": "Profile reported"
"profile_reported": "Profile reported",
"your_friend_code": "Your friend code:"
}
}

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

@@ -22,6 +22,7 @@ import ro from "./ro/translation.json";
import ca from "./ca/translation.json";
import kk from "./kk/translation.json";
import cs from "./cs/translation.json";
import nb from "./nb/translation.json";
export default {
"pt-BR": ptBR,
@@ -48,4 +49,5 @@ export default {
ca,
kk,
cs,
nb,
};

View File

@@ -0,0 +1,316 @@
{
"language_name": "Norsk Bokmål",
"app": {
"successfully_signed_in": "Logget inn vellykket"
},
"home": {
"featured": "Anbefalinger",
"trending": "Trender",
"surprise_me": "Overrask meg",
"no_results": "Ingen resultater fundet",
"start_typing": "Begynn å skrive for å søke...",
"hot": "Populært akkurat nå",
"weekly": "📅 De mest populære spillene denne uken"
},
"sidebar": {
"catalogue": "Katalog",
"downloads": "Nedlastinger",
"settings": "Innstillinger",
"my_library": "Mitt bibliotek",
"downloading_metadata": "{{title}} (Laster ned metadata…)",
"paused": "{{title}} (Satt på pause)",
"downloading": "{{title}} ({{percentage}} - Laster ned…)",
"filter": "Filtrér bibliotek",
"home": "Hjem",
"queued": "{{title}} (I køen)",
"game_has_no_executable": "Spillet har ikke noen kjørbar fil valgt",
"sign_in": "Logge inn",
"friends": "Venner"
},
"header": {
"search": "Søk efter spill",
"home": "Hjem",
"catalogue": "Katalog",
"downloads": "Nedlastinger",
"search_results": "Søkeresultater",
"settings": "Innstillinger",
"version_available_install": "Versjon {{version}} tilgjengelig. Klikk her for å gjenstarte og installere.",
"version_available_download": "Versjon {{version}} tilgjengelig. Klikk her for at laste ned."
},
"bottom_panel": {
"no_downloads_in_progress": "Ingen nedlastinger pågår",
"downloading_metadata": "Laster ned {{title}} metadata…",
"downloading": "Laster ned {{title}}… ({{percentage}} ferdig) - Fullstendig nedlastet {{eta}} - {{speed}}",
"calculating_eta": "Laster ned {{title}}… ({{percentage}} ferdig) - Regner ut resterende tid…",
"checking_files": "Sjekker {{title}} filer… ({{percentage}} ferdig)"
},
"catalogue": {
"next_page": "Neste side",
"previous_page": "Forrige side"
},
"game_details": {
"open_download_options": "Åpne nedlastingsmuligheter",
"download_options_zero": "Ingen nedlastingsmulighet",
"download_options_one": "{{count}} nedlastingsmulighet",
"download_options_other": "{{count}} nedlastingsmuligheter",
"updated_at": "Oppdatert {{updated_at}}",
"install": "Installere",
"resume": "Fortsett",
"pause": "Pause",
"cancel": "Kansellere",
"remove": "Fjern",
"space_left_on_disk": "{{space}} tilbake på harddisken",
"eta": "Konklusjon {{eta}}",
"calculating_eta": "Utregner resterende tid…",
"downloading_metadata": "Laster ned metadata…",
"filter": "Filtrér gjennpakkinger",
"requirements": "Systemkrav",
"minimum": "Mindste",
"recommended": "Anbefalet",
"paused": "Satt på pause",
"release_date": "Offentliggjort den {{date}}",
"publisher": "Gitt ut av {{publisher}}",
"hours": "timer",
"minutes": "minutter",
"amount_hours": "{{amount}} timer",
"amount_minutes": "{{amount}} minutter",
"accuracy": "{{accuracy}}% nøyaktighet",
"add_to_library": "Tilføy til biblioteket",
"remove_from_library": "Fjern fra biblioteket",
"no_downloads": "Ingen nedlastinger tilgjengelig",
"play_time": "Spilt i {{amount}}",
"last_time_played": "Sist spilt {{period}}",
"not_played_yet": "Du har ikke spilt {{title}} enda",
"next_suggestion": "Neste forslag",
"play": "Spil",
"deleting": "Sletter installatør…",
"close": "Lukk",
"playing_now": "Spiller nå",
"change": "Endre",
"repacks_modal_description": "Velg den gjennpakking du vil laste ned",
"select_folder_hint": "For å endre standard mappen, gå til <0>Innstillingene</0>",
"download_now": "Last ned nå",
"no_shop_details": "Kunne ikke modta butikksdetaljene.",
"download_options": "Nedlastingsmuligheter",
"download_path": "Nedlastingssti",
"previous_screenshot": "Forrige skjermbilde",
"next_screenshot": "Neste skjermbilde",
"screenshot": "Skjermbilde {{number}}",
"open_screenshot": "Åpen skjermbilde {{number}}",
"download_settings": "Nedlastingsinnstillinger",
"downloader": "Laster ned",
"select_executable": "Velg",
"no_executable_selected": "Ingen kjørbar fil valgt",
"open_folder": "Åpne mappe",
"open_download_location": "Se nedlastingede filer",
"create_shortcut": "Opprett snarvei på skrivebordet",
"remove_files": "Fjern filer",
"remove_from_library_title": "Er du sikker?",
"remove_from_library_description": "Dette vil fjerne {{game}} fra biblioteket ditt",
"options": "Valgmuligheter",
"executable_section_title": "Kjørbar fil",
"executable_section_description": "Sti til filen som skal brukes når det trykkes på \"Spill\"",
"downloads_secion_title": "Nedlastinger",
"downloads_section_description": "Sjekk for oppdateringer eller andre versjoner af dette spillet",
"danger_zone_section_title": "Faresonen",
"danger_zone_section_description": "Fjern dette spillet fra biblioteket ditt eller filene som har blitt lastet ned av Hydra",
"download_in_progress": "Nedlasting pågår",
"download_paused": "Nedlasting satt på pause",
"last_downloaded_option": "Siste nedlastingsmulighet",
"create_shortcut_success": "Opprettelse av snarvei vellykket",
"create_shortcut_error": "Feil under oprettelsen av snarvei",
"nsfw_content_title": "Dette spillet inneholder upassende innhold",
"nsfw_content_description": "{{title}} inneholder innhold som ikke passer til alle aldre. Er du sikker på at du vil fortsette?",
"allow_nsfw_content": "Fortsett",
"refuse_nsfw_content": "Gå tilbake",
"stats": "Statistikk",
"download_count": "Nedlastinger",
"player_count": "Aktive spillere",
"download_error": "Denne nedlastingsmulighet er ikke tilgjengelig",
"download": "Last ned",
"executable_path_in_use": "Kjørbar fil blir allerede brukt av \"{{game}}\"",
"warning": "Advarsel:",
"hydra_needs_to_remain_open": "Hydra skal forbli åpent for at denne nedlastingen kan gjennomføres. I tilfelle av at Hydra lukker før nedlastingen er ferdig, mister du fremskrittet ditt."
},
"activation": {
"title": "Aktivér Hydra",
"installation_id": "Installasjons ID:",
"enter_activation_code": "Inntast aktiveringskoden din",
"message": "Hvis du ikke vet hvor du skal spørre om dette, burde du ikke ha dette.",
"activate": "Aktivér",
"loading": "Innleser…"
},
"downloads": {
"resume": "Fortsett",
"pause": "Pause",
"eta": "Konklusjon {{eta}}",
"paused": "Satt på pause",
"verifying": "Verifiserer…",
"completed": "Ferdig",
"removed": "Ikke lastet ned",
"cancel": "Kansellér",
"filter": "Filtrér nedlastede spill",
"remove": "Fjern",
"downloading_metadata": "Laster ned metadata…",
"deleting": "Sletter installatør…",
"delete": "Fjern installatør",
"delete_modal_title": "Er du sikker?",
"delete_modal_description": "Dette vil fjerne alle installasjonsfilene fra datamaskinen din",
"install": "Installér",
"download_in_progress": "Pågår",
"queued_downloads": "Nedlastingskø",
"downloads_completed": "Gjennomførte",
"queued": "I kø",
"no_downloads_title": "Ganske tomt",
"no_downloads_description": "Du har ikke lastet ned noe med Hydra enda, men det er aldri for sent å begynne.",
"checking_files": "Undersøker filer…"
},
"settings": {
"downloads_path": "Nedlastingssti",
"change": "Oppdater",
"notifications": "Notifikasjoner",
"enable_download_notifications": "Når en nedlasting blir ferdig",
"enable_repack_list_notifications": "Når en ny gjennpakking bliver lagt til",
"real_debrid_api_token_label": "Real-Debrid API nøkkel",
"quit_app_instead_hiding": "Avslut Hydra i stedet for å minimere til prosesslinjen",
"launch_with_system": "Åpne Hydra ved oppstart av datamaskinen",
"general": "Generelt",
"behavior": "Oppførsel",
"download_sources": "Nedlastingskilder",
"language": "Språk",
"real_debrid_api_token": "API nøkkel",
"enable_real_debrid": "Slå på Real-Debrid",
"real_debrid_description": "Real-Debrid er en ubegrenset nedlaster som gør det mulig for deg å laste ned filer med en gang og med den beste utnyttelsen av internethastigheten din.",
"real_debrid_invalid_token": "Ugyldig API nøkkel",
"real_debrid_api_token_hint": "Du kan få API nøkkelen din <0>her</0>",
"real_debrid_free_account_error": "Brukeren \"{{username}}\" er en gratis bruker. Vennligst abboner på Real-Debrid",
"real_debrid_linked_message": "Brukeren \"{{username}}\" er forbunnet",
"save_changes": "Lagre endringer",
"changes_saved": "Lagring av endringer vellykket",
"download_sources_description": "Hydra vil hente nedlastingslenker fra disse kildene. Kilde URLen skal være en direkte lenke til en .json fil som inneholder nedlastingslenkene.",
"validate_download_source": "Validér",
"remove_download_source": "Fjern",
"add_download_source": "Legg til kilde",
"download_count_zero": "Ingen nedlastingsmuligheter",
"download_count_one": "{{countFormatted}} nedlastingsmulighet",
"download_count_other": "{{countFormatted}} nedlastingsmuligheter",
"download_source_url": "Last ned kilde URL",
"add_download_source_description": "Sett inn URLen som inneholder .json filen",
"download_source_up_to_date": "Oppdatert",
"download_source_errored": "Mislyktes",
"sync_download_sources": "Synkroniser kilder",
"removed_download_source": "Nedlastingskilde fjernet",
"added_download_source": "La til Nedlastingskilde",
"download_sources_synced": "Alle nedlastingskilder er synkroniserte",
"insert_valid_json_url": "Innsett en gyldig JSON url",
"found_download_option_zero": "Ingen nedlastingsmulighet funnet",
"found_download_option_one": "Fant {{countFormatted}} nedlastingsmulighet",
"found_download_option_other": "Fant {{countFormatted}} nedlastingsmuligheter",
"import": "Importer",
"public": "Offentlig",
"private": "Privat",
"friends_only": "Kun blant venner",
"privacy": "Privatliv",
"profile_visibility": "Synlighet av profil",
"profile_visibility_description": "Velg hvem som kan se profilen din og biblioteket ditt",
"required_field": "Dette feltet er påkrevet",
"source_already_exists": "Denne kilden har allerede blitt lagt til",
"must_be_valid_url": "Kilden må være en gyldig URL",
"blocked_users": "Blokerte brukere",
"user_unblocked": "Brukeren har blit avblokert"
},
"notifications": {
"download_complete": "Nedlasting ferdig",
"game_ready_to_install": "{{title}} er klar til å bli installert",
"repack_list_updated": "Gjennpakkingslisten er opdateret",
"repack_count_one": "{{count}} gjennpakking lagt til",
"repack_count_other": "{{count}} gjennpakkinger lagt til",
"new_update_available": "Versjon {{version}} tilgjengelig",
"restart_to_install_update": "Gjenstart Hydra for å installere oppdateringen"
},
"system_tray": {
"open": "Åpne Hydra",
"quit": "Avslutt"
},
"game_card": {
"no_downloads": "Ingen nedlastinger tilgjengelig"
},
"binary_not_found_modal": {
"title": "Programmer ikke installert",
"description": "Wine eller Lutris kjørbar ble ikke funnet på systemet ditt",
"instructions": "Sjekk den korrekte måten å installere noen av de, på Linux distributionen din, så spillet kan kjøre på vanlig måte"
},
"modal": {
"close": "Lukk knapp"
},
"forms": {
"toggle_password_visibility": "Skift synlighet af passord"
},
"user_profile": {
"amount_hours": "{{amount}} timer",
"amount_minutes": "{{amount}} minutter",
"last_time_played": "Sist spilt {{period}}",
"activity": "Seneste aktivitet",
"library": "Bibliotek",
"total_play_time": "Samlet spilltid: {{amount}}",
"no_recent_activity_title": "Hmmm… ikke noe her",
"no_recent_activity_description": "Du har ikke spilt noen spill for på det seneste. Det er det på tide at endre på!",
"display_name": "Brukernavn",
"saving": "Lagrer",
"save": "Lagre",
"edit_profile": "Rediger Profil",
"saved_successfully": "Lagring vellykket",
"try_again": "Vennligst, prøv igjen",
"sign_out_modal_title": "Er du sikker?",
"cancel": "Kansellér",
"successfully_signed_out": "Utlogging vellykket",
"sign_out": "Log ut",
"playing_for": "Spiller i {{amount}}",
"sign_out_modal_text": "Biblioteket ditt er sammenkobelt med den nåverende brukeren. Når du logger ut er biblioteket ditt ikke synlig lenger, og hvilken som helst form for fremskritt bliver ikke lagret. Vil du fortsette med å logge ut?",
"add_friends": "Legg til venner",
"add": "Legg til",
"friend_code": "Vennekode",
"see_profile": "Se profil",
"sending": "Sender",
"friend_request_sent": "Venneforespørsel sendt",
"friends": "Venner",
"friends_list": "Venneliste",
"user_not_found": "Bruker ikke funnet",
"block_user": "Blokkere bruker",
"add_friend": "Legg til venn",
"request_sent": "Forespørsel sendt",
"request_received": "Forespørsel modtatt",
"accept_request": "Akseptere forespørsel",
"ignore_request": "Ignorere forespørsel",
"cancel_request": "Kansellre forespørsel",
"undo_friendship": "Angre venskab",
"request_accepted": "Forespørsel akseptert",
"user_blocked_successfully": "Blokkering av bruker vellykket",
"user_block_modal_text": "Dette blokerer {{displayName}}",
"blocked_users": "Blokerte brukere",
"unblock": "Avblokere",
"no_friends_added": "Du har fortsatt ikke lagt til noen venner",
"pending": "Avventer",
"no_pending_invites": "Du har ingen avventende invitasjoner",
"no_blocked_users": "Du har ingen blokerte brukere",
"friend_code_copied": "Vennekode kopiert",
"undo_friendship_modal_text": "Dette vil angre venskapet ditt med {{displayName}}",
"privacy_hint": "For å justere på hvem som kan se dette, gå til <0>Innstillingene</0>",
"locked_profile": "Denne profilen er privat",
"image_process_failure": "Mislyktes under håndteringen av bildet",
"required_field": "Dette feltet er påkrevet",
"displayname_min_length": "Brukernavnet skal være minst 3 karakterer langt",
"displayname_max_length": "Brukernavnet skal være maksimalt 50 karakterer langt",
"report_profile": "Rapportér denne profilen",
"report_reason": "Hvorfor rapportérer du denne profilen?",
"report_description": "Mer informasjon",
"report_description_placeholder": "Mer informasjon",
"report": "Rapportér",
"report_reason_hate": "Hatytringer",
"report_reason_sexual_content": "Seksuelt innhold",
"report_reason_violence": "Vold",
"report_reason_spam": "Spam",
"report_reason_other": "Annet",
"profile_reported": "Profil rapportert"
}
}

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",
@@ -315,6 +315,7 @@
"report_reason_violence": "Violência",
"report_reason_spam": "Spam",
"report_reason_other": "Outro",
"profile_reported": "Perfil reportado"
"profile_reported": "Perfil reportado",
"your_friend_code": "Seu código de amigo:"
}
}

View File

@@ -275,6 +275,7 @@
"no_pending_invites": "Não tens convites de amizade pendentes",
"no_blocked_users": "Não tens nenhum utilizador bloqueado",
"friend_code_copied": "Código de amigo copiado",
"image_process_failure": "Falha ao processar a imagem"
"image_process_failure": "Falha ao processar a imagem",
"your_friend_code": "Seu código de amigo:"
}
}

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;
@@ -49,7 +51,10 @@ export class HydraApi {
expirationTimestamp: tokenExpirationTimestamp,
};
logger.log("Sign in received", this.userAuth);
logger.log(
"Sign in received. Token expiration timestamp:",
tokenExpirationTimestamp
);
await userAuthRepository.upsert(
{
@@ -79,12 +84,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 +105,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 +178,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

@@ -106,7 +106,7 @@ export class WindowManager {
});
authWindow.loadURL(
`https://auth.hydralauncher.gg/?${searchParams.toString()}`
`${import.meta.env.MAIN_VITE_AUTH_URL}/?${searchParams.toString()}`
);
authWindow.once("ready-to-show", () => {

View File

@@ -3,6 +3,7 @@
interface ImportMetaEnv {
readonly MAIN_VITE_STEAMGRIDDB_API_KEY: string;
readonly MAIN_VITE_API_URL: string;
readonly MAIN_VITE_AUTH_URL: string;
readonly MAIN_VITE_SENTRY_DSN: string;
}

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

@@ -6,7 +6,7 @@
<title>Hydra</title>
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: local: https://*.cloudfront.net https://*.s3.amazonaws.com https://steamcdn-a.akamaihd.net https://shared.akamai.steamstatic.com https://cdn.cloudflare.steamstatic.com https://cdn2.steamgriddb.com https://cdn.akamai.steamstatic.com; media-src 'self' local: data: https://steamcdn-a.akamaihd.net https://cdn.cloudflare.steamstatic.com https://cdn2.steamgriddb.com https://cdn.akamai.steamstatic.com https://shared.akamai.steamstatic.com https://video.akamai.steamstatic.com;"
content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: local: *; media-src 'self' local: data: *;"
/>
</head>
<body style="background-color: #1c1c1c">

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

@@ -19,6 +19,7 @@ export const gallerySliderMedia = style({
flexGrow: "0",
transition: "translate 0.3s ease-in-out",
borderRadius: "4px",
alignSelf: "center",
});
export const gallerySliderAnimationContainer = style({
@@ -60,7 +61,6 @@ export const mediaPreviewButton = recipe({
base: {
cursor: "pointer",
width: "20%",
height: "20%",
display: "block",
flexShrink: "0",
flexGrow: "0",
@@ -84,7 +84,6 @@ export const mediaPreviewButton = recipe({
export const mediaPreview = style({
width: "100%",
height: "100%",
display: "flex",
});

View File

@@ -128,6 +128,7 @@ export function GallerySlider() {
src={image.path_full}
style={{ translate: `${-100 * mediaIndex}%` }}
alt={t("screenshot", { number: i + 1 })}
loading="lazy"
/>
))}

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

@@ -81,7 +81,7 @@ export const UserFriendModal = ({
alignItems: "center",
}}
>
<p>Seu código de amigo: </p>
<p>{t("your_friend_code")}</p>
<button
className={styles.friendCodeButton}
onClick={copyToClipboard}

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;
}