mirror of
https://github.com/hydralauncher/hydra.git
synced 2025-01-23 13:34:54 +03:00
fix: fixing add game to library
This commit is contained in:
commit
3bd8662b18
34
README.md
34
README.md
@ -239,6 +239,13 @@ yarn build:linux
|
|||||||
<sub><b>Null</b></sub>
|
<sub><b>Null</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/Mkdantas">
|
||||||
|
<img src="https://avatars.githubusercontent.com/u/50972667?v=4" width="100;" alt="Mkdantas"/>
|
||||||
|
<br />
|
||||||
|
<sub><b>Matheus Dantas</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<a href="https://github.com/Hachi-R">
|
<a href="https://github.com/Hachi-R">
|
||||||
<img src="https://avatars.githubusercontent.com/u/58823742?v=4" width="100;" alt="Hachi-R"/>
|
<img src="https://avatars.githubusercontent.com/u/58823742?v=4" width="100;" alt="Hachi-R"/>
|
||||||
@ -259,15 +266,15 @@ yarn build:linux
|
|||||||
<br />
|
<br />
|
||||||
<sub><b>FeriVOQ</b></sub>
|
<sub><b>FeriVOQ</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td></tr>
|
||||||
|
<tr>
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<a href="https://github.com/xbozo">
|
<a href="https://github.com/xbozo">
|
||||||
<img src="https://avatars.githubusercontent.com/u/119091492?v=4" width="100;" alt="xbozo"/>
|
<img src="https://avatars.githubusercontent.com/u/119091492?v=4" width="100;" alt="xbozo"/>
|
||||||
<br />
|
<br />
|
||||||
<sub><b>Guilherme Viana</b></sub>
|
<sub><b>Guilherme Viana</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td></tr>
|
</td>
|
||||||
<tr>
|
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<a href="https://github.com/eltociear">
|
<a href="https://github.com/eltociear">
|
||||||
<img src="https://avatars.githubusercontent.com/u/22633385?v=4" width="100;" alt="eltociear"/>
|
<img src="https://avatars.githubusercontent.com/u/22633385?v=4" width="100;" alt="eltociear"/>
|
||||||
@ -283,10 +290,10 @@ yarn build:linux
|
|||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<a href="https://github.com/FerNikoMF">
|
<a href="https://github.com/vnumex">
|
||||||
<img src="https://avatars.githubusercontent.com/u/76095334?v=4" width="100;" alt="FerNikoMF"/>
|
<img src="https://avatars.githubusercontent.com/u/10434535?v=4" width="100;" alt="vnumex"/>
|
||||||
<br />
|
<br />
|
||||||
<sub><b>Firdavs</b></sub>
|
<sub><b>Vnumex</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td align="center">
|
<td align="center">
|
||||||
@ -296,6 +303,21 @@ yarn build:linux
|
|||||||
<sub><b>Ruslan</b></sub>
|
<sub><b>Ruslan</b></sub>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/FerNikoMF">
|
||||||
|
<img src="https://avatars.githubusercontent.com/u/76095334?v=4" width="100;" alt="FerNikoMF"/>
|
||||||
|
<br />
|
||||||
|
<sub><b>Firdavs</b></sub>
|
||||||
|
</a>
|
||||||
|
</td></tr>
|
||||||
|
<tr>
|
||||||
|
<td align="center">
|
||||||
|
<a href="https://github.com/PCTroller">
|
||||||
|
<img src="https://avatars.githubusercontent.com/u/146987801?v=4" width="100;" alt="PCTroller"/>
|
||||||
|
<br />
|
||||||
|
<sub><b>Null</b></sub>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
<td align="center">
|
<td align="center">
|
||||||
<a href="https://github.com/Chr1s0Blood">
|
<a href="https://github.com/Chr1s0Blood">
|
||||||
<img src="https://avatars.githubusercontent.com/u/166660500?v=4" width="100;" alt="Chr1s0Blood"/>
|
<img src="https://avatars.githubusercontent.com/u/166660500?v=4" width="100;" alt="Chr1s0Blood"/>
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
"github": "Contribute on GitHub"
|
"github": "Contribute on GitHub"
|
||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
"search": "Search",
|
"search": "Search games",
|
||||||
"home": "Home",
|
"home": "Home",
|
||||||
"catalogue": "Catalogue",
|
"catalogue": "Catalogue",
|
||||||
"downloads": "Downloads",
|
"downloads": "Downloads",
|
||||||
@ -86,8 +86,7 @@
|
|||||||
"change": "Change",
|
"change": "Change",
|
||||||
"repacks_modal_description": "Choose the repack you want to download",
|
"repacks_modal_description": "Choose the repack you want to download",
|
||||||
"downloads_path": "Downloads path",
|
"downloads_path": "Downloads path",
|
||||||
"select_folder_hint": "To change the default folder, access the",
|
"select_folder_hint": "To change the default folder, go to the <0>Settings</0>",
|
||||||
"settings": "Settings",
|
|
||||||
"download_now": "Download now",
|
"download_now": "Download now",
|
||||||
"installation_instructions": "Installation Instructions",
|
"installation_instructions": "Installation Instructions",
|
||||||
"installation_instructions_description": "Additional steps are required to install this game",
|
"installation_instructions_description": "Additional steps are required to install this game",
|
||||||
@ -144,7 +143,9 @@
|
|||||||
"launch_with_system": "Launch Hydra on system start-up",
|
"launch_with_system": "Launch Hydra on system start-up",
|
||||||
"general": "General",
|
"general": "General",
|
||||||
"behavior": "Behavior",
|
"behavior": "Behavior",
|
||||||
"real_debrid": "Real Debrid"
|
"enable_real_debrid": "Enable Real Debrid",
|
||||||
|
"real_debrid": "Real Debrid",
|
||||||
|
"real_debrid_api_token_hint": "You can get your API key <0>here</0>"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"download_complete": "Download complete",
|
"download_complete": "Download complete",
|
||||||
|
@ -64,12 +64,14 @@
|
|||||||
"copied_link_to_clipboard": "Enlace copiado",
|
"copied_link_to_clipboard": "Enlace copiado",
|
||||||
"hours": "horas",
|
"hours": "horas",
|
||||||
"minutes": "minutos",
|
"minutes": "minutos",
|
||||||
|
"amount_hours": "{{amount}} horas",
|
||||||
|
"amount_minutes": "{{amount}} minutos",
|
||||||
"accuracy": "{{accuracy}}% precisión",
|
"accuracy": "{{accuracy}}% precisión",
|
||||||
"add_to_library": "Agregar a la biblioteca",
|
"add_to_library": "Agregar a la biblioteca",
|
||||||
"remove_from_library": "Eliminar de la biblioteca",
|
"remove_from_library": "Eliminar de la biblioteca",
|
||||||
"no_downloads": "No hay descargas disponibles",
|
"no_downloads": "No hay descargas disponibles",
|
||||||
"next_suggestion": "Siguiente sugerencia",
|
"next_suggestion": "Siguiente sugerencia",
|
||||||
"play_time": "Jugado {{amount}}",
|
"play_time": "Jugado por {{amount}}",
|
||||||
"install": "Instalar",
|
"install": "Instalar",
|
||||||
"play": "Jugar",
|
"play": "Jugar",
|
||||||
"not_played_yet": "Aún no has jugado a {{title}}",
|
"not_played_yet": "Aún no has jugado a {{title}}",
|
||||||
|
@ -6,3 +6,4 @@ export { default as hu } from "./hu/translation.json";
|
|||||||
export { default as it } from "./it/translation.json";
|
export { default as it } from "./it/translation.json";
|
||||||
export { default as pl } from "./pl/translation.json";
|
export { default as pl } from "./pl/translation.json";
|
||||||
export { default as ru } from "./ru/translation.json";
|
export { default as ru } from "./ru/translation.json";
|
||||||
|
export { default as tr } from "./tr/translation.json";
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
"github": "Contribua no GitHub"
|
"github": "Contribua no GitHub"
|
||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
"search": "Buscar",
|
"search": "Buscar jogos",
|
||||||
"catalogue": "Catálogo",
|
"catalogue": "Catálogo",
|
||||||
"downloads": "Downloads",
|
"downloads": "Downloads",
|
||||||
"search_results": "Resultados da busca",
|
"search_results": "Resultados da busca",
|
||||||
@ -82,8 +82,7 @@
|
|||||||
"change": "Mudar",
|
"change": "Mudar",
|
||||||
"repacks_modal_description": "Escolha o repack do jogo que deseja baixar",
|
"repacks_modal_description": "Escolha o repack do jogo que deseja baixar",
|
||||||
"downloads_path": "Diretório do download",
|
"downloads_path": "Diretório do download",
|
||||||
"select_folder_hint": "Para trocar a pasta padrão, acesse as ",
|
"select_folder_hint": "Para trocar a pasta padrão, acesse a <0>Tela de Configurações</0>",
|
||||||
"settings": "Configurações do Hydra",
|
|
||||||
"download_now": "Baixe agora",
|
"download_now": "Baixe agora",
|
||||||
"installation_instructions": "Instruções de Instalação",
|
"installation_instructions": "Instruções de Instalação",
|
||||||
"installation_instructions_description": "Passos adicionais são necessários para instalar esse jogo",
|
"installation_instructions_description": "Passos adicionais são necessários para instalar esse jogo",
|
||||||
|
164
src/locales/tr/translation.json
Normal file
164
src/locales/tr/translation.json
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
{
|
||||||
|
"home": {
|
||||||
|
"featured": "Öne çıkan",
|
||||||
|
"recently_added": "Son eklenen",
|
||||||
|
"trending": "Popüler",
|
||||||
|
"surprise_me": "Şaşırt beni",
|
||||||
|
"no_results": "Sonuç bulunamadı"
|
||||||
|
},
|
||||||
|
"sidebar": {
|
||||||
|
"catalogue": "Katalog",
|
||||||
|
"downloads": "İndirmeler",
|
||||||
|
"settings": "Ayarlar",
|
||||||
|
"my_library": "Kütüphane",
|
||||||
|
"downloading_metadata": "{{title}} (Metadata indiriliyor…)",
|
||||||
|
"checking_files": "{{title}} ({{percentage}} - Dosyalar kontrol ediliyor…)",
|
||||||
|
"paused": "{{title}} (Duraklatıldı)",
|
||||||
|
"downloading": "{{title}} ({{percentage}} - İndiriliyor…)",
|
||||||
|
"filter": "Kütüphaneyi filtrele",
|
||||||
|
"follow_us": "Bizi takip et",
|
||||||
|
"home": "Ana menü",
|
||||||
|
"discord": "Discord'umuza katıl",
|
||||||
|
"x": "X'te bizi takip et",
|
||||||
|
"github": "GitHub'da bize katkı yap"
|
||||||
|
},
|
||||||
|
"header": {
|
||||||
|
"search": "Ara",
|
||||||
|
"home": "Ana menü",
|
||||||
|
"catalogue": "Katalog",
|
||||||
|
"downloads": "İndirmeler",
|
||||||
|
"search_results": "Arama sonuçları",
|
||||||
|
"settings": "Ayarlar"
|
||||||
|
},
|
||||||
|
"bottom_panel": {
|
||||||
|
"no_downloads_in_progress": "İndirilen bir şey yok",
|
||||||
|
"downloading_metadata": "{{title}} metadatası indiriliyor…",
|
||||||
|
"checking_files": "{{title}} dosyaları kontrol ediliyor… ({{percentage}} tamamlandı)",
|
||||||
|
"downloading": "{{title}} indiriliyor… ({{percentage}} tamamlandı) - Bitiş {{eta}} - {{speed}}"
|
||||||
|
},
|
||||||
|
"catalogue": {
|
||||||
|
"next_page": "Sonraki sayfa",
|
||||||
|
"previous_page": "Önceki sayfa"
|
||||||
|
},
|
||||||
|
"game_details": {
|
||||||
|
"open_download_options": "İndirme seçeneklerini aç",
|
||||||
|
"download_options_zero": "İndirme seçeneği yok",
|
||||||
|
"download_options_one": "{{count}} indirme seçeneği",
|
||||||
|
"download_options_other": "{{count}} indirme seçeneği",
|
||||||
|
"updated_at": "{{updated_at}} güncellendi",
|
||||||
|
"install": "İndir",
|
||||||
|
"resume": "Devam et",
|
||||||
|
"pause": "Duraklat",
|
||||||
|
"cancel": "İptal et",
|
||||||
|
"remove": "Sil",
|
||||||
|
"remove_from_list": "Sil",
|
||||||
|
"space_left_on_disk": "Diskte {{space}} yer kaldı",
|
||||||
|
"eta": "Bitiş {{eta}}",
|
||||||
|
"downloading_metadata": "Metadata indiriliyor…",
|
||||||
|
"checking_files": "Dosyalar kontrol ediliyor…",
|
||||||
|
"filter": "Repackleri filtrele",
|
||||||
|
"requirements": "Sistem gereksinimleri",
|
||||||
|
"minimum": "Minimum",
|
||||||
|
"recommended": "Önerilen",
|
||||||
|
"no_minimum_requirements": "{{title}} minimum sistem gereksinim bilgilerini karşılamıyor",
|
||||||
|
"no_recommended_requirements": "{{title}} önerilen sistem gereksinim bilgilerini karşılamıyor",
|
||||||
|
"paused_progress": "{{progress}} (Duraklatıldı)",
|
||||||
|
"release_date": "{{date}} tarihinde çıktı",
|
||||||
|
"publisher": "{{publisher}} tarihinde yayınlandı",
|
||||||
|
"copy_link_to_clipboard": "Link'i kopyala",
|
||||||
|
"copied_link_to_clipboard": "Link kopyalandı",
|
||||||
|
"hours": "saatler",
|
||||||
|
"minutes": "dakikalar",
|
||||||
|
"amount_hours": "{{amount}} saat",
|
||||||
|
"amount_minutes": "{{amount}} dakika",
|
||||||
|
"accuracy": "%{{accuracy}} doğruluk",
|
||||||
|
"add_to_library": "Kütüphaneye ekle",
|
||||||
|
"remove_from_library": "Kütüphaneden kaldır",
|
||||||
|
"no_downloads": "İndirme yok",
|
||||||
|
"play_time": "{{amount}} oynandı",
|
||||||
|
"last_time_played": "Son oynanan {{period}}",
|
||||||
|
"not_played_yet": "Bu {{title}} hiç oynanmadı",
|
||||||
|
"next_suggestion": "Sıradaki öneri",
|
||||||
|
"play": "Oyna",
|
||||||
|
"deleting": "Installer siliniyor…",
|
||||||
|
"close": "Kapat",
|
||||||
|
"playing_now": "Şimdi oynanıyor",
|
||||||
|
"change": "Değiştir",
|
||||||
|
"repacks_modal_description": "İndirmek istediğiiniz repacki seçin",
|
||||||
|
"downloads_path": "İndirme yolu",
|
||||||
|
"select_folder_hint": "Varsayılan klasörü değiştirmek için ulaşmanız gereken ayar",
|
||||||
|
"settings": "Ayarlar",
|
||||||
|
"download_now": "Şimdi",
|
||||||
|
"installation_instructions": "Kurulum",
|
||||||
|
"installation_instructions_description": "Bu oyunu kurmak için ek adımlar gerekiyor",
|
||||||
|
"online_fix_instruction": "OnlineFix oyunlarını ayıklamak için parola gerekiyor. Gerekli olduğunda bu parolayı kullanın:",
|
||||||
|
"dodi_installation_instruction": "Dodi installerını açtığınızda, kurulumu başlatmak için bu tuşa basın <0 />:",
|
||||||
|
"dont_show_it_again": "Tekrar gösterme",
|
||||||
|
"copy_to_clipboard": "Kopyala",
|
||||||
|
"copied_to_clipboard": "Kopyalandı",
|
||||||
|
"got_it": "Tamam"
|
||||||
|
},
|
||||||
|
"activation": {
|
||||||
|
"title": "Hydra'yı aktif et",
|
||||||
|
"installation_id": "Kurulum ID'si:",
|
||||||
|
"enter_activation_code": "Aktifleştirme kodunuzu girin",
|
||||||
|
"message": "Bunu nerede soracağınızı bilmiyorsanız, buna sahip olmamanız gerekiyor.",
|
||||||
|
"activate": "Aktif et",
|
||||||
|
"loading": "Yükleniyor…"
|
||||||
|
},
|
||||||
|
"downloads": {
|
||||||
|
"resume": "Devam et",
|
||||||
|
"pause": "Duraklat",
|
||||||
|
"eta": "Bitiş {{eta}}",
|
||||||
|
"paused": "Duraklatıldı",
|
||||||
|
"verifying": "Doğrulanıyor…",
|
||||||
|
"completed_at": "{{date}} tarihinde tamamlanacak",
|
||||||
|
"completed": "Tamamlandı",
|
||||||
|
"cancelled": "İptal edildi",
|
||||||
|
"download_again": "Tekrar indir",
|
||||||
|
"cancel": "İptal et",
|
||||||
|
"filter": "Yüklü oyunları filtrele",
|
||||||
|
"remove": "Kaldır",
|
||||||
|
"downloading_metadata": "Metadata indiriliyor…",
|
||||||
|
"checking_files": "Dosyalar kontrol ediliyor…",
|
||||||
|
"starting_download": "İndirme başlatılıyor…",
|
||||||
|
"deleting": "Installer siliniyor…",
|
||||||
|
"delete": "Installer'ı sil",
|
||||||
|
"remove_from_list": "Kaldır",
|
||||||
|
"delete_modal_title": "Emin misiniz?",
|
||||||
|
"delete_modal_description": "Bu bilgisayarınızdan tüm kurulum dosyalarını silecek",
|
||||||
|
"install": "Kur"
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"downloads_path": "İndirme yolu",
|
||||||
|
"change": "Güncelle",
|
||||||
|
"notifications": "Bildirimler",
|
||||||
|
"enable_download_notifications": "Bir indirme bittiğinde",
|
||||||
|
"enable_repack_list_notifications": "Yeni bir repack eklendiğinde",
|
||||||
|
"telemetry": "Telemetri",
|
||||||
|
"telemetry_description": "Anonim kullanım istatistiklerini aktifleştir"
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"download_complete": "İndirme tamamlandı",
|
||||||
|
"game_ready_to_install": "{{title}} kuruluma hazır",
|
||||||
|
"repack_list_updated": "Repack listesi güncellendi",
|
||||||
|
"repack_count_one": "{{count}} yeni repack eklendi",
|
||||||
|
"repack_count_other": "{{count}} yeni repack eklendi"
|
||||||
|
},
|
||||||
|
"system_tray": {
|
||||||
|
"open": "Hydra'yı aç",
|
||||||
|
"quit": "Çık"
|
||||||
|
},
|
||||||
|
"game_card": {
|
||||||
|
"no_downloads": "İndirme mevcut değil"
|
||||||
|
},
|
||||||
|
"binary_not_found_modal": {
|
||||||
|
"title": "Programlar yüklü değil",
|
||||||
|
"description": "Sisteminizde Wine veya Lutris çalıştırılabiliri bulunamadı",
|
||||||
|
"instructions": "Oyunları düzgün şekilde çalıştırmak için Linux distronuza bunlardan birini nasıl yükleyebileceğinize bakın"
|
||||||
|
},
|
||||||
|
"modal": {
|
||||||
|
"close": "Kapat tuşu"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,42 +8,35 @@ import { requestSteam250 } from "@main/services";
|
|||||||
|
|
||||||
const repacks = stateManager.getValue("repacks");
|
const repacks = stateManager.getValue("repacks");
|
||||||
|
|
||||||
interface GetStringForLookup {
|
const getStringForLookup = (index: number): string => {
|
||||||
(index: number): string;
|
const repack = repacks[index];
|
||||||
}
|
const formatter =
|
||||||
|
repackerFormatter[repack.repacker as keyof typeof repackerFormatter];
|
||||||
|
|
||||||
|
return formatName(formatter(repack.title));
|
||||||
|
};
|
||||||
|
|
||||||
|
const resultSize = 12;
|
||||||
|
|
||||||
const getCatalogue = async (
|
const getCatalogue = async (
|
||||||
_event: Electron.IpcMainInvokeEvent,
|
_event: Electron.IpcMainInvokeEvent,
|
||||||
category: CatalogueCategory
|
category: CatalogueCategory
|
||||||
) => {
|
) => {
|
||||||
const getStringForLookup = (index: number): string => {
|
|
||||||
const repack = repacks[index];
|
|
||||||
const formatter =
|
|
||||||
repackerFormatter[repack.repacker as keyof typeof repackerFormatter];
|
|
||||||
|
|
||||||
return formatName(formatter(repack.title));
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!repacks.length) return [];
|
if (!repacks.length) return [];
|
||||||
|
|
||||||
const resultSize = 12;
|
|
||||||
|
|
||||||
if (category === "trending") {
|
if (category === "trending") {
|
||||||
return getTrendingCatalogue(resultSize);
|
return getTrendingCatalogue(resultSize);
|
||||||
} else {
|
|
||||||
return getRecentlyAddedCatalogue(
|
|
||||||
resultSize,
|
|
||||||
resultSize,
|
|
||||||
getStringForLookup
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return getRecentlyAddedCatalogue(resultSize);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getTrendingCatalogue = async (
|
const getTrendingCatalogue = async (
|
||||||
resultSize: number
|
resultSize: number
|
||||||
): Promise<CatalogueEntry[]> => {
|
): Promise<CatalogueEntry[]> => {
|
||||||
const results: CatalogueEntry[] = [];
|
const results: CatalogueEntry[] = [];
|
||||||
const trendingGames = await requestSteam250("/30day");
|
const trendingGames = await requestSteam250("/90day");
|
||||||
|
|
||||||
for (
|
for (
|
||||||
let i = 0;
|
let i = 0;
|
||||||
i < trendingGames.length && results.length < resultSize;
|
i < trendingGames.length && results.length < resultSize;
|
||||||
@ -51,7 +44,7 @@ const getTrendingCatalogue = async (
|
|||||||
) {
|
) {
|
||||||
if (!trendingGames[i]) continue;
|
if (!trendingGames[i]) continue;
|
||||||
|
|
||||||
const { title, objectID } = trendingGames[i];
|
const { title, objectID } = trendingGames[i]!;
|
||||||
const repacks = searchRepacks(title);
|
const repacks = searchRepacks(title);
|
||||||
|
|
||||||
if (title && repacks.length) {
|
if (title && repacks.length) {
|
||||||
@ -69,11 +62,8 @@ const getTrendingCatalogue = async (
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getRecentlyAddedCatalogue = async (
|
const getRecentlyAddedCatalogue = async (
|
||||||
resultSize: number,
|
resultSize: number
|
||||||
requestSize: number,
|
|
||||||
getStringForLookup: GetStringForLookup
|
|
||||||
): Promise<CatalogueEntry[]> => {
|
): Promise<CatalogueEntry[]> => {
|
||||||
let lookupRequest = [];
|
|
||||||
const results: CatalogueEntry[] = [];
|
const results: CatalogueEntry[] = [];
|
||||||
|
|
||||||
for (let i = 0; results.length < resultSize; i++) {
|
for (let i = 0; results.length < resultSize; i++) {
|
||||||
@ -84,15 +74,7 @@ const getRecentlyAddedCatalogue = async (
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
lookupRequest.push(searchGames({ query: stringForLookup }));
|
const games = searchGames({ query: stringForLookup });
|
||||||
|
|
||||||
if (lookupRequest.length < requestSize) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const games = (await Promise.all(lookupRequest)).map((value) =>
|
|
||||||
value.at(0)
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const game of games) {
|
for (const game of games) {
|
||||||
const isAlreadyIncluded = results.some(
|
const isAlreadyIncluded = results.some(
|
||||||
@ -105,7 +87,6 @@ const getRecentlyAddedCatalogue = async (
|
|||||||
|
|
||||||
results.push(game);
|
results.push(game);
|
||||||
}
|
}
|
||||||
lookupRequest = [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return results.slice(0, resultSize);
|
return results.slice(0, resultSize);
|
||||||
|
@ -16,7 +16,6 @@ const addGameToLibrary = async (
|
|||||||
const game = await gameRepository.findOne({
|
const game = await gameRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
objectID,
|
objectID,
|
||||||
isDeleted: false,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ import { registerEvent } from "../register-event";
|
|||||||
const deleteGameFolder = async (
|
const deleteGameFolder = async (
|
||||||
_event: Electron.IpcMainInvokeEvent,
|
_event: Electron.IpcMainInvokeEvent,
|
||||||
gameId: number
|
gameId: number
|
||||||
) => {
|
): Promise<void> => {
|
||||||
const game = await gameRepository.findOne({
|
const game = await gameRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
id: gameId,
|
id: gameId,
|
||||||
@ -38,7 +38,8 @@ const deleteGameFolder = async (
|
|||||||
logger.error(error);
|
logger.error(error);
|
||||||
reject();
|
reject();
|
||||||
}
|
}
|
||||||
resolve(null);
|
|
||||||
|
resolve();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import axios from "axios";
|
||||||
import { getSteamAppAsset } from "@main/helpers";
|
import { getSteamAppAsset } from "@main/helpers";
|
||||||
|
|
||||||
export interface SteamGridResponse {
|
export interface SteamGridResponse {
|
||||||
@ -27,33 +28,35 @@ export const getSteamGridData = async (
|
|||||||
): Promise<SteamGridResponse> => {
|
): Promise<SteamGridResponse> => {
|
||||||
const searchParams = new URLSearchParams(params);
|
const searchParams = new URLSearchParams(params);
|
||||||
|
|
||||||
const response = await fetch(
|
if (!import.meta.env.MAIN_VITE_STEAMGRIDDB_API_KEY) {
|
||||||
|
throw new Error("STEAMGRIDDB_API_KEY is not set");
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await axios.get(
|
||||||
`https://www.steamgriddb.com/api/v2/${path}/${shop}/${objectID}?${searchParams.toString()}`,
|
`https://www.steamgriddb.com/api/v2/${path}/${shop}/${objectID}?${searchParams.toString()}`,
|
||||||
{
|
{
|
||||||
method: "GET",
|
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${import.meta.env.MAIN_VITE_STEAMGRIDDB_API_KEY}`,
|
Authorization: `Bearer ${import.meta.env.MAIN_VITE_STEAMGRIDDB_API_KEY}`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
return response.json();
|
return response.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getSteamGridGameById = async (
|
export const getSteamGridGameById = async (
|
||||||
id: number
|
id: number
|
||||||
): Promise<SteamGridGameResponse> => {
|
): Promise<SteamGridGameResponse> => {
|
||||||
const response = await fetch(
|
const response = await axios.get(
|
||||||
`https://www.steamgriddb.com/api/public/game/${id}`,
|
`https://www.steamgriddb.com/api/public/game/${id}`,
|
||||||
{
|
{
|
||||||
method: "GET",
|
|
||||||
headers: {
|
headers: {
|
||||||
Referer: "https://www.steamgriddb.com/",
|
Referer: "https://www.steamgriddb.com/",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
return response.json();
|
return response.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getSteamGameIconUrl = async (objectID: string) => {
|
export const getSteamGameIconUrl = async (objectID: string) => {
|
||||||
|
@ -32,7 +32,7 @@ export function App({ children }: AppProps) {
|
|||||||
const contentRef = useRef<HTMLDivElement>(null);
|
const contentRef = useRef<HTMLDivElement>(null);
|
||||||
const { updateLibrary } = useLibrary();
|
const { updateLibrary } = useLibrary();
|
||||||
|
|
||||||
const { clearDownload, addPacket } = useDownload();
|
const { clearDownload, setLastPacket } = useDownload();
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
@ -64,14 +64,14 @@ export function App({ children }: AppProps) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
addPacket(downloadProgress);
|
setLastPacket(downloadProgress);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
unsubscribe();
|
unsubscribe();
|
||||||
};
|
};
|
||||||
}, [clearDownload, addPacket, updateLibrary]);
|
}, [clearDownload, setLastPacket, updateLibrary]);
|
||||||
|
|
||||||
const handleSearch = useCallback(
|
const handleSearch = useCallback(
|
||||||
(query: string) => {
|
(query: string) => {
|
||||||
|
@ -64,7 +64,7 @@ export function BottomPanel() {
|
|||||||
<small>{status}</small>
|
<small>{status}</small>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<small>
|
<small tabIndex={0}>
|
||||||
v{version} "{VERSION_CODENAME}"
|
v{version} "{VERSION_CODENAME}"
|
||||||
</small>
|
</small>
|
||||||
</footer>
|
</footer>
|
||||||
|
@ -19,6 +19,7 @@ const base = style({
|
|||||||
":disabled": {
|
":disabled": {
|
||||||
opacity: vars.opacity.disabled,
|
opacity: vars.opacity.disabled,
|
||||||
pointerEvents: "none",
|
pointerEvents: "none",
|
||||||
|
cursor: "not-allowed",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -7,3 +7,4 @@ export * from "./modal/modal";
|
|||||||
export * from "./sidebar/sidebar";
|
export * from "./sidebar/sidebar";
|
||||||
export * from "./text-field/text-field";
|
export * from "./text-field/text-field";
|
||||||
export * from "./checkbox-field/checkbox-field";
|
export * from "./checkbox-field/checkbox-field";
|
||||||
|
export * from "./link/link";
|
||||||
|
9
src/renderer/src/components/link/link.css.ts
Normal file
9
src/renderer/src/components/link/link.css.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { style } from "@vanilla-extract/css";
|
||||||
|
|
||||||
|
export const link = style({
|
||||||
|
textDecoration: "none",
|
||||||
|
color: "#C0C1C7",
|
||||||
|
":hover": {
|
||||||
|
textDecoration: "underline",
|
||||||
|
},
|
||||||
|
});
|
33
src/renderer/src/components/link/link.tsx
Normal file
33
src/renderer/src/components/link/link.tsx
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { Link as ReactRouterDomLink, LinkProps } from "react-router-dom";
|
||||||
|
import cn from "classnames";
|
||||||
|
import * as styles from "./link.css";
|
||||||
|
|
||||||
|
export function Link({ children, to, className, ...props }: LinkProps) {
|
||||||
|
const openExternal = (event: React.MouseEvent) => {
|
||||||
|
event.preventDefault();
|
||||||
|
window.electron.openExternal(to as string);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (typeof to === "string" && to.startsWith("http")) {
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
href={to}
|
||||||
|
className={cn(styles.link, className)}
|
||||||
|
onClick={openExternal}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ReactRouterDomLink
|
||||||
|
className={cn(styles.link, className)}
|
||||||
|
to={to}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</ReactRouterDomLink>
|
||||||
|
);
|
||||||
|
}
|
@ -2,6 +2,13 @@ import { SPACING_UNIT, vars } from "../../theme.css";
|
|||||||
import { style } from "@vanilla-extract/css";
|
import { style } from "@vanilla-extract/css";
|
||||||
import { recipe } from "@vanilla-extract/recipes";
|
import { recipe } from "@vanilla-extract/recipes";
|
||||||
|
|
||||||
|
export const textFieldContainer = style({
|
||||||
|
flex: "1",
|
||||||
|
gap: `${SPACING_UNIT}px`,
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
});
|
||||||
|
|
||||||
export const textField = recipe({
|
export const textField = recipe({
|
||||||
base: {
|
base: {
|
||||||
display: "inline-flex",
|
display: "inline-flex",
|
||||||
@ -50,9 +57,3 @@ export const textFieldInput = style({
|
|||||||
cursor: "text",
|
cursor: "text",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const label = style({
|
|
||||||
marginBottom: `${SPACING_UNIT}px`,
|
|
||||||
display: "block",
|
|
||||||
color: vars.color.bodyText,
|
|
||||||
});
|
|
||||||
|
@ -9,28 +9,31 @@ export interface TextFieldProps
|
|||||||
> {
|
> {
|
||||||
theme?: NonNullable<RecipeVariants<typeof styles.textField>>["theme"];
|
theme?: NonNullable<RecipeVariants<typeof styles.textField>>["theme"];
|
||||||
label?: string | React.ReactNode;
|
label?: string | React.ReactNode;
|
||||||
|
hint?: string | React.ReactNode;
|
||||||
textFieldProps?: React.DetailedHTMLProps<
|
textFieldProps?: React.DetailedHTMLProps<
|
||||||
React.HTMLAttributes<HTMLDivElement>,
|
React.HTMLAttributes<HTMLDivElement>,
|
||||||
HTMLDivElement
|
HTMLDivElement
|
||||||
>;
|
>;
|
||||||
|
containerProps?: React.DetailedHTMLProps<
|
||||||
|
React.HTMLAttributes<HTMLDivElement>,
|
||||||
|
HTMLDivElement
|
||||||
|
>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TextField({
|
export function TextField({
|
||||||
theme = "primary",
|
theme = "primary",
|
||||||
label,
|
label,
|
||||||
|
hint,
|
||||||
textFieldProps,
|
textFieldProps,
|
||||||
|
containerProps,
|
||||||
...props
|
...props
|
||||||
}: TextFieldProps) {
|
}: TextFieldProps) {
|
||||||
const [isFocused, setIsFocused] = useState(false);
|
const [isFocused, setIsFocused] = useState(false);
|
||||||
const id = useId();
|
const id = useId();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ flex: 1 }}>
|
<div className={styles.textFieldContainer} {...containerProps}>
|
||||||
{label && (
|
{label && <label tabIndex={0}>{label}</label>}
|
||||||
<label htmlFor={id} className={styles.label} tabIndex={0}>
|
|
||||||
{label}
|
|
||||||
</label>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={styles.textField({ focused: isFocused, theme })}
|
className={styles.textField({ focused: isFocused, theme })}
|
||||||
@ -45,6 +48,8 @@ export function TextField({
|
|||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{hint && <small tabIndex={0}>{hint}</small>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -3,13 +3,13 @@ import type { PayloadAction } from "@reduxjs/toolkit";
|
|||||||
import type { TorrentProgress } from "@types";
|
import type { TorrentProgress } from "@types";
|
||||||
|
|
||||||
interface DownloadState {
|
interface DownloadState {
|
||||||
packets: TorrentProgress[];
|
lastPacket: TorrentProgress | null;
|
||||||
gameId: number | null;
|
gameId: number | null;
|
||||||
gamesWithDeletionInProgress: number[];
|
gamesWithDeletionInProgress: number[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: DownloadState = {
|
const initialState: DownloadState = {
|
||||||
packets: [],
|
lastPacket: null,
|
||||||
gameId: null,
|
gameId: null,
|
||||||
gamesWithDeletionInProgress: [],
|
gamesWithDeletionInProgress: [],
|
||||||
};
|
};
|
||||||
@ -18,12 +18,12 @@ export const downloadSlice = createSlice({
|
|||||||
name: "download",
|
name: "download",
|
||||||
initialState,
|
initialState,
|
||||||
reducers: {
|
reducers: {
|
||||||
addPacket: (state, action: PayloadAction<TorrentProgress>) => {
|
setLastPacket: (state, action: PayloadAction<TorrentProgress>) => {
|
||||||
state.packets = [...state.packets, action.payload];
|
state.lastPacket = action.payload;
|
||||||
if (!state.gameId) state.gameId = action.payload.game.id;
|
if (!state.gameId) state.gameId = action.payload.game.id;
|
||||||
},
|
},
|
||||||
clearDownload: (state) => {
|
clearDownload: (state) => {
|
||||||
state.packets = [];
|
state.lastPacket = null;
|
||||||
state.gameId = null;
|
state.gameId = null;
|
||||||
},
|
},
|
||||||
setGameDeleting: (state, action: PayloadAction<number>) => {
|
setGameDeleting: (state, action: PayloadAction<number>) => {
|
||||||
@ -42,7 +42,7 @@ export const downloadSlice = createSlice({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const {
|
export const {
|
||||||
addPacket,
|
setLastPacket,
|
||||||
clearDownload,
|
clearDownload,
|
||||||
setGameDeleting,
|
setGameDeleting,
|
||||||
removeGameFromDeleting,
|
removeGameFromDeleting,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { formatDistance } from "date-fns";
|
import { formatDistance } from "date-fns";
|
||||||
import type { FormatDistanceOptions } from "date-fns";
|
import type { FormatDistanceOptions } from "date-fns";
|
||||||
import { ptBR, enUS, es, fr } from "date-fns/locale";
|
import { ptBR, enUS, es, fr, pl, hu, tr, ru, it } from "date-fns/locale";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export function useDate() {
|
export function useDate() {
|
||||||
@ -10,6 +10,11 @@ export function useDate() {
|
|||||||
if (i18n.language.startsWith("pt")) return ptBR;
|
if (i18n.language.startsWith("pt")) return ptBR;
|
||||||
if (i18n.language.startsWith("es")) return es;
|
if (i18n.language.startsWith("es")) return es;
|
||||||
if (i18n.language.startsWith("fr")) return fr;
|
if (i18n.language.startsWith("fr")) return fr;
|
||||||
|
if (i18n.language.startsWith("hu")) return hu;
|
||||||
|
if (i18n.language.startsWith("pl")) return pl;
|
||||||
|
if (i18n.language.startsWith("tr")) return tr;
|
||||||
|
if (i18n.language.startsWith("ru")) return ru;
|
||||||
|
if (i18n.language.startsWith("it")) return it;
|
||||||
|
|
||||||
return enUS;
|
return enUS;
|
||||||
};
|
};
|
||||||
|
@ -4,7 +4,7 @@ import { formatDownloadProgress } from "@renderer/helpers";
|
|||||||
import { useLibrary } from "./use-library";
|
import { useLibrary } from "./use-library";
|
||||||
import { useAppDispatch, useAppSelector } from "./redux";
|
import { useAppDispatch, useAppSelector } from "./redux";
|
||||||
import {
|
import {
|
||||||
addPacket,
|
setLastPacket,
|
||||||
clearDownload,
|
clearDownload,
|
||||||
setGameDeleting,
|
setGameDeleting,
|
||||||
removeGameFromDeleting,
|
removeGameFromDeleting,
|
||||||
@ -18,13 +18,11 @@ export function useDownload() {
|
|||||||
const { updateLibrary } = useLibrary();
|
const { updateLibrary } = useLibrary();
|
||||||
const { formatDistance } = useDate();
|
const { formatDistance } = useDate();
|
||||||
|
|
||||||
const { packets, gamesWithDeletionInProgress } = useAppSelector(
|
const { lastPacket, gamesWithDeletionInProgress } = useAppSelector(
|
||||||
(state) => state.download
|
(state) => state.download
|
||||||
);
|
);
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const lastPacket = packets.at(-1);
|
|
||||||
|
|
||||||
const startDownload = (
|
const startDownload = (
|
||||||
repackId: number,
|
repackId: number,
|
||||||
objectID: string,
|
objectID: string,
|
||||||
@ -128,6 +126,6 @@ export function useDownload() {
|
|||||||
deleteGame,
|
deleteGame,
|
||||||
isGameDeleting,
|
isGameDeleting,
|
||||||
clearDownload: () => dispatch(clearDownload()),
|
clearDownload: () => dispatch(clearDownload()),
|
||||||
addPacket: (packet: TorrentProgress) => dispatch(addPacket(packet)),
|
setLastPacket: (packet: TorrentProgress) => dispatch(setLastPacket(packet)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@ export const downloaderName = style({
|
|||||||
borderRadius: "4px",
|
borderRadius: "4px",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
|
alignSelf: "flex-start",
|
||||||
});
|
});
|
||||||
|
|
||||||
export const downloads = style({
|
export const downloads = style({
|
||||||
|
@ -266,12 +266,11 @@ export function Downloads() {
|
|||||||
>
|
>
|
||||||
{game.title}
|
{game.title}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<small className={styles.downloaderName}>
|
|
||||||
{downloaderName[game?.downloader]}
|
|
||||||
</small>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<small className={styles.downloaderName}>
|
||||||
|
{downloaderName[game?.downloader]}
|
||||||
|
</small>
|
||||||
{getGameInfo(game)}
|
{getGameInfo(game)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ export function GallerySlider({ gameDetails }: GallerySliderProps) {
|
|||||||
return gameDetails.screenshots.length;
|
return gameDetails.screenshots.length;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ export function HeroPanelActions({
|
|||||||
filters: [
|
filters: [
|
||||||
{
|
{
|
||||||
name: "Game executable",
|
name: "Game executable",
|
||||||
extensions: window.electron.platform === "win32" ? ["exe"] : [],
|
extensions: ["exe"],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
@ -36,7 +36,7 @@ export function RepacksModal({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setFilteredRepacks(gameDetails.repacks);
|
setFilteredRepacks(gameDetails.repacks);
|
||||||
}, [gameDetails.repacks]);
|
}, [gameDetails.repacks, visible]);
|
||||||
|
|
||||||
const handleRepackClick = (repack: GameRepack) => {
|
const handleRepackClick = (repack: GameRepack) => {
|
||||||
setRepack(repack);
|
setRepack(repack);
|
||||||
|
@ -17,11 +17,3 @@ export const hintText = style({
|
|||||||
fontSize: "12px",
|
fontSize: "12px",
|
||||||
color: vars.color.bodyText,
|
color: vars.color.bodyText,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const settingsLink = style({
|
|
||||||
textDecoration: "none",
|
|
||||||
color: "#C0C1C7",
|
|
||||||
":hover": {
|
|
||||||
textDecoration: "underline",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
import { Button, Modal, TextField } from "@renderer/components";
|
import { Button, Link, Modal, TextField } from "@renderer/components";
|
||||||
import { GameRepack, ShopDetails } from "@types";
|
import { GameRepack, ShopDetails } from "@types";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { Trans, useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { formatBytes } from "@renderer/utils";
|
import { formatBytes } from "@renderer/utils";
|
||||||
import { DiskSpace } from "check-disk-space";
|
import { DiskSpace } from "check-disk-space";
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
import * as styles from "./select-folder-modal.css";
|
import * as styles from "./select-folder-modal.css";
|
||||||
import { DownloadIcon } from "@primer/octicons-react";
|
import { DownloadIcon } from "@primer/octicons-react";
|
||||||
|
|
||||||
@ -100,10 +99,9 @@ export function SelectFolderModal({
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<p className={styles.hintText}>
|
<p className={styles.hintText}>
|
||||||
{t("select_folder_hint")}{" "}
|
<Trans i18nKey="select_folder_hint" ns="game_details">
|
||||||
<Link to="/settings" className={styles.settingsLink}>
|
<Link to="/settings" />
|
||||||
{t("settings")}
|
</Trans>
|
||||||
</Link>
|
|
||||||
</p>
|
</p>
|
||||||
<Button onClick={handleStartClick} disabled={downloadStarting}>
|
<Button onClick={handleStartClick} disabled={downloadStarting}>
|
||||||
<DownloadIcon />
|
<DownloadIcon />
|
||||||
|
7
src/renderer/src/pages/settings/settings-general.css.ts
Normal file
7
src/renderer/src/pages/settings/settings-general.css.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { style } from "@vanilla-extract/css";
|
||||||
|
import { SPACING_UNIT } from "../../theme.css";
|
||||||
|
|
||||||
|
export const downloadsPathField = style({
|
||||||
|
display: "flex",
|
||||||
|
gap: `${SPACING_UNIT}px`,
|
||||||
|
});
|
115
src/renderer/src/pages/settings/settings-general.tsx
Normal file
115
src/renderer/src/pages/settings/settings-general.tsx
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
import { TextField, Button, CheckboxField } from "@renderer/components";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
import * as styles from "./settings-general.css";
|
||||||
|
import type { UserPreferences } from "@types";
|
||||||
|
|
||||||
|
export interface SettingsGeneralProps {
|
||||||
|
userPreferences: UserPreferences | null;
|
||||||
|
updateUserPreferences: (values: Partial<UserPreferences>) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SettingsGeneral({
|
||||||
|
userPreferences,
|
||||||
|
updateUserPreferences,
|
||||||
|
}: SettingsGeneralProps) {
|
||||||
|
const [form, setForm] = useState({
|
||||||
|
downloadsPath: "",
|
||||||
|
downloadNotificationsEnabled: false,
|
||||||
|
repackUpdatesNotificationsEnabled: false,
|
||||||
|
telemetryEnabled: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (userPreferences) {
|
||||||
|
const {
|
||||||
|
downloadsPath,
|
||||||
|
downloadNotificationsEnabled,
|
||||||
|
repackUpdatesNotificationsEnabled,
|
||||||
|
telemetryEnabled,
|
||||||
|
} = userPreferences;
|
||||||
|
|
||||||
|
window.electron.getDefaultDownloadsPath().then((defaultDownloadsPath) => {
|
||||||
|
setForm((prev) => ({
|
||||||
|
...prev,
|
||||||
|
downloadsPath: downloadsPath ?? defaultDownloadsPath,
|
||||||
|
downloadNotificationsEnabled,
|
||||||
|
repackUpdatesNotificationsEnabled,
|
||||||
|
telemetryEnabled,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [userPreferences]);
|
||||||
|
|
||||||
|
const { t } = useTranslation("settings");
|
||||||
|
|
||||||
|
const handleChooseDownloadsPath = async () => {
|
||||||
|
const { filePaths } = await window.electron.showOpenDialog({
|
||||||
|
defaultPath: form.downloadsPath,
|
||||||
|
properties: ["openDirectory"],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (filePaths && filePaths.length > 0) {
|
||||||
|
const path = filePaths[0];
|
||||||
|
updateUserPreferences({ downloadsPath: path });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={styles.downloadsPathField}>
|
||||||
|
<TextField
|
||||||
|
label={t("downloads_path")}
|
||||||
|
value={form.downloadsPath}
|
||||||
|
readOnly
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
style={{ alignSelf: "flex-end" }}
|
||||||
|
theme="outline"
|
||||||
|
onClick={handleChooseDownloadsPath}
|
||||||
|
>
|
||||||
|
{t("change")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>{t("notifications")}</h3>
|
||||||
|
|
||||||
|
<CheckboxField
|
||||||
|
label={t("enable_download_notifications")}
|
||||||
|
checked={form.downloadNotificationsEnabled}
|
||||||
|
onChange={() =>
|
||||||
|
updateUserPreferences({
|
||||||
|
downloadNotificationsEnabled: !form.downloadNotificationsEnabled,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<CheckboxField
|
||||||
|
label={t("enable_repack_list_notifications")}
|
||||||
|
checked={form.repackUpdatesNotificationsEnabled}
|
||||||
|
onChange={() =>
|
||||||
|
updateUserPreferences({
|
||||||
|
repackUpdatesNotificationsEnabled:
|
||||||
|
!form.repackUpdatesNotificationsEnabled,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<h3>{t("telemetry")}</h3>
|
||||||
|
|
||||||
|
<CheckboxField
|
||||||
|
label={t("telemetry_description")}
|
||||||
|
checked={form.telemetryEnabled}
|
||||||
|
onChange={() =>
|
||||||
|
updateUserPreferences({
|
||||||
|
telemetryEnabled: !form.telemetryEnabled,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
import { style } from "@vanilla-extract/css";
|
||||||
|
|
||||||
|
import { SPACING_UNIT } from "../../theme.css";
|
||||||
|
|
||||||
|
export const form = style({
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
gap: `${SPACING_UNIT}px`,
|
||||||
|
});
|
83
src/renderer/src/pages/settings/settings-real-debrid.tsx
Normal file
83
src/renderer/src/pages/settings/settings-real-debrid.tsx
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { Trans, useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
import { Button, CheckboxField, Link, TextField } from "@renderer/components";
|
||||||
|
import * as styles from "./settings-real-debrid.css";
|
||||||
|
import type { UserPreferences } from "@types";
|
||||||
|
import { SPACING_UNIT } from "@renderer/theme.css";
|
||||||
|
|
||||||
|
const REAL_DEBRID_API_TOKEN_URL = "https://real-debrid.com/apitoken";
|
||||||
|
|
||||||
|
export interface SettingsRealDebridProps {
|
||||||
|
userPreferences: UserPreferences | null;
|
||||||
|
updateUserPreferences: (values: Partial<UserPreferences>) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SettingsRealDebrid({
|
||||||
|
userPreferences,
|
||||||
|
updateUserPreferences,
|
||||||
|
}: SettingsRealDebridProps) {
|
||||||
|
const [form, setForm] = useState({
|
||||||
|
useRealDebrid: false,
|
||||||
|
realDebridApiToken: null as string | null,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { t } = useTranslation("settings");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (userPreferences) {
|
||||||
|
setForm({
|
||||||
|
useRealDebrid: Boolean(userPreferences.realDebridApiToken),
|
||||||
|
realDebridApiToken: userPreferences.realDebridApiToken ?? null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [userPreferences]);
|
||||||
|
|
||||||
|
const handleFormSubmit: React.FormEventHandler<HTMLFormElement> = (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
updateUserPreferences({ realDebridApiToken: form.realDebridApiToken });
|
||||||
|
};
|
||||||
|
|
||||||
|
const isButtonDisabled = form.useRealDebrid && !form.realDebridApiToken;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form className={styles.form} onSubmit={handleFormSubmit}>
|
||||||
|
<CheckboxField
|
||||||
|
label={t("enable_real_debrid")}
|
||||||
|
checked={form.useRealDebrid}
|
||||||
|
onChange={() =>
|
||||||
|
setForm((prev) => ({
|
||||||
|
...prev,
|
||||||
|
useRealDebrid: !form.useRealDebrid,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{form.useRealDebrid && (
|
||||||
|
<TextField
|
||||||
|
label={t("real_debrid_api_token_description")}
|
||||||
|
value={form.realDebridApiToken ?? ""}
|
||||||
|
type="password"
|
||||||
|
onChange={(event) =>
|
||||||
|
setForm({ ...form, realDebridApiToken: event.target.value })
|
||||||
|
}
|
||||||
|
placeholder="API Token"
|
||||||
|
containerProps={{ style: { marginTop: `${SPACING_UNIT}px` } }}
|
||||||
|
hint={
|
||||||
|
<Trans i18nKey="real_debrid_api_token_hint" ns="settings">
|
||||||
|
<Link to={REAL_DEBRID_API_TOKEN_URL} />
|
||||||
|
</Trans>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
style={{ alignSelf: "flex-end" }}
|
||||||
|
disabled={isButtonDisabled}
|
||||||
|
>
|
||||||
|
Save changes
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
@ -20,11 +20,6 @@ export const content = style({
|
|||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
});
|
});
|
||||||
|
|
||||||
export const downloadsPathField = style({
|
|
||||||
display: "flex",
|
|
||||||
gap: `${SPACING_UNIT}px`,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const settingsCategories = style({
|
export const settingsCategories = style({
|
||||||
display: "flex",
|
display: "flex",
|
||||||
gap: `${SPACING_UNIT}px`,
|
gap: `${SPACING_UNIT}px`,
|
||||||
|
@ -1,138 +1,46 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Button, CheckboxField, TextField } from "@renderer/components";
|
import { Button, CheckboxField } from "@renderer/components";
|
||||||
|
|
||||||
import * as styles from "./settings.css";
|
import * as styles from "./settings.css";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { UserPreferences } from "@types";
|
import { UserPreferences } from "@types";
|
||||||
|
import { SettingsRealDebrid } from "./settings-real-debrid";
|
||||||
|
import { SettingsGeneral } from "./settings-general";
|
||||||
|
|
||||||
const categories = ["general", "behavior", "real_debrid"];
|
const categories = ["general", "behavior", "real_debrid"];
|
||||||
|
|
||||||
export function Settings() {
|
export function Settings() {
|
||||||
const [currentCategory, setCurrentCategory] = useState(categories.at(0)!);
|
const [currentCategory, setCurrentCategory] = useState(categories.at(0)!);
|
||||||
|
const [userPreferences, setUserPreferences] =
|
||||||
const [form, setForm] = useState({
|
useState<UserPreferences | null>(null);
|
||||||
downloadsPath: "",
|
|
||||||
downloadNotificationsEnabled: false,
|
|
||||||
repackUpdatesNotificationsEnabled: false,
|
|
||||||
telemetryEnabled: false,
|
|
||||||
realDebridApiToken: null as string | null,
|
|
||||||
preferQuitInsteadOfHiding: false,
|
|
||||||
runAtStartup: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { t } = useTranslation("settings");
|
const { t } = useTranslation("settings");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
Promise.all([
|
window.electron.getUserPreferences().then((userPreferences) => {
|
||||||
window.electron.getDefaultDownloadsPath(),
|
setUserPreferences(userPreferences);
|
||||||
window.electron.getUserPreferences(),
|
|
||||||
]).then(([path, userPreferences]) => {
|
|
||||||
setForm({
|
|
||||||
downloadsPath: userPreferences?.downloadsPath || path,
|
|
||||||
downloadNotificationsEnabled:
|
|
||||||
userPreferences?.downloadNotificationsEnabled ?? false,
|
|
||||||
repackUpdatesNotificationsEnabled:
|
|
||||||
userPreferences?.repackUpdatesNotificationsEnabled ?? false,
|
|
||||||
telemetryEnabled: userPreferences?.telemetryEnabled ?? false,
|
|
||||||
realDebridApiToken: userPreferences?.realDebridApiToken ?? null,
|
|
||||||
preferQuitInsteadOfHiding:
|
|
||||||
userPreferences?.preferQuitInsteadOfHiding ?? false,
|
|
||||||
runAtStartup: userPreferences?.runAtStartup ?? false,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const updateUserPreferences = <T extends keyof UserPreferences>(
|
const handleUpdateUserPreferences = (values: Partial<UserPreferences>) => {
|
||||||
field: T,
|
window.electron.updateUserPreferences(values);
|
||||||
value: UserPreferences[T]
|
|
||||||
) => {
|
|
||||||
setForm((prev) => ({ ...prev, [field]: value }));
|
|
||||||
|
|
||||||
window.electron.updateUserPreferences({
|
|
||||||
[field]: value,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleChooseDownloadsPath = async () => {
|
|
||||||
const { filePaths } = await window.electron.showOpenDialog({
|
|
||||||
defaultPath: form.downloadsPath,
|
|
||||||
properties: ["openDirectory"],
|
|
||||||
});
|
|
||||||
|
|
||||||
if (filePaths && filePaths.length > 0) {
|
|
||||||
const path = filePaths[0];
|
|
||||||
updateUserPreferences("downloadsPath", path);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderCategory = () => {
|
const renderCategory = () => {
|
||||||
if (currentCategory === "general") {
|
if (currentCategory === "general") {
|
||||||
return (
|
return (
|
||||||
<>
|
<SettingsGeneral
|
||||||
<div className={styles.downloadsPathField}>
|
userPreferences={userPreferences}
|
||||||
<TextField
|
updateUserPreferences={handleUpdateUserPreferences}
|
||||||
label={t("downloads_path")}
|
/>
|
||||||
value={form.downloadsPath}
|
|
||||||
readOnly
|
|
||||||
disabled
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
style={{ alignSelf: "flex-end" }}
|
|
||||||
theme="outline"
|
|
||||||
onClick={handleChooseDownloadsPath}
|
|
||||||
>
|
|
||||||
{t("change")}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3>{t("notifications")}</h3>
|
|
||||||
|
|
||||||
<CheckboxField
|
|
||||||
label={t("enable_download_notifications")}
|
|
||||||
checked={form.downloadNotificationsEnabled}
|
|
||||||
onChange={() =>
|
|
||||||
updateUserPreferences(
|
|
||||||
"downloadNotificationsEnabled",
|
|
||||||
!form.downloadNotificationsEnabled
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<CheckboxField
|
|
||||||
label={t("enable_repack_list_notifications")}
|
|
||||||
checked={form.repackUpdatesNotificationsEnabled}
|
|
||||||
onChange={() =>
|
|
||||||
updateUserPreferences(
|
|
||||||
"repackUpdatesNotificationsEnabled",
|
|
||||||
!form.repackUpdatesNotificationsEnabled
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<h3>{t("telemetry")}</h3>
|
|
||||||
|
|
||||||
<CheckboxField
|
|
||||||
label={t("telemetry_description")}
|
|
||||||
checked={form.telemetryEnabled}
|
|
||||||
onChange={() =>
|
|
||||||
updateUserPreferences("telemetryEnabled", !form.telemetryEnabled)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentCategory === "real_debrid") {
|
if (currentCategory === "real_debrid") {
|
||||||
return (
|
return (
|
||||||
<TextField
|
<SettingsRealDebrid
|
||||||
label={t("real_debrid_api_token_description")}
|
userPreferences={userPreferences}
|
||||||
value={form.realDebridApiToken ?? ""}
|
updateUserPreferences={handleUpdateUserPreferences}
|
||||||
type="password"
|
|
||||||
onChange={(event) => {
|
|
||||||
updateUserPreferences("realDebridApiToken", event.target.value);
|
|
||||||
}}
|
|
||||||
placeholder="API Token"
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -177,7 +85,7 @@ export function Settings() {
|
|||||||
))}
|
))}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<h3>{t(currentCategory)}</h3>
|
<h2>{t(currentCategory)}</h2>
|
||||||
{renderCategory()}
|
{renderCategory()}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
Loading…
Reference in New Issue
Block a user