[feature] can now launch beat saber from shortcut

This commit is contained in:
MathieuG-P
2023-07-14 14:18:23 +02:00
parent 8a7ffdc264
commit 2f3b5bf610
32 changed files with 2119 additions and 347 deletions
@@ -152,6 +152,7 @@ const configuration: webpack.Configuration = {
new HtmlWebpackPlugin(getHtmlPageOptions("oneclick-download-map.html")),
new HtmlWebpackPlugin(getHtmlPageOptions("oneclick-download-playlist.html")),
new HtmlWebpackPlugin(getHtmlPageOptions("oneclick-download-model.html")),
new HtmlWebpackPlugin(getHtmlPageOptions("shortcut-launch.html")),
],
node: {
@@ -146,6 +146,7 @@ const configuration: webpack.Configuration = {
new HtmlWebpackPlugin(getHtmlPageOptions("oneclick-download-map.html")),
new HtmlWebpackPlugin(getHtmlPageOptions("oneclick-download-playlist.html")),
new HtmlWebpackPlugin(getHtmlPageOptions("oneclick-download-model.html")),
new HtmlWebpackPlugin(getHtmlPageOptions("shortcut-launch.html")),
],
};
+14
View File
@@ -861,6 +861,20 @@
"checking": "Prüfe auf Updates",
"downloading": "Lade Updates Herunter"
},
"bs-shortcut-launch": {
"beat-saber-launching": "Start von Beat Saber",
"launching": "Starten",
"open-bsmanager": "BSManager öffnen",
"status-text": {
"init": "Initialisierung...",
"success": {
"BS_LAUNCHING": "Start von Beat Saber...",
"STEAM_LAUNCHING": "Start von Steam...",
"STEAM_LAUNCHED": "Steam erfolgreich gestartet!",
"UNABLE_TO_LAUNCH_STEAM": "Steam kann nicht gestartet werden, erzwungener Start von Beat Saber..."
}
}
},
"dateformat": {
"dayNames": ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa", "Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag"],
"monthNames": ["Jan", "Feb", "Mär", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez", "Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"],
+14
View File
@@ -861,6 +861,20 @@
"checking": "Checking for updates",
"downloading": "Downloading updates"
},
"bs-shortcut-launch": {
"beat-saber-launching": "Launching Beat Saber",
"launching": "Launching",
"open-bsmanager": "Open BSManager",
"status-text": {
"init": "Initializing...",
"success": {
"BS_LAUNCHING": "Launching Beat Saber...",
"STEAM_LAUNCHING": "Launching Steam...",
"STEAM_LAUNCHED": "Steam successfully launched!",
"UNABLE_TO_LAUNCH_STEAM": "Unable to launch Steam, forcing Beat Saber launch..."
}
}
},
"dateformat": {
"dayNames": ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
"monthNames": ["Jan", "Feb", "Mar", "Apr", "May", "June", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec", "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
+14
View File
@@ -860,6 +860,20 @@
"checking": "Comprobar las actualizaciones",
"downloading": "Descargar actualizaciones"
},
"bs-shortcut-launch": {
"beat-saber-launching": "Iniciando Beat Saber",
"launching": "Iniciando",
"open-bsmanager": "Abrir BSManager",
"status-text": {
"init": "Inicializando...",
"success": {
"BS_LAUNCHING": "Iniciando Beat Saber...",
"STEAM_LAUNCHING": "Iniciando Steam...",
"STEAM_LAUNCHED": "¡Steam iniciado exitosamente!",
"UNABLE_TO_LAUNCH_STEAM": "Incapaz de iniciar Steam, forzando el inicio de Beat Saber..."
}
}
},
"dateformat": {
"dayNames": ["Dom", "Lun", "Mar", "Mié", "Jue", "Vie", "Sáb", "Domingo", "Lunes", "Martes", "Miércoles", "Jueves", "Viernes", "Sábado"],
"monthNames": ["Ene", "Feb", "Mar", "Abr", "May", "Jun", "Jul", "Ago", "Sep", "Oct", "Nov", "Dic", "Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio", "Julio", "Agosto", "Septiembre", "Octubre", "Noviembre", "Diciembre"],
+14
View File
@@ -860,6 +860,20 @@
"checking": "Vérification des mises à jour",
"downloading": "Téléchargement des mises à jour"
},
"bs-shortcut-launch": {
"beat-saber-launching": "Lancement de Beat Saber",
"launching": "Lancement",
"open-bsmanager": "Ouvrir BSManager",
"status-text": {
"init": "Initialisation...",
"success": {
"BS_LAUNCHING": "Lancement de Beat Saber...",
"STEAM_LAUNCHING": "Lancement de Steam...",
"STEAM_LAUNCHED": "Steam lancé avec succès !",
"UNABLE_TO_LAUNCH_STEAM": "Impossible de lancer Steam, lancement forcé de Beat Saber..."
}
}
},
"dateformat": {
"dayNames": ["Dim", "Lun", "Mar", "Mer", "Jeu", "Ven", "Sam", "Dimanche", "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi"],
"monthNames": ["Jan", "Fév", "Mar", "Avr", "Mai", "Jun", "Juil", "Aou", "Sept", "Oct", "Nov", "Déc", "Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet", "Août", "Septembre", "Octobre", "Novembre", "Décembre"],
+1322 -190
View File
File diff suppressed because it is too large Load Diff
+3 -1
View File
@@ -146,6 +146,7 @@
]
},
"devDependencies": {
"@electron/rebuild": "^3.2.13",
"@pmmmwh/react-refresh-webpack-plugin": "0.5.5",
"@teamsupercell/typings-for-css-modules-loader": "^2.5.1",
"@testing-library/jest-dom": "^5.16.5",
@@ -164,6 +165,7 @@
"@types/react-window": "^1.8.5",
"@types/recursive-readdir": "^2.2.1",
"@types/terser-webpack-plugin": "^5.0.4",
"@types/to-ico": "^1.1.1",
"@types/use-double-click": "^1.0.1",
"@types/webpack-bundle-analyzer": "^4.4.2",
"@types/webpack-env": "^1.18.0",
@@ -182,7 +184,6 @@
"electron-builder": "^24.4.0",
"electron-devtools-installer": "^3.2.0",
"electron-notarize": "^1.2.1",
"electron-rebuild": "^3.2.9",
"electronmon": "^2.0.2",
"eslint": "^8.22.0",
"eslint-config-airbnb-base": "^15.0.0",
@@ -266,6 +267,7 @@
"tailwind-scrollbar-hide": "^1.1.7",
"tailwindcss-scoped-groups": "^2.0.0",
"tippy.js": "^6.3.7",
"to-ico": "^1.1.5",
"use-double-click": "^1.0.5",
"use-fit-text": "^2.4.0"
},
+305 -11
View File
@@ -10,10 +10,213 @@
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"@resvg/resvg-js": "^2.4.1",
"ctrlc-windows": "^2.1.0",
"ps-list": "^7.2.0"
}
},
"node_modules/@resvg/resvg-js": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/@resvg/resvg-js/-/resvg-js-2.4.1.tgz",
"integrity": "sha512-wTOf1zerZX8qYcMmLZw3czR4paI4hXqPjShNwJRh5DeHxvgffUS5KM7XwxtbIheUW6LVYT5fhT2AJiP6mU7U4A==",
"engines": {
"node": ">= 10"
},
"optionalDependencies": {
"@resvg/resvg-js-android-arm-eabi": "2.4.1",
"@resvg/resvg-js-android-arm64": "2.4.1",
"@resvg/resvg-js-darwin-arm64": "2.4.1",
"@resvg/resvg-js-darwin-x64": "2.4.1",
"@resvg/resvg-js-linux-arm-gnueabihf": "2.4.1",
"@resvg/resvg-js-linux-arm64-gnu": "2.4.1",
"@resvg/resvg-js-linux-arm64-musl": "2.4.1",
"@resvg/resvg-js-linux-x64-gnu": "2.4.1",
"@resvg/resvg-js-linux-x64-musl": "2.4.1",
"@resvg/resvg-js-win32-arm64-msvc": "2.4.1",
"@resvg/resvg-js-win32-ia32-msvc": "2.4.1",
"@resvg/resvg-js-win32-x64-msvc": "2.4.1"
}
},
"node_modules/@resvg/resvg-js-android-arm-eabi": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/@resvg/resvg-js-android-arm-eabi/-/resvg-js-android-arm-eabi-2.4.1.tgz",
"integrity": "sha512-AA6f7hS0FAPpvQMhBCf6f1oD1LdlqNXKCxAAPpKh6tR11kqV0YIB9zOlIYgITM14mq2YooLFl6XIbbvmY+jwUw==",
"cpu": [
"arm"
],
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@resvg/resvg-js-android-arm64": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/@resvg/resvg-js-android-arm64/-/resvg-js-android-arm64-2.4.1.tgz",
"integrity": "sha512-/QleoRdPfsEuH9jUjilYcDtKK/BkmWcK+1LXM8L2nsnf/CI8EnFyv7ZzCj4xAIvZGAy9dTYr/5NZBcTwxG2HQg==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@resvg/resvg-js-darwin-arm64": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/@resvg/resvg-js-darwin-arm64/-/resvg-js-darwin-arm64-2.4.1.tgz",
"integrity": "sha512-U1oMNhea+kAXgiEXgzo7EbFGCD1Edq5aSlQoe6LMly6UjHzgx2W3N5kEXCwU/CgN5FiQhZr7PlSJSlcr7mdhfg==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@resvg/resvg-js-darwin-x64": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/@resvg/resvg-js-darwin-x64/-/resvg-js-darwin-x64-2.4.1.tgz",
"integrity": "sha512-avyVh6DpebBfHHtTQTZYSr6NG1Ur6TEilk1+H0n7V+g4F7x7WPOo8zL00ZhQCeRQ5H4f8WXNWIEKL8fwqcOkYw==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@resvg/resvg-js-linux-arm-gnueabihf": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm-gnueabihf/-/resvg-js-linux-arm-gnueabihf-2.4.1.tgz",
"integrity": "sha512-isY/mdKoBWH4VB5v621co+8l101jxxYjuTkwOLsbW+5RK9EbLciPlCB02M99ThAHzI2MYxIUjXNmNgOW8btXvw==",
"cpu": [
"arm"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@resvg/resvg-js-linux-arm64-gnu": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm64-gnu/-/resvg-js-linux-arm64-gnu-2.4.1.tgz",
"integrity": "sha512-uY5voSCrFI8TH95vIYBm5blpkOtltLxLRODyhKJhGfskOI7XkRw5/t1u0sWAGYD8rRSNX+CA+np86otKjubrNg==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@resvg/resvg-js-linux-arm64-musl": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm64-musl/-/resvg-js-linux-arm64-musl-2.4.1.tgz",
"integrity": "sha512-6mT0+JBCsermKMdi/O2mMk3m7SqOjwi9TKAwSngRZ/nQoL3Z0Z5zV+572ztgbWr0GODB422uD8e9R9zzz38dRQ==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@resvg/resvg-js-linux-x64-gnu": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-x64-gnu/-/resvg-js-linux-x64-gnu-2.4.1.tgz",
"integrity": "sha512-60KnrscLj6VGhkYOJEmmzPlqqfcw1keDh6U+vMcNDjPhV3B5vRSkpP/D/a8sfokyeh4VEacPSYkWGezvzS2/mg==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@resvg/resvg-js-linux-x64-musl": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-x64-musl/-/resvg-js-linux-x64-musl-2.4.1.tgz",
"integrity": "sha512-0AMyZSICC1D7ge115cOZQW8Pcad6PjWuZkBFF3FJuSxC6Dgok0MQnLTs2MfMdKBlAcwO9dXsf3bv9tJZj8pATA==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@resvg/resvg-js-win32-arm64-msvc": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-arm64-msvc/-/resvg-js-win32-arm64-msvc-2.4.1.tgz",
"integrity": "sha512-76XDFOFSa3d0QotmcNyChh2xHwk+JTFiEQBVxMlHpHMeq7hNrQJ1IpE1zcHSQvrckvkdfLboKRrlGB86B10Qjw==",
"cpu": [
"arm64"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@resvg/resvg-js-win32-ia32-msvc": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-ia32-msvc/-/resvg-js-win32-ia32-msvc-2.4.1.tgz",
"integrity": "sha512-odyVFGrEWZIzzJ89KdaFtiYWaIJh9hJRW/frcEcG3agJ464VXkN/2oEVF5ulD+5mpGlug9qJg7htzHcKxDN8sg==",
"cpu": [
"ia32"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@resvg/resvg-js-win32-x64-msvc": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-x64-msvc/-/resvg-js-win32-x64-msvc-2.4.1.tgz",
"integrity": "sha512-vY4kTLH2S3bP+puU5x7hlAxHv+ulFgcK6Zn3efKSr0M0KnZ9A3qeAjZteIpkowEFfUeMPNg2dvvoFRJA9zqxSw==",
"cpu": [
"x64"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/ctrlc-windows": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/ctrlc-windows/-/ctrlc-windows-2.1.0.tgz",
@@ -31,16 +234,107 @@
}
}
},
"dependencies": {
"ctrlc-windows": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/ctrlc-windows/-/ctrlc-windows-2.1.0.tgz",
"integrity": "sha512-OrX5KI+K+2NMN91QIhYZdW7VDO2YsSdTZW494pA7Nvw/wBdU2hz+MGP006bR978zOTrG6Q8EIeJvLJmLqc6MsQ=="
},
"ps-list": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/ps-list/-/ps-list-7.2.0.tgz",
"integrity": "sha512-v4Bl6I3f2kJfr5o80ShABNHAokIgY+wFDTQfE+X3zWYgSGQOCBeYptLZUpoOALBqO5EawmDN/tjTldJesd0ujQ=="
}
"dependencies": {
"@resvg/resvg-js": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/@resvg/resvg-js/-/resvg-js-2.4.1.tgz",
"integrity": "sha512-wTOf1zerZX8qYcMmLZw3czR4paI4hXqPjShNwJRh5DeHxvgffUS5KM7XwxtbIheUW6LVYT5fhT2AJiP6mU7U4A==",
"requires": {
"@resvg/resvg-js-android-arm-eabi": "2.4.1",
"@resvg/resvg-js-android-arm64": "2.4.1",
"@resvg/resvg-js-darwin-arm64": "2.4.1",
"@resvg/resvg-js-darwin-x64": "2.4.1",
"@resvg/resvg-js-linux-arm-gnueabihf": "2.4.1",
"@resvg/resvg-js-linux-arm64-gnu": "2.4.1",
"@resvg/resvg-js-linux-arm64-musl": "2.4.1",
"@resvg/resvg-js-linux-x64-gnu": "2.4.1",
"@resvg/resvg-js-linux-x64-musl": "2.4.1",
"@resvg/resvg-js-win32-arm64-msvc": "2.4.1",
"@resvg/resvg-js-win32-ia32-msvc": "2.4.1",
"@resvg/resvg-js-win32-x64-msvc": "2.4.1"
}
},
"@resvg/resvg-js-android-arm-eabi": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/@resvg/resvg-js-android-arm-eabi/-/resvg-js-android-arm-eabi-2.4.1.tgz",
"integrity": "sha512-AA6f7hS0FAPpvQMhBCf6f1oD1LdlqNXKCxAAPpKh6tR11kqV0YIB9zOlIYgITM14mq2YooLFl6XIbbvmY+jwUw==",
"optional": true
},
"@resvg/resvg-js-android-arm64": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/@resvg/resvg-js-android-arm64/-/resvg-js-android-arm64-2.4.1.tgz",
"integrity": "sha512-/QleoRdPfsEuH9jUjilYcDtKK/BkmWcK+1LXM8L2nsnf/CI8EnFyv7ZzCj4xAIvZGAy9dTYr/5NZBcTwxG2HQg==",
"optional": true
},
"@resvg/resvg-js-darwin-arm64": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/@resvg/resvg-js-darwin-arm64/-/resvg-js-darwin-arm64-2.4.1.tgz",
"integrity": "sha512-U1oMNhea+kAXgiEXgzo7EbFGCD1Edq5aSlQoe6LMly6UjHzgx2W3N5kEXCwU/CgN5FiQhZr7PlSJSlcr7mdhfg==",
"optional": true
},
"@resvg/resvg-js-darwin-x64": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/@resvg/resvg-js-darwin-x64/-/resvg-js-darwin-x64-2.4.1.tgz",
"integrity": "sha512-avyVh6DpebBfHHtTQTZYSr6NG1Ur6TEilk1+H0n7V+g4F7x7WPOo8zL00ZhQCeRQ5H4f8WXNWIEKL8fwqcOkYw==",
"optional": true
},
"@resvg/resvg-js-linux-arm-gnueabihf": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm-gnueabihf/-/resvg-js-linux-arm-gnueabihf-2.4.1.tgz",
"integrity": "sha512-isY/mdKoBWH4VB5v621co+8l101jxxYjuTkwOLsbW+5RK9EbLciPlCB02M99ThAHzI2MYxIUjXNmNgOW8btXvw==",
"optional": true
},
"@resvg/resvg-js-linux-arm64-gnu": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm64-gnu/-/resvg-js-linux-arm64-gnu-2.4.1.tgz",
"integrity": "sha512-uY5voSCrFI8TH95vIYBm5blpkOtltLxLRODyhKJhGfskOI7XkRw5/t1u0sWAGYD8rRSNX+CA+np86otKjubrNg==",
"optional": true
},
"@resvg/resvg-js-linux-arm64-musl": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-arm64-musl/-/resvg-js-linux-arm64-musl-2.4.1.tgz",
"integrity": "sha512-6mT0+JBCsermKMdi/O2mMk3m7SqOjwi9TKAwSngRZ/nQoL3Z0Z5zV+572ztgbWr0GODB422uD8e9R9zzz38dRQ==",
"optional": true
},
"@resvg/resvg-js-linux-x64-gnu": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-x64-gnu/-/resvg-js-linux-x64-gnu-2.4.1.tgz",
"integrity": "sha512-60KnrscLj6VGhkYOJEmmzPlqqfcw1keDh6U+vMcNDjPhV3B5vRSkpP/D/a8sfokyeh4VEacPSYkWGezvzS2/mg==",
"optional": true
},
"@resvg/resvg-js-linux-x64-musl": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/@resvg/resvg-js-linux-x64-musl/-/resvg-js-linux-x64-musl-2.4.1.tgz",
"integrity": "sha512-0AMyZSICC1D7ge115cOZQW8Pcad6PjWuZkBFF3FJuSxC6Dgok0MQnLTs2MfMdKBlAcwO9dXsf3bv9tJZj8pATA==",
"optional": true
},
"@resvg/resvg-js-win32-arm64-msvc": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-arm64-msvc/-/resvg-js-win32-arm64-msvc-2.4.1.tgz",
"integrity": "sha512-76XDFOFSa3d0QotmcNyChh2xHwk+JTFiEQBVxMlHpHMeq7hNrQJ1IpE1zcHSQvrckvkdfLboKRrlGB86B10Qjw==",
"optional": true
},
"@resvg/resvg-js-win32-ia32-msvc": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-ia32-msvc/-/resvg-js-win32-ia32-msvc-2.4.1.tgz",
"integrity": "sha512-odyVFGrEWZIzzJ89KdaFtiYWaIJh9hJRW/frcEcG3agJ464VXkN/2oEVF5ulD+5mpGlug9qJg7htzHcKxDN8sg==",
"optional": true
},
"@resvg/resvg-js-win32-x64-msvc": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/@resvg/resvg-js-win32-x64-msvc/-/resvg-js-win32-x64-msvc-2.4.1.tgz",
"integrity": "sha512-vY4kTLH2S3bP+puU5x7hlAxHv+ulFgcK6Zn3efKSr0M0KnZ9A3qeAjZteIpkowEFfUeMPNg2dvvoFRJA9zqxSw==",
"optional": true
},
"ctrlc-windows": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/ctrlc-windows/-/ctrlc-windows-2.1.0.tgz",
"integrity": "sha512-OrX5KI+K+2NMN91QIhYZdW7VDO2YsSdTZW494pA7Nvw/wBdU2hz+MGP006bR978zOTrG6Q8EIeJvLJmLqc6MsQ=="
},
"ps-list": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/ps-list/-/ps-list-7.2.0.tgz",
"integrity": "sha512-v4Bl6I3f2kJfr5o80ShABNHAokIgY+wFDTQfE+X3zWYgSGQOCBeYptLZUpoOALBqO5EawmDN/tjTldJesd0ujQ=="
}
}
}
+1
View File
@@ -14,6 +14,7 @@
"postinstall": "npm run electron-rebuild && npm run link-modules"
},
"dependencies": {
"@resvg/resvg-js": "^2.4.1",
"ctrlc-windows": "^2.1.0",
"ps-list": "^7.2.0"
},
+6
View File
@@ -1,3 +1,6 @@
import { app } from "electron";
import path from "path";
export const BS_EXECUTABLE = "Beat Saber.exe";
export const OCULUS_BS_DIR = "hyperbolic-magnetism-beat-saber";
export const BS_APP_ID = "620980";
@@ -5,3 +8,6 @@ export const BS_DEPOT = "620981";
export const APP_NAME = "BSManager";
export const STEAMVR_APP_ID = "250820";
export const IMAGE_CACHE_FOLDER = "imagescache";
export const IMAGE_CACHE_PATH = path.join(path.dirname(app.getPath("exe")), IMAGE_CACHE_FOLDER);
+13
View File
@@ -0,0 +1,13 @@
import { ProviderPlatform } from "shared/models/provider-platform.enum";
export function execOnOs<T>(executions: { [key in ProviderPlatform]?: () => T }, noError = false): T {
if(executions[process.platform as ProviderPlatform]) {
return executions[process.platform as ProviderPlatform]();
}
if(!noError) {
throw new Error(`No execution found for platform ${process.platform}`);
}
return undefined;
}
+8
View File
@@ -3,8 +3,11 @@ import { WindowManagerService } from "../services/window-manager.service";
import { IpcRequest } from "shared/models/ipc";
import { AppWindow } from "shared/models/window-manager/app-window.model";
import { BSLauncherService } from "../services/bs-launcher.service";
import { IpcService } from "../services/ipc.service";
import { from } from "rxjs";
const launcher = BSLauncherService.getInstance();
const ipc = IpcService.getInstance();
ipcMain.on("open-window-then-close-all", async (event, request: IpcRequest<AppWindow>) => {
const windowManager = WindowManagerService.getInstance();
@@ -25,3 +28,8 @@ ipcMain.on("close-windows", async (event, request: IpcRequest<AppWindow[]>) => {
const windowManager = WindowManagerService.getInstance();
windowManager.close(...request.args);
});
ipc.on<AppWindow>("open-window-or-focus", (req, reply) => {
const windowManager = WindowManagerService.getInstance();
reply(from(windowManager.openWindowOrFocus(req.args)));
});
+6 -2
View File
@@ -88,7 +88,11 @@ if (!gotTheLock) {
initServicesMustBeInitialized();
// DeepLinkService.getInstance().dispatchLinkOpened("bsmanager://launch/?launchOptions=%7B%22debug%22%3Afalse%2C%22oculus%22%3Atrue%2C%22desktop%22%3Atrue%2C%22version%22%3A%7B%22BSVersion%22%3A%221.29.0%22%2C%22BSManifest%22%3A%223341527958186345367%22%2C%22ReleaseURL%22%3A%22https%3A%2F%2Fsteamcommunity.com%2Fgames%2F620980%2Fannouncements%2Fdetail%2F6169409105082976092%22%2C%22ReleaseImg%22%3A%22https%3A%2F%2Fcdn.cloudflare.steamstatic.com%2Fsteamcommunity%2Fpublic%2Fimages%2Fclans%2F%2F32055887%2F255fd49cd96042b97089f754609be291293e783f.png%22%2C%22ReleaseDate%22%3A%221680188760%22%2C%22year%22%3A%222023%22%2C%22name%22%3A%221.29.0+%281%29%22%7D%7D");
// TODO : to remove
setTimeout(() => {
// DeepLinkService.getInstance().dispatchLinkOpened("bsmanager://launch?version=1.29.1&versionName=Hi+Twitter&versionIno=2533274792493431&oculusMode=true&desktopMode=true&additionalArgs=--nowait&additionalArgs=-omgargs");
}, 3000);
const deepLink = process.argv.find(arg => DeepLinkService.getInstance().isDeepLink(arg));
@@ -124,7 +128,7 @@ if (!gotTheLock) {
year: '2023',
name: 'Hi Twitter',
ino: 2533274792493431,
color: '#6545ff af'
color: '#6545ff'
},
additionalArgs: [ '--nowait', '-omgargs' ]
});
+6 -6
View File
@@ -1,6 +1,6 @@
import { BS_APP_ID, BS_DEPOT } from "../constants";
import path from "path";
import { BSVersion, PartialBSVersion } from "shared/bs-version.interface";
import { BSVersion } from "shared/bs-version.interface";
import { UtilsService } from "./utils.service";
import { ChildProcessWithoutNullStreams, spawn, spawnSync } from "child_process";
import log from "electron-log";
@@ -194,18 +194,18 @@ export class BSInstallerService {
return destPath;
}
public async importVersion(path: string): Promise<PartialBSVersion> {
const rawBsVersion = await this.localVersionService.getVersionOfBSFolder(path);
public async importVersion(path: string): Promise<BSVersion> {
const version = await this.localVersionService.getVersionOfBSFolder(path);
if (!rawBsVersion) {
if (!version) {
throw new Error("NOT_BS_FOLDER");
}
const destPath = await this.getPathNotAleardyExist(await this.localVersionService.getVersionPath(rawBsVersion));
const destPath = await this.getPathNotAleardyExist(await this.localVersionService.getVersionPath(version));
await copy(path, destPath, { dereference: true });
return rawBsVersion;
return version;
}
}
+133 -52
View File
@@ -1,7 +1,7 @@
import path from "path";
import { LaunchOption, BSLaunchEvent, BSLaunchErrorEvent, BSLaunchErrorType, BSLaunchEventType } from "../../shared/models/bs-launch";
import { LaunchOption, BSLaunchEvent, BSLaunchEventData, BSLaunchWarning, BSLaunchErrorData, BSLaunchError } from "../../shared/models/bs-launch";
import { UtilsService } from "./utils.service";
import { BS_EXECUTABLE, BS_APP_ID, STEAMVR_APP_ID } from "../constants";
import { BS_EXECUTABLE, BS_APP_ID, STEAMVR_APP_ID, IMAGE_CACHE_PATH } from "../constants";
import { ChildProcessWithoutNullStreams, SpawnOptionsWithoutStdio, spawn } from "child_process";
import { SteamService } from "./steam.service";
import { BSLocalVersionService } from "./bs-local-version.service";
@@ -9,10 +9,17 @@ import { OculusService } from "./oculus.service";
import { pathExist } from "../helpers/fs.helpers";
import { rename } from "fs/promises";
import log from "electron-log";
import { Observable, lastValueFrom, timer } from "rxjs";
import { Observable, lastValueFrom, of, timer } from "rxjs";
import { BsmProtocolService } from "./bsm-protocol.service";
import { app, shell } from "electron";
import { app} from "electron";
import { Resvg } from "@resvg/resvg-js";
import Color from "color";
import { ensureDir, writeFile } from "fs-extra";
import toIco from "to-ico";
import { objectFromEntries } from "../../shared/helpers/object.helpers";
import { WindowManagerService } from "./window-manager.service";
import { IpcService } from "./ipc.service";
import { BSVersionLibService } from "./bs-version-lib.service";
export class BSLauncherService {
private static instance: BSLauncherService;
@@ -22,8 +29,9 @@ export class BSLauncherService {
private readonly oculusService: OculusService;
private readonly localVersionService: BSLocalVersionService;
private readonly bsmProtocolService: BsmProtocolService;
private bsProcess: ChildProcessWithoutNullStreams;
private readonly windows: WindowManagerService;
private readonly ipc: IpcService;
private readonly remoteVersion: BSVersionLibService;
public static getInstance(): BSLauncherService {
if (!BSLauncherService.instance) {
@@ -38,11 +46,14 @@ export class BSLauncherService {
this.oculusService = OculusService.getInstance();
this.localVersionService = BSLocalVersionService.getInstance();
this.bsmProtocolService = BsmProtocolService.getInstance();
this.windows = WindowManagerService.getInstance();
this.ipc = IpcService.getInstance();
this.remoteVersion = BSVersionLibService.getInstance();
this.bsmProtocolService.on("launch", link => {
log.info("Launch from bsm protocol", link.toString());
if(!link.searchParams.has("launchOptions")){ return; }
this.launch(JSON.parse(link.searchParams.get("launchOptions"))).subscribe();
const shortcutParams = objectFromEntries(link.searchParams.entries()) as ShortcutParams;
this.openShortcutLaunchWindow(shortcutParams);
});
}
@@ -93,11 +104,7 @@ export class BSLauncherService {
return Array.from(new Set(launchArgs).values());
}
private launchBSProcess(bsExePath: string, args: string[], debug = false): Promise<void>{
if(this.bsProcess?.connected){
return Promise.reject("Beat Saber process already running");
}
private launchBSProcess(bsExePath: string, args: string[], debug = false): ChildProcessWithoutNullStreams{
const spawnOptions: SpawnOptionsWithoutStdio = { shell: true, cwd: path.dirname(bsExePath), env: {...process.env, "SteamAppId": BS_APP_ID} };
@@ -106,54 +113,44 @@ export class BSLauncherService {
spawnOptions.windowsVerbatimArguments = true;
}
this.bsProcess = spawn(`\"${bsExePath}\"`, args, spawnOptions);
return new Promise((resolve, reject) => {
this.bsProcess.on('error', e => { log.error(e); reject(e); });
this.bsProcess.once('exit', code => {
if(code !== 0){
log.error(`Beat Saber process exited with code ${code}`);
}
resolve();
});
});
return spawn(`\"${bsExePath}\"`, args, spawnOptions);
}
public launch(launchOptions: LaunchOption): Observable<BSLaunchEvent>{
public launch(launchOptions: LaunchOption): Observable<BSLaunchEventData>{
// const t = new URL("bsmanager://launch");
// t.searchParams.set("launchOptions", JSON.stringify(launchOptions));
// console.log(t.toString());
return new Observable<BSLaunchEventData>(obs => {(async () => {
console.log(launchOptions);
return new Observable<BSLaunchEvent>(obs => {(async () => {
if(await this.isBsRunning()){
return obs.error({type: BSLaunchErrorType.BS_ALREADY_RUNNING} as BSLaunchErrorEvent);
if(launchOptions.skipAlreadyRunning !== true && await this.isBsRunning()){
return obs.error({type: BSLaunchError.BS_ALREADY_RUNNING} as BSLaunchErrorData);
}
if(launchOptions.version.oculus && (await this.oculusService.oculusRunning())){
return obs.error({type: BSLaunchErrorType.OCULUS_NOT_RUNNING} as BSLaunchErrorEvent);
return obs.error({type: BSLaunchError.OCULUS_NOT_RUNNING} as BSLaunchErrorData);
}
const bsFolderPath = await this.localVersionService.getInstalledVersionPath(launchOptions.version);
const exePath = path.join(bsFolderPath, BS_EXECUTABLE);
if(!(await pathExist(exePath))){
return obs.error({type: BSLaunchErrorType.BS_NOT_FOUND} as BSLaunchErrorEvent);
return obs.error({type: BSLaunchError.BS_NOT_FOUND} as BSLaunchErrorData);
}
// Open Steam if not running
if(!launchOptions.version.oculus && !(await this.steamService.steamRunning())){
obs.next({type: BSLaunchEventType.STEAM_LAUNCHING});
await this.steamService.openSteam().catch(log.error);
obs.next({type: BSLaunchEvent.STEAM_LAUNCHING});
await this.steamService.openSteam().then(() => {
obs.next({type: BSLaunchEvent.STEAM_LAUNCHED});
}).catch(e => {
log.error(e);
obs.next({type: BSLaunchWarning.UNABLE_TO_LAUNCH_STEAM});
});
}
// Backup SteamVR when desktop mode is enabled
if(!launchOptions.version.oculus && launchOptions.desktop){
await this.backupSteamVR().catch(e => {
await this.backupSteamVR().catch(() => {
return this.restoreSteamVR();
});
await lastValueFrom(timer(2_000));
@@ -163,22 +160,52 @@ export class BSLauncherService {
const launchArgs = this.buildBsLaunchArgs(launchOptions);
obs.next({type: BSLaunchEventType.BS_LAUNCHING});
obs.next({type: BSLaunchEvent.BS_LAUNCHING});
await this.launchBSProcess(exePath, launchArgs, launchOptions.debug).catch(err => {
obs.error({type: BSLaunchErrorType.BS_EXIT_ERROR, data: err} as BSLaunchErrorEvent);
}).finally(() => {
if(!launchOptions.desktop || launchOptions.version.oculus){ return; }
this.restoreSteamVR();
await new Promise<number>((resolve, reject) => {
const bsProcess = this.launchBSProcess(exePath, launchArgs, launchOptions.debug);
bsProcess.on("error", reject);
bsProcess.on("exit", resolve);
setTimeout(() => {
bsProcess.removeAllListeners("error");
bsProcess.removeAllListeners("exit");
resolve(-1);
}, 4000);
}).then(exitCode => {
log.info("BS process exit code", exitCode);
}).catch(err => {
log.error(err);
obs.error({type: BSLaunchError.BS_EXIT_ERROR, data: err} as BSLaunchErrorData);
});
})().then(() => {
obs.complete();
}).catch(err => {
obs.error({type: BSLaunchErrorType.UNKNOWN_ERROR, data: err} as BSLaunchErrorEvent);
obs.error({type: BSLaunchError.UNKNOWN_ERROR, data: err} as BSLaunchErrorData);
})});
}
private shortcutParamsToLaunchOption(params: ShortcutParams): LaunchOption{
const res: LaunchOption = {
version: {
BSVersion: params.version,
name: params.versionName,
steam: params.versionSteam === "true",
oculus: params.versionOculus === "true",
ino: +params.versionIno
},
oculus: params.oculusMode === "true",
desktop: params.desktopMode === "true",
debug: params.debug === "true",
additionalArgs: params.additionalArgs
};
return res;
}
private launchOptionToShortcutParams(launchOptions: LaunchOption): ShortcutParams{
const res: ShortcutParams = { version: launchOptions.version.BSVersion };
@@ -195,19 +222,73 @@ export class BSLauncherService {
return res;
}
/**
* Create .ico file for the shortcut with the given color
* @param {Color} color
* @returns {Promise<string>} Path of the icon
*/
private async createShortcutIco(color: Color): Promise<string>{
const svgIcon = `
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 406.4 406.4" height="406.4" width="406.4">
<rect rx="69.453" height="406.4" width="406.4" fill="${color.hex()}"/>
<path d="M65.467 60.6H336.4v33.867L200.933 162.2 65.467 94.467z" fill="#fff"/>
</svg>
`;
const pngBuffer = new Resvg(svgIcon, {
fitTo: { mode: "width", value: 256 }
}).render().asPng();
const icoBuffer = await toIco([pngBuffer]);
await ensureDir(IMAGE_CACHE_PATH);
const iconPath = path.join(IMAGE_CACHE_PATH, `launch_shortcut_${color.hex()}.ico`);
await writeFile(iconPath, icoBuffer);
return iconPath;
}
private createInternetShortcut(options: { url: string, output: string, iconFile?: string, iconIndex?: number }): Promise<void>{
const data = [
"[InternetShortcut]",
`URL=${options.url}`,
options.iconFile ? `IconFile=${options.iconFile}` : null,
`IconIndex=${options.iconIndex ?? 0}`
].join("\r\n");
return writeFile(options.output, data);
}
public async createLaunchShortcut(launchOptions: LaunchOption): Promise<void>{
const shortcutParams = this.launchOptionToShortcutParams(launchOptions);
const shortcutUrl = this.bsmProtocolService.buildLink("launch", shortcutParams);
console.log(objectFromEntries(shortcutUrl.searchParams.entries()));
const shortcutName = ["Beat Saber", launchOptions.version.BSVersion, launchOptions.version.name].join(" ")
// shell.writeShortcutLink(path.join(app.getPath("desktop"), "test.lnk"), "create", {
// target: shortcutUrl.toString(),
// description: "test allo allo",
// });
return this.createInternetShortcut({
output: path.join(app.getPath("desktop"), `${shortcutName}.url`),
url: shortcutUrl.toString(),
iconFile: await this.createShortcutIco(new Color(launchOptions.version.color, "hex")),
});
}
private async openShortcutLaunchWindow(launchOptions: ShortcutParams): Promise<void>{
const launchOption = this.shortcutParamsToLaunchOption(launchOptions);
launchOption.version = await this.localVersionService.getVersionOfBSFolder(await this.localVersionService.getInstalledVersionPath(launchOption.version));
launchOption.version = {...(await this.remoteVersion.getVersionDetails(launchOption.version.BSVersion)), ...launchOption.version};
this.ipc.once("shortcut-launch-options", (_data, reply) => {
reply(of(launchOption));
});
this.windows.openWindow("shortcut-launch.html");
}
}
+49 -52
View File
@@ -1,5 +1,5 @@
import { BSVersionLibService } from "./bs-version-lib.service";
import { BSVersion, PartialBSVersion } from "shared/bs-version.interface";
import { BSVersion } from "shared/bs-version.interface";
import { InstallationLocationService } from "./installation-location.service";
import { SteamService } from "./steam.service";
import { BS_APP_ID, OCULUS_BS_DIR } from "../constants";
@@ -45,50 +45,55 @@ export class BSLocalVersionService {
public async getVersionOfBSFolder(bsPath: string): Promise<PartialBSVersion>{
public async getVersionOfBSFolder(bsPath: string): Promise<BSVersion>{
const versionFilePath = path.join(bsPath, 'Beat Saber_Data', 'globalgamemanagers');
if(!(await pathExist(versionFilePath))){ return null; }
const versionsAvailable = await this.remoteVersionService.getAvailableVersions();
const versionsDict = await this.remoteVersionService.getAvailableVersions();
return new Promise<PartialBSVersion>(async (resolve, reject) => {
const folderVersion = await new Promise<BSVersion>(async (resolve, reject) => {
const stream = createReadStream(versionFilePath);
stream.on('data', async line => {
stream.on('data', line => {
line = line.toString();
for(const version of versionsAvailable){
if(!line.includes(version.BSVersion)){ continue; }
const findVersion: PartialBSVersion = {BSVersion: version.BSVersion};
if(findVersion.BSVersion !== path.basename(bsPath)){
findVersion.name = path.basename(bsPath);
}
for(const bsVersion of versionsDict){
const folderStats = await lstat(bsPath);
if(folderStats.ino){
findVersion.ino = folderStats.ino;
}
if(!line.includes(bsVersion.BSVersion)){ continue; }
resolve({...bsVersion});
stream.close();
stream.destroy();
return resolve(findVersion);
return;
}
});
stream.on("close", () => {
resolve(null);
});
stream.on("error", e => {
reject(e);
});
stream.on("close", () => { resolve(null); });
stream.on("error", e => { log.error(e); reject(e); });
});
if(!folderVersion){ return null; }
if(folderVersion.BSVersion !== path.basename(bsPath)){
folderVersion.name = path.basename(bsPath);
}
const folderStats = await lstat(bsPath);
if(folderStats.ino){
folderVersion.ino = folderStats.ino;
}
const customVersion = this.getCustomVersions().find(customVersion => {
return customVersion.BSVersion === folderVersion.BSVersion && customVersion.name === folderVersion.name;
});
folderVersion.color = customVersion?.color;
return folderVersion;
}
private setCustomVersions(versions: BSVersion[]): void{
@@ -165,35 +170,38 @@ export class BSLocalVersionService {
private async getSteamVersion(): Promise<BSVersion> {
const steamBsFolder = await this.steamService.getGameFolder(BS_APP_ID, "Beat Saber");
if (!steamBsFolder || !(await pathExist(steamBsFolder))) {
return null;
}
const steamBsVersion = await this.getVersionOfBSFolder(steamBsFolder);
if (!steamBsVersion) {
if(!steamBsVersion){
return null;
}
const version = await this.remoteVersionService.getVersionDetails(steamBsVersion.BSVersion);
if (!version) {
return null;
}
return { ...version, steam: true };
steamBsVersion.name = undefined;
steamBsVersion.steam = true;
return steamBsVersion;
}
private async getOculusVersion(): Promise<BSVersion> {
const oculusBsFolder = await this.oculusService.getGameFolder(OCULUS_BS_DIR);
if (!oculusBsFolder) {
return null;
}
const oculusBsVersion = await this.getVersionOfBSFolder(oculusBsFolder);
if(!oculusBsVersion){ return null; }
const version = await this.remoteVersionService.getVersionDetails(oculusBsVersion.BSVersion);
if (!version) {
return null;
}
return { ...version, oculus: true };
oculusBsVersion.name = undefined;
oculusBsVersion.oculus = true;
return oculusBsVersion;
}
public async getInstalledVersions(): Promise<BSVersion[]> {
@@ -217,23 +225,12 @@ export class BSLocalVersionService {
for (const f of folderInInstallation) {
log.info("try get version from folder", f);
const rawVersion = await this.getVersionOfBSFolder(f);
if(!rawVersion){ continue; }
const version = await this.getVersionOfBSFolder(f);
const vertionDetails = await this.remoteVersionService.getVersionDetails(rawVersion.BSVersion);
if(!version){ continue; }
if(!vertionDetails){ continue; }
const bsVersion: BSVersion = {...vertionDetails, ...rawVersion};
const customVersion = this.getCustomVersions().find(custom => custom.BSVersion === bsVersion.BSVersion && custom.name === bsVersion.name);
if (customVersion) {
bsVersion.color = customVersion.color;
}
versions.push(bsVersion);
versions.push(version);
};
this.setCustomVersions(versions.filter(v => !!v.color));
+5 -5
View File
@@ -39,11 +39,11 @@ export class BsmProtocolService {
}
public buildLink(host: string, params?: Record<string, string|string[]>): URL {
return buildUrl({
protocol: this.BSM_PROTOCOL,
host,
search: params
});
return buildUrl({
protocol: this.BSM_PROTOCOL,
host,
search: params
});
}
}
@@ -18,6 +18,7 @@ export class WindowManagerService {
"oneclick-download-map.html": { width: 350, height: 400, minWidth: 350, minHeight: 400, resizable: false },
"oneclick-download-playlist.html": { width: 350, height: 400, minWidth: 350, minHeight: 400, resizable: false },
"oneclick-download-model.html": { width: 350, height: 400, minWidth: 350, minHeight: 400, resizable: false },
"shortcut-launch.html": { width: 600, height: 300, minWidth: 600, minHeight: 300, resizable: false },
};
private readonly baseWindowOption: BrowserWindowConstructorOptions = {
@@ -89,4 +90,15 @@ export class WindowManagerService {
public getAppWindowFromWebContents(sender: Electron.WebContents): AppWindow {
return Array.from(this.windows.entries()).find(([, value]) => value.webContents.id === sender.id)[0];
}
public openWindowOrFocus(window: AppWindow): Promise<void> {
const win = this.getWindow(window);
if (win) {
win.focus();
return Promise.resolve();
}
return this.openWindow(window).then(() => {});
}
}
@@ -32,7 +32,12 @@ export function BsVersionItem(props: { version: BSVersion }) {
};
const handleDoubleClick = () => {
launcherService.launch(state, !!configService.get<boolean>(LaunchMods.OCULUS_MOD), !!configService.get<boolean>(LaunchMods.DESKTOP_MOD), !!configService.get<boolean>(LaunchMods.DEBUG_MOD));
launcherService.launch({
version: state,
oculus: !!configService.get<boolean>(LaunchMods.OCULUS_MOD),
desktop: !!configService.get<boolean>(LaunchMods.DESKTOP_MOD),
debug: !!configService.get<boolean>(LaunchMods.DEBUG_MOD),
});
};
const cancel = () => {
@@ -2,7 +2,7 @@ import { CSSProperties } from "react";
export function BsNoteFill(props: { className?: string; style?: CSSProperties }) {
return (
<svg className={props.className} style={props.style} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 406.4 406.4" height="406.4" width="406.4">
<svg className={props.className} style={props.style} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 406.4 406.4">
<g>
<rect rx="69.453" height="406.4" width="406.4" fill="currentColor" fillRule="evenodd" paintOrder="fill markers stroke" />
<path transform="translate(-70 170)" d="M135.467-109.4H406.4v33.867L270.933-7.8 135.467-75.533z" fill="white" />
@@ -116,7 +116,7 @@ export default function TitleBar({ template = "index.html" }: { template: AppWin
</header>
);
}
if (template === "oneclick-download-map.html" || template === "oneclick-download-playlist.html" || template === "oneclick-download-model.html") {
if (template === "oneclick-download-map.html" || template === "oneclick-download-playlist.html" || template === "oneclick-download-model.html" || "shortcut-launch.html") {
return (
<header id="titlebar" className="min-h-[22px] bg-transparent w-screen h-[22px] flex content-center items-center justify-start z-10">
<div id="drag-region" className="grow h-full">
@@ -127,9 +127,7 @@ export default function TitleBar({ template = "index.html" }: { template: AppWin
<div id="window-controls" className="h-full flex shrink-0">
<div onClick={closeWindow} className="text-gray-200 cursor-pointer w-7 h-full shrink-0 flex justify-center items-center rounded-bl-md" id="close-button" draggable="false">
<svg aria-hidden="false" width="12" height="12" viewBox="0 0 12 12">
<polygon fill="currentColor" fillRule="evenodd" points="11 1.576 6.583 6 11 10.424 10.424 11 6 6.583 1.576 11 1 10.424 5.417 6 1 1.576 1.576 1 6 5.417 10.424 1">
{" "}
</polygon>
<polygon fill="currentColor" fillRule="evenodd" points="11 1.576 6.583 6 11 10.424 10.424 11 6 6.583 1.576 11 1 10.424 5.417 6 1 1.576 1.576 1 6 5.417 10.424 1"/>
</svg>
</div>
</div>
@@ -50,7 +50,14 @@ export function LaunchSlide({ version }: Props) {
.map(arg => arg.trim())
.filter(arg => arg.length > 0)
: undefined;
bsLauncherService.launch(version, version.oculus ? false : oculusMode, desktopMode, debugMode, additionalArgs);
bsLauncherService.launch({
version,
oculus: version.oculus ? false : oculusMode,
desktop: desktopMode,
debug: debugMode,
additionalArgs
})
};
return (
+5
View File
@@ -8,6 +8,7 @@ const launcherContainer = document.getElementById("launcher");
const oneclickDownloadMapContainer = document.getElementById("oneclick-download-map");
const oneclickDownloadPlaylistContainer = document.getElementById("oneclick-download-playlist");
const oneclickDownloadModelContainer = document.getElementById("oneclick-download-model");
const shortcutLaunchContainer = document.getElementById("shortcut-launch");
const ipc = IpcService.getInstance();
@@ -31,6 +32,10 @@ if (launcherContainer) {
import("./windows/OneClick/OneClickDownloadModel").then(reactWindow => {
createRoot(oneclickDownloadModelContainer).render(<reactWindow.default />);
});
} else if (shortcutLaunchContainer) {
import("./windows/ShortcutLaunch").then(reactWindow => {
createRoot(shortcutLaunchContainer).render(<reactWindow.default />);
});
} else {
const root = document.getElementById("root");
import("./windows/App").then(reactWindow => {
+18 -12
View File
@@ -1,9 +1,9 @@
import { LaunchOption, LaunchResult, BSLaunchErrorEvent, BSLaunchErrorType, BSLaunchEvent } from "shared/models/bs-launch";
import { LaunchOption, LaunchResult, BSLaunchEvent, BSLaunchWarning, BSLaunchEventData, BSLaunchErrorData, BSLaunchError } from "shared/models/bs-launch";
import { BSVersion } from 'shared/bs-version.interface';
import { IpcService } from "./ipc.service";
import { NotificationService } from "./notification.service";
import { BsDownloaderService } from "./bs-downloader.service";
import { BehaviorSubject, Observable } from "rxjs";
import { BehaviorSubject, Observable, filter } from "rxjs";
import { NotificationResult } from "shared/models/notification/notification.model";
export class BSLauncherService {
@@ -66,26 +66,32 @@ export class BSLauncherService {
});
}
public launch(version: BSVersion, oculus: boolean, desktop: boolean, debug: boolean, additionalArgs?: string[]): Observable<BSLaunchEvent> {
const launchState$ = this.ipcService.sendV2<BSLaunchEvent, LaunchOption>("bs-launch.launch", {args: {debug, oculus, desktop, version, additionalArgs}});
public doLaunch(launchOptions: LaunchOption): Observable<BSLaunchEventData>{
return this.ipcService.sendV2<BSLaunchEventData, LaunchOption>("bs-launch.launch", {args: launchOptions});
}
this.versionRunning$.next(version);
public launch(launchOptions: LaunchOption): Observable<BSLaunchEventData> {
const launchState$ = this.doLaunch(launchOptions);
launchState$.subscribe({
this.versionRunning$.next(launchOptions.version);
launchState$.pipe(filter(event => {
const eventToFilter = [...Object.values(BSLaunchWarning), BSLaunchEvent.STEAM_LAUNCHED]
return !eventToFilter.includes(event.type);
})).subscribe({
next: event => {
this.notificationService.notifySuccess({title: `notifications.bs-launch.success.titles.${event.type}`, desc: `notifications.bs-launch.success.msg.${event.type}`});
},
error: (err: BSLaunchErrorEvent) => {
if(!Object.values(BSLaunchErrorType).includes(err.type)){
error: (err: BSLaunchErrorData) => {
if(!Object.values(BSLaunchError).includes(err.type)){
this.notificationService.notifyError({title: "notifications.bs-launch.errors.titles.UNKNOWN_ERROR", desc: "notifications.bs-launch.errors.msg.UNKNOWN_ERROR"});
} else {
this.notificationService.notifyError({title: `notifications.bs-launch.errors.titles.${err.type}`, desc: `notifications.bs-launch.errors.msg.${err.type}`})
}
},
complete: () => {
this.versionRunning$.next(null);
}
})
}).add(() => {
this.versionRunning$.next(null);
});
return launchState$;
}
@@ -1,5 +1,6 @@
import { AppWindow } from "shared/models/window-manager/app-window.model";
import { IpcService } from "./ipc.service";
import { lastValueFrom } from "rxjs";
export class WindowManagerService {
private static instance: WindowManagerService;
@@ -28,4 +29,9 @@ export class WindowManagerService {
public close(...win: AppWindow[]) {
this.ipcService.sendLazy<AppWindow[]>("close-windows", { args: win });
}
public openWindowOrFocus(window: AppWindow): Promise<void> {
return lastValueFrom(this.ipcService.sendV2<void, AppWindow>("open-window-or-focus", { args: window }));
}
}
+14
View File
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://use.typekit.net/okr8ven.css">
<meta charset="utf-8" />
<meta
http-equiv="Content-Security-Policy"
content="script-src 'self' 'unsafe-inline'"
/>
</head>
<body>
<div id="shortcut-launch"></div>
</body>
</html>
+115
View File
@@ -0,0 +1,115 @@
import TitleBar from "renderer/components/title-bar/title-bar.component";
import { useService } from "renderer/hooks/use-service.hook";
import { ThemeService } from "renderer/services/theme.service";
import { useEffect, useState } from "react"
import { WindowManagerService } from "renderer/services/window-manager.service";
import { IpcService } from "renderer/services/ipc.service";
import { take } from "rxjs";
import { BSLauncherService } from "renderer/services/bs-launcher.service";
import { BsmImage } from "renderer/components/shared/bsm-image.component";
import defaultImage from "../../../assets/images/default-version-img.jpg";
import { useObservable } from "renderer/hooks/use-observable.hook";
import { useOnUpdate } from "renderer/hooks/use-on-update.hook";
import { BsNoteFill } from "renderer/components/svgs/icons/bs-note-fill.component";
import { useThemeColor } from "renderer/hooks/use-theme-color.hook";
import { BsmButton } from "renderer/components/shared/bsm-button.component";
import { motion } from "framer-motion"
import { BSLaunchError, BSLaunchEventType, LaunchOption } from "shared/models/bs-launch";
import { NotificationService } from "renderer/services/notification.service";
import { useTranslation } from "renderer/hooks/use-translation.hook";
export default function ShortcutLaunch() {
const themeService = useService(ThemeService);
const windows = useService(WindowManagerService);
const ipc = useService(IpcService);
const bsLauncher = useService(BSLauncherService);
const notification = useService(NotificationService);
const t = useTranslation();
const color = useThemeColor("second-color");
const launchOptions = useObservable(ipc.sendV2<LaunchOption>("shortcut-launch-options").pipe(take(1)), null)
const [rotation, setRotation] = useState(0);
const [status, setStatus] = useState<BSLaunchEventType>();
useEffect(() => {
const sub = themeService.theme$.subscribe(() => {
if (themeService.isDark || (themeService.isOS && window.matchMedia("(prefers-color-scheme: dark)").matches)) {
document.documentElement.classList.add("dark");
} else {
document.documentElement.classList.remove("dark");
}
});
const interval = setInterval(() => {
setRotation(rotation => rotation + 45 * 3);
}, 1500);
return () => {
sub.unsubscribe();
clearInterval(interval);
}
}, []);
useOnUpdate(() => {
if(!launchOptions) { return; }
launchOptions.skipAlreadyRunning = true;
const sub = bsLauncher.doLaunch(launchOptions).subscribe({
next: event => {
setStatus(event.type);
},
error: err => {
if(!Object.values(BSLaunchError).includes(err.type)){
notification.notifySystem({title: t("bs-launch.errors.titles.UNKNOWN_ERROR"), body: t("bs-launch.errors.msg.UNKNOWN_ERROR")});
} else {
notification.notifySystem({title: t(`bs-launch.errors.titles.${err.type}`), body: t(`bs-launch.errors.msg.${err.type}`)})
}
}
});
sub.add(() => {
windows.close("shortcut-launch.html");
});
return () => sub.unsubscribe();
}, [launchOptions]);
return (
<div className="relative w-screen h-screen overflow-hidden ">
<BsmImage className="absolute top-0 left-0 w-full h-full object-cover" placeholder={defaultImage} image={launchOptions?.version?.ReleaseImg ?? defaultImage}/>
<div className="w-full h-full backdrop-blur-lg flex flex-col">
<TitleBar template="shortcut-launch.html"/>
<div className="grow px-6 pb-6 pt-3 flex gap-6">
<div className="h-full w-40 relative flex items-center justify-center">
<BsmImage className="absolute top-0 left-0 w-full h-full object-cover shadow-black shadow-center" placeholder={defaultImage} image={launchOptions?.version?.ReleaseImg ?? defaultImage}/>
<motion.div className="shadow-black shadow-[0px_0px_11px_5px] rounded-2xl aspect-square w-20 z-[1]" animate={{
rotate: rotation,
transition: {
type: "spring",
}
}}>
<BsNoteFill className="w-full h-full" style={{color: launchOptions?.version?.color ?? color}}/>
</motion.div>
</div>
<div className="grow flex flex-col py-3">
<div className="w-full h-full bg-main-color-2 rounded-md shadow-black shadow-md p-3 flex flex-col gap-3">
<h1 className="text-neutral-300 tracking-wide italic">{t("bs-shortcut-launch.beat-saber-launching")}</h1>
<h2 className="text-light-main-color-2 font-bold text-3xl">{[launchOptions?.version?.BSVersion, launchOptions?.version?.name].join(" ")}</h2>
<div className="flex flex-col grow justify-center text-neutral-400">
<span className="uppercase text-neutral-300 tracking-wide">{t("bs-shortcut-launch.launching")}</span>
<span className="italic text-neutral-400 leading-4 text-sm font-bold">{(
status ? t(`bs-shortcut-launch.status-text.success.${status}`) : t("bs-shortcut-launch.status-text.init")
)}
</span>
</div>
<BsmButton className="shrink-0 h-10 rounded-md flex items-center justify-center" text="bs-shortcut-launch.open-bsmanager" typeColor="cancel" withBar={false} onClick={() => windows.openWindowOrFocus("index.html")}/>
</div>
</div>
</div>
</div>
</div>
)
}
+1 -1
View File
@@ -1,3 +1,3 @@
export { LaunchOption } from "./launch-option.interface"
export { LaunchResult } from "./launch-result.interface"
export { BSLaunchErrorEvent, BSLaunchEvent, BSLaunchErrorType, BSLaunchEventType } from "./launch-event.model"
export { BSLaunchError, BSLaunchErrorData, BSLaunchEvent, BSLaunchEventData, BSLaunchEventType, BSLaunchWarning } from "./launch-event.model"
@@ -1,14 +1,14 @@
export interface BSLaunchEvent{
export interface BSLaunchEventData{
type: BSLaunchEventType;
data?: unknown;
}
export interface BSLaunchErrorEvent{
type: BSLaunchErrorType;
export interface BSLaunchErrorData{
type: BSLaunchError;
data?: unknown;
}
export enum BSLaunchErrorType{
export enum BSLaunchError{
BS_NOT_FOUND = "EXE_NOT_FINDED",
BS_ALREADY_RUNNING = "BS_ALREADY_RUNNING",
OCULUS_NOT_RUNNING = "OCULUS_NOT_RUNNING",
@@ -16,7 +16,14 @@ export enum BSLaunchErrorType{
UNKNOWN_ERROR = "UNKNOWN_ERROR",
}
export enum BSLaunchEventType{
export enum BSLaunchEvent{
STEAM_LAUNCHING = "STEAM_LAUNCHING",
STEAM_LAUNCHED = "STEAM_LAUNCHED",
BS_LAUNCHING = "BS_LAUNCHING",
}
}
export enum BSLaunchWarning{
UNABLE_TO_LAUNCH_STEAM = "UNABLE_TO_LAUNCH_STEAM",
}
export type BSLaunchEventType = BSLaunchEvent | BSLaunchWarning;
@@ -5,5 +5,6 @@ export interface LaunchOption {
oculus: boolean,
desktop: boolean,
debug: boolean,
additionalArgs?: string[]
additionalArgs?: string[],
skipAlreadyRunning?: boolean
}
@@ -1 +1 @@
export type AppWindow = "index.html" | "launcher.html" | "oneclick-download-map.html" | "oneclick-download-playlist.html" | "oneclick-download-model.html";
export type AppWindow = "index.html" | "launcher.html" | "oneclick-download-map.html" | "oneclick-download-playlist.html" | "oneclick-download-model.html" | "shortcut-launch.html";