mirror of
https://github.com/Zagrios/bs-manager.git
synced 2026-07-03 14:08:25 +02:00
Compare commits
35 Commits
v1.1.0
...
v1.1.1-alpha.1
| Author | SHA1 | Date | |
|---|---|---|---|
| b7ac9901c4 | |||
| 962f1d8aae | |||
| 66b834bd31 | |||
| 0214a518ee | |||
| 40137b15fa | |||
| d30e203d29 | |||
| 7810b31d13 | |||
| 9720335adf | |||
| 1af3ef7565 | |||
| 7da09c117f | |||
| 9ff47b6895 | |||
| ed4137949d | |||
| f833f28930 | |||
| 97fa44cd5d | |||
| 6562f6c764 | |||
| 1177091cf0 | |||
| a785828a6b | |||
| 39b4a48fef | |||
| a62038450f | |||
| 070eb5b796 | |||
| bc1e809545 | |||
| dee49c964a | |||
| 4aa1521e88 | |||
| 4a53917f27 | |||
| 75c849d203 | |||
| 3e3cffc6bc | |||
| 2d211e3c05 | |||
| e5142fe131 | |||
| 89312f0bf9 | |||
| f2e6fa486a | |||
| 4f0f80bbc2 | |||
| 31e5c0ed10 | |||
| 9a2afe6820 | |||
| c93f9a7041 | |||
| 5401248286 |
+1
-1
@@ -1 +1 @@
|
||||
|
||||
patreon: bsmanager
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
@@ -517,5 +517,21 @@
|
||||
"ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans//32055887/8f978bbf292810004bca12523e76be8a7eec8c4e.png",
|
||||
"ReleaseDate": "1677593822",
|
||||
"year": "2023"
|
||||
},
|
||||
{
|
||||
"BSVersion": "1.29.0",
|
||||
"BSManifest": "3341527958186345367",
|
||||
"ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/6169409105082976092",
|
||||
"ReleaseImg": "https://cdn.cloudflare.steamstatic.com/steamcommunity/public/images/clans//32055887/255fd49cd96042b97089f754609be291293e783f.png",
|
||||
"ReleaseDate": "1680188760",
|
||||
"year": "2023"
|
||||
},
|
||||
{
|
||||
"BSVersion": "1.29.1",
|
||||
"BSManifest": "886973241045584398",
|
||||
"ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/6169409105101272202",
|
||||
"ReleaseImg": "https://cdn.akamai.steamstatic.com/steamcommunity/public/images/clans/32055887/c328e407367e9914abaf92f609501877ee5abb63.png",
|
||||
"ReleaseDate": "1680623885",
|
||||
"year": "2023"
|
||||
}
|
||||
]
|
||||
@@ -1,3 +1,5 @@
|
||||
[
|
||||
{"username": "Shidorien"}
|
||||
{"username": "Shidorien"},
|
||||
{"username": "Doloro", "type": "gold"},
|
||||
{"username": "iPixelGalaxy", "type": "diamond", "link": "https://twitch.tv/ipixelgalaxy"}
|
||||
]
|
||||
@@ -0,0 +1,741 @@
|
||||
{
|
||||
"misc": {
|
||||
"download": "Laden",
|
||||
"verify": "Prüfen",
|
||||
"launch": "Starten",
|
||||
"mods": "Mods",
|
||||
"maps": "Maps",
|
||||
"playlists": "Playlisten",
|
||||
"models": "Modelle",
|
||||
"cancel": "Abbruch",
|
||||
"delete": "Löschen",
|
||||
"accept": "Akzeptieren",
|
||||
"refuse": "Ablehnen",
|
||||
"apply": "Übernehmen"
|
||||
},
|
||||
"nav-bar":{
|
||||
"add-version": "Version Hinzufügen",
|
||||
"settings": "Einstellungen"
|
||||
},
|
||||
"pages": {
|
||||
"shared-maps":{
|
||||
"title": "Geteilte Maps"
|
||||
},
|
||||
"version-viewer": {
|
||||
"launch-mods": {
|
||||
"oculus": "Oculus Modus",
|
||||
"oculus-description": "Wenn du Beat Saber über Steam benutzt, es ermöglicht dir Oculus's VR-Composer zu nutzen ohne SteamVR, um bessere Leistung zu erhalten. Dies wird nicht benötigt für Oculus Headsets",
|
||||
"desktop": "Desktop Modus",
|
||||
"desktop-description": "Auf diese Weise kannst du WASD und die Maus verwenden, um im Spiel durch das Menü zu navigieren. Dies erleichtert das Testen erheblich, da Du dein Headset nicht aufsetzen musst!",
|
||||
"debug": "Debug Modus",
|
||||
"debug-description": "Aktiviert das Ausgabeprotokollfenster für IPA. Dies zeigt die Debug-Konsole, die Mods verwenden.",
|
||||
"advanced-launch": {
|
||||
"button": "Erweiterter Start",
|
||||
"placeholder": "Weitere Argumente Bspw: --revert; --nowait"
|
||||
}
|
||||
},
|
||||
"maps": {
|
||||
"title": "Geteilte Maps",
|
||||
"search-bar": {
|
||||
"search-placeholder": "Suche eine Map",
|
||||
"filters-btn": "Filter",
|
||||
"dropdown": {
|
||||
"export-maps": "Maps Exportieren",
|
||||
"delete-maps": "Maps Löschen"
|
||||
}
|
||||
},
|
||||
"tabs": {
|
||||
"maps": {
|
||||
"actions": {
|
||||
"add-maps": {
|
||||
"text": "Hinzufügen"
|
||||
},
|
||||
"link-maps": {
|
||||
"tooltips": {
|
||||
"link": "Maps Verknüpfen",
|
||||
"unlink": "Maps Verknüpfung aufheben"
|
||||
}
|
||||
}
|
||||
},
|
||||
"empty-maps":{
|
||||
"text":"Keine Maps",
|
||||
"button":"Maps Herunterladen"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"mods": {
|
||||
"loading-mods": "Lade Mods...",
|
||||
"no-internet": "Kein Internet",
|
||||
"mods-not-available": "Für diese Version von Beat Saber sind noch keine Mods verfügbar",
|
||||
"buttons": {
|
||||
"more-infos": "Mehr Infos",
|
||||
"install-or-update": "Installieren oder aktualisieren"
|
||||
},
|
||||
"mods-grid": {
|
||||
"header-bar": {
|
||||
"name": "Name",
|
||||
"installed": "Installiert",
|
||||
"latest": "Neueste",
|
||||
"description": "Beschreibung",
|
||||
"dropdown": {
|
||||
"uninstall-all": "Alle deinstallieren"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"dropdown": {
|
||||
"open-folder": "Ordner Öffnen",
|
||||
"verify-files": "Daten Prüfen",
|
||||
"clone": "Klonen",
|
||||
"edit": "Bearbeiten",
|
||||
"uninstall": "Entfernen",
|
||||
"shared-folders": "Geteilte Ordner"
|
||||
}
|
||||
},
|
||||
"available-versions": {
|
||||
"title": "Wähle eine Version",
|
||||
"steam-release": "Versions Seite",
|
||||
"dropdown": {
|
||||
"refresh": "Liste aktualisieren",
|
||||
"import-version" :"Importiere eine Version"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"steam": {
|
||||
"title": "Steam",
|
||||
"description": "Wenn Du dich von Steam abmeldest, musst Du dich erneut anmelden, um eine neue Version von Beat Saber herunterzuladen.",
|
||||
"logout": "Abmelden"
|
||||
},
|
||||
"appearance": {
|
||||
"title": "Aussehen",
|
||||
"description": "Wähle Zwei von den Hauptfarben von BSManager",
|
||||
"reset": "Zurücksetzen",
|
||||
"sub-title": "Design",
|
||||
"themes": {
|
||||
"dark": "Dunkel",
|
||||
"light": "Hell",
|
||||
"os": "Mit Computer Synchronisieren"
|
||||
}
|
||||
},
|
||||
"installation-folder": {
|
||||
"title": "Installations Ordner",
|
||||
"description": "Ändern Sie den Standardordner für Beat Saber-Versionen und andere kommende Funktionen.",
|
||||
"choose-folder": "Ordner Wählen"
|
||||
},
|
||||
"additional-content": {
|
||||
"title": "Zusätzlicher Inhalt",
|
||||
"description": "Mit zusätzlichen Inhalten kannst du BeatSaber anpassen!",
|
||||
"deep-links": {
|
||||
"sub-title": "OneClick-Installationen"
|
||||
}
|
||||
},
|
||||
"language": {
|
||||
"title": "Sprache",
|
||||
"description": "Wähle eine Sprache.",
|
||||
"languages": {
|
||||
"en-EN": "English, UK",
|
||||
"en-US": "English, US",
|
||||
"fr-FR": "Français",
|
||||
"es-ES": "Español",
|
||||
"de-DE": "Deutsch",
|
||||
"translated": {
|
||||
"en-EN": "Englisch, Vereinigtes Königreich",
|
||||
"en-US": "Englisch, USA",
|
||||
"fr-FR": "Französisch",
|
||||
"es-ES": "Spanisch",
|
||||
"de-DE": "Deutsch"
|
||||
}
|
||||
}
|
||||
},
|
||||
"patreon": {
|
||||
"title": "BSManager Unterstützen 💖",
|
||||
"description": "Unterstütze das Projekt und helfe uns, BSManager kontinuierlich zu verbessern.",
|
||||
"buttons": {
|
||||
"support": "BSManager Unterstützen 🥰",
|
||||
"supporters": "Unterstützer 👀"
|
||||
},
|
||||
"view": {
|
||||
"no-supporters": "Noch keine Unterstützer",
|
||||
"sponsors": "Sponsoren",
|
||||
"supporters": "Unterstützer"
|
||||
}
|
||||
},
|
||||
"discord": {
|
||||
"description": "Trete der BSManager-Community bei, indem Du uns in unseren sozialen Medien folgst!"
|
||||
},
|
||||
"contribution": {
|
||||
"description": "Schlage neue Funktionen vor, oder melde einen Fehler und BSManager zu verbessern!",
|
||||
"buttons": {
|
||||
"request-features": "Funktion Vorschlagen",
|
||||
"report-bug": "Fehler melden",
|
||||
"open-logs": "Protokolle öffnen"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
"types": {
|
||||
"error": "🚨 Fehler",
|
||||
"warning": "⚠️ Warnung",
|
||||
"success": "🎉 Erfolg"
|
||||
},
|
||||
"common": {
|
||||
"msg": {
|
||||
"error-occurred": "Ein Fehler ist aufgetreten"
|
||||
}
|
||||
},
|
||||
"shared": {
|
||||
"errors": {
|
||||
"titles": {
|
||||
"operation-running": "Vorgang läuft",
|
||||
"no-internet": "Kein Internet"
|
||||
},
|
||||
"msg": {
|
||||
"operation-running": "Warte auf das Ende des Aktuellen Vorgangs und versuche es erneut",
|
||||
"no-internet": "Überprüfe deine Verbindung und versuche es erneut."
|
||||
}
|
||||
}
|
||||
},
|
||||
"bs-download": {
|
||||
"success": {
|
||||
"titles": {
|
||||
"download-success": "Erfolgreich Heruntergeladen",
|
||||
"verification-finished": "Verifikation Erfolgreich"
|
||||
}
|
||||
},
|
||||
"warnings": {
|
||||
"msg": {
|
||||
"ManifestChecksum": "Die zuvor heruntergeladene Manifest stimmt nicht mit dem neuen überein 🤔",
|
||||
"ConnectionTimeout": "Deine Internetverbindung scheint instabil zu sein 🥶",
|
||||
"ConnectionLost": "Die Verbindung wurde unterbrochen, versuche es erneut...",
|
||||
"ConnectionError": "Kann keine Verbindung zu Steam herstellen, versuche es erneut...",
|
||||
"Unknown": "Etwas Seltsames ist passiert 🤔 Deine Verbindung ist wahrscheinlich instabil."
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"titles":{
|
||||
"dotnet-required": ".NET 6 Benötigt"
|
||||
},
|
||||
"msg": {
|
||||
"401": "Steam scheint uns BeatSaber nicht herunterladen zu lassen 😢",
|
||||
"404": "Steam-Server können nicht kontaktiert werden.",
|
||||
"Password": "Passwort ist ungültig.",
|
||||
"InvalidCredentials": "Deine E-Mail oder Passwort ist ungültig 😴",
|
||||
"InvalidPassword": "Dein Passwort ist ungültig 😕",
|
||||
"NoManifest": "Es wurde keine Manifest gefunden",
|
||||
"DirectoryCreate": "Die erforderlichen Ordner können nicht installiert werden.",
|
||||
"NotAvailableApp": "Versuchst du, Beat Saber herunterzuladen, wenn Du es nicht hast? 🤣",
|
||||
"DepotNotFound": "BeatSaber kann nicht heruntergeladen werden 😥 versuche es später noch einmal 😕",
|
||||
"NotCompleted": "Der Download konnte nicht beendet werden ¯\\_(ツ)_/¯",
|
||||
"InvalidManifest": "BeatSaber kann nicht heruntergeladen werden 😥 versuche es später noch einmal 😕",
|
||||
"NoValidKey": "BeatSaber kann nicht heruntergeladen werden 😥 versuche es später noch einmal 😕",
|
||||
"NoManifestCode": "BeatSaber kann nicht heruntergeladen werden 😥 versuche es später noch einmal 😕",
|
||||
"Unknown": "Ein Unbekannter Fehler ist aufgetreten ¯\\_(ツ)_/¯",
|
||||
"NoServer": "Steam-Server können nicht kontaktiert werden.",
|
||||
"NotAllowed": "Anscheinend darfst Du BeatSaber nicht herunterladen 🥱",
|
||||
"ConnectionTimeout": "Kann keine Verbindung zu Steam herstellen 😕",
|
||||
"SteamLib": "Wenn dieser Fehler auftritt, melde den Fehler auf Github mit Protokollen, bitte.",
|
||||
"ConnectionError": "Kann nach 10 Versuchen keine Verbindung zu Steam herstellen 🤯",
|
||||
"LicenceError": "Die Liste der Lizenzen kann nicht abgerufen werden.",
|
||||
"verification-failed": "Verifikation fehlgeschlagen, versuche es später erneut.",
|
||||
"RateLimitExceeded": "Du hast es zu oft versucht, warte eine Weile und versuche es später erneut.",
|
||||
"AlreadyDownloading": "Ein Download ist bereits im Gange",
|
||||
"no-internet": "Keine Internet Verbindung",
|
||||
"dotnet-required": ".NET 6 Runtime muss installiert sein, um eine Version von BeatSaber herunterzuladen. Lade es herunter, indem Du auf die Schaltfläche unten klickst."
|
||||
},
|
||||
"actions":{
|
||||
"download-dotnet": ".NET 6 Herunterladen"
|
||||
}
|
||||
}
|
||||
},
|
||||
"bs-import-version":{
|
||||
"success":{
|
||||
"start-import": {
|
||||
"title": "Import im Gange 👌",
|
||||
"desc": "Der Import kann je nach Konfiguration einige Minuten dauern."
|
||||
},
|
||||
"imported": {
|
||||
"title": "Versionen Importiert 🎉"
|
||||
}
|
||||
},
|
||||
"errors":{
|
||||
"import-error":{
|
||||
"desc": "Überprüfe, ob der ausgewählte Ordner eine BeatSaber-Installation ist."
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"move-folder": {
|
||||
"success": {
|
||||
"titles": {
|
||||
"transfer-started": "Übertragung läuft",
|
||||
"transfer-finished": "Transfer abgeschlossen"
|
||||
},
|
||||
"descs": {
|
||||
"transfer-started": "Die Übertragung hat begonnen und kann je nach Konfiguration mehrere Minuten dauern."
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"titles": {
|
||||
"transfer-failed": "Transfer fehlgeschlagen 😕"
|
||||
},
|
||||
"descs": {
|
||||
"COPY_TO_SUBPATH": "Der Zielordner darf kein Unterordner des Quellordners sein.",
|
||||
"restore-linked-folders": "Beim Wiederherstellen von freigegebenen Ordnern ist ein Fehler aufgetreten. Sie können sie dennoch manuell über das Menü 'Freigegebene Ordner' auf der Versionsseite wiederherstellen."
|
||||
}
|
||||
}
|
||||
},
|
||||
"steam": {
|
||||
"success": {
|
||||
"titles": {
|
||||
"logout": "Von Steam getrennt."
|
||||
}
|
||||
}
|
||||
},
|
||||
"additional-content":{
|
||||
"deep-link": {
|
||||
"select-all": "Alle Auswählen",
|
||||
"activation":{
|
||||
"success":{
|
||||
"title":"OneClick aktiviert!",
|
||||
"description":"OneClick-Installationen wurden aktiviert."
|
||||
},
|
||||
"error":{
|
||||
"description":"OneClick-Installationen konnten nicht aktiviert werden."
|
||||
}
|
||||
},
|
||||
"deactivation":{
|
||||
"success":{
|
||||
"title":"OneClick deaktiviert!",
|
||||
"description":"OneClick-Installationen wurden deaktiviert."
|
||||
},
|
||||
"error":{
|
||||
"description":"Ein Unbekannter Fehler ist aufgetreten."
|
||||
}
|
||||
},
|
||||
"check-all-enabled":{
|
||||
"title": "OneClick deaktiviert",
|
||||
"description": "Eine oder mehrere OneClick-Installationen sind deaktiviert. Gehe in die Einstellungen, um sie zu aktivieren.",
|
||||
"actions":{
|
||||
"settings": "Einstellungen",
|
||||
"not-remind": "Erinnere mich nicht"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"bs-launch": {
|
||||
"success": {
|
||||
"titles": {
|
||||
"launching": "Starte...🚀"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"titles": {
|
||||
"UNABLE_TO_LAUNCH": "Start nicht möglich",
|
||||
"STEAM_NOT_RUNNING": "Steam läuft nicht",
|
||||
"OCULUS_NOT_RUNNING": "Oculus läuft nicht",
|
||||
"BS_ALREADY_RUNNING": "BeatSaber läuft bereits",
|
||||
"EXE_NOT_FINDED": "Dateien Fehlen",
|
||||
"EXIT": "Abrupter Stop"
|
||||
},
|
||||
"msg": {
|
||||
"STEAM_NOT_RUNNING": "Steam muss laufen, um BeatSaber zu starten.",
|
||||
"OCULUS_NOT_RUNNING": "Oculus muss ausgeführt werden, um BeatSaber zu starten.",
|
||||
"BS_ALREADY_RUNNING": "Schließe BeatSaber, bevor Du es erneut startest.",
|
||||
"EXE_NOT_FINDED": "Einige Dateien scheinen zu fehlen, versuche die Dateien zu überprüfen.",
|
||||
"EXIT": "BeatSaber stoppte abrupt, versuche die Dateien zu überprüfen."
|
||||
},
|
||||
"actions":{
|
||||
"STEAM_NOT_RUNNING": "Starte Steam"
|
||||
}
|
||||
}
|
||||
},
|
||||
"steam":{
|
||||
"steam-launching":{
|
||||
"title": "Steam startet!",
|
||||
"description": "Abhängig von deiner Konfiguration kann es einige Zeit dauern, bis Steam gestartet wird."
|
||||
}
|
||||
},
|
||||
"custom-version": {
|
||||
"errors": {
|
||||
"titles": {
|
||||
"CantEditSteam": "Kann nicht bearbeitet werden",
|
||||
"CantRename": "Umbenennen unmöglich",
|
||||
"VersionAlreadExist": "Diese Version existiert bereits",
|
||||
"CantClone": "Klonen unmöglich"
|
||||
},
|
||||
"msg": {
|
||||
"CantEditSteam": "Die Steam-Version kann nicht bearbeitet werden. Du kannst es aber klonen."
|
||||
}
|
||||
},
|
||||
"success": {
|
||||
"titles": {
|
||||
"CloningFinished": "Klonen abgeschlossen 🎉"
|
||||
}
|
||||
}
|
||||
},
|
||||
"mods": {
|
||||
"install-mods": {
|
||||
"titles": {
|
||||
"success": "Mods Installiert 🎉",
|
||||
"warning": "Mods Installiert 🤔"
|
||||
},
|
||||
"msg": {
|
||||
"success": "Alle Mods wurden installiert.",
|
||||
"warning": "Eine oder mehr Mods konnten nicht installiert werden.",
|
||||
"errors": {
|
||||
"no-mods": "Keine Mods zum installieren.",
|
||||
"cannot-install-bsipa": "BSIPA installation fehlgeschlagen 😨"
|
||||
}
|
||||
}
|
||||
},
|
||||
"uninstall-mod": {
|
||||
"titles": {
|
||||
"success": "Mod entfernt 🎉"
|
||||
},
|
||||
"msg": {
|
||||
"errors": {
|
||||
"no-mods": "Diese Mod kann nicht installiert werden 😑"
|
||||
}
|
||||
}
|
||||
},
|
||||
"uninstall-all-mods": {
|
||||
"titles": {
|
||||
"success": "Mods entfernt 🎉"
|
||||
},
|
||||
"msg": {
|
||||
"success": "Alle Mods wurden entfernt.",
|
||||
"errors": {
|
||||
"no-mods": "Keine Mods sind in dieser Version installiert 😑"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"maps":{
|
||||
"one-click-install":{
|
||||
"success":"Map installation abegschlossen",
|
||||
"error":"Ein Fehler ist aufgetreten während der installation der Map"
|
||||
}
|
||||
},
|
||||
"playlists":{
|
||||
"one-click-install":{
|
||||
"success":"Playlist installation abgeschlossen",
|
||||
"error":"Ein Fehler ist aufgetreten während der installation der Playlist"
|
||||
}
|
||||
},
|
||||
"models":{
|
||||
"one-click-install":{
|
||||
"success":"Modell installation abgeschlossen",
|
||||
"error":"Ein Fehler ist aufgetreten während der installation des Modells"
|
||||
}
|
||||
},
|
||||
"shared-folder": {
|
||||
"info": {
|
||||
"userdata-backup-created":{
|
||||
"title": "Sicherung erstellt",
|
||||
"msg": "Das Freigeben des Ordners „UserData“ kann zu Fehlern führen. Bei Problemen trenne den Ordner, um die Sicherung wiederherzustellen"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"modals": {
|
||||
"misc":{
|
||||
"remember-my-choice": "Merke meine Wahl"
|
||||
},
|
||||
"guard": {
|
||||
"title": "Steam Guard",
|
||||
"inputs": {
|
||||
"guard-code": {
|
||||
"label": "Guard-Code",
|
||||
"placeholder": "Gebe deinen Guard-Code ein"
|
||||
}
|
||||
},
|
||||
"buttons": {
|
||||
"submit": "Anmelden"
|
||||
}
|
||||
},
|
||||
"steam-login": {
|
||||
"title": "Steam Login",
|
||||
"inputs": {
|
||||
"username": {
|
||||
"label": "Nutzername",
|
||||
"placeholder": "Gebe deinen Nutzernamen ein"
|
||||
},
|
||||
"password": {
|
||||
"label": "Passwort",
|
||||
"placeholder": "Gebe dein Passwort ein"
|
||||
},
|
||||
"stay": {
|
||||
"label": "Verbunden bleiben"
|
||||
}
|
||||
},
|
||||
"why-credentials": "Warum braucht ihr meine Anmeldeinformationen?",
|
||||
"buttons": {
|
||||
"submit": "Anmelden"
|
||||
}
|
||||
},
|
||||
"steam-credentials":{
|
||||
"title": "Steam Anmeldeinformationen",
|
||||
"p-1": "Die Anmeldeinformationen werden nur zum Herunterladen des Spiels verwendet, da Steam überprüfen muss, ob Du das Spiel bezahlt hast, um es herunterladen zu dürfen. Sie werden nicht gespeichert und direkt an DepotDownloader weitergegeben. Wenn Du deine Anmeldeinformationen nicht eingeben möchtest, kannst Du diesem Tutorial folgen:",
|
||||
"p-2": "und klicke dann auf das Zahnradsymbol in der oberen rechten Ecke und wähle \"Version importieren\", wähle den Ordner aus, in den Beat Saber heruntergeladen wurde (Wenn Du dem obigen Tutorial folgst, solltest Du den richtigen Speicherort haben)"
|
||||
},
|
||||
"bs-import-version":{
|
||||
"title": "Importiere eine Version",
|
||||
"description": "Importiert eine Version von BeatSaber zur Verwendung mit BSManager. Der Import kopiert den ausgewählten BeatSaber-Installationsordner in den BSManager-Versionsordner.",
|
||||
"buttons":{
|
||||
"submit": "Importiere eine Version"
|
||||
}
|
||||
},
|
||||
"bs-uninstall": {
|
||||
"title": "Deinstallieren",
|
||||
"description": "Möchtest du BeatSaber {version} wirklich deinstallieren? Du musst es erneut herunterladen, um es zu spielen.",
|
||||
"buttons": {
|
||||
"submit": "Deinstallieren"
|
||||
}
|
||||
},
|
||||
"install-folder": {
|
||||
"title": "Installation Ordner",
|
||||
"description": "Das Ändern des Standardinstallationsordners führt dazu, dass alle installierten Daten in den neuen Ordner verschoben werden.",
|
||||
"buttons": {
|
||||
"submit": "Ordner Wählen"
|
||||
}
|
||||
},
|
||||
"edit-version": {
|
||||
"title": "Version ändern",
|
||||
"buttons": {
|
||||
"submit": "Ändern"
|
||||
}
|
||||
},
|
||||
"clone-version": {
|
||||
"title": "Version Klonen",
|
||||
"description": "Durch das Klonen der Version kannst Du zusätzlichen BeatSaber-Inhalt zwischen zwei Versionen trennen.",
|
||||
"inputs": {
|
||||
"name": {
|
||||
"label": "Name",
|
||||
"placeholder": "Versions Name"
|
||||
},
|
||||
"color": {
|
||||
"label": "Farbe"
|
||||
}
|
||||
},
|
||||
"buttons": {
|
||||
"submit": "Klonen"
|
||||
}
|
||||
},
|
||||
"uninstall-mod": {
|
||||
"title": "Entfernen",
|
||||
"description": "Bist du sicher das du die Mod {mod} entfernen möchtest? Es könnte dafür sorgen, dass andere installationen nicht mehr richtig funktionieren.",
|
||||
"description-bsipa": "Bist du sicher, dass du BSIPA entfernen möchtest? Dannach werden deine Mods nicht mehr funktionieren."
|
||||
},
|
||||
"uninstall-all-mods": {
|
||||
"title": "Entferne Alle Mods",
|
||||
"description": "Bist du sicher das du alle Mods von der Version {version} entfernen möchtest? Diese Aktion kann nicht rückgängig gemacht werden."
|
||||
},
|
||||
"maps-actions": {
|
||||
"delete-maps": {
|
||||
"title": {
|
||||
"single": "Map Entfernen?",
|
||||
"multiple": "Maps Entfernen?"
|
||||
},
|
||||
"desc": {
|
||||
"single": "Bist du sicher das du die Map {name} entfernen willst?",
|
||||
"multiple": "Bist du sicher das du alle {nb} maps entfernen willst?"
|
||||
},
|
||||
"info": {
|
||||
"desc": {
|
||||
"single": "Diese Map ist teil der geteilten Maps",
|
||||
"multiple": "Diese Maps sind teil der geteilten Maps"
|
||||
},
|
||||
"title": {
|
||||
"single": "Diese Map wird auch aus Versionen entfernt, die geteilte Maps verwenden",
|
||||
"multiple": "Diese Maps werden auch aus Versionen entfernt, die geteilte Maps verwenden"
|
||||
}
|
||||
}
|
||||
},
|
||||
"link-maps": {
|
||||
"title": "Maps Verknüpfen",
|
||||
"desc": "Das Verknüpfen von Maps ermöglicht es, Maps zwischen allen Versionen zu teilen. Nach der Verknüpfung profitiert diese Version von den geteilten Maps",
|
||||
"info": "Das Hinzufügen und Löschen von Maps wird ebenfalls geteilt",
|
||||
"keep-maps": {
|
||||
"label": "Maps behalten",
|
||||
"title": "Maps behalten verschiebt die Maps der aktuellen Version in den geteilten Maps. Andernfalls gehen sie verloren."
|
||||
},
|
||||
"valid-btn": "Maps Verknüpfen"
|
||||
},
|
||||
"unlink-maps": {
|
||||
"title": "Maps Verknüpfung aufheben",
|
||||
"desc": "Bitte beachte, dass das Aufheben der Verknüpfung von Maps die Verwendung von geteilten Maps für diese Version nicht zulässt.",
|
||||
"keep-maps": {
|
||||
"label": "Maps behalten",
|
||||
"title": "Maps behalten verschiebt die Maps der aktuellen Version in den geteilten Maps. Andernfalls gehen sie verloren."
|
||||
},
|
||||
"valid-btn": "Verknüpfung aufheben"
|
||||
}
|
||||
},
|
||||
"download-maps": {
|
||||
"search-btn": "Suchen",
|
||||
"loading-maps": "Lade Maps...",
|
||||
"no-maps-found": "Keine Maps gefunden",
|
||||
"no-internet": "Kein Internet"
|
||||
},
|
||||
"mods-disclaimer":{
|
||||
"title": "Haftungsausschluss",
|
||||
"p-1": "Indem Du dich für die Verwendung von Mods entscheiden, verstehst Du Folgendes:",
|
||||
"li-1": "Möglicherweise treten Probleme auf, die im Vanilla-Spiel nicht vorhanden sind. 99.9% der Fehler, Abstürze und Verzögerungen sind auf Mods zurückzuführen.",
|
||||
"li-2": "Mods können durch Updates beschädigt werden und das ist normal - sei geduldig und respektvoll, wenn dies passiert, da Modder Freiwillige mit echtem Leben sind.",
|
||||
"li-3": "Beat Games versuchen nicht absichtlich, Mods kaputt zu machen. Sie möchten an der Codebasis arbeiten und manchmal macht dies Mods kaputt, aber sie sind nicht darauf aus, Mods zu verbieten.",
|
||||
"p-2": "Greife die Spieleentwickler nicht wegen Problemen im Zusammenhang mit Mods an und umgekehrt - Modder und Spieleentwickler sind zwei getrennte Gruppen. Sei einfach nicht gemein, okay?"
|
||||
},
|
||||
"shared-folders":{
|
||||
"title": "Geteilte Ordner",
|
||||
"description": "Verknüpfe BeatSaber-Ordner, um ihre Inhalte mit freigegebenen Ordnern in anderen Versionen zu synchronisieren. Beachte, dass auch das Löschen von Inhalten geteilt wird.",
|
||||
"buttons": {
|
||||
"add-folder": "Ordner Hinzufügen",
|
||||
"link-folder": "Ordner Verknüpfen",
|
||||
"unlink-folder": "Ordner Verknüpfung Aufheben",
|
||||
"link-all": "Alle verlinken"
|
||||
}
|
||||
}
|
||||
},
|
||||
"maps": {
|
||||
"map-filter-panel": {
|
||||
"duration": "Länge",
|
||||
"tags": "Stichworte",
|
||||
"specificities": "Allgemein",
|
||||
"requirements": "Anforderungen"
|
||||
},
|
||||
"map-types": {
|
||||
"accuracy": "Präzision",
|
||||
"balanced": "Ausgeglichen",
|
||||
"challenge": "Herausforderung",
|
||||
"dancestyle": "Tanz",
|
||||
"fitness": "Fitness",
|
||||
"speed": "Geschwindigkeit",
|
||||
"tech": "Tech"
|
||||
},
|
||||
"map-styles": {
|
||||
"dance": "Tanz",
|
||||
"swing": "Swing",
|
||||
"nightcore": "Nightcore",
|
||||
"folk": "Folk",
|
||||
"family": "Familie",
|
||||
"ambient": "umgebung",
|
||||
"funk": "Funk",
|
||||
"jazz": "Jazz",
|
||||
"soul": "Soul",
|
||||
"speedcore": "Speedcore",
|
||||
"punk": "Punk",
|
||||
"rb": "R&B",
|
||||
"holiday": "Urlaub",
|
||||
"vocaloid": "Vokaloid",
|
||||
"jrock": "J-Rock",
|
||||
"trance": "Trance",
|
||||
"drumbass": "Basstrommel",
|
||||
"comedy": "Komödie",
|
||||
"instrumental": "Instrumental",
|
||||
"hardcore": "Hardcore",
|
||||
"kpop": "K-Pop",
|
||||
"indie": "Indie",
|
||||
"techno": "Techno",
|
||||
"house": "House",
|
||||
"game": "Videospiele",
|
||||
"film": "Filme",
|
||||
"alt": "Alternativ",
|
||||
"dubstep": "Dubstep",
|
||||
"metal": "Metal",
|
||||
"anime": "Anime",
|
||||
"hiphop": "HipHop",
|
||||
"jpop": "J-Pop",
|
||||
"rock": "Rock",
|
||||
"pop": "Pop",
|
||||
"electronic": "Elektronisch",
|
||||
"classical-orchestral": "Klassisch & Orchestra"
|
||||
},
|
||||
"map-specificities": {
|
||||
"automapper": "KI",
|
||||
"ranked": "ranked",
|
||||
"curated": "curated",
|
||||
"verified": "verifiziert",
|
||||
"fullSpread": "full spread"
|
||||
},
|
||||
"difficulties": {
|
||||
"Easy": "einfach",
|
||||
"Normal": "normal",
|
||||
"Hard": "hard",
|
||||
"Expert": "expert",
|
||||
"ExpertPlus": "expert+"
|
||||
},
|
||||
"map-item": {
|
||||
"by": "Von {songAutor}",
|
||||
"mapped-by": "Gemapped von"
|
||||
}
|
||||
},
|
||||
"beat-saver": {
|
||||
"maps-sorts": {
|
||||
"Latest": "Neueste",
|
||||
"Relevance": "Relevanz",
|
||||
"Rating": "Bewertung",
|
||||
"Curated": "Curated"
|
||||
}
|
||||
},
|
||||
"auto-update": {
|
||||
"checking": "Prüfe auf Updates",
|
||||
"downloading": "Lade Updates Herunter"
|
||||
},
|
||||
"dateformat": {
|
||||
"dayNames": [
|
||||
"So",
|
||||
"Mo",
|
||||
"Di",
|
||||
"Mi",
|
||||
"Do",
|
||||
"Fr",
|
||||
"Sa",
|
||||
"Sonntag",
|
||||
"Montag",
|
||||
"Dienstag",
|
||||
"Mittwoch",
|
||||
"Donnerstag",
|
||||
"Freitag",
|
||||
"Samstag"
|
||||
],
|
||||
"monthNames": [
|
||||
"Jan",
|
||||
"Feb",
|
||||
"Mär",
|
||||
"Apr",
|
||||
"Mai",
|
||||
"Jun",
|
||||
"Jul",
|
||||
"Aug",
|
||||
"Sep",
|
||||
"Okt",
|
||||
"Nov",
|
||||
"Dez",
|
||||
"Januar",
|
||||
"Februar",
|
||||
"März",
|
||||
"April",
|
||||
"Mai",
|
||||
"Juni",
|
||||
"Juli",
|
||||
"August",
|
||||
"September",
|
||||
"Oktober",
|
||||
"November",
|
||||
"Dezember"
|
||||
],
|
||||
"timeNames": [
|
||||
"a",
|
||||
"p",
|
||||
"am",
|
||||
"pm",
|
||||
"A",
|
||||
"P",
|
||||
"AM",
|
||||
"PM"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -67,6 +67,7 @@
|
||||
"mods": {
|
||||
"loading-mods": "Loading mods...",
|
||||
"no-internet": "No internet",
|
||||
"mods-not-available": "No mods are available yet for this version of Beat Saber",
|
||||
"buttons": {
|
||||
"more-infos": "More info",
|
||||
"install-or-update": "Install or update"
|
||||
@@ -137,11 +138,13 @@
|
||||
"en-US": "English, US",
|
||||
"fr-FR": "Français",
|
||||
"es-ES": "Español",
|
||||
"de-DE": "Deutsch",
|
||||
"translated": {
|
||||
"en-EN": "English, United Kingdom",
|
||||
"en-US": "English, USA",
|
||||
"fr-FR": "French",
|
||||
"es-ES": "Spanish"
|
||||
"es-ES": "Spanish",
|
||||
"de-DE": "German"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -266,12 +269,20 @@
|
||||
"move-folder": {
|
||||
"success": {
|
||||
"titles": {
|
||||
"transfer-started": "Transfer in progress",
|
||||
"transfer-finished": "Transfer complete"
|
||||
},
|
||||
"descs": {
|
||||
"transfer-started": "The transfer has started and may take several minutes depending on your configuration."
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"titles": {
|
||||
"transfer-failed": "Transfer failed 😕"
|
||||
},
|
||||
"descs": {
|
||||
"COPY_TO_SUBPATH": "The destination folder cannot be a subfolder of the source folder.",
|
||||
"restore-linked-folders": "An error occurred while restoring shared folders. You can still manually restore them via the 'Shared Folders' menu on the versions page."
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -583,7 +594,8 @@
|
||||
"buttons": {
|
||||
"add-folder": "Add Folder",
|
||||
"link-folder": "Link Folder",
|
||||
"unlink-folder": "Unlink Folder"
|
||||
"unlink-folder": "Unlink Folder",
|
||||
"link-all": "Link all"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -66,6 +66,7 @@
|
||||
"mods": {
|
||||
"loading-mods": "Cargando mods...",
|
||||
"no-internet": "Sin internet",
|
||||
"mods-not-available": "Aún no hay mods disponibles para esta versión de Beat Saber",
|
||||
"buttons": {
|
||||
"more-infos": "Más información",
|
||||
"install-or-update": "Instalar o actualizar"
|
||||
@@ -136,11 +137,13 @@
|
||||
"en-US": "English, US",
|
||||
"fr-FR": "Français",
|
||||
"es-ES": "Español",
|
||||
"de-DE": "Deutsch",
|
||||
"translated": {
|
||||
"en-EN": "Inglés, Reino Unido",
|
||||
"en-US": "Inglés, Estados Unidos",
|
||||
"fr-FR": "Francés",
|
||||
"es-ES": "Español"
|
||||
"es-ES": "Español",
|
||||
"de-DE": "Alemán"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -265,12 +268,20 @@
|
||||
"move-folder": {
|
||||
"success": {
|
||||
"titles": {
|
||||
"transfer-started": "Transferencia en curso",
|
||||
"transfer-finished": "Transferencia completa"
|
||||
},
|
||||
"descs": {
|
||||
"transfer-started": "La transferencia ha comenzado y puede tomar varios minutos dependiendo de su configuración."
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"titles": {
|
||||
"transfer-failed": "Transferencia fallida 😕"
|
||||
},
|
||||
"descs": {
|
||||
"COPY_TO_SUBPATH": "La carpeta de destino no puede ser una subcarpeta de la carpeta de origen.",
|
||||
"restore-linked-folders": "Se produjo un error al restaurar las carpetas compartidas. Aún puede restaurarlas manualmente a través del menú 'Carpetas compartidas' en la página de versiones."
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -582,7 +593,8 @@
|
||||
"buttons": {
|
||||
"add-folder": "Añadir Carpeta",
|
||||
"link-folder": "Enlazar Carpeta",
|
||||
"unlink-folder": "Desenlazar Carpeta"
|
||||
"unlink-folder": "Desenlazar Carpeta",
|
||||
"link-all": "Enlazar todo"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -66,6 +66,7 @@
|
||||
"mods": {
|
||||
"loading-mods": "Chargement des mods...",
|
||||
"no-internet": "Pas d'internet",
|
||||
"mods-not-available": "Aucun mod n'est encore disponible pour cette version de Beat Saber",
|
||||
"buttons": {
|
||||
"more-infos": "Plus d'infos",
|
||||
"install-or-update": "Installer ou mettre à jour"
|
||||
@@ -136,11 +137,13 @@
|
||||
"en-US": "English, US",
|
||||
"fr-FR": "Français",
|
||||
"es-ES": "Español",
|
||||
"de-DE": "Deutsch",
|
||||
"translated": {
|
||||
"en-EN": "Anglais, Royaume-Uni",
|
||||
"en-US": "Anglais, États-Unis",
|
||||
"fr-FR": "Français",
|
||||
"es-ES": "Espagnol"
|
||||
"es-ES": "Espagnol",
|
||||
"de-DE": "Allemand"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -265,13 +268,21 @@
|
||||
"move-folder": {
|
||||
"success": {
|
||||
"titles": {
|
||||
"transfer-started": "Transfert en cours",
|
||||
"transfer-finished": "Transfert terminé 👌"
|
||||
},
|
||||
"descs":{
|
||||
"transfer-started": "Le transfert a commencé, il peut durer plusieurs minutes selon votre configuration."
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"titles": {
|
||||
"transfer-failed": "Le transfert a échoué 😕"
|
||||
}
|
||||
},
|
||||
"descs": {
|
||||
"COPY_TO_SUBPATH": "Le dossier de destination ne peut pas être un sous-dossier du dossier source.",
|
||||
"restore-linked-folders": "Une erreur s'est produite lors de la restauration des dossiers partagés. Vous pouvez toujours les restaurer manuellement via le menu 'Dossiers partagés' sur la page des versions."
|
||||
}
|
||||
}
|
||||
},
|
||||
"steam": {
|
||||
@@ -582,7 +593,8 @@
|
||||
"buttons": {
|
||||
"add-folder": "Ajouter un dossier",
|
||||
"link-folder": "Lier le dossier",
|
||||
"unlink-folder": "Délier le dossier"
|
||||
"unlink-folder": "Délier le dossier",
|
||||
"link-all": "Tout lier"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Generated
+1015
-1329
File diff suppressed because it is too large
Load Diff
+13
-14
@@ -154,10 +154,13 @@
|
||||
"@types/dateformat": "^5.0.0",
|
||||
"@types/jest": "^27.5.2",
|
||||
"@types/node": "17.0.23",
|
||||
"@types/react": "^17.0.48",
|
||||
"@types/react-dom": "^18.0.10",
|
||||
"@types/react": "^18.0.33",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"@types/react-outside-click-handler": "^1.3.1",
|
||||
"@types/react-test-renderer": "^17.0.2",
|
||||
"@types/react-virtualized-auto-sizer": "^1.0.1",
|
||||
"@types/react-window": "^1.8.5",
|
||||
"@types/recursive-readdir": "^2.2.1",
|
||||
"@types/terser-webpack-plugin": "^5.0.4",
|
||||
"@types/use-double-click": "^1.0.1",
|
||||
"@types/webpack-bundle-analyzer": "^4.4.2",
|
||||
@@ -173,7 +176,7 @@
|
||||
"css-loader": "^6.7.1",
|
||||
"css-minimizer-webpack-plugin": "^4.1.0",
|
||||
"detect-port": "^1.3.0",
|
||||
"electron": "^20.0.3",
|
||||
"electron": "^24.0.0",
|
||||
"electron-builder": "^23.6.0",
|
||||
"electron-devtools-installer": "^3.2.0",
|
||||
"electron-notarize": "^1.2.1",
|
||||
@@ -210,7 +213,7 @@
|
||||
"sass-loader": "^12.6.0",
|
||||
"style-loader": "^3.3.1",
|
||||
"tailwind-scrollbar": "^2.0.1",
|
||||
"tailwindcss": "^3.2.4",
|
||||
"tailwindcss": "^3.3.1",
|
||||
"terser-webpack-plugin": "^5.3.5",
|
||||
"ts-jest": "^27.1.5",
|
||||
"ts-loader": "^9.3.0",
|
||||
@@ -226,19 +229,16 @@
|
||||
"dependencies": {
|
||||
"@node-steam/vdf": "^2.2.0",
|
||||
"@tippyjs/react": "^4.2.6",
|
||||
"@types/react-virtualized-auto-sizer": "^1.0.1",
|
||||
"@types/react-window": "^1.8.5",
|
||||
"@types/recursive-readdir": "^2.2.1",
|
||||
"archiver": "^5.3.1",
|
||||
"dateformat": "^5.0.3",
|
||||
"dot-prop": "^7.2.0",
|
||||
"electron-debug": "^3.2.0",
|
||||
"electron-log": "^4.4.8",
|
||||
"electron-store": "^8.1.0",
|
||||
"electron-updater": "^5.2.4",
|
||||
"electron-updater": "^6.0.3",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"framer-motion": "^8.0.2",
|
||||
"fs-extra": "^10.1.0",
|
||||
"framer-motion": "^10.11.2",
|
||||
"fs-extra": "^11.1.1",
|
||||
"history": "^5.3.0",
|
||||
"is-online": "^9.0.1",
|
||||
"md5-file": "^5.0.0",
|
||||
@@ -249,17 +249,16 @@
|
||||
"react-dom": "^18.2.0",
|
||||
"react-range": "^1.8.14",
|
||||
"react-router-dom": "^6.3.0",
|
||||
"react-virtualized-auto-sizer": "^1.0.7",
|
||||
"react-virtualized-auto-sizer": "^1.0.12",
|
||||
"react-window": "^1.8.8",
|
||||
"recursive-readdir": "^2.2.3",
|
||||
"regedit": "^5.1.1",
|
||||
"regedit": "^5.1.2",
|
||||
"rfdc": "^1.3.0",
|
||||
"rxjs": "^7.5.6",
|
||||
"rxjs": "^7.8.0",
|
||||
"sanitize-filename": "^1.6.3",
|
||||
"semver": "^7.3.8",
|
||||
"tailwind-scrollbar-hide": "^1.1.7",
|
||||
"tailwindcss-scoped-groups": "^2.0.0",
|
||||
"use-delayed-state": "^1.2.0",
|
||||
"use-double-click": "^1.0.5",
|
||||
"use-fit-text": "^2.4.0"
|
||||
},
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "bs-manager",
|
||||
"version": "1.0.1",
|
||||
"version": "1.1.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "bs-manager",
|
||||
"version": "1.0.1",
|
||||
"version": "1.1.0",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { copyFile, ensureDir, move, symlink } from "fs-extra";
|
||||
import { CopyOptions, copy, ensureDir, move, symlink } from "fs-extra";
|
||||
import { access, mkdir, rm, readdir, unlink, lstat, readlink } from "fs/promises";
|
||||
import path from "path";
|
||||
import { Observable } from "rxjs";
|
||||
import log from "electron-log"
|
||||
import { BsmException } from "shared/models/bsm-exception.model";
|
||||
|
||||
export async function pathExist(path: string): Promise<boolean> {
|
||||
try{
|
||||
@@ -29,7 +30,7 @@ export async function unlinkPath(path: string): Promise<void>{
|
||||
return unlink(path);
|
||||
}
|
||||
|
||||
export async function getFoldersInFolder(folderPath: string): Promise<string[]> {
|
||||
export async function getFoldersInFolder(folderPath: string, opts?: {ignoreSymlinkTargetError?: boolean}): Promise<string[]> {
|
||||
if(!(await pathExist(folderPath))){ return []; }
|
||||
|
||||
const files = await readdir(folderPath, {withFileTypes: true});
|
||||
@@ -40,7 +41,10 @@ export async function getFoldersInFolder(folderPath: string): Promise<string[]>
|
||||
try{
|
||||
const targetPath = await readlink(path.join(folderPath, file.name));
|
||||
return (await lstat(targetPath)).isDirectory() ? path.join(folderPath, file.name) : undefined;
|
||||
}catch(e){
|
||||
}catch(e: any){
|
||||
if(e.code === "ENOENT" && opts?.ignoreSymlinkTargetError === true && !path.extname(file.name)){
|
||||
return path.join(folderPath, file.name);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
@@ -81,23 +85,44 @@ export function moveFolderContent(src: string, dest: string): Observable<Progres
|
||||
});
|
||||
}
|
||||
|
||||
export async function copyDirectoryWithJunctions(source: string, destination: string): Promise<void> {
|
||||
|
||||
await ensureDir(destination);
|
||||
const items = await readdir(source);
|
||||
export function isSubdirectory(parent: string, child: string): boolean {
|
||||
const parentNormalized = path.resolve(parent);
|
||||
const childNormalized = path.resolve(child);
|
||||
|
||||
if (parentNormalized === childNormalized) { return false; }
|
||||
|
||||
const relativePath = path.relative(parentNormalized, childNormalized);
|
||||
|
||||
if (path.parse(parentNormalized).root !== path.parse(childNormalized).root) { return false; }
|
||||
|
||||
return relativePath && !relativePath.startsWith('..') && !path.isAbsolute(relativePath);
|
||||
}
|
||||
|
||||
export async function copyDirectoryWithJunctions(src: string, dest: string, options?: CopyOptions): Promise<void> {
|
||||
|
||||
if(isSubdirectory(src, dest)){
|
||||
throw {message: `Cannot copy directory '${src}' into itself '${dest}'.`, code: "COPY_TO_SUBPATH"} as BsmException;
|
||||
}
|
||||
|
||||
await ensureDir(dest);
|
||||
const items = await readdir(src, { withFileTypes: true });
|
||||
|
||||
for (const item of items) {
|
||||
const sourcePath = path.join(source, item);
|
||||
const destinationPath = path.join(destination, item);
|
||||
const stats = await lstat(sourcePath);
|
||||
|
||||
if (stats.isDirectory()) {
|
||||
await copyDirectoryWithJunctions(sourcePath, destinationPath);
|
||||
} else if (stats.isFile()) {
|
||||
await copyFile(sourcePath, destinationPath);
|
||||
} else if (stats.isSymbolicLink()) {
|
||||
const sourcePath = path.join(src, item.name);
|
||||
const destinationPath = path.join(dest, item.name);
|
||||
|
||||
if (item.isDirectory()) {
|
||||
await copyDirectoryWithJunctions(sourcePath, destinationPath, options);
|
||||
} else if (item.isFile()) {
|
||||
await copy(sourcePath, destinationPath, options);
|
||||
} else if (item.isSymbolicLink()) {
|
||||
if(options?.overwrite){
|
||||
await unlinkPath(destinationPath);
|
||||
}
|
||||
const symlinkTarget = await readlink(sourcePath);
|
||||
await symlink(symlinkTarget, destinationPath, "junction");
|
||||
const relativePath = path.relative(src, symlinkTarget);
|
||||
const newTarget = path.join(dest, relativePath);
|
||||
await symlink(newTarget, destinationPath, 'junction');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
export type AllSettledHelperOptions = {
|
||||
keepStructure?: boolean;
|
||||
removeFalsy?: boolean;
|
||||
}
|
||||
|
||||
export async function allSettled<T>(promises: Promise<T>[], options?: AllSettledHelperOptions): Promise<T[]> {
|
||||
const settledPromises = await Promise.allSettled(promises);
|
||||
if(options?.keepStructure){
|
||||
return settledPromises.map(p => p.status === 'fulfilled' ? p.value : null);
|
||||
}
|
||||
|
||||
return settledPromises.reduce((acc, p) => {
|
||||
if(p.status === 'fulfilled'){
|
||||
if(options?.removeFalsy && !p.value){ return acc; }
|
||||
acc.push(p.value);
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
@@ -151,4 +151,9 @@ ipc.on("unlink-folder", async (req: IpcRequest<{ folder: string, options?: LinkO
|
||||
|
||||
});
|
||||
|
||||
ipc.on("relink-all-versions-folders", async (req: IpcRequest<void>, reply) => {
|
||||
const versionLinker = VersionFolderLinkerService.getInstance();
|
||||
reply(from(versionLinker.relinkAllVersionsFolders()));
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import { Archive } from "../../models/archive.class";
|
||||
import { deleteFolder, ensureFolderExist, getFoldersInFolder, pathExist } from "../../helpers/fs.helpers";
|
||||
import { readFile } from "fs/promises";
|
||||
import { FolderLinkerService } from "../folder-linker.service";
|
||||
import { allSettled } from "../../helpers/promise.helpers";
|
||||
|
||||
export class LocalMapsManagerService {
|
||||
|
||||
@@ -155,12 +156,7 @@ export class LocalMapsManagerService {
|
||||
return mapInfo;
|
||||
});
|
||||
|
||||
const mapsInfo = (await Promise.allSettled(promises)).reduce((acc, mapInfo) => {
|
||||
if(mapInfo.status === "fulfilled" && mapInfo.value){
|
||||
acc.push(mapInfo.value);
|
||||
}
|
||||
return acc;
|
||||
} ,[] as BsmLocalMap[]);
|
||||
const mapsInfo = await allSettled(promises, {removeFalsy: true});
|
||||
|
||||
progression.maps = mapsInfo;
|
||||
|
||||
|
||||
@@ -96,7 +96,6 @@ export class BSLocalVersionService{
|
||||
public async getVersionPath(version: BSVersion): Promise<string>{
|
||||
if(version.steam){ return this.steamService.getGameFolder(BS_APP_ID, "Beat Saber") }
|
||||
if(version.oculus){ return this.oculusService.getGameFolder(OCULUS_BS_DIR); }
|
||||
console.log("getVersionPath", this.getVersionFolder(version), version);
|
||||
return path.join(
|
||||
this.installLocationService.versionsDirectory,
|
||||
this.getVersionFolder(version)
|
||||
@@ -184,7 +183,7 @@ export class BSLocalVersionService{
|
||||
}
|
||||
|
||||
public async editVersion(version: BSVersion, name: string, color: string): Promise<BSVersion>{
|
||||
if(version.steam || version.oculus){ throw {title: "CantEditSteam", msg: "CantEditSteam"} as BsmException; }
|
||||
if(version.steam || version.oculus){ throw {title: "CantEditSteam", message: "CantEditSteam"} as BsmException; }
|
||||
const oldPath = await this.getVersionPath(version);
|
||||
const editedVersion: BSVersion = version.BSVersion === name
|
||||
? {...version, name: undefined, color}
|
||||
@@ -205,7 +204,7 @@ export class BSLocalVersionService{
|
||||
return editedVersion;
|
||||
}).catch((err: Error) => {
|
||||
log.error("edit version error", err, version, name, color);
|
||||
throw {title: "CantRename", error: err} as BsmException;
|
||||
throw {title: "CantRename", ...err} as BsmException;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -228,7 +227,7 @@ export class BSLocalVersionService{
|
||||
return cloneVersion;
|
||||
}).catch((err: Error) => {
|
||||
log.error("clone version error", err, version, name, color);
|
||||
throw {title: "CantClone", error: err} as BsmException
|
||||
throw {title: "CantClone", ...err} as BsmException
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ import path from 'path';
|
||||
import { writeFileSync } from 'fs';
|
||||
import { BSVersion } from 'shared/bs-version.interface';
|
||||
import { RequestService } from "./request.service"
|
||||
import isOnline from 'is-online';
|
||||
import { readJSON } from 'fs-extra';
|
||||
import { allSettled } from '../helpers/promise.helpers';
|
||||
|
||||
export class BSVersionLibService{
|
||||
|
||||
@@ -43,16 +43,19 @@ export class BSVersionLibService{
|
||||
writeFileSync(localVersionsPath, JSON.stringify(versions, null, "\t"), {encoding: 'utf-8', flag: 'w'});
|
||||
};
|
||||
|
||||
private async loadBsVersions(): Promise<BSVersion[]>{
|
||||
if(this.bsVersions){ return this.bsVersions; }
|
||||
const [localVersions, remoteVersions] = await Promise.all([
|
||||
this.getLocalVersions(), (await isOnline({timeout: 1500}) && this.getRemoteVersions())
|
||||
]);
|
||||
let resVersions = localVersions;
|
||||
if(remoteVersions && remoteVersions.length){ resVersions = remoteVersions; this.updateLocalVersions(resVersions); }
|
||||
this.bsVersions = resVersions;
|
||||
return this.bsVersions;
|
||||
}
|
||||
private async loadBsVersions(): Promise<BSVersion[]>{
|
||||
|
||||
if(this.bsVersions){ return this.bsVersions; }
|
||||
|
||||
const [localVersions, remoteVersions] = await allSettled([
|
||||
this.getLocalVersions(), this.getRemoteVersions()
|
||||
], {keepStructure: true});
|
||||
|
||||
let resVersions = localVersions;
|
||||
if(remoteVersions && remoteVersions.length){ resVersions = remoteVersions; this.updateLocalVersions(resVersions); }
|
||||
this.bsVersions = resVersions;
|
||||
return this.bsVersions;
|
||||
}
|
||||
|
||||
public async getAvailableVersions(): Promise<BSVersion[]>{
|
||||
const bsVersions = await this.loadBsVersions();
|
||||
|
||||
@@ -3,7 +3,7 @@ import log from "electron-log";
|
||||
import { deleteFolder, ensureFolderExist, moveFolderContent, pathExist, unlinkPath } from "../helpers/fs.helpers";
|
||||
import { lstat, symlink } from "fs/promises";
|
||||
import path from "path";
|
||||
import { copy } from "fs-extra";
|
||||
import { copy, readlink } from "fs-extra";
|
||||
|
||||
export class FolderLinkerService {
|
||||
|
||||
@@ -18,12 +18,12 @@ export class FolderLinkerService {
|
||||
|
||||
private readonly installLocationService = InstallationLocationService.getInstance();
|
||||
|
||||
private readonly sharedFolder: string;
|
||||
|
||||
private constructor(){
|
||||
this.installLocationService = InstallationLocationService.getInstance();
|
||||
}
|
||||
|
||||
this.sharedFolder = this.installLocationService.sharedContentPath;
|
||||
private get sharedFolder(): string{
|
||||
return this.installLocationService.sharedContentPath;
|
||||
}
|
||||
|
||||
private getSharedFolder(folderPath: string, intermediateFolder?: string): string {
|
||||
@@ -48,10 +48,15 @@ export class FolderLinkerService {
|
||||
|
||||
public async linkFolder(folderPath: string, options?: LinkOptions): Promise<void> {
|
||||
|
||||
if(await this.isFolderSymlink(folderPath)){ return; }
|
||||
|
||||
const sharedPath = this.getSharedFolder(folderPath, options?.intermediateFolder);
|
||||
|
||||
if(await this.isFolderSymlink(folderPath)){
|
||||
const isTargetedToSharedPath = await readlink(folderPath).then(target => target === sharedPath).catch(() => false);
|
||||
if(isTargetedToSharedPath){ return; }
|
||||
await unlinkPath(folderPath);
|
||||
return symlink(sharedPath, folderPath, "junction");
|
||||
}
|
||||
|
||||
await ensureFolderExist(sharedPath);
|
||||
|
||||
if(options?.backup === true){
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import path from "path";
|
||||
import fs from 'fs-extra';
|
||||
import log from "electron-log";
|
||||
import { app } from "electron";
|
||||
import { BsmException } from "shared/models/bsm-exception.model";
|
||||
import ElectronStore from "electron-store";
|
||||
import { ensureFolderExist, pathExist } from "../helpers/fs.helpers";
|
||||
import { copyDirectoryWithJunctions, deleteFolder, ensureFolderExist } from "../helpers/fs.helpers";
|
||||
|
||||
export class InstallationLocationService {
|
||||
|
||||
@@ -48,15 +46,24 @@ export class InstallationLocationService {
|
||||
const oldDir = this.installationDirectory;
|
||||
const newDest = path.join(newDir, this.INSTALLATION_FOLDER);
|
||||
return new Promise<string>(async (resolve, reject) => {
|
||||
if(!(await pathExist(oldDir))){ ensureFolderExist(oldDir); }
|
||||
fs.move(oldDir, newDest, { overwrite: true }).then(() => {
|
||||
|
||||
await ensureFolderExist(oldDir);
|
||||
|
||||
try{
|
||||
await copyDirectoryWithJunctions(oldDir, newDest, {overwrite: true});
|
||||
|
||||
this._installationDirectory = newDir;
|
||||
this.installPathConfig.set(this.STORE_INSTALLATION_PATH_KEY, newDir);
|
||||
resolve(this.installationDirectory);
|
||||
}).catch((err: Error) => {
|
||||
reject({title: "CantMoveFolder", error: err} as BsmException);
|
||||
|
||||
deleteFolder(oldDir);
|
||||
|
||||
return resolve(this.installationDirectory);
|
||||
}
|
||||
catch(err){
|
||||
log.error(err);
|
||||
})
|
||||
reject(err);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ export class RequestService {
|
||||
resolve(JSON.parse(body));
|
||||
});
|
||||
res.on('error', (err) => reject(err))
|
||||
});
|
||||
}).on("error", err => reject(err));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import { BSVersion } from "shared/bs-version.interface";
|
||||
import { LocalMapsManagerService } from "./additional-content/local-maps-manager.service";
|
||||
import { BSLocalVersionService } from "./bs-local-version.service";
|
||||
import { FolderLinkerService, LinkOptions } from "./folder-linker.service";
|
||||
import { allSettled } from "../helpers/promise.helpers";
|
||||
|
||||
export class VersionFolderLinkerService {
|
||||
|
||||
@@ -37,17 +38,12 @@ export class VersionFolderLinkerService {
|
||||
|
||||
private async isOtherVersionHaveFolderLinked(relativeFolder: string, ignorePath: string): Promise<boolean>{
|
||||
const versions = await this.localVersion.getInstalledVersions();
|
||||
const versionPaths = (await Promise.allSettled(versions.map(version => this.localVersion.getVersionPath(version)))).reduce((acc, res) => {
|
||||
if(res.status === "fulfilled"){
|
||||
acc.push(res.value);
|
||||
}
|
||||
return acc;
|
||||
}, [] as string[]);
|
||||
const versionPaths = await allSettled(versions.map(version => this.localVersion.getVersionPath(version)));
|
||||
|
||||
for(const versionPath of versionPaths){
|
||||
const folderPath = this.relativeToFullPath(versionPath, relativeFolder);
|
||||
if(folderPath === ignorePath){ continue; }
|
||||
if(await this.folderLinker.isFolderSymlink(folderPath)){ console.log(folderPath); return true; }
|
||||
if(await this.folderLinker.isFolderSymlink(folderPath)){ return true; }
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -89,23 +85,33 @@ export class VersionFolderLinkerService {
|
||||
return this.folderLinker.isFolderSymlink(folderPath);
|
||||
}
|
||||
|
||||
public async getLinkedFolders(version: BSVersion, options?: { relative?: boolean }): Promise<string[]>{
|
||||
public async getLinkedFolders(version: BSVersion, options?: { relative?: boolean, ignoreSymlinkTargetError?: boolean }): Promise<string[]>{
|
||||
const versionPath = await this.localVersion.getVersionPath(version);
|
||||
const [rootFolders, beatSaberDataFolders] = await Promise.all([
|
||||
getFoldersInFolder(versionPath),
|
||||
getFoldersInFolder(path.join(versionPath, "Beat Saber_Data"))
|
||||
getFoldersInFolder(versionPath, {ignoreSymlinkTargetError: options?.ignoreSymlinkTargetError}),
|
||||
getFoldersInFolder(path.join(versionPath, "Beat Saber_Data"), {ignoreSymlinkTargetError: options?.ignoreSymlinkTargetError})
|
||||
]);
|
||||
|
||||
const linkedFolder = await Promise.all([...rootFolders, ...beatSaberDataFolders].map(async folder => {
|
||||
const linkedFolders = await Promise.all([...rootFolders, ...beatSaberDataFolders].map(async folder => {
|
||||
if(!(await this.folderLinker.isFolderSymlink(folder))){ return null; }
|
||||
return folder;
|
||||
}));
|
||||
|
||||
if(options?.relative){
|
||||
return linkedFolder.filter(folder => folder).map(folder => path.relative(versionPath, folder));
|
||||
return linkedFolders.filter(folder => folder).map(folder => path.relative(versionPath, folder));
|
||||
}
|
||||
|
||||
return linkedFolder.filter(folder => folder);
|
||||
return linkedFolders.filter(folder => folder);
|
||||
}
|
||||
|
||||
public async relinkAllVersionsFolders(): Promise<void>{
|
||||
const versions = await this.localVersion.getInstalledVersions();
|
||||
|
||||
for(const version of versions){
|
||||
const linkedFolders = await this.getLinkedFolders(version, { relative: true, ignoreSymlinkTargetError: true });
|
||||
const actions = linkedFolders.map(folder => ({ type: "link", version, relativeFolder: folder } as VersionLinkFolderAction));
|
||||
await Promise.all(actions.map(action => this.doAction(action)));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -64,8 +64,6 @@ export const LocalMapsListPanel = forwardRef(({version, className, filter, searc
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
console.log(linked);
|
||||
|
||||
if(isVisible){
|
||||
loadMaps();
|
||||
mapsDownloader.addOnMapDownloadedListener((map, targerVersion) => {
|
||||
|
||||
@@ -11,7 +11,6 @@ import dateFormat from "dateformat";
|
||||
import { AudioPlayerService } from "renderer/services/audio-player.service";
|
||||
import { useObservable } from "renderer/hooks/use-observable.hook";
|
||||
import { map } from "rxjs/operators";
|
||||
import useDelayedState from "use-delayed-state";
|
||||
import equal from "fast-deep-equal/es6";
|
||||
import { getMapZipUrlFromHash } from "renderer/helpers/maps-utils";
|
||||
import { BsmBasicSpinner } from "../shared/bsm-basic-spinner/bsm-basic-spinner.component";
|
||||
@@ -21,6 +20,7 @@ import { MAP_DIFFICULTIES } from "renderer/partials/maps/map-difficulties/map-di
|
||||
import { MAP_DIFFICULTIES_COLORS } from "renderer/partials/maps/map-difficulties/map-difficulties-colors"
|
||||
import useDoubleClick from "use-double-click";
|
||||
import { GlowEffect } from "../shared/glow-effect.component";
|
||||
import { useDelayedState } from "renderer/hooks/use-delayed-state.hook";
|
||||
|
||||
export type ParsedMapDiff = {type: BsvMapDifficultyType, name: string, stars: number}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ export const GuardModal: ModalComponent<string> = ({resolver}) => {
|
||||
<BsmImage className="mx-auto h-24" image={BeatConflict}/>
|
||||
<div className="mb-4">
|
||||
<label className="block font-bold tracking-wide text-gray-800 dark:text-gray-200" htmlFor="guard">{t("modals.guard.inputs.guard-code.label")}</label>
|
||||
<input className="w-full bg-light-main-color-1 dark:bg-main-color-1 px-2 rounded-md py-[2px]" onChange={e => setGuardCode(e.target.value.toUpperCase())} value={guardCode} type="guard" name="guard" id="guard" placeholder={t("modals.guard.inputs.guard-code.placeholder")}/>
|
||||
<input className="w-full bg-light-main-color-1 dark:bg-main-color-1 px-2 rounded-md py-[2px]" onChange={e => setGuardCode(e.target.value.toUpperCase().replaceAll(" ", ""))} value={guardCode} type="guard" name="guard" id="guard" placeholder={t("modals.guard.inputs.guard-code.placeholder")}/>
|
||||
</div>
|
||||
<div className="grid grid-flow-col grid-cols-2 gap-4">
|
||||
<BsmButton typeColor="cancel" className="rounded-md text-center transition-all" onClick={() => {resolver({exitCode: ModalExitCode.CANCELED})}} withBar={false} text="misc.cancel"/>
|
||||
|
||||
@@ -67,6 +67,10 @@ export const ShareFoldersModal: ModalComponent<void, BSVersion> = ({ data }) =>
|
||||
setFolders((prev) => prev.filter((_, i) => i !== index));
|
||||
}
|
||||
|
||||
const linkAll = () => {
|
||||
folders.forEach(relativeFolder => linker.linkVersionFolder({ version: data, relativeFolder, type: VersionLinkerActionType.Link }))
|
||||
}
|
||||
|
||||
return (
|
||||
<form className="w-full max-w-md ">
|
||||
<h1 className="text-3xl uppercase tracking-wide w-full text-center text-gray-800 dark:text-gray-200">{t("modals.shared-folders.title")}</h1>
|
||||
@@ -76,14 +80,16 @@ export const ShareFoldersModal: ModalComponent<void, BSVersion> = ({ data }) =>
|
||||
<FolderItem key={index} version={data} relativeFolder={folder} onDelete={() => {removeFolder(index)}}/>
|
||||
))}
|
||||
</ul>
|
||||
<div className="w-full h-12 rounded-md flex justify-center items-center cursor-pointer bg-light-main-color-1 dark:bg-main-color-1 hover:bg-light-main-color-3 hover:dark:bg-main-color-3" onClick={addFolder}>
|
||||
<BsmIcon className="aspect-square h-8" icon="add"/>
|
||||
<span className="font-bold">{t("modals.shared-folders.buttons.add-folder")}</span>
|
||||
<div className="grid grid-flow-col gap-3 grid-cols-2">
|
||||
<BsmButton icon="add" className="h-8 rounded-md flex justify-center items-center font-bold bg-light-main-color-1 dark:bg-main-color-1" iconClassName="h-6 aspect-square text-current" onClick={addFolder} withBar={false} text="modals.shared-folders.buttons.add-folder"/>
|
||||
<BsmButton icon="link" className="h-8 rounded-md flex justify-center items-center font-bold" typeColor="primary" iconClassName="h-6 aspect-square text-current -rotate-45" onClick={linkAll} withBar={false} text="modals.shared-folders.buttons.link-all"/>
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
// -------- FOLDER ITEM --------
|
||||
|
||||
type FolderProps = {
|
||||
version: BSVersion;
|
||||
relativeFolder: string;
|
||||
@@ -111,8 +117,9 @@ const FolderItem = ({version, relativeFolder, onDelete}: FolderProps) => {
|
||||
const [linked, setLinked] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if(pending){ return; }
|
||||
loadFolderIsLinked();
|
||||
}, [version, relativeFolder]);
|
||||
}, [version, relativeFolder, pending]);
|
||||
|
||||
const loadFolderIsLinked = () => {
|
||||
linker.isVersionFolderLinked(version, relativeFolder).toPromise().then(setLinked);
|
||||
@@ -124,13 +131,14 @@ const FolderItem = ({version, relativeFolder, onDelete}: FolderProps) => {
|
||||
version,
|
||||
relativeFolder,
|
||||
type: VersionLinkerActionType.Unlink,
|
||||
}).then(loadFolderIsLinked);
|
||||
})
|
||||
}
|
||||
|
||||
return linker.linkVersionFolder({
|
||||
version,
|
||||
relativeFolder,
|
||||
type: VersionLinkerActionType.Link,
|
||||
}).then(loadFolderIsLinked);
|
||||
})
|
||||
}
|
||||
|
||||
const cancelLink = () => {
|
||||
|
||||
@@ -24,7 +24,7 @@ export function BsmProgressBar() {
|
||||
|
||||
return (
|
||||
<AnimatePresence> { visible &&
|
||||
<motion.div initial={{y: "120%"}} animate={{y:"0%"}} exit={{y:"120%"}} className="w-full absolute h-14 flex justify-center items-center bottom-2 pointer-events-none" style={style}>
|
||||
<motion.div initial={{y: "120%"}} animate={{y:"0%"}} exit={{y:"120%"}} className="w-full absolute h-14 flex justify-center items-center bottom-2 pointer-events-none z-10" style={style}>
|
||||
<div className={`flex items-center content-center justify-center bottom-9 z-10 rounded-full bg-light-main-color-2 dark:bg-main-color-2 shadow-center shadow-black transition-all duration-300 ${!progressValue && "h-14 w-14 "} ${!!progressValue && "h-5 w-3/4 p-[6px]"}`}>
|
||||
{ !!progressValue && (
|
||||
<div className="relative h-full w-full rounded-full bg-black">
|
||||
|
||||
@@ -15,19 +15,19 @@ export function SupporterItem({supporter, delay}: Props) {
|
||||
const additionnalStyles: CSSProperties = (() => {
|
||||
const commonStyles: CSSProperties = {backgroundImage: `url(${txtBg})`, backgroundRepeat: "no-repeat", backgroundPosition: "center"}
|
||||
if(supporter.type === "gold"){ return {color: "#ffe270", textShadow: "0px 0px 10px #ffdd59", backgroundSize: "70% 15px", ...commonStyles}; }
|
||||
if(supporter.type === "diamond"){ return {color: "#e574fc", textShadow: "0px 0px 15px #e056fd", backgroundSize: "70% 19px", ...commonStyles}; }
|
||||
if(supporter.type === "diamond"){ return {color: "#0ef2f8", textShadow: "0px 0px 15px #0ef2f8", backgroundSize: "70% 19px", ...commonStyles}; }
|
||||
if(supporter.type === "sponsor"){ return {color: "#0be881", textShadow: "0px 0px 15px #0be881", backgroundSize: "70% 19px", ...commonStyles}; }
|
||||
return {};
|
||||
})();
|
||||
|
||||
const renderSpan = () => {
|
||||
return <motion.span className="text-2xl font-bold px-3 pb-1" style={additionnalStyles} initial={{y: "100%", opacity: 0}} animate={{y: 0, opacity: 1}} transition={{delay: delay}}>{supporter.username}</motion.span>;
|
||||
return <motion.span className={`text-2xl font-bold px-3 pb-1 ${supporter.link && "cursor-pointer underline"}`} style={additionnalStyles} onClick={openSupporterLink} initial={{y: "100%", opacity: 0}} animate={{y: 0, opacity: 1}} transition={{delay: delay}}>{supporter.username}</motion.span>;
|
||||
}
|
||||
|
||||
const renderItem = () => {
|
||||
if(supporter.type !== "sponsor"){ return renderSpan(); }
|
||||
return (
|
||||
<motion.div className={`flex flex-col justify-center items-center mx-4 ${supporter.link && "cursor-pointer"}`} onClick={openSupporterLink} initial={{y: "100%", opacity: 0}} animate={{y: 0, opacity: 1}} transition={{delay: delay}}>
|
||||
<motion.div className={`flex flex-col justify-center items-center mx-4 ${supporter.link && "cursor-pointer underline"}`} onClick={openSupporterLink} initial={{y: "100%", opacity: 0}} animate={{y: 0, opacity: 1}} transition={{delay: delay}}>
|
||||
<img className="max-w-xs max-h-52 mb-2" src={supporter.img}/>
|
||||
{renderSpan()}
|
||||
</motion.div>
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useClickOutside } from "renderer/hooks/use-click-outside.hook";
|
||||
import { useThemeColor } from "renderer/hooks/use-theme-color.hook";
|
||||
import { getCorrectTextColor } from "renderer/helpers/correct-text-color";
|
||||
|
||||
type BsmButtonType = "primary"|"success"|"cancel"|"error";
|
||||
type BsmButtonType = "primary"|"secondary"|"success"|"cancel"|"error";
|
||||
|
||||
type Props = {
|
||||
className?: string,
|
||||
@@ -36,7 +36,7 @@ export function BsmButton({className, style, imgClassName, iconClassName, icon,
|
||||
const ref = useRef(null);
|
||||
useClickOutside(ref, onClickOutside);
|
||||
|
||||
const primaryColor = typeColor === "primary" && useThemeColor("first-color");
|
||||
const primaryColor = typeColor === "primary" ? useThemeColor("first-color") : typeColor === "secondary" ? secondColor : undefined;
|
||||
|
||||
const textColor = (() => {
|
||||
if(primaryColor){ return getCorrectTextColor(primaryColor); }
|
||||
@@ -57,7 +57,7 @@ export function BsmButton({className, style, imgClassName, iconClassName, icon,
|
||||
return (
|
||||
<div ref={ref} onClick={handleClick} title={t(title)} className={`${className} overflow-hidden cursor-pointer group ${(!withBar && !disabled && (!!typeColor || !!color)) && "hover:brightness-[1.15]"} ${disabled && "brightness-75 cursor-not-allowed"} ${renderTypeColor}`} style={{...style, backgroundColor: primaryColor || color}}>
|
||||
{ image && <BsmImage image={image} className={imgClassName}/> }
|
||||
{ icon && <BsmIcon icon={icon} className={iconClassName ?? "h-full w-full text-gray-800 dark:text-white"} style={{color: iconColor}}/> }
|
||||
{ icon && <BsmIcon icon={icon} className={iconClassName ?? "h-full w-full text-gray-800 dark:text-white"} style={{color: iconColor || textColor}}/> }
|
||||
{text && (type === "submit" ? <button type="submit" className={textClassName || "h-full w-full"} style={{...(!!textColor && {color: textColor})}}>{t(text)}</button> : <span className={textClassName} style={{...(!!textColor && {color: `${textColor}`})}}>{t(text)}</span>)}
|
||||
{ withBar && (
|
||||
<div className="absolute bottom-0 left-0 w-full h-1 bg-current" style={{color: secondColor}}>
|
||||
|
||||
@@ -51,12 +51,13 @@ import { SyncIcon } from "./icons/sync-icon.component";
|
||||
import { VolumeUpIcon } from "./icons/volume-up-icon.component";
|
||||
import { VolumeOffIcon } from "./icons/volume-off-icon.component";
|
||||
import { VolumeDownIcon } from "./icons/volume-down-icon.component";
|
||||
import { GermanIcon } from "./flags/german-icon.component";
|
||||
|
||||
export type BsmIconType = BsvMapCharacteristic | (
|
||||
"settings"|"trash"|"favorite"|"folder"|"bsNote"|"check"|"three-dots"|"twitch"|"eye"|"play"|"checkCircleIcon"|"discord"|"info"|
|
||||
"terminal"|"desktop"|"oculus"|"add"|"cross"|"task"|"github"|"close"|"thumbUpFill"|"timerFill"|"pause"|"twitter"|"sync"|
|
||||
"copy"|"steam"|"edit"|"export"|"patreon"|"search"|"bsMapDifficulty"|"link"|"unlink"|"download"|"filter"|"mee6"|"volume-up"|"volume-off"|"volume-down"|
|
||||
"fr-FR-flag"|"es-ES-flag"|"en-US-flag"|"en-EN-flag"
|
||||
"fr-FR-flag"|"es-ES-flag"|"en-US-flag"|"en-EN-flag"|"de-DE-flag"
|
||||
);
|
||||
|
||||
export const BsmIcon = memo(({className, icon, style}: {className?: string, icon: BsmIconType, style?: CSSProperties}) => {
|
||||
@@ -76,6 +77,7 @@ export const BsmIcon = memo(({className, icon, style}: {className?: string, icon
|
||||
if(icon === "es-ES-flag"){ return <SpainIcon className={className} style={style}/> }
|
||||
if(icon === "en-US-flag"){ return <UsaIcon className={className} style={style}/> }
|
||||
if(icon === "en-EN-flag"){ return <UkIcon className={className} style={style}/> }
|
||||
if(icon === "de-DE-flag"){ return <GermanIcon className={className} style={style}/> }
|
||||
if(icon === "task"){ return <TaskIcon className={className} style={style}/> }
|
||||
if(icon === "copy"){ return <CopyIcon className={className} style={style}/> }
|
||||
if(icon === "steam"){ return <SteamIcon className={className} style={style}/> }
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { CSSProperties } from "react";
|
||||
|
||||
export function GermanIcon(props: {className?: string, style?: CSSProperties}) {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="71px" height="48px" viewBox="0 0 71 48" version="1.1" className={props.className} style={props.style}>
|
||||
<defs/>
|
||||
<g id="Flags" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" transform="translate(-239.000000, -805.000000)">
|
||||
<g transform="translate(70.000000, 70.000000)" fill-rule="nonzero" id="Germany">
|
||||
<g transform="translate(169.000000, 735.000000)">
|
||||
<g id="Bolivia-9">
|
||||
<path d="M0.52,7 L0.52,16 L70.52,16 L70.52,7 C70.52,3.33282926 67.5471707,0.36 63.88,0.36 L7.17,0.36 C5.40723053,0.357345224 3.71575038,1.05574249 2.46834517,2.3012719 C1.22093997,3.54680131 0.519998001,5.23722853 0.52,7 Z" id="Shape" fill="#434A54"/>
|
||||
<polygon id="Shape" fill="#ED5565" points="0.52 31.74 70.52 31.74 70.52 16.07 0.52 16.07"/>
|
||||
<path d="M0.52,40.76 C0.52,44.4271707 3.49282926,47.4 7.16,47.4 L63.88,47.4 C67.5471707,47.4 70.52,44.4271707 70.52,40.76 L70.52,31.76 L0.52,31.76 L0.52,40.76 Z" id="Shape" fill="#F6BB42"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import { CSSProperties } from "react";
|
||||
|
||||
export function LinkIcon(props: {className?: string, style?: CSSProperties}) {
|
||||
return (
|
||||
<svg className={props.className} style={props.style} stroke="currentColor" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg" strokeWidth="48" strokeLinecap="round" strokeLinejoin="round">
|
||||
<svg className={props.className} style={props.style} stroke="currentColor" fill="none" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg" strokeWidth="48" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M200.66,352H144a96,96,0,0,1,0-192h55.41"/>
|
||||
<path d="M312.59,160H368a96,96,0,0,1,0,192H311.34"/>
|
||||
<line x1="169.07" x2="344.93" y1="256" y2="256"/>
|
||||
|
||||
@@ -135,32 +135,49 @@ export function ModsSlide({version, onDisclamerDecline}: {version: BSVersion, on
|
||||
if(modsAvailable){
|
||||
setDownloadWidth(downloadRef?.current?.offsetWidth)
|
||||
}
|
||||
}, [modsAvailable])
|
||||
}, [modsAvailable]);
|
||||
|
||||
const renderContent = () => {
|
||||
if(!isOnline){ return <ModStatus text="pages.version-viewer.mods.no-internet" image={BeatConflictImg}/> }
|
||||
if(!modsAvailable){ return <ModStatus text="pages.version-viewer.mods.loading-mods" image={BeatWaitingImg} spin/> }
|
||||
if(!modsAvailable.size){ return <ModStatus text="pages.version-viewer.mods.mods-not-available" image={BeatConflictImg}/> }
|
||||
return (
|
||||
<>
|
||||
<div className="grow overflow-scroll w-full min-h-0 scrollbar-thin scrollbar-thumb-neutral-900 scrollbar-thumb-rounded-full">
|
||||
<ModsGrid modsMap={modsAvailable} installed={modsInstalled} modsSelected={modsSelected} onModChange={handleModChange} moreInfoMod={moreInfoMod} onWantInfos={handleMoreInfo}/>
|
||||
</div>
|
||||
<div className="h-10 shrink-0 flex items-center justify-between px-3">
|
||||
<BsmButton className="text-center rounded-md px-2 py-[2px]" text="pages.version-viewer.mods.buttons.more-infos" typeColor="cancel" withBar={false} disabled={!moreInfoMod} onClick={handleOpenMoreInfo} style={{width: downloadWith}}/>
|
||||
<div ref={downloadRef}>
|
||||
<BsmButton className="text-center rounded-md px-2 py-[2px]" text="pages.version-viewer.mods.buttons.install-or-update" withBar={false} disabled={installing} typeColor="primary" onClick={installMods}/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div ref={ref} className='shrink-0 w-full h-full px-8 pb-7 flex justify-center'>
|
||||
<div className='relative flex flex-col grow-0 bg-light-main-color-2 dark:bg-main-color-2 h-full w-full rounded-md shadow-black shadow-center overflow-hidden'>
|
||||
{isOnline && modsAvailable ? (
|
||||
<>
|
||||
<div className="grow overflow-scroll w-full min-h-0 scrollbar-thin scrollbar-thumb-neutral-900 scrollbar-thumb-rounded-full">
|
||||
<ModsGrid modsMap={modsAvailable} installed={modsInstalled} modsSelected={modsSelected} onModChange={handleModChange} moreInfoMod={moreInfoMod} onWantInfos={handleMoreInfo}/>
|
||||
</div>
|
||||
<div className="h-10 shrink-0 flex items-center justify-between px-3">
|
||||
<BsmButton className="text-center rounded-md px-2 py-[2px]" text="pages.version-viewer.mods.buttons.more-infos" typeColor="cancel" withBar={false} disabled={!moreInfoMod} onClick={handleOpenMoreInfo} style={{width: downloadWith}}/>
|
||||
<div ref={downloadRef}>
|
||||
<BsmButton className="text-center rounded-md px-2 py-[2px]" text="pages.version-viewer.mods.buttons.install-or-update" withBar={false} disabled={installing} typeColor="primary" onClick={installMods}/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="w-full h-full flex flex-col items-center justify-center text-gray-800 dark:text-gray-200">
|
||||
<img className={`w-32 h-32 ${isOnline && "spin-loading"}`} src={isOnline ? BeatWaitingImg : BeatConflictImg} alt=" "/>
|
||||
<span className="text-xl mt-3 h-0 italic">{t(isOnline ? "pages.version-viewer.mods.loading-mods" : "pages.version-viewer.mods.no-internet")}</span>
|
||||
</div>
|
||||
)}
|
||||
{renderContent()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
function ModStatus({text, image, spin = false}: {text: string, image: string, spin?: boolean}) {
|
||||
|
||||
const t = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="w-full h-full flex flex-col items-center justify-center text-gray-800 dark:text-gray-200">
|
||||
<img className={`w-32 h-32 ${spin ? "spin-loading": ""}`} src={image} alt=" "/>
|
||||
<span className="text-xl mt-3 h-0 italic">{t(text)}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ export const defaultConfiguration: {[key in DefaultConfigKey]: any} = {
|
||||
"second-color": "#ff4444",
|
||||
"theme": "os",
|
||||
"language": window.navigator.language.length <= 2 ? `${window.navigator.language}-${window.navigator.language.toLocaleUpperCase()}` : window.navigator.language,
|
||||
"supported_languages": ["en-US", "en-EN", "fr-FR", "es-ES"],
|
||||
"supported_languages": ["en-US", "en-EN", "fr-FR", "es-ES", "de-DE"],
|
||||
"default_mods": ["SongCore", "WhyIsThereNoLeaderboard", "BeatSaverDownloader", "BeatSaverVoting", "PlaylistManager"],
|
||||
"default-shared-folders": [
|
||||
"Beat Saber_Data\\CustomLevels",
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { useEffect, useState, useRef } from 'react'
|
||||
|
||||
// Code improved from : https://www.npmjs.com/package/use-delayed-state
|
||||
|
||||
export function useDelayedState<T = unknown>(initialState: T): [T, (newState: T, delay: number) => void, () => void] {
|
||||
|
||||
const [state, setState] = useState(initialState);
|
||||
const timeoutRef = useRef<NodeJS.Timeout>();
|
||||
|
||||
const setStateAfter = (newState: T, delay: number) => {
|
||||
if (delay === 0 || delay === undefined) {
|
||||
return setState(newState);
|
||||
}
|
||||
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current);
|
||||
}
|
||||
|
||||
timeoutRef.current = setTimeout(() => {
|
||||
setState(newState);
|
||||
timeoutRef.current = null;
|
||||
}, delay);
|
||||
}
|
||||
|
||||
const cancelSetState = () => {
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current);
|
||||
timeoutRef.current = null
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (timeoutRef.current) { clearTimeout(timeoutRef.current); }
|
||||
}
|
||||
}, []);
|
||||
|
||||
return [state, setStateAfter, cancelSetState]
|
||||
}
|
||||
@@ -5,9 +5,7 @@ import { RadioItem, SettingRadioArray } from "renderer/components/settings/setti
|
||||
import { BsmButton } from "renderer/components/shared/bsm-button.component";
|
||||
import { BsmIconType } from "renderer/components/svgs/bsm-icon.component";
|
||||
import { DefaultConfigKey, ThemeConfig } from "renderer/config/default-configuration.config";
|
||||
import { useObservable } from "renderer/hooks/use-observable.hook";
|
||||
import { useThemeColor } from "renderer/hooks/use-theme-color.hook";
|
||||
import { AuthUserService } from "renderer/services/auth-user.service";
|
||||
import { BsDownloaderService } from "renderer/services/bs-downloader.service";
|
||||
import { ConfigurationService } from "renderer/services/configuration.service"
|
||||
import { I18nService } from "renderer/services/i18n.service";
|
||||
@@ -26,11 +24,13 @@ import modelSaberIcon from "../../../assets/images/third-party-icons/model-saber
|
||||
import beatSaverIcon from "../../../assets/images/third-party-icons/beat-saver.png";
|
||||
import beastSaberIcon from "../../../assets/images/third-party-icons/beast-saber.png";
|
||||
import scoreSaberIcon from "../../../assets/images/third-party-icons/score-saber.png";
|
||||
import beatleaderIcon from "../../../assets/images/third-party-icons/beat-leader.png";
|
||||
import Tippy from '@tippyjs/react';
|
||||
import { MapsManagerService } from "renderer/services/maps-manager.service";
|
||||
import { PlaylistsManagerService } from "renderer/services/playlists-manager.service";
|
||||
import { ModelsManagerService } from "renderer/services/models-manager.service";
|
||||
import { useTranslation } from "renderer/hooks/use-translation.hook";
|
||||
import { VersionFolderLinkerService } from "renderer/services/version-folder-linker.service";
|
||||
|
||||
export function SettingsPage() {
|
||||
|
||||
@@ -46,6 +46,7 @@ export function SettingsPage() {
|
||||
const mapsManager = MapsManagerService.getInstance();
|
||||
const playlistsManager = PlaylistsManagerService.getInstance();
|
||||
const modelsManager = ModelsManagerService.getInstance();
|
||||
const versionLinker = VersionFolderLinkerService.getInstance();
|
||||
|
||||
const {firstColor, secondColor} = useThemeColor();
|
||||
|
||||
@@ -103,33 +104,48 @@ export function SettingsPage() {
|
||||
setLanguageSelected(languagesItems.find(l => l.value === i18nService.currentLanguage).id);
|
||||
}
|
||||
|
||||
const setDefaultInstallationFolder = () => {
|
||||
if(!progressBarService.require()){ return; }
|
||||
const setDefaultInstallationFolder = () => {
|
||||
if(!progressBarService.require()){ return; }
|
||||
|
||||
modalService.openModal(InstallationFolderModal).then(async res => {
|
||||
if(res.exitCode !== ModalExitCode.COMPLETED){ return; }
|
||||
modalService.openModal(InstallationFolderModal).then(async res => {
|
||||
if(res.exitCode !== ModalExitCode.COMPLETED){ return; }
|
||||
|
||||
const fileChooserRes = await ipcService.sendV2<{canceled: boolean, filePaths: string[]}>("choose-folder").toPromise();
|
||||
const fileChooserRes = await ipcService.sendV2<{canceled: boolean, filePaths: string[]}>("choose-folder").toPromise();
|
||||
|
||||
if(!fileChooserRes.canceled && fileChooserRes.filePaths?.length){
|
||||
progressBarService.showFake(.008);
|
||||
downloaderService.setInstallationFolder(fileChooserRes.filePaths[0]).then(res => {
|
||||
setTimeout(() => {
|
||||
progressBarService.complete();
|
||||
setTimeout(() => progressBarService.hide(true), 1000);
|
||||
}, 1000);
|
||||
if(res.success){
|
||||
setInstallationFolder(res.data);
|
||||
notificationService.notifySuccess({title: "notifications.settings.move-folder.success.titles.transfer-finished", duration: 3000});
|
||||
}
|
||||
else{
|
||||
notificationService.notifyError({title: "notifications.settings.move-folder.errors.titles.transfer-failed"});
|
||||
}
|
||||
});
|
||||
}
|
||||
if(!fileChooserRes.canceled && fileChooserRes.filePaths?.length){
|
||||
progressBarService.showFake(.008);
|
||||
|
||||
});
|
||||
}
|
||||
notificationService.notifySuccess({title: "notifications.settings.move-folder.success.titles.transfer-started", desc: "notifications.settings.move-folder.success.descs.transfer-started"});
|
||||
|
||||
downloaderService.setInstallationFolder(fileChooserRes.filePaths[0]).then(async res => {
|
||||
setTimeout(() => {
|
||||
progressBarService.complete();
|
||||
setTimeout(() => progressBarService.hide(true), 1000);
|
||||
}, 1000);
|
||||
|
||||
if(res.success){
|
||||
setInstallationFolder(res.data);
|
||||
|
||||
notificationService.notifySuccess({title: "notifications.settings.move-folder.success.titles.transfer-finished", duration: 3000});
|
||||
|
||||
versionLinker.relinkAllVersionsFolders().toPromise().catch(() => {
|
||||
notificationService.notifyError({title: "notifications.types.error", desc: "notifications.settings.move-folder.errors.descs.restore-linked-folders", duration: 15_000});
|
||||
});
|
||||
}
|
||||
else{
|
||||
|
||||
if(res?.error?.code === "COPY_TO_SUBPATH"){
|
||||
notificationService.notifyError({title: "notifications.settings.move-folder.errors.titles.transfer-failed", desc: "notifications.settings.move-folder.errors.descs.COPY_TO_SUBPATH", duration: 10_000});
|
||||
return;
|
||||
}
|
||||
|
||||
notificationService.notifyError({title: "notifications.settings.move-folder.errors.titles.transfer-failed"});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
const toogleShowSupporters = () => {
|
||||
@@ -226,6 +242,9 @@ export function SettingsPage() {
|
||||
<Tippy content="ScoreSaber" placement="top" className="font-bold bg-main-color-3" arrow={false} duration={[200, 0]}>
|
||||
<BsmImage className="h-8 cursor-pointer" image={scoreSaberIcon} onClick={e => {e.stopPropagation(); linkOpener.open("https://scoresaber.com/")}}/>
|
||||
</Tippy>
|
||||
<Tippy content="BeatLeader" placement="top" className="font-bold bg-main-color-3" arrow={false} duration={[200, 0]}>
|
||||
<BsmImage className="h-8 cursor-pointer" image={beatleaderIcon} onClick={e => {e.stopPropagation(); linkOpener.open("https://www.beatleader.xyz/")}}/>
|
||||
</Tippy>
|
||||
<Tippy content="ModelSaber" placement="top" className="font-bold bg-main-color-3" arrow={false} duration={[200, 0]}>
|
||||
<BsmImage className="h-8 cursor-pointer" image={modelSaberIcon} onClick={e => {e.stopPropagation(); linkOpener.open("https://modelsaber.com/")}}/>
|
||||
</Tippy>
|
||||
@@ -247,6 +266,9 @@ export function SettingsPage() {
|
||||
<Tippy content="ScoreSaber" placement="top" className="font-bold bg-main-color-3" arrow={false} duration={[200, 0]}>
|
||||
<BsmImage className="h-8 cursor-pointer" image={scoreSaberIcon} onClick={e => {e.stopPropagation(); linkOpener.open("https://scoresaber.com/")}}/>
|
||||
</Tippy>
|
||||
<Tippy content="BeatLeader" placement="top" className="font-bold bg-main-color-3" arrow={false} duration={[200, 0]}>
|
||||
<BsmImage className="h-8 cursor-pointer" image={beatleaderIcon} onClick={e => {e.stopPropagation(); linkOpener.open("https://www.beatleader.xyz/")}}/>
|
||||
</Tippy>
|
||||
</div>
|
||||
</li>
|
||||
<li className="bg-light-main-color-1 dark:bg-main-color-1 rounded-md group-one flex justify-between items-center basis-0 py-2 px-3 cursor-pointer" onClick={() => tooglePlaylistsDeepLinks()}>
|
||||
|
||||
@@ -84,7 +84,7 @@ export class BSVersionManagerService {
|
||||
if(!res.success){
|
||||
this.notificationService.notifyError({
|
||||
title: `notifications.custom-version.errors.titles.${res.error.title}`,
|
||||
...(res.error.msg && {desc: `notifications.custom-version.errors.msg.${res.error.msg}`})
|
||||
...(res.error.message && {desc: `notifications.custom-version.errors.msg.${res.error.message}`})
|
||||
});
|
||||
return null;
|
||||
}
|
||||
@@ -104,7 +104,7 @@ export class BSVersionManagerService {
|
||||
if(!res.success){
|
||||
this.notificationService.notifyError({
|
||||
title: `notifications.custom-version.errors.titles.${res.error.title}`,
|
||||
...(res.error.msg && {desc: `notifications.custom-version.errors.msg.${res.error.msg}`})
|
||||
...(res.error.message && {desc: `notifications.custom-version.errors.msg.${res.error.message}`})
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -133,6 +133,10 @@ export class VersionFolderLinkerService{
|
||||
return this.ipcService.sendV2("get-linked-folders", { args: { version, options } });
|
||||
}
|
||||
|
||||
public relinkAllVersionsFolders(): Observable<void>{
|
||||
return this.ipcService.sendV2("relink-all-versions-folders");
|
||||
}
|
||||
|
||||
public get currentAction$(): Observable<VersionLinkerAction>{
|
||||
return this._queue$.pipe(map(actions => actions.at(0)), distinctUntilChanged());
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
export interface BsmException {
|
||||
title: string,
|
||||
msg?: string,
|
||||
error?: Error,
|
||||
}
|
||||
export interface BsmException extends Readonly<Omit<NodeJS.ErrnoException, "message">>{
|
||||
readonly title?: string,
|
||||
readonly message?: string,
|
||||
};
|
||||
Reference in New Issue
Block a user