mirror of
https://github.com/Zagrios/bs-manager.git
synced 2026-07-03 14:08:25 +02:00
Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9964940020 | |||
| 0df4cef2a6 | |||
| 846e1f55be | |||
| 4dbc552ca9 | |||
| 066ced228d | |||
| 40ebf89389 | |||
| 59892160c3 | |||
| 7f6e87bdfa | |||
| cde12371d7 | |||
| d9b4259b97 | |||
| 9c20640fa7 | |||
| c7c28d6461 | |||
| 3859e04756 | |||
| d210c0f38d | |||
| 10f8685b68 | |||
| a2052d6e0f | |||
| 5b5a4a0817 | |||
| a372707ebd | |||
| b1d9a29da9 | |||
| 46556cf3cc | |||
| 06c242eb01 | |||
| c6500f7cf6 | |||
| b00d41f98f | |||
| e1b55e4019 | |||
| feb6ea3f87 | |||
| 9d945cdcfb | |||
| 5ea12c2c3a | |||
| 0a30867a12 | |||
| f46fee7ee7 | |||
| ed5f8fb073 | |||
| dfe63e525e | |||
| 207d3c782e | |||
| 56b462ad8a | |||
| 2e85c62c02 | |||
| 7fcef774a8 | |||
| 5337c4a61f | |||
| ed158f55c5 | |||
| 568ad7d051 | |||
| e1ca8dbf99 |
@@ -847,7 +847,59 @@
|
||||
"ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/510702041542166484",
|
||||
"ReleaseImg": "https://clan.fastly.steamstatic.com/images/32055887/c27dac7f96e53672d020d97838cec1d5d28e3180.png",
|
||||
"ReleaseDate": "1743515622",
|
||||
"year": "2025"
|
||||
},
|
||||
{
|
||||
"BSVersion": "1.40.5",
|
||||
"BSManifest": "2447038720841508806",
|
||||
"OculusBinaryId": "9120095564756808",
|
||||
"ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/502825275922317447",
|
||||
"ReleaseImg": "https://clan.fastly.steamstatic.com/images/32055887/2b5845893d915902106aaf30b6d1a95f68a4b226.png",
|
||||
"ReleaseDate": "1747321305",
|
||||
"year": "2025"
|
||||
},
|
||||
{
|
||||
"BSVersion": "1.40.6",
|
||||
"BSManifest": "3050696685250334115",
|
||||
"OculusBinaryId": "9315734585192904",
|
||||
"ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/510708375093248769",
|
||||
"ReleaseImg": "https://clan.fastly.steamstatic.com/images/32055887/0f658ed39f480446e0dc5807b1591e8a18aef2f5.png",
|
||||
"ReleaseDate": "1749135144",
|
||||
"year": "2025"
|
||||
},
|
||||
{
|
||||
"BSVersion": "1.40.7",
|
||||
"BSManifest": "7263483117834945201",
|
||||
"OculusBinaryId": "9533840436715650",
|
||||
"ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/510710285788516821",
|
||||
"ReleaseImg": "https://clan.akamai.steamstatic.com/images//32055887/c34cf1f62f18898280e2bf905518ba71cce0cffd.png",
|
||||
"ReleaseDate": "1752159922",
|
||||
"year": "2025"
|
||||
},
|
||||
{
|
||||
"BSVersion": "1.40.8",
|
||||
"BSManifest": "8437413909225671968",
|
||||
"OculusBinaryId": "9637940462972313",
|
||||
"ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/510712822654566578",
|
||||
"ReleaseImg": "https://clan.fastly.steamstatic.com/images//32055887/5e32755428ba8f737bf7e9cb7cfc58465b72818c.png",
|
||||
"ReleaseDate": "1753369967",
|
||||
"year": "2025",
|
||||
"recommended": true
|
||||
},
|
||||
{
|
||||
"BSVersion": "1.40.9",
|
||||
"BSManifest": "3458990238328802301",
|
||||
"OculusBinaryId": "9907754402657583",
|
||||
"ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/510715359276302413",
|
||||
"ReleaseImg": "https://clan.fastly.steamstatic.com/images/32055887/2c6d12b6791daf434c74a5b7ab94a236789c6836.png",
|
||||
"ReleaseDate": "1755776928",
|
||||
"year": "2025"
|
||||
},
|
||||
{
|
||||
"BSVersion": "1.40.10",
|
||||
"BSManifest": "6631675572109548834",
|
||||
"OculusBinaryId": "10067683139998041",
|
||||
"ReleaseDate": "1756386614",
|
||||
"year": "2025"
|
||||
}
|
||||
]
|
||||
@@ -161,6 +161,14 @@
|
||||
},
|
||||
{
|
||||
"username": "KingCrocman",
|
||||
"type": "diamond",
|
||||
"link": "https://www.youtube.com/KingCrocman"
|
||||
},
|
||||
{
|
||||
"username": "bosspie",
|
||||
"type": "diamond"
|
||||
},
|
||||
{
|
||||
"username": "Camryn Jackson"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -37,7 +37,10 @@
|
||||
}
|
||||
},
|
||||
"title-bar": {
|
||||
"outdated": "veraltet"
|
||||
"outdated": "veraltet",
|
||||
"update-text": "BSManager {version} ist verfügbar!",
|
||||
"update-button": "Aktualisieren und neu starten",
|
||||
"see-changelog": "Änderungen ansehen"
|
||||
},
|
||||
"nav-bar": {
|
||||
"add-version": "Version hinzufügen",
|
||||
@@ -337,6 +340,13 @@
|
||||
"error-notification": {
|
||||
"message": "Es ist ein Fehler aufgetreten, die Systemproxyeinstellungen können nicht geändert werden."
|
||||
}
|
||||
},
|
||||
"auto-update": {
|
||||
"title": "Automatische Aktualisierung",
|
||||
"description": "BSManager wird beim Start der Anwendung automatisch aktualisiert.",
|
||||
"error-notification": {
|
||||
"message": "Ein Fehler ist aufgetreten, die Einstellungen für die automatische Aktualisierung können nicht geändert werden."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,10 @@
|
||||
}
|
||||
},
|
||||
"title-bar": {
|
||||
"outdated": "outdated"
|
||||
"outdated": "outdated",
|
||||
"update-text": "BSManager {version} is available!",
|
||||
"update-button": "Update and Restart",
|
||||
"see-changelog": "See changelog"
|
||||
},
|
||||
"nav-bar": {
|
||||
"add-version": "Add a version",
|
||||
@@ -337,6 +340,13 @@
|
||||
"error-notification": {
|
||||
"message": "An error occurred, unable to change system proxy settings."
|
||||
}
|
||||
},
|
||||
"auto-update": {
|
||||
"title": "Auto Update",
|
||||
"description": "BSManager will automatically update when you launch the application.",
|
||||
"error-notification": {
|
||||
"message": "An error occurred, unable to change auto update settings."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,10 @@
|
||||
}
|
||||
},
|
||||
"title-bar": {
|
||||
"outdated": "obsoleto"
|
||||
"outdated": "obsoleto",
|
||||
"update-text": "¡BSManager {version} está disponible!",
|
||||
"update-button": "Actualizar y reiniciar",
|
||||
"see-changelog": "Ver los cambios"
|
||||
},
|
||||
"nav-bar": {
|
||||
"add-version": "Agregar una versión",
|
||||
@@ -337,6 +340,13 @@
|
||||
"error-notification": {
|
||||
"message": "Ocurrió un error, no se pueden cambiar los ajustes del proxy del sistema."
|
||||
}
|
||||
},
|
||||
"auto-update": {
|
||||
"title": "Actualización automática",
|
||||
"description": "BSManager se actualizará automáticamente al iniciar la aplicación.",
|
||||
"error-notification": {
|
||||
"message": "Ocurrió un error, no se pudo cambiar la configuración de actualización automática."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+110
-100
@@ -33,11 +33,14 @@
|
||||
},
|
||||
"generic": {
|
||||
"env": {
|
||||
"parse": "Impossible d'analyser correctement la chaîne de variable d'environnement."
|
||||
"parse": "Impossible de lire les variables d'environnement."
|
||||
}
|
||||
},
|
||||
"title-bar": {
|
||||
"outdated": "obsolète"
|
||||
"outdated": "obsolète",
|
||||
"update-text": "BSManager {version} est disponible!",
|
||||
"update-button": "Mettre à jour et redémarrer",
|
||||
"see-changelog": "Voir les changements"
|
||||
},
|
||||
"nav-bar": {
|
||||
"add-version": "Ajouter une version",
|
||||
@@ -51,23 +54,23 @@
|
||||
"version-viewer": {
|
||||
"launch-mods": {
|
||||
"oculus": "Mode Oculus",
|
||||
"oculus-description": "Si vous utilisez Beat Saber via Steam, cela vous permet d'utiliser le compositeur VR d'Oculus (sans passer par SteamVR pour gagner en performances). Ceci n'est pas nécessaire pour utiliser les casques Oculus.",
|
||||
"oculus-description": "Si vous utilisez Beat Saber via Steam, cela permet d'utiliser l'environnement Oculus sans passer par SteamVR, pour un gain en performances potentiel. Ceci n'est pas obligatoire pour utiliser les casques Oculus.",
|
||||
"desktop": "Mode FPFC",
|
||||
"desktop-description": "Cela vous permet d'utiliser WASD et la souris pour naviguer dans le menu en jeu. Cela rend les tests beaucoup plus faciles, car vous n'avez pas à mettre votre casque!",
|
||||
"desktop-description": "Ce mode vous permet d'utiliser votre clavier (WASD) et votre souris pour naviguer en jeu. Cela rend les tests beaucoup plus faciles, car vous n'avez pas à mettre de casque VR !",
|
||||
"debug": "Mode Debug",
|
||||
"debug-description": "Active la fenêtre de log pour IPA. Cela affichera la console de débogage utilisée par les mods.",
|
||||
"outdated-tippy": "Cette version est obsolète et certains mods ou fonctionnalités peuvent ne plus fonctionner comme prévu. Préférez utiliser la version recommandée ({recommendedVersion}) de Beat Saber pour profiter des dernières fonctionnalités et correctifs.",
|
||||
"advanced-launch": {
|
||||
"button": "Options de lancement",
|
||||
"placeholder": "Options de lancement ex: KEY=VALUE %command% fpfc",
|
||||
"placeholder": "Options de lancement. Exemple : KEY=VALUE %command% fpfc",
|
||||
"create-launch-option": "Créer une option de lancement"
|
||||
},
|
||||
"skipsteam": "Ignorer Steam",
|
||||
"skipsteam-description": "Empêche Steam de s'ouvrir automatiquement avec Beat Saber, activez-le si vous utilisez un autre runtime VR comme WiVRn ou Monado avec lequel SteamVR pourrait interférer.",
|
||||
"skipsteam-description": "Empêche Steam de s'ouvrir automatiquement avec Beat Saber, activez-le si vous utilisez un autre environnement VR comme WiVRn ou Monado avec lequel SteamVR pourrait interférer.",
|
||||
"map-editor": "Éditeur de map",
|
||||
"map-editor-description": "Lance l'éditeur officiel de map de Beat Saber au lieu du jeu.",
|
||||
"proton-logs": "Journaux de Proton",
|
||||
"proton-logs-description": "Active l'enregistrement des journaux Proton pour cette installation de Beat Saber sur \"{versionPath}\"."
|
||||
"proton-logs": "Logs de Proton",
|
||||
"proton-logs-description": "Active l'enregistrement des logs Proton pour cette installation de Beat Saber sur \"{versionPath}\"."
|
||||
},
|
||||
"maps": {
|
||||
"search-bar": {
|
||||
@@ -104,11 +107,11 @@
|
||||
"sort": {
|
||||
"name": "Nom",
|
||||
"song-author": "Auteur de la chanson",
|
||||
"map-author": "Auteur de la carte",
|
||||
"map-author": "Auteur de la map",
|
||||
"bpm": "BPM",
|
||||
"duration": "Durée",
|
||||
"likes": "J'aime",
|
||||
"date-uploaded": "Date de téléchargement"
|
||||
"date-uploaded": "Date de publication"
|
||||
}
|
||||
},
|
||||
"playlists": {
|
||||
@@ -124,7 +127,7 @@
|
||||
"sort": {
|
||||
"title": "Titre",
|
||||
"author": "Auteur",
|
||||
"number-of-maps": "Nombre de cartes",
|
||||
"number-of-maps": "Nombre de maps",
|
||||
"duration": "Durée",
|
||||
"notes-per-second": "NPS"
|
||||
}
|
||||
@@ -136,8 +139,8 @@
|
||||
"no-internet": "Pas d'accès Internet",
|
||||
"mods-not-available": "Aucun mod n'est encore disponible pour cette version de Beat Saber",
|
||||
"status": {
|
||||
"no-wineprefix": "Impossible de trouver le chemin WINEPREFIX de BSManager. Veuillez d'abord lancer Beat Saber dans BSManager.",
|
||||
"beatmods-down": "Beatmods est actuellement inaccessible. Veuillez réessayer plus tard. Si le problème persiste, informez-nous sur {links}.",
|
||||
"no-wineprefix": "Impossible de trouver le chemin WINEPREFIX de BSManager. Veuillez d'abord lancer Beat Saber depuis BSManager.",
|
||||
"beatmods-down": "BeatMods est actuellement inaccessible. Veuillez réessayer plus tard. Si le problème persiste, informez-nous sur {links}.",
|
||||
"unknown": "Une erreur inconnue s'est produite ¯\\_(ツ)_/¯"
|
||||
},
|
||||
"buttons": {
|
||||
@@ -149,8 +152,8 @@
|
||||
"header-bar": {
|
||||
"name": "Nom",
|
||||
"size": "Taille",
|
||||
"installed": "Installé",
|
||||
"latest": "Récent",
|
||||
"installed": "Installée",
|
||||
"latest": "Dernière",
|
||||
"description": "Description",
|
||||
"dropdown": {
|
||||
"import-mods": "Importer des mods",
|
||||
@@ -165,8 +168,8 @@
|
||||
},
|
||||
"notifications": {
|
||||
"all-mods-already-installed": {
|
||||
"title": "Mods déjà installées",
|
||||
"description": "Tous les mods séléctionnées sont déjà installées"
|
||||
"title": "Mods déjà installés",
|
||||
"description": "Tous les mods sélectionnés sont déjà installés"
|
||||
},
|
||||
"outdated-mods": {
|
||||
"title": "Mod obsolète",
|
||||
@@ -206,17 +209,17 @@
|
||||
"steam-and-oculus": {
|
||||
"title": "Steam & Oculus",
|
||||
"description": "Te déconnecter te permettra de changer de compte au prochain téléchargement de Beat Saber.",
|
||||
"logout": "Déconnexion",
|
||||
"logout": "Se déconnecter",
|
||||
"logout-success": "Déconnexion réussie",
|
||||
"download-platform": {
|
||||
"title": "Plateforme par défaut",
|
||||
"desc": "Choisi la plateforme par défaut qui sera utilisée pour télécharger les versions de Beat Saber.",
|
||||
"desc": "Choisissez la plateforme par défaut qui sera utilisée pour télécharger les versions de Beat Saber.",
|
||||
"always-ask": "Toujours demander"
|
||||
}
|
||||
},
|
||||
"appearance": {
|
||||
"title": "Apparence",
|
||||
"description": "Choisis les deux couleurs principales de BSManager.",
|
||||
"description": "Choisissez les deux couleurs principales de BSManager.",
|
||||
"reset": "Réinitialiser",
|
||||
"sub-title": "Thème",
|
||||
"themes": {
|
||||
@@ -227,7 +230,7 @@
|
||||
},
|
||||
"installation-folder": {
|
||||
"title": "Dossier d'installation",
|
||||
"description": "Changer le dossier qui contiendra tout le contenu téléchargé par BSManager."
|
||||
"description": "Changez le dossier qui contiendra tout le contenu téléchargé par BSManager."
|
||||
},
|
||||
"proton-folder": {
|
||||
"title": "Dossier Proton",
|
||||
@@ -245,8 +248,8 @@
|
||||
}
|
||||
},
|
||||
"language": {
|
||||
"title": "Langage",
|
||||
"description": "Sélectionne un langage.",
|
||||
"title": "Langue",
|
||||
"description": "Sélectionnez une langue.",
|
||||
"languages": {
|
||||
"en-EN": "English, UK",
|
||||
"en-US": "English, US",
|
||||
@@ -309,7 +312,7 @@
|
||||
"description": "Paramètres avancés pour BSManager.",
|
||||
"hardware-acceleration": {
|
||||
"title": "Accélération matérielle",
|
||||
"description": "Activez l'accélération matérielle pour utiliser votre GPU et améliorer les performances de BSManager. Désactivez cette option si vous rencontrez des chutes d'IPS.",
|
||||
"description": "Activez l'accélération matérielle pour utiliser votre carte graphique et améliorer les performances de BSManager. Désactivez cette option si vous rencontrez des problèmes de performance.",
|
||||
"modal": {
|
||||
"title": "Redémarrage nécessaire",
|
||||
"body": "Changer le paramètre d'accélération matérielle va quitter et relancer BSManager. Êtes-vous sûr de vouloir continuer ?",
|
||||
@@ -337,6 +340,13 @@
|
||||
"error-notification": {
|
||||
"message": "Une erreur est survenue, impossible de modifier les paramètres du proxy système."
|
||||
}
|
||||
},
|
||||
"auto-update": {
|
||||
"title": "Mise à jour automatique",
|
||||
"description": "BSManager se mettra automatiquement à jour au lancement de l'application.",
|
||||
"error-notification": {
|
||||
"message": "Une erreur est survenue, impossible de modifier les paramètres de mise à jour automatique."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -360,8 +370,8 @@
|
||||
"file-not-supported": "Fichier non supporté"
|
||||
},
|
||||
"msg": {
|
||||
"operation-running": "Attends la fin de l'opération en cours puis recommence.",
|
||||
"no-internet": "Vérifie ta connexion internet et ressaye.",
|
||||
"operation-running": "Veuillez attendre la fin de l'opération en cours puis recommencez.",
|
||||
"no-internet": "Vérifiez votre connexion Internet et réessayez.",
|
||||
"file-not-supported": "Seuls les fichiers {types} sont pris en charge."
|
||||
}
|
||||
}
|
||||
@@ -377,9 +387,9 @@
|
||||
"warnings": {
|
||||
"msg": {
|
||||
"ManifestChecksum": "Le manifeste précédemment téléchargé ne correspond pas au nouveau 🤔",
|
||||
"ConnectionTimeout": "Ta connexion internet semble instable 🥶",
|
||||
"ConnectionLost": "La connexion a été perdue, essayez à nouveau...",
|
||||
"ConnectionError": "Impossible de se connecter à Steam, essayez à nouveau...",
|
||||
"ConnectionTimeout": "Votre connexion internet semble instable 🥶",
|
||||
"ConnectionLost": "La connexion a été perdue, réessayez...",
|
||||
"ConnectionError": "Impossible de se connecter à Steam, réessayez...",
|
||||
"Unknown": "Quelque chose d'étrange s'est produit 🤔 Votre connexion est probablement instable."
|
||||
}
|
||||
},
|
||||
@@ -387,26 +397,26 @@
|
||||
"msg": {
|
||||
"401": "Steam ne semble pas vouloir nous laisser télécharger Beat Saber 😢",
|
||||
"404": "Impossible de contacter les serveurs de Steam.",
|
||||
"ExeNotFoundWindows": "\"DepotDownloader.exe\" est manquant. Veuillez vérifier si l'exécutable est mis en quarantaine par votre antivirus.",
|
||||
"ExeNotFoundLinux": "L'exécutable \"DepotDownloader\" est manquanto.",
|
||||
"ExeNotFoundWindows": "\"DepotDownloader.exe\" est manquant. Vérifiez que votre antivirus n'a pas mis le fichier en quarantaine.",
|
||||
"ExeNotFoundLinux": "L'exécutable \"DepotDownloader\" est manquant.",
|
||||
"Password": "Le mot de passe est invalide.",
|
||||
"InvalidCredentials": "Identifiants de connexion invalides, connexion non approuvée, ou trop de tentatives de connexion.",
|
||||
"NoManifest": "Aucun manifest n'a été trouvé.",
|
||||
"NoManifest": "Aucun manifeste n'a été trouvé.",
|
||||
"DirectoryCreate": "Impossible d'installer les dossiers nécessaires.",
|
||||
"NotAvailableApp": "Tu essayes de télécharger Beat Saber alors que tu ne l'as pas ? 🤣",
|
||||
"DepotNotFound": "Impossible de télécharger Beat Saber 😥 réessaye plus tard 😕",
|
||||
"DepotNotFound": "Impossible de télécharger Beat Saber 😥 réessayez plus tard 😕",
|
||||
"NotCompleted": "Le téléchargement n'a pas pu se terminer ¯\\_(ツ)_/¯",
|
||||
"InvalidManifest": "Impossible de télécharger Beat Saber 😥 réessaye plus tard 😕",
|
||||
"NoValidKey": "Impossible de télécharger Beat Saber 😥 réessaye plus tard 😕",
|
||||
"NoManifestCode": "Impossible de télécharger Beat Saber 😥 réessaye plus tard 😕",
|
||||
"InvalidManifest": "Impossible de télécharger Beat Saber 😥 réessayez plus tard 😕",
|
||||
"NoValidKey": "Impossible de télécharger Beat Saber 😥 réessayez plus tard 😕",
|
||||
"NoManifestCode": "Impossible de télécharger Beat Saber 😥 réessayez plus tard 😕",
|
||||
"Unknown": "Une erreur inconnue s'est produite ¯\\_(ツ)_/¯",
|
||||
"NoServer": "Impossible de contacter les serveurs de Steam.",
|
||||
"NotAllowed": "Apparemment tu n'es pas autorisé à télécharger Beat Saber 🥱",
|
||||
"ConnectionTimeout": "Impossible de se connecter à Steam 😕",
|
||||
"SteamLib": "Si tu as cette erreur, signale le bug sur GitHub avec les logs stp.",
|
||||
"SteamLib": "Si tu vois cette erreur, signale le bug sur GitHub avec les logs de BSManager.",
|
||||
"ConnectionError": "Impossible de se connecter à Steam après 10 essais 🤯",
|
||||
"LicenceError": "Impossible d'obtenir la liste des licences.",
|
||||
"RateLimitExceeded": "Tu as essayé trop de fois attends un peu et recommence plus tard.",
|
||||
"RateLimitExceeded": "Tu as essayé trop de fois, attends un peu et réessaye plus tard.",
|
||||
"TokenRejected": "Votre token de connexion a été rejeté 😕 Veuillez réessayer.",
|
||||
"AccessDenied": "L'accès à Steam a été refusé."
|
||||
}
|
||||
@@ -415,14 +425,14 @@
|
||||
"oculus-download": {
|
||||
"errors": {
|
||||
"msg": {
|
||||
"DOWNLOAD_MANIFEST_FAILED": "Impossible de télécharger le manifest de cette version. Votre token de connexion est peut-être invalide, ou vous ne possédez pas la version PC de Beat Saber.",
|
||||
"MANIFEST_FILE_NOT_FOUND": "Impossible de trouver le manifest de cette version.",
|
||||
"PARSE_MANIFEST_FILE_FAILED": "Une erreur c'est produite lors de la lecture du manifest.",
|
||||
"DOWNLOAD_MANIFEST_FAILED": "Impossible de télécharger le manifeste de cette version. Votre token de connexion est peut-être invalide, ou vous ne possédez pas la version PC de Beat Saber.",
|
||||
"MANIFEST_FILE_NOT_FOUND": "Impossible de trouver le manifeste de cette version.",
|
||||
"PARSE_MANIFEST_FILE_FAILED": "Une erreur s'est produite lors de la lecture du manifeste.",
|
||||
"ALREADY_DOWNLOADING": "Une version est déjà en cours de téléchargement.",
|
||||
"UNABLE_TO_GET_MANIFEST": "Impossible d'obtenir le manifest nécessaire au téléchargement. Votre token de connexion est peut-être invalide, ou vous ne possédez pas la version PC de Beat Saber.",
|
||||
"VERIFY_INTEGRITY_FAILED": "Une erreur c'est produite lors de la vérification des fichiers.",
|
||||
"UNABLE_TO_GET_MANIFEST": "Impossible d'obtenir le manifeste nécessaire au téléchargement. Votre token de connexion est peut-être invalide, ou vous ne possédez pas la version PC de Beat Saber.",
|
||||
"VERIFY_INTEGRITY_FAILED": "Une erreur s'est produite lors de la vérification des fichiers.",
|
||||
"SOME_FILES_FAILED_TO_DOWNLOAD": "Certains fichiers n'ont pas pu être téléchargés.",
|
||||
"META_LOGIN_TIMED_OUT": "Le Token de connexion à mis trop de temps à être récupéré.",
|
||||
"META_LOGIN_TIMED_OUT": "Le token de connexion à mis trop de temps à être récupéré.",
|
||||
"META_LOGIN_WINDOW_CLOSED_BY_USER": "La fenêtre de connexion à Meta a été fermée.",
|
||||
"NO_META_AUTH_TOKEN": "Impossible de récupérer le token de connexion à Meta nécessaire au téléchargement.",
|
||||
"UNKNOWN_ERROR": "Une erreur inconnue s'est produite."
|
||||
@@ -445,7 +455,7 @@
|
||||
},
|
||||
"errors": {
|
||||
"import-error": {
|
||||
"desc": "Vérifier que le dossier sélectionné est une installation de Beat Saber."
|
||||
"desc": "Vérifie que le dossier sélectionné est une installation de Beat Saber."
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -499,7 +509,7 @@
|
||||
}
|
||||
},
|
||||
"check-all-enabled": {
|
||||
"title": "OneClick désactivée(s)",
|
||||
"title": "OneClick désactivé",
|
||||
"description": "Une ou plusieurs installations OneClick sont désactivées. Rendez-vous dans les paramètres pour les activer.",
|
||||
"actions": {
|
||||
"settings": "Paramètres",
|
||||
@@ -514,12 +524,12 @@
|
||||
"titles": {
|
||||
"BS_LAUNCHING": "Lancement...🚀",
|
||||
"STEAM_LAUNCHING": "Steam se lance !",
|
||||
"SKIPPING_STEAM_LAUNCH": "Sauter le lancement de Steam"
|
||||
"SKIPPING_STEAM_LAUNCH": "Contourner le lancement de Steam"
|
||||
},
|
||||
"msg": {
|
||||
"BS_LAUNCHING": "N'oublie pas de t'échauffer 😉",
|
||||
"STEAM_LAUNCHING": "Beat Saber se lancera automatiquement après Steam.",
|
||||
"SKIPPING_STEAM_LAUNCH": "J'espère que vous savez ce que vous faites :)"
|
||||
"SKIPPING_STEAM_LAUNCH": "J'espère que tu sais ce que tu fais :)"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
@@ -568,7 +578,7 @@
|
||||
"UnknownError": "Une erreur inconnue s'est produite"
|
||||
},
|
||||
"msg": {
|
||||
"CantEditSteam": "Tu ne peux pas modifier la version Steam, cependant tu peux la cloner."
|
||||
"CantEditSteam": "Vous ne pouvez pas modifier la version Steam, vous pouvez cependant la cloner."
|
||||
}
|
||||
},
|
||||
"success": {
|
||||
@@ -632,8 +642,8 @@
|
||||
"error": "Une erreur s'est produite lors de l'installation de la map"
|
||||
},
|
||||
"no-duplicates-maps": {
|
||||
"title": "Pas de doublons",
|
||||
"msg": "Aucune carte n'a été supprimée"
|
||||
"title": "Aucun doublon",
|
||||
"msg": "Aucune map n'a été supprimée"
|
||||
},
|
||||
"duplicates-maps-deleted": {
|
||||
"title": "Doublons supprimés",
|
||||
@@ -669,7 +679,7 @@
|
||||
"info": {
|
||||
"userdata-backup-created": {
|
||||
"title": "Sauvegarde créée",
|
||||
"msg": "Le partage du dossier 'UserData' peut générer des erreurs, en cas de soucis déliez le dossier pour restaurer la sauvegarde"
|
||||
"msg": "Le partage du dossier 'UserData' peut générer des erreurs. En cas de soucis, déliez le dossier pour restaurer la sauvegarde"
|
||||
}
|
||||
},
|
||||
"linking-error": {
|
||||
@@ -701,7 +711,7 @@
|
||||
"title": "Version obsolète",
|
||||
"msg": "Cette version de Beat Saber est obsolète. Utilisez la version recommandée pour profiter des dernières fonctionnalités et correctifs.",
|
||||
"actions": {
|
||||
"do-not-remind": "Ne plus me rappeler",
|
||||
"do-not-remind": "Ne plus me le rappeler",
|
||||
"ok": "Ok"
|
||||
}
|
||||
},
|
||||
@@ -754,7 +764,7 @@
|
||||
"note": {
|
||||
"use-the": "Utilisez l'",
|
||||
"steam-mobile-app": "application mobile Steam",
|
||||
"to-connect-with-qr": "pour vous connecter avec un QR code."
|
||||
"to-connect-with-qr": "pour vous connecter avec un code QR."
|
||||
}
|
||||
},
|
||||
"stay": "Se souvenir de moi"
|
||||
@@ -774,20 +784,20 @@
|
||||
"steam-credentials": {
|
||||
"title": "Steam Credentials",
|
||||
"p-1": "Les informations d'identification ne sont utilisées que pour télécharger le jeu, car Steam doit vérifier que vous avez payé le jeu afin d'être autorisé à le télécharger. Ils ne sont pas sauvegardés et transmis directement à DepotDownloader. Si vous ne voulez pas entrer vos informations d'identification, vous pouvez suivre ce tutoriel :",
|
||||
"p-2": "puis cliquez sur l'icône de l'engrenage dans le coin supérieur droit et sélectionnez \"Importer une version\", sélectionnez le dossier où beat saber a été téléchargé (Si vous suivez le tutoriel ci-dessus, vous devriez avoir le bon emplacement)."
|
||||
"p-2": "puis cliquez sur l'icône de l'engrenage dans le coin supérieur droit et sélectionnez \"Importer une version\", vous pouvez ensuite sélectionner le dossier où Beat Saber a été téléchargé (Si vous suivez le tutoriel ci-dessus, vous devriez avoir le bon emplacement)."
|
||||
},
|
||||
"bs-import-version": {
|
||||
"title": "Importer une version",
|
||||
"description": "Importe une version de Beat Saber pour profiter des fonctionnalités de BSManager. L'importation copiera le dossier d'installation de Beat Saber sélectionné dans le dossier de versions de BSManager.",
|
||||
"oculus-version": "Version Oculus",
|
||||
"oculus-version-tooltip": "Cocher si c'est une version Oculus",
|
||||
"oculus-version-tooltip": "Cochez si l'installation provient du magasin Oculus",
|
||||
"buttons": {
|
||||
"submit": "Importer une version"
|
||||
}
|
||||
},
|
||||
"bs-uninstall": {
|
||||
"title": "Désinstaller",
|
||||
"description": "Es-tu sûr de vouloir désinstaller Beat Saber {version} ? Tu vas devoir la retélécharger pour y jouer.",
|
||||
"description": "Êtes-vous sûr de vouloir désinstaller Beat Saber {version} ? Vous allez devoir la retélécharger pour y jouer.",
|
||||
"buttons": {
|
||||
"submit": "Désinstaller"
|
||||
}
|
||||
@@ -823,12 +833,12 @@
|
||||
},
|
||||
"uninstall-mod": {
|
||||
"title": "Désinstaller",
|
||||
"description": "Es-tu sûr de vouloir désinstaller {mod} ? Cela pourrait faire dysfonctionner d'autres mods installés.",
|
||||
"description-bsipa": "Es-tu sûr de vouloir désinstaller BSIPA ? Après cela, tous les mods installés ne fonctionneront plus."
|
||||
"description": "Êtes-vous sûr de vouloir désinstaller {mod} ? Cela pourrait faire dysfonctionner d'autres mods installés.",
|
||||
"description-bsipa": "Êtes-vous sûr de vouloir désinstaller BSIPA ? Après cela, tous les mods installés ne fonctionneront plus."
|
||||
},
|
||||
"uninstall-all-mods": {
|
||||
"title": "Désinstaller les mods",
|
||||
"description": "Es-tu sûr de vouloir désinstaller tous les mods de la version {version} ? Cette opération ne pourra pas être annulée."
|
||||
"description": "Êtes-vous sûr de vouloir désinstaller tous les mods de la version {version} ? Cette opération ne pourra pas être annulée."
|
||||
},
|
||||
"maps-actions": {
|
||||
"delete-maps": {
|
||||
@@ -837,8 +847,8 @@
|
||||
"multiple": "Supprimer les maps ?"
|
||||
},
|
||||
"desc": {
|
||||
"single": "Es-tu sur de vouloir supprimer la map {name} ?",
|
||||
"multiple": "Es-tu sur de vouloir supprimer {nb} maps ?"
|
||||
"single": "Êtes-vous sûr de vouloir supprimer la map {name} ?",
|
||||
"multiple": "Êtes-vous sûr de vouloir supprimer {nb} maps ?"
|
||||
},
|
||||
"info": {
|
||||
"desc": {
|
||||
@@ -853,8 +863,8 @@
|
||||
},
|
||||
"delete-duplicate-maps": {
|
||||
"title": "Supprimer les maps ?",
|
||||
"desc": "Seule la map \"{map}\" est en double. Es-tu sûr de vouloir la supprimer ?",
|
||||
"desc-plural": "{nb} maps en double ont été trouvées. Es-tu sûr de vouloir les supprimer ?"
|
||||
"desc": "Seule la map \"{map}\" est un doublon. Êtes-vous sûr de vouloir la supprimer ?",
|
||||
"desc-plural": "{nb} maps en double ont été trouvées. Êtes-vous sûr de vouloir les supprimer ?"
|
||||
}
|
||||
},
|
||||
"link-contents": {
|
||||
@@ -878,7 +888,7 @@
|
||||
"search-btn": "Rechercher",
|
||||
"loading-maps": "Chargement des maps...",
|
||||
"no-maps-found": "Aucune map trouvée",
|
||||
"no-internet": "Pas d'internet"
|
||||
"no-internet": "Pas d'accès Internet"
|
||||
},
|
||||
"mods-disclaimer": {
|
||||
"title": "avis de non-responsabilité",
|
||||
@@ -941,7 +951,7 @@
|
||||
"token-is-invalid": "Le Token est invalide.",
|
||||
"save-my-token": "Sauvegarder mon token",
|
||||
"have-token-saved": "J'ai déjà un token sauvegardé",
|
||||
"save-token-info": "Ceci enregistrera votre token pour le réutiliser plus facilement. Vous devrez créer un mot de passe pour crypter votre token à des fins de stockage. Si jamais vous oubliez votre mot de passe, il vous suffit de fournir à nouveau votre token.",
|
||||
"save-token-info": "Ceci enregistrera votre token pour le réutiliser plus facilement. Vous devrez créer un mot de passe pour chiffrer votre token à des fins de stockage. Si jamais vous oubliez votre mot de passe, il vous suffira de fournir à nouveau votre token.",
|
||||
"password": "Mot de passe",
|
||||
"password-too-short": "Mot de passe trop court",
|
||||
"info-enter-password": "Pour télécharger Beat Saber, votre token de connexion à Oculus est nécessaire. Entrez le mot de passe utilisé pour sauvegarder votre token Oculus.",
|
||||
@@ -962,7 +972,7 @@
|
||||
},
|
||||
"ask-install-path": {
|
||||
"title": "Dossier d'installation",
|
||||
"choose-folder-description": "Choisissez le dossier qui contiendra tout le contenu téléchargé par BSManager. (versions, mods, cartes, playlists, etc.)",
|
||||
"choose-folder-description": "Choisissez le dossier qui contiendra tout le contenu téléchargé par BSManager. (versions, mods, maps, playlists, etc.)",
|
||||
"default": "Par défaut",
|
||||
"default-tooltip": "Par défaut, dans votre dossier personnel"
|
||||
},
|
||||
@@ -987,13 +997,13 @@
|
||||
"maps": {
|
||||
"map-filter-panel": {
|
||||
"duration": "Durée",
|
||||
"nps": "Notes Par Seconde",
|
||||
"nps": "Notes par seconde",
|
||||
"njs": "Vitesse de saut des notes",
|
||||
"tags": "tags",
|
||||
"specificities": "général",
|
||||
"requirements": "requis",
|
||||
"exclude": "exclure",
|
||||
"leaderboard": "tableau des leaders"
|
||||
"leaderboard": "classement"
|
||||
},
|
||||
"map-types": {
|
||||
"accuracy": "précision",
|
||||
@@ -1046,11 +1056,11 @@
|
||||
"automapper": "IA",
|
||||
"curated": "recommandée",
|
||||
"verified": "vérifiée",
|
||||
"fullSpread": "panel complet"
|
||||
"fullSpread": "toutes difficultés"
|
||||
},
|
||||
"map-leaderboard": {
|
||||
"All": "Tout",
|
||||
"Ranked": "Classée",
|
||||
"Ranked": "Classé",
|
||||
"BeatLeader": "BeatLeader",
|
||||
"ScoreSaber": "ScoreSaber"
|
||||
},
|
||||
@@ -1068,10 +1078,10 @@
|
||||
"by": "Par {songAutor}",
|
||||
"mapped-by": "mappée par",
|
||||
"delete": "Supprimer",
|
||||
"preview": "Aperçu de la carte",
|
||||
"preview": "Aperçu de la map",
|
||||
"bsr-code": "Code BSR",
|
||||
"download": "Télécharger la carte",
|
||||
"downloading": "Téléchargement de la carte",
|
||||
"download": "Télécharger la map",
|
||||
"downloading": "Téléchargement de la map",
|
||||
"cancel-download": "Annuler le téléchargement",
|
||||
"hightlight-difficulty": "Surligner la difficulté"
|
||||
}
|
||||
@@ -1115,12 +1125,12 @@
|
||||
"modals": {
|
||||
"delete-model": {
|
||||
"title": "Supprimer le modèle",
|
||||
"desc": "Es-tu sûr de vouloir supprimer le modèle {modelName} ?",
|
||||
"desc": "Êtes-vous sûr de vouloir supprimer le modèle {modelName} ?",
|
||||
"linked-annotation": "Ce modèle sera supprimé de toutes les versions liées."
|
||||
},
|
||||
"delete-models": {
|
||||
"title": "Supprimer les modèles",
|
||||
"desc": "Es-tu sûr de vouloir supprimer {nb} modèles ?",
|
||||
"desc": "Êtes-vous sûr de vouloir supprimer {nb} modèles ?",
|
||||
"linked-annotation": "Ces modèles seront supprimés de toutes les versions liées."
|
||||
},
|
||||
"download-models": {
|
||||
@@ -1136,11 +1146,11 @@
|
||||
"tag-desc": "Afficher seulement les modèles avec le tag spécifié.",
|
||||
"name-desc": "Afficher seulement les modèles avec le nom spécifié.",
|
||||
"discordid-desc": "Afficher seulement les modèles de l'utilisateur discord spécifié.",
|
||||
"status-desc": "Afficher seulement les modèles avec le status spécifié. (profil seulement, et seulement pour l'auteur)"
|
||||
"status-desc": "Afficher seulement les modèles avec le statut spécifié. (profil seulement, et seulement pour l'auteur)"
|
||||
},
|
||||
"no-models": "Aucun modèles trouvés.",
|
||||
"no-models": "Aucun modèle trouvé.",
|
||||
"no-internet": "Pas de connexion internet.",
|
||||
"error-occured": "Une erreur est survenue, réessaye plus tard."
|
||||
"error-occured": "Une erreur est survenue, réessayez plus tard."
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
@@ -1151,16 +1161,16 @@
|
||||
"not-remind": "Ne plus me rappeler"
|
||||
},
|
||||
"export-success": {
|
||||
"title": "Export terminé 🎉"
|
||||
"title": "Exportation terminée 🎉"
|
||||
}
|
||||
}
|
||||
},
|
||||
"beat-saver": {
|
||||
"maps-sorts": {
|
||||
"Latest": "Dernière",
|
||||
"Latest": "Récent",
|
||||
"Relevance": "Pertinence",
|
||||
"Rating": "Notes",
|
||||
"Curated": "Recommandée"
|
||||
"Rating": "Évaluation",
|
||||
"Curated": "Recommandé"
|
||||
}
|
||||
},
|
||||
"auto-update": {
|
||||
@@ -1188,7 +1198,7 @@
|
||||
"error-playlist-creation-title": "Erreur lors de la création de la playlist",
|
||||
"error-playlist-creation-desc": "Une erreur est survenue lors de la création de la playlist.",
|
||||
"playlist-created-title": "Playlist créée",
|
||||
"playlist-created-desc": "La playlist a été créée avec succès. Tu peut maintenant synchroniser ses maps !",
|
||||
"playlist-created-desc": "La playlist a été créée avec succès. Vous pouvez maintenant synchroniser ses maps !",
|
||||
"download-playlist": "Télécharger la playlist",
|
||||
"synchronize-playlist": "Synchroniser la playlist",
|
||||
"synchronize-maps": "Synchroniser les maps",
|
||||
@@ -1209,8 +1219,8 @@
|
||||
"playlist-edit-error-title": "Erreur lors de l'édition de la playlist",
|
||||
"playlist-edit-error-desc": "Une erreur est survenue lors de l'édition de la playlist.",
|
||||
"playlist-edited-title": "Playlist éditée !",
|
||||
"playlist-edited-desc": "La playlist a été modifiée avec succès. Tu peut maintenant synchroniser ses maps !",
|
||||
"playlists-loading": "Chargement des playlist...",
|
||||
"playlist-edited-desc": "La playlist a été modifiée avec succès. Vous pouvez maintenant synchroniser ses maps !",
|
||||
"playlists-loading": "Chargement des playlists...",
|
||||
"no-playlists": "Aucune playlist",
|
||||
"download-playlists": "Télécharger des playlists",
|
||||
"created-by": "Créée par",
|
||||
@@ -1219,15 +1229,15 @@
|
||||
"open-file": "Ouvrir le fichier",
|
||||
"delete-playlist-ask": "Supprimer la playlist ?",
|
||||
"delete-playlists-ask": "Supprimer les playlists ?",
|
||||
"delete-playlist-desc": "Es-tu sur de vouloir supprimer la playlist \"{playlistTitle}\" ?",
|
||||
"delete-playlists-desc": "Es-tu sur de vouloir supprimer {nb} playlists ?",
|
||||
"delete-playlist-desc": "Êtes-vous sûr de vouloir supprimer la playlist \"{playlistTitle}\" ?",
|
||||
"delete-playlists-desc": "Êtes-vous sûr de vouloir supprimer {nb} playlists ?",
|
||||
"delete-maps": "Supprimer les maps",
|
||||
"delete-playlist-maps-tip": "Si activé, toutes les maps de la playlist seront supprimées",
|
||||
"delete-playlists-maps-tip": "Si activé, toutes les maps des playlists seront supprimées",
|
||||
"export-playlist-ask": "Exporter la playlist ?",
|
||||
"export-playlists-ask": "Exporter les playlists ?",
|
||||
"export-playlist-desc": "Es-tu sur de vouloir exporter la playlist \"{playlistTitle}\" ?",
|
||||
"export-playlists-desc": "Es-tu sur de vouloir exporter les {nb} playlists ?",
|
||||
"export-playlist-desc": "Êtes-vous sûr de vouloir exporter la playlist \"{playlistTitle}\" ?",
|
||||
"export-playlists-desc": "Êtes-vous sûr de vouloir exporter {nb} playlists ?",
|
||||
"export-maps": "Exporter les maps",
|
||||
"export-playlist-maps-tip": "Si activé, toutes les maps de la playlist seront également exportées",
|
||||
"export-playlists-maps-tip": "Si activé, toutes les maps des playlists seront également exportées",
|
||||
@@ -1239,15 +1249,15 @@
|
||||
"understood": "J'ai compris",
|
||||
"synchronize-playlist-ask": "Synchroniser la playlist ?",
|
||||
"synchronize-playlists-ask": "Synchroniser les playlists ?",
|
||||
"synchronize-playlist-desc": "Es-tu sur de vouloir synchroniser la playlist \"{playlistTitle}\" ?",
|
||||
"synchronize-playlists-desc": "Es-tu sur de vouloir synchroniser les {nb} playlists ?",
|
||||
"synchronize-playlist-desc": "Êtes-vous sûr de vouloir synchroniser la playlist \"{playlistTitle}\" ?",
|
||||
"synchronize-playlists-desc": "Êtes-vous sûr de vouloir synchroniser {nb} playlists ?",
|
||||
"synchronize-playlist-tip": "Cette action met à jour les playlists et télécharge les maps manquantes; cela peut durer plusieurs minutes.",
|
||||
"synchronize": "Synchroniser",
|
||||
"curated": "Recommandée",
|
||||
"verified-mapper": "Mapper vérifié",
|
||||
"empty-playlists": "Playlists vides",
|
||||
"search-playlist": "Rechercher une playlist",
|
||||
"no-playlists-found": "Aucune playlists trouvées",
|
||||
"no-playlists-found": "Aucune playlist trouvée",
|
||||
"error-occur-while-loading-playlists": "Une erreur est survenue lors du chargement des playlists",
|
||||
"error-occur-while-loading-playlist": "Une erreur est survenue lors du chargement de la playlist",
|
||||
"loading-maps": "Chargement des maps...",
|
||||
@@ -1274,7 +1284,7 @@
|
||||
"loading": "Chargement...",
|
||||
"installed": "Installée",
|
||||
"no-map-found": "Aucune map trouvée",
|
||||
"edit-playlist-shortcuts": "Maintenez Maj ou Ctrl pour sélectionner plusieurs maps",
|
||||
"edit-playlist-shortcuts": "Maintenir Maj ou Ctrl pour sélectionner plusieurs maps",
|
||||
"add-to-playlist": "Ajouter à la playlist",
|
||||
"remove-from-playlist": "Retirer de la playlist",
|
||||
"playlist-is-empty": "La playlist est vide",
|
||||
@@ -1284,7 +1294,7 @@
|
||||
"duration": "Durée",
|
||||
"nps": "Notes par secondes",
|
||||
"date-picker": {
|
||||
"start-date-end-date": "Date début — Date fin",
|
||||
"start-date-end-date": "Date de début — Date de fin",
|
||||
"all": "Tout",
|
||||
"last-24h": "Dernières 24h",
|
||||
"last-week": "Dernière semaine",
|
||||
@@ -1295,18 +1305,18 @@
|
||||
"all-playlists-have-been-successfully-imported": "Toutes les playlists ont été importées avec succès",
|
||||
"no-playlist-found": "Aucune playlist trouvée",
|
||||
"no-playlist-found-in-selected-files": "Aucune playlist trouvée dans les fichiers sélectionnés",
|
||||
"some-playlists-not-imported": "Certaines playlists non importées",
|
||||
"some-playlists-not-imported": "Certaines playlists n'ont pas été importées",
|
||||
"some-playlists-have-been-imported": {
|
||||
"INVALID_SOURCE": "Certaines playlists n'ont pas pu être trouvées",
|
||||
"INVALID_PLAYLIST_FILE": "Certaines playlists ne sont pas valides",
|
||||
"CANNOT_PARSE_PLAYLIST": "Certaines playlists ne sont pas lisibles",
|
||||
"INVALID_PLAYLIST_FILE": "Certaines playlists sont invalides",
|
||||
"CANNOT_PARSE_PLAYLIST": "Certaines playlists sont illisibles",
|
||||
"unknown": "Certaines playlists n'ont pas pu être importées"
|
||||
},
|
||||
"no-playlists-imported": "Aucune playlist importée",
|
||||
"no-playlists-imported-errors": {
|
||||
"INVALID_SOURCE": "Les playlists n'ont pas été trouvées",
|
||||
"INVALID_PLAYLIST_FILE": "Les playlists ne sont pas valides",
|
||||
"CANNOT_PARSE_PLAYLIST": "Les playlists ne sont pas lisibles",
|
||||
"INVALID_PLAYLIST_FILE": "Les playlists sont invalides",
|
||||
"CANNOT_PARSE_PLAYLIST": "Les playlists sont illisibles",
|
||||
"unknown": "Aucune playlist n'a pu être importée"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -37,7 +37,10 @@
|
||||
}
|
||||
},
|
||||
"title-bar": {
|
||||
"outdated": "obsoleta"
|
||||
"outdated": "obsoleta",
|
||||
"update-text": "BSManager {version} è disponibile!",
|
||||
"update-button": "Aggiorna e riavvia",
|
||||
"see-changelog": "Vedi i cambiamenti"
|
||||
},
|
||||
"nav-bar": {
|
||||
"add-version": "Aggiungi una versione",
|
||||
@@ -337,6 +340,13 @@
|
||||
"error-notification": {
|
||||
"message": "C'è stato un errore, impossibile cambiare le impostazioni del proxy di sistema."
|
||||
}
|
||||
},
|
||||
"auto-update": {
|
||||
"title": "Aggiornamento automatico",
|
||||
"description": "BSManager si aggiornerà automaticamente all'avvio dell'applicazione.",
|
||||
"error-notification": {
|
||||
"message": "Si è verificato un errore, impossibile modificare le impostazioni di aggiornamento automatico."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,10 @@
|
||||
}
|
||||
},
|
||||
"title-bar": {
|
||||
"outdated": "時代遅れ"
|
||||
"outdated": "時代遅れ",
|
||||
"update-text": "BSManager {version} が利用可能です!",
|
||||
"update-button": "更新して再起動",
|
||||
"see-changelog": "変更点を確認"
|
||||
},
|
||||
"nav-bar": {
|
||||
"add-version": "バージョンを追加",
|
||||
@@ -337,6 +340,13 @@
|
||||
"error-notification": {
|
||||
"message": "エラーが発生しました。システムプロキシ設定を変更できません。"
|
||||
}
|
||||
},
|
||||
"auto-update": {
|
||||
"title": "自動更新",
|
||||
"description": "アプリケーション起動時にBSManagerが自動的に更新されます。",
|
||||
"error-notification": {
|
||||
"message": "エラーが発生し、自動更新の設定を変更できませんでした。"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,10 @@
|
||||
}
|
||||
},
|
||||
"title-bar": {
|
||||
"outdated": "구식"
|
||||
"outdated": "구식",
|
||||
"update-text": "BSManager {version} 가 사용 가능합니다!",
|
||||
"update-button": "업데이트 후 재시작",
|
||||
"see-changelog": "변경 사항 보기"
|
||||
},
|
||||
"nav-bar": {
|
||||
"add-version": "버전 추가",
|
||||
@@ -337,6 +340,13 @@
|
||||
"error-notification": {
|
||||
"message": "오류가 발생했습니다. 시스템 프록시 설정을 변경할 수 없습니다."
|
||||
}
|
||||
},
|
||||
"auto-update": {
|
||||
"title": "자동 업데이트",
|
||||
"description": "애플리케이션을 실행할 때 BSManager가 자동으로 업데이트됩니다.",
|
||||
"error-notification": {
|
||||
"message": "오류가 발생하여 자동 업데이트 설정을 변경할 수 없습니다."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,10 @@
|
||||
}
|
||||
},
|
||||
"title-bar": {
|
||||
"outdated": "Desatualizado"
|
||||
"outdated": "Desatualizado",
|
||||
"update-text": "BSManager {version} está disponível!",
|
||||
"update-button": "Atualizar e reiniciar",
|
||||
"see-changelog": "Ver os changelogs"
|
||||
},
|
||||
"nav-bar": {
|
||||
"add-version": "Adicionar uma versão",
|
||||
@@ -337,6 +340,13 @@
|
||||
"error-notification": {
|
||||
"message": "Um erro aconteceu, não foi possível alterar a configuração de proxy de sistema."
|
||||
}
|
||||
},
|
||||
"auto-update": {
|
||||
"title": "Atualização automática",
|
||||
"description": "O BSManager será atualizado automaticamente ao iniciar o aplicativo.",
|
||||
"error-notification": {
|
||||
"message": "Ocorreu um erro, não foi possível alterar as configurações de atualização automática."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,10 @@
|
||||
}
|
||||
},
|
||||
"title-bar": {
|
||||
"outdated": "устаревший"
|
||||
"outdated": "устаревший",
|
||||
"update-text": "BSManager {version} доступен!",
|
||||
"update-button": "Обновить и перезапустить",
|
||||
"see-changelog": "Смотреть изменения"
|
||||
},
|
||||
"nav-bar": {
|
||||
"add-version": "Добавить версию игры",
|
||||
@@ -337,6 +340,13 @@
|
||||
"error-notification": {
|
||||
"message": "Произошла ошибка, не удалось изменить настройки системного прокси."
|
||||
}
|
||||
},
|
||||
"auto-update": {
|
||||
"title": "Автообновление",
|
||||
"description": "BSManager будет автоматически обновляться при запуске приложения.",
|
||||
"error-notification": {
|
||||
"message": "Произошла ошибка, не удалось изменить настройки автообновления."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,10 @@
|
||||
}
|
||||
},
|
||||
"title-bar": {
|
||||
"outdated": "lipas na"
|
||||
"outdated": "lipas na",
|
||||
"update-text": "Ang BSManager {version} ay available!",
|
||||
"update-button": "I-update at i-restart",
|
||||
"see-changelog": "Tingnan ang mga pagbabago"
|
||||
},
|
||||
"nav-bar": {
|
||||
"add-version": "Magdagdag ng bersyon",
|
||||
@@ -337,6 +340,13 @@
|
||||
"error-notification": {
|
||||
"message": "Nagkaroon ng error, hindi ma-bago ang setting ng system proxy."
|
||||
}
|
||||
},
|
||||
"auto-update": {
|
||||
"title": "Awtomatikong Pag-update",
|
||||
"description": "Ang BSManager ay awtomatikong mag-uupdate kapag binuksan mo ang aplikasyon.",
|
||||
"error-notification": {
|
||||
"message": "Nagkaroon ng error, hindi mababago ang mga setting ng awtomatikong pag-update."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,10 @@
|
||||
}
|
||||
},
|
||||
"title-bar": {
|
||||
"outdated": "застарілий"
|
||||
"outdated": "застарілий",
|
||||
"update-text": "BSManager {version} доступний!",
|
||||
"update-button": "Оновити та перезапустити",
|
||||
"see-changelog": "Переглянути зміни"
|
||||
},
|
||||
"nav-bar": {
|
||||
"add-version": "Додати версію",
|
||||
@@ -337,6 +340,13 @@
|
||||
"error-notification": {
|
||||
"message": "Сталася помилка, неможливо змінити налаштування системного проксі."
|
||||
}
|
||||
},
|
||||
"auto-update": {
|
||||
"title": "Автоматичне оновлення (Avtomatychne onovlennya)",
|
||||
"description": "BSManager автоматично оновлюватиметься, коли ви запустите програму.",
|
||||
"error-notification": {
|
||||
"message": "Сталася помилка, неможливо змінити налаштування автоматичного оновлення."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,10 @@
|
||||
}
|
||||
},
|
||||
"title-bar": {
|
||||
"outdated": "過時"
|
||||
"outdated": "過時",
|
||||
"update-text": "BSManager {version} 可用!",
|
||||
"update-button": "更新並重新啟動",
|
||||
"see-changelog": "查看變更"
|
||||
},
|
||||
"nav-bar": {
|
||||
"add-version": "新增版本",
|
||||
@@ -337,6 +340,13 @@
|
||||
"error-notification": {
|
||||
"message": "發生錯誤,無法更改系統代理設置。"
|
||||
}
|
||||
},
|
||||
"auto-update": {
|
||||
"title": "自動更新",
|
||||
"description": "啟動應用程式時,BSManager 將自動更新。",
|
||||
"error-notification": {
|
||||
"message": "發生錯誤,無法更改自動更新設定。"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,10 @@
|
||||
}
|
||||
},
|
||||
"title-bar": {
|
||||
"outdated": "过时"
|
||||
"outdated": "过时",
|
||||
"update-text": "BSManager {version} 可用!",
|
||||
"update-button": "更新并重新启动",
|
||||
"see-changelog": "查看变更"
|
||||
},
|
||||
"nav-bar": {
|
||||
"add-version": "添加版本",
|
||||
@@ -337,6 +340,13 @@
|
||||
"error-notification": {
|
||||
"message": "发生错误,无法启用系统代理。"
|
||||
}
|
||||
},
|
||||
"auto-update": {
|
||||
"title": "自动更新",
|
||||
"description": "启动应用程序时,BSManager 将自动更新。",
|
||||
"error-notification": {
|
||||
"message": "发生错误,无法更改自动更新设置。"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
+6
-5
@@ -30,11 +30,12 @@
|
||||
In the developer tools:
|
||||
|
||||
1. Open the __`Network`__ tab.
|
||||
2. Filter for __`graph`__.
|
||||
3. Click on the first request that appears.
|
||||
4. Open the __`Payload`__ tab.
|
||||
5. Scroll to the bottom to locate your token. It should start with __`FRL`__.
|
||||
6. Select the token using your mouse and press `Ctrl` + `C` to copy it.
|
||||
2. Press CTRL+R.
|
||||
3. Filter for __`graph`__.
|
||||
4. Click on the first request that appears.
|
||||
5. Open the __`Payload`__ tab.
|
||||
6. Scroll to the bottom to locate your token. It should start with __`FRL`__.
|
||||
7. Select the token using your mouse and press `Ctrl` + `C` to copy it.
|
||||
|
||||
<div align="center">
|
||||
<img src="../wiki/Guides/Login/How-to-obtain-your-Oculus-Token/GetYourOculusToken.png" alt="GetYourOculusToken.png" />
|
||||
|
||||
Generated
+27
-27
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "bs-manager",
|
||||
"version": "1.5.2",
|
||||
"version": "1.5.3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "bs-manager",
|
||||
"version": "1.5.2",
|
||||
"version": "1.5.3",
|
||||
"hasInstallScript": true,
|
||||
"license": "GPL-3.0-only",
|
||||
"dependencies": {
|
||||
@@ -27,7 +27,7 @@
|
||||
"electron-updater": "^6.3.9",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"format-duration": "^3.0.2",
|
||||
"framer-motion": "^12.6.3",
|
||||
"framer-motion": "^12.17.0",
|
||||
"fs-extra": "^11.3.0",
|
||||
"global-agent": "^3.0.0",
|
||||
"got": "^14.4.7",
|
||||
@@ -37,7 +37,7 @@
|
||||
"node-abi": "^4.2.0",
|
||||
"node-fetch": "^3.3.2",
|
||||
"pako": "^2.1.0",
|
||||
"protobufjs": "^7.4.0",
|
||||
"protobufjs": "^7.5.3",
|
||||
"qrcode.react": "^4.2.0",
|
||||
"query-process": "^0.0.3",
|
||||
"react": "^18.2.0",
|
||||
@@ -52,7 +52,7 @@
|
||||
"rfdc": "^1.4.1",
|
||||
"rxjs": "^7.8.2",
|
||||
"sanitize-filename": "^1.6.3",
|
||||
"semver": "^7.7.1",
|
||||
"semver": "^7.7.2",
|
||||
"serialize-error": "^12.0.0",
|
||||
"striptags": "^4.0.0-alpha.4",
|
||||
"tailwind-merge": "^3.0.2",
|
||||
@@ -105,7 +105,7 @@
|
||||
"css-loader": "^6.10.0",
|
||||
"css-minimizer-webpack-plugin": "^6.0.0",
|
||||
"detect-port": "^1.5.1",
|
||||
"electron": "^35.1.4",
|
||||
"electron": "^36.4.0",
|
||||
"electron-builder": "^25.1.8",
|
||||
"electron-devtools-installer": "^4.0.0",
|
||||
"electronmon": "^2.0.3",
|
||||
@@ -11870,9 +11870,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/electron": {
|
||||
"version": "35.1.4",
|
||||
"resolved": "https://registry.npmjs.org/electron/-/electron-35.1.4.tgz",
|
||||
"integrity": "sha512-8HjE2wqxY//T09Of8k1eTpK/NeTG2FkTyRD+fyKXmec4wZVscGgZcmWFC0HYN4ktyHAjtplpxdFXjtqRnvzBMg==",
|
||||
"version": "36.4.0",
|
||||
"resolved": "https://registry.npmjs.org/electron/-/electron-36.4.0.tgz",
|
||||
"integrity": "sha512-LLOOZEuW5oqvnjC7HBQhIqjIIJAZCIFjQxltQGLfEC7XFsBoZgQ3u3iFj+Kzw68Xj97u1n57Jdt7P98qLvUibQ==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
@@ -14433,13 +14433,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/framer-motion": {
|
||||
"version": "12.6.3",
|
||||
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.6.3.tgz",
|
||||
"integrity": "sha512-2hsqknz23aloK85bzMc9nSR2/JP+fValQ459ZTVElFQ0xgwR2YqNjYSuDZdFBPOwVCt4Q9jgyTt6hg6sVOALzw==",
|
||||
"version": "12.17.0",
|
||||
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.17.0.tgz",
|
||||
"integrity": "sha512-2hISKgDk49yCLStwG1wf4Kdy/D6eBw9/eRNaWFIYoI9vMQ/Mqd1Fz+gzVlEtxJmtQ9y4IWnXm19/+UXD3dAYAA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"motion-dom": "^12.6.3",
|
||||
"motion-utils": "^12.6.3",
|
||||
"motion-dom": "^12.17.0",
|
||||
"motion-utils": "^12.12.1",
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
@@ -19233,18 +19233,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/motion-dom": {
|
||||
"version": "12.6.3",
|
||||
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.6.3.tgz",
|
||||
"integrity": "sha512-gRY08RjcnzgFYLemUZ1lo/e9RkBxR+6d4BRvoeZDSeArG4XQXERSPapKl3LNQRu22Sndjf1h+iavgY0O4NrYqA==",
|
||||
"version": "12.17.0",
|
||||
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.17.0.tgz",
|
||||
"integrity": "sha512-FA6/c70R9NKs3g41XDVONzmUUrEmyaifLVGCWtAmHP0usDnX9W+RN/tmbC4EUl0w6yLGvMTOwnWCFVgA5luhRg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"motion-utils": "^12.6.3"
|
||||
"motion-utils": "^12.12.1"
|
||||
}
|
||||
},
|
||||
"node_modules/motion-utils": {
|
||||
"version": "12.6.3",
|
||||
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.6.3.tgz",
|
||||
"integrity": "sha512-R/b3Ia2VxtTNZ4LTEO5pKYau1OUNHOuUfxuP0WFCTDYdHkeTBR9UtxR1cc8mDmKr8PEhmmfnTKGz3rSMjNRoRg==",
|
||||
"version": "12.12.1",
|
||||
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.12.1.tgz",
|
||||
"integrity": "sha512-f9qiqUHm7hWSLlNW8gS9pisnsN7CRFRD58vNjptKdsqFLpkVnX00TNeD6Q0d27V9KzT7ySFyK1TZ/DShfVOv6w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/mrmime": {
|
||||
@@ -21153,9 +21153,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/protobufjs": {
|
||||
"version": "7.4.0",
|
||||
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz",
|
||||
"integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==",
|
||||
"version": "7.5.3",
|
||||
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.3.tgz",
|
||||
"integrity": "sha512-sildjKwVqOI2kmFDiXQ6aEB0fjYTafpEvIBs8tOR8qI4spuL9OPROLVu2qZqi/xgCfsHIwVqlaF8JBjWFHnKbw==",
|
||||
"hasInstallScript": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
@@ -22470,9 +22470,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
|
||||
"integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
|
||||
"version": "7.7.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
|
||||
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
|
||||
+5
-5
@@ -2,7 +2,7 @@
|
||||
"name": "bs-manager",
|
||||
"description": "Manage maps, mods and more for Beat Saber",
|
||||
"main": "./.erb/dll/main.bundle.dev.js",
|
||||
"version": "1.5.3",
|
||||
"version": "1.5.4",
|
||||
"scripts": {
|
||||
"build-rust-scripts": "ts-node ./.erb/scripts/build-rust-scripts.js",
|
||||
"build": "concurrently \"npm run build:main\" \"npm run build:renderer\"",
|
||||
@@ -108,7 +108,7 @@
|
||||
"css-loader": "^6.10.0",
|
||||
"css-minimizer-webpack-plugin": "^6.0.0",
|
||||
"detect-port": "^1.5.1",
|
||||
"electron": "^35.1.4",
|
||||
"electron": "^36.4.0",
|
||||
"electron-builder": "^25.1.8",
|
||||
"electron-devtools-installer": "^4.0.0",
|
||||
"electronmon": "^2.0.3",
|
||||
@@ -170,7 +170,7 @@
|
||||
"electron-updater": "^6.3.9",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"format-duration": "^3.0.2",
|
||||
"framer-motion": "^12.6.3",
|
||||
"framer-motion": "^12.17.0",
|
||||
"fs-extra": "^11.3.0",
|
||||
"global-agent": "^3.0.0",
|
||||
"got": "^14.4.7",
|
||||
@@ -180,7 +180,7 @@
|
||||
"node-abi": "^4.2.0",
|
||||
"node-fetch": "^3.3.2",
|
||||
"pako": "^2.1.0",
|
||||
"protobufjs": "^7.4.0",
|
||||
"protobufjs": "^7.5.3",
|
||||
"qrcode.react": "^4.2.0",
|
||||
"query-process": "^0.0.3",
|
||||
"react": "^18.2.0",
|
||||
@@ -195,7 +195,7 @@
|
||||
"rfdc": "^1.4.1",
|
||||
"rxjs": "^7.8.2",
|
||||
"sanitize-filename": "^1.6.3",
|
||||
"semver": "^7.7.1",
|
||||
"semver": "^7.7.2",
|
||||
"serialize-error": "^12.0.0",
|
||||
"striptags": "^4.0.0-alpha.4",
|
||||
"tailwind-merge": "^3.0.2",
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "bs-manager",
|
||||
"version": "1.5.3",
|
||||
"version": "1.5.4",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "bs-manager",
|
||||
"version": "1.5.3",
|
||||
"version": "1.5.4",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bs-manager",
|
||||
"version": "1.5.3",
|
||||
"version": "1.5.4",
|
||||
"description": "BSManager",
|
||||
"main": "./dist/main/main.js",
|
||||
"author": {
|
||||
|
||||
@@ -51,7 +51,15 @@ async function enableWindowsProxy(enable: boolean): Promise<void> {
|
||||
}
|
||||
|
||||
if (enable) {
|
||||
const httpProxyUrl = `http://${await getProxyServer().catch(err => log.error(err))}`
|
||||
const httpProxyServer = await getProxyServer().catch(err => log.error(err));
|
||||
let httpProxyUrl: string | null = null;
|
||||
if (httpProxyServer) {
|
||||
// Prepend the http protocol
|
||||
httpProxyUrl = httpProxyServer.startsWith("http://")
|
||||
? httpProxyServer
|
||||
: `http://${httpProxyServer}`;
|
||||
}
|
||||
|
||||
globalProxyAgent.HTTP_PROXY = httpProxyUrl;
|
||||
globalProxyAgent.HTTPS_PROXY = httpProxyUrl;
|
||||
globalProxyAgent.NO_PROXY = `${await getProxyOverride().catch(err => log.error(err))}`;
|
||||
|
||||
@@ -14,6 +14,11 @@ ipc.on("check-update", (_, reply) => {
|
||||
reply(from(updaterService.isUpdateAvailable()));
|
||||
});
|
||||
|
||||
ipc.on("get-available-update", (_, reply) => {
|
||||
const updaterService = AutoUpdaterService.getInstance();
|
||||
reply(from(updaterService.getAvailableUpdate()));
|
||||
});
|
||||
|
||||
ipc.on("install-update", (_, reply) => {
|
||||
const updaterService = AutoUpdaterService.getInstance();
|
||||
reply(of(updaterService.quitAndInstall()));
|
||||
|
||||
+23
-7
@@ -28,6 +28,7 @@ import { StaticConfigurationService } from "./services/static-configuration.serv
|
||||
import { configureProxy } from './helpers/proxy.helpers';
|
||||
import { deleteFileSync, deleteFolderSync } from "./helpers/fs.helpers";
|
||||
import { tryit } from "shared/helpers/error.helpers";
|
||||
import { AutoUpdate } from "shared/models/config";
|
||||
|
||||
const isDebug = process.env.NODE_ENV === "development" || process.env.DEBUG_PROD === "true";
|
||||
const staticConfig = StaticConfigurationService.getInstance();
|
||||
@@ -99,6 +100,16 @@ const findAssociatedFileInArgs = (args: string[]): string => {
|
||||
|
||||
const gotTheLock = app.requestSingleInstanceLock();
|
||||
|
||||
const init = () => {
|
||||
initServicesMustBeInitialized();
|
||||
|
||||
if (process.platform === "linux") {
|
||||
// Properly set up the home path for linux
|
||||
const homePath = process.env.XDG_DATA_HOME || path.join(process.env.HOME, ".local", "share");
|
||||
app.setPath("home", homePath);
|
||||
}
|
||||
}
|
||||
|
||||
if (!gotTheLock) {
|
||||
app.quit();
|
||||
} else {
|
||||
@@ -121,11 +132,8 @@ if (!gotTheLock) {
|
||||
|
||||
app.whenReady().then(() => {
|
||||
|
||||
|
||||
|
||||
app.setAppUserModelId(APP_NAME);
|
||||
|
||||
initServicesMustBeInitialized();
|
||||
init();
|
||||
|
||||
const deepLink = findDeepLinkInArgs(process.argv);
|
||||
const associatedFile = findAssociatedFileInArgs(process.argv);
|
||||
@@ -134,10 +142,18 @@ if (!gotTheLock) {
|
||||
DeepLinkService.getInstance().dispatchLinkOpened(deepLink);
|
||||
} else if (associatedFile) {
|
||||
FileAssociationService.getInstance().handleFileAssociation(associatedFile);
|
||||
} else if (process.platform === "linux") {
|
||||
createWindow("index.html");
|
||||
} else {
|
||||
createWindow(process.platform === "linux"
|
||||
? "index.html" : "launcher.html"
|
||||
);
|
||||
const configService = StaticConfigurationService.getInstance();
|
||||
const autoUpdate = configService.get("auto-update", AutoUpdate.ALWAYS);
|
||||
const update = autoUpdate !== AutoUpdate.NEVER;
|
||||
if (autoUpdate === AutoUpdate.ONCE) {
|
||||
configService.set("auto-update", AutoUpdate.NEVER);
|
||||
}
|
||||
|
||||
// Skip launcher only if autoUpdate is strictly false
|
||||
createWindow(update ? "launcher.html" : "index.html");
|
||||
}
|
||||
|
||||
SteamLauncherService.getInstance().restoreSteamVR();
|
||||
|
||||
@@ -359,7 +359,18 @@ export class LocalPlaylistsManagerService {
|
||||
continue;
|
||||
}
|
||||
|
||||
const downloadedMap = await this.maps.downloadMap(mapDetail, version);
|
||||
const downloadedMap = await this.maps.downloadMap(mapDetail, version)
|
||||
.catch(error => {
|
||||
log.error(
|
||||
"Could not download map for playlist",
|
||||
`[${mapDetail.id}] "${mapDetail.name}"`,
|
||||
error
|
||||
);
|
||||
return null;
|
||||
});
|
||||
if (!downloadedMap) {
|
||||
continue;
|
||||
}
|
||||
|
||||
progress.data.downloadedMaps.push(downloadedMap);
|
||||
progress.data.currentDownload = mapDetail;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { autoUpdater, CancellationToken, ProgressInfo } from "electron-updater";
|
||||
import { autoUpdater, CancellationToken, ProgressInfo, UpdateInfo } from "electron-updater";
|
||||
import log from "electron-log";
|
||||
import { gt } from "semver";
|
||||
import { Progression } from "main/helpers/fs.helpers";
|
||||
import { Observable } from "rxjs";
|
||||
import { safeGt } from "shared/helpers/semver.helpers";
|
||||
|
||||
export class AutoUpdaterService {
|
||||
private static instance: AutoUpdaterService;
|
||||
@@ -31,6 +32,18 @@ export class AutoUpdaterService {
|
||||
});
|
||||
}
|
||||
|
||||
public async getAvailableUpdate(): Promise<UpdateInfo | null> {
|
||||
return autoUpdater.checkForUpdates().then(info => {
|
||||
if (info?.updateInfo && safeGt(info.updateInfo.version, autoUpdater.currentVersion.version)) {
|
||||
return info.updateInfo;
|
||||
}
|
||||
return null;
|
||||
}).catch((error: Error): UpdateInfo | null => {
|
||||
log.error("Could not get update", error);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
public downloadUpdate(): Observable<Progression> {
|
||||
return new Observable<Progression>(observer => {
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ export class BSLocalVersionService {
|
||||
return null;
|
||||
}
|
||||
|
||||
const versionsDict = await this.remoteVersionService.getAvailableVersions();
|
||||
const versionsDict = (await this.remoteVersionService.getAvailableVersions()).reverse();
|
||||
|
||||
let stream: ReadStream;
|
||||
|
||||
@@ -244,8 +244,12 @@ export class BSLocalVersionService {
|
||||
private async getSteamVersion(): Promise<BSVersion> {
|
||||
const steamBsFolder = await this.steamService.getGameFolder(BS_APP_ID, "Beat Saber");
|
||||
|
||||
if (!steamBsFolder || !(await pathExists(steamBsFolder))) {
|
||||
return null;
|
||||
if (!steamBsFolder) {
|
||||
throw new Error("No Beat Saber Steam version found");
|
||||
}
|
||||
|
||||
if (!(await pathExists(steamBsFolder))) {
|
||||
throw new Error(`Beat Saber Steam version not found in "${steamBsFolder}"`);
|
||||
}
|
||||
|
||||
return this.getVersionOfBSFolder(steamBsFolder, { steam: true });
|
||||
@@ -265,7 +269,8 @@ export class BSLocalVersionService {
|
||||
const versions: BSVersion[] = [];
|
||||
|
||||
const steamVersion = await this.getSteamVersion().catch(e => {
|
||||
log.error("unable to get original Steam version", e);
|
||||
log.error("Unable to get original Steam version", e);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (steamVersion) {
|
||||
|
||||
@@ -129,12 +129,7 @@ export class LinuxService {
|
||||
return fs.pathExistsSync(protonPath) && fs.pathExistsSync(winePath);
|
||||
}
|
||||
|
||||
public async getWinePath(): Promise<string> {
|
||||
if (await this.isNixOS()) {
|
||||
// Use system wine for nixos
|
||||
return "wine";
|
||||
}
|
||||
|
||||
public getWinePath(): string {
|
||||
if (!this.staticConfig.has("proton-folder")) {
|
||||
throw new Error("proton-folder variable not set");
|
||||
}
|
||||
|
||||
@@ -76,8 +76,7 @@ export class BeatModsApiService {
|
||||
}
|
||||
|
||||
return this.requestService.getJSON<{ modVersions: BbmModVersion[] }>(
|
||||
`${this.MODS_REPO_API_URL}/hashlookup?hash=${hash}`,
|
||||
{ silentError: true }
|
||||
`${this.MODS_REPO_API_URL}/hashlookup?hash=${hash}`
|
||||
).then(({ data }) => {
|
||||
this.updateModsHashCache(data?.modVersions ?? []);
|
||||
return data?.modVersions?.at(0);
|
||||
|
||||
@@ -49,12 +49,11 @@ export class BsModsManagerService {
|
||||
return undefined;
|
||||
});
|
||||
|
||||
if(mod?.contentHashes?.some(content => content.path.includes("IPA.exe"))){
|
||||
if (mod?.contentHashes?.some(content => content.path.includes("IPA.exe"))) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return mod;
|
||||
|
||||
}
|
||||
|
||||
private async getModsInDir(
|
||||
@@ -145,9 +144,9 @@ export class BsModsManagerService {
|
||||
|
||||
return BsmZipExtractor.fromBuffer(buffer);
|
||||
}
|
||||
private async executeBSIPA(version: BSVersion, args: string[]): Promise<boolean> {
|
||||
|
||||
log.info("executeBSIPA", version?.BSVersion, args);
|
||||
private async executeIPA(version: BSVersion, args: string[]): Promise<boolean> {
|
||||
log.info("executeIPA", version?.BSVersion, args);
|
||||
|
||||
const versionPath = await this.bsLocalService.getVersionPath(version);
|
||||
const ipaPath = path.join(versionPath, "IPA.exe");
|
||||
@@ -162,18 +161,22 @@ export class BsModsManagerService {
|
||||
let winePath: string = "";
|
||||
if (process.platform === "linux") {
|
||||
const { error: winePathError, result: winePathResult } =
|
||||
await tryit(async () => this.linuxService.getWinePath());
|
||||
tryit(() => this.linuxService.getWinePath());
|
||||
if (winePathError) {
|
||||
log.error(winePathError);
|
||||
return false;
|
||||
}
|
||||
winePath = `"${winePathResult}"`;
|
||||
|
||||
winePath = await this.linuxService.isNixOS()
|
||||
? `steam-run "${winePathResult}"`
|
||||
: `"${winePathResult}"`;
|
||||
|
||||
const winePrefix = this.linuxService.getWinePrefixPath();
|
||||
if (!winePrefix) {
|
||||
throw new CustomError("Could not find BSManager WINEPREFIX path", "no-wineprefix");
|
||||
}
|
||||
env.WINEPREFIX = winePrefix;
|
||||
Object.assign(env, process.env);
|
||||
}
|
||||
|
||||
return new Promise<boolean>(resolve => {
|
||||
@@ -189,7 +192,7 @@ export class BsModsManagerService {
|
||||
});
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
log.info("Ipa process timeout");
|
||||
log.info("IPA process timed out");
|
||||
resolve(false)
|
||||
}, sToMs(30));
|
||||
|
||||
@@ -203,13 +206,11 @@ export class BsModsManagerService {
|
||||
processIPA.once("exit", code => {
|
||||
clearTimeout(timeout);
|
||||
if (code === 0) {
|
||||
log.info("Ipa process exist with code 0");
|
||||
return resolve(true);
|
||||
}
|
||||
log.error("Ipa process exist with non 0 code", code);
|
||||
log.error("IPA process exited with non-zero code", code);
|
||||
resolve(false);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@@ -222,7 +223,7 @@ export class BsModsManagerService {
|
||||
|
||||
const isBSIPA = mod.mod.name.toLowerCase() === "bsipa";
|
||||
|
||||
if(isBSIPA){
|
||||
if (isBSIPA) {
|
||||
await this.clearIpaFolder(version).catch(e => log.error("Error while clearing IPA folder", e));
|
||||
}
|
||||
|
||||
@@ -269,9 +270,11 @@ export class BsModsManagerService {
|
||||
|
||||
log.info("Mod zip extraction end", mod.mod.name, "to", destDir, "success:", extracted);
|
||||
|
||||
const res = isBSIPA
|
||||
// Executing IPA.exe when BSIPA is already installed could potentially corrupt the game.
|
||||
const shouldRunIPA = isBSIPA && !pathExistsSync(path.join(versionPath, "winhttp.dll"));
|
||||
const res = shouldRunIPA
|
||||
? extracted &&
|
||||
(await this.executeBSIPA(version, ["-n"]).catch(e => {
|
||||
(await this.executeIPA(version, ["-n"]).catch(e => {
|
||||
log.error(e);
|
||||
return false;
|
||||
}))
|
||||
@@ -286,14 +289,14 @@ export class BsModsManagerService {
|
||||
const versionPath = await this.bsLocalService.getVersionPath(version);
|
||||
const ipaPath = path.join(versionPath, ModsInstallFolder.IPA);
|
||||
|
||||
if(!pathExistsSync(ipaPath)){
|
||||
if (!pathExistsSync(ipaPath)) {
|
||||
log.info("IPA folder does not exist, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
const contents = readdirSync(ipaPath, { withFileTypes: true });
|
||||
|
||||
for(const content of contents){
|
||||
for (const content of contents) {
|
||||
|
||||
if (content.name === 'Backups' || content.name === 'Pending') {
|
||||
continue;
|
||||
@@ -306,13 +309,12 @@ export class BsModsManagerService {
|
||||
: deleteFile(contentPath)
|
||||
);
|
||||
|
||||
if(res.error){
|
||||
if (res.error) {
|
||||
log.error("Error while clearing IPA folder content", content.name, res.error);
|
||||
}
|
||||
}
|
||||
|
||||
log.info("IPA folder cleared successfully");
|
||||
|
||||
}
|
||||
|
||||
private async uninstallBSIPA(mod: BbmFullMod, version: BSVersion): Promise<void> {
|
||||
@@ -324,7 +326,7 @@ export class BsModsManagerService {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.executeBSIPA(version, ["--revert", "-n"]);
|
||||
await this.executeIPA(version, ["--revert", "-n"]);
|
||||
|
||||
const promises = mod.version.contentHashes.map(content => {
|
||||
const file = content.path.replaceAll("IPA/", "").replaceAll("Data", "Beat Saber_Data");
|
||||
@@ -333,6 +335,7 @@ export class BsModsManagerService {
|
||||
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
private async uninstallMod(mod: BbmFullMod, version: BSVersion): Promise<void> {
|
||||
if (mod.mod.name.toLowerCase() === "bsipa") {
|
||||
return this.uninstallBSIPA(mod, version);
|
||||
@@ -343,13 +346,13 @@ export class BsModsManagerService {
|
||||
const promises: Promise<void>[] = mod.version.contentHashes.map(async content => {
|
||||
return (async () => {
|
||||
const modPath = path.join(versionPath, content.path);
|
||||
if(pathExistsSync(modPath)){
|
||||
if (pathExistsSync(modPath)) {
|
||||
log.info("Deleting mod", modPath);
|
||||
await deleteFile(modPath);
|
||||
}
|
||||
|
||||
const pendingPath = path.join(versionPath, "IPA", "Pending", content.path);
|
||||
if(pathExistsSync(pendingPath)){
|
||||
if (pathExistsSync(pendingPath)) {
|
||||
log.info("Deleting pending mod", pendingPath);
|
||||
return deleteFile(pendingPath);
|
||||
}
|
||||
@@ -513,12 +516,12 @@ export class BsModsManagerService {
|
||||
|
||||
const bsipa = popElement(mod => mod.mod.name.toLowerCase() === "bsipa", mods);
|
||||
|
||||
if(bsipa){
|
||||
if (bsipa) {
|
||||
const bsipaInstalled = await this.installMod(bsipa, version).catch(err => {
|
||||
log.error("Error while installing BSIPA", err);
|
||||
});
|
||||
|
||||
if(!bsipaInstalled){
|
||||
if (!bsipaInstalled) {
|
||||
throw CustomError.throw(new Error("BSIPA failed to install"), "cannot-install-bsipa");
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import { tryit } from 'shared/helpers/error.helpers';
|
||||
import path from 'path';
|
||||
import { pipeline } from 'stream/promises';
|
||||
import sanitize from 'sanitize-filename';
|
||||
import internal from 'stream';
|
||||
import { app } from 'electron';
|
||||
|
||||
export class RequestService {
|
||||
@@ -17,31 +18,55 @@ export class RequestService {
|
||||
'User-Agent': `BSManager/${app.getVersion()} (Electron/${process.versions.electron} Chrome/${process.versions.chrome} Node/${process.versions.node})`,
|
||||
}
|
||||
|
||||
private readonly PREFERRED_FAMILY_TESTS = [4, 6];
|
||||
private preferredFamilyCache: Record<string, number> = {};
|
||||
|
||||
public static getInstance(): RequestService {
|
||||
if (!RequestService.instance) {
|
||||
RequestService.instance = new RequestService();
|
||||
}
|
||||
return RequestService.instance;
|
||||
|
||||
}
|
||||
|
||||
private constructor() {}
|
||||
|
||||
public async getJSON<T = unknown>(url: string, options?: {
|
||||
silentError?: boolean
|
||||
}): Promise<{ data: T; headers: IncomingHttpHeaders }> {
|
||||
|
||||
try {
|
||||
// @ts-ignore (ESM is not well supported in this project, We need to move out electron-react-boilerplate, and use Vite)
|
||||
const res = await got(url, { responseType: 'json', headers: this.baseHeaders });
|
||||
return { data: res.body as T, headers: res.headers };
|
||||
} catch (err) {
|
||||
if (options?.silentError !== true) {
|
||||
log.error(`Failed to get JSON from URL: ${url}`, err);
|
||||
public async getJSON<T = unknown>(url: string): Promise<{ data: T; headers: IncomingHttpHeaders }> {
|
||||
const domain = (new URL(url)).hostname;
|
||||
const cachedFamily = this.preferredFamilyCache[domain];
|
||||
if (cachedFamily) {
|
||||
try {
|
||||
return await this.requestData<T>(url, cachedFamily);
|
||||
} catch (error: any) {
|
||||
throw new Error(`Request failed: ${url}`, error);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
|
||||
// Try on each IPv4/6 families on first request to a domain/website
|
||||
for (const family of this.PREFERRED_FAMILY_TESTS) {
|
||||
try {
|
||||
const response = await this.requestData<T>(url, family);
|
||||
log.info(`Caching "${domain}" with IPv${family}`);
|
||||
this.preferredFamilyCache[domain] = family;
|
||||
return response;
|
||||
} catch (err) {
|
||||
log.warn(`IPv${family} request failed, trying next one... URL: ${url}`, err);
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`IPv4 and IPv6 requests failed for URL: ${url}`);
|
||||
}
|
||||
|
||||
private async requestData<T>(url: string, family: number): Promise<{ data: T; headers: IncomingHttpHeaders }> {
|
||||
const response = await got(url, {
|
||||
// @ts-ignore (ESM is not well supported in this project, We need to move out electron-react-boilerplate, and use Vite)
|
||||
dnsLookupIpVersion: family,
|
||||
responseType: "json",
|
||||
headers: this.baseHeaders
|
||||
});
|
||||
return {
|
||||
data: response.body as T,
|
||||
headers: response.headers
|
||||
};
|
||||
}
|
||||
|
||||
public downloadFile(
|
||||
@@ -52,46 +77,69 @@ export class RequestService {
|
||||
return new Observable<Progression<string>>((subscriber) => {
|
||||
const progress: Progression<string> = { current: 0, total: 0 };
|
||||
|
||||
let file: WriteStream | undefined;
|
||||
let attempt = 0;
|
||||
let stream: got.GotEmitter & internal.Duplex;
|
||||
|
||||
// @ts-ignore (ESM is not well supported in this project, We need to move out electron-react-boilerplate, and use Vite)
|
||||
const stream = got.stream(url, { headers: this.baseHeaders });
|
||||
const domain = (new URL(url)).hostname;
|
||||
const cachedFamily = this.preferredFamilyCache[domain];
|
||||
const familiesToTry = cachedFamily
|
||||
? [ cachedFamily ] : this.PREFERRED_FAMILY_TESTS;
|
||||
|
||||
stream.on('response', (response) => {
|
||||
|
||||
const filename = opt?.preferContentDisposition ? this.getFilenameFromContentDisposition(response.headers['content-disposition']) : null;
|
||||
|
||||
if (filename) {
|
||||
dest = path.join(path.dirname(dest), sanitize(filename));
|
||||
const tryNextFamily = () => {
|
||||
if (attempt >= familiesToTry.length) {
|
||||
subscriber.error(new Error(`Download failed over IPv4 and IPv6 for URL: ${url}`));
|
||||
return;
|
||||
}
|
||||
|
||||
progress.data = dest;
|
||||
file = createWriteStream(dest);
|
||||
const family = familiesToTry[attempt++];
|
||||
let file: WriteStream | undefined;
|
||||
|
||||
pipeline(stream, file).catch(err => {
|
||||
file?.destroy();
|
||||
tryit(() => deleteFileSync(dest));
|
||||
subscriber.error(err);
|
||||
// @ts-ignore (ESM is not well supported in this project, We need to move out electron-react-boilerplate, and use Vite)
|
||||
stream = got.stream(url, { dnsLookupIpVersion: family, headers: this.baseHeaders });
|
||||
|
||||
stream.on('response', (response) => {
|
||||
if (!cachedFamily) {
|
||||
log.info(`Caching "${domain}" with IPv${family}`);
|
||||
this.preferredFamilyCache[domain] = family;
|
||||
}
|
||||
|
||||
const filename = opt?.preferContentDisposition ? this.getFilenameFromContentDisposition(response.headers['content-disposition']) : null;
|
||||
|
||||
if (filename) {
|
||||
dest = path.join(path.dirname(dest), sanitize(filename));
|
||||
}
|
||||
|
||||
progress.data = dest;
|
||||
file = createWriteStream(dest);
|
||||
|
||||
pipeline(stream, file).catch(err => {
|
||||
file?.destroy();
|
||||
tryit(() => deleteFileSync(dest));
|
||||
subscriber.error(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
stream.on('downloadProgress', ({ transferred, total }) => {
|
||||
progress.current = transferred;
|
||||
progress.total = total;
|
||||
subscriber.next(progress);
|
||||
});
|
||||
stream.on('downloadProgress', ({ transferred, total }) => {
|
||||
progress.current = transferred;
|
||||
progress.total = total;
|
||||
subscriber.next(progress);
|
||||
});
|
||||
|
||||
stream.on('error', err => {
|
||||
log.error(`Download failed for URL: ${url}`, err);
|
||||
stream.destroy();
|
||||
file?.destroy();
|
||||
});
|
||||
stream.on('error', err => {
|
||||
log.warn(`Download failed over IPv${family} for URL: ${url}`, err);
|
||||
stream.destroy();
|
||||
file?.destroy();
|
||||
tryNextFamily();
|
||||
});
|
||||
|
||||
stream.on('end', () => {
|
||||
file?.end();
|
||||
subscriber.next(progress);
|
||||
subscriber.complete();
|
||||
});
|
||||
stream.on('end', () => {
|
||||
file?.end();
|
||||
subscriber.next(progress);
|
||||
subscriber.complete();
|
||||
});
|
||||
};
|
||||
|
||||
tryNextFamily();
|
||||
|
||||
return () => {
|
||||
stream?.destroy();
|
||||
@@ -106,7 +154,6 @@ export class RequestService {
|
||||
url: string,
|
||||
options?: got.GotOptions<null>
|
||||
): Observable<Progression<Buffer, IncomingMessage>> {
|
||||
|
||||
return new Observable<Progression<Buffer, IncomingMessage>>((subscriber) => {
|
||||
const progress: Progression<Buffer, IncomingMessage> = {
|
||||
current: 0,
|
||||
@@ -116,37 +163,60 @@ export class RequestService {
|
||||
|
||||
const headers = { ...this.baseHeaders, ...(options?.headers ?? {}) };
|
||||
|
||||
// @ts-ignore (ESM is not well supported in this project, We need to move out electron-react-boilerplate, and use Vite)
|
||||
const stream = got.stream(url, { ...(options ?? {}), headers });
|
||||
let attempt = 0;
|
||||
let stream: got.GotEmitter & internal.Duplex;
|
||||
|
||||
let data = Buffer.alloc(0);
|
||||
let response: IncomingMessage;
|
||||
const domain = (new URL(url)).hostname;
|
||||
const cachedFamily = this.preferredFamilyCache[domain];
|
||||
const familiesToTry = cachedFamily
|
||||
? [ cachedFamily ] : this.PREFERRED_FAMILY_TESTS;
|
||||
|
||||
stream.once('response', (res) => {
|
||||
response = res;
|
||||
});
|
||||
const tryNextFamily = () => {
|
||||
if (attempt >= familiesToTry.length) {
|
||||
subscriber.error(new Error(`Download failed over IPv4 and IPv6 for URL: ${url}`));
|
||||
return;
|
||||
}
|
||||
|
||||
stream.on('data', (chunk: Buffer) => {
|
||||
data = Buffer.concat([data, chunk]);
|
||||
});
|
||||
const family = familiesToTry[attempt++];
|
||||
// @ts-ignore (ESM is not well supported in this project, We need to move out electron-react-boilerplate, and use Vite)
|
||||
stream = got.stream(url, { dnsLookupIpVersion: family, ...(options ?? {}), headers });
|
||||
|
||||
stream.on('downloadProgress', ({ transferred, total }) => {
|
||||
progress.current = transferred;
|
||||
progress.total = total;
|
||||
subscriber.next(progress);
|
||||
});
|
||||
let data = Buffer.alloc(0);
|
||||
let response: IncomingMessage;
|
||||
|
||||
stream.on('error', err => {
|
||||
log.error(`Download failed for URL: ${url}`, err);
|
||||
stream.destroy();
|
||||
});
|
||||
stream.once('response', (res) => {
|
||||
if (!cachedFamily) {
|
||||
log.info(`Caching "${domain}" with IPv${family}`);
|
||||
this.preferredFamilyCache[domain] = family;
|
||||
}
|
||||
response = res;
|
||||
});
|
||||
|
||||
stream.on('end', () => {
|
||||
progress.data = data;
|
||||
progress.extra = response;
|
||||
subscriber.next(progress);
|
||||
subscriber.complete();
|
||||
});
|
||||
stream.on('data', (chunk: Buffer) => {
|
||||
data = Buffer.concat([data, chunk]);
|
||||
});
|
||||
|
||||
stream.on('downloadProgress', ({ transferred, total }) => {
|
||||
progress.current = transferred;
|
||||
progress.total = total;
|
||||
subscriber.next(progress);
|
||||
});
|
||||
|
||||
stream.on('error', err => {
|
||||
log.warn(`Download failed over IPv${family} for URL: ${url}`, err);
|
||||
stream.destroy();
|
||||
tryNextFamily();
|
||||
});
|
||||
|
||||
stream.on('end', () => {
|
||||
progress.data = data;
|
||||
progress.extra = response;
|
||||
subscriber.next(progress);
|
||||
subscriber.complete();
|
||||
});
|
||||
};
|
||||
|
||||
tryNextFamily();
|
||||
|
||||
return () => {
|
||||
stream?.destroy();
|
||||
|
||||
@@ -5,6 +5,7 @@ import { PROTON_BINARY_PREFIX, WINE_BINARY_PREFIX } from "main/constants";
|
||||
import { Observable, Subject } from "rxjs";
|
||||
import { CustomError } from "shared/models/exceptions/custom-error.class";
|
||||
import { BSVersion } from "shared/bs-version.interface";
|
||||
import { AutoUpdate } from "shared/models/config";
|
||||
|
||||
export class StaticConfigurationService {
|
||||
private static instance: StaticConfigurationService;
|
||||
@@ -30,8 +31,8 @@ export class StaticConfigurationService {
|
||||
return this.store.has(key);
|
||||
}
|
||||
|
||||
public get<K extends StaticConfigKeys>(key: K): StaticConfigKeyValues[K] {
|
||||
return this.store.get<K>(key) as StaticConfigKeyValues[K];
|
||||
public get<K extends StaticConfigKeys>(key: K, defaultValue?: StaticConfigKeyValues[K]): StaticConfigKeyValues[K] {
|
||||
return this.store.get<K>(key, defaultValue) as StaticConfigKeyValues[K];
|
||||
}
|
||||
|
||||
public take<K extends StaticConfigKeys>(key: K, cb: (val: StaticConfigKeyValues[K]) => void): void {
|
||||
@@ -90,6 +91,7 @@ export interface StaticConfigKeyValues {
|
||||
"use-symlinks": boolean;
|
||||
"use-system-proxy": boolean;
|
||||
"last-version-launched": BSVersion;
|
||||
"auto-update": AutoUpdate;
|
||||
|
||||
// Linux Specific static configs
|
||||
"proton-folder": string;
|
||||
|
||||
+1
-1
@@ -249,7 +249,7 @@ export const LocalMapsListPanel = forwardRef<unknown, Props>(({
|
||||
|
||||
return (
|
||||
<VirtualScroll
|
||||
classNames={{ mainDiv: className, rows: "gap-x-2 px-2 py-2" }}
|
||||
classNames={{ mainDiv: className, rows: "gap-x-2 px-2 pt-2 pb-[108px]" }}
|
||||
itemHeight={108}
|
||||
maxColumns={3}
|
||||
minItemWidth={400}
|
||||
|
||||
+10
-17
@@ -6,13 +6,12 @@ import { BsmButton } from "renderer/components/shared/bsm-button.component";
|
||||
import { LaunchOption } from "shared/models/bs-launch";
|
||||
import { useService } from "renderer/hooks/use-service.hook";
|
||||
import { BSLauncherService } from "renderer/services/bs-launcher.service";
|
||||
import { useMemo, useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { BsNoteFill } from "renderer/components/svgs/icons/bs-note-fill.component";
|
||||
import { useThemeColor } from "renderer/hooks/use-theme-color.hook";
|
||||
import { ChevronTopIcon } from "renderer/components/svgs/icons/chevron-top-icon.component";
|
||||
import Tippy from "@tippyjs/react";
|
||||
import { LaunchMod } from "shared/models/bs-launch/launch-option.interface";
|
||||
import { BsStore } from "shared/models/bs-store.enum";
|
||||
|
||||
export const CreateLaunchShortcutModal: ModalComponent<{ steamShortcut: boolean, launchOption: LaunchOption }, BSVersion> = ({resolver, options: {data}}) => {
|
||||
|
||||
@@ -26,10 +25,6 @@ export const CreateLaunchShortcutModal: ModalComponent<{ steamShortcut: boolean,
|
||||
const [command, setCommand] = useState(launchOption.command || "");
|
||||
const [steamShortcut, setSteamShortcut] = useState(false);
|
||||
|
||||
const isSteamVersion = useMemo(() => {
|
||||
return data.steam || data.metadata?.store === BsStore.STEAM;
|
||||
}, [data]);
|
||||
|
||||
const completeModal = () => {
|
||||
launchOption.command = command.trim();
|
||||
resolver({exitCode: ModalExitCode.COMPLETED, data: { launchOption, steamShortcut }});
|
||||
@@ -96,17 +91,15 @@ export const CreateLaunchShortcutModal: ModalComponent<{ steamShortcut: boolean,
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{isSteamVersion && (
|
||||
<Tippy placement="right" theme="default" content={t("modals.create-launch-shortcut.steam-shortcut-tippy")}>
|
||||
<div className="h-full flex items-center gap-1.5 mt-3 mb-4 w-fit pr-1">
|
||||
<BsmCheckbox className="h-5 aspect-square relative z-[1]" checked={steamShortcut} onChange={e => setSteamShortcut(() => e)} />
|
||||
<span>{t("modals.create-launch-shortcut.create-steam-shortcut")}</span>
|
||||
</div>
|
||||
</Tippy>
|
||||
)}
|
||||
<div className="grid grid-flow-col grid-cols-2 gap-4 mt-2">
|
||||
<BsmButton typeColor="cancel" className="rounded-md text-center transition-all" onClick={() => resolver({ exitCode: ModalExitCode.CANCELED })} withBar={false} text="misc.cancel" />
|
||||
<BsmButton typeColor="primary" className="rounded-md text-center transition-all" onClick={completeModal} withBar={false} text="modals.create-launch-shortcut.valid-btn" />
|
||||
<Tippy placement="right" theme="default" content={t("modals.create-launch-shortcut.steam-shortcut-tippy")}>
|
||||
<div className="h-full flex items-center gap-1.5 mt-3 mb-4 w-fit pr-1">
|
||||
<BsmCheckbox className="h-5 aspect-square relative z-[1]" checked={steamShortcut} onChange={e => setSteamShortcut(() => e)} />
|
||||
<span>{t("modals.create-launch-shortcut.create-steam-shortcut")}</span>
|
||||
</div>
|
||||
</Tippy>
|
||||
<div className="grid grid-flow-col grid-cols-2 gap-4 h-8">
|
||||
<BsmButton typeColor="cancel" className="h-full flex items-center justify-center rounded-md text-center transition-all" onClick={() => resolver({ exitCode: ModalExitCode.CANCELED })} withBar={false} text="misc.cancel" />
|
||||
<BsmButton typeColor="primary" className="h-full flex items-center justify-center rounded-md text-center transition-all" onClick={completeModal} withBar={false} text="modals.create-launch-shortcut.valid-btn" />
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
|
||||
@@ -119,7 +119,7 @@ export const DownloadMapsModal: ModalComponent<void, { version: BSVersion; owned
|
||||
setMaps(prev => [...prev, ...maps])
|
||||
|
||||
if (maps.length < tryToLoad) {
|
||||
handleLoadMore();
|
||||
handleLoadMore(false);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -185,9 +185,9 @@ export const DownloadMapsModal: ModalComponent<void, { version: BSVersion; owned
|
||||
setSearchParams(() => searchParamsLocal);
|
||||
};
|
||||
|
||||
const handleLoadMore = () => {
|
||||
const handleLoadMore = (skipLoading: boolean = true) => {
|
||||
|
||||
if(loading){ return; }
|
||||
if(skipLoading && loading){ return; }
|
||||
|
||||
setSearchParams(prev => {
|
||||
return { ...prev, page: prev.page + 1 };
|
||||
|
||||
@@ -8,10 +8,11 @@ type Props<T> = {
|
||||
selectedItemId?: number;
|
||||
selectedItemValue?: T;
|
||||
direction?: CSSProperties["flexDirection"],
|
||||
columnCount?: number;
|
||||
onItemSelected?: (item: RadioItem<T>) => void
|
||||
};
|
||||
|
||||
export function SettingRadioArray<T>({ id, items, selectedItemId, selectedItemValue, onItemSelected, direction = "column" }: Props<T>) {
|
||||
export function SettingRadioArray<T>({ id, items, selectedItemId, selectedItemValue, onItemSelected, direction = "column",columnCount = 1, }: Props<T>) {
|
||||
const t = useTranslation();
|
||||
|
||||
const isSelected = (item: RadioItem<T>) => {
|
||||
@@ -19,17 +20,17 @@ export function SettingRadioArray<T>({ id, items, selectedItemId, selectedItemVa
|
||||
}
|
||||
|
||||
return (
|
||||
<div id={id} className="w-full flex gap-1.5" style={{flexDirection: direction}}>
|
||||
<div id={id} className="w-full grid gap-1.5" style={{flexDirection: direction, gridTemplateColumns: `repeat(${columnCount}, minmax(0, 1fr))`}}>
|
||||
{items.map(i => (
|
||||
<div onClick={() => onItemSelected(i)} key={i.id} className={`py-3 w-full flex cursor-pointer justify-between items-center rounded-md px-2 transition-colors duration-300 ${isSelected(i) ? "bg-light-main-color-3 dark:bg-main-color-3" : "bg-light-main-color-1 dark:bg-main-color-1"} ${i.className}`}>
|
||||
<div onClick={() => onItemSelected(i)} key={i.id} className={`h-12 py-3 w-full flex cursor-pointer justify-between rounded-md px-2 transition-colors duration-300 ${isSelected(i) ? "bg-light-main-color-3 dark:bg-main-color-3" : "bg-light-main-color-1 dark:bg-main-color-1"} ${i.className}`}>
|
||||
<div className="flex items-center">
|
||||
<div className="h-5 rounded-full aspect-square border-2 border-gray-800 dark:border-white p-[3px] mr-2">
|
||||
<motion.span initial={{ scale: 0 }} animate={{ scale: isSelected(i) ? 1 : 0 }} className="h-full w-full block bg-gray-800 dark:bg-white rounded-full" />
|
||||
</div>
|
||||
<h2 className="font-extrabold">{t(i.text)}</h2>
|
||||
<h2 className="font-extrabold text-nowrap">{t(i.text)}</h2>
|
||||
</div>
|
||||
{i.icon && (
|
||||
<div className="flex items-center">
|
||||
<div className="flex items-center text-right">
|
||||
{i.textIcon && <span className="text-sm">{t(i.textIcon)}</span>}
|
||||
{i.icon}
|
||||
</div>
|
||||
|
||||
@@ -12,36 +12,47 @@ import { useService } from "renderer/hooks/use-service.hook";
|
||||
import { lastValueFrom } from "rxjs";
|
||||
import { useWindowControls } from "renderer/hooks/use-window-controls.hook";
|
||||
import { useTranslationV2 } from "renderer/hooks/use-translation.hook";
|
||||
import Tippy from "@tippyjs/react";
|
||||
import { StaticConfigurationService } from "renderer/services/static-configuration.service";
|
||||
import { AutoUpdate } from "shared/models/config";
|
||||
import { UpdateInfo } from "electron-updater";
|
||||
|
||||
function TitleBarTags() {
|
||||
function useVersion() {
|
||||
const ipcService = useService(IpcService);
|
||||
const t = useTranslationV2();
|
||||
|
||||
const [previewVersion, setPreviewVersion] = useState("");
|
||||
const [outdated, setOutdated] = useState(false);
|
||||
const [currentVersion, setCurrentVersion] = useState("");
|
||||
const [latestVersion, setLatestVersion] = useState<UpdateInfo | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const requests: Promise<any>[] = [
|
||||
lastValueFrom(ipcService.sendV2("current-version"))
|
||||
];
|
||||
if (window.electron.platform === "linux") {
|
||||
requests.push(lastValueFrom(ipcService.sendV2("check-update")));
|
||||
}
|
||||
|
||||
Promise.all(requests).then(([ currentVersion, outdated ]) => {
|
||||
handlePrerelease(currentVersion);
|
||||
setOutdated(outdated);
|
||||
Promise.all([
|
||||
lastValueFrom(ipcService.sendV2("current-version")),
|
||||
lastValueFrom(ipcService.sendV2("get-available-update"))
|
||||
]).then(([ currentVersion, latestVersion ]) => {
|
||||
setCurrentVersion(currentVersion);
|
||||
setLatestVersion(latestVersion);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handlePrerelease = (version: string) => {
|
||||
return { currentVersion, latestVersion };
|
||||
}
|
||||
|
||||
function TitleBarTags({ version, latestVersion }: Readonly<{
|
||||
version: string;
|
||||
latestVersion: UpdateInfo | null;
|
||||
}>) {
|
||||
const t = useTranslationV2();
|
||||
|
||||
const previewVersion = (() => {
|
||||
if (version.toLowerCase().includes("alpha")) {
|
||||
return setPreviewVersion("ALPHA");
|
||||
return "ALPHA";
|
||||
}
|
||||
|
||||
if (version.toLowerCase().includes("beta")) {
|
||||
return setPreviewVersion("BETA");
|
||||
return "BETA";
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
})();
|
||||
|
||||
return <>
|
||||
{previewVersion &&
|
||||
@@ -49,7 +60,7 @@ function TitleBarTags() {
|
||||
{previewVersion}
|
||||
</span>
|
||||
}
|
||||
{outdated &&
|
||||
{latestVersion &&
|
||||
<span className="bg-warning-500 text-black rounded-full ml-1 text-[10px] italic px-1 uppercase h-3.5 font-bold">
|
||||
{t.text("title-bar.outdated")}
|
||||
</span>
|
||||
@@ -57,12 +68,65 @@ function TitleBarTags() {
|
||||
</>;
|
||||
}
|
||||
|
||||
function AutoUpdateButton({ latestVersion }: Readonly<{
|
||||
latestVersion: UpdateInfo | null;
|
||||
}>) {
|
||||
const configService = useService(StaticConfigurationService);
|
||||
const ipcService = useService(IpcService);
|
||||
const { text: t } = useTranslationV2();
|
||||
|
||||
const isLinux = window.electron.platform === "linux";
|
||||
|
||||
const updateAndRestart = async () => {
|
||||
await configService.set("auto-update", AutoUpdate.ONCE);
|
||||
await lastValueFrom(ipcService.sendV2("restart-app"));
|
||||
};
|
||||
|
||||
|
||||
const renderTippyContent = () => {
|
||||
return (
|
||||
<div className="p-2">
|
||||
<div>{t("title-bar.update-text", { version: latestVersion.version })}</div>
|
||||
{latestVersion?.version ? (
|
||||
<a href={`https://github.com/Zagrios/bs-manager/releases/tag/v${latestVersion.version}`} target="_blank" className="cursor-pointer underline text-sm hover:text-gray-300">{t("title-bar.see-changelog")}</a>
|
||||
) : null}
|
||||
{!isLinux &&
|
||||
<BsmButton typeColor="primary"
|
||||
className="text-center rounded-md px-2 py-1 mt-2"
|
||||
text={t("title-bar.update-button")}
|
||||
withBar={false}
|
||||
onClick={() => updateAndRestart()}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return latestVersion && (
|
||||
<Tippy
|
||||
zIndex={1000}
|
||||
placement="bottom"
|
||||
content={renderTippyContent()}
|
||||
theme="default"
|
||||
hideOnClick
|
||||
interactive
|
||||
>
|
||||
<BsmButton
|
||||
className="shrink-0 w-11 h-full aspect-square !bg-transparent flex items-start p-0.5"
|
||||
icon="download"
|
||||
withBar={false}
|
||||
/>
|
||||
</Tippy>
|
||||
);
|
||||
}
|
||||
|
||||
export default function TitleBar({ template = "index.html" }: { template: AppWindow }) {
|
||||
const audio = useService(AudioPlayerService);
|
||||
const windowControls = useWindowControls();
|
||||
|
||||
const volume = useObservable(() => audio.volume$, audio.volume);
|
||||
const color = useThemeColor("first-color");
|
||||
const { currentVersion, latestVersion } = useVersion();
|
||||
|
||||
const [maximized, setMaximized] = useState(false);
|
||||
|
||||
@@ -107,7 +171,7 @@ export default function TitleBar({ template = "index.html" }: { template: AppWin
|
||||
<div id="drag-region" className="grow basis-0 h-full">
|
||||
<div id="window-title" className="pl-1">
|
||||
<span className="text-gray-800 dark:text-gray-100 font-bold text-xs italic">BSManager</span>
|
||||
<TitleBarTags />
|
||||
<TitleBarTags version={currentVersion} latestVersion={latestVersion} />
|
||||
</div>
|
||||
</div>
|
||||
<div id="window-controls" className="h-full flex shrink-0 items-center">
|
||||
@@ -117,6 +181,7 @@ export default function TitleBar({ template = "index.html" }: { template: AppWin
|
||||
</div>
|
||||
<BsmButton className="shrink-0 h-[23px] w-[23px] aspect-square !bg-transparent flex items-start" iconClassName={volumeIcon === "volume-down" ? "-translate-x-[1.8px]" : null} icon={volumeIcon} withBar={false} onClick={() => audio.toggleMute()} />
|
||||
</div>
|
||||
<AutoUpdateButton latestVersion={latestVersion} />
|
||||
<button onClick={minimizeWindow} className="text-gray-800 dark:text-gray-200 hover:bg-gray-300 dark:hover:bg-[#4F545C] cursor-pointer w-11 h-full shrink-0 flex justify-center items-center" id="min-button">
|
||||
<svg aria-hidden="false" width="12" height="12" viewBox="0 0 12 12">
|
||||
<rect fill="currentColor" width="10" height="1" x="1" y="6" />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { BsmButton } from "renderer/components/shared/bsm-button.component";
|
||||
import { useObservable } from "renderer/hooks/use-observable.hook";
|
||||
import { useTranslationV2 } from "renderer/hooks/use-translation.hook";
|
||||
@@ -30,29 +30,13 @@ import { CreateCustomLaunchOptionModal } from "renderer/components/modal/modal-t
|
||||
|
||||
type Props = { version: BSVersion };
|
||||
|
||||
export function LaunchSlide({ version }: Props) {
|
||||
const { text: t, element: te } = useTranslationV2();
|
||||
|
||||
const configService = useService(ConfigurationService);
|
||||
function useLaunchMods() {
|
||||
const bsLauncherService = useService(BSLauncherService);
|
||||
const bsDownloader = useService(BsDownloaderService);
|
||||
const versions = useService(BSVersionManagerService);
|
||||
const modal = useService(ModalService);
|
||||
const configService = useService(ConfigurationService);
|
||||
|
||||
const [advancedLaunch, setAdvancedLaunch] = useState(false);
|
||||
const [command, setCommand] = useState<string>(configService.get<string>("launch-command") || "");
|
||||
const customLaunchOptions = useObservable<CustomLaunchOption[]>(() => configService.watch<CustomLaunchOption[]>("custom-launch-options"), []);
|
||||
const [customLaunchModsArgs, setCustomLaunchModsArgs] = useState<string[]>([]);
|
||||
const versionDownloading = useObservable(() => bsDownloader.downloadingVersion$);
|
||||
const [activeLaunchMods, setActiveLaunchMods] = useState<string[]>(configService.get("launch-mods") ?? []);
|
||||
const [pinnedLaunchMods, setPinnedLaunchMods] = useState<string[]>(configService.get("pinned-launch-mods" as DefaultConfigKey) ?? []);
|
||||
|
||||
const versionRunning = useObservable(() => bsLauncherService.versionRunning$);
|
||||
|
||||
useEffect(() => {
|
||||
configService.set("launch-command", command);
|
||||
}, [command]);
|
||||
|
||||
useEffect(() => {
|
||||
configService.set("pinned-launch-mods", pinnedLaunchMods);
|
||||
}, [pinnedLaunchMods])
|
||||
@@ -65,24 +49,66 @@ export function LaunchSlide({ version }: Props) {
|
||||
}
|
||||
}, [activeLaunchMods]);
|
||||
|
||||
const toggleActiveLaunchMod = (checked: boolean, launchMod: string) => checked
|
||||
const toggleActiveLaunchMod = useCallback((checked: boolean, launchMod: string) => checked
|
||||
? setActiveLaunchMods(prev => [...prev, launchMod])
|
||||
: setActiveLaunchMods(prev => prev.filter(mod => mod !== launchMod));
|
||||
: setActiveLaunchMods(prev => prev.filter(mod => mod !== launchMod)),
|
||||
[]);
|
||||
|
||||
const togglePinnedLaunchMod = (pinned: boolean, launchMod: string) => pinned
|
||||
const togglePinnedLaunchMod = useCallback((pinned: boolean, launchMod: string) => pinned
|
||||
? setPinnedLaunchMods(prev => [...prev, launchMod])
|
||||
: setPinnedLaunchMods(prev => prev.filter(mod => mod !== launchMod));
|
||||
: setPinnedLaunchMods(prev => prev.filter(mod => mod !== launchMod)),
|
||||
[]);
|
||||
|
||||
const launchModItems = useMemo<LaunchModItemProps[]>(() => {
|
||||
return {
|
||||
activeLaunchMods,
|
||||
pinnedLaunchMods,
|
||||
toggleActiveLaunchMod,
|
||||
togglePinnedLaunchMod,
|
||||
}
|
||||
}
|
||||
|
||||
let protonLogsPath: string[] = [];
|
||||
if (window.electron.platform === "linux") {
|
||||
protonLogsPath = version.steam
|
||||
? [version.path, "Logs"]
|
||||
: ["BSInstances", version.name, "Logs"];
|
||||
}
|
||||
function useCustomLaunchMods({
|
||||
activeLaunchMods, pinnedLaunchMods,
|
||||
toggleActiveLaunchMod,
|
||||
togglePinnedLaunchMod,
|
||||
}: {
|
||||
activeLaunchMods: string[];
|
||||
pinnedLaunchMods: string[];
|
||||
|
||||
const customOptions = customLaunchOptions?.map<LaunchModItemProps>(option => ({
|
||||
toggleActiveLaunchMod: (checked: boolean, launchMod: string) => void;
|
||||
togglePinnedLaunchMod: (checked: boolean, launchMod: string) => void;
|
||||
}) {
|
||||
const configService = useService(ConfigurationService);
|
||||
const modalService = useService(ModalService);
|
||||
|
||||
const customLaunchOptions = useObservable<CustomLaunchOption[]>(() => (
|
||||
configService.watch<CustomLaunchOption[]>("custom-launch-options")
|
||||
), []);
|
||||
|
||||
const [customLaunchModsArgs, setCustomLaunchModsArgs] = useState<string[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
const command = customLaunchOptions
|
||||
?.map(option => option.data.command || "")
|
||||
.filter(Boolean);
|
||||
setCustomLaunchModsArgs(() => command ?? []);
|
||||
}, [customLaunchOptions]);
|
||||
|
||||
const deleteCustomLaunchOption = useCallback((id: string) => {
|
||||
const newCustomLaunchOptions = (customLaunchOptions ?? []).filter(mode => mode.id !== id);
|
||||
configService.set("custom-launch-options", newCustomLaunchOptions);
|
||||
}, [customLaunchOptions]);
|
||||
|
||||
const saveCustomLaunchOption = useCallback(async (option: Partial<CustomLaunchOption>) => {
|
||||
const result = await modalService.openModal(CreateCustomLaunchOptionModal, { data: option });
|
||||
if(result.exitCode !== ModalExitCode.COMPLETED) { return; }
|
||||
const newCustomLaunchOptions = (customLaunchOptions ?? []).filter(mode => mode.id !== result.data.id);
|
||||
newCustomLaunchOptions.push(result.data);
|
||||
configService.set("custom-launch-options", newCustomLaunchOptions);
|
||||
}, [customLaunchOptions]);
|
||||
|
||||
const getCustomLaunch = useCallback(() => {
|
||||
return customLaunchOptions?.map<LaunchModItemProps>(option => ({
|
||||
id: option.id,
|
||||
label: option.label,
|
||||
active: activeLaunchMods.includes(option.id),
|
||||
@@ -104,6 +130,57 @@ export function LaunchSlide({ version }: Props) {
|
||||
deleteCustomLaunchOption(option.id);
|
||||
},
|
||||
})) ?? [];
|
||||
}, [customLaunchOptions, activeLaunchMods, pinnedLaunchMods])
|
||||
|
||||
return {
|
||||
customLaunchOptions,
|
||||
customLaunchModsArgs,
|
||||
getCustomLaunch,
|
||||
saveCustomLaunchOption
|
||||
};
|
||||
}
|
||||
|
||||
export function LaunchSlide({ version }: Props) {
|
||||
const { text: t, element: te } = useTranslationV2();
|
||||
|
||||
const configService = useService(ConfigurationService);
|
||||
const bsLauncherService = useService(BSLauncherService);
|
||||
const bsDownloader = useService(BsDownloaderService);
|
||||
const versions = useService(BSVersionManagerService);
|
||||
|
||||
const [advancedLaunch, setAdvancedLaunch] = useState(false);
|
||||
const [command, setCommand] = useState<string>(configService.get<string>("launch-command") || "");
|
||||
const versionDownloading = useObservable(() => bsDownloader.downloadingVersion$);
|
||||
const versionRunning = useObservable(() => bsLauncherService.versionRunning$);
|
||||
|
||||
const {
|
||||
activeLaunchMods, pinnedLaunchMods,
|
||||
toggleActiveLaunchMod, togglePinnedLaunchMod,
|
||||
} = useLaunchMods();
|
||||
|
||||
const {
|
||||
customLaunchOptions, customLaunchModsArgs,
|
||||
getCustomLaunch, saveCustomLaunchOption,
|
||||
} = useCustomLaunchMods({
|
||||
activeLaunchMods, pinnedLaunchMods,
|
||||
toggleActiveLaunchMod,
|
||||
togglePinnedLaunchMod,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
configService.set("launch-command", command);
|
||||
}, [command]);
|
||||
|
||||
const launchModItems = useMemo<LaunchModItemProps[]>(() => {
|
||||
|
||||
let protonLogsPath: string[] = [];
|
||||
if (window.electron.platform === "linux") {
|
||||
protonLogsPath = version.steam
|
||||
? [version.path, "Logs"]
|
||||
: ["BSInstances", version.name, "Logs"];
|
||||
}
|
||||
|
||||
const customOptions = getCustomLaunch();
|
||||
|
||||
return [
|
||||
{
|
||||
@@ -187,19 +264,6 @@ export function LaunchSlide({ version }: Props) {
|
||||
return safeLt(version?.BSVersion, versions.getRecommendedVersion()?.BSVersion);
|
||||
}, [version]);
|
||||
|
||||
const saveCustomLaunchOption = async (option: Partial<CustomLaunchOption>) => {
|
||||
const result = await modal.openModal(CreateCustomLaunchOptionModal, { data: option });
|
||||
if(result.exitCode !== ModalExitCode.COMPLETED) { return; }
|
||||
const newCustomLaunchOptions = (customLaunchOptions ?? []).filter(mode => mode.id !== result.data.id);
|
||||
newCustomLaunchOptions.push(result.data);
|
||||
configService.set("custom-launch-options", newCustomLaunchOptions);
|
||||
}
|
||||
|
||||
const deleteCustomLaunchOption = (id: string) => {
|
||||
const newCustomLaunchOptions = (customLaunchOptions ?? []).filter(mode => mode.id !== id);
|
||||
configService.set("custom-launch-options", newCustomLaunchOptions);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full shrink-0 items-center relative flex flex-col justify-start overflow-hidden">
|
||||
<div className="flex flex-col gap-3 justify-center items-center mb-4">
|
||||
|
||||
@@ -48,6 +48,7 @@ import { InstallationLocationService } from "renderer/services/installation-loca
|
||||
import { AutoUpdaterService } from "renderer/services/auto-updater.service";
|
||||
import { OculusDownloaderService } from "renderer/services/bs-version-download/oculus-downloader.service";
|
||||
import { DISCORD_URL } from "shared/constants";
|
||||
import { AutoUpdate } from "shared/models/config";
|
||||
|
||||
export function SettingsPage() {
|
||||
|
||||
@@ -201,11 +202,18 @@ export function SettingsPage() {
|
||||
const fileChooserRes = await lastValueFrom(ipcService.sendV2("choose-folder"));
|
||||
|
||||
if (!fileChooserRes.canceled && fileChooserRes.filePaths?.length) {
|
||||
|
||||
const newInstallationPath = fileChooserRes.filePaths[0];
|
||||
|
||||
if(newInstallationPath === installationFolder){
|
||||
return;
|
||||
}
|
||||
|
||||
progressBarService.showFake(0.008);
|
||||
|
||||
notificationService.notifySuccess({ title: "notifications.settings.move-folder.success.titles.transfer-started", desc: "notifications.settings.move-folder.success.descs.transfer-started" });
|
||||
|
||||
lastValueFrom(installationLocationService.setInstallationFolder(fileChooserRes.filePaths[0], true)).then(res => {
|
||||
lastValueFrom(installationLocationService.setInstallationFolder(newInstallationPath, true)).then(res => {
|
||||
|
||||
progressBarService.complete();
|
||||
progressBarService.hide();
|
||||
@@ -497,7 +505,7 @@ export function SettingsPage() {
|
||||
</SettingContainer>
|
||||
|
||||
<SettingContainer title="pages.settings.language.title" description="pages.settings.language.description">
|
||||
<SettingRadioArray items={languagesItems} selectedItemValue={languageSelected} onItemSelected={handleChangeLanguage} />
|
||||
<SettingRadioArray items={languagesItems} selectedItemValue={languageSelected} onItemSelected={handleChangeLanguage} columnCount={2} />
|
||||
</SettingContainer>
|
||||
|
||||
<SettingContainer title="pages.settings.patreon.title" description="pages.settings.patreon.description">
|
||||
@@ -546,11 +554,17 @@ function AdvancedSettings() {
|
||||
const [hardwareAccelerationEnabled, setHardwareAccelerationEnabled] = useState(true);
|
||||
const [useSymlink, setUseSymlink] = useState(false);
|
||||
const [useSystemProxy, setUseSystemProxy] = useState(false);
|
||||
const [autoUpdate, setAutoUpdate] = useState<AutoUpdate>(AutoUpdate.NEVER);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
staticConfig.get("disable-hadware-acceleration").then(disabled =>setHardwareAccelerationEnabled(() => disabled !== true));
|
||||
staticConfig.get("use-symlinks").then(useSymlinks => setUseSymlink(() => useSymlinks));
|
||||
staticConfig.get("use-system-proxy").then(useSystemProxy => setUseSystemProxy(() => useSystemProxy));
|
||||
|
||||
if (window.electron.platform === "win32") {
|
||||
staticConfig.get("use-symlinks").then(useSymlinks => setUseSymlink(() => useSymlinks));
|
||||
staticConfig.get("use-system-proxy").then(useSystemProxy => setUseSystemProxy(() => useSystemProxy));
|
||||
staticConfig.get("auto-update").then(res => setAutoUpdate(() => res ?? AutoUpdate.ALWAYS));
|
||||
}
|
||||
}, []);
|
||||
|
||||
const onChangeHardwareAcceleration = async (newHardwareAccelerationEnabled: boolean) => {
|
||||
@@ -631,12 +645,43 @@ function AdvancedSettings() {
|
||||
setUseSystemProxy(() => newUseSystemProxy);
|
||||
}
|
||||
|
||||
const advancedItems: Item[] = [{
|
||||
const onChangeAutoUpdate = async (value: boolean) => {
|
||||
if (window.electron.platform !== "win32") {
|
||||
return;
|
||||
}
|
||||
|
||||
const newAutoUpdate = value ? AutoUpdate.ALWAYS : AutoUpdate.NEVER;
|
||||
const { error } = await tryit(() => staticConfig.set("auto-update", newAutoUpdate));
|
||||
|
||||
if (error) {
|
||||
notification.notifyError({
|
||||
title: "notifications.types.error",
|
||||
desc: "pages.settings.advanced.auto-update.error-notification.message",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setAutoUpdate(() => newAutoUpdate);
|
||||
}
|
||||
|
||||
const advancedItems: Item[] = [];
|
||||
|
||||
if (window.electron.platform === "win32") {
|
||||
advancedItems.push({
|
||||
checked: autoUpdate === AutoUpdate.ALWAYS,
|
||||
text: t.text("pages.settings.advanced.auto-update.title"),
|
||||
desc: t.text("pages.settings.advanced.auto-update.description"),
|
||||
onChange: onChangeAutoUpdate
|
||||
});
|
||||
}
|
||||
|
||||
advancedItems.push({
|
||||
checked: hardwareAccelerationEnabled,
|
||||
text: t.text("pages.settings.advanced.hardware-acceleration.title"),
|
||||
desc: t.text("pages.settings.advanced.hardware-acceleration.description"),
|
||||
onChange: onChangeHardwareAcceleration
|
||||
}];
|
||||
});
|
||||
|
||||
if (window.electron.platform === "win32") {
|
||||
advancedItems.push({
|
||||
checked: useSymlink,
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
|
||||
export enum AutoUpdate {
|
||||
ALWAYS = "always",
|
||||
// When "Update and restart" is clicked
|
||||
ONCE = "once",
|
||||
NEVER = "never",
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import { LocalBPList, LocalBPListsDetails } from "../playlists/local-playlist.mo
|
||||
import { StaticConfigGetIpcRequestResponse, StaticConfigKeys, StaticConfigSetIpcRequest } from "main/services/static-configuration.service";
|
||||
import { BbmFullMod, BbmModVersion, ExternalMod } from "../mods/mod.interface";
|
||||
import { OculusDownloadInfo } from "main/services/bs-version-download/bs-oculus-downloader.service";
|
||||
import { UpdateInfo } from "electron-updater";
|
||||
|
||||
export type IpcReplier<T> = (data: Observable<T>) => void;
|
||||
|
||||
@@ -122,6 +123,7 @@ export interface IpcChannelMapping {
|
||||
/* ** launcher-ipcs ** */
|
||||
"download-update": { request: void, response: Progression };
|
||||
"check-update": { request: void, response: boolean };
|
||||
"get-available-update": { request: void, response: UpdateInfo | null };
|
||||
"install-update": { request: void, response: void };
|
||||
|
||||
/* ** model-saber.ipcs ** */
|
||||
|
||||
Reference in New Issue
Block a user