diff --git a/assets/jsons/translations/de.json b/assets/jsons/translations/de.json index f5213e78..d3772125 100644 --- a/assets/jsons/translations/de.json +++ b/assets/jsons/translations/de.json @@ -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.", diff --git a/assets/jsons/translations/en.json b/assets/jsons/translations/en.json index 07f6cb62..1ef9eab9 100644 --- a/assets/jsons/translations/en.json +++ b/assets/jsons/translations/en.json @@ -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.", diff --git a/assets/jsons/translations/es.json b/assets/jsons/translations/es.json index 2a740437..553790e0 100644 --- a/assets/jsons/translations/es.json +++ b/assets/jsons/translations/es.json @@ -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.", diff --git a/assets/jsons/translations/fr.json b/assets/jsons/translations/fr.json index b7d51c7a..ec4d562e 100644 --- a/assets/jsons/translations/fr.json +++ b/assets/jsons/translations/fr.json @@ -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.", diff --git a/assets/jsons/translations/ja.json b/assets/jsons/translations/ja.json index 4afba835..d7d95c14 100644 --- a/assets/jsons/translations/ja.json +++ b/assets/jsons/translations/ja.json @@ -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を起動できます。", diff --git a/assets/jsons/translations/ko.json b/assets/jsons/translations/ko.json index d468de79..581aca3b 100644 --- a/assets/jsons/translations/ko.json +++ b/assets/jsons/translations/ko.json @@ -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를 손쉽게 실행하세요.", diff --git a/assets/jsons/translations/ru.json b/assets/jsons/translations/ru.json index d4fada19..09a9ea1f 100644 --- a/assets/jsons/translations/ru.json +++ b/assets/jsons/translations/ru.json @@ -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.", diff --git a/assets/jsons/translations/zh-tw.json b/assets/jsons/translations/zh-tw.json index 59722593..6bf2364b 100644 --- a/assets/jsons/translations/zh-tw.json +++ b/assets/jsons/translations/zh-tw.json @@ -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", diff --git a/assets/jsons/translations/zh.json b/assets/jsons/translations/zh.json index 27773aa0..a84ec492 100644 --- a/assets/jsons/translations/zh.json +++ b/assets/jsons/translations/zh.json @@ -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", diff --git a/docs/assets/linux-missing-icons.png b/docs/assets/linux-missing-icons.png new file mode 100644 index 00000000..e015445c Binary files /dev/null and b/docs/assets/linux-missing-icons.png differ diff --git a/docs/wiki/Linux.md b/docs/wiki/Linux.md index cc0ca312..0ab1eb25 100644 --- a/docs/wiki/Linux.md +++ b/docs/wiki/Linux.md @@ -47,6 +47,18 @@ Once Proton is installed, when you open your BSManager application for the first # Troubleshooting +## Missing Icons in game + +

+ Linux Missing Icons +
+ NOTE: "?" are missing unicode icons +

+ +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"
diff --git a/src/main/services/bs-launcher/abstract-launcher.service.ts b/src/main/services/bs-launcher/abstract-launcher.service.ts
index 6ed46121..9f6d8a84 100644
--- a/src/main/services/bs-launcher/abstract-launcher.service.ts
+++ b/src/main/services/bs-launcher/abstract-launcher.service.ts
@@ -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);
         }
diff --git a/src/main/services/bs-launcher/bs-launcher.service.ts b/src/main/services/bs-launcher/bs-launcher.service.ts
index 7cda217d..5afe996e 100644
--- a/src/main/services/bs-launcher/bs-launcher.service.ts
+++ b/src/main/services/bs-launcher/bs-launcher.service.ts
@@ -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;
diff --git a/src/main/services/bs-launcher/steam-launcher.service.ts b/src/main/services/bs-launcher/steam-launcher.service.ts
index 8b8816cf..fdae1e1c 100644
--- a/src/main/services/bs-launcher/steam-launcher.service.ts
+++ b/src/main/services/bs-launcher/steam-launcher.service.ts
@@ -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();
                 });
