mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-01-23 05:24:55 +03:00
chore: merge with main
This commit is contained in:
commit
498a889f1d
71
.github/workflows/build.yml
vendored
Normal file
71
.github/workflows/build.yml
vendored
Normal file
@ -0,0 +1,71 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: "**"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
os:
|
||||
[
|
||||
{
|
||||
name: windows-latest,
|
||||
build_path: out/Hydra-win32-x64,
|
||||
artifact: Hydra-win32-x64,
|
||||
},
|
||||
{
|
||||
name: ubuntu-latest,
|
||||
build_path: out/Hydra-linux-x64,
|
||||
artifact: Hydra-linux-x64,
|
||||
},
|
||||
]
|
||||
|
||||
runs-on: ${{ matrix.os.name }}
|
||||
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20.11.1
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn
|
||||
|
||||
- name: Install Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.9
|
||||
|
||||
- name: Install dependencies
|
||||
run: pip install -r requirements.txt
|
||||
|
||||
- name: Build with cx_Freeze
|
||||
run: python torrent-client/setup.py build
|
||||
|
||||
- name: VirusTotal Scan
|
||||
uses: crazy-max/ghaction-virustotal@v4
|
||||
with:
|
||||
vt_api_key: ${{ secrets.VT_API_KEY }}
|
||||
files: |
|
||||
.exe$
|
||||
|
||||
# - name: Publish
|
||||
# run: yarn run publish
|
||||
# env:
|
||||
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
# STEAMGRIDDB_API_KEY: ${{ secrets.STEAMGRIDDB_API_KEY }}
|
||||
# SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
||||
# SENTRY_DSN: ${{ vars.SENTRY_DSN }}
|
||||
# ONLINEFIX_USERNAME: ${{ secrets.ONLINEFIX_USERNAME }}
|
||||
# ONLINEFIX_PASSWORD: ${{ secrets.ONLINEFIX_PASSWORD }}
|
||||
|
||||
- name: Create artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.os.artifact }}
|
||||
path: ${{ matrix.os.build_path }}
|
24
.github/workflows/lint.yml
vendored
Normal file
24
.github/workflows/lint.yml
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
name: Lint
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: "**"
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Check out Git repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20.11.1
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn
|
||||
|
||||
- name: Lint
|
||||
run: yarn lint
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -7,3 +7,4 @@ out
|
||||
.DS_Store
|
||||
*.log*
|
||||
.env
|
||||
.vite
|
||||
|
3
.npmrc
3
.npmrc
@ -1,3 +0,0 @@
|
||||
electron_mirror=https://npmmirror.com/mirrors/electron/
|
||||
electron_builder_binaries_mirror=https://npmmirror.com/mirrors/electron-builder-binaries/
|
||||
shamefully-hoist=true
|
6
.prettierrc.cjs
Normal file
6
.prettierrc.cjs
Normal file
@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
semi: true,
|
||||
trailingComma: "es5",
|
||||
singleQuote: false,
|
||||
tabWidth: 2,
|
||||
};
|
@ -1,4 +0,0 @@
|
||||
singleQuote: false
|
||||
semi: true
|
||||
tabWidth: 2
|
||||
trailingComma: es5
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Los Broxas
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
BIN
build/icon.png
BIN
build/icon.png
Binary file not shown.
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 188 KiB |
@ -4,6 +4,7 @@ directories:
|
||||
buildResources: build
|
||||
extraResources:
|
||||
- hydra-download-manager
|
||||
- resources/hydra.db
|
||||
files:
|
||||
- "!**/.vscode/*"
|
||||
- "!src/*"
|
||||
@ -42,7 +43,6 @@ appImage:
|
||||
artifactName: ${name}-${version}.${ext}
|
||||
npmRebuild: false
|
||||
publish:
|
||||
provider: generic
|
||||
url: https://example.com/auto-updates
|
||||
provider: github
|
||||
electronDownload:
|
||||
mirror: https://npmmirror.com/mirrors/electron/
|
||||
|
@ -10,7 +10,7 @@ import react from "@vitejs/plugin-react";
|
||||
import { vanillaExtractPlugin } from "@vanilla-extract/vite-plugin";
|
||||
import svgr from "vite-plugin-svgr";
|
||||
|
||||
export default defineConfig(({ command, mode }) => {
|
||||
export default defineConfig(({ mode }) => {
|
||||
loadEnv(mode);
|
||||
|
||||
return {
|
||||
|
@ -3,8 +3,12 @@
|
||||
"version": "1.0.0",
|
||||
"description": "An Electron application with React and TypeScript",
|
||||
"main": "./out/main/index.js",
|
||||
"author": "Hydra Launcher",
|
||||
"author": "Los Broxas",
|
||||
"homepage": "https://electron-vite.org",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/hydralauncher/hydra.git"
|
||||
},
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"format": "prettier --write .",
|
||||
@ -61,7 +65,7 @@
|
||||
"@electron-toolkit/tsconfig": "^1.0.1",
|
||||
"@swc/core": "^1.4.16",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^18.19.9",
|
||||
"@types/node": "^20.12.7",
|
||||
"@types/react": "^18.2.48",
|
||||
"@types/react-dom": "^18.2.18",
|
||||
"@vanilla-extract/vite-plugin": "^4.0.7",
|
||||
|
BIN
resources/hydra.db
Normal file
BIN
resources/hydra.db
Normal file
Binary file not shown.
BIN
resources/icon.png
Normal file
BIN
resources/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 188 KiB |
@ -77,7 +77,13 @@
|
||||
"play": "Play",
|
||||
"deleting": "Deleting installer…",
|
||||
"close": "Close",
|
||||
"playing_now": "Playing now"
|
||||
"playing_now": "Playing now",
|
||||
"change": "Change",
|
||||
"repacks_modal_description": "Choose the repack you want to download",
|
||||
"downloads_path": "Downloads path",
|
||||
"select_folder_hint": "To change the default folder, access the",
|
||||
"settings": "Hydra settings",
|
||||
"download_now": "Download now"
|
||||
},
|
||||
"activation": {
|
||||
"title": "Activate Hydra",
|
||||
|
147
src/locales/hu/translation.json
Normal file
147
src/locales/hu/translation.json
Normal file
@ -0,0 +1,147 @@
|
||||
{
|
||||
"home": {
|
||||
"featured": "Featured",
|
||||
"recently_added": "Nemrég hozzáadott",
|
||||
"trending": "Népszerű",
|
||||
"surprise_me": "Lepj meg",
|
||||
"no_results": "Nem található"
|
||||
},
|
||||
"sidebar": {
|
||||
"catalogue": "Katalógus",
|
||||
"downloads": "Letöltések",
|
||||
"settings": "Beállítások",
|
||||
"my_library": "Könyvtáram",
|
||||
"downloading_metadata": "{{title}} (Metadata letöltése…)",
|
||||
"checking_files": "{{title}} ({{percentage}} - Fájlok ellenőrzése…)",
|
||||
"paused": "{{title}} (Szünet)",
|
||||
"downloading": "{{title}} ({{percentage}} - Letöltés…)",
|
||||
"filter": "Könyvtár szűrése",
|
||||
"follow_us": "Kövess minket",
|
||||
"home": "Főoldal"
|
||||
},
|
||||
"header": {
|
||||
"search": "Keresés",
|
||||
"home": "Főoldal",
|
||||
"catalogue": "Katalógus",
|
||||
"downloads": "Letöltések",
|
||||
"search_results": "Keresési eredmények",
|
||||
"settings": "Beállítások"
|
||||
},
|
||||
"bottom_panel": {
|
||||
"no_downloads_in_progress": "Nincsenek folyamatban lévő letöltések",
|
||||
"downloading_metadata": "{{title}} metaadatainak letöltése…",
|
||||
"checking_files": "{{title}} fájlok ellenőrzése… ({{percentage}} kész)",
|
||||
"downloading": "{{title}} letöltése… ({{percentage}} kész) - Befejezés {{eta}} - {{speed}}"
|
||||
},
|
||||
"catalogue": {
|
||||
"next_page": "Következő olda",
|
||||
"previous_page": "Előző olda"
|
||||
},
|
||||
"game_details": {
|
||||
"open_download_options": "Letöltési lehetőségek",
|
||||
"download_options_zero": "Nincs letöltési lehetőség",
|
||||
"download_options_one": "{{count}} letöltési lehetőség",
|
||||
"download_options_other": "{{count}} letöltési lehetőség",
|
||||
"updated_at": "Frissítve: {{updated_at}}",
|
||||
"install": "Letöltés",
|
||||
"resume": "Folytatás",
|
||||
"pause": "Szüneteltetés",
|
||||
"cancel": "Mégse",
|
||||
"remove": "Eltávolítás",
|
||||
"remove_from_list": "Eltávolítás",
|
||||
"space_left_on_disk": "{{space}} szabad hely a lemezen",
|
||||
"eta": "Befejezés {{eta}}",
|
||||
"downloading_metadata": "Metaadatok letöltése…",
|
||||
"checking_files": "Fájlok ellenőrzése…",
|
||||
"filter": "Repackek szűrése",
|
||||
"requirements": "Rendszerkövetelmények",
|
||||
"minimum": "Minimális",
|
||||
"recommended": "Ajánlott",
|
||||
"no_minimum_requirements": "{{title}} nem tartalmaz információt a minimális követelményekről",
|
||||
"no_recommended_requirements": "{{title}} nem tartalmaz információt az ajánlott követelményekről",
|
||||
"paused_progress": "{{progress}} (Szünetel)",
|
||||
"release_date": "Megjelenés: {{date}}",
|
||||
"publisher": "Kiadta: {{publisher}}",
|
||||
"copy_link_to_clipboard": "Link másolása",
|
||||
"copied_link_to_clipboard": "Link másolva",
|
||||
"hours": "óra",
|
||||
"minutes": "perc",
|
||||
"accuracy": "{{accuracy}}% pontosság",
|
||||
"add_to_library": "Hozzáadás a könyvtárhoz",
|
||||
"remove_from_library": "Eltávolítás a könyvtárból",
|
||||
"no_downloads": "Nincs elérhető letöltés",
|
||||
"play_time": "Játszva: {{amount}}",
|
||||
"last_time_played": "Utoljára játszva {{period}}",
|
||||
"not_played_yet": "{{title}} még nem játszottál",
|
||||
"next_suggestion": "Következő javaslat",
|
||||
"play": "Játék",
|
||||
"deleting": "Telepítő törlése…",
|
||||
"close": "Bezárás",
|
||||
"playing_now": "Jelenleg játszva",
|
||||
"change": "Változtatás",
|
||||
"repacks_modal_description": "Choose the repack you want to download",
|
||||
"downloads_path": "Letöltések helye",
|
||||
"select_folder_hint": "Ahhoz, hogy megváltoztasd a helyet, hozzákell férned a",
|
||||
"hydra_settings": "Hydra beállítások",
|
||||
"download_now": "Töltsd le most"
|
||||
},
|
||||
"activation": {
|
||||
"title": "Hydra Aktiválása",
|
||||
"installation_id": "Telepítési ID:",
|
||||
"enter_activation_code": "Add meg az aktiválási kódodat",
|
||||
"message": "Ha nem tudod, hol kérdezd meg ezt, akkor nem is kellene, hogy legyen ilyened.",
|
||||
"activate": "Aktiválás",
|
||||
"loading": "Betöltés…"
|
||||
},
|
||||
"downloads": {
|
||||
"resume": "Folytatás",
|
||||
"pause": "Szüneteltetés",
|
||||
"eta": "Befejezés {{eta}}",
|
||||
"paused": "Szüneteltetve",
|
||||
"verifying": "Ellenőrzés…",
|
||||
"completed_at": "Befejezve {{date}}-kor",
|
||||
"completed": "Befejezve",
|
||||
"cancelled": "Megszakítva",
|
||||
"download_again": "Újra letöltés",
|
||||
"cancel": "Mégse",
|
||||
"filter": "Letöltött játékok szűrése",
|
||||
"remove": "Eltávolítás",
|
||||
"downloading_metadata": "Metaadatok letöltése…",
|
||||
"checking_files": "Fájlok ellenőrzése…",
|
||||
"starting_download": "Letöltés indítása…",
|
||||
"deleting": "Telepítő törlése…",
|
||||
"delete": "Telepítő eltávolítása",
|
||||
"remove_from_list": "Eltávolítás",
|
||||
"delete_modal_title": "Biztos vagy benne?",
|
||||
"delete_modal_description": "Ez eltávolít minden telepítési fájlt a számítógépedről",
|
||||
"install": "Telepítés"
|
||||
},
|
||||
"settings": {
|
||||
"downloads_path": "Letöltések helye",
|
||||
"change": "Frissítés",
|
||||
"notifications": "Értesítések",
|
||||
"enable_download_notifications": "Amikor egy letöltés befejeződik",
|
||||
"enable_repack_list_notifications": "Amikor egy új repack hozzáadásra kerül",
|
||||
"telemetry": "Telemetria",
|
||||
"telemetry_description": "Névtelen felhasználási statisztikák engedélyezése"
|
||||
},
|
||||
"notifications": {
|
||||
"download_complete": "Letöltés befejeződött",
|
||||
"game_ready_to_install": "{{title}} telepítésre kész",
|
||||
"repack_list_updated": "Repack lista frissítve",
|
||||
"repack_count_one": "{{count}} repack hozzáadva",
|
||||
"repack_count_other": "{{count}} repack hozzáadva"
|
||||
},
|
||||
"system_tray": {
|
||||
"open": "Hydra megnyitása",
|
||||
"quit": "Kilépés"
|
||||
},
|
||||
"game_card": {
|
||||
"no_downloads": "Nincs elérhető letöltés"
|
||||
},
|
||||
"binary_not_found_modal": {
|
||||
"title": "A programok nincsenek telepítve",
|
||||
"description": "A Wine vagy a Lutris végrehajtható fájljai nem találhatók a rendszereden",
|
||||
"instructions": "Ellenőrizd a megfelelő telepítési módot bármelyiküknek a Linux disztribúciódon, hogy a játék normálisan fusson"
|
||||
}
|
||||
}
|
@ -2,3 +2,5 @@ export { default as en } from "./en/translation.json";
|
||||
export { default as pt } from "./pt/translation.json";
|
||||
export { default as es } from "./es/translation.json";
|
||||
export { default as fr } from "./fr/translation.json";
|
||||
export { default as hu } from "./hu/translation.json";
|
||||
export { default as it } from "./it/translation.json";
|
||||
|
141
src/locales/it/translation.json
Normal file
141
src/locales/it/translation.json
Normal file
@ -0,0 +1,141 @@
|
||||
{
|
||||
"home": {
|
||||
"featured": "In primo piano",
|
||||
"recently_added": "Aggiunti di recente",
|
||||
"trending": "Di tendenza",
|
||||
"surprise_me": "Sorprendimi",
|
||||
"no_results": "Nessun risultato trovato"
|
||||
},
|
||||
"sidebar": {
|
||||
"catalogue": "Catalogo",
|
||||
"downloads": "Download",
|
||||
"settings": "Impostazioni",
|
||||
"my_library": "La mia libreria",
|
||||
"downloading_metadata": "{{title}} (Scaricamento metadati…)",
|
||||
"checking_files": "{{title}} ({{percentage}} - Verifica file…)",
|
||||
"paused": "{{title}} (In pausa)",
|
||||
"downloading": "{{title}} ({{percentage}} - Download…)",
|
||||
"filter": "Filtra libreria",
|
||||
"follow_us": "Seguici",
|
||||
"home": "Home"
|
||||
},
|
||||
"header": {
|
||||
"search": "Cerca",
|
||||
"home": "Home",
|
||||
"catalogue": "Catalogo",
|
||||
"downloads": "Download",
|
||||
"search_results": "Risultati della ricerca",
|
||||
"settings": "Impostazioni"
|
||||
},
|
||||
"bottom_panel": {
|
||||
"no_downloads_in_progress": "Nessun download in corso",
|
||||
"downloading_metadata": "Scaricamento metadati di {{title}}…",
|
||||
"checking_files": "Verifica file di {{title}}… ({{percentage}} completato)",
|
||||
"downloading": "Download di {{title}}… ({{percentage}} completato) - Conclusione {{eta}} - {{speed}}"
|
||||
},
|
||||
"catalogue": {
|
||||
"next_page": "Pagina successiva",
|
||||
"previous_page": "Pagina precedente"
|
||||
},
|
||||
"game_details": {
|
||||
"open_download_options": "Apri opzioni di download",
|
||||
"download_options_zero": "Nessuna opzione di download",
|
||||
"download_options_one": "{{count}} opzione di download",
|
||||
"download_options_other": "{{count}} opzioni di download",
|
||||
"updated_at": "Aggiornato il {{updated_at}}",
|
||||
"install": "Installa",
|
||||
"resume": "Riprendi",
|
||||
"pause": "Metti in pausa",
|
||||
"cancel": "Annulla",
|
||||
"remove": "Rimuovi",
|
||||
"remove_from_list": "Rimuovi",
|
||||
"space_left_on_disk": "{{space}} rimasto sul disco",
|
||||
"eta": "Conclusione {{eta}}",
|
||||
"downloading_metadata": "Scaricamento metadati…",
|
||||
"checking_files": "Verifica file…",
|
||||
"filter": "Filtra repack",
|
||||
"requirements": "Requisiti di sistema",
|
||||
"minimum": "Minimi",
|
||||
"recommended": "Consigliati",
|
||||
"no_minimum_requirements": "{{title}} non fornisce informazioni sui requisiti minimi",
|
||||
"no_recommended_requirements": "{{title}} non fornisce informazioni sui requisiti consigliati",
|
||||
"paused_progress": "{{progress}} (In pausa)",
|
||||
"release_date": "Rilasciato il {{date}}",
|
||||
"publisher": "Pubblicato da {{publisher}}",
|
||||
"copy_link_to_clipboard": "Copia link",
|
||||
"copied_link_to_clipboard": "Link copiato",
|
||||
"hours": "ore",
|
||||
"minutes": "minuti",
|
||||
"accuracy": "{{accuratezza}}% di accuratezza",
|
||||
"add_to_library": "Aggiungi alla libreria",
|
||||
"remove_from_library": "Rimuovi dalla libreria",
|
||||
"no_downloads": "Nessun download disponibile",
|
||||
"play_time": "Giocato per {{amount}}",
|
||||
"last_time_played": "Ultimo gioco giocato {{period}}",
|
||||
"not_played_yet": "Non hai ancora giocato a {{title}}",
|
||||
"next_suggestion": "Prossimo suggerimento",
|
||||
"play": "Gioca",
|
||||
"deleting": "Eliminazione dell'installer…",
|
||||
"close": "Chiudi",
|
||||
"playing_now": "Stai giocando adesso"
|
||||
},
|
||||
"activation": {
|
||||
"title": "Attiva Hydra",
|
||||
"installation_id": "ID installazione:",
|
||||
"enter_activation_code": "Inserisci il tuo codice di attivazione",
|
||||
"message": "Se non sai dove chiederlo, allora non dovresti averlo.",
|
||||
"activate": "Attiva",
|
||||
"loading": "Caricamento…"
|
||||
},
|
||||
"downloads": {
|
||||
"resume": "Riprendi",
|
||||
"pause": "Metti in pausa",
|
||||
"eta": "Conclusione {{eta}}",
|
||||
"paused": "In pausa",
|
||||
"verifying": "Verifica…",
|
||||
"completed_at": "Completato in {{date}}",
|
||||
"completed": "Completato",
|
||||
"cancelled": "Annullato",
|
||||
"download_again": "Scarica di nuovo",
|
||||
"cancel": "Annulla",
|
||||
"filter": "Filtra giochi scaricati",
|
||||
"remove": "Rimuovi",
|
||||
"downloading_metadata": "Scaricamento metadati…",
|
||||
"checking_files": "Verifica file…",
|
||||
"starting_download": "Avvio download…",
|
||||
"deleting": "Eliminazione dell'installer…",
|
||||
"delete": "Rimuovi installer",
|
||||
"remove_from_list": "Rimuovi",
|
||||
"delete_modal_title": "Sei sicuro?",
|
||||
"delete_modal_description": "Questo rimuoverà tutti i file di installazione dal tuo computer",
|
||||
"install": "Installa"
|
||||
},
|
||||
"settings": {
|
||||
"downloads_path": "Percorso dei download",
|
||||
"change": "Aggiorna",
|
||||
"notifications": "Notifiche",
|
||||
"enable_download_notifications": "Quando un download è completo",
|
||||
"enable_repack_list_notifications": "Quando viene aggiunto un nuovo repack",
|
||||
"telemetry": "Telemetria",
|
||||
"telemetry_description": "Abilita statistiche di utilizzo anonime"
|
||||
},
|
||||
"notifications": {
|
||||
"download_complete": "Download completato",
|
||||
"game_ready_to_install": "{{title}} è pronto per l'installazione",
|
||||
"repack_list_updated": "Elenco repack aggiornato",
|
||||
"repack_count_one": "{{count}} repack aggiunto",
|
||||
"repack_count_other": "{{count}} repack aggiunti"
|
||||
},
|
||||
"system_tray": {
|
||||
"open": "Apri Hydra",
|
||||
"quit": "Esci"
|
||||
},
|
||||
"game_card": {
|
||||
"no_downloads": "Nessun download disponibile"
|
||||
},
|
||||
"binary_not_found_modal": {
|
||||
"title": "Programmi non installati",
|
||||
"description": "Gli eseguibili di Wine o Lutris non sono stati trovati sul tuo sistema",
|
||||
"instructions": "Verifica il modo corretto di installare uno di essi sulla tua distribuzione Linux in modo che il gioco possa funzionare normalmente"
|
||||
}
|
||||
}
|
@ -73,7 +73,13 @@
|
||||
"not_played_yet": "Você ainda não jogou {{title}}",
|
||||
"close": "Fechar",
|
||||
"deleting": "Excluindo instalador…",
|
||||
"playing_now": "Jogando agora"
|
||||
"playing_now": "Jogando agora",
|
||||
"change": "Mudar",
|
||||
"repacks_modal_description": "Escolha o repack do jogo que deseja baixar",
|
||||
"downloads_path": "Diretório do download",
|
||||
"select_folder_hint": "Para trocar a pasta padrão, acesse as ",
|
||||
"settings": "Configurações do Hydra",
|
||||
"download_now": "Baixe agora"
|
||||
},
|
||||
"activation": {
|
||||
"title": "Ativação",
|
||||
|
@ -40,7 +40,7 @@ export class Game {
|
||||
shop: GameShop;
|
||||
|
||||
@Column("text", { nullable: true })
|
||||
status: string;
|
||||
status: string | null;
|
||||
|
||||
@Column("float", { default: 0 })
|
||||
progress: number;
|
||||
@ -61,6 +61,9 @@ export class Game {
|
||||
@JoinColumn()
|
||||
repack: Repack;
|
||||
|
||||
@Column("boolean", { default: false })
|
||||
isDeleted: boolean;
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt: Date;
|
||||
|
||||
|
@ -16,8 +16,6 @@ const getGames = async (
|
||||
|
||||
let i = 0 + cursor;
|
||||
|
||||
if (!steamGames.length) return [];
|
||||
|
||||
while (results.length < take) {
|
||||
const game = steamGames[i];
|
||||
const repacks = searchRepacks(game.name);
|
||||
|
@ -1,10 +1,11 @@
|
||||
import checkDiskSpace from "check-disk-space";
|
||||
|
||||
import { registerEvent } from "../register-event";
|
||||
import { getDownloadsPath } from "../helpers/get-downloads-path";
|
||||
|
||||
const getDiskFreeSpace = async (_event: Electron.IpcMainInvokeEvent) =>
|
||||
checkDiskSpace(await getDownloadsPath());
|
||||
const getDiskFreeSpace = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
path: string
|
||||
) => checkDiskSpace(path);
|
||||
|
||||
registerEvent(getDiskFreeSpace, {
|
||||
name: "getDiskFreeSpace",
|
||||
|
@ -7,8 +7,8 @@ import { formatName, getSteamAppAsset, repackerFormatter } from "@main/helpers";
|
||||
import { stateManager } from "@main/state-manager";
|
||||
|
||||
const { Index } = flexSearch;
|
||||
const repacksIndex = new Index({ tokenize: "strict" });
|
||||
const steamGamesIndex = new Index({ tokenize: "forward" });
|
||||
const repacksIndex = new Index();
|
||||
const steamGamesIndex = new Index({ tokenize: "reverse" });
|
||||
|
||||
const repacks = stateManager.getValue("repacks");
|
||||
const steamGames = stateManager.getValue("steamGames");
|
||||
@ -21,8 +21,6 @@ for (let i = 0; i < repacks.length; i++) {
|
||||
repacksIndex.add(i, formatName(formatter(repack.title)));
|
||||
}
|
||||
|
||||
console.log(true);
|
||||
|
||||
for (let i = 0; i < steamGames.length; i++) {
|
||||
const steamGame = steamGames[i];
|
||||
steamGamesIndex.add(i, formatName(steamGame.name));
|
||||
|
@ -17,6 +17,7 @@ import "./library/get-repackers-friendly-names";
|
||||
import "./library/open-game";
|
||||
import "./library/open-game-installer";
|
||||
import "./library/remove-game";
|
||||
import "./library/remove-game-from-library";
|
||||
import "./misc/get-or-cache-image";
|
||||
import "./misc/open-external";
|
||||
import "./misc/show-open-dialog";
|
||||
@ -24,6 +25,7 @@ import "./torrenting/cancel-game-download";
|
||||
import "./torrenting/pause-game-download";
|
||||
import "./torrenting/resume-game-download";
|
||||
import "./torrenting/start-game-download";
|
||||
import "./torrenting/remove-game-from-download";
|
||||
import "./user-preferences/get-user-preferences";
|
||||
import "./user-preferences/update-user-preferences";
|
||||
|
||||
|
@ -13,6 +13,24 @@ const addGameToLibrary = async (
|
||||
gameShop: GameShop,
|
||||
executablePath: string
|
||||
) => {
|
||||
const game = await gameRepository.findOne({
|
||||
where: {
|
||||
objectID,
|
||||
},
|
||||
});
|
||||
|
||||
if (game) {
|
||||
return gameRepository.update(
|
||||
{
|
||||
id: game.id,
|
||||
},
|
||||
{
|
||||
shop: gameShop,
|
||||
executablePath,
|
||||
isDeleted: false,
|
||||
}
|
||||
);
|
||||
} else {
|
||||
const iconUrl = await getImageBase64(await getSteamGameIconUrl(objectID));
|
||||
|
||||
return gameRepository.insert({
|
||||
@ -22,6 +40,7 @@ const addGameToLibrary = async (
|
||||
shop: gameShop,
|
||||
executablePath,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
registerEvent(addGameToLibrary, {
|
||||
|
@ -22,7 +22,10 @@ const deleteGameFolder = async (
|
||||
if (!game) return;
|
||||
|
||||
if (game.folderName) {
|
||||
const folderPath = path.join(await getDownloadsPath(), game.folderName);
|
||||
const folderPath = path.join(
|
||||
game.downloadPath ?? (await getDownloadsPath()),
|
||||
game.folderName
|
||||
);
|
||||
|
||||
if (fs.existsSync(folderPath)) {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
@ -9,6 +9,7 @@ const getGameByObjectID = async (
|
||||
gameRepository.findOne({
|
||||
where: {
|
||||
objectID,
|
||||
isDeleted: false,
|
||||
},
|
||||
relations: {
|
||||
repack: true,
|
||||
|
@ -5,9 +5,12 @@ import { searchRepacks } from "../helpers/search-games";
|
||||
import { registerEvent } from "../register-event";
|
||||
import { sortBy } from "lodash-es";
|
||||
|
||||
const getLibrary = async (_event: Electron.IpcMainInvokeEvent) =>
|
||||
const getLibrary = async () =>
|
||||
gameRepository
|
||||
.find({
|
||||
where: {
|
||||
isDeleted: false,
|
||||
},
|
||||
order: {
|
||||
createdAt: "desc",
|
||||
},
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { registerEvent } from "../register-event";
|
||||
import { stateManager } from "@main/state-manager";
|
||||
|
||||
const getRepackersFriendlyNames = async (_event: Electron.IpcMainInvokeEvent) =>
|
||||
const getRepackersFriendlyNames = async () =>
|
||||
stateManager.getValue("repackersFriendlyNames").reduce((prev, next) => {
|
||||
return { ...prev, [next.name]: next.friendlyName };
|
||||
}, {});
|
||||
|
@ -23,7 +23,7 @@ const openGameInstaller = async (
|
||||
);
|
||||
|
||||
if (!fs.existsSync(gamePath)) {
|
||||
await gameRepository.delete({ id: gameId });
|
||||
await gameRepository.update({ id: gameId }, { status: null });
|
||||
return true;
|
||||
}
|
||||
|
||||
|
13
src/main/events/library/remove-game-from-library.ts
Normal file
13
src/main/events/library/remove-game-from-library.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { registerEvent } from "../register-event";
|
||||
import { gameRepository } from "../../repository";
|
||||
|
||||
const removeGameFromLibrary = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
gameId: number
|
||||
) => {
|
||||
gameRepository.update({ id: gameId }, { isDeleted: true });
|
||||
};
|
||||
|
||||
registerEvent(removeGameFromLibrary, {
|
||||
name: "removeGameFromLibrary",
|
||||
});
|
@ -25,14 +25,13 @@ const cancelGameDownload = async (
|
||||
|
||||
if (!game) return;
|
||||
|
||||
gameRepository
|
||||
await gameRepository
|
||||
.update(
|
||||
{
|
||||
id: game.id,
|
||||
},
|
||||
{
|
||||
status: GameStatus.Cancelled,
|
||||
downloadPath: null,
|
||||
bytesDownloaded: 0,
|
||||
progress: 0,
|
||||
}
|
||||
|
34
src/main/events/torrenting/remove-game-from-download.ts
Normal file
34
src/main/events/torrenting/remove-game-from-download.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { GameStatus } from "@main/constants";
|
||||
import { gameRepository } from "@main/repository";
|
||||
|
||||
import { registerEvent } from "../register-event";
|
||||
|
||||
const removeGameFromDownload = async (
|
||||
_event: Electron.IpcMainInvokeEvent,
|
||||
gameId: number
|
||||
) => {
|
||||
const game = await gameRepository.findOne({
|
||||
where: {
|
||||
id: gameId,
|
||||
status: GameStatus.Cancelled,
|
||||
},
|
||||
});
|
||||
|
||||
if (!game) return;
|
||||
|
||||
gameRepository.update(
|
||||
{
|
||||
id: game.id,
|
||||
},
|
||||
{
|
||||
status: null,
|
||||
downloadPath: null,
|
||||
bytesDownloaded: 0,
|
||||
progress: 0,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
registerEvent(removeGameFromDownload, {
|
||||
name: "removeGameFromDownload",
|
||||
});
|
@ -5,7 +5,6 @@ import { GameStatus } from "@main/constants";
|
||||
import { registerEvent } from "../register-event";
|
||||
|
||||
import type { GameShop } from "@types";
|
||||
import { getDownloadsPath } from "../helpers/get-downloads-path";
|
||||
import { getImageBase64 } from "@main/helpers";
|
||||
import { In } from "typeorm";
|
||||
|
||||
@ -14,7 +13,8 @@ const startGameDownload = async (
|
||||
repackId: number,
|
||||
objectID: string,
|
||||
title: string,
|
||||
gameShop: GameShop
|
||||
gameShop: GameShop,
|
||||
downloadPath: string
|
||||
) => {
|
||||
const [game, repack] = await Promise.all([
|
||||
gameRepository.findOne({
|
||||
@ -37,8 +37,6 @@ const startGameDownload = async (
|
||||
|
||||
writePipe.write({ action: "pause" });
|
||||
|
||||
const downloadsPath = game?.downloadPath ?? (await getDownloadsPath());
|
||||
|
||||
await gameRepository.update(
|
||||
{
|
||||
status: In([
|
||||
@ -57,8 +55,9 @@ const startGameDownload = async (
|
||||
},
|
||||
{
|
||||
status: GameStatus.DownloadingMetadata,
|
||||
downloadPath: downloadsPath,
|
||||
downloadPath: downloadPath,
|
||||
repack: { id: repackId },
|
||||
isDeleted: false,
|
||||
}
|
||||
);
|
||||
|
||||
@ -66,18 +65,11 @@ const startGameDownload = async (
|
||||
action: "start",
|
||||
game_id: game.id,
|
||||
magnet: repack.magnet,
|
||||
save_path: downloadsPath,
|
||||
save_path: downloadPath,
|
||||
});
|
||||
|
||||
game.status = GameStatus.DownloadingMetadata;
|
||||
|
||||
writePipe.write({
|
||||
action: "start",
|
||||
game_id: game.id,
|
||||
magnet: repack.magnet,
|
||||
save_path: downloadsPath,
|
||||
});
|
||||
|
||||
return game;
|
||||
} else {
|
||||
const iconUrl = await getImageBase64(await getSteamGameIconUrl(objectID));
|
||||
@ -88,7 +80,7 @@ const startGameDownload = async (
|
||||
objectID,
|
||||
shop: gameShop,
|
||||
status: GameStatus.DownloadingMetadata,
|
||||
downloadPath: downloadsPath,
|
||||
downloadPath: downloadPath,
|
||||
repack: { id: repackId },
|
||||
});
|
||||
|
||||
@ -96,7 +88,7 @@ const startGameDownload = async (
|
||||
action: "start",
|
||||
game_id: createdGame.id,
|
||||
magnet: repack.magnet,
|
||||
save_path: downloadsPath,
|
||||
save_path: downloadPath,
|
||||
});
|
||||
|
||||
const { repack: _, ...rest } = createdGame;
|
||||
|
@ -2,6 +2,7 @@ import { app, BrowserWindow } from "electron";
|
||||
import { init } from "@sentry/electron/main";
|
||||
import i18n from "i18next";
|
||||
import path from "node:path";
|
||||
import { electronApp, optimizer } from "@electron-toolkit/utils";
|
||||
import { resolveDatabaseUpdates, WindowManager } from "@main/services";
|
||||
import { dataSource } from "@main/data-source";
|
||||
import * as resources from "@locales";
|
||||
@ -49,8 +50,10 @@ if (process.defaultApp) {
|
||||
// initialization and is ready to create browser windows.
|
||||
// Some APIs can only be used after this event occurs.
|
||||
app.whenReady().then(() => {
|
||||
electronApp.setAppUserModelId("site.hydralauncher.hydra");
|
||||
|
||||
dataSource.initialize().then(async () => {
|
||||
// await resolveDatabaseUpdates();
|
||||
await resolveDatabaseUpdates();
|
||||
|
||||
await import("./main");
|
||||
|
||||
@ -59,10 +62,14 @@ app.whenReady().then(() => {
|
||||
});
|
||||
|
||||
WindowManager.createMainWindow();
|
||||
// WindowManager.createSystemTray(userPreferences?.language || "en");
|
||||
WindowManager.createSystemTray(userPreferences?.language || "en");
|
||||
});
|
||||
});
|
||||
|
||||
app.on("browser-window-created", (_, window) => {
|
||||
optimizer.watchWindowShortcuts(window);
|
||||
});
|
||||
|
||||
app.on("second-instance", (_event, commandLine) => {
|
||||
// Someone tried to run a second instance, we should focus our window.
|
||||
if (WindowManager.mainWindow) {
|
||||
|
@ -4,7 +4,7 @@ import {
|
||||
getNewGOGGames,
|
||||
getNewRepacksFromCPG,
|
||||
getNewRepacksFromUser,
|
||||
getNewRepacksFromXatab,
|
||||
// getNewRepacksFromXatab,
|
||||
// getNewRepacksFromOnlineFix,
|
||||
readPipe,
|
||||
startProcessWatcher,
|
||||
@ -73,9 +73,9 @@ const checkForNewRepacks = async () => {
|
||||
getNewGOGGames(
|
||||
existingRepacks.filter((repack) => repack.repacker === "GOG")
|
||||
),
|
||||
getNewRepacksFromXatab(
|
||||
existingRepacks.filter((repack) => repack.repacker === "Xatab")
|
||||
),
|
||||
// getNewRepacksFromXatab(
|
||||
// existingRepacks.filter((repack) => repack.repacker === "Xatab")
|
||||
// ),
|
||||
getNewRepacksFromCPG(
|
||||
existingRepacks.filter((repack) => repack.repacker === "CPG")
|
||||
),
|
||||
|
@ -1,11 +1,27 @@
|
||||
import { BrowserWindow, Menu, Tray, app } from "electron";
|
||||
import { electronApp, optimizer, is } from "@electron-toolkit/utils";
|
||||
import { is } from "@electron-toolkit/utils";
|
||||
import { t } from "i18next";
|
||||
import path from "node:path";
|
||||
import icon from "../../../resources/icon.png?asset";
|
||||
import trayIcon from "../../../resources/icon.png?asset";
|
||||
|
||||
export class WindowManager {
|
||||
public static mainWindow: Electron.BrowserWindow | null = null;
|
||||
|
||||
private static loadURL(hash = "") {
|
||||
// HMR for renderer base on electron-vite cli.
|
||||
// Load the remote URL for development or the local html file for production.
|
||||
if (is.dev && process.env["ELECTRON_RENDERER_URL"]) {
|
||||
this.mainWindow.loadURL(
|
||||
`${process.env["ELECTRON_RENDERER_URL"]}#/${hash}`
|
||||
);
|
||||
} else {
|
||||
this.mainWindow.loadFile(path.join(__dirname, "../renderer/index.html"), {
|
||||
hash,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static createMainWindow() {
|
||||
// Create the browser window.
|
||||
this.mainWindow = new BrowserWindow({
|
||||
@ -14,7 +30,7 @@ export class WindowManager {
|
||||
minWidth: 1024,
|
||||
minHeight: 540,
|
||||
titleBarStyle: "hidden",
|
||||
// icon: path.join(__dirname, "..", "..", "images", "icon.png"),
|
||||
...(process.platform === "linux" ? { icon } : {}),
|
||||
trafficLightPosition: { x: 16, y: 16 },
|
||||
titleBarOverlay: {
|
||||
symbolColor: "#DADBE1",
|
||||
@ -27,42 +43,24 @@ export class WindowManager {
|
||||
},
|
||||
});
|
||||
|
||||
this.loadURL();
|
||||
this.mainWindow.removeMenu();
|
||||
|
||||
// HMR for renderer base on electron-vite cli.
|
||||
// Load the remote URL for development or the local html file for production.
|
||||
if (is.dev && process.env["ELECTRON_RENDERER_URL"]) {
|
||||
this.mainWindow.loadURL(process.env["ELECTRON_RENDERER_URL"]);
|
||||
} else {
|
||||
this.mainWindow.loadFile(path.join(__dirname, "../renderer/index.html"));
|
||||
}
|
||||
|
||||
this.mainWindow.webContents.on("did-finish-load", () => {
|
||||
if (!app.isPackaged) {
|
||||
// Open the DevTools.
|
||||
this.mainWindow.webContents.openDevTools();
|
||||
}
|
||||
});
|
||||
|
||||
this.mainWindow.on("close", () => {
|
||||
WindowManager.mainWindow.setProgressBar(-1);
|
||||
});
|
||||
}
|
||||
|
||||
public static redirect(path: string) {
|
||||
public static redirect(hash: string) {
|
||||
if (!this.mainWindow) this.createMainWindow();
|
||||
this.mainWindow.loadURL(`${MAIN_WINDOW_WEBPACK_ENTRY}#${path}`);
|
||||
this.loadURL(hash);
|
||||
|
||||
if (this.mainWindow.isMinimized()) this.mainWindow.restore();
|
||||
this.mainWindow.focus();
|
||||
}
|
||||
|
||||
public static createSystemTray(language: string) {
|
||||
const tray = new Tray(
|
||||
app.isPackaged
|
||||
? path.join(process.resourcesPath, "icon_tray.png")
|
||||
: path.join(__dirname, "..", "..", "resources", "icon_tray.png")
|
||||
);
|
||||
const tray = new Tray(trayIcon);
|
||||
|
||||
const contextMenu = Menu.buildFromTemplate([
|
||||
{
|
||||
|
@ -15,8 +15,17 @@ contextBridge.exposeInMainWorld("electron", {
|
||||
repackId: number,
|
||||
objectID: string,
|
||||
title: string,
|
||||
shop: GameShop
|
||||
) => ipcRenderer.invoke("startGameDownload", repackId, objectID, title, shop),
|
||||
shop: GameShop,
|
||||
downloadPath: string
|
||||
) =>
|
||||
ipcRenderer.invoke(
|
||||
"startGameDownload",
|
||||
repackId,
|
||||
objectID,
|
||||
title,
|
||||
shop,
|
||||
downloadPath
|
||||
),
|
||||
cancelGameDownload: (gameId: number) =>
|
||||
ipcRenderer.invoke("cancelGameDownload", gameId),
|
||||
pauseGameDownload: (gameId: number) =>
|
||||
@ -90,7 +99,8 @@ contextBridge.exposeInMainWorld("electron", {
|
||||
},
|
||||
|
||||
/* Hardware */
|
||||
getDiskFreeSpace: () => ipcRenderer.invoke("getDiskFreeSpace"),
|
||||
getDiskFreeSpace: (path: string) =>
|
||||
ipcRenderer.invoke("getDiskFreeSpace", path),
|
||||
|
||||
/* Misc */
|
||||
getOrCacheImage: (url: string) => ipcRenderer.invoke("getOrCacheImage", url),
|
||||
|
@ -4,9 +4,14 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<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: https://steamcdn-a.akamaihd.net https://cdn.cloudflare.steamstatic.com https://cdn2.steamgriddb.com https://cdn.akamai.steamstatic.com;"
|
||||
/>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<h1>hello</h1>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { XIcon } from "@primer/octicons-react";
|
||||
|
||||
@ -23,8 +23,9 @@ export function Modal({
|
||||
}: ModalProps) {
|
||||
const [isClosing, setIsClosing] = useState(false);
|
||||
const dispatch = useAppDispatch();
|
||||
const modalContentRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const handleCloseClick = () => {
|
||||
const handleCloseClick = useCallback(() => {
|
||||
setIsClosing(true);
|
||||
const zero = performance.now();
|
||||
|
||||
@ -36,8 +37,44 @@ export function Modal({
|
||||
setIsClosing(false);
|
||||
}
|
||||
});
|
||||
}, [onClose]);
|
||||
|
||||
const isTopMostModal = () => {
|
||||
const openModals = document.querySelectorAll("[role=modal]");
|
||||
return (
|
||||
openModals.length &&
|
||||
openModals[openModals.length - 1] === modalContentRef.current
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const onKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === "Escape" && isTopMostModal()) {
|
||||
handleCloseClick();
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("keydown", onKeyDown);
|
||||
return () => window.removeEventListener("keydown", onKeyDown);
|
||||
}, [handleCloseClick]);
|
||||
|
||||
useEffect(() => {
|
||||
const onMouseDown = (e: MouseEvent) => {
|
||||
if (!isTopMostModal()) return;
|
||||
|
||||
const clickedOutsideContent = !modalContentRef.current.contains(
|
||||
e.target as Node
|
||||
);
|
||||
|
||||
if (clickedOutsideContent) {
|
||||
handleCloseClick();
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("mousedown", onMouseDown);
|
||||
return () => window.removeEventListener("mousedown", onMouseDown);
|
||||
}, [handleCloseClick]);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(toggleDragging(visible));
|
||||
}, [dispatch, visible]);
|
||||
@ -46,7 +83,11 @@ export function Modal({
|
||||
|
||||
return createPortal(
|
||||
<div className={styles.backdrop({ closing: isClosing })}>
|
||||
<div className={styles.modal({ closing: isClosing })}>
|
||||
<div
|
||||
className={styles.modal({ closing: isClosing })}
|
||||
role="modal"
|
||||
ref={modalContentRef}
|
||||
>
|
||||
<div className={styles.modalHeader}>
|
||||
<div style={{ display: "flex", gap: 4, flexDirection: "column" }}>
|
||||
<h3>{title}</h3>
|
||||
|
6
src/renderer/src/declaration.d.ts
vendored
6
src/renderer/src/declaration.d.ts
vendored
@ -21,8 +21,8 @@ declare global {
|
||||
startGameDownload: (
|
||||
repackId: number,
|
||||
objectID: string,
|
||||
title: string,
|
||||
shop: GameShop
|
||||
shop: GameShop,
|
||||
downloadPath: string
|
||||
) => Promise<Game>;
|
||||
cancelGameDownload: (gameId: number) => Promise<void>;
|
||||
pauseGameDownload: (gameId: number) => Promise<void>;
|
||||
@ -75,7 +75,7 @@ declare global {
|
||||
) => Promise<void>;
|
||||
|
||||
/* Hardware */
|
||||
getDiskFreeSpace: () => Promise<DiskSpace>;
|
||||
getDiskFreeSpace: (path: string) => Promise<DiskSpace>;
|
||||
|
||||
/* Misc */
|
||||
getOrCacheImage: (url: string) => Promise<string>;
|
||||
|
@ -28,10 +28,11 @@ export function useDownload() {
|
||||
repackId: number,
|
||||
objectID: string,
|
||||
title: string,
|
||||
shop: GameShop
|
||||
shop: GameShop,
|
||||
downloadPath: string
|
||||
) =>
|
||||
window.electron
|
||||
.startGameDownload(repackId, objectID, title, shop)
|
||||
.startGameDownload(repackId, objectID, title, shop, downloadPath)
|
||||
.then((game) => {
|
||||
dispatch(clearDownload());
|
||||
updateLibrary();
|
||||
|
@ -19,15 +19,15 @@ import { useAppDispatch, useDownload } from "@renderer/hooks";
|
||||
import starsAnimation from "@renderer/assets/lottie/stars.json";
|
||||
|
||||
import { vars } from "../../theme.css";
|
||||
import Lottie from "lottie-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { SkeletonTheme } from "react-loading-skeleton";
|
||||
import { DescriptionHeader } from "./description-header";
|
||||
import { GameDetailsSkeleton } from "./game-details-skeleton";
|
||||
import * as styles from "./game-details.css";
|
||||
import { HeroPanel } from "./hero-panel";
|
||||
import { HowLongToBeatSection } from "./how-long-to-beat-section";
|
||||
import { RepacksModal } from "./repacks-modal";
|
||||
import Lottie from "lottie-react";
|
||||
import { DescriptionHeader } from "./description-header";
|
||||
|
||||
export function GameDetails() {
|
||||
const { objectID, shop } = useParams();
|
||||
@ -51,6 +51,7 @@ export function GameDetails() {
|
||||
const { t, i18n } = useTranslation("game_details");
|
||||
|
||||
const [showRepacksModal, setShowRepacksModal] = useState(false);
|
||||
const [showSelectFolderModal, setShowSelectFolderModal] = useState(false);
|
||||
|
||||
const randomGameObjectID = useRef<string | null>(null);
|
||||
|
||||
@ -140,15 +141,20 @@ export function GameDetails() {
|
||||
};
|
||||
}, [game?.id, isGamePlaying, getGame]);
|
||||
|
||||
const handleStartDownload = async (repackId: number) => {
|
||||
const handleStartDownload = async (
|
||||
repackId: number,
|
||||
downloadPath: string
|
||||
) => {
|
||||
return startDownload(
|
||||
repackId,
|
||||
gameDetails.objectID,
|
||||
gameDetails.name,
|
||||
shop as GameShop
|
||||
shop as GameShop,
|
||||
downloadPath
|
||||
).then(() => {
|
||||
getGame();
|
||||
setShowRepacksModal(false);
|
||||
setShowSelectFolderModal(false);
|
||||
});
|
||||
};
|
||||
|
||||
@ -173,6 +179,8 @@ export function GameDetails() {
|
||||
visible={showRepacksModal}
|
||||
gameDetails={gameDetails}
|
||||
startDownload={handleStartDownload}
|
||||
showSelectFolderModal={showSelectFolderModal}
|
||||
setShowSelectFolderModal={setShowSelectFolderModal}
|
||||
onClose={() => setShowRepacksModal(false)}
|
||||
/>
|
||||
)}
|
||||
|
@ -6,28 +6,30 @@ import type { GameRepack, ShopDetails } from "@types";
|
||||
|
||||
import * as styles from "./repacks-modal.css";
|
||||
|
||||
import type { DiskSpace } from "check-disk-space";
|
||||
import { format } from "date-fns";
|
||||
import { SPACING_UNIT } from "../../theme.css";
|
||||
import { formatBytes } from "@renderer/utils";
|
||||
import { useAppSelector } from "@renderer/hooks";
|
||||
import { SPACING_UNIT } from "../../theme.css";
|
||||
import { format } from "date-fns";
|
||||
import { SelectFolderModal } from "./select-folder-modal";
|
||||
|
||||
export interface RepacksModalProps {
|
||||
visible: boolean;
|
||||
gameDetails: ShopDetails;
|
||||
startDownload: (repackId: number) => Promise<void>;
|
||||
showSelectFolderModal: boolean;
|
||||
setShowSelectFolderModal: (value: boolean) => void;
|
||||
startDownload: (repackId: number, downloadPath: string) => Promise<void>;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export function RepacksModal({
|
||||
visible,
|
||||
gameDetails,
|
||||
showSelectFolderModal,
|
||||
setShowSelectFolderModal,
|
||||
startDownload,
|
||||
onClose,
|
||||
}: RepacksModalProps) {
|
||||
const [downloadStarting, setDownloadStarting] = useState(false);
|
||||
const [diskFreeSpace, setDiskFreeSpace] = useState<DiskSpace>(null);
|
||||
const [filteredRepacks, setFilteredRepacks] = useState<GameRepack[]>([]);
|
||||
const [repack, setRepack] = useState<GameRepack>(null);
|
||||
|
||||
const repackersFriendlyNames = useAppSelector(
|
||||
(state) => state.repackersFriendlyNames.value
|
||||
@ -39,21 +41,9 @@ export function RepacksModal({
|
||||
setFilteredRepacks(gameDetails.repacks);
|
||||
}, [gameDetails.repacks]);
|
||||
|
||||
const getDiskFreeSpace = () => {
|
||||
window.electron.getDiskFreeSpace().then((result) => {
|
||||
setDiskFreeSpace(result);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getDiskFreeSpace();
|
||||
}, [visible]);
|
||||
|
||||
const handleRepackClick = (repack: GameRepack) => {
|
||||
setDownloadStarting(true);
|
||||
startDownload(repack.id).finally(() => {
|
||||
setDownloadStarting(false);
|
||||
});
|
||||
setRepack(repack);
|
||||
setShowSelectFolderModal(true);
|
||||
};
|
||||
|
||||
const handleFilter: React.ChangeEventHandler<HTMLInputElement> = (event) => {
|
||||
@ -70,11 +60,16 @@ export function RepacksModal({
|
||||
<Modal
|
||||
visible={visible}
|
||||
title={`${gameDetails.name} Repacks`}
|
||||
description={t("space_left_on_disk", {
|
||||
space: formatBytes(diskFreeSpace?.free ?? 0),
|
||||
})}
|
||||
description={t("repacks_modal_description")}
|
||||
onClose={onClose}
|
||||
>
|
||||
<SelectFolderModal
|
||||
visible={showSelectFolderModal}
|
||||
onClose={() => setShowSelectFolderModal(false)}
|
||||
gameDetails={gameDetails}
|
||||
startDownload={startDownload}
|
||||
repack={repack}
|
||||
/>
|
||||
<div style={{ marginBottom: `${SPACING_UNIT * 2}px` }}>
|
||||
<TextField placeholder={t("filter")} onChange={handleFilter} />
|
||||
</div>
|
||||
@ -85,7 +80,6 @@ export function RepacksModal({
|
||||
key={repack.id}
|
||||
theme="dark"
|
||||
onClick={() => handleRepackClick(repack)}
|
||||
disabled={downloadStarting}
|
||||
className={styles.repackButton}
|
||||
>
|
||||
<p style={{ color: "#DADBE1" }}>{repack.title}</p>
|
||||
|
@ -0,0 +1,19 @@
|
||||
import { style } from "@vanilla-extract/css";
|
||||
import { SPACING_UNIT, vars } from "../../theme.css";
|
||||
|
||||
export const container = style({
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: `${SPACING_UNIT * 2}px`,
|
||||
width: "100%",
|
||||
});
|
||||
|
||||
export const downloadsPathField = style({
|
||||
display: "flex",
|
||||
gap: `${SPACING_UNIT * 2}px`,
|
||||
});
|
||||
|
||||
export const hintText = style({
|
||||
fontSize: "12px",
|
||||
color: vars.color.bodyText,
|
||||
});
|
115
src/renderer/src/pages/game-details/select-folder-modal.tsx
Normal file
115
src/renderer/src/pages/game-details/select-folder-modal.tsx
Normal file
@ -0,0 +1,115 @@
|
||||
import { Button, Modal, TextField } from "@renderer/components";
|
||||
import { GameRepack, ShopDetails } from "@types";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { formatBytes } from "@renderer/utils";
|
||||
import { DiskSpace } from "check-disk-space";
|
||||
import { Link } from "react-router-dom";
|
||||
import * as styles from "./select-folder-modal.css";
|
||||
|
||||
export interface SelectFolderModalProps {
|
||||
visible: boolean;
|
||||
gameDetails: ShopDetails;
|
||||
onClose: () => void;
|
||||
startDownload: (repackId: number, downloadPath: string) => Promise<void>;
|
||||
repack: GameRepack;
|
||||
}
|
||||
|
||||
export function SelectFolderModal({
|
||||
visible,
|
||||
gameDetails,
|
||||
onClose,
|
||||
startDownload,
|
||||
repack,
|
||||
}: SelectFolderModalProps) {
|
||||
const { t } = useTranslation("game_details");
|
||||
|
||||
const [diskFreeSpace, setDiskFreeSpace] = useState<DiskSpace>(null);
|
||||
const [selectedPath, setSelectedPath] = useState("");
|
||||
const [downloadStarting, setDownloadStarting] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
visible && getDiskFreeSpace(selectedPath);
|
||||
}, [visible, selectedPath]);
|
||||
|
||||
useEffect(() => {
|
||||
Promise.all([
|
||||
window.electron.getDefaultDownloadsPath(),
|
||||
window.electron.getUserPreferences(),
|
||||
]).then(([path, userPreferences]) => {
|
||||
setSelectedPath(userPreferences?.downloadsPath || path);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const getDiskFreeSpace = (path: string) => {
|
||||
window.electron.getDiskFreeSpace(path).then((result) => {
|
||||
setDiskFreeSpace(result);
|
||||
});
|
||||
};
|
||||
|
||||
const handleChooseDownloadsPath = async () => {
|
||||
const { filePaths } = await window.electron.showOpenDialog({
|
||||
defaultPath: selectedPath,
|
||||
properties: ["openDirectory"],
|
||||
});
|
||||
|
||||
if (filePaths && filePaths.length > 0) {
|
||||
const path = filePaths[0];
|
||||
setSelectedPath(path);
|
||||
}
|
||||
};
|
||||
|
||||
const handleStartClick = () => {
|
||||
setDownloadStarting(true);
|
||||
startDownload(repack.id, selectedPath).finally(() => {
|
||||
setDownloadStarting(false);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible={visible}
|
||||
title={`${gameDetails.name} Installation folder`}
|
||||
description={t("space_left_on_disk", {
|
||||
space: formatBytes(diskFreeSpace?.free ?? 0),
|
||||
})}
|
||||
onClose={onClose}
|
||||
>
|
||||
<div className={styles.container}>
|
||||
<div className={styles.downloadsPathField}>
|
||||
<TextField
|
||||
label={t("downloads_path")}
|
||||
value={selectedPath}
|
||||
readOnly
|
||||
disabled
|
||||
/>
|
||||
|
||||
<Button
|
||||
style={{ alignSelf: "flex-end" }}
|
||||
theme="outline"
|
||||
onClick={handleChooseDownloadsPath}
|
||||
disabled={downloadStarting}
|
||||
>
|
||||
{t("change")}
|
||||
</Button>
|
||||
</div>
|
||||
<p className={styles.hintText}>
|
||||
{t("select_folder_hint")}{" "}
|
||||
<Link
|
||||
to="/settings"
|
||||
style={{
|
||||
textDecoration: "none",
|
||||
color: "#C0C1C7",
|
||||
}}
|
||||
>
|
||||
{t("settings")}
|
||||
</Link>
|
||||
</p>
|
||||
<Button onClick={handleStartClick} disabled={downloadStarting}>
|
||||
{t("download_now")}
|
||||
</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue
Block a user