mirror of
https://github.com/Zagrios/bs-manager.git
synced 2026-07-03 14:08:25 +02:00
Merge remote-tracking branch 'origin/master' into feat/702-respect-system-proxy-windows
This commit is contained in:
@@ -24,7 +24,9 @@
|
||||
"unknown": "Ein unbekannter Fehler ist aufgetreten ¯\\_(ツ)_/¯",
|
||||
"warning": "Achtung",
|
||||
"continue": "Weiter",
|
||||
"shared": "Geteilt"
|
||||
"shared": "Geteilt",
|
||||
"pin": "Anheften",
|
||||
"unpin": "Lösen"
|
||||
},
|
||||
"title-bar": {
|
||||
"outdated": "veraltet"
|
||||
@@ -48,9 +50,13 @@
|
||||
"debug-description": "Aktiviert das Ausgabeprotokollfenster für IPA. Dies zeigt die Debug-Konsole, welche die Mods verwenden.",
|
||||
"outdated-tippy": "Diese Version ist veraltet, und einige Mods oder Funktionen funktionieren möglicherweise nicht wie erwartet. Es wird empfohlen, die empfohlene Version ({recommendedVersion}) von Beat Saber zu verwenden, um die neuesten Funktionen und Fehlerbehebungen zu genießen.",
|
||||
"advanced-launch": {
|
||||
"button": "Erweiterter Start",
|
||||
"button": "Startoptionen",
|
||||
"placeholder": "Weitere Argumente, bspw: --revert; --nowait"
|
||||
}
|
||||
},
|
||||
"skipsteam": "Steam überspringen",
|
||||
"skipsteam-description": "Verhindert, dass Steam automatisch mit Beat Saber geöffnet wird. Aktivieren Sie dies, wenn Sie eine andere VR-Laufzeit wie WiVRn oder Monado verwenden, mit der SteamVR interferieren könnte.",
|
||||
"map-editor": "Karteneditor",
|
||||
"map-editor-description": "Starte den offiziellen Beat Saber-Karteneditor anstelle des Spiels."
|
||||
},
|
||||
"maps": {
|
||||
"search-bar": {
|
||||
@@ -442,11 +448,13 @@
|
||||
"success": {
|
||||
"titles": {
|
||||
"BS_LAUNCHING": "Starte... 🚀",
|
||||
"STEAM_LAUNCHING": "Steam startet!"
|
||||
"STEAM_LAUNCHING": "Steam startet!",
|
||||
"SKIPPING_STEAM_LAUNCH": "Steam-Start überspringen"
|
||||
},
|
||||
"msg": {
|
||||
"BS_LAUNCHING": "Vergiss nicht, dich aufzuwärmen 😉",
|
||||
"STEAM_LAUNCHING": "Beat Saber wird automatisch nach Steam gestartet."
|
||||
"STEAM_LAUNCHING": "Beat Saber wird automatisch nach Steam gestartet.",
|
||||
"SKIPPING_STEAM_LAUNCH": "Ich hoffe, du weißt, was du tust :)"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
@@ -606,6 +614,10 @@
|
||||
"ENOSPC": "Die Festplatte ist voll, machen Sie Platz und versuchen Sie es erneut.",
|
||||
"UNKNOWN_ERROR": "Ein unbekannter Fehler ist beim Verknüpfen des Ordners aufgetreten."
|
||||
}
|
||||
},
|
||||
"adding-error": {
|
||||
"title": "Das Hinzufügen des freigegebenen Ordners ist fehlgeschlagen",
|
||||
"msg": "Sie können \"{folder}\" nicht zu freigegebenen Ordnern hinzufügen."
|
||||
}
|
||||
},
|
||||
"create-launch-shortcut": {
|
||||
@@ -805,9 +817,14 @@
|
||||
"add-folder": "Ordner hinzufügen",
|
||||
"link-folder": "Ordner verknüpfen",
|
||||
"unlink-folder": "Ordner-Verknüpfung aufheben",
|
||||
"link-all": "Alle verlinken"
|
||||
"link-all": "Alle verlinken",
|
||||
"remove-from-the-list": "Aus der Liste entfernen"
|
||||
}
|
||||
},
|
||||
"adding-shared-folder": {
|
||||
"title": "Freigegebenen Ordner hinzufügen",
|
||||
"description": "Beim Verknüpfen des Ordners \"{folder}\" können Probleme auftreten. Möchten Sie ihn wirklich hinzufügen?"
|
||||
},
|
||||
"create-launch-shortcut": {
|
||||
"title": "Verknüpfung erstellen",
|
||||
"desc": "Das Erstellen einer Verknüpfung ermöglicht es dir, Beat Saber mit den ausgewählten Optionen zu starten, ohne durch BSManager zu gehen.",
|
||||
|
||||
@@ -24,7 +24,9 @@
|
||||
"unknown": "An unknown error occurred ¯\\_(ツ)_/¯",
|
||||
"warning": "Warning",
|
||||
"continue": "Continue",
|
||||
"shared": "Shared"
|
||||
"shared": "Shared",
|
||||
"pin": "Pin",
|
||||
"unpin": "Unpin"
|
||||
},
|
||||
"title-bar": {
|
||||
"outdated": "outdated"
|
||||
@@ -48,9 +50,13 @@
|
||||
"debug-description": "Enables the output log window for IPA. This will show the debug console that mods use.",
|
||||
"outdated-tippy": "This version is outdated, and some mods or features may no longer work as expected. Prefer using the recommended version ({recommendedVersion}) of Beat Saber to enjoy the latest features and bugfixes.",
|
||||
"advanced-launch": {
|
||||
"button": "Advanced launch",
|
||||
"button": "Launch options",
|
||||
"placeholder": "Additional arguments ex: --revert; --nowait"
|
||||
}
|
||||
},
|
||||
"skipsteam": "Skip Steam",
|
||||
"skipsteam-description": "Stops Steam from opening automatically with Beat Saber, enable if you are using a different VR runtime like WiVRn or Monado that SteamVR may interfere with.",
|
||||
"map-editor": "Map Editor",
|
||||
"map-editor-description": "Start the official Beat Saber map editor instead of the game."
|
||||
},
|
||||
"maps": {
|
||||
"search-bar": {
|
||||
@@ -257,7 +263,7 @@
|
||||
"confirm-btn": "Yes I'm sure"
|
||||
},
|
||||
"error-notification": {
|
||||
"message": "An error occur, unable to disable hardware acceleration."
|
||||
"message": "An error occured, unable to disable hardware acceleration."
|
||||
}
|
||||
},
|
||||
"use-symlinks": {
|
||||
@@ -269,7 +275,7 @@
|
||||
"confirm-btn": "Yes I'm sure"
|
||||
},
|
||||
"error-notification": {
|
||||
"message": "An error occur, unable to change symlinks settings."
|
||||
"message": "An error occured, unable to change symlinks settings."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -442,11 +448,13 @@
|
||||
"success": {
|
||||
"titles": {
|
||||
"BS_LAUNCHING": "Launching...🚀",
|
||||
"STEAM_LAUNCHING": "Steam is launching!"
|
||||
"STEAM_LAUNCHING": "Steam is launching!",
|
||||
"SKIPPING_STEAM_LAUNCH": "Skipping Steam launch"
|
||||
},
|
||||
"msg": {
|
||||
"BS_LAUNCHING": "Don't forget to warm up 😉",
|
||||
"STEAM_LAUNCHING": "Beat Saber will automatically launch after Steam."
|
||||
"STEAM_LAUNCHING": "Beat Saber will automatically launch after Steam.",
|
||||
"SKIPPING_STEAM_LAUNCH": "I hope you know what you're doing :)"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
@@ -606,6 +614,10 @@
|
||||
"ENOSPC": "The disk is full, make some space and try again.",
|
||||
"UNKNOWN_ERROR": "An unknown error has occurred while linking the folder."
|
||||
}
|
||||
},
|
||||
"adding-error": {
|
||||
"title": "Adding shared folder failed",
|
||||
"msg": "You can't add \"{folder}\" to shared folders."
|
||||
}
|
||||
},
|
||||
"create-launch-shortcut": {
|
||||
@@ -800,9 +812,14 @@
|
||||
"add-folder": "Add Folder",
|
||||
"link-folder": "Link Folder",
|
||||
"unlink-folder": "Unlink Folder",
|
||||
"link-all": "Link all"
|
||||
"link-all": "Link all",
|
||||
"remove-from-the-list": "Remove from the list"
|
||||
}
|
||||
},
|
||||
"adding-shared-folder": {
|
||||
"title": "Adding Shared Folder",
|
||||
"description": "You might experience issues when linking the \"{folder}\" folder. Are you sure you want to add it?"
|
||||
},
|
||||
"create-launch-shortcut": {
|
||||
"title": "Create a shortcut",
|
||||
"desc": "Creating a shortcut will allow you to start Beat Saber with the chosen options without going through BSManager.",
|
||||
|
||||
@@ -24,7 +24,9 @@
|
||||
"unknown": "Se ha producido un error desconocido ¯\\_(ツ)_/¯",
|
||||
"warning": "Advertencia",
|
||||
"continue": "Continuar",
|
||||
"shared": "Compartido"
|
||||
"shared": "Compartido",
|
||||
"pin": "Fijar",
|
||||
"unpin": "Desfijar"
|
||||
},
|
||||
"title-bar": {
|
||||
"outdated": "obsoleto"
|
||||
@@ -48,7 +50,7 @@
|
||||
"debug-description": "Habilita la ventana de registro de salida para IPA. Esto mostrará la consola de depuración que usan los mods.",
|
||||
"outdated-tippy": "Esta versión está desactualizada, y algunos mods o funciones pueden no funcionar como se espera. Es mejor usar la versión recomendada ({recommendedVersion}) de Beat Saber para disfrutar de las últimas características y correcciones.",
|
||||
"advanced-launch": {
|
||||
"button": "Opciones avanzadas",
|
||||
"button": "Opciones de lanzamiento",
|
||||
"placeholder": "Argumentos adicionales ej: --revert; --no-wait"
|
||||
}
|
||||
},
|
||||
@@ -60,7 +62,11 @@
|
||||
"export-maps": "Exportar mapas",
|
||||
"delete-maps": "Borrar mapas",
|
||||
"delete-duplicate-maps": "Borrar duplicados"
|
||||
}
|
||||
},
|
||||
"skipsteam": "Saltar Steam",
|
||||
"skipsteam-description": "Evita que Steam se abra automáticamente con Beat Saber, habilita esto si estás usando un runtime de VR diferente como WiVRn o Monado que SteamVR podría interferir.",
|
||||
"map-editor": "Editor de mapas",
|
||||
"map-editor-description": "Inicia el editor oficial de mapas de Beat Saber en lugar del juego."
|
||||
},
|
||||
"tabs": {
|
||||
"maps": {
|
||||
@@ -442,11 +448,13 @@
|
||||
"success": {
|
||||
"titles": {
|
||||
"BS_LAUNCHING": "Lanzamiento...🚀",
|
||||
"STEAM_LAUNCHING": "¡Steam se está iniciando!"
|
||||
"STEAM_LAUNCHING": "¡Steam se está iniciando!",
|
||||
"SKIPPING_STEAM_LAUNCH": "Saltando el lanzamiento de Steam"
|
||||
},
|
||||
"msg": {
|
||||
"BS_LAUNCHING": "No olvides calentar 😉",
|
||||
"STEAM_LAUNCHING": "Beat Saber se iniciará automáticamente después de Steam."
|
||||
"STEAM_LAUNCHING": "Beat Saber se iniciará automáticamente después de Steam.",
|
||||
"SKIPPING_STEAM_LAUNCH": "Espero que sepas lo que estás haciendo :)"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
@@ -606,6 +614,10 @@
|
||||
"ENOSPC": "El disco está lleno, libera espacio e inténtalo de nuevo.",
|
||||
"UNKNOWN_ERROR": "Se ha producido un error desconocido al enlazar la carpeta."
|
||||
}
|
||||
},
|
||||
"adding-error": {
|
||||
"title": "Error al agregar una carpeta compartida",
|
||||
"msg": "No puedes agregar \"{folder}\" a las carpetas compartidas."
|
||||
}
|
||||
},
|
||||
"create-launch-shortcut": {
|
||||
@@ -805,9 +817,14 @@
|
||||
"add-folder": "Añadir Carpeta",
|
||||
"link-folder": "Enlazar Carpeta",
|
||||
"unlink-folder": "Desenlazar Carpeta",
|
||||
"link-all": "Enlazar todo"
|
||||
"link-all": "Enlazar todo",
|
||||
"remove-from-the-list": "Eliminar de la lista"
|
||||
}
|
||||
},
|
||||
"adding-shared-folder": {
|
||||
"title": "Agregar una carpeta compartida",
|
||||
"description": "Es posible que experimentes problemas al vincular la carpeta \"{folder}\". ¿Estás seguro de que deseas agregarla?"
|
||||
},
|
||||
"create-launch-shortcut": {
|
||||
"title": "Crear un atajo",
|
||||
"desc": "Crear un atajo te permitirá iniciar Beat Saber con las opciones seleccionadas sin pasar por BSManager.",
|
||||
|
||||
@@ -24,7 +24,9 @@
|
||||
"unknown": "Une erreur inconnue s'est produite ¯\\_(ツ)_/¯",
|
||||
"warning": "Attention",
|
||||
"continue": "Continuer",
|
||||
"shared": "Partagé"
|
||||
"shared": "Partagé",
|
||||
"pin": "Épingler",
|
||||
"unpin": "Désépingler"
|
||||
},
|
||||
"title-bar": {
|
||||
"outdated": "obsolète"
|
||||
@@ -48,9 +50,13 @@
|
||||
"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": "Lancement avancé",
|
||||
"button": "Options de lancement",
|
||||
"placeholder": "Arguments supplémentaires ex: --revert; --nowait"
|
||||
}
|
||||
},
|
||||
"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.",
|
||||
"map-editor": "Éditeur de map",
|
||||
"map-editor-description": "Lance l'éditeur officiel de map de Beat Saber au lieu du jeu."
|
||||
},
|
||||
"maps": {
|
||||
"search-bar": {
|
||||
@@ -442,11 +448,13 @@
|
||||
"success": {
|
||||
"titles": {
|
||||
"BS_LAUNCHING": "Lancement...🚀",
|
||||
"STEAM_LAUNCHING": "Steam se lance !"
|
||||
"STEAM_LAUNCHING": "Steam se lance !",
|
||||
"SKIPPING_STEAM_LAUNCH": "Sauter le lancement de Steam"
|
||||
},
|
||||
"msg": {
|
||||
"BS_LAUNCHING": "N'oublie pas de t'échauffer 😉",
|
||||
"STEAM_LAUNCHING": "Beat Saber se lancera automatiquement après Steam."
|
||||
"STEAM_LAUNCHING": "Beat Saber se lancera automatiquement après Steam.",
|
||||
"SKIPPING_STEAM_LAUNCH": "J'espère que vous savez ce que vous faites :)"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
@@ -606,6 +614,10 @@
|
||||
"ENOSPC": "Le disque est plein, faites de la place et réessayez.",
|
||||
"UNKNOWN_ERROR": "Une erreur inconnue est survenue lors de la liaison du dossier."
|
||||
}
|
||||
},
|
||||
"adding-error": {
|
||||
"title": "L'ajout du dossier partagé a échoué",
|
||||
"msg": "Vous ne pouvez pas ajouter \"{folder}\" aux dossiers partagés."
|
||||
}
|
||||
|
||||
},
|
||||
@@ -806,9 +818,14 @@
|
||||
"add-folder": "Ajouter un dossier",
|
||||
"link-folder": "Lier le dossier",
|
||||
"unlink-folder": "Délier le dossier",
|
||||
"link-all": "Tout lier"
|
||||
"link-all": "Tout lier",
|
||||
"remove-from-the-list": "Retirer de la liste"
|
||||
}
|
||||
},
|
||||
"adding-shared-folder": {
|
||||
"title": "Ajout d'un dossier partagé",
|
||||
"description": "Vous pourriez rencontrer des problèmes lors de la liaison du dossier \"{folder}\". Êtes-vous sûr de vouloir l'ajouter ?"
|
||||
},
|
||||
"create-launch-shortcut": {
|
||||
"title": "Créer un raccourci",
|
||||
"desc": "Créer un raccourci te permettra de démarrer Beat Saber avec les options choisies sans passer par BSManager.",
|
||||
|
||||
@@ -24,7 +24,9 @@
|
||||
"unknown": "不明なエラーが発生しました (´・ω・`)",
|
||||
"warning": "警告",
|
||||
"continue": "続ける",
|
||||
"shared": "共有"
|
||||
"shared": "共有",
|
||||
"pin": "固定",
|
||||
"unpin": "固定解除"
|
||||
},
|
||||
"title-bar": {
|
||||
"outdated": "時代遅れ"
|
||||
@@ -48,9 +50,11 @@
|
||||
"debug-description": "IPAの出力ログウィンドウを有効にします。これはMODのデバッグコンソールを表示します。",
|
||||
"outdated-tippy": "このバージョンは古いため、一部のMODや機能が期待通りに動作しない可能性があります。最新の機能やバグ修正を楽しむには、推奨バージョン ({recommendedVersion}) のBeat Saberを使用することをお勧めします。",
|
||||
"advanced-launch": {
|
||||
"button": "高度な起動",
|
||||
"button": "起動オプション",
|
||||
"placeholder": "追加引数 例:--revert; --nowait"
|
||||
}
|
||||
},
|
||||
"skipsteam": "Steamをスキップ",
|
||||
"skipsteam-description": "Beat Saberと一緒にSteamが自動的に開くのを防ぎます。SteamVRが干渉する可能性のあるWiVRnやMonadoなど、別のVRランタイムを使用している場合は有効にしてください。"
|
||||
},
|
||||
"maps": {
|
||||
"search-bar": {
|
||||
@@ -442,11 +446,13 @@
|
||||
"success": {
|
||||
"titles": {
|
||||
"BS_LAUNCHING": "起動中...🚀",
|
||||
"STEAM_LAUNCHING": "Steamの起動を確認しました!"
|
||||
"STEAM_LAUNCHING": "Steamの起動を確認しました!",
|
||||
"SKIPPING_STEAM_LAUNCH": "Steamの起動をスキップ"
|
||||
},
|
||||
"msg": {
|
||||
"BS_LAUNCHING": "ウォームアップを忘れずに 😉",
|
||||
"STEAM_LAUNCHING": "Beat SaberはSteamの起動後に自動的に起動します。"
|
||||
"STEAM_LAUNCHING": "Beat SaberはSteamの起動後に自動的に起動します。",
|
||||
"SKIPPING_STEAM_LAUNCH": "あなたが何をしているのか分かっていることを願っています :)"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
@@ -606,6 +612,10 @@
|
||||
"ENOSPC": "ディスクがいっぱいです。空き容量を作ってもう一度試してください。",
|
||||
"UNKNOWN_ERROR": "フォルダをリンク中に不明なエラーが発生しました。"
|
||||
}
|
||||
},
|
||||
"adding-error": {
|
||||
"title": "共有フォルダの追加に失敗しました",
|
||||
"msg": "「{folder}」を共有フォルダに追加することはできません。"
|
||||
}
|
||||
},
|
||||
"create-launch-shortcut": {
|
||||
@@ -805,9 +815,14 @@
|
||||
"add-folder": "フォルダーを追加",
|
||||
"link-folder": "フォルダーをリンク",
|
||||
"unlink-folder": "フォルダーのリンクを解除",
|
||||
"link-all": "すべてリンク"
|
||||
"link-all": "すべてリンク",
|
||||
"remove-from-the-list": "リストから削除"
|
||||
}
|
||||
},
|
||||
"adding-shared-folder": {
|
||||
"title": "共有フォルダの追加",
|
||||
"description": "「{folder}」フォルダをリンクするときに問題が発生する可能性があります。追加してもよろしいですか?"
|
||||
},
|
||||
"create-launch-shortcut": {
|
||||
"title": "ショートカットを作成",
|
||||
"desc": "ショートカットを作成すると、BSManagerを起動せずに、直接Beat Saberを起動できます。",
|
||||
|
||||
@@ -24,7 +24,9 @@
|
||||
"unknown": "알 수 없는 오류가 발생했습니다 (´・ω・`)",
|
||||
"warning": "경고",
|
||||
"continue": "계속",
|
||||
"shared": "공유"
|
||||
"shared": "공유",
|
||||
"pin": "고정",
|
||||
"unpin": "고정 해제"
|
||||
},
|
||||
"title-bar": {
|
||||
"outdated": "구식"
|
||||
@@ -48,9 +50,13 @@
|
||||
"debug-description": "IPA 출력 로그 창을 활성화하여 MOD의 디버그 콘솔을 표시합니다.",
|
||||
"outdated-tippy": "이 버전은 오래되어 일부 모드나 기능이 예상대로 작동하지 않을 수 있습니다. 최신 기능과 버그 수정을 사용하려면 권장 버전({recommendedVersion})의 Beat Saber를 사용하는 것이 좋습니다.",
|
||||
"advanced-launch": {
|
||||
"button": "고급 실행",
|
||||
"button": "실행 옵션",
|
||||
"placeholder": "추가 인수 예: --revert; --nowait"
|
||||
}
|
||||
},
|
||||
"skipsteam": "Steam 실행 건너뛰기",
|
||||
"skipsteam-description": "Beat Saber와 함께 Steam이 자동으로 열리는 것을 방지합니다. SteamVR이 간섭할 수 있는 WiVRn 또는 Monado와 같은 다른 VR 런타임을 사용하는 경우 활성화하세요.",
|
||||
"map-editor": "맵 편집기",
|
||||
"map-editor-description": "게임 대신 공식 Beat Saber 맵 편집기를 실행해."
|
||||
},
|
||||
"maps": {
|
||||
"search-bar": {
|
||||
@@ -442,11 +448,13 @@
|
||||
"success": {
|
||||
"titles": {
|
||||
"BS_LAUNCHING": "실행 중...🚀",
|
||||
"STEAM_LAUNCHING": "Steam 실행 확인 완료!"
|
||||
"STEAM_LAUNCHING": "Steam 실행 확인 완료!",
|
||||
"SKIPPING_STEAM_LAUNCH": "Steam 실행 건너뛰기"
|
||||
},
|
||||
"msg": {
|
||||
"BS_LAUNCHING": "워밍업을 잊지 마세요 😉",
|
||||
"STEAM_LAUNCHING": "Beat Saber는 Steam 실행 후 자동으로 시작됩니다."
|
||||
"STEAM_LAUNCHING": "Beat Saber는 Steam 실행 후 자동으로 시작됩니다.",
|
||||
"SKIPPING_STEAM_LAUNCH": "신이 무엇을 하고 있는지 알고 있기를 바랍니다 :)"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
@@ -606,6 +614,10 @@
|
||||
"ENOSPC": "디스크가 가득 찼습니다. 공간을 확보하고 다시 시도하세요.",
|
||||
"UNKNOWN_ERROR": "폴더를 연결하는 중 알 수 없는 오류가 발생했습니다."
|
||||
}
|
||||
},
|
||||
"adding-error": {
|
||||
"title": "공유 폴더 추가에 실패했습니다.",
|
||||
"msg": "공유 폴더에 「{folder}」을 추가할 수 없습니다."
|
||||
}
|
||||
},
|
||||
"create-launch-shortcut": {
|
||||
@@ -805,9 +817,14 @@
|
||||
"add-folder": "폴더 추가",
|
||||
"link-folder": "폴더 연결",
|
||||
"unlink-folder": "폴더 연결 해제",
|
||||
"link-all": "모두 연결"
|
||||
"link-all": "모두 연결",
|
||||
"remove-from-the-list": "목록에서 제거"
|
||||
}
|
||||
},
|
||||
"adding-shared-folder": {
|
||||
"title": "공유 폴더 추가",
|
||||
"description": "「{folder}」폴더를 연결할 때 문제가 발생할 수 있습니다. 정말 추가하시겠습니까?"
|
||||
},
|
||||
"create-launcher-shortcut": {
|
||||
"title": "런처 바로가기 만들기",
|
||||
"description": "바탕화면에 실행 파일의 바로가기를 만들어 BSManager를 손쉽게 실행하세요.",
|
||||
|
||||
@@ -24,7 +24,9 @@
|
||||
"unknown": "Неизвестная ошибка ¯\\_(ツ)_/¯",
|
||||
"warning": "Внимание",
|
||||
"continue": "Продолжить",
|
||||
"shared": "Общий"
|
||||
"shared": "Общий",
|
||||
"pin": "Закрепить",
|
||||
"unpin": "Открепить"
|
||||
},
|
||||
"title-bar": {
|
||||
"outdated": "устаревший"
|
||||
@@ -48,9 +50,13 @@
|
||||
"debug-description": "Включает окно логов для IPA. В окне будут отображаться логи модов.",
|
||||
"outdated-tippy": "Эта версия устарела, и некоторые моды или функции могут работать не так, как ожидалось. Рекомендуется использовать рекомендуемую версию ({recommendedVersion}) Beat Saber, чтобы воспользоваться последними функциями и исправлениями ошибок.",
|
||||
"advanced-launch": {
|
||||
"button": "Расширенный запуск",
|
||||
"button": "Параметры запуска",
|
||||
"placeholder": "Параметры запуска, например: --revert; --nowait"
|
||||
}
|
||||
},
|
||||
"skipsteam": "Пропустить Steam",
|
||||
"skipsteam-description": "Предотвращает автоматическое открытие Steam с Beat Saber, включите, если вы используете другую VR-среду, такую как WiVRn или Monado, с которой SteamVR может мешать.",
|
||||
"map-editor": "Редактор карт",
|
||||
"map-editor-description": "Запусти официальный редактор карт Beat Saber вместо игры."
|
||||
},
|
||||
"maps": {
|
||||
"search-bar": {
|
||||
@@ -442,11 +448,13 @@
|
||||
"success": {
|
||||
"titles": {
|
||||
"BS_LAUNCHING": "Запуск...🚀",
|
||||
"STEAM_LAUNCHING": "Steam запускается!"
|
||||
"STEAM_LAUNCHING": "Steam запускается!",
|
||||
"SKIPPING_STEAM_LAUNCH": "Пропуск запуска Steam"
|
||||
},
|
||||
"msg": {
|
||||
"BS_LAUNCHING": "Не забудьте разогреться 😉",
|
||||
"STEAM_LAUNCHING": "Beat Saber запустится сам после Steam."
|
||||
"STEAM_LAUNCHING": "Beat Saber запустится сам после Steam.",
|
||||
"SKIPPING_STEAM_LAUNCH": "Надеюсь, вы знаете, что делаете :)"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
@@ -606,6 +614,10 @@
|
||||
"ENOSPC": "Диск заполнен, освободите место и попробуйте снова.",
|
||||
"UNKNOWN_ERROR": "Произошла неизвестная ошибка при связывании папки."
|
||||
}
|
||||
},
|
||||
"adding-error": {
|
||||
"title": "Не удалось добавить общую папку.",
|
||||
"msg": "Вы не можете добавлять \"{folder}\" в общие папки."
|
||||
}
|
||||
},
|
||||
"create-launch-shortcut": {
|
||||
@@ -805,9 +817,14 @@
|
||||
"add-folder": "Добавить папку",
|
||||
"link-folder": "Связать папку",
|
||||
"unlink-folder": "Отвязать папку",
|
||||
"link-all": "Связать всё"
|
||||
"link-all": "Связать всё",
|
||||
"remove-from-the-list": "Удалить из списка"
|
||||
}
|
||||
},
|
||||
"adding-shared-folder": {
|
||||
"title": "Добавление общей папки",
|
||||
"description": "У вас могут возникнуть проблемы при связывании папки \"{folder}\". Вы уверены, что хотите ее добавить?"
|
||||
},
|
||||
"create-launch-shortcut": {
|
||||
"title": "Создать ярлык",
|
||||
"desc": "Ярлык позволит вам запустить Beat Saber без помощи BSManager.",
|
||||
|
||||
@@ -24,7 +24,9 @@
|
||||
"unknown": "發生了一個錯誤 ¯\\_(ツ)_/¯",
|
||||
"warning": "警告",
|
||||
"continue": "繼續",
|
||||
"shared": "共享"
|
||||
"shared": "共享",
|
||||
"pin": "固定",
|
||||
"unpin": "取消固定"
|
||||
},
|
||||
"title-bar": {
|
||||
"outdated": "過時"
|
||||
@@ -48,9 +50,13 @@
|
||||
"debug-description": "為 IPA 啟用輸出日誌窗口。 會顯示所用 Mod 的 debug 輸出",
|
||||
"outdated-tippy": "此版本已過時,某些模組或功能可能無法按預期運作。建議使用推薦版本的 Beat Saber ({recommendedVersion}),以享受最新功能和修復。",
|
||||
"advanced-launch": {
|
||||
"button": "高級啟動",
|
||||
"button": "啟動選項",
|
||||
"placeholder": "額外啟動參數,例如: --revert; --nowait"
|
||||
}
|
||||
},
|
||||
"skipsteam": "跳過 Steam",
|
||||
"skipsteam-description": "防止 Steam 與 Beat Saber 自動打開,如果您使用的是其他 VR 執行時,如 WiVRn 或 Monado,SteamVR 可能會干擾,請啟用此選項。",
|
||||
"map-editor": "地圖編輯器",
|
||||
"map-editor-description": "啟動官方的 Beat Saber 地圖編輯器,而不是遊戲。"
|
||||
},
|
||||
"maps": {
|
||||
"search-bar": {
|
||||
@@ -442,11 +448,13 @@
|
||||
"success": {
|
||||
"titles": {
|
||||
"BS_LAUNCHING": "啟動中...🚀",
|
||||
"STEAM_LAUNCHING": "Steam 正在啟動!"
|
||||
"STEAM_LAUNCHING": "Steam 正在啟動!",
|
||||
"SKIPPING_STEAM_LAUNCH": "跳過 Steam 啟動"
|
||||
},
|
||||
"msg": {
|
||||
"BS_LAUNCHING": "別忘了熱身哦 😉",
|
||||
"STEAM_LAUNCHING": "Beat Saber 會在 Steam 啟動後自動啟動。"
|
||||
"STEAM_LAUNCHING": "Beat Saber 會在 Steam 啟動後自動啟動。",
|
||||
"SKIPPING_STEAM_LAUNCH": "希望你知道自己在做什麼 :)"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
@@ -606,6 +614,10 @@
|
||||
"ENOSPC": "磁碟已滿,請騰出空間後再試。",
|
||||
"UNKNOWN_ERROR": "連結文件夾時發生未知錯誤。"
|
||||
}
|
||||
},
|
||||
"adding-error": {
|
||||
"title": "添加共享文件夹失败",
|
||||
"msg": "您无法将\"{folder}\"添加到共享文件夹。"
|
||||
}
|
||||
},
|
||||
"create-launch-shortcut": {
|
||||
@@ -805,9 +817,14 @@
|
||||
"add-folder": "新增文件夾",
|
||||
"link-folder": "關聯文件夾",
|
||||
"unlink-folder": "取消關聯文件夾",
|
||||
"link-all": "關聯所有"
|
||||
"link-all": "關聯所有",
|
||||
"remove-from-the-list": "從列表中移除"
|
||||
}
|
||||
},
|
||||
"adding-shared-folder": {
|
||||
"title": "添加共享文件夹",
|
||||
"description": "链接\"{folder}\"文件夹时可能会遇到问题。您确定要添加它吗?"
|
||||
},
|
||||
"create-launch-shortcut": {
|
||||
"title": "創建捷徑",
|
||||
"desc": "創建捷徑使得你可以在不通過 BSManager 的情況下以特定選項啟動 BeatSaber",
|
||||
|
||||
@@ -24,7 +24,9 @@
|
||||
"Unknown": "发生了一个错误 ¯\\_(ツ)_/¯",
|
||||
"warning": "警告",
|
||||
"continue": "继续",
|
||||
"shared": "共享"
|
||||
"shared": "共享",
|
||||
"pin": "固定",
|
||||
"unpin": "取消固定"
|
||||
},
|
||||
"title-bar": {
|
||||
"outdated": "过时"
|
||||
@@ -48,9 +50,13 @@
|
||||
"debug-description": "为 IPA 启用输出日志窗口。 会显示所用 Mod 的 debug 输出",
|
||||
"outdated-tippy": "此版本已过时,某些模组或功能可能无法按预期运行。建议使用推荐版本的 Beat Saber ({recommendedVersion}),以享受最新功能和修复。",
|
||||
"advanced-launch": {
|
||||
"button": "高级启动",
|
||||
"button": "启动选项",
|
||||
"placeholder": "额外启动参数,例如: --revert; --nowait"
|
||||
}
|
||||
},
|
||||
"skipsteam": "跳过 Steam",
|
||||
"skipsteam-description": "防止 Steam 与 Beat Saber 自动打开,如果您使用的是其他 VR 运行时,如 WiVRn 或 Monado,SteamVR 可能会干扰,请启用此选项。",
|
||||
"map-editor": "地图编辑器",
|
||||
"map-editor-description": "启动官方的 Beat Saber 地图编辑器,而不是游戏。"
|
||||
},
|
||||
"maps": {
|
||||
"search-bar": {
|
||||
@@ -442,11 +448,13 @@
|
||||
"success": {
|
||||
"titles": {
|
||||
"BS_LAUNCHING": "启动中...🚀",
|
||||
"STEAM_LAUNCHING": "Steam 正在启动!"
|
||||
"STEAM_LAUNCHING": "Steam 正在启动!",
|
||||
"SKIPPING_STEAM_LAUNCH": "跳过 Steam 启动"
|
||||
},
|
||||
"msg": {
|
||||
"BS_LAUNCHING": "别忘了热身哦 😉",
|
||||
"STEAM_LAUNCHING": "Beat Saber 会在 Steam 启动后自动启动。"
|
||||
"STEAM_LAUNCHING": "Beat Saber 会在 Steam 启动后自动启动。",
|
||||
"SKIPPING_STEAM_LAUNCH": "希望你知道自己在做什么 :)"
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
@@ -606,6 +614,10 @@
|
||||
"ENOSPC": "磁盘已满,请腾出空间后再试。",
|
||||
"UNKNOWN_ERROR": "链接文件夹时发生未知错误。"
|
||||
}
|
||||
},
|
||||
"adding-error": {
|
||||
"title": "添加共享文件夹失败",
|
||||
"msg": "您无法将\"{folder}\"添加到共享文件夹。"
|
||||
}
|
||||
},
|
||||
"create-launch-shortcut": {
|
||||
@@ -805,9 +817,14 @@
|
||||
"add-folder": "添加文件夹",
|
||||
"link-folder": "关联文件夹",
|
||||
"unlink-folder": "取消关联文件夹",
|
||||
"link-all": "关联所有"
|
||||
"link-all": "关联所有",
|
||||
"remove-from-the-list": "从列表中移除"
|
||||
}
|
||||
},
|
||||
"adding-shared-folder": {
|
||||
"title": "添加共享文件夹",
|
||||
"description": "链接\"{folder}\"文件夹时可能会遇到问题。您确定要添加它吗?"
|
||||
},
|
||||
"create-launch-shortcut": {
|
||||
"title": "创建快捷方式",
|
||||
"desc": "创建快捷方式使得你可以在不通过 BSManager 的情况下以特定选项启动 BeatSaber",
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 782 KiB |
@@ -47,6 +47,18 @@ Once Proton is installed, when you open your BSManager application for the first
|
||||
|
||||
# Troubleshooting
|
||||
|
||||
## Missing Icons in game
|
||||
|
||||
<p align="center">
|
||||
<img src="../assets/linux-missing-icons.png" alt="Linux Missing Icons" width="300"/>
|
||||
<br>
|
||||
NOTE: "?" are missing unicode icons
|
||||
</p>
|
||||
|
||||
This is due to [BSML](https://github.com/monkeymanboy/BeatSaberMarkupLanguage) using a missing font (`Segoe UI Symbols`) for unicode emojis.
|
||||
|
||||
To fix the issue, you need to get a copy of `seguisym.ttf` from the fonts folder an official copy Windows. Once you have a copy, add the font file to `~/.steam/steam/steamapps/compatdata/620980/pfx/drive_c/windows/Fonts`.
|
||||
|
||||
## Permission denied on "bs-versions.json"
|
||||
|
||||
<pre>
|
||||
|
||||
@@ -7,6 +7,7 @@ import { sToMs } from "../../../shared/helpers/time.helpers";
|
||||
import { LinuxService } from "../linux.service";
|
||||
import { bsmSpawn } from "main/helpers/os.helpers";
|
||||
import { IS_FLATPAK } from "main/constants";
|
||||
import { LaunchMods } from "shared/models/bs-launch/launch-option.interface";
|
||||
|
||||
export abstract class AbstractLauncherService {
|
||||
|
||||
@@ -24,16 +25,20 @@ export abstract class AbstractLauncherService {
|
||||
if(!launchOptions.version.steam && !launchOptions.version.oculus){
|
||||
launchArgs.push("--no-yeet")
|
||||
}
|
||||
if (launchOptions.oculus) {
|
||||
if(launchOptions.launchMods?.includes(LaunchMods.OCULUS)) {
|
||||
launchArgs.push("-vrmode");
|
||||
launchArgs.push("oculus");
|
||||
}
|
||||
if (launchOptions.desktop) {
|
||||
if(launchOptions.launchMods?.includes(LaunchMods.FPFC)) {
|
||||
launchArgs.push("fpfc");
|
||||
}
|
||||
if (launchOptions.debug) {
|
||||
if(launchOptions.launchMods?.includes(LaunchMods.DEBUG)) {
|
||||
launchArgs.push("--verbose");
|
||||
}
|
||||
if(launchOptions.launchMods?.includes(LaunchMods.EDITOR)) {
|
||||
launchArgs.push("editor");
|
||||
}
|
||||
|
||||
if (launchOptions.additionalArgs) {
|
||||
launchArgs.push(...launchOptions.additionalArgs);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import { SteamLauncherService } from "./steam-launcher.service";
|
||||
import { OculusLauncherService } from "./oculus-launcher.service";
|
||||
import { BSVersion } from "shared/bs-version.interface";
|
||||
import { BsStore } from "../../../shared/models/bs-store.enum";
|
||||
import { LaunchMod, LaunchMods } from "shared/models/bs-launch/launch-option.interface";
|
||||
|
||||
export class BSLauncherService {
|
||||
private static instance: BSLauncherService;
|
||||
@@ -94,6 +95,14 @@ export class BSLauncherService {
|
||||
}
|
||||
|
||||
private shortcutParamsToLaunchOption(params: ShortcutParams): LaunchOption{
|
||||
|
||||
const launchMods: LaunchMod[] = [];
|
||||
|
||||
if(params.oculusMode === "true"){ launchMods.push(LaunchMods.OCULUS); }
|
||||
if(params.desktopMode === "true"){ launchMods.push(LaunchMods.FPFC); }
|
||||
if(params.debug === "true"){ launchMods.push(LaunchMods.DEBUG); }
|
||||
if(params.skipSteam === "true"){ launchMods.push(LaunchMods.SKIP_STEAM); }
|
||||
|
||||
const res: LaunchOption = {
|
||||
version: {
|
||||
BSVersion: params.version,
|
||||
@@ -102,10 +111,8 @@ export class BSLauncherService {
|
||||
oculus: params.versionOculus === "true",
|
||||
ino: +params.versionIno
|
||||
},
|
||||
oculus: params.oculusMode === "true",
|
||||
desktop: params.desktopMode === "true",
|
||||
debug: params.debug === "true",
|
||||
additionalArgs: params.additionalArgs
|
||||
additionalArgs: params.additionalArgs,
|
||||
launchMods,
|
||||
};
|
||||
|
||||
return res;
|
||||
@@ -119,10 +126,11 @@ export class BSLauncherService {
|
||||
if(launchOptions.version.oculus){ res.versionOculus = `${launchOptions.version.oculus}`; }
|
||||
if(launchOptions.version.ino){ res.versionIno = `${launchOptions.version.ino}`; }
|
||||
|
||||
if(launchOptions.oculus){ res.oculusMode = "true"; }
|
||||
if(launchOptions.desktop){ res.desktopMode = "true"; }
|
||||
if(launchOptions.debug){ res.debug = "true"; }
|
||||
if(launchOptions.launchMods?.includes(LaunchMods.OCULUS)){ res.oculusMode = "true"; }
|
||||
if(launchOptions.launchMods?.includes(LaunchMods.FPFC)){ res.desktopMode = "true"; }
|
||||
if(launchOptions.launchMods?.includes(LaunchMods.DEBUG)){ res.debug = "true"; }
|
||||
if(launchOptions.additionalArgs){ res.additionalArgs = launchOptions.additionalArgs; }
|
||||
if(launchOptions.launchMods?.includes(LaunchMods.SKIP_STEAM)){ res.skipSteam = "true"; }
|
||||
|
||||
return res;
|
||||
}
|
||||
@@ -239,6 +247,7 @@ type ShortcutParams = {
|
||||
desktopMode?: string;
|
||||
debug?: string;
|
||||
additionalArgs?: string[];
|
||||
skipSteam?: string;
|
||||
version: string;
|
||||
versionName?: string;
|
||||
versionIno?: string;
|
||||
|
||||
@@ -10,6 +10,7 @@ import { AbstractLauncherService } from "./abstract-launcher.service";
|
||||
import { CustomError } from "../../../shared/models/exceptions/custom-error.class";
|
||||
import { UtilsService } from "../utils.service";
|
||||
import { exec } from "child_process";
|
||||
import { LaunchMods } from "shared/models/bs-launch/launch-option.interface";
|
||||
|
||||
export class SteamLauncherService extends AbstractLauncherService implements StoreLauncherInterface{
|
||||
|
||||
@@ -73,8 +74,11 @@ export class SteamLauncherService extends AbstractLauncherService implements Sto
|
||||
throw CustomError.fromError(new Error(`Path not exist : ${bsExePath}`), BSLaunchError.BS_NOT_FOUND);
|
||||
}
|
||||
|
||||
const skipSteam: boolean = launchOptions.launchMods?.includes(LaunchMods.SKIP_STEAM) ?? false;
|
||||
|
||||
// Open Steam if not running
|
||||
if(!(await this.steam.isSteamRunning())){
|
||||
if(!skipSteam && !(await this.steam.isSteamRunning())){
|
||||
|
||||
obs.next({type: BSLaunchEvent.STEAM_LAUNCHING});
|
||||
|
||||
await this.steam.openSteam().then(() => {
|
||||
@@ -84,9 +88,12 @@ export class SteamLauncherService extends AbstractLauncherService implements Sto
|
||||
obs.next({type: BSLaunchWarning.UNABLE_TO_LAUNCH_STEAM});
|
||||
});
|
||||
}
|
||||
else {
|
||||
obs.next({ type: BSLaunchEvent.SKIPPING_STEAM_LAUNCH});
|
||||
}
|
||||
|
||||
// Backup SteamVR when desktop mode is enabled
|
||||
if(launchOptions.desktop){
|
||||
if(launchOptions.launchMods?.includes(LaunchMods.FPFC)){
|
||||
await this.backupSteamVR().catch(() => {
|
||||
return this.restoreSteamVR();
|
||||
});
|
||||
|
||||
@@ -42,7 +42,7 @@ export class BSVersionLibService {
|
||||
|
||||
private async shouldLoadFromConfig(): Promise<boolean> {
|
||||
// Some special cases of readonly memory installations
|
||||
return IS_FLATPAK || this.linuxService.isNixOS();
|
||||
return process.platform === "linux" && (IS_FLATPAK || this.linuxService.isNixOS());
|
||||
}
|
||||
|
||||
private async getLocalVersions(): Promise<BSVersion[]> {
|
||||
|
||||
@@ -21,19 +21,24 @@ export class FolderLinkerService {
|
||||
private readonly installLocationService = InstallationLocationService.getInstance();
|
||||
private readonly staticConfig: StaticConfigurationService;
|
||||
|
||||
private linkingType: "junction" | "symlink" = "junction";
|
||||
// Only Windows support "junction", this is disregarded in other os'es
|
||||
private linkingType: "junction" | "symlink" =
|
||||
process.platform === "win32" ? "junction" : "symlink";
|
||||
|
||||
private constructor() {
|
||||
this.installLocationService = InstallationLocationService.getInstance();
|
||||
this.staticConfig = StaticConfigurationService.getInstance();
|
||||
|
||||
this.linkingType = this.staticConfig.get("use-symlinks") === true ? "symlink" : "junction";
|
||||
log.info(`Linking type is set to ${this.linkingType}`);
|
||||
if (process.platform === "win32") {
|
||||
// Only Windows support "junction", this is disregarded in other os'es
|
||||
this.linkingType = this.staticConfig.get("use-symlinks") === true ? "symlink" : "junction";
|
||||
log.info(`Linking type is set to ${this.linkingType}`);
|
||||
|
||||
this.staticConfig.$watch("use-symlinks").subscribe((useSymlink) => {
|
||||
this.linkingType = useSymlink === true ? "symlink" : "junction";
|
||||
log.info(`Linking type set to ${this.linkingType}`);
|
||||
});
|
||||
this.staticConfig.$watch("use-symlinks").subscribe((useSymlink) => {
|
||||
this.linkingType = useSymlink === true ? "symlink" : "junction";
|
||||
log.info(`Linking type set to ${this.linkingType}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public sharedFolder(): string {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { BsmButton, BsmButtonType } from "renderer/components/shared/bsm-button.component";
|
||||
import { BsmImage } from "renderer/components/shared/bsm-image.component";
|
||||
import { cn } from "renderer/helpers/css-class.helpers";
|
||||
import { useTranslation } from "renderer/hooks/use-translation.hook";
|
||||
import { useTranslationV2 } from "renderer/hooks/use-translation.hook";
|
||||
import { ModalComponent, ModalExitCode } from "renderer/services/modale.service";
|
||||
|
||||
type BasicModalOptions = {
|
||||
@@ -12,7 +12,11 @@ type BasicModalOptions = {
|
||||
id: string;
|
||||
text: string;
|
||||
type: BsmButtonType,
|
||||
isCancel?: boolean;
|
||||
/**
|
||||
* true - ModalExitCode.COMPLETED
|
||||
* undefined/false - ModalExitCode.CANCELED
|
||||
*/
|
||||
onClick?: () => boolean;
|
||||
}[];
|
||||
buttonsLayout?: "row" | "column";
|
||||
};
|
||||
@@ -21,20 +25,32 @@ export const BasicModal: ModalComponent<BasicModalOptions["buttons"][0]["id"], B
|
||||
data: { title, image, body, buttons, buttonsLayout = "column" }
|
||||
} }) => {
|
||||
|
||||
const t = useTranslation();
|
||||
const t = useTranslationV2();
|
||||
|
||||
const handleClick = (button: BasicModalOptions["buttons"][0]) => {
|
||||
resolver({ exitCode: button.isCancel ? ModalExitCode.CANCELED : ModalExitCode.COMPLETED, data: button.id });
|
||||
resolver({
|
||||
exitCode: button.onClick?.()
|
||||
? ModalExitCode.COMPLETED
|
||||
: ModalExitCode.CANCELED,
|
||||
data: button.id
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<form className="text-gray-900 dark:text-white">
|
||||
<h1 className="text-3xl uppercase tracking-wide w-full text-center">{t(title)}</h1>
|
||||
<h1 className="text-3xl uppercase tracking-wide w-full text-center">{t.text(title)}</h1>
|
||||
<BsmImage className="mx-auto h-24" image={image} />
|
||||
{ body && <p className="w-full">{t(body)}</p> }
|
||||
{ body && <p className="w-full">{t.text(body)}</p> }
|
||||
<div className={cn("grid gap-2 mt-4")} style={{ gridAutoFlow: buttonsLayout, ...(buttonsLayout === "row" ? { gridTemplateRows: `repeat(${buttons.length}, 1fr)` } : { gridTemplateColumns: `repeat(${buttons.length}, 1fr)` }) }}>
|
||||
{buttons.map(button => (
|
||||
<BsmButton key={button.id} typeColor={button.type} className="h-8 rounded-md text-center flex justify-center items-center" onClick={() => handleClick(button)} withBar={false} text={button.text} />
|
||||
<BsmButton
|
||||
key={button.id}
|
||||
typeColor={button.type}
|
||||
className="h-8 rounded-md text-center flex justify-center items-center"
|
||||
onClick={() => handleClick(button)}
|
||||
withBar={false}
|
||||
text={button.text}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -6,30 +6,45 @@ import { BsmButton } from "renderer/components/shared/bsm-button.component";
|
||||
import { useObservable } from "renderer/hooks/use-observable.hook";
|
||||
import { useService } from "renderer/hooks/use-service.hook";
|
||||
import { useThemeColor } from "renderer/hooks/use-theme-color.hook";
|
||||
import { useTranslation } from "renderer/hooks/use-translation.hook";
|
||||
import { useTranslationV2 } from "renderer/hooks/use-translation.hook";
|
||||
import { BSVersionManagerService } from "renderer/services/bs-version-manager.service";
|
||||
import { ConfigurationService } from "renderer/services/configuration.service";
|
||||
import { IpcService } from "renderer/services/ipc.service";
|
||||
import { ModalComponent } from "renderer/services/modale.service";
|
||||
import { ModalComponent, ModalService } from "renderer/services/modale.service";
|
||||
import { FolderLinkState, VersionFolderLinkerService, VersionLinkerActionType } from "renderer/services/version-folder-linker.service";
|
||||
import { lastValueFrom } from "rxjs";
|
||||
import { BSVersion } from "shared/bs-version.interface";
|
||||
import { NotificationService } from "renderer/services/notification.service";
|
||||
import { BasicModal } from "../basic-modal.component";
|
||||
import BeatConflict from "../../../../../assets/images/apngs/beat-conflict.png";
|
||||
|
||||
export const ShareFoldersModal: ModalComponent<void, BSVersion> = ({ options: {data} }) => {
|
||||
const SHARED_FOLDERS_KEY = "default-shared-folders";
|
||||
const SHARED_FOLDERS_KEY = "default-shared-folders";
|
||||
const SHARED_FOLDER_BLACKLIST = {
|
||||
error: [
|
||||
".DepotDownloader",
|
||||
"Beat Saber_Data",
|
||||
"IPA",
|
||||
"Libs",
|
||||
"Plugins",
|
||||
"MonoBleedingEdge",
|
||||
],
|
||||
warn: [
|
||||
"DLC",
|
||||
"Logs",
|
||||
],
|
||||
};
|
||||
|
||||
export const ShareFoldersModal: ModalComponent<void, BSVersion> = ({ options: { data: version } }) => {
|
||||
const config = useService(ConfigurationService);
|
||||
const ipc = useService(IpcService);
|
||||
const linker = useService(VersionFolderLinkerService);
|
||||
const versionManager = useService(BSVersionManagerService);
|
||||
|
||||
const t = useTranslation();
|
||||
const t = useTranslationV2();
|
||||
|
||||
const [folders, setFolders] = useState<string[]>(Array.from(new Set([...config.get<string[]>(SHARED_FOLDERS_KEY)]).values()));
|
||||
|
||||
useEffect(() => {
|
||||
linker
|
||||
.getLinkedFolders(data, { relative: true })
|
||||
.getLinkedFolders(version, { relative: true })
|
||||
.toPromise()
|
||||
.then(linkedFolders => {
|
||||
setFolders(prev => Array.from(new Set([...prev, ...linkedFolders]).values()));
|
||||
@@ -45,42 +60,24 @@ export const ShareFoldersModal: ModalComponent<void, BSVersion> = ({ options: {d
|
||||
config.set(SHARED_FOLDERS_KEY, folders);
|
||||
}, [folders]);
|
||||
|
||||
const addFolder = async () => {
|
||||
const versionPath = await lastValueFrom(versionManager.getVersionPath(data));
|
||||
const folder = await lastValueFrom(ipc.sendV2("choose-folder", {
|
||||
defaultPath: versionPath
|
||||
}));
|
||||
|
||||
if (!folder || folder.canceled || !folder.filePaths?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const relativeFolder = await lastValueFrom(ipc.sendV2("full-version-path-to-relative", { version: data, fullPath: folder.filePaths[0] }));
|
||||
|
||||
if (folders.includes(relativeFolder)) {
|
||||
return;
|
||||
}
|
||||
|
||||
setFolders(pre => [...pre, relativeFolder]);
|
||||
};
|
||||
|
||||
const removeFolder = (index: number) => {
|
||||
setFolders(prev => prev.filter((_, i) => i !== index));
|
||||
};
|
||||
|
||||
const linkAll = () => {
|
||||
folders.forEach(relativeFolder => linker.linkVersionFolder({ version: data, relativeFolder, type: VersionLinkerActionType.Link }));
|
||||
folders.forEach(relativeFolder => linker.linkVersionFolder({ version, 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>
|
||||
<p className="my-3">{t("modals.shared-folders.description")}</p>
|
||||
<h1 className="text-3xl uppercase tracking-wide w-full text-center text-gray-800 dark:text-gray-200">{t.text("modals.shared-folders.title")}</h1>
|
||||
<p className="my-3">{t.text("modals.shared-folders.description")}</p>
|
||||
<ul className="flex flex-col gap-1 mb-2 h-[300px] max-h-[300px] overflow-scroll scrollbar-default px-1">
|
||||
{folders.map((folder, index) => (
|
||||
<FolderItem
|
||||
key={folder}
|
||||
version={data}
|
||||
version={version}
|
||||
relativeFolder={folder}
|
||||
onDelete={() => {
|
||||
removeFolder(index);
|
||||
@@ -89,13 +86,93 @@ export const ShareFoldersModal: ModalComponent<void, BSVersion> = ({ options: {d
|
||||
))}
|
||||
</ul>
|
||||
<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" />
|
||||
<AddFolderButton
|
||||
version={version}
|
||||
folders={folders}
|
||||
setFolders={setFolders}
|
||||
/>
|
||||
<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>
|
||||
);
|
||||
};
|
||||
|
||||
function AddFolderButton({
|
||||
version,
|
||||
folders,
|
||||
setFolders,
|
||||
}: Readonly<{
|
||||
version: BSVersion;
|
||||
folders: string[];
|
||||
setFolders: (value: string[]) => void;
|
||||
}>) {
|
||||
const config = useService(ConfigurationService);
|
||||
const ipc = useService(IpcService);
|
||||
const versionManager = useService(BSVersionManagerService);
|
||||
const notification = useService(NotificationService);
|
||||
const modal = useService(ModalService);
|
||||
|
||||
const t = useTranslationV2();
|
||||
|
||||
const addFolder = async () => {
|
||||
const versionPath = await lastValueFrom(versionManager.getVersionPath(version));
|
||||
const folder = await lastValueFrom(ipc.sendV2("choose-folder", {
|
||||
defaultPath: versionPath
|
||||
}));
|
||||
|
||||
if (!folder || folder.canceled || !folder.filePaths?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const relativeFolder = await lastValueFrom(ipc.sendV2("full-version-path-to-relative", { version, fullPath: folder.filePaths[0] }));
|
||||
if (folders.includes(relativeFolder)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (SHARED_FOLDER_BLACKLIST.error.includes(relativeFolder)) {
|
||||
notification.notifyError({
|
||||
title: "notifications.shared-folder.adding-error.title",
|
||||
desc: t.text("notifications.shared-folder.adding-error.msg", {
|
||||
folder: relativeFolder
|
||||
}),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (SHARED_FOLDER_BLACKLIST.warn.includes(relativeFolder)) {
|
||||
await modal.openModal(BasicModal, { data: {
|
||||
title: "modals.adding-shared-folder.title",
|
||||
body: t.text("modals.adding-shared-folder.description", {
|
||||
folder: relativeFolder
|
||||
}),
|
||||
image: BeatConflict,
|
||||
buttons: [
|
||||
{ id: "cancel", text: "misc.cancel", type: "cancel" },
|
||||
{
|
||||
id: "confirm", text: "misc.confirm", type: "primary",
|
||||
onClick() {
|
||||
config.set(SHARED_FOLDERS_KEY, [...folders, relativeFolder]);
|
||||
return true;
|
||||
},
|
||||
}
|
||||
]
|
||||
}});
|
||||
return;
|
||||
}
|
||||
|
||||
setFolders([...folders, relativeFolder]);
|
||||
};
|
||||
|
||||
return <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"
|
||||
/>;
|
||||
}
|
||||
|
||||
// -------- FOLDER ITEM --------
|
||||
|
||||
type FolderProps = {
|
||||
@@ -107,7 +184,7 @@ type FolderProps = {
|
||||
const FolderItem = ({ version, relativeFolder, onDelete }: FolderProps) => {
|
||||
const linker = useService(VersionFolderLinkerService);
|
||||
|
||||
const t = useTranslation();
|
||||
const t = useTranslationV2();
|
||||
|
||||
const color = useThemeColor("first-color");
|
||||
const state = useObservable(() => linker.$folderLinkedState(version, relativeFolder), FolderLinkState.Unlinked, [version, relativeFolder]);
|
||||
@@ -139,7 +216,7 @@ const FolderItem = ({ version, relativeFolder, onDelete }: FolderProps) => {
|
||||
{name}
|
||||
</span>
|
||||
<div className="flex flex-row gap-1.5">
|
||||
<Tippy placement="left" content={t(`modals.shared-folders.buttons.${state === FolderLinkState.Linked ? "unlink-folder" : "link-folder"}`)} arrow={false}>
|
||||
<Tippy placement="top" theme="default" content={t.text(`modals.shared-folders.buttons.${state === FolderLinkState.Linked ? "unlink-folder" : "link-folder"}`)}>
|
||||
<LinkButton
|
||||
className="p-0.5 h-7 shrink-0 aspect-square blur-0 cursor-pointer hover:brightness-75"
|
||||
state={state}
|
||||
@@ -164,15 +241,17 @@ const FolderItem = ({ version, relativeFolder, onDelete }: FolderProps) => {
|
||||
);
|
||||
}
|
||||
return (
|
||||
<BsmButton
|
||||
className="aspect-square h-7 rounded-md p-1"
|
||||
icon="trash"
|
||||
withBar={false}
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
onDelete?.();
|
||||
}}
|
||||
/>
|
||||
<Tippy content={t.text("modals.shared-folders.buttons.remove-from-the-list")} theme="default" hideOnClick={false} placement="top">
|
||||
<BsmButton
|
||||
className="aspect-square h-7 rounded-md p-1"
|
||||
icon="trash"
|
||||
withBar={false}
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
onDelete?.();
|
||||
}}
|
||||
/>
|
||||
</Tippy>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@ import { BSVersion } from "shared/bs-version.interface";
|
||||
import { Link, useLocation } from "react-router-dom";
|
||||
import { useState } from "react";
|
||||
import { distinctUntilChanged, lastValueFrom, map, of, Subscription, switchMap, take } from "rxjs";
|
||||
import { BSLauncherService, LaunchMods } from "renderer/services/bs-launcher.service";
|
||||
import { BSLauncherService } from "renderer/services/bs-launcher.service";
|
||||
import { ConfigurationService } from "renderer/services/configuration.service";
|
||||
import { BSUninstallerService } from "renderer/services/bs-uninstaller.service";
|
||||
import { BSVersionManagerService } from "renderer/services/bs-version-manager.service";
|
||||
@@ -35,7 +35,7 @@ export function BsVersionItem(props: { version: BSVersion }) {
|
||||
|
||||
useOnUpdate(() => {
|
||||
const subs: Subscription[] = []
|
||||
|
||||
|
||||
subs.push(bsDownloader.downloadingVersion$.pipe(map(download => equal(download, props.version)), distinctUntilChanged()).subscribe(isDownloading => {
|
||||
setIsDownloading(() => isDownloading);
|
||||
}));
|
||||
@@ -60,9 +60,7 @@ export function BsVersionItem(props: { version: BSVersion }) {
|
||||
if(equal(downloadingVersion, props.version)){ return; }
|
||||
const launch$ = launcherService.launch({
|
||||
version: state,
|
||||
oculus: !!configService.get<boolean>(LaunchMods.OCULUS_MOD),
|
||||
desktop: !!configService.get<boolean>(LaunchMods.DESKTOP_MOD),
|
||||
debug: !!configService.get<boolean>(LaunchMods.DEBUG_MOD),
|
||||
launchMods: configService.get("launch-mods") ?? [],
|
||||
});
|
||||
return lastValueFrom(launch$).catch(() => {});
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ReactNode } from "react";
|
||||
import { useTranslation } from "renderer/hooks/use-translation.hook";
|
||||
import { useTranslationV2 } from "renderer/hooks/use-translation.hook";
|
||||
|
||||
type Props = {
|
||||
id?: string;
|
||||
@@ -8,11 +8,11 @@ type Props = {
|
||||
minorTitle?: string;
|
||||
description?: string;
|
||||
children?: ReactNode;
|
||||
os?: string;
|
||||
os?: "win32" | "linux";
|
||||
};
|
||||
|
||||
export function SettingContainer({ id, className, title, minorTitle, description, children, os }: Props) {
|
||||
const t = useTranslation();
|
||||
const t = useTranslationV2();
|
||||
|
||||
if (os && os !== window.electron.platform) {
|
||||
return undefined;
|
||||
@@ -20,9 +20,9 @@ export function SettingContainer({ id, className, title, minorTitle, description
|
||||
|
||||
return (
|
||||
<div id={id} className={className || "relative mb-5"}>
|
||||
{title && <h1 className="mb-1 text-2xl font-bold tracking-wide">{t(title)}</h1>}
|
||||
{minorTitle && <h2 className="mb-1 font-bold tracking-wide text-gray-600 dark:text-gray-300">{t(minorTitle)}</h2>}
|
||||
{description && <p className="mb-3 text-sm text-gray-600 dark:text-gray-400">{t(description)}</p>}
|
||||
{title && <h1 className="mb-1 text-2xl font-bold tracking-wide">{t.text(title)}</h1>}
|
||||
{minorTitle && <h2 className="mb-1 font-bold tracking-wide text-gray-600 dark:text-gray-300">{t.text(minorTitle)}</h2>}
|
||||
{description && <p className="mb-3 text-sm text-gray-600 dark:text-gray-400">{t.text(description)}</p>}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ToogleSwitch } from "../shared/toogle-switch.component";
|
||||
|
||||
type Item = {
|
||||
export type Item = {
|
||||
text: string;
|
||||
desc?: string;
|
||||
checked?: boolean;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { motion } from "framer-motion";
|
||||
import { useThemeColor } from "renderer/hooks/use-theme-color.hook";
|
||||
import { BsmIcon } from "../svgs/bsm-icon.component";
|
||||
import { useTranslation } from "renderer/hooks/use-translation.hook";
|
||||
import { useTranslationV2 } from "renderer/hooks/use-translation.hook";
|
||||
import { FolderLinkState } from "renderer/services/version-folder-linker.service";
|
||||
import React from "react";
|
||||
import React, { forwardRef } from "react";
|
||||
|
||||
export type LinkBtnProps = {
|
||||
className?: string;
|
||||
@@ -12,8 +12,8 @@ export type LinkBtnProps = {
|
||||
onClick?: () => unknown;
|
||||
};
|
||||
|
||||
export function LinkButton({className, title, state, onClick}: LinkBtnProps) {
|
||||
const t = useTranslation();
|
||||
export const LinkButton = forwardRef<HTMLDivElement, LinkBtnProps>(({className, title, state, onClick}, forwardedRef) => {
|
||||
const { text: t } = useTranslationV2();
|
||||
|
||||
const color = useThemeColor("first-color");
|
||||
const disabled = state === FolderLinkState.Processing || state === FolderLinkState.Pending;
|
||||
@@ -41,6 +41,7 @@ export function LinkButton({className, title, state, onClick}: LinkBtnProps) {
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
ref={forwardedRef}
|
||||
variants={{ hover: { rotate: 22.5 }, tap: { rotate: 45 } }}
|
||||
whileHover="hover"
|
||||
whileTap="tap"
|
||||
@@ -55,4 +56,4 @@ export function LinkButton({className, title, state, onClick}: LinkBtnProps) {
|
||||
<BsmIcon className="p-1 absolute top-0 left-0 h-full w-full !bg-transparent -rotate-45 brightness-150" icon={state === FolderLinkState.Linked ? "link" : "unlink"} style={{ color: btnColor() }} />
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -16,7 +16,7 @@ import { TaskIcon } from "./icons/task-icon.component";
|
||||
import { CopyIcon } from "./icons/copy-icon.component";
|
||||
import { SteamIcon } from "./icons/steam-icon.component";
|
||||
import { CSSProperties, memo } from "react";
|
||||
import EditIcon from "./icons/edit-icon.component";
|
||||
import { EditIcon } from "./icons/edit-icon.component";
|
||||
import { ExportIcon } from "./icons/export-icon.component";
|
||||
import PatreonIcon from "./icons/patreon-icon.component";
|
||||
import { SearchIcon } from "./icons/search-icon.component";
|
||||
@@ -69,7 +69,8 @@ import { CancelIcon } from "./icons/cancel-icon.component";
|
||||
import { WarningIcon } from "./icons/warning-icon.component";
|
||||
|
||||
|
||||
export type BsmIconType = SongDetailDiffCharactertistic | ("settings" | "trash" | "favorite" | "folder" | "bsNote" | "check" | "three-dots" | "twitch" | "eye" | "play" | "checkCircleIcon" | "discord" | "info" | "eye-cross" | "terminal" | "desktop" | "oculus" | "add" | "cross" | "task" | "github" | "close" | "thumbUpFill" | "timerFill" | "pause" | "twitter" | "sync" | "chevron-top" | "copy" | "steam" | "edit" | "export" | "patreon" | "search" | "bsMapDifficulty" | "link" | "unlink" | "download" | "filter" | "mee6" | "volume-up" | "volume-off" | "volume-down" | "shortcut" | "backup-restore" | "web-site" | "clean" | "browse" | "add-file" | "cancel" | "warning" | "fr-FR-flag" | "es-ES-flag" | "en-US-flag" | "en-EN-flag" | "de-DE-flag" | "ru-RU-flag" | "zh-CN-flag" | "zh-TW-flag" | "ja-JP-flag" | "ko-KR-flag");
|
||||
|
||||
export type BsmIconType = SongDetailDiffCharactertistic | ("settings" | "trash" | "favorite" | "folder" | "bsNote" | "check" | "three-dots" | "twitch" | "eye" | "play" | "checkCircleIcon" | "discord" | "info" | "eye-cross" | "terminal" | "desktop" | "oculus" | "add" | "cross" | "task" | "github" | "close" | "thumbUpFill" | "timerFill" | "pause" | "twitter" | "sync" | "chevron-top" | "copy" | "steam" | "edit" | "export" | "patreon" | "search" | "bsMapDifficulty" | "link" | "unlink" | "download" | "filter" | "mee6" | "volume-up" | "volume-off" | "volume-down" | "shortcut" | "backup-restore" | "web-site" | "clean" | "browse" | "add-file" | "cancel" | "warning" | "fr-FR-flag" | "es-ES-flag" | "en-US-flag" | "en-EN-flag" | "de-DE-flag" | "ru-RU-flag" | "zh-CN-flag" | "zh-TW-flag" | "ja-JP-flag" | "ko-KR-flag" | "null" );
|
||||
|
||||
export const BsmIcon = memo(({ className, icon, style }: { className?: string; icon: BsmIconType; style?: CSSProperties }) => {
|
||||
// TODO : Very ugly very messy, need to find a better way to do this
|
||||
@@ -277,13 +278,14 @@ export const BsmIcon = memo(({ className, icon, style }: { className?: string; i
|
||||
if(icon === "add-file") {
|
||||
return <AddFileIcon className={className} style={style} />;
|
||||
}
|
||||
|
||||
if(icon === "cancel"){
|
||||
if(icon === "null") {
|
||||
return null;
|
||||
}
|
||||
if(icon === "cancel") {
|
||||
return <CancelIcon className={className} style={style} />;
|
||||
}
|
||||
|
||||
if(icon === "warning"){
|
||||
return <WarningIcon className={className} style={style} />;
|
||||
if(icon === "warning") {
|
||||
return <WarningIcon className={className} style={style} />
|
||||
}
|
||||
|
||||
return <TrashIcon className={className} style={style} />;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { CSSProperties } from "react";
|
||||
import { createSvgIcon } from "../svg-icon.type";
|
||||
|
||||
export function DesktopIcon(props: { className?: string; style?: CSSProperties }) {
|
||||
export const DesktopIcon = createSvgIcon((props, ref) => {
|
||||
return (
|
||||
<svg className={props.className} style={props.style} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40" height="40" width="40">
|
||||
<svg ref={ref} {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40" height="40" width="40">
|
||||
<path fill="currentColor" d="M13.417 38V33.75H16.875V29.875H6.708Q4.917 29.875 3.687 28.646Q2.458 27.417 2.458 25.667V7.25Q2.458 5.5 3.687 4.271Q4.917 3.042 6.708 3.042H33.375Q35.125 3.042 36.375 4.271Q37.625 5.5 37.625 7.25V25.667Q37.625 27.417 36.375 28.646Q35.125 29.875 33.375 29.875H23.167V33.75H26.667V38ZM6.708 25.667H33.375Q33.375 25.667 33.375 25.667Q33.375 25.667 33.375 25.667V7.25Q33.375 7.25 33.375 7.25Q33.375 7.25 33.375 7.25H6.708Q6.708 7.25 6.708 7.25Q6.708 7.25 6.708 7.25V25.667Q6.708 25.667 6.708 25.667Q6.708 25.667 6.708 25.667ZM6.708 25.667Q6.708 25.667 6.708 25.667Q6.708 25.667 6.708 25.667V7.25Q6.708 7.25 6.708 7.25Q6.708 7.25 6.708 7.25Q6.708 7.25 6.708 7.25Q6.708 7.25 6.708 7.25V25.667Q6.708 25.667 6.708 25.667Q6.708 25.667 6.708 25.667Z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { CSSProperties } from "react";
|
||||
import { createSvgIcon } from "../svg-icon.type";
|
||||
|
||||
export default function EditIcon(props: { className?: string; style?: CSSProperties }) {
|
||||
export const EditIcon = createSvgIcon((props, ref) => {
|
||||
return (
|
||||
<svg className={props.className} style={props.style} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40" height="40" width="40">
|
||||
<svg ref={ref} {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40" height="40" width="40">
|
||||
<path fill="currentColor" d="M7.833 32.25h1.875L26.75 15.208l-1.875-1.875L7.833 30.375Zm24.959-19.042-5.959-5.916 1.917-1.917q.833-.833 2.042-.833 1.208 0 2.083.875l1.833 1.833q.875.833.854 2.021-.02 1.187-.854 2.021ZM6.542 35.125q-.667 0-1.125-.458-.459-.459-.459-1.125v-3.709q0-.333.104-.604.105-.271.355-.521L24.875 9.25l5.958 5.958-19.458 19.459q-.25.25-.542.354-.291.104-.583.104Zm19.25-20.833-.917-.959 1.875 1.875Z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { HTMLProps } from "react";
|
||||
import { createSvgIcon } from "../svg-icon.type";
|
||||
|
||||
export function OculusIcon(props: HTMLProps<SVGSVGElement>) {
|
||||
export const OculusIcon = createSvgIcon((props, ref) => {
|
||||
return (
|
||||
<svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<svg ref={ref} {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M24.182 18.599c-0.427 0.297-0.901 0.474-1.411 0.552-0.51 0.083-1.016 0.068-1.521 0.068h-10.5c-0.51 0-1.016 0.021-1.526-0.068-0.51-0.083-0.979-0.255-1.411-0.552-0.854-0.604-1.365-1.563-1.365-2.604 0-1.057 0.516-2.016 1.37-2.599 0.417-0.297 0.896-0.479 1.396-0.557 0.5-0.083 1-0.083 1.526-0.083h10.5c0.5 0 1.016-0.016 1.516 0.063s0.984 0.26 1.401 0.542c0.865 0.578 1.365 1.557 1.365 2.599 0 1.036-0.526 2-1.38 2.599zM28.411 8.526c-1.125-0.906-2.417-1.531-3.818-1.865-0.802-0.193-1.604-0.281-2.432-0.307-0.599-0.021-1.198-0.010-1.818-0.010h-8.661c-0.609 0-1.224-0.010-1.833 0.010-0.823 0.026-1.63 0.109-2.432 0.307-1.401 0.339-2.698 0.964-3.818 1.865-2.281 1.823-3.599 4.568-3.599 7.474 0 2.911 1.318 5.656 3.583 7.474 1.13 0.906 2.422 1.531 3.823 1.87 0.802 0.193 1.609 0.281 2.432 0.302 0.599 0.021 1.198 0.016 1.818 0.016h8.661c0.599 0 1.219 0.005 1.818-0.016 0.823-0.021 1.62-0.109 2.417-0.302 1.401-0.344 2.682-0.969 3.823-1.87 2.307-1.823 3.625-4.568 3.625-7.474s-1.318-5.656-3.589-7.474z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { createSvgIcon } from "../svg-icon.type";
|
||||
|
||||
export const PinIcon = createSvgIcon((props, ref) => {
|
||||
return (
|
||||
<svg ref={ref} {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="currentColor">
|
||||
<path d="M648.13-763.11v274.74l66.57 66.57q6.71 6.71 10.07 15.05 3.36 8.34 3.36 17.53v26.59q0 19.15-13.17 32.33-13.18 13.17-32.33 13.17H525.5v232.09q0 19.15-13.17 32.32Q499.15-39.54 480-39.54t-32.33-13.18Q434.5-65.89 434.5-85.04v-232.09H277.37q-19.15 0-32.33-13.17-13.17-13.18-13.17-32.33v-26.59q0-9.19 3.36-17.53 3.36-8.34 10.07-15.05l66.57-66.57v-274.74q-17-1.67-28.5-14.25-11.5-12.57-11.5-30.29 0-19.15 13.17-32.33 13.18-13.17 32.33-13.17h325.26q19.15 0 32.33 13.17 13.17 13.18 13.17 32.33 0 17.72-11.5 30.29-11.5 12.58-28.5 14.25Z"/>
|
||||
</svg>
|
||||
)
|
||||
});
|
||||
@@ -1,9 +1,9 @@
|
||||
import { CSSProperties } from "react";
|
||||
import { createSvgIcon } from "../svg-icon.type";
|
||||
|
||||
export function TerminalIcon(props: { className?: string; style?: CSSProperties }) {
|
||||
export const TerminalIcon = createSvgIcon((props, ref) => {
|
||||
return (
|
||||
<svg className={props.className} style={props.style} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40" height="40" width="40">
|
||||
<svg ref={ref} {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40" height="40" width="40">
|
||||
<path fill="currentColor" d="M6.333 33.375Q5.125 33.375 4.229 32.479Q3.333 31.583 3.333 30.333V9.667Q3.333 8.417 4.229 7.521Q5.125 6.625 6.333 6.625H33.667Q34.875 6.625 35.771 7.521Q36.667 8.417 36.667 9.667V30.333Q36.667 31.583 35.771 32.479Q34.875 33.375 33.667 33.375ZM6.333 30.333H33.667Q33.667 30.333 33.667 30.333Q33.667 30.333 33.667 30.333V13.042H6.333V30.333Q6.333 30.333 6.333 30.333Q6.333 30.333 6.333 30.333ZM20.375 28V25.375H29.667V28ZM12.5 27.875 10.667 26.042 14.958 21.708 10.625 17.375 12.5 15.5 18.667 21.708Z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { createSvgIcon } from "../svg-icon.type"
|
||||
|
||||
export const UnpinIcon = createSvgIcon((props, ref) => {
|
||||
return (
|
||||
<svg ref={ref} {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="currentColor">
|
||||
<path d="M570.57-514.15 294.3-790.65q-5.47-5.72-8.21-12.2-2.74-6.48-2.74-12.95 0-14.2 10.07-25.78 10.08-11.57 26.51-11.57h322.7q19.15 0 32.33 13.77 13.17 13.77 13.17 37.23 0 16.24-13.78 23.93-13.78 7.7-26.22 16.07v215.41q0 30.59-27.97 42.4-27.96 11.82-49.59-9.81ZM434.5-78.09v-239.04H290.98q-27.39 0-44.07-19.05-16.67-19.06-16.67-43.21 0-11.72 4.62-23.32 4.62-11.59 14.86-22.07l62.15-63.59v-46.72L74.91-774.28Q63.2-786 62.93-803.1q-.26-17.1 12.22-29.57 11.72-11.72 29.2-11.72 17.48 0 29.19 11.72l690.72 690.71q12.48 12.48 12.1 29.58-.38 17.1-12.86 29.58-12.48 11.71-29.32 12.09-16.83.38-29.31-12.09L530.3-317.13l-4.8.48v238.56q0 19.16-13.17 32.33Q499.15-32.59 480-32.59t-32.33-13.17Q434.5-58.93 434.5-78.09Z"/>
|
||||
</svg>
|
||||
)
|
||||
})
|
||||
+9
-8
@@ -1,33 +1,34 @@
|
||||
import Tippy from "@tippyjs/react";
|
||||
import { GlowEffect } from "renderer/components/shared/glow-effect.component";
|
||||
import { BsmIcon, BsmIconType } from "renderer/components/svgs/bsm-icon.component";
|
||||
import { BsmIcon } from "renderer/components/svgs/bsm-icon.component";
|
||||
import { SvgIcon } from "renderer/components/svgs/svg-icon.type";
|
||||
import { useTranslation } from "renderer/hooks/use-translation.hook";
|
||||
|
||||
type Props = {
|
||||
onClick: (active: boolean) => void;
|
||||
active: boolean;
|
||||
text: string;
|
||||
icon: BsmIconType;
|
||||
icon?: SvgIcon;
|
||||
infoText?: string;
|
||||
};
|
||||
|
||||
export function LaunchModToogle({ onClick, active, text, icon, infoText }: Props) {
|
||||
export function LaunchModToogle({ onClick, active, text, icon: Icon, infoText }: Props) {
|
||||
const t = useTranslation();
|
||||
|
||||
return (
|
||||
<div className={`relative rounded-full cursor-pointer group active:scale-95 transition-transform ${!active && "shadow-md shadow-black"}`} onClick={() => onClick(!active)}>
|
||||
<button className={`shrink-0 relative rounded-full cursor-pointer group active:scale-95 transition-transform ${!active && "shadow-md shadow-black"}`} onClick={() => onClick(!active)}>
|
||||
<GlowEffect visible={active} className="absolute !rounded-full blur-[2px]" />
|
||||
<div className="w-full h-full px-6 flex gap-1.5 justify-center items-center bg-light-main-color-2 dark:bg-main-color-2 p-3 rounded-full text-gray-800 dark:text-white group-hover:bg-light-main-color-1 dark:group-hover:bg-main-color-1">
|
||||
<BsmIcon icon={icon} className="h-7 text-gray-800 dark:text-white" />
|
||||
{Icon && <Icon className="h-7 shrink-0 text-gray-800 dark:text-white" />}
|
||||
<span className="w-fit min-w-fit text-lg font-bold uppercase tracking-wide italic ">{t(text)}</span>
|
||||
{infoText && (
|
||||
<Tippy content={t(infoText)} placement="bottom" delay={[400, 0]}>
|
||||
<div className="h-[25px] w-[25px] p-1.5 rounded-full cursor-help bg-light-main-color-1 dark:bg-main-color-3 hover:brightness-110">
|
||||
<Tippy content={t(infoText)} placement="top" theme="default" delay={[400, 0]}>
|
||||
<div className="h-[25px] w-[25px] shrink-0 p-1.5 rounded-full cursor-help bg-light-main-color-1 dark:bg-main-color-3 hover:brightness-110">
|
||||
<BsmIcon className="w-full h-full" icon="info" />
|
||||
</div>
|
||||
</Tippy>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
import Tippy from "@tippyjs/react";
|
||||
import { BsmCheckbox } from "renderer/components/shared/bsm-checkbox.component";
|
||||
import { PinIcon } from "renderer/components/svgs/icons/pin-icon.component";
|
||||
import { UnpinIcon } from "renderer/components/svgs/icons/unpin-icon.component";
|
||||
import { SvgIcon } from "renderer/components/svgs/svg-icon.type";
|
||||
import { cn } from "renderer/helpers/css-class.helpers";
|
||||
import { useTranslationV2 } from "renderer/hooks/use-translation.hook";
|
||||
import { LaunchMod } from "shared/models/bs-launch/launch-option.interface";
|
||||
|
||||
type Props = {
|
||||
readonly className?: string;
|
||||
readonly open: boolean;
|
||||
readonly launchArgs?: string;
|
||||
readonly launchMods?: LaunchModItemProps[];
|
||||
readonly onLaunchArgsChange?: (args: string) => void;
|
||||
};
|
||||
|
||||
export function LaunchOptionsPanel({ className, open, launchArgs, launchMods, onLaunchArgsChange }: Props) {
|
||||
|
||||
const {text: t} = useTranslationV2();
|
||||
|
||||
return (
|
||||
<div className={cn("grid grid-rows-[0fr] transition-[grid-template-rows] bg-theme-2 rounded-md shadow-md shadow-black overflow-hidden", open && "!grid-rows-[1fr]", className)}>
|
||||
<div className="flex flex-col overflow-y-scroll scrollbar-default">
|
||||
<div className="p-3.5">
|
||||
<input className="h-10 w-full rounded-md bg-theme-1 text-center mb-2" type="text" placeholder={t("pages.version-viewer.launch-mods.advanced-launch.placeholder")} value={launchArgs} onChange={e => onLaunchArgsChange(e.target.value)}/>
|
||||
<div className={cn("flex gap-2 flex-wrap")}>
|
||||
{launchMods?.map((mod) => (
|
||||
<LaunchModItem key={mod.id} {...mod}/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export type LaunchModItemProps = {
|
||||
readonly id: LaunchMod;
|
||||
readonly icon?: SvgIcon;
|
||||
readonly label: string;
|
||||
readonly description: string;
|
||||
readonly active: boolean;
|
||||
readonly visible?: boolean;
|
||||
readonly pinned?: boolean;
|
||||
readonly onChange?: (val: boolean) => void;
|
||||
readonly onPinChange?: (val: boolean) => void;
|
||||
}
|
||||
|
||||
export function LaunchModItem({ id, icon: Icon, label, description, active, visible, pinned, onChange, onPinChange }: LaunchModItemProps) {
|
||||
|
||||
const { text: t } = useTranslationV2();
|
||||
|
||||
return (
|
||||
<Tippy theme="default" placement="top" content={description}>
|
||||
<button id={id} className={cn("grow rounded-md bg-theme-1 relative flex justify-center items-center h-10 py-1 px-3", visible === false && "hidden")} onClick={e => { e.preventDefault(); e.stopPropagation(); onChange?.(!active) }}>
|
||||
<BsmCheckbox className="h-4 aspect-square z-[1] relative mr-1.5" checked={active} onChange={onChange}/>
|
||||
{Icon && <Icon className="h-full w-fit py-0.5 mr-1.5"/>}
|
||||
<span className="font-bold">{label}</span>
|
||||
{onPinChange && (
|
||||
<Tippy theme="default" placement="right" content={pinned ? t("misc.unpin") : t("misc.pin")} hideOnClick>
|
||||
<button className="h-full py-2 px-1.5" onClick={e => { e.preventDefault(); e.stopPropagation(); onPinChange?.(!pinned) }}>
|
||||
{pinned ? <UnpinIcon className="size-full"/> : <PinIcon className="size-full"/>}
|
||||
</button>
|
||||
</Tippy>
|
||||
)}
|
||||
</button>
|
||||
</Tippy>
|
||||
);
|
||||
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ChangeEvent, useEffect, useMemo, useState } from "react";
|
||||
import { 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";
|
||||
import { BSLauncherService, LaunchMods } from "renderer/services/bs-launcher.service";
|
||||
import { BSLauncherService } from "renderer/services/bs-launcher.service";
|
||||
import { ConfigurationService } from "renderer/services/configuration.service";
|
||||
import { BSVersion } from "shared/bs-version.interface";
|
||||
import { LaunchModToogle } from "./launch-mod-toogle.component";
|
||||
@@ -19,6 +19,13 @@ import { BSVersionManagerService } from "renderer/services/bs-version-manager.se
|
||||
import { safeLt } from "shared/helpers/semver.helpers";
|
||||
import { WarningIcon } from "renderer/components/svgs/icons/warning-icon.component";
|
||||
import Tippy from "@tippyjs/react";
|
||||
import { LaunchModItemProps, LaunchOptionsPanel } from "./launch-options-panel.component";
|
||||
import { LaunchMod, LaunchMods } from "shared/models/bs-launch/launch-option.interface";
|
||||
import { OculusIcon } from "renderer/components/svgs/icons/oculus-icon.component";
|
||||
import { DesktopIcon } from "renderer/components/svgs/icons/desktop-icon.component";
|
||||
import { TerminalIcon } from "renderer/components/svgs/icons/terminal-icon.component";
|
||||
import { DefaultConfigKey } from "renderer/config/default-configuration.config";
|
||||
import { EditIcon } from "renderer/components/svgs/icons/edit-icon.component";
|
||||
|
||||
type Props = { version: BSVersion };
|
||||
|
||||
@@ -30,12 +37,12 @@ export function LaunchSlide({ version }: Props) {
|
||||
const bsDownloader = useService(BsDownloaderService);
|
||||
const versions = useService(BSVersionManagerService);
|
||||
|
||||
const [oculusMode, setOculusMode] = useState(!!configService.get<boolean>(LaunchMods.OCULUS_MOD));
|
||||
const [desktopMode, setDesktopMode] = useState(!!configService.get<boolean>(LaunchMods.DESKTOP_MOD));
|
||||
const [debugMode, setDebugMode] = useState(!!configService.get<boolean>(LaunchMods.DEBUG_MOD));
|
||||
const [advancedLaunch, setAdvancedLaunch] = useState(false);
|
||||
const [additionalArgsString, setAdditionalArgsString] = useState<string>(configService.get<string>("additionnal-args") || "");
|
||||
const versionDownloading = useObservable(() => bsDownloader.downloadingVersion$);
|
||||
const [activeLaunchMods, setActiveLaunchMods] = useState<LaunchMod[]>(configService.get("launch-mods") ?? []);
|
||||
const [pinnedLaunchMods, setPinnedLaunchMods] = useState<LaunchMod[]>(configService.get("pinned-launch-mods" as DefaultConfigKey) ?? []);
|
||||
const [launchModItems, setLaunchModItems] = useState<LaunchModItemProps[]>([]);
|
||||
|
||||
const versionRunning = useObservable(() => bsLauncherService.versionRunning$);
|
||||
|
||||
@@ -44,32 +51,80 @@ export function LaunchSlide({ version }: Props) {
|
||||
}, [additionalArgsString]);
|
||||
|
||||
useEffect(() => {
|
||||
if(desktopMode){ return; }
|
||||
bsLauncherService.restoreSteamVR();
|
||||
}, [desktopMode]);
|
||||
configService.set("pinned-launch-mods", pinnedLaunchMods);
|
||||
}, [pinnedLaunchMods])
|
||||
|
||||
const setMode = (mode: LaunchMods, value: boolean) => {
|
||||
useEffect(() => {
|
||||
configService.set("launch-mods", activeLaunchMods);
|
||||
|
||||
if (mode === LaunchMods.DEBUG_MOD) {
|
||||
setDebugMode(value);
|
||||
} else if (mode === LaunchMods.OCULUS_MOD) {
|
||||
setOculusMode(value);
|
||||
} else if (mode === LaunchMods.DESKTOP_MOD) {
|
||||
setDesktopMode(value);
|
||||
if(!activeLaunchMods.includes("fpfc")){
|
||||
bsLauncherService.restoreSteamVR();
|
||||
}
|
||||
configService.set(mode, value);
|
||||
};
|
||||
}, [activeLaunchMods]);
|
||||
|
||||
const handleAdditionalArgsChange = (e: ChangeEvent<HTMLInputElement>) => setAdditionalArgsString(() => e.target.value);
|
||||
useEffect(() => {
|
||||
setLaunchModItems(() => [
|
||||
{
|
||||
id: LaunchMods.OCULUS,
|
||||
icon: OculusIcon,
|
||||
label: t("pages.version-viewer.launch-mods.oculus"),
|
||||
description: t("pages.version-viewer.launch-mods.oculus-description"),
|
||||
active: activeLaunchMods.includes(LaunchMods.OCULUS),
|
||||
visible: !(version.metadata?.store === BsStore.OCULUS),
|
||||
pinned: pinnedLaunchMods.includes(LaunchMods.OCULUS),
|
||||
onChange: checked => checked ? setActiveLaunchMods(prev => [...prev, LaunchMods.OCULUS]) : setActiveLaunchMods(prev => prev.filter(mod => mod !== LaunchMods.OCULUS)),
|
||||
onPinChange: pinned => pinned ? setPinnedLaunchMods(prev => [...prev, LaunchMods.OCULUS]) : setPinnedLaunchMods(prev => prev.filter(mod => mod !== LaunchMods.OCULUS)),
|
||||
},
|
||||
{
|
||||
id: LaunchMods.FPFC,
|
||||
icon: DesktopIcon,
|
||||
label: t("pages.version-viewer.launch-mods.desktop"),
|
||||
description: t("pages.version-viewer.launch-mods.desktop-description"),
|
||||
active: activeLaunchMods.includes(LaunchMods.FPFC),
|
||||
pinned: pinnedLaunchMods.includes(LaunchMods.FPFC),
|
||||
onChange: checked => checked ? setActiveLaunchMods(prev => [...prev, LaunchMods.FPFC]) : setActiveLaunchMods(prev => prev.filter(mod => mod !== LaunchMods.FPFC)),
|
||||
onPinChange: pinned => pinned ? setPinnedLaunchMods(prev => [...prev, LaunchMods.FPFC]) : setPinnedLaunchMods(prev => prev.filter(mod => mod !== LaunchMods.FPFC)),
|
||||
},
|
||||
{
|
||||
id: LaunchMods.DEBUG,
|
||||
icon: TerminalIcon,
|
||||
label: t("pages.version-viewer.launch-mods.debug"),
|
||||
description: t("pages.version-viewer.launch-mods.debug-description"),
|
||||
active: activeLaunchMods.includes(LaunchMods.DEBUG),
|
||||
pinned: pinnedLaunchMods.includes(LaunchMods.DEBUG),
|
||||
onChange: checked => checked ? setActiveLaunchMods(prev => [...prev, LaunchMods.DEBUG]) : setActiveLaunchMods(prev => prev.filter(mod => mod !== LaunchMods.DEBUG)),
|
||||
onPinChange: pinned => pinned ? setPinnedLaunchMods(prev => [...prev, LaunchMods.DEBUG]) : setPinnedLaunchMods(prev => prev.filter(mod => mod !== LaunchMods.DEBUG)),
|
||||
},
|
||||
{
|
||||
id: LaunchMods.SKIP_STEAM,
|
||||
label: t("pages.version-viewer.launch-mods.skipsteam"),
|
||||
description: t("pages.version-viewer.launch-mods.skipsteam-description"),
|
||||
active: activeLaunchMods.includes(LaunchMods.SKIP_STEAM),
|
||||
pinned: pinnedLaunchMods.includes(LaunchMods.SKIP_STEAM),
|
||||
onChange: checked => checked ? setActiveLaunchMods(prev => [...prev, LaunchMods.SKIP_STEAM]) : setActiveLaunchMods(prev => prev.filter(mod => mod !== LaunchMods.SKIP_STEAM)),
|
||||
onPinChange: pinned => pinned ? setPinnedLaunchMods(prev => [...prev, LaunchMods.SKIP_STEAM]) : setPinnedLaunchMods(prev => prev.filter(mod => mod !== LaunchMods.SKIP_STEAM)),
|
||||
},
|
||||
{
|
||||
id: LaunchMods.EDITOR,
|
||||
icon: EditIcon,
|
||||
label: t("pages.version-viewer.launch-mods.map-editor"),
|
||||
description: t("pages.version-viewer.launch-mods.map-editor-description"),
|
||||
active: activeLaunchMods.includes(LaunchMods.EDITOR),
|
||||
pinned: pinnedLaunchMods.includes(LaunchMods.EDITOR),
|
||||
visible: !safeLt(version.BSVersion, "1.23.0"),
|
||||
onChange: checked => checked ? setActiveLaunchMods(prev => [...prev, LaunchMods.EDITOR]) : setActiveLaunchMods(prev => prev.filter(mod => mod !== LaunchMods.EDITOR)),
|
||||
onPinChange: pinned => pinned ? setPinnedLaunchMods(prev => [...prev, LaunchMods.EDITOR]) : setPinnedLaunchMods(prev => prev.filter(mod => mod !== LaunchMods.EDITOR)),
|
||||
|
||||
}
|
||||
]);
|
||||
}, [activeLaunchMods, pinnedLaunchMods, version]);
|
||||
|
||||
const launch = () => {
|
||||
const additionalArgs = additionalArgsString?.split(";").map(arg => arg.trim()).filter(arg => arg.length > 0);
|
||||
|
||||
const launch$ = bsLauncherService.launch({
|
||||
version,
|
||||
oculus: version.oculus ? false : oculusMode,
|
||||
desktop: desktopMode,
|
||||
debug: debugMode,
|
||||
launchMods: activeLaunchMods,
|
||||
additionalArgs: advancedLaunch ? additionalArgs : [],
|
||||
});
|
||||
|
||||
@@ -81,9 +136,11 @@ export function LaunchSlide({ version }: Props) {
|
||||
}, [version]);
|
||||
|
||||
return (
|
||||
<div className="w-full shrink-0 items-center relative flex flex-col justify-start">
|
||||
<div className="flex flex-col gap-3 justify-center items-center mb-5">
|
||||
<BsmImage className="relative object-cover h-28" image={BSLogo} />
|
||||
<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">
|
||||
<div className="h-24 flex justify-center items-center">
|
||||
<BsmImage className="relative object-cover h-28" image={BSLogo} />
|
||||
</div>
|
||||
<h1 className="relative text-4xl font-bold italic -top-3">
|
||||
{version.name ? `${version.BSVersion} - ${version.name}` : version.BSVersion}
|
||||
{isOutdated && (
|
||||
@@ -95,14 +152,23 @@ export function LaunchSlide({ version }: Props) {
|
||||
)}
|
||||
</h1>
|
||||
</div>
|
||||
<div className="grid grid-flow-col gap-6">
|
||||
{!(version.oculus || version.metadata?.store === BsStore.OCULUS) && <LaunchModToogle infoText="pages.version-viewer.launch-mods.oculus-description" icon="oculus" onClick={() => setMode(LaunchMods.OCULUS_MOD, !oculusMode)} active={oculusMode} text="pages.version-viewer.launch-mods.oculus" />}
|
||||
<LaunchModToogle infoText="pages.version-viewer.launch-mods.desktop-description" icon="desktop" onClick={() => setMode(LaunchMods.DESKTOP_MOD, !desktopMode)} active={desktopMode} text="pages.version-viewer.launch-mods.desktop" />
|
||||
<LaunchModToogle infoText="pages.version-viewer.launch-mods.debug-description" icon="terminal" onClick={() => setMode(LaunchMods.DEBUG_MOD, !debugMode)} active={debugMode} text="pages.version-viewer.launch-mods.debug" />
|
||||
<div className="w-full flex justify-center items-center gap-4 flex-wrap scrollbar-default">
|
||||
{pinnedLaunchMods.map(id => launchModItems.find(mod => mod.id === id)).map(launchMod => {
|
||||
if(launchMod?.visible === false || !launchMod?.pinned) { return undefined; }
|
||||
return (
|
||||
<LaunchModToogle
|
||||
icon={launchMod.icon}
|
||||
infoText={launchMod.description}
|
||||
text={launchMod.label}
|
||||
active={launchMod.active}
|
||||
onClick={() => launchMod.onChange(!launchMod.active)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="pt-4 w-2/3 flex flex-col items-center gap-3">
|
||||
<div className="mt-4 flex flex-col items-center justify-center gap-3">
|
||||
<div className="relative">
|
||||
<GlowEffect className="!rounded-full" visible={!!(advancedLaunch && additionalArgsString)}/>
|
||||
<GlowEffect className="!rounded-full" visible={!!(activeLaunchMods?.length || additionalArgsString)}/>
|
||||
<BsmButton
|
||||
className={cn("rounded-full w-fit text-lg py-1 px-7 bg-theme-2 text-gray-800 dark:text-white", (advancedLaunch && additionalArgsString) ? "" : "shadow-md shadow-black")}
|
||||
text="pages.version-viewer.launch-mods.advanced-launch.button"
|
||||
@@ -113,15 +179,13 @@ export function LaunchSlide({ version }: Props) {
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="bg-light-main-color-2 dark:bg-main-color-2 h-9 rounded-full overflow-hidden flex items-center justify-center transition-all duration-100 ease-in-out w-full origin-top shadow-black shadow-sm" style={{ scale: advancedLaunch ? "100% 100%" : "0 0" }}>
|
||||
<input className="w-[calc(100%-12px)] h-[calc(100%-12px)] bg-light-main-color-1 dark:bg-main-color-1 text-black dark:text-white rounded-full outline-none text-center" type="text" placeholder={t("pages.version-viewer.launch-mods.advanced-launch.placeholder")} value={additionalArgsString} onChange={handleAdditionalArgsChange} />
|
||||
</div>
|
||||
</div>
|
||||
<div className='grow flex justify-center items-center'>
|
||||
<LaunchOptionsPanel open={advancedLaunch} launchMods={launchModItems} launchArgs={additionalArgsString} className="w-full max-w-3xl mt-3" onLaunchArgsChange={setAdditionalArgsString}/>
|
||||
<div className='grow flex justify-center items-center p-2'>
|
||||
<BsmButton
|
||||
onClick={launch}
|
||||
active={JSON.stringify(version) === JSON.stringify(versionRunning)}
|
||||
className='relative -translate-y-1/2 text-5xl text-gray-800 dark:text-gray-200 font-bold tracking-wide pt-1 pb-3 px-7 rounded-lg shadow-md italic shadow-black active:scale-90 transition-transform'
|
||||
className='relative text-5xl text-gray-800 dark:text-gray-200 font-bold tracking-wide pt-1 pb-3 px-7 rounded-lg shadow-md italic shadow-black active:scale-90 transition-transform'
|
||||
text="misc.launch"
|
||||
disabled={equal(version, versionDownloading)}
|
||||
/>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { LaunchMod, LaunchMods } from "shared/models/bs-launch/launch-option.interface";
|
||||
import { BsvSearchOrder } from "shared/models/maps/beat-saver.model";
|
||||
|
||||
// NOTE: To refactor. Rename to LocalStorageConfigKeyValues since these are stored in the
|
||||
@@ -12,6 +13,7 @@ export interface DefaultConfigKeyValues {
|
||||
"default-shared-folders": string[];
|
||||
"playlist-sort-order": BsvSearchOrder;
|
||||
"map-sort-order": BsvSearchOrder;
|
||||
"pinned-launch-mods": LaunchMod[];
|
||||
};
|
||||
|
||||
export type DefaultConfigKey = keyof DefaultConfigKeyValues;
|
||||
@@ -31,6 +33,8 @@ export const defaultConfiguration: {
|
||||
],
|
||||
"playlist-sort-order": BsvSearchOrder.Relevance,
|
||||
"map-sort-order": BsvSearchOrder.Relevance,
|
||||
"pinned-launch-mods": [LaunchMods.OCULUS, LaunchMods.DEBUG]
|
||||
};
|
||||
|
||||
export type ThemeConfig = "dark" | "light" | "os";
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ 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-management/models-manager.service";
|
||||
import { useTranslation } from "renderer/hooks/use-translation.hook";
|
||||
import { useTranslation, useTranslationV2 } from "renderer/hooks/use-translation.hook";
|
||||
import { VersionFolderLinkerService } from "renderer/services/version-folder-linker.service";
|
||||
import { useService } from "renderer/hooks/use-service.hook";
|
||||
import { lastValueFrom } from "rxjs";
|
||||
@@ -40,7 +40,7 @@ import { SteamIcon } from "renderer/components/svgs/icons/steam-icon.component";
|
||||
import { OculusIcon } from "renderer/components/svgs/icons/oculus-icon.component";
|
||||
import { BsDownloaderService } from "renderer/services/bs-version-download/bs-downloader.service";
|
||||
import BeatConflict from "../../../assets/images/apngs/beat-conflict.png";
|
||||
import { SettingToogleSwitchGrid } from "renderer/components/settings/setting-toogle-switch-grid.component";
|
||||
import { Item, SettingToogleSwitchGrid } from "renderer/components/settings/setting-toogle-switch-grid.component";
|
||||
import { BasicModal } from "renderer/components/modal/basic-modal.component";
|
||||
import { StaticConfigurationService } from "renderer/services/static-configuration.service";
|
||||
import { tryit } from "shared/helpers/error.helpers";
|
||||
@@ -96,8 +96,6 @@ export function SettingsPage() {
|
||||
const [playlistsDeepLinkEnabled, setPlaylistsDeepLinkEnabled] = useState(false);
|
||||
const [modelsDeepLinkEnabled, setModelsDeepLinkEnabled] = useState(false);
|
||||
const [hasDownloaderSession, setHasDownloaderSession] = useState(false);
|
||||
const [hardwareAccelerationEnabled, setHardwareAccelerationEnabled] = useState(true);
|
||||
const [useSymlink, setUseSymlink] = useState(false);
|
||||
const appVersion = useObservable(() => autoUpdater.getAppVersion());
|
||||
|
||||
useEffect(() => {
|
||||
@@ -107,8 +105,6 @@ export function SettingsPage() {
|
||||
playlistsManager.isDeepLinksEnabled().then(enabled => setPlaylistsDeepLinkEnabled(() => enabled));
|
||||
modelsManager.isDeepLinksEnabled().then(enabled => setModelsDeepLinkEnabled(() => enabled));
|
||||
|
||||
staticConfig.get("disable-hadware-acceleration").then(disabled =>setHardwareAccelerationEnabled(() => disabled !== true));
|
||||
staticConfig.get("use-symlinks").then(useSymlinks => setUseSymlink(() => useSymlinks));
|
||||
staticConfig.get("proton-folder").then(setProtonFolder);
|
||||
}, []);
|
||||
|
||||
@@ -233,66 +229,6 @@ export function SettingsPage() {
|
||||
});
|
||||
};
|
||||
|
||||
const onChangeHardwareAcceleration = async (newHardwareAccelerationEnabled: boolean) => {
|
||||
if(newHardwareAccelerationEnabled === hardwareAccelerationEnabled){ return; }
|
||||
|
||||
const res = await modalService.openModal(BasicModal, { data: {
|
||||
title: "pages.settings.advanced.hardware-acceleration.modal.title",
|
||||
body: "pages.settings.advanced.hardware-acceleration.modal.body",
|
||||
image: BeatConflict,
|
||||
buttons: [
|
||||
{ id: "cancel", text: "misc.cancel", type: "cancel", isCancel: true },
|
||||
{ id: "confirm", text: "pages.settings.advanced.hardware-acceleration.modal.confirm-btn", type: "error" }
|
||||
]
|
||||
}});
|
||||
|
||||
if(res.exitCode !== ModalExitCode.COMPLETED || res.data !== "confirm"){ return; }
|
||||
|
||||
const { error } = await tryit(() => staticConfig.set("disable-hadware-acceleration", !newHardwareAccelerationEnabled));
|
||||
|
||||
if(error){
|
||||
notificationService.notifyError({ title: "notifications.types.error", desc: "pages.settings.advanced.hardware-acceleration.error-notification.message" });
|
||||
setHardwareAccelerationEnabled(() => !newHardwareAccelerationEnabled);
|
||||
return;
|
||||
}
|
||||
|
||||
setHardwareAccelerationEnabled(() => newHardwareAccelerationEnabled);
|
||||
|
||||
if(!progressBarService.require()){
|
||||
return;
|
||||
}
|
||||
|
||||
await lastValueFrom(ipcService.sendV2("restart-app"));
|
||||
};
|
||||
|
||||
const onChangeUseSymlinks = async (newUseSymlink: boolean) => {
|
||||
|
||||
if(newUseSymlink === useSymlink){ return; }
|
||||
|
||||
if(newUseSymlink){
|
||||
const res = await modalService.openModal(BasicModal, { data: {
|
||||
title: "pages.settings.advanced.use-symlinks.modal.title",
|
||||
body: "pages.settings.advanced.use-symlinks.modal.body",
|
||||
image: BeatConflict,
|
||||
buttons: [
|
||||
{ id: "cancel", text: "misc.cancel", type: "cancel", isCancel: true },
|
||||
{ id: "confirm", text: "pages.settings.advanced.use-symlinks.modal.confirm-btn", type: "error" }
|
||||
]
|
||||
}});
|
||||
|
||||
if(res.exitCode !== ModalExitCode.COMPLETED || res.data !== "confirm"){ return; }
|
||||
}
|
||||
|
||||
const { error } = await tryit(() => staticConfig.set("use-symlinks", newUseSymlink));
|
||||
|
||||
if(error){
|
||||
notificationService.notifyError({ title: "notifications.types.error", desc: "pages.settings.advanced.use-symlinks.error-notification.message" });
|
||||
return;
|
||||
}
|
||||
|
||||
setUseSymlink(() => newUseSymlink);
|
||||
}
|
||||
|
||||
const toogleShowSupporters = () => {
|
||||
setShowSupporters(show => !show);
|
||||
};
|
||||
@@ -585,12 +521,7 @@ export function SettingsPage() {
|
||||
</SettingContainer>
|
||||
</SettingContainer>
|
||||
|
||||
<SettingContainer title="pages.settings.advanced.title" description="pages.settings.advanced.description">
|
||||
<SettingToogleSwitchGrid items={[
|
||||
{ checked: hardwareAccelerationEnabled, text: t("pages.settings.advanced.hardware-acceleration.title"), desc: t("pages.settings.advanced.hardware-acceleration.description"), onChange: onChangeHardwareAcceleration },
|
||||
{ checked: useSymlink, text: t("pages.settings.advanced.use-symlinks.title"), desc: t("pages.settings.advanced.use-symlinks.description"), onChange: onChangeUseSymlinks },
|
||||
]}/>
|
||||
</SettingContainer>
|
||||
<AdvancedSettings />
|
||||
|
||||
<span className="bg-light-main-color-1 dark:bg-main-color-1 rounded-md py-1 px-2 font-bold float-right mb-5">v{appVersion}</span>
|
||||
</div>
|
||||
@@ -598,3 +529,104 @@ export function SettingsPage() {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function AdvancedSettings() {
|
||||
const ipc = useService(IpcService);
|
||||
const modal = useService(ModalService);
|
||||
const notification = useService(NotificationService);
|
||||
const progressBar = useService(ProgressBarService);
|
||||
const staticConfig = useService(StaticConfigurationService);
|
||||
|
||||
const t = useTranslationV2();
|
||||
|
||||
const [hardwareAccelerationEnabled, setHardwareAccelerationEnabled] = useState(true);
|
||||
const [useSymlink, setUseSymlink] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
staticConfig.get("disable-hadware-acceleration").then(disabled =>setHardwareAccelerationEnabled(() => disabled !== true));
|
||||
staticConfig.get("use-symlinks").then(useSymlinks => setUseSymlink(() => useSymlinks));
|
||||
}, []);
|
||||
|
||||
const onChangeHardwareAcceleration = async (newHardwareAccelerationEnabled: boolean) => {
|
||||
if(newHardwareAccelerationEnabled === hardwareAccelerationEnabled){ return; }
|
||||
|
||||
const res = await modal.openModal(BasicModal, { data: {
|
||||
title: "pages.settings.advanced.hardware-acceleration.modal.title",
|
||||
body: "pages.settings.advanced.hardware-acceleration.modal.body",
|
||||
image: BeatConflict,
|
||||
buttons: [
|
||||
{ id: "cancel", text: "misc.cancel", type: "cancel" },
|
||||
{ id: "confirm", text: "pages.settings.advanced.hardware-acceleration.modal.confirm-btn", type: "error", onClick: () => true },
|
||||
]
|
||||
}});
|
||||
|
||||
if(res.exitCode !== ModalExitCode.COMPLETED || res.data !== "confirm"){ return; }
|
||||
|
||||
const { error } = await tryit(() => staticConfig.set("disable-hadware-acceleration", !newHardwareAccelerationEnabled));
|
||||
|
||||
if(error){
|
||||
notification.notifyError({ title: "notifications.types.error", desc: "pages.settings.advanced.hardware-acceleration.error-notification.message" });
|
||||
setHardwareAccelerationEnabled(() => !newHardwareAccelerationEnabled);
|
||||
return;
|
||||
}
|
||||
|
||||
setHardwareAccelerationEnabled(() => newHardwareAccelerationEnabled);
|
||||
|
||||
if(!progressBar.require()){
|
||||
return;
|
||||
}
|
||||
|
||||
await lastValueFrom(ipc.sendV2("restart-app"));
|
||||
};
|
||||
|
||||
const onChangeUseSymlinks = async (newUseSymlink: boolean) => {
|
||||
|
||||
if (window.electron.platform !== "win32" || newUseSymlink === useSymlink) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(newUseSymlink){
|
||||
const res = await modal.openModal(BasicModal, { data: {
|
||||
title: "pages.settings.advanced.use-symlinks.modal.title",
|
||||
body: "pages.settings.advanced.use-symlinks.modal.body",
|
||||
image: BeatConflict,
|
||||
buttons: [
|
||||
{ id: "cancel", text: "misc.cancel", type: "cancel" },
|
||||
{ id: "confirm", text: "pages.settings.advanced.use-symlinks.modal.confirm-btn", type: "error", onClick: () => true }
|
||||
]
|
||||
}});
|
||||
|
||||
if(res.exitCode !== ModalExitCode.COMPLETED || res.data !== "confirm"){ return; }
|
||||
}
|
||||
|
||||
const { error } = await tryit(() => staticConfig.set("use-symlinks", newUseSymlink));
|
||||
|
||||
if(error){
|
||||
notification.notifyError({ title: "notifications.types.error", desc: "pages.settings.advanced.use-symlinks.error-notification.message" });
|
||||
return;
|
||||
}
|
||||
|
||||
setUseSymlink(() => newUseSymlink);
|
||||
}
|
||||
|
||||
const advancedItems: Item[] = [{
|
||||
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,
|
||||
text: t.text("pages.settings.advanced.use-symlinks.title"),
|
||||
desc: t.text("pages.settings.advanced.use-symlinks.description"),
|
||||
onChange: onChangeUseSymlinks
|
||||
});
|
||||
}
|
||||
|
||||
return <SettingContainer title="pages.settings.advanced.title" description="pages.settings.advanced.description">
|
||||
<SettingToogleSwitchGrid items={advancedItems}/>
|
||||
</SettingContainer>
|
||||
|
||||
}
|
||||
|
||||
|
||||
Vendored
+1
-1
@@ -3,7 +3,7 @@ import { webUtils } from "electron";
|
||||
declare global {
|
||||
interface Window {
|
||||
electron: {
|
||||
platform: "win32"|"linux"|"darwin",
|
||||
platform: "win32" | "linux",
|
||||
ipcRenderer: {
|
||||
sendMessage(channel: string, args: any): void;
|
||||
on(channel: string, func: (...args: any) => void): (() => void) | undefined;
|
||||
|
||||
@@ -47,10 +47,8 @@ export class BSLauncherService {
|
||||
public getLaunchOptions(version: BSVersion): LaunchOption{
|
||||
return {
|
||||
version,
|
||||
oculus: this.config.get(LaunchMods.OCULUS_MOD),
|
||||
desktop: this.config.get(LaunchMods.DESKTOP_MOD),
|
||||
debug: this.config.get(LaunchMods.DEBUG_MOD),
|
||||
additionalArgs: (this.config.get<string>("additionnal-args") || "").split(";").map(arg => arg.trim()).filter(arg => arg.length > 0)
|
||||
launchMods: this.config.get("launch-mods") ?? [],
|
||||
additionalArgs: (this.config.get<string>("additionnal-args") || "").split(";").map(arg => arg.trim()).filter(arg => arg.length > 0),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,9 +121,3 @@ export class BSLauncherService {
|
||||
return lastValueFrom(this.ipcService.sendV2("bs-launch.restore-steamvr"));
|
||||
}
|
||||
}
|
||||
|
||||
export enum LaunchMods {
|
||||
OCULUS_MOD = "LAUNCH_OCULUS_MOD",
|
||||
DESKTOP_MOD = "LAUNCH_DESKTOP_MOD",
|
||||
DEBUG_MOD = "LAUNCH_DEBUG_MOD",
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ export enum BSLaunchError{
|
||||
export enum BSLaunchEvent{
|
||||
STEAM_LAUNCHING = "STEAM_LAUNCHING",
|
||||
STEAM_LAUNCHED = "STEAM_LAUNCHED",
|
||||
SKIPPING_STEAM_LAUNCH = "SKIPPING_STEAM_LAUNCH",
|
||||
BS_LAUNCHING = "BS_LAUNCHING",
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
import { BSVersion } from "shared/bs-version.interface";
|
||||
|
||||
export const LaunchMods = {
|
||||
OCULUS: "oculus",
|
||||
FPFC: "fpfc",
|
||||
DEBUG: "debug",
|
||||
SKIP_STEAM: "skip_steam",
|
||||
EDITOR: "editor",
|
||||
} as const;
|
||||
|
||||
export type LaunchMod = typeof LaunchMods[keyof typeof LaunchMods];
|
||||
|
||||
export interface LaunchOption {
|
||||
version: BSVersion,
|
||||
oculus?: boolean,
|
||||
desktop?: boolean,
|
||||
debug?: boolean,
|
||||
launchMods?: LaunchMod[],
|
||||
additionalArgs?: string[],
|
||||
admin?: boolean,
|
||||
admin?: boolean
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user