diff --git a/src/main/services/bs-version-lib.service.ts b/src/main/services/bs-version-lib.service.ts
index a8ca4ce8..70c16e9c 100644
--- a/src/main/services/bs-version-lib.service.ts
+++ b/src/main/services/bs-version-lib.service.ts
@@ -42,7 +42,7 @@ export class BSVersionLibService {
 
     private async shouldLoadFromConfig(): Promise {
         // 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 {
diff --git a/src/main/services/folder-linker.service.ts b/src/main/services/folder-linker.service.ts
index 70f1e48a..65e9f507 100644
--- a/src/main/services/folder-linker.service.ts
+++ b/src/main/services/folder-linker.service.ts
@@ -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 {
diff --git a/src/renderer/components/modal/basic-modal.component.tsx b/src/renderer/components/modal/basic-modal.component.tsx
index 0e68ab16..d1acbce0 100644
--- a/src/renderer/components/modal/basic-modal.component.tsx
+++ b/src/renderer/components/modal/basic-modal.component.tsx
@@ -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 {
 
-    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 (
         
-

{t(title)}

+

{t.text(title)}

- { body &&

{t(body)}

} + { body &&

{t.text(body)}

}
{buttons.map(button => ( - handleClick(button)} withBar={false} text={button.text} /> + handleClick(button)} + withBar={false} + text={button.text} + /> ))}
diff --git a/src/renderer/components/modal/modal-types/share-folders-modal.component.tsx b/src/renderer/components/modal/modal-types/share-folders-modal.component.tsx index b685a3fe..db4eca3a 100644 --- a/src/renderer/components/modal/modal-types/share-folders-modal.component.tsx +++ b/src/renderer/components/modal/modal-types/share-folders-modal.component.tsx @@ -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 = ({ 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 = ({ 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(Array.from(new Set([...config.get(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 = ({ 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 (
-

{t("modals.shared-folders.title")}

-

{t("modals.shared-folders.description")}

+

{t.text("modals.shared-folders.title")}

+

{t.text("modals.shared-folders.description")}

    {folders.map((folder, index) => ( { removeFolder(index); @@ -89,13 +86,93 @@ export const ShareFoldersModal: ModalComponent = ({ options: {d ))}
- +
); }; +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 ; +} + // -------- 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}
- + { ); } return ( - { - e.preventDefault(); - onDelete?.(); - }} - /> + + { + e.preventDefault(); + onDelete?.(); + }} + /> + ); })()}
diff --git a/src/renderer/components/nav-bar/nav-bar-items/bs-version-item.component.tsx b/src/renderer/components/nav-bar/nav-bar-items/bs-version-item.component.tsx index 4b204c82..8117d0aa 100644 --- a/src/renderer/components/nav-bar/nav-bar-items/bs-version-item.component.tsx +++ b/src/renderer/components/nav-bar/nav-bar-items/bs-version-item.component.tsx @@ -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(LaunchMods.OCULUS_MOD), - desktop: !!configService.get(LaunchMods.DESKTOP_MOD), - debug: !!configService.get(LaunchMods.DEBUG_MOD), + launchMods: configService.get("launch-mods") ?? [], }); return lastValueFrom(launch$).catch(() => {}); }; diff --git a/src/renderer/components/settings/setting-container.component.tsx b/src/renderer/components/settings/setting-container.component.tsx index 00aba9b7..0ed25deb 100644 --- a/src/renderer/components/settings/setting-container.component.tsx +++ b/src/renderer/components/settings/setting-container.component.tsx @@ -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 (
- {title &&

{t(title)}

} - {minorTitle &&

{t(minorTitle)}

} - {description &&

{t(description)}

} + {title &&

{t.text(title)}

} + {minorTitle &&

{t.text(minorTitle)}

} + {description &&

{t.text(description)}

} {children}
); diff --git a/src/renderer/components/settings/setting-toogle-switch-grid.component.tsx b/src/renderer/components/settings/setting-toogle-switch-grid.component.tsx index cba43faf..a5b6bda0 100644 --- a/src/renderer/components/settings/setting-toogle-switch-grid.component.tsx +++ b/src/renderer/components/settings/setting-toogle-switch-grid.component.tsx @@ -1,6 +1,6 @@ import { ToogleSwitch } from "../shared/toogle-switch.component"; -type Item = { +export type Item = { text: string; desc?: string; checked?: boolean; diff --git a/src/renderer/components/shared/link-button.component.tsx b/src/renderer/components/shared/link-button.component.tsx index 3f8446d7..dce72ee7 100644 --- a/src/renderer/components/shared/link-button.component.tsx +++ b/src/renderer/components/shared/link-button.component.tsx @@ -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(({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 ( ); -} +}); diff --git a/src/renderer/components/svgs/bsm-icon.component.tsx b/src/renderer/components/svgs/bsm-icon.component.tsx index bf947f81..237616e3 100644 --- a/src/renderer/components/svgs/bsm-icon.component.tsx +++ b/src/renderer/components/svgs/bsm-icon.component.tsx @@ -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 ; } - - if(icon === "cancel"){ + if(icon === "null") { + return null; + } + if(icon === "cancel") { return ; } - - if(icon === "warning"){ - return ; + if(icon === "warning") { + return } return ; diff --git a/src/renderer/components/svgs/icons/desktop-icon.component.tsx b/src/renderer/components/svgs/icons/desktop-icon.component.tsx index 367ba71f..56aa8af5 100644 --- a/src/renderer/components/svgs/icons/desktop-icon.component.tsx +++ b/src/renderer/components/svgs/icons/desktop-icon.component.tsx @@ -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 ( - + ); -} +}) diff --git a/src/renderer/components/svgs/icons/edit-icon.component.tsx b/src/renderer/components/svgs/icons/edit-icon.component.tsx index 9523437e..6f8cc7d2 100644 --- a/src/renderer/components/svgs/icons/edit-icon.component.tsx +++ b/src/renderer/components/svgs/icons/edit-icon.component.tsx @@ -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 ( - + ); -} +}) diff --git a/src/renderer/components/svgs/icons/oculus-icon.component.tsx b/src/renderer/components/svgs/icons/oculus-icon.component.tsx index dc2484fc..d2736bc5 100644 --- a/src/renderer/components/svgs/icons/oculus-icon.component.tsx +++ b/src/renderer/components/svgs/icons/oculus-icon.component.tsx @@ -1,12 +1,12 @@ -import { HTMLProps } from "react"; +import { createSvgIcon } from "../svg-icon.type"; -export function OculusIcon(props: HTMLProps) { +export const OculusIcon = createSvgIcon((props, ref) => { return ( - + ); -} +}) diff --git a/src/renderer/components/svgs/icons/pin-icon.component.tsx b/src/renderer/components/svgs/icons/pin-icon.component.tsx new file mode 100644 index 00000000..42a580aa --- /dev/null +++ b/src/renderer/components/svgs/icons/pin-icon.component.tsx @@ -0,0 +1,9 @@ +import { createSvgIcon } from "../svg-icon.type"; + +export const PinIcon = createSvgIcon((props, ref) => { + return ( + + + + ) +}); diff --git a/src/renderer/components/svgs/icons/terminal-icon.component.tsx b/src/renderer/components/svgs/icons/terminal-icon.component.tsx index a553809c..ac930525 100644 --- a/src/renderer/components/svgs/icons/terminal-icon.component.tsx +++ b/src/renderer/components/svgs/icons/terminal-icon.component.tsx @@ -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 ( - + ); -} +}) diff --git a/src/renderer/components/svgs/icons/unpin-icon.component.tsx b/src/renderer/components/svgs/icons/unpin-icon.component.tsx new file mode 100644 index 00000000..bf38bd5b --- /dev/null +++ b/src/renderer/components/svgs/icons/unpin-icon.component.tsx @@ -0,0 +1,9 @@ +import { createSvgIcon } from "../svg-icon.type" + +export const UnpinIcon = createSvgIcon((props, ref) => { + return ( + + + + ) +}) diff --git a/src/renderer/components/version-viewer/slides/launch/launch-mod-toogle.component.tsx b/src/renderer/components/version-viewer/slides/launch/launch-mod-toogle.component.tsx index 48cc5d72..e560a7a2 100644 --- a/src/renderer/components/version-viewer/slides/launch/launch-mod-toogle.component.tsx +++ b/src/renderer/components/version-viewer/slides/launch/launch-mod-toogle.component.tsx @@ -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 ( -
onClick(!active)}> + ); } diff --git a/src/renderer/components/version-viewer/slides/launch/launch-options-panel.component.tsx b/src/renderer/components/version-viewer/slides/launch/launch-options-panel.component.tsx new file mode 100644 index 00000000..db2cf231 --- /dev/null +++ b/src/renderer/components/version-viewer/slides/launch/launch-options-panel.component.tsx @@ -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 ( +
+
+
+ onLaunchArgsChange(e.target.value)}/> +
+ {launchMods?.map((mod) => ( + + ))} +
+
+
+
+ ) +} + +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 ( + + + + )} + + + ); + +} diff --git a/src/renderer/components/version-viewer/slides/launch/launch-slide.component.tsx b/src/renderer/components/version-viewer/slides/launch/launch-slide.component.tsx index 27902989..b705b132 100644 --- a/src/renderer/components/version-viewer/slides/launch/launch-slide.component.tsx +++ b/src/renderer/components/version-viewer/slides/launch/launch-slide.component.tsx @@ -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(LaunchMods.OCULUS_MOD)); - const [desktopMode, setDesktopMode] = useState(!!configService.get(LaunchMods.DESKTOP_MOD)); - const [debugMode, setDebugMode] = useState(!!configService.get(LaunchMods.DEBUG_MOD)); const [advancedLaunch, setAdvancedLaunch] = useState(false); const [additionalArgsString, setAdditionalArgsString] = useState(configService.get("additionnal-args") || ""); const versionDownloading = useObservable(() => bsDownloader.downloadingVersion$); + const [activeLaunchMods, setActiveLaunchMods] = useState(configService.get("launch-mods") ?? []); + const [pinnedLaunchMods, setPinnedLaunchMods] = useState(configService.get("pinned-launch-mods" as DefaultConfigKey) ?? []); + const [launchModItems, setLaunchModItems] = useState([]); 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) => 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 ( -
-
- +
+
+
+ +

{version.name ? `${version.BSVersion} - ${version.name}` : version.BSVersion} {isOutdated && ( @@ -95,14 +152,23 @@ export function LaunchSlide({ version }: Props) { )}

-
- {!(version.oculus || version.metadata?.store === BsStore.OCULUS) && setMode(LaunchMods.OCULUS_MOD, !oculusMode)} active={oculusMode} text="pages.version-viewer.launch-mods.oculus" />} - setMode(LaunchMods.DESKTOP_MOD, !desktopMode)} active={desktopMode} text="pages.version-viewer.launch-mods.desktop" /> - setMode(LaunchMods.DEBUG_MOD, !debugMode)} active={debugMode} text="pages.version-viewer.launch-mods.debug" /> +
+ {pinnedLaunchMods.map(id => launchModItems.find(mod => mod.id === id)).map(launchMod => { + if(launchMod?.visible === false || !launchMod?.pinned) { return undefined; } + return ( + launchMod.onChange(!launchMod.active)} + /> + ); + })}
-
+
- +
-
- -
-
+ +
diff --git a/src/renderer/config/default-configuration.config.ts b/src/renderer/config/default-configuration.config.ts index 2e4316ad..81c3b0fa 100644 --- a/src/renderer/config/default-configuration.config.ts +++ b/src/renderer/config/default-configuration.config.ts @@ -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"; + diff --git a/src/renderer/pages/settings-page.component.tsx b/src/renderer/pages/settings-page.component.tsx index 27f43e73..991c4ece 100644 --- a/src/renderer/pages/settings-page.component.tsx +++ b/src/renderer/pages/settings-page.component.tsx @@ -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() { - - - + v{appVersion}
@@ -598,3 +529,104 @@ export function SettingsPage() {
); } + +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 + + + +} + diff --git a/src/renderer/preload.d.ts b/src/renderer/preload.d.ts index 23e2b0ac..0c8cd4c6 100644 --- a/src/renderer/preload.d.ts +++ b/src/renderer/preload.d.ts @@ -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; diff --git a/src/renderer/services/bs-launcher.service.ts b/src/renderer/services/bs-launcher.service.ts index 8bc89ec8..3c78ff16 100644 --- a/src/renderer/services/bs-launcher.service.ts +++ b/src/renderer/services/bs-launcher.service.ts @@ -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("additionnal-args") || "").split(";").map(arg => arg.trim()).filter(arg => arg.length > 0) + launchMods: this.config.get("launch-mods") ?? [], + additionalArgs: (this.config.get("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", -} diff --git a/src/shared/models/bs-launch/launch-event.model.ts b/src/shared/models/bs-launch/launch-event.model.ts index 8ee1a454..b744d66d 100644 --- a/src/shared/models/bs-launch/launch-event.model.ts +++ b/src/shared/models/bs-launch/launch-event.model.ts @@ -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", } diff --git a/src/shared/models/bs-launch/launch-option.interface.ts b/src/shared/models/bs-launch/launch-option.interface.ts index 21056c19..21455b5a 100644 --- a/src/shared/models/bs-launch/launch-option.interface.ts +++ b/src/shared/models/bs-launch/launch-option.interface.ts @@ -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 }