diff --git a/assets/jsons/bs-versions.json b/assets/jsons/bs-versions.json index 5b8fe316..1e27b40a 100644 --- a/assets/jsons/bs-versions.json +++ b/assets/jsons/bs-versions.json @@ -847,7 +847,25 @@ "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/510702041542166484", "ReleaseImg": "https://clan.fastly.steamstatic.com/images/32055887/c27dac7f96e53672d020d97838cec1d5d28e3180.png", "ReleaseDate": "1743515622", + "year": "2025" + }, + { + "BSVersion": "1.40.5", + "BSManifest": "2447038720841508806", + "OculusBinaryId": "9120095564756808", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/502825275922317447", + "ReleaseImg": "https://clan.fastly.steamstatic.com/images/32055887/2b5845893d915902106aaf30b6d1a95f68a4b226.png", + "ReleaseDate": "1747321305", + "year": "2025" + }, + { + "BSVersion": "1.40.6", + "BSManifest": "3050696685250334115", + "OculusBinaryId": "9315734585192904", + "ReleaseURL": "https://steamcommunity.com/games/620980/announcements/detail/510708375093248769", + "ReleaseImg": "https://clan.fastly.steamstatic.com/images/32055887/0f658ed39f480446e0dc5807b1591e8a18aef2f5.png", + "ReleaseDate": "1749135144", "year": "2025", "recommended": true } -] \ No newline at end of file +] diff --git a/assets/jsons/patreons.json b/assets/jsons/patreons.json index fd4ab56b..e32f2e99 100644 --- a/assets/jsons/patreons.json +++ b/assets/jsons/patreons.json @@ -161,6 +161,14 @@ }, { "username": "KingCrocman", + "type": "diamond", + "link": "https://www.youtube.com/KingCrocman" + }, + { + "username": "bosspie", "type": "diamond" + }, + { + "username": "Camryn Jackson" } ] diff --git a/assets/jsons/translations/de.json b/assets/jsons/translations/de.json index 202810d7..03d88495 100644 --- a/assets/jsons/translations/de.json +++ b/assets/jsons/translations/de.json @@ -37,7 +37,10 @@ } }, "title-bar": { - "outdated": "veraltet" + "outdated": "veraltet", + "update-text": "BSManager {version} ist verfügbar!", + "update-button": "Aktualisieren und neu starten", + "see-changelog": "Änderungen ansehen" }, "nav-bar": { "add-version": "Version hinzufügen", @@ -337,6 +340,13 @@ "error-notification": { "message": "Es ist ein Fehler aufgetreten, die Systemproxyeinstellungen können nicht geändert werden." } + }, + "auto-update": { + "title": "Automatische Aktualisierung", + "description": "BSManager wird beim Start der Anwendung automatisch aktualisiert.", + "error-notification": { + "message": "Ein Fehler ist aufgetreten, die Einstellungen für die automatische Aktualisierung können nicht geändert werden." + } } } } diff --git a/assets/jsons/translations/en.json b/assets/jsons/translations/en.json index 4ae373b0..fb33467b 100644 --- a/assets/jsons/translations/en.json +++ b/assets/jsons/translations/en.json @@ -37,7 +37,10 @@ } }, "title-bar": { - "outdated": "outdated" + "outdated": "outdated", + "update-text": "BSManager {version} is available!", + "update-button": "Update and Restart", + "see-changelog": "See changelog" }, "nav-bar": { "add-version": "Add a version", @@ -337,6 +340,13 @@ "error-notification": { "message": "An error occurred, unable to change system proxy settings." } + }, + "auto-update": { + "title": "Auto Update", + "description": "BSManager will automatically update when you launch the application.", + "error-notification": { + "message": "An error occurred, unable to change auto update settings." + } } } } diff --git a/assets/jsons/translations/es.json b/assets/jsons/translations/es.json index 507b3be5..18d87f1f 100644 --- a/assets/jsons/translations/es.json +++ b/assets/jsons/translations/es.json @@ -37,7 +37,10 @@ } }, "title-bar": { - "outdated": "obsoleto" + "outdated": "obsoleto", + "update-text": "¡BSManager {version} está disponible!", + "update-button": "Actualizar y reiniciar", + "see-changelog": "Ver los cambios" }, "nav-bar": { "add-version": "Agregar una versión", @@ -337,6 +340,13 @@ "error-notification": { "message": "Ocurrió un error, no se pueden cambiar los ajustes del proxy del sistema." } + }, + "auto-update": { + "title": "Actualización automática", + "description": "BSManager se actualizará automáticamente al iniciar la aplicación.", + "error-notification": { + "message": "Ocurrió un error, no se pudo cambiar la configuración de actualización automática." + } } } } diff --git a/assets/jsons/translations/fr.json b/assets/jsons/translations/fr.json index fbfcab63..640f6ded 100644 --- a/assets/jsons/translations/fr.json +++ b/assets/jsons/translations/fr.json @@ -37,7 +37,10 @@ } }, "title-bar": { - "outdated": "obsolète" + "outdated": "obsolète", + "update-text": "BSManager {version} est disponible!", + "update-button": "Mettre à jour et redémarrer", + "see-changelog": "Voir les changements" }, "nav-bar": { "add-version": "Ajouter une version", @@ -337,6 +340,13 @@ "error-notification": { "message": "Une erreur est survenue, impossible de modifier les paramètres du proxy système." } + }, + "auto-update": { + "title": "Mise à jour automatique", + "description": "BSManager se mettra automatiquement à jour au lancement de l'application.", + "error-notification": { + "message": "Une erreur est survenue, impossible de modifier les paramètres de mise à jour automatique." + } } } } diff --git a/assets/jsons/translations/it.json b/assets/jsons/translations/it.json index a0e51c9c..c7e542b4 100644 --- a/assets/jsons/translations/it.json +++ b/assets/jsons/translations/it.json @@ -37,7 +37,10 @@ } }, "title-bar": { - "outdated": "obsoleta" + "outdated": "obsoleta", + "update-text": "BSManager {version} è disponibile!", + "update-button": "Aggiorna e riavvia", + "see-changelog": "Vedi i cambiamenti" }, "nav-bar": { "add-version": "Aggiungi una versione", @@ -337,6 +340,13 @@ "error-notification": { "message": "C'è stato un errore, impossibile cambiare le impostazioni del proxy di sistema." } + }, + "auto-update": { + "title": "Aggiornamento automatico", + "description": "BSManager si aggiornerà automaticamente all'avvio dell'applicazione.", + "error-notification": { + "message": "Si è verificato un errore, impossibile modificare le impostazioni di aggiornamento automatico." + } } } } diff --git a/assets/jsons/translations/ja.json b/assets/jsons/translations/ja.json index 47600eb5..d2d05150 100644 --- a/assets/jsons/translations/ja.json +++ b/assets/jsons/translations/ja.json @@ -37,7 +37,10 @@ } }, "title-bar": { - "outdated": "時代遅れ" + "outdated": "時代遅れ", + "update-text": "BSManager {version} が利用可能です!", + "update-button": "更新して再起動", + "see-changelog": "変更点を確認" }, "nav-bar": { "add-version": "バージョンを追加", @@ -337,6 +340,13 @@ "error-notification": { "message": "エラーが発生しました。システムプロキシ設定を変更できません。" } + }, + "auto-update": { + "title": "自動更新", + "description": "アプリケーション起動時にBSManagerが自動的に更新されます。", + "error-notification": { + "message": "エラーが発生し、自動更新の設定を変更できませんでした。" + } } } } diff --git a/assets/jsons/translations/ko.json b/assets/jsons/translations/ko.json index d051216d..24dc70e5 100644 --- a/assets/jsons/translations/ko.json +++ b/assets/jsons/translations/ko.json @@ -37,7 +37,10 @@ } }, "title-bar": { - "outdated": "구식" + "outdated": "구식", + "update-text": "BSManager {version} 가 사용 가능합니다!", + "update-button": "업데이트 후 재시작", + "see-changelog": "변경 사항 보기" }, "nav-bar": { "add-version": "버전 추가", @@ -337,6 +340,13 @@ "error-notification": { "message": "오류가 발생했습니다. 시스템 프록시 설정을 변경할 수 없습니다." } + }, + "auto-update": { + "title": "자동 업데이트", + "description": "애플리케이션을 실행할 때 BSManager가 자동으로 업데이트됩니다.", + "error-notification": { + "message": "오류가 발생하여 자동 업데이트 설정을 변경할 수 없습니다." + } } } } diff --git a/assets/jsons/translations/pt-br.json b/assets/jsons/translations/pt-br.json index 36a86561..76921b70 100644 --- a/assets/jsons/translations/pt-br.json +++ b/assets/jsons/translations/pt-br.json @@ -37,7 +37,10 @@ } }, "title-bar": { - "outdated": "Desatualizado" + "outdated": "Desatualizado", + "update-text": "BSManager {version} está disponível!", + "update-button": "Atualizar e reiniciar", + "see-changelog": "Ver os changelogs" }, "nav-bar": { "add-version": "Adicionar uma versão", @@ -337,6 +340,13 @@ "error-notification": { "message": "Um erro aconteceu, não foi possível alterar a configuração de proxy de sistema." } + }, + "auto-update": { + "title": "Atualização automática", + "description": "O BSManager será atualizado automaticamente ao iniciar o aplicativo.", + "error-notification": { + "message": "Ocorreu um erro, não foi possível alterar as configurações de atualização automática." + } } } } diff --git a/assets/jsons/translations/ru.json b/assets/jsons/translations/ru.json index 5ba36e17..ce71eeda 100644 --- a/assets/jsons/translations/ru.json +++ b/assets/jsons/translations/ru.json @@ -37,7 +37,10 @@ } }, "title-bar": { - "outdated": "устаревший" + "outdated": "устаревший", + "update-text": "BSManager {version} доступен!", + "update-button": "Обновить и перезапустить", + "see-changelog": "Смотреть изменения" }, "nav-bar": { "add-version": "Добавить версию игры", @@ -337,6 +340,13 @@ "error-notification": { "message": "Произошла ошибка, не удалось изменить настройки системного прокси." } + }, + "auto-update": { + "title": "Автообновление", + "description": "BSManager будет автоматически обновляться при запуске приложения.", + "error-notification": { + "message": "Произошла ошибка, не удалось изменить настройки автообновления." + } } } } diff --git a/assets/jsons/translations/tl.json b/assets/jsons/translations/tl.json index 42ea75c4..748bcd43 100644 --- a/assets/jsons/translations/tl.json +++ b/assets/jsons/translations/tl.json @@ -37,7 +37,10 @@ } }, "title-bar": { - "outdated": "lipas na" + "outdated": "lipas na", + "update-text": "Ang BSManager {version} ay available!", + "update-button": "I-update at i-restart", + "see-changelog": "Tingnan ang mga pagbabago" }, "nav-bar": { "add-version": "Magdagdag ng bersyon", @@ -337,6 +340,13 @@ "error-notification": { "message": "Nagkaroon ng error, hindi ma-bago ang setting ng system proxy." } + }, + "auto-update": { + "title": "Awtomatikong Pag-update", + "description": "Ang BSManager ay awtomatikong mag-uupdate kapag binuksan mo ang aplikasyon.", + "error-notification": { + "message": "Nagkaroon ng error, hindi mababago ang mga setting ng awtomatikong pag-update." + } } } } diff --git a/assets/jsons/translations/uk.json b/assets/jsons/translations/uk.json index 260e831c..903d9824 100644 --- a/assets/jsons/translations/uk.json +++ b/assets/jsons/translations/uk.json @@ -37,7 +37,10 @@ } }, "title-bar": { - "outdated": "застарілий" + "outdated": "застарілий", + "update-text": "BSManager {version} доступний!", + "update-button": "Оновити та перезапустити", + "see-changelog": "Переглянути зміни" }, "nav-bar": { "add-version": "Додати версію", @@ -337,6 +340,13 @@ "error-notification": { "message": "Сталася помилка, неможливо змінити налаштування системного проксі." } + }, + "auto-update": { + "title": "Автоматичне оновлення (Avtomatychne onovlennya)", + "description": "BSManager автоматично оновлюватиметься, коли ви запустите програму.", + "error-notification": { + "message": "Сталася помилка, неможливо змінити налаштування автоматичного оновлення." + } } } } diff --git a/assets/jsons/translations/zh-tw.json b/assets/jsons/translations/zh-tw.json index 6d3553f9..84985291 100644 --- a/assets/jsons/translations/zh-tw.json +++ b/assets/jsons/translations/zh-tw.json @@ -37,7 +37,10 @@ } }, "title-bar": { - "outdated": "過時" + "outdated": "過時", + "update-text": "BSManager {version} 可用!", + "update-button": "更新並重新啟動", + "see-changelog": "查看變更" }, "nav-bar": { "add-version": "新增版本", @@ -337,6 +340,13 @@ "error-notification": { "message": "發生錯誤,無法更改系統代理設置。" } + }, + "auto-update": { + "title": "自動更新", + "description": "啟動應用程式時,BSManager 將自動更新。", + "error-notification": { + "message": "發生錯誤,無法更改自動更新設定。" + } } } } diff --git a/assets/jsons/translations/zh.json b/assets/jsons/translations/zh.json index f6ee41f0..5f171a95 100644 --- a/assets/jsons/translations/zh.json +++ b/assets/jsons/translations/zh.json @@ -37,7 +37,10 @@ } }, "title-bar": { - "outdated": "过时" + "outdated": "过时", + "update-text": "BSManager {version} 可用!", + "update-button": "更新并重新启动", + "see-changelog": "查看变更" }, "nav-bar": { "add-version": "添加版本", @@ -337,6 +340,13 @@ "error-notification": { "message": "发生错误,无法启用系统代理。" } + }, + "auto-update": { + "title": "自动更新", + "description": "启动应用程序时,BSManager 将自动更新。", + "error-notification": { + "message": "发生错误,无法更改自动更新设置。" + } } } } diff --git a/docs/wiki/Guides/Login/How-to-obtain-your-Oculus-Token/How-to-obtain-your-Oculus-Token.md b/docs/wiki/Guides/Login/How-to-obtain-your-Oculus-Token/How-to-obtain-your-Oculus-Token.md index 294856ac..4d139b61 100644 --- a/docs/wiki/Guides/Login/How-to-obtain-your-Oculus-Token/How-to-obtain-your-Oculus-Token.md +++ b/docs/wiki/Guides/Login/How-to-obtain-your-Oculus-Token/How-to-obtain-your-Oculus-Token.md @@ -30,11 +30,12 @@ In the developer tools: 1. Open the __`Network`__ tab. -2. Filter for __`graph`__. -3. Click on the first request that appears. -4. Open the __`Payload`__ tab. -5. Scroll to the bottom to locate your token. It should start with __`FRL`__. -6. Select the token using your mouse and press `Ctrl` + `C` to copy it. +2. Press CTRL+R. +3. Filter for __`graph`__. +4. Click on the first request that appears. +5. Open the __`Payload`__ tab. +6. Scroll to the bottom to locate your token. It should start with __`FRL`__. +7. Select the token using your mouse and press `Ctrl` + `C` to copy it.
GetYourOculusToken.png diff --git a/package-lock.json b/package-lock.json index f1c3bf53..12a462ac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "bs-manager", - "version": "1.5.2", + "version": "1.5.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "bs-manager", - "version": "1.5.2", + "version": "1.5.3", "hasInstallScript": true, "license": "GPL-3.0-only", "dependencies": { @@ -27,7 +27,7 @@ "electron-updater": "^6.3.9", "fast-deep-equal": "^3.1.3", "format-duration": "^3.0.2", - "framer-motion": "^12.6.3", + "framer-motion": "^12.17.0", "fs-extra": "^11.3.0", "global-agent": "^3.0.0", "got": "^14.4.7", @@ -37,7 +37,7 @@ "node-abi": "^4.2.0", "node-fetch": "^3.3.2", "pako": "^2.1.0", - "protobufjs": "^7.4.0", + "protobufjs": "^7.5.3", "qrcode.react": "^4.2.0", "query-process": "^0.0.3", "react": "^18.2.0", @@ -52,7 +52,7 @@ "rfdc": "^1.4.1", "rxjs": "^7.8.2", "sanitize-filename": "^1.6.3", - "semver": "^7.7.1", + "semver": "^7.7.2", "serialize-error": "^12.0.0", "striptags": "^4.0.0-alpha.4", "tailwind-merge": "^3.0.2", @@ -105,7 +105,7 @@ "css-loader": "^6.10.0", "css-minimizer-webpack-plugin": "^6.0.0", "detect-port": "^1.5.1", - "electron": "^35.1.4", + "electron": "^36.4.0", "electron-builder": "^25.1.8", "electron-devtools-installer": "^4.0.0", "electronmon": "^2.0.3", @@ -11870,9 +11870,9 @@ } }, "node_modules/electron": { - "version": "35.1.4", - "resolved": "https://registry.npmjs.org/electron/-/electron-35.1.4.tgz", - "integrity": "sha512-8HjE2wqxY//T09Of8k1eTpK/NeTG2FkTyRD+fyKXmec4wZVscGgZcmWFC0HYN4ktyHAjtplpxdFXjtqRnvzBMg==", + "version": "36.4.0", + "resolved": "https://registry.npmjs.org/electron/-/electron-36.4.0.tgz", + "integrity": "sha512-LLOOZEuW5oqvnjC7HBQhIqjIIJAZCIFjQxltQGLfEC7XFsBoZgQ3u3iFj+Kzw68Xj97u1n57Jdt7P98qLvUibQ==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -14433,13 +14433,13 @@ } }, "node_modules/framer-motion": { - "version": "12.6.3", - "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.6.3.tgz", - "integrity": "sha512-2hsqknz23aloK85bzMc9nSR2/JP+fValQ459ZTVElFQ0xgwR2YqNjYSuDZdFBPOwVCt4Q9jgyTt6hg6sVOALzw==", + "version": "12.17.0", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.17.0.tgz", + "integrity": "sha512-2hISKgDk49yCLStwG1wf4Kdy/D6eBw9/eRNaWFIYoI9vMQ/Mqd1Fz+gzVlEtxJmtQ9y4IWnXm19/+UXD3dAYAA==", "license": "MIT", "dependencies": { - "motion-dom": "^12.6.3", - "motion-utils": "^12.6.3", + "motion-dom": "^12.17.0", + "motion-utils": "^12.12.1", "tslib": "^2.4.0" }, "peerDependencies": { @@ -19233,18 +19233,18 @@ } }, "node_modules/motion-dom": { - "version": "12.6.3", - "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.6.3.tgz", - "integrity": "sha512-gRY08RjcnzgFYLemUZ1lo/e9RkBxR+6d4BRvoeZDSeArG4XQXERSPapKl3LNQRu22Sndjf1h+iavgY0O4NrYqA==", + "version": "12.17.0", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.17.0.tgz", + "integrity": "sha512-FA6/c70R9NKs3g41XDVONzmUUrEmyaifLVGCWtAmHP0usDnX9W+RN/tmbC4EUl0w6yLGvMTOwnWCFVgA5luhRg==", "license": "MIT", "dependencies": { - "motion-utils": "^12.6.3" + "motion-utils": "^12.12.1" } }, "node_modules/motion-utils": { - "version": "12.6.3", - "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.6.3.tgz", - "integrity": "sha512-R/b3Ia2VxtTNZ4LTEO5pKYau1OUNHOuUfxuP0WFCTDYdHkeTBR9UtxR1cc8mDmKr8PEhmmfnTKGz3rSMjNRoRg==", + "version": "12.12.1", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.12.1.tgz", + "integrity": "sha512-f9qiqUHm7hWSLlNW8gS9pisnsN7CRFRD58vNjptKdsqFLpkVnX00TNeD6Q0d27V9KzT7ySFyK1TZ/DShfVOv6w==", "license": "MIT" }, "node_modules/mrmime": { @@ -21153,9 +21153,9 @@ } }, "node_modules/protobufjs": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", - "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==", + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.3.tgz", + "integrity": "sha512-sildjKwVqOI2kmFDiXQ6aEB0fjYTafpEvIBs8tOR8qI4spuL9OPROLVu2qZqi/xgCfsHIwVqlaF8JBjWFHnKbw==", "hasInstallScript": true, "license": "BSD-3-Clause", "dependencies": { @@ -22470,9 +22470,9 @@ } }, "node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "license": "ISC", "bin": { "semver": "bin/semver.js" diff --git a/package.json b/package.json index 5d4735dd..b7347f4d 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "bs-manager", "description": "Manage maps, mods and more for Beat Saber", "main": "./.erb/dll/main.bundle.dev.js", - "version": "1.5.3-alpha.1", + "version": "1.5.3", "scripts": { "build-rust-scripts": "ts-node ./.erb/scripts/build-rust-scripts.js", "build": "concurrently \"npm run build:main\" \"npm run build:renderer\"", @@ -108,7 +108,7 @@ "css-loader": "^6.10.0", "css-minimizer-webpack-plugin": "^6.0.0", "detect-port": "^1.5.1", - "electron": "^35.1.4", + "electron": "^36.4.0", "electron-builder": "^25.1.8", "electron-devtools-installer": "^4.0.0", "electronmon": "^2.0.3", @@ -170,7 +170,7 @@ "electron-updater": "^6.3.9", "fast-deep-equal": "^3.1.3", "format-duration": "^3.0.2", - "framer-motion": "^12.6.3", + "framer-motion": "^12.17.0", "fs-extra": "^11.3.0", "global-agent": "^3.0.0", "got": "^14.4.7", @@ -180,7 +180,7 @@ "node-abi": "^4.2.0", "node-fetch": "^3.3.2", "pako": "^2.1.0", - "protobufjs": "^7.4.0", + "protobufjs": "^7.5.3", "qrcode.react": "^4.2.0", "query-process": "^0.0.3", "react": "^18.2.0", @@ -195,7 +195,7 @@ "rfdc": "^1.4.1", "rxjs": "^7.8.2", "sanitize-filename": "^1.6.3", - "semver": "^7.7.1", + "semver": "^7.7.2", "serialize-error": "^12.0.0", "striptags": "^4.0.0-alpha.4", "tailwind-merge": "^3.0.2", diff --git a/release/app/package-lock.json b/release/app/package-lock.json index 6547d474..770e7ae1 100644 --- a/release/app/package-lock.json +++ b/release/app/package-lock.json @@ -1,12 +1,12 @@ { "name": "bs-manager", - "version": "1.5.3-alpha.1", + "version": "1.5.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "bs-manager", - "version": "1.5.3-alpha.1", + "version": "1.5.3", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/release/app/package.json b/release/app/package.json index 3d4f58ca..6edc5731 100644 --- a/release/app/package.json +++ b/release/app/package.json @@ -1,6 +1,6 @@ { "name": "bs-manager", - "version": "1.5.3-alpha.1", + "version": "1.5.3", "description": "BSManager", "main": "./dist/main/main.js", "author": { diff --git a/src/main/helpers/proxy.helpers.ts b/src/main/helpers/proxy.helpers.ts index 0a102b13..98175230 100644 --- a/src/main/helpers/proxy.helpers.ts +++ b/src/main/helpers/proxy.helpers.ts @@ -51,7 +51,15 @@ async function enableWindowsProxy(enable: boolean): Promise { } if (enable) { - const httpProxyUrl = `http://${await getProxyServer().catch(err => log.error(err))}` + const httpProxyServer = await getProxyServer().catch(err => log.error(err)); + let httpProxyUrl: string | null = null; + if (httpProxyServer) { + // Prepend the http protocol + httpProxyUrl = httpProxyServer.startsWith("http://") + ? httpProxyServer + : `http://${httpProxyServer}`; + } + globalProxyAgent.HTTP_PROXY = httpProxyUrl; globalProxyAgent.HTTPS_PROXY = httpProxyUrl; globalProxyAgent.NO_PROXY = `${await getProxyOverride().catch(err => log.error(err))}`; diff --git a/src/main/ipcs/launcher-ipcs.ts b/src/main/ipcs/launcher-ipcs.ts index 1091cbf7..ee92cdd2 100644 --- a/src/main/ipcs/launcher-ipcs.ts +++ b/src/main/ipcs/launcher-ipcs.ts @@ -14,6 +14,11 @@ ipc.on("check-update", (_, reply) => { reply(from(updaterService.isUpdateAvailable())); }); +ipc.on("get-available-update", (_, reply) => { + const updaterService = AutoUpdaterService.getInstance(); + reply(from(updaterService.getAvailableUpdate())); +}); + ipc.on("install-update", (_, reply) => { const updaterService = AutoUpdaterService.getInstance(); reply(of(updaterService.quitAndInstall())); diff --git a/src/main/main.ts b/src/main/main.ts index 60d4bd92..40c9e9d3 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -28,6 +28,7 @@ import { StaticConfigurationService } from "./services/static-configuration.serv import { configureProxy } from './helpers/proxy.helpers'; import { deleteFileSync, deleteFolderSync } from "./helpers/fs.helpers"; import { tryit } from "shared/helpers/error.helpers"; +import { AutoUpdate } from "shared/models/config"; const isDebug = process.env.NODE_ENV === "development" || process.env.DEBUG_PROD === "true"; const staticConfig = StaticConfigurationService.getInstance(); @@ -121,8 +122,6 @@ if (!gotTheLock) { app.whenReady().then(() => { - - app.setAppUserModelId(APP_NAME); initServicesMustBeInitialized(); @@ -134,10 +133,18 @@ if (!gotTheLock) { DeepLinkService.getInstance().dispatchLinkOpened(deepLink); } else if (associatedFile) { FileAssociationService.getInstance().handleFileAssociation(associatedFile); + } else if (process.platform === "linux") { + createWindow("index.html"); } else { - createWindow(process.platform === "linux" - ? "index.html" : "launcher.html" - ); + const configService = StaticConfigurationService.getInstance(); + const autoUpdate = configService.get("auto-update", AutoUpdate.ALWAYS); + const update = autoUpdate !== AutoUpdate.NEVER; + if (autoUpdate === AutoUpdate.ONCE) { + configService.set("auto-update", AutoUpdate.NEVER); + } + + // Skip launcher only if autoUpdate is strictly false + createWindow(update ? "launcher.html" : "index.html"); } SteamLauncherService.getInstance().restoreSteamVR(); diff --git a/src/main/services/additional-content/local-playlists-manager.service.ts b/src/main/services/additional-content/local-playlists-manager.service.ts index 7f22684b..328cf509 100644 --- a/src/main/services/additional-content/local-playlists-manager.service.ts +++ b/src/main/services/additional-content/local-playlists-manager.service.ts @@ -359,7 +359,18 @@ export class LocalPlaylistsManagerService { continue; } - const downloadedMap = await this.maps.downloadMap(mapDetail, version); + const downloadedMap = await this.maps.downloadMap(mapDetail, version) + .catch(error => { + log.error( + "Could not download map for playlist", + `[${mapDetail.id}] "${mapDetail.name}"`, + error + ); + return null; + }); + if (!downloadedMap) { + continue; + } progress.data.downloadedMaps.push(downloadedMap); progress.data.currentDownload = mapDetail; diff --git a/src/main/services/auto-updater.service.ts b/src/main/services/auto-updater.service.ts index 9a054115..fd8a6a4f 100644 --- a/src/main/services/auto-updater.service.ts +++ b/src/main/services/auto-updater.service.ts @@ -1,8 +1,9 @@ -import { autoUpdater, CancellationToken, ProgressInfo } from "electron-updater"; +import { autoUpdater, CancellationToken, ProgressInfo, UpdateInfo } from "electron-updater"; import log from "electron-log"; import { gt } from "semver"; import { Progression } from "main/helpers/fs.helpers"; import { Observable } from "rxjs"; +import { safeGt } from "shared/helpers/semver.helpers"; export class AutoUpdaterService { private static instance: AutoUpdaterService; @@ -31,6 +32,18 @@ export class AutoUpdaterService { }); } + public async getAvailableUpdate(): Promise { + return autoUpdater.checkForUpdates().then(info => { + if (info?.updateInfo && safeGt(info.updateInfo.version, autoUpdater.currentVersion.version)) { + return info.updateInfo; + } + return null; + }).catch((error: Error): UpdateInfo | null => { + log.error("Could not get update", error); + return null; + }); + } + public downloadUpdate(): Observable { return new Observable(observer => { diff --git a/src/main/services/bs-local-version.service.ts b/src/main/services/bs-local-version.service.ts index 5e73f99d..1c8d29ee 100644 --- a/src/main/services/bs-local-version.service.ts +++ b/src/main/services/bs-local-version.service.ts @@ -244,8 +244,12 @@ export class BSLocalVersionService { private async getSteamVersion(): Promise { const steamBsFolder = await this.steamService.getGameFolder(BS_APP_ID, "Beat Saber"); - if (!steamBsFolder || !(await pathExists(steamBsFolder))) { - return null; + if (!steamBsFolder) { + throw new Error("No Beat Saber Steam version found"); + } + + if (!(await pathExists(steamBsFolder))) { + throw new Error(`Beat Saber Steam version not found in "${steamBsFolder}"`); } return this.getVersionOfBSFolder(steamBsFolder, { steam: true }); @@ -265,7 +269,8 @@ export class BSLocalVersionService { const versions: BSVersion[] = []; const steamVersion = await this.getSteamVersion().catch(e => { - log.error("unable to get original Steam version", e); + log.error("Unable to get original Steam version", e); + return null; }); if (steamVersion) { diff --git a/src/main/services/linux.service.ts b/src/main/services/linux.service.ts index 7f92bed4..5f12896a 100644 --- a/src/main/services/linux.service.ts +++ b/src/main/services/linux.service.ts @@ -129,12 +129,7 @@ export class LinuxService { return fs.pathExistsSync(protonPath) && fs.pathExistsSync(winePath); } - public async getWinePath(): Promise { - if (await this.isNixOS()) { - // Use system wine for nixos - return "wine"; - } - + public getWinePath(): string { if (!this.staticConfig.has("proton-folder")) { throw new Error("proton-folder variable not set"); } diff --git a/src/main/services/mods/beat-mods-api.service.ts b/src/main/services/mods/beat-mods-api.service.ts index c0136b96..8e63f141 100644 --- a/src/main/services/mods/beat-mods-api.service.ts +++ b/src/main/services/mods/beat-mods-api.service.ts @@ -76,8 +76,7 @@ export class BeatModsApiService { } return this.requestService.getJSON<{ modVersions: BbmModVersion[] }>( - `${this.MODS_REPO_API_URL}/hashlookup?hash=${hash}`, - { silentError: true } + `${this.MODS_REPO_API_URL}/hashlookup?hash=${hash}` ).then(({ data }) => { this.updateModsHashCache(data?.modVersions ?? []); return data?.modVersions?.at(0); diff --git a/src/main/services/mods/bs-mods-manager.service.ts b/src/main/services/mods/bs-mods-manager.service.ts index 5f726093..60cc1905 100644 --- a/src/main/services/mods/bs-mods-manager.service.ts +++ b/src/main/services/mods/bs-mods-manager.service.ts @@ -162,18 +162,22 @@ export class BsModsManagerService { let winePath: string = ""; if (process.platform === "linux") { const { error: winePathError, result: winePathResult } = - await tryit(async () => this.linuxService.getWinePath()); + tryit(() => this.linuxService.getWinePath()); if (winePathError) { log.error(winePathError); return false; } - winePath = `"${winePathResult}"`; + + winePath = await this.linuxService.isNixOS() + ? `steam-run "${winePathResult}"` + : `"${winePathResult}"`; const winePrefix = this.linuxService.getWinePrefixPath(); if (!winePrefix) { throw new CustomError("Could not find BSManager WINEPREFIX path", "no-wineprefix"); } env.WINEPREFIX = winePrefix; + Object.assign(env, process.env); } return new Promise(resolve => { diff --git a/src/main/services/request.service.ts b/src/main/services/request.service.ts index 8ade56ce..863c1bd9 100644 --- a/src/main/services/request.service.ts +++ b/src/main/services/request.service.ts @@ -9,6 +9,7 @@ import { tryit } from 'shared/helpers/error.helpers'; import path from 'path'; import { pipeline } from 'stream/promises'; import sanitize from 'sanitize-filename'; +import internal from 'stream'; import { app } from 'electron'; export class RequestService { @@ -17,31 +18,55 @@ export class RequestService { 'User-Agent': `BSManager/${app.getVersion()} (Electron/${process.versions.electron} Chrome/${process.versions.chrome} Node/${process.versions.node})`, } + private readonly PREFERRED_FAMILY_TESTS = [4, 6]; + private preferredFamilyCache: Record = {}; public static getInstance(): RequestService { if (!RequestService.instance) { RequestService.instance = new RequestService(); } return RequestService.instance; - } private constructor() {} - public async getJSON(url: string, options?: { - silentError?: boolean - }): Promise<{ data: T; headers: IncomingHttpHeaders }> { - - try { - // @ts-ignore (ESM is not well supported in this project, We need to move out electron-react-boilerplate, and use Vite) - const res = await got(url, { responseType: 'json', headers: this.baseHeaders }); - return { data: res.body as T, headers: res.headers }; - } catch (err) { - if (options?.silentError !== true) { - log.error(`Failed to get JSON from URL: ${url}`, err); + public async getJSON(url: string): Promise<{ data: T; headers: IncomingHttpHeaders }> { + const domain = (new URL(url)).hostname; + const cachedFamily = this.preferredFamilyCache[domain]; + if (cachedFamily) { + try { + return await this.requestData(url, cachedFamily); + } catch (error: any) { + throw new Error(`Request failed: ${url}`, error); } - throw err; } + + // Try on each IPv4/6 families on first request to a domain/website + for (const family of this.PREFERRED_FAMILY_TESTS) { + try { + const response = await this.requestData(url, family); + log.info(`Caching "${domain}" with IPv${family}`); + this.preferredFamilyCache[domain] = family; + return response; + } catch (err) { + log.warn(`IPv${family} request failed, trying next one... URL: ${url}`, err); + } + } + + throw new Error(`IPv4 and IPv6 requests failed for URL: ${url}`); + } + + private async requestData(url: string, family: number): Promise<{ data: T; headers: IncomingHttpHeaders }> { + const response = await got(url, { + // @ts-ignore (ESM is not well supported in this project, We need to move out electron-react-boilerplate, and use Vite) + dnsLookupIpVersion: family, + responseType: "json", + headers: this.baseHeaders + }); + return { + data: response.body as T, + headers: response.headers + }; } public downloadFile( @@ -52,46 +77,69 @@ export class RequestService { return new Observable>((subscriber) => { const progress: Progression = { current: 0, total: 0 }; - let file: WriteStream | undefined; + let attempt = 0; + let stream: got.GotEmitter & internal.Duplex; - // @ts-ignore (ESM is not well supported in this project, We need to move out electron-react-boilerplate, and use Vite) - const stream = got.stream(url, { headers: this.baseHeaders }); + const domain = (new URL(url)).hostname; + const cachedFamily = this.preferredFamilyCache[domain]; + const familiesToTry = cachedFamily + ? [ cachedFamily ] : this.PREFERRED_FAMILY_TESTS; - stream.on('response', (response) => { - - const filename = opt?.preferContentDisposition ? this.getFilenameFromContentDisposition(response.headers['content-disposition']) : null; - - if (filename) { - dest = path.join(path.dirname(dest), sanitize(filename)); + const tryNextFamily = () => { + if (attempt >= familiesToTry.length) { + subscriber.error(new Error(`Download failed over IPv4 and IPv6 for URL: ${url}`)); + return; } - progress.data = dest; - file = createWriteStream(dest); + const family = familiesToTry[attempt++]; + let file: WriteStream | undefined; - pipeline(stream, file).catch(err => { - file?.destroy(); - tryit(() => deleteFileSync(dest)); - subscriber.error(err); + // @ts-ignore (ESM is not well supported in this project, We need to move out electron-react-boilerplate, and use Vite) + stream = got.stream(url, { dnsLookupIpVersion: family, headers: this.baseHeaders }); + + stream.on('response', (response) => { + if (!cachedFamily) { + log.info(`Caching "${domain}" with IPv${family}`); + this.preferredFamilyCache[domain] = family; + } + + const filename = opt?.preferContentDisposition ? this.getFilenameFromContentDisposition(response.headers['content-disposition']) : null; + + if (filename) { + dest = path.join(path.dirname(dest), sanitize(filename)); + } + + progress.data = dest; + file = createWriteStream(dest); + + pipeline(stream, file).catch(err => { + file?.destroy(); + tryit(() => deleteFileSync(dest)); + subscriber.error(err); + }); }); - }); - stream.on('downloadProgress', ({ transferred, total }) => { - progress.current = transferred; - progress.total = total; - subscriber.next(progress); - }); + stream.on('downloadProgress', ({ transferred, total }) => { + progress.current = transferred; + progress.total = total; + subscriber.next(progress); + }); - stream.on('error', err => { - log.error(`Download failed for URL: ${url}`, err); - stream.destroy(); - file?.destroy(); - }); + stream.on('error', err => { + log.warn(`Download failed over IPv${family} for URL: ${url}`, err); + stream.destroy(); + file?.destroy(); + tryNextFamily(); + }); - stream.on('end', () => { - file?.end(); - subscriber.next(progress); - subscriber.complete(); - }); + stream.on('end', () => { + file?.end(); + subscriber.next(progress); + subscriber.complete(); + }); + }; + + tryNextFamily(); return () => { stream?.destroy(); @@ -106,7 +154,6 @@ export class RequestService { url: string, options?: got.GotOptions ): Observable> { - return new Observable>((subscriber) => { const progress: Progression = { current: 0, @@ -116,37 +163,60 @@ export class RequestService { const headers = { ...this.baseHeaders, ...(options?.headers ?? {}) }; - // @ts-ignore (ESM is not well supported in this project, We need to move out electron-react-boilerplate, and use Vite) - const stream = got.stream(url, { ...(options ?? {}), headers }); + let attempt = 0; + let stream: got.GotEmitter & internal.Duplex; - let data = Buffer.alloc(0); - let response: IncomingMessage; + const domain = (new URL(url)).hostname; + const cachedFamily = this.preferredFamilyCache[domain]; + const familiesToTry = cachedFamily + ? [ cachedFamily ] : this.PREFERRED_FAMILY_TESTS; - stream.once('response', (res) => { - response = res; - }); + const tryNextFamily = () => { + if (attempt >= familiesToTry.length) { + subscriber.error(new Error(`Download failed over IPv4 and IPv6 for URL: ${url}`)); + return; + } - stream.on('data', (chunk: Buffer) => { - data = Buffer.concat([data, chunk]); - }); + const family = familiesToTry[attempt++]; + // @ts-ignore (ESM is not well supported in this project, We need to move out electron-react-boilerplate, and use Vite) + stream = got.stream(url, { dnsLookupIpVersion: family, ...(options ?? {}), headers }); - stream.on('downloadProgress', ({ transferred, total }) => { - progress.current = transferred; - progress.total = total; - subscriber.next(progress); - }); + let data = Buffer.alloc(0); + let response: IncomingMessage; - stream.on('error', err => { - log.error(`Download failed for URL: ${url}`, err); - stream.destroy(); - }); + stream.once('response', (res) => { + if (!cachedFamily) { + log.info(`Caching "${domain}" with IPv${family}`); + this.preferredFamilyCache[domain] = family; + } + response = res; + }); - stream.on('end', () => { - progress.data = data; - progress.extra = response; - subscriber.next(progress); - subscriber.complete(); - }); + stream.on('data', (chunk: Buffer) => { + data = Buffer.concat([data, chunk]); + }); + + stream.on('downloadProgress', ({ transferred, total }) => { + progress.current = transferred; + progress.total = total; + subscriber.next(progress); + }); + + stream.on('error', err => { + log.warn(`Download failed over IPv${family} for URL: ${url}`, err); + stream.destroy(); + tryNextFamily(); + }); + + stream.on('end', () => { + progress.data = data; + progress.extra = response; + subscriber.next(progress); + subscriber.complete(); + }); + }; + + tryNextFamily(); return () => { stream?.destroy(); diff --git a/src/main/services/static-configuration.service.ts b/src/main/services/static-configuration.service.ts index 47e99245..4244f5a2 100644 --- a/src/main/services/static-configuration.service.ts +++ b/src/main/services/static-configuration.service.ts @@ -5,6 +5,7 @@ import { PROTON_BINARY_PREFIX, WINE_BINARY_PREFIX } from "main/constants"; import { Observable, Subject } from "rxjs"; import { CustomError } from "shared/models/exceptions/custom-error.class"; import { BSVersion } from "shared/bs-version.interface"; +import { AutoUpdate } from "shared/models/config"; export class StaticConfigurationService { private static instance: StaticConfigurationService; @@ -30,8 +31,8 @@ export class StaticConfigurationService { return this.store.has(key); } - public get(key: K): StaticConfigKeyValues[K] { - return this.store.get(key) as StaticConfigKeyValues[K]; + public get(key: K, defaultValue?: StaticConfigKeyValues[K]): StaticConfigKeyValues[K] { + return this.store.get(key, defaultValue) as StaticConfigKeyValues[K]; } public take(key: K, cb: (val: StaticConfigKeyValues[K]) => void): void { @@ -90,6 +91,7 @@ export interface StaticConfigKeyValues { "use-symlinks": boolean; "use-system-proxy": boolean; "last-version-launched": BSVersion; + "auto-update": AutoUpdate; // Linux Specific static configs "proton-folder": string; diff --git a/src/renderer/components/maps-playlists-panel/maps/local-maps-list-panel.component.tsx b/src/renderer/components/maps-playlists-panel/maps/local-maps-list-panel.component.tsx index 39b670c4..d7e4f7a8 100644 --- a/src/renderer/components/maps-playlists-panel/maps/local-maps-list-panel.component.tsx +++ b/src/renderer/components/maps-playlists-panel/maps/local-maps-list-panel.component.tsx @@ -249,7 +249,7 @@ export const LocalMapsListPanel = forwardRef(({ return ( = ({resolver, options: {data}}) => { @@ -26,10 +25,6 @@ export const CreateLaunchShortcutModal: ModalComponent<{ steamShortcut: boolean, const [command, setCommand] = useState(launchOption.command || ""); const [steamShortcut, setSteamShortcut] = useState(false); - const isSteamVersion = useMemo(() => { - return data.steam || data.metadata?.store === BsStore.STEAM; - }, [data]); - const completeModal = () => { launchOption.command = command.trim(); resolver({exitCode: ModalExitCode.COMPLETED, data: { launchOption, steamShortcut }}); @@ -96,17 +91,15 @@ export const CreateLaunchShortcutModal: ModalComponent<{ steamShortcut: boolean,
- {isSteamVersion && ( - -
- setSteamShortcut(() => e)} /> - {t("modals.create-launch-shortcut.create-steam-shortcut")} -
-
- )} -
- resolver({ exitCode: ModalExitCode.CANCELED })} withBar={false} text="misc.cancel" /> - + +
+ setSteamShortcut(() => e)} /> + {t("modals.create-launch-shortcut.create-steam-shortcut")} +
+
+
+ resolver({ exitCode: ModalExitCode.CANCELED })} withBar={false} text="misc.cancel" /> +
) diff --git a/src/renderer/components/settings/setting-radio-array.component.tsx b/src/renderer/components/settings/setting-radio-array.component.tsx index 1a7f2e60..05f1d083 100644 --- a/src/renderer/components/settings/setting-radio-array.component.tsx +++ b/src/renderer/components/settings/setting-radio-array.component.tsx @@ -8,10 +8,11 @@ type Props = { selectedItemId?: number; selectedItemValue?: T; direction?: CSSProperties["flexDirection"], + columnCount?: number; onItemSelected?: (item: RadioItem) => void }; -export function SettingRadioArray({ id, items, selectedItemId, selectedItemValue, onItemSelected, direction = "column" }: Props) { +export function SettingRadioArray({ id, items, selectedItemId, selectedItemValue, onItemSelected, direction = "column",columnCount = 1, }: Props) { const t = useTranslation(); const isSelected = (item: RadioItem) => { @@ -19,17 +20,17 @@ export function SettingRadioArray({ id, items, selectedItemId, selectedItemVa } return ( -
+
{items.map(i => ( -
onItemSelected(i)} key={i.id} className={`py-3 w-full flex cursor-pointer justify-between items-center rounded-md px-2 transition-colors duration-300 ${isSelected(i) ? "bg-light-main-color-3 dark:bg-main-color-3" : "bg-light-main-color-1 dark:bg-main-color-1"} ${i.className}`}> +
onItemSelected(i)} key={i.id} className={`h-12 py-3 w-full flex cursor-pointer justify-between rounded-md px-2 transition-colors duration-300 ${isSelected(i) ? "bg-light-main-color-3 dark:bg-main-color-3" : "bg-light-main-color-1 dark:bg-main-color-1"} ${i.className}`}>
-

{t(i.text)}

+

{t(i.text)}

{i.icon && ( -
+
{i.textIcon && {t(i.textIcon)}} {i.icon}
diff --git a/src/renderer/components/title-bar/title-bar.component.tsx b/src/renderer/components/title-bar/title-bar.component.tsx index b17647e6..73ef4401 100644 --- a/src/renderer/components/title-bar/title-bar.component.tsx +++ b/src/renderer/components/title-bar/title-bar.component.tsx @@ -12,36 +12,47 @@ import { useService } from "renderer/hooks/use-service.hook"; import { lastValueFrom } from "rxjs"; import { useWindowControls } from "renderer/hooks/use-window-controls.hook"; import { useTranslationV2 } from "renderer/hooks/use-translation.hook"; +import Tippy from "@tippyjs/react"; +import { StaticConfigurationService } from "renderer/services/static-configuration.service"; +import { AutoUpdate } from "shared/models/config"; +import { UpdateInfo } from "electron-updater"; -function TitleBarTags() { +function useVersion() { const ipcService = useService(IpcService); - const t = useTranslationV2(); - const [previewVersion, setPreviewVersion] = useState(""); - const [outdated, setOutdated] = useState(false); + const [currentVersion, setCurrentVersion] = useState(""); + const [latestVersion, setLatestVersion] = useState(null); useEffect(() => { - const requests: Promise[] = [ - lastValueFrom(ipcService.sendV2("current-version")) - ]; - if (window.electron.platform === "linux") { - requests.push(lastValueFrom(ipcService.sendV2("check-update"))); - } - - Promise.all(requests).then(([ currentVersion, outdated ]) => { - handlePrerelease(currentVersion); - setOutdated(outdated); + Promise.all([ + lastValueFrom(ipcService.sendV2("current-version")), + lastValueFrom(ipcService.sendV2("get-available-update")) + ]).then(([ currentVersion, latestVersion ]) => { + setCurrentVersion(currentVersion); + setLatestVersion(latestVersion); }); }, []); - const handlePrerelease = (version: string) => { + return { currentVersion, latestVersion }; +} + +function TitleBarTags({ version, latestVersion }: Readonly<{ + version: string; + latestVersion: UpdateInfo | null; +}>) { + const t = useTranslationV2(); + + const previewVersion = (() => { if (version.toLowerCase().includes("alpha")) { - return setPreviewVersion("ALPHA"); + return "ALPHA"; } + if (version.toLowerCase().includes("beta")) { - return setPreviewVersion("BETA"); + return "BETA"; } - } + + return ""; + })(); return <> {previewVersion && @@ -49,7 +60,7 @@ function TitleBarTags() { {previewVersion} } - {outdated && + {latestVersion && {t.text("title-bar.outdated")} @@ -57,12 +68,65 @@ function TitleBarTags() { ; } +function AutoUpdateButton({ latestVersion }: Readonly<{ + latestVersion: UpdateInfo | null; +}>) { + const configService = useService(StaticConfigurationService); + const ipcService = useService(IpcService); + const { text: t } = useTranslationV2(); + + const isLinux = window.electron.platform === "linux"; + + const updateAndRestart = async () => { + await configService.set("auto-update", AutoUpdate.ONCE); + await lastValueFrom(ipcService.sendV2("restart-app")); + }; + + + const renderTippyContent = () => { + return ( +
+
{t("title-bar.update-text", { version: latestVersion.version })}
+ {latestVersion?.version ? ( + {t("title-bar.see-changelog")} + ) : null} + {!isLinux && + updateAndRestart()} + /> + } +
+ ) + } + + return latestVersion && ( + + + + ); +} + export default function TitleBar({ template = "index.html" }: { template: AppWindow }) { const audio = useService(AudioPlayerService); const windowControls = useWindowControls(); const volume = useObservable(() => audio.volume$, audio.volume); const color = useThemeColor("first-color"); + const { currentVersion, latestVersion } = useVersion(); const [maximized, setMaximized] = useState(false); @@ -107,7 +171,7 @@ export default function TitleBar({ template = "index.html" }: { template: AppWin
BSManager - +
@@ -117,6 +181,7 @@ export default function TitleBar({ template = "index.html" }: { template: AppWin
audio.toggleMute()} />
+