From b91e1a3567d7cb1f0bd1b9137f86b6b4868fa979 Mon Sep 17 00:00:00 2001 From: MathieuG-P <40181755+Zagrios@users.noreply.github.com> Date: Mon, 19 Sep 2022 01:04:50 +0200 Subject: [PATCH] set auto update AND fix download version errors --- .erb/configs/webpack.config.renderer.dev.ts | 14 ++ .erb/configs/webpack.config.renderer.prod.ts | 12 ++ assets/jsons/translations/en.json | 14 +- assets/jsons/translations/es.json | 14 +- assets/jsons/translations/fr.json | 14 +- package-lock.json | 18 ++- package.json | 8 +- release/app/package.json | 2 +- src/main/ipcs/bs-download-ipcs.ts | 2 - src/main/ipcs/bs-launcher-ipcs.ts | 1 + src/main/ipcs/bs-version-ipcs.ts | 4 - src/main/ipcs/index.ts | 2 + src/main/ipcs/launcher-ipcs.ts | 31 +++++ src/main/ipcs/os-controls-ipcs.ts | 18 ++- src/main/ipcs/window-manager-ipcs.ts | 12 ++ src/main/main.ts | 123 +++--------------- src/main/services/auto-updater.service.ts | 55 ++++++++ src/main/services/bs-installer.service.ts | 45 +++---- src/main/services/bs-launcher.service.ts | 2 + src/main/services/map.service.ts | 2 - src/main/services/utils.service.ts | 7 +- src/main/services/window-manager.service.ts | 68 ++++++++++ src/renderer/Launcher.tsx | 53 ++++++++ .../available-versions-slide.component.tsx | 3 - .../available-versions-slider.component.tsx | 1 - .../edit-version-modal.component.tsx | 2 +- .../nav-bar/bsmanager-icon.component.tsx | 46 ++++--- .../components/nav-bar/nav-bar.component.tsx | 4 +- .../title-bar/title-bar.component.tsx | 24 ++-- src/renderer/index.tsx | 21 ++- src/renderer/launcher.ejs | 15 +++ .../pages/version-viewer.component.tsx | 1 - src/renderer/services/auto-updater.service.ts | 49 +++++++ .../services/bs-downloader.service.ts | 4 +- src/renderer/services/i18n.service.ts | 1 + src/renderer/services/ipc.service.ts | 2 +- .../services/window-manager.service.ts | 25 ++++ .../models/window-manager/app-window.model.ts | 1 + 38 files changed, 500 insertions(+), 220 deletions(-) create mode 100644 src/main/ipcs/launcher-ipcs.ts create mode 100644 src/main/ipcs/window-manager-ipcs.ts create mode 100644 src/main/services/auto-updater.service.ts create mode 100644 src/main/services/window-manager.service.ts create mode 100644 src/renderer/Launcher.tsx create mode 100644 src/renderer/launcher.ejs create mode 100644 src/renderer/services/auto-updater.service.ts create mode 100644 src/renderer/services/window-manager.service.ts create mode 100644 src/shared/models/window-manager/app-window.model.ts diff --git a/.erb/configs/webpack.config.renderer.dev.ts b/.erb/configs/webpack.config.renderer.dev.ts index 3e1a0442..a8d91ee8 100644 --- a/.erb/configs/webpack.config.renderer.dev.ts +++ b/.erb/configs/webpack.config.renderer.dev.ts @@ -164,6 +164,20 @@ const configuration: webpack.Configuration = { isDevelopment: process.env.NODE_ENV !== 'production', nodeModules: webpackPaths.appNodeModulesPath, }), + + new HtmlWebpackPlugin({ + filename: path.join('launcher.html'), + template: path.join(webpackPaths.srcRendererPath, 'launcher.ejs'), + minify: { + collapseWhitespace: true, + removeAttributeQuotes: true, + removeComments: true, + }, + isBrowser: false, + env: process.env.NODE_ENV, + isDevelopment: process.env.NODE_ENV !== 'production', + nodeModules: webpackPaths.appNodeModulesPath, + }) ], node: { diff --git a/.erb/configs/webpack.config.renderer.prod.ts b/.erb/configs/webpack.config.renderer.prod.ts index 08d29ce4..5e80effd 100644 --- a/.erb/configs/webpack.config.renderer.prod.ts +++ b/.erb/configs/webpack.config.renderer.prod.ts @@ -143,6 +143,18 @@ const configuration: webpack.Configuration = { isBrowser: false, isDevelopment: process.env.NODE_ENV !== 'production', }), + + new HtmlWebpackPlugin({ + filename: 'launcher.html', + template: path.join(webpackPaths.srcRendererPath, 'launcher.ejs'), + minify: { + collapseWhitespace: true, + removeAttributeQuotes: true, + removeComments: true, + }, + isBrowser: false, + isDevelopment: process.env.NODE_ENV !== 'production', + }) ], }; diff --git a/assets/jsons/translations/en.json b/assets/jsons/translations/en.json index 71094f8c..3f573cf1 100644 --- a/assets/jsons/translations/en.json +++ b/assets/jsons/translations/en.json @@ -108,14 +108,10 @@ } }, "errors":{ - "titles":{ - "already-downloading": "A download is already in progress", - "verification-failed":"The verification failed.", - "no-internet": "No internet connection." - }, "msg":{ "Password": "Password is invalid.", "InvalidCredentials": "Your e-mail or password is invalid 😴", + "InvalidPassword": "Your password is invalid 😕", "NoManifest": "No manifest was found", "DirectoryCreate": "Unable to install the necessary folders.", "NotAvailableApp": "Are you trying to download BeatSaber when you don't have it ? 🤣", @@ -134,7 +130,9 @@ "ConnectionError": "Unable to connect to Steam after 10 tries 🤯", "LicenceError": "Unable to get the list of licenses.", "verification-failed": "Verification failed, try again later.", - "RateLimitExceeded": "You've tried too many times, wait a while and try again later." + "RateLimitExceeded": "You've tried too many times, wait a while and try again later.", + "AlreadyDownloading": "A download is already in progress", + "no-internet": "No internet connection." } } }, @@ -260,6 +258,10 @@ } } }, + "auto-update":{ + "checking": "Checking for updates", + "downloading": "Downloading updates" + }, "dateformat":{ "dayNames": [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", diff --git a/assets/jsons/translations/es.json b/assets/jsons/translations/es.json index b068006a..5ee28177 100644 --- a/assets/jsons/translations/es.json +++ b/assets/jsons/translations/es.json @@ -108,14 +108,10 @@ } }, "errors":{ - "titles":{ - "already-downloading": "La descarga ya está en curso", - "verification-failed":"La verificación falló.", - "no-internet": "Sin conexión a Internet." - }, "msg":{ "Password": "La contraseña es inválida.", "InvalidCredentials": "Su correo electrónico o contraseña no son válidos 😴", + "InvalidPassword": "Su contraseña no es válida 😕", "NoManifest": "No se ha encontrado el manifiesto", "DirectoryCreate": "No se pueden instalar las carpetas necesarias.", "NotAvailableApp": "¿Intentas descargar BeatSaber cuando no lo tienes? 🤣", @@ -134,7 +130,9 @@ "ConnectionError": "No se puede conectar a Steam después de 10 intentos 🤯", "LicenceError": "No se puede obtener la lista de licencias.", "verification-failed": "Verificación fallida, inténtelo más tarde.", - "RateLimitExceeded": "Lo has intentado demasiadas veces, espera un poco y vuelve a intentarlo más tarde." + "RateLimitExceeded": "Lo has intentado demasiadas veces, espera un poco y vuelve a intentarlo más tarde.", + "AlreadyDownloading": "La descarga ya está en curso", + "no-internet": "Sin conexión a Internet." } } }, @@ -260,6 +258,10 @@ } } }, + "auto-update":{ + "checking": "Comprobar las actualizaciones", + "downloading": "Descargar actualizaciones" + }, "dateformat":{ "dayNames": [ "Dom", "Lun", "Mar", "Mié", "Jue", "Vie", "Sáb", diff --git a/assets/jsons/translations/fr.json b/assets/jsons/translations/fr.json index dbb81c14..34377231 100644 --- a/assets/jsons/translations/fr.json +++ b/assets/jsons/translations/fr.json @@ -108,14 +108,10 @@ } }, "errors":{ - "titles":{ - "already-downloading": "Un téléchargement est déjà en cours", - "verification-failed": "La vérification a échoué.", - "no-internet": "Pas de connexion internet." - }, "msg":{ "Password": "Le mot de passe est invalide.", "InvalidCredentials": "Ton e-mail ou mot de passe est invalide 😴", + "InvalidPassword": "Ton mot de passe est invalide 😕", "NoManifest": "Aucun manifest n'a été trouvé.", "DirectoryCreate": "Impossible d'installer les dossiers nécessaires.", "NotAvailableApp": "Tu essayes de télécharger BeatSaber alors que tu ne l'as pas ? 🤣", @@ -134,7 +130,9 @@ "ConnectionError": "Impossible de se connecter à Steam après 10 essais 🤯", "LicenceError": "Impossible d'obtenir la liste des licences.", "verification-failed": "La vérification a échoué, réessaye plus tard.", - "RateLimitExceeded": "Tu as essayé trop de fois attend un peu et recommence plus tard." + "RateLimitExceeded": "Tu as essayé trop de fois attend un peu et recommence plus tard.", + "AlreadyDownloading": "Un téléchargement est déjà en cours", + "no-internet": "Pas de connexion internet." } } }, @@ -260,6 +258,10 @@ } } }, + "auto-update":{ + "checking": "Vérification des mises à jour", + "downloading": "Téléchargement des mises à jour" + }, "dateformat":{ "dayNames": [ "Dim", "Lun", "Mar", "Mer", "Jeu", "Ven", "Sam", diff --git a/package-lock.json b/package-lock.json index aa70c256..0fd536b0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3249,6 +3249,7 @@ "version": "9.0.3", "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.0.3.tgz", "integrity": "sha512-SfG2wnyjpUbbdtpnqDpWwklujofC6GarGpvdWrEkg9p5AD/xJmTF2buTNaqs3qtsNBEVQDDjZz9xc2GGpVyMfA==", + "dev": true, "requires": { "debug": "^4.3.4", "sax": "^1.2.4" @@ -5180,12 +5181,12 @@ "dev": true }, "electron-updater": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-5.2.1.tgz", - "integrity": "sha512-OQZVIvqcK8j03HjT07uVPgvguP/r8RY2wZcwCM26+fcDOjtrm01Dfz3G8Eru+69znbrR+F9pDzr98ewMavBrWQ==", + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-5.2.4.tgz", + "integrity": "sha512-AE+I4cEagOJvHvtkBvJCp+hu1mrpinCt7PMmEOsDcpfayrPvWLuxeFVR9BzRvZVbz0eAqpRQjcEuR8gK80K7lQ==", "requires": { "@types/semver": "^7.3.6", - "builder-util-runtime": "9.0.3", + "builder-util-runtime": "9.1.1", "fs-extra": "^10.0.0", "js-yaml": "^4.1.0", "lazy-val": "^1.0.5", @@ -5195,6 +5196,15 @@ "typed-emitter": "^2.1.0" }, "dependencies": { + "builder-util-runtime": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.1.1.tgz", + "integrity": "sha512-azRhYLEoDvRDR8Dhis4JatELC/jUvYjm4cVSj7n9dauGTOM2eeNn9KS0z6YA6oDsjI1xphjNbY6PZZeHPzzqaw==", + "requires": { + "debug": "^4.3.4", + "sax": "^1.2.4" + } + }, "fs-extra": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", diff --git a/package.json b/package.json index 7aaadc4c..a0266456 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "start:main": "cross-env NODE_ENV=development electronmon -r ts-node/register/transpile-only ./src/main/main.ts", "start:preload": "cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.preload.dev.ts", "start:renderer": "cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack serve --config ./.erb/configs/webpack.config.renderer.dev.ts", - "test": "jest" + "test": "jest", + "publish": "npm run build && electron-builder --publish always --win --x64" }, "lint-staged": { "*.{js,jsx,ts,tsx}": [ @@ -90,8 +91,7 @@ }, "publish": { "provider": "github", - "owner": "Zagrios", - "repo": "bs-manager" + "owner": "Zagrios" } }, "repository": { @@ -228,7 +228,7 @@ "electron-debug": "^3.2.0", "electron-log": "^4.4.8", "electron-store": "^8.1.0", - "electron-updater": "^5.2.1", + "electron-updater": "^5.2.4", "framer-motion": "^7.2.1", "history": "^5.3.0", "is-online": "^9.0.1", diff --git a/release/app/package.json b/release/app/package.json index 06d9fdc1..b6190266 100644 --- a/release/app/package.json +++ b/release/app/package.json @@ -1,6 +1,6 @@ { "name": "BSManager", - "version": "0.1.0", + "version": "0.0.8", "description": "A foundation for scalable desktop apps", "main": "./dist/main/main.js", "author": { diff --git a/src/main/ipcs/bs-download-ipcs.ts b/src/main/ipcs/bs-download-ipcs.ts index d80f924a..6d592c01 100644 --- a/src/main/ipcs/bs-download-ipcs.ts +++ b/src/main/ipcs/bs-download-ipcs.ts @@ -49,10 +49,8 @@ ipcMain.on('bs-download.installation-folder', async (event, request: IpcRequest< ipcMain.on('bs-download.set-installation-folder', (event, request: IpcRequest) => { const installerService = InstallationLocationService.getInstance(); installerService.setInstallationDirectory(request.args).then(res => { - console.log("déplacement terminer"); UtilsService.getInstance().ipcSend(request.responceChannel, {success: true, data: res}); }).catch((err: BsmException) => { - console.log(err); UtilsService.getInstance().ipcSend(request.responceChannel, {success: false, error: err}); }); }) diff --git a/src/main/ipcs/bs-launcher-ipcs.ts b/src/main/ipcs/bs-launcher-ipcs.ts index 53ff080a..c269e603 100644 --- a/src/main/ipcs/bs-launcher-ipcs.ts +++ b/src/main/ipcs/bs-launcher-ipcs.ts @@ -10,6 +10,7 @@ ipcMain.on('bs-launch.launch', (event, request: IpcRequest) => { const utilsService = UtilsService.getInstance(); launcherService.launch(request.args).then(res => { + console.log("BEAT SABER LANCER"); utilsService.ipcSend(request.responceChannel, {success: true, data: res}); }).catch((err: BsmException) => { utilsService.ipcSend(request.responceChannel, {success: false, error: err}); diff --git a/src/main/ipcs/bs-version-ipcs.ts b/src/main/ipcs/bs-version-ipcs.ts index d7898422..de28bf17 100644 --- a/src/main/ipcs/bs-version-ipcs.ts +++ b/src/main/ipcs/bs-version-ipcs.ts @@ -35,20 +35,16 @@ ipcMain.on("bs-version.open-folder", async (event, req: IpcRequest) = ipcMain.on("bs-version.edit", async (event, req: IpcRequest<{version: BSVersion, name: string, color: string}>) => { BSLocalVersionService.getInstance().editVersion(req.args.version, req.args.name, req.args.color).then(res => { - console.log(res); UtilsService.getInstance().ipcSend(req.responceChannel, {success: !!res, data: res}); }).catch((error: BsmException) => { UtilsService.getInstance().ipcSend(req.responceChannel, {success: false, error}); - console.log(error); }); }); ipcMain.on("bs-version.clone", async (event, req: IpcRequest<{version: BSVersion, name: string, color: string}>) => { BSLocalVersionService.getInstance().cloneVersion(req.args.version, req.args.name, req.args.color).then(res => { - console.log(res); UtilsService.getInstance().ipcSend(req.responceChannel, {success: !!res, data: res}); }).catch((error: BsmException) => { UtilsService.getInstance().ipcSend(req.responceChannel, {success: false, error}); - console.log(error); }); }); diff --git a/src/main/ipcs/index.ts b/src/main/ipcs/index.ts index e008ce72..7d9b243f 100644 --- a/src/main/ipcs/index.ts +++ b/src/main/ipcs/index.ts @@ -5,3 +5,5 @@ import './bs-version-ipcs'; import './bs-uninstall-ipcs'; import './map-ipcs'; import './supporters-ipcs'; +import './launcher-ipcs'; +import './window-manager-ipcs'; diff --git a/src/main/ipcs/launcher-ipcs.ts b/src/main/ipcs/launcher-ipcs.ts new file mode 100644 index 00000000..921d6baf --- /dev/null +++ b/src/main/ipcs/launcher-ipcs.ts @@ -0,0 +1,31 @@ +import { ipcMain } from "electron"; +import { AutoUpdaterService } from "../services/auto-updater.service"; +import { IpcRequest } from "shared/models/ipc"; +import { UtilsService } from "../services/utils.service"; + +ipcMain.on("download-update", async (event, request: IpcRequest) => { + + const updaterService = AutoUpdaterService.getInstance(); + const utilsService = UtilsService.getInstance(); + + updaterService.downloadUpdate().then(res => utilsService.ipcSend(request.responceChannel, {success: res})) + .catch(() => utilsService.ipcSend(request.responceChannel, {success: false})); + +}); + +ipcMain.on("check-update", async (event, request: IpcRequest) => { + + const updaterService = AutoUpdaterService.getInstance(); + const utilsService = UtilsService.getInstance(); + + updaterService.isUpdateAvailable().then(updateAvailable => { + utilsService.ipcSend(request.responceChannel, {success: true, data: updateAvailable}); + }) + .catch(() => utilsService.ipcSend(request.responceChannel, {success: false})); + +}); + +ipcMain.on("install-update", async (event, request: IpcRequest) => { + const updaterService = AutoUpdaterService.getInstance(); + updaterService.quitAndInstall(); +}); \ No newline at end of file diff --git a/src/main/ipcs/os-controls-ipcs.ts b/src/main/ipcs/os-controls-ipcs.ts index 3367295f..ceda3cc6 100644 --- a/src/main/ipcs/os-controls-ipcs.ts +++ b/src/main/ipcs/os-controls-ipcs.ts @@ -1,26 +1,29 @@ import { ipcMain, shell, dialog, app } from 'electron'; import { UtilsService } from '../services/utils.service'; import { IpcRequest } from 'shared/models/ipc'; -import { getMainWindow } from '../main'; ipcMain.on('window.close', async () => { - getMainWindow()?.close(); + const utils = UtilsService.getInstance(); + utils.getMainWindow()?.close(); }); ipcMain.on('window.maximize', async () => { - getMainWindow()?.maximize(); + const utils = UtilsService.getInstance(); + utils.getMainWindow()?.maximize(); }); ipcMain.on('window.minimize', async () => { - getMainWindow()?.minimize(); + const utils = UtilsService.getInstance(); + utils.getMainWindow()?.minimize(); }); ipcMain.on('window.reset', async () => { - getMainWindow()?.restore(); + const utils = UtilsService.getInstance(); + utils.getMainWindow()?.restore(); }); ipcMain.on('new-window', async (event, request: IpcRequest) => { - shell.openExternal(request.args); + shell.openExternal(request.args); }); ipcMain.on('choose-folder', async (event, request: IpcRequest) => { @@ -30,7 +33,8 @@ ipcMain.on('choose-folder', async (event, request: IpcRequest) => { }); ipcMain.on("window.progression", async (event, request: IpcRequest) => { - getMainWindow().setProgressBar(request.args / 100); + const utils = UtilsService.getInstance(); + utils.getMainWindow().setProgressBar(request.args / 100); }); ipcMain.on('save-file', async (event, request: IpcRequest<{filename?: string, filters?: Electron.FileFilter[]}>) => { diff --git a/src/main/ipcs/window-manager-ipcs.ts b/src/main/ipcs/window-manager-ipcs.ts new file mode 100644 index 00000000..b0551ddc --- /dev/null +++ b/src/main/ipcs/window-manager-ipcs.ts @@ -0,0 +1,12 @@ +import { ipcMain } from "electron"; +import { WindowManagerService } from "../services/window-manager.service"; +import { IpcRequest } from "shared/models/ipc"; +import { AppWindow } from "shared/models/window-manager/app-window.model"; + +ipcMain.on("open-window-then-close-all", async (event, request: IpcRequest) => { + const windowManager = WindowManagerService.getInstance(); + + windowManager.openWindow(request.args).then(window => { + windowManager.closeAllWindows(request.args); + }); +}); \ No newline at end of file diff --git a/src/main/main.ts b/src/main/main.ts index 8f428ab2..8b66731a 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -9,19 +9,15 @@ * `./src/main.js` using webpack. This gives us some performance wins. */ import path from 'path'; -import { app, BrowserWindow, shell } from 'electron'; -import { autoUpdater } from 'electron-updater'; +import { app } from 'electron'; import log from 'electron-log'; -import { resolveHtmlPath } from './util'; import './ipcs'; import { UtilsService } from './services/utils.service'; +import { WindowManagerService } from './services/window-manager.service'; -export default class AppUpdater { - constructor() { - autoUpdater.logger = log; - //autoUpdater.checkForUpdatesAndNotify(); - } -} +export const PRELOAD_PATH = app.isPackaged ? path.join(__dirname, 'preload.js') : path.join(__dirname, '../../.erb/dll/preload.js') + +const isDebug = process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD === 'true'; log.transports.file.level = 'info'; log.transports.file.resolvePath = (() => { @@ -31,11 +27,8 @@ log.transports.file.resolvePath = (() => { log.catchErrors(); -let mainWindow: BrowserWindow = null; - -export function getMainWindow(): BrowserWindow{ - return mainWindow; -} +const utilsService = UtilsService.getInstance(); +utilsService.setAssetsPath(app.isPackaged ? path.join(process.resourcesPath, 'assets') : path.join(__dirname, '../../assets')); if (process.env.NODE_ENV === 'production') { @@ -43,107 +36,31 @@ if (process.env.NODE_ENV === 'production') { sourceMapSupport.install(); } -const isDebug = process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD === 'true'; - if (isDebug) { require('electron-debug')(); } const installExtensions = async () => { - const installer = require('electron-devtools-installer'); - const forceDownload = !!process.env.UPGRADE_EXTENSIONS; - const extensions = ['REACT_DEVELOPER_TOOLS']; + const installer = require('electron-devtools-installer'); + const forceDownload = !!process.env.UPGRADE_EXTENSIONS; + const extensions = ['REACT_DEVELOPER_TOOLS']; - return installer - .default( - extensions.map((name) => installer[name]), - forceDownload - ) - .catch(console.log); + return installer.default(extensions.map((name) => installer[name]), forceDownload).catch(console.log); }; const createWindow = async () => { - if (isDebug) { - await installExtensions(); - } - - const RESOURCES_PATH = app.isPackaged ? path.join(process.resourcesPath, 'assets') : path.join(__dirname, '../../assets'); - - UtilsService.getInstance().setAssetsPath(RESOURCES_PATH); - - const getAssetPath = (...paths: string[]): string => { - return path.join(RESOURCES_PATH, ...paths); - }; - - mainWindow = new BrowserWindow({ - show: false, - width: 1080, - height: 720, - minWidth: 900, - minHeight: 500, - frame: false, - titleBarOverlay: false, - icon: getAssetPath('favicon.ico'), - webPreferences: { - preload: app.isPackaged - ? path.join(__dirname, 'preload.js') - : path.join(__dirname, '../../.erb/dll/preload.js'), - }, - }); - - UtilsService.getInstance().setMainWindow(mainWindow); - - mainWindow.loadURL(resolveHtmlPath('index.html')); - - mainWindow.on('ready-to-show', () => { - if (!mainWindow) { - throw new Error('"mainWindow" is not defined'); - } - if (process.env.START_MINIMIZED) { - mainWindow.minimize(); - } else { - mainWindow.show(); - } - }); - - mainWindow.on('closed', () => { - mainWindow = null; - }); - - mainWindow.removeMenu(); - mainWindow.setMenu(null); - - // Open urls in the user's browser - mainWindow.webContents.setWindowOpenHandler((edata) => { - shell.openExternal(edata.url); - return { action: 'deny' }; - }); - - // Remove this if your app does not use auto updates - // eslint-disable-next-line - new AppUpdater(); + if(isDebug){ await installExtensions(); } + WindowManagerService.getInstance().openWindow("launcher.html"); }; -/** - * Add event listeners... - */ - app.on('window-all-closed', () => { - // Respect the OSX convention of having the application in memory even - // after all windows have been closed - if (process.platform !== 'darwin') { - app.quit(); - } + // Respect the OSX convention of having the application in memory even + // after all windows have been closed + if (process.platform !== 'darwin') { + app.quit(); + } }); -app - .whenReady() - .then(() => { +app.whenReady().then(() => { createWindow(); - app.on('activate', () => { - // On macOS it's common to re-create a window in the app when the - // dock icon is clicked and there are no other windows open. - if (mainWindow === null) createWindow(); - }); - }) - .catch(console.log); \ No newline at end of file +}).catch(console.log); \ No newline at end of file diff --git a/src/main/services/auto-updater.service.ts b/src/main/services/auto-updater.service.ts new file mode 100644 index 00000000..0c48e227 --- /dev/null +++ b/src/main/services/auto-updater.service.ts @@ -0,0 +1,55 @@ +import { autoUpdater } from 'electron-updater'; +import log from 'electron-log'; +import { UtilsService } from './utils.service'; +export class AutoUpdaterService { + + private static instance: AutoUpdaterService; + + private readonly utilsService: UtilsService; + + public static getInstance(): AutoUpdaterService{ + if(!AutoUpdaterService.instance){ AutoUpdaterService.instance = new AutoUpdaterService(); } + return AutoUpdaterService.instance; + } + + constructor(){ + autoUpdater.logger = log; + autoUpdater.autoDownload = false; + + this.utilsService = UtilsService.getInstance(); + } + + public isUpdateAvailable(): Promise{ + return new Promise(resolve => { + autoUpdater.checkForUpdates().then(info => { + const needUpdate = (() => { + if(!info || !info.updateInfo){ return false; } + if(autoUpdater.currentVersion.version === info.updateInfo.version){ return false; } + return true; + })(); + resolve(needUpdate)} + ).catch(() => resolve(false)); + }); + } + + public downloadUpdate(): Promise{ + + autoUpdater.removeAllListeners("download-progress"); + autoUpdater.addListener("download-progress", info => { + this.utilsService.ipcSend("update-download-progress", {success: true, data: info.percent}); + }); + // const promise = new Promise((resolve, reject) => { + // autoUpdater.addListener("update-downloaded", () => resolve(true)); + // autoUpdater.addListener("error", () => reject(false)) + // }); + + return autoUpdater.downloadUpdate().then(res => !!res && !!res.length); + } + + public quitAndInstall(){ + autoUpdater.quitAndInstall(); + } + + + +} \ No newline at end of file diff --git a/src/main/services/bs-installer.service.ts b/src/main/services/bs-installer.service.ts index 3d0af6c3..06971716 100644 --- a/src/main/services/bs-installer.service.ts +++ b/src/main/services/bs-installer.service.ts @@ -7,7 +7,6 @@ import log from "electron-log"; import { InstallationLocationService } from "./installation-location.service"; import { ctrlc } from "ctrlc-windows"; import { BSLocalVersionService } from "./bs-local-version.service"; -import { getMainWindow } from "../main"; import isOnline from 'is-online'; export class BSInstallerService{ @@ -25,7 +24,7 @@ export class BSInstallerService{ this.installLocationService = InstallationLocationService.getInstance(); this.localVersionService = BSLocalVersionService.getInstance(); - getMainWindow().on("close", () => { + this.utils.getMainWindow().on("close", () => { this.killDownloadProcess(); }); } @@ -35,14 +34,16 @@ export class BSInstallerService{ return BSInstallerService.instance; } - private getDepotDownloaderExePath(): string{ - return path.join(this.utils.getAssetsScriptsPath(), 'depot-downloader', 'DepotDownloader.exe'); - } + private getDepotDownloaderExePath(): string{ + return path.join(this.utils.getAssetsScriptsPath(), 'depot-downloader', 'DepotDownloader.exe'); + } - private sendDownloadEvent(event: DownloadEventType, data?: string|number, success = true): void{ - if(typeof data === "string"){ data = data.replaceAll(/\[|\]/g, ""); } - this.utils.ipcSend(`bs-download.${event}`, { success, data }); - } + private removeSpecialSchar(txt: string): string{ return txt.replaceAll(/\[|\]/g, ""); } + + private sendDownloadEvent(event: DownloadEventType, data?: string|number, success = true): void{ + if(typeof data === "string"){ data = this.removeSpecialSchar(data); } + this.utils.ipcSend(`bs-download.${event}`, { success, data }); + } public sendInputProcess(input: string){ if(this.downloadProcess.stdin.writable){ @@ -54,16 +55,16 @@ export class BSInstallerService{ return new Promise(resolve => { if(this.downloadProcess?.killed && !this.downloadProcess?.pid){ return resolve(false); } - this.downloadProcess.on('close', () => resolve(true)); + this.downloadProcess.once('exit', () => resolve(true)); ctrlc(this.downloadProcess.pid); - if(!this.downloadProcess.kill()){ return resolve(false); } - setTimeout(() => resolve(false), 2000); + setTimeout(() => resolve(false), 3000); }); } public async downloadBsVersion(downloadInfos: DownloadInfo): Promise{ - if(this.downloadProcess && !this.downloadProcess?.killed){ return {type: "[AlreadyDownloading]"}; } + + if(this.downloadProcess && this.downloadProcess.connected){ throw "AlreadyDownloading"; } const {bsVersion} = downloadInfos; if(!bsVersion){ return {type: "[Error]"}; } if(!(await isOnline({timeout: 1500}))){ throw "no-internet"; } @@ -86,9 +87,10 @@ export class BSInstallerService{ this.downloadProcess.stdout.on('data', data => { const matched = (data.toString() as string).match(/(?:\[(.*?)\])\|(?:\[(.*?)\]\|)?(.*?)(?=$|\[)/gm) ?? []; - console.log(data.toString()); matched.forEach(match => { const out = match.split("|"); + + if(out[0] !== "[Progress]" && out[0] !== "[Validated]"){ log.info(data.toString()); } if(out[0] === "[Password]" as DownloadEventType && downloadInfos.password){ this.sendInputProcess(downloadInfos.password); @@ -114,8 +116,7 @@ export class BSInstallerService{ this.sendDownloadEvent("[Warning]", out[1]); } else if(out[0] === "[Error]" as DownloadEventType){ - reject(out[1]); - this.sendDownloadEvent("[Error]", out[1], false); + reject(this.removeSpecialSchar(out[1])); this.killDownloadProcess(); log.error("Download Event, Error", data.toString()); } @@ -125,18 +126,18 @@ export class BSInstallerService{ this.downloadProcess.stdout.on('error', (err) => { log.error("BS-DOWNLOAD ERROR", err.toString()); - this.downloadProcess.kill(); - reject(err); + this.killDownloadProcess(); + reject(); }); this.downloadProcess.stderr.on('data', (err) => { log.error("BS-DOWNLOAD ERROR", err.toString()); - this.downloadProcess.kill(); - reject(err); + this.killDownloadProcess(); + reject(); }); this.downloadProcess.stderr.on('error', (err) => { log.error("BS-DOWNLOAD ERROR", err.toString()); - this.downloadProcess.kill(); - reject(err); + this.killDownloadProcess(); + reject(); }); this.downloadProcess.on('close', () => reject()); diff --git a/src/main/services/bs-launcher.service.ts b/src/main/services/bs-launcher.service.ts index 6b0a9e3e..8cbd5058 100644 --- a/src/main/services/bs-launcher.service.ts +++ b/src/main/services/bs-launcher.service.ts @@ -38,6 +38,8 @@ export class BSLauncherService{ const cwd = await this.localVersionService.getVersionPath(launchOptions.version); const exePath = path.join(cwd, BS_EXECUTABLE); + console.log(exePath); + if(!this.utilsService.pathExist(exePath)){ return "EXE_NOT_FINDED"; } const launchMods = [launchOptions.oculus && "-vrmode oculus", launchOptions.desktop && "fpfc", launchOptions.debug && "--verbose"]; diff --git a/src/main/services/map.service.ts b/src/main/services/map.service.ts index 0b1f7df8..596321c8 100644 --- a/src/main/services/map.service.ts +++ b/src/main/services/map.service.ts @@ -30,8 +30,6 @@ export class MapService{ const versionPath = await this.localVersionService.getVersionPath(version); const mapsPath = this.getMapsPath(versionPath); - console.log(outputZip); - const output = createWriteStream(outputZip); const archive = archiver("zip", {zlib: {level: 9}}); diff --git a/src/main/services/utils.service.ts b/src/main/services/utils.service.ts index 4d3cebdd..dbbc3791 100644 --- a/src/main/services/utils.service.ts +++ b/src/main/services/utils.service.ts @@ -23,12 +23,13 @@ export class UtilsService{ } public setAssetsPath(path: string): void{ this.assetsPath = path; } - public getAssetsPath(): string{ return this.assetsPath; } + public getAssetsPath(pathToFile: string): string{ return path.join(this.assetsPath, pathToFile); } - public getAssetsScriptsPath(): string { return path.join(this.getAssetsPath(), 'scripts'); } - public getAssestsJsonsPath(): string { return path.join(this.getAssetsPath(), 'jsons'); } + public getAssetsScriptsPath(): string { return this.getAssetsPath("scripts") } + public getAssestsJsonsPath(): string { return this.getAssetsPath("jsons"); } public setMainWindow(win: BrowserWindow){ this.mainWindow = win; } + public getMainWindow(){ return this.mainWindow; } public pathExist(path: string): boolean{ return existsSync(path); } diff --git a/src/main/services/window-manager.service.ts b/src/main/services/window-manager.service.ts new file mode 100644 index 00000000..d4d075f0 --- /dev/null +++ b/src/main/services/window-manager.service.ts @@ -0,0 +1,68 @@ +import { app, BrowserWindow, BrowserWindowConstructorOptions } from "electron"; +import { resolveHtmlPath } from "../util"; +import { UtilsService } from "./utils.service"; +import { AppWindow } from "shared/models/window-manager/app-window.model"; +import { PRELOAD_PATH } from "../main"; + +export class WindowManagerService{ + + private static instance: WindowManagerService; + + private readonly utilsService: UtilsService = UtilsService.getInstance(); + + private readonly appWindowsOptions: Record = { + "launcher.html": {width: 380, height: 500, minWidth: 380, minHeight: 500, resizable: false}, + "index.html": {width: 1080, height: 720, minWidth: 900, minHeight: 500} + } + + private readonly baseWindowOption: BrowserWindowConstructorOptions = { + icon: this.utilsService.getAssetsPath("favicon.ico"), + show: false, + frame: false, + titleBarOverlay: false, + webPreferences: { preload: PRELOAD_PATH } + } + + private readonly windows: Map = new Map(); + + public static getInstance(): WindowManagerService{ + if(!WindowManagerService.instance){ WindowManagerService.instance = new WindowManagerService(); } + return WindowManagerService.instance; + } + + private constructor(){} + + public openWindow(windowType: AppWindow): Promise{ + const window = new BrowserWindow({...this.appWindowsOptions[windowType], ...this.baseWindowOption}); + const promise = window.loadURL(resolveHtmlPath(windowType)); + window.removeMenu(); + window.setMenu(null); + + window.once("ready-to-show", () => { + if (!window) { throw new Error('"window" is not defined'); } + return window.show(); + }); + + window.once("closed", () => { + this.windows.delete(windowType); + if(!this.windows.size){ app.quit(); } + }); + + this.windows.set(windowType, window); + this.utilsService.setMainWindow(window); + + return promise.then(() => window); + } + + public closeWindow(window: AppWindow){ + this.windows.get(window).close(); + } + + public closeAllWindows(except?: AppWindow){ + this.windows.forEach((window, key) => { + if(key === except){ return; } + window.close(); + }) + } + +} \ No newline at end of file diff --git a/src/renderer/Launcher.tsx b/src/renderer/Launcher.tsx new file mode 100644 index 00000000..25f7f48b --- /dev/null +++ b/src/renderer/Launcher.tsx @@ -0,0 +1,53 @@ +import { motion } from "framer-motion"; +import { useEffect, useRef, useState } from "react"; +import { BsManagerIcon } from "./components/nav-bar/bsmanager-icon.component"; +import { BsmProgressBar } from "./components/progress-bar/bsm-progress-bar.component"; +import TitleBar from "./components/title-bar/title-bar.component"; +import { useTranslation } from "./hooks/use-translation.hook"; +import { AutoUpdaterService } from "./services/auto-updater.service"; +import { ThemeService } from "./services/theme.service"; +import { WindowManagerService } from "./services/window-manager.service"; + +export default function Launcher() { + + const themeService = ThemeService.getInstance(); + const updaterService = AutoUpdaterService.getInstance(); + const windowService = WindowManagerService.getInstance(); + + const constraintsRef = useRef(null); + + const [text, setText] = useState("auto-update.checking"); + + const t = useTranslation(); + + + 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'); } + }); + + updaterService.isUpdateAvailable().then(available => { + if(!available){ return windowService.openThenCloseAll("index.html"); } + setText("auto-update.downloading"); + updaterService.downloadUpdate().then(installed => installed && updaterService.quitAndInstall()); + }); + + return () => sub.unsubscribe(); + }, []); + + return ( +
+ +
+ + + + + + {t(text)} + +
+
+ ) +} diff --git a/src/renderer/components/available-versions/available-versions-slide.component.tsx b/src/renderer/components/available-versions/available-versions-slide.component.tsx index 9d244cdc..b3d66626 100644 --- a/src/renderer/components/available-versions/available-versions-slide.component.tsx +++ b/src/renderer/components/available-versions/available-versions-slide.component.tsx @@ -10,9 +10,6 @@ export function AvailableVersionsSlide(props: {year: string}) { const versionsService = BSVersionManagerService.getInstance(); - console.log("*** AVAILABLE VERISON"); - console.log(availableVersions); - useEffect(() => { versionsService.availableVersions$.pipe(filter(versions => !!versions?.length), take(1)).subscribe(() => { setAvailableVersions(versionsService.getAvaibleVersionsOfYear(props.year)); diff --git a/src/renderer/components/available-versions/available-versions-slider.component.tsx b/src/renderer/components/available-versions/available-versions-slider.component.tsx index effb7da9..0a8f6ffe 100644 --- a/src/renderer/components/available-versions/available-versions-slider.component.tsx +++ b/src/renderer/components/available-versions/available-versions-slider.component.tsx @@ -17,7 +17,6 @@ export function AvailableVersionsSlider() { useEffect(() => { versionManagerService.availableVersions$.pipe(filter(versions => !!versions?.length), take(1)).subscribe(() => { - console.log("ouiiii"); setAvailableYears(versionManagerService.getAvailableYears()); }); }, []) diff --git a/src/renderer/components/modal/modal-types/edit-version-modal.component.tsx b/src/renderer/components/modal/modal-types/edit-version-modal.component.tsx index bc775ac0..41f68313 100644 --- a/src/renderer/components/modal/modal-types/edit-version-modal.component.tsx +++ b/src/renderer/components/modal/modal-types/edit-version-modal.component.tsx @@ -21,7 +21,7 @@ export function EditVersionModal({resolver, clone = false}: {resolver: (x: Modal const rename = () => { if(!name){ return; } - resolver({exitCode: ModalExitCode.COMPLETED, data: {name, color}}) + resolver({exitCode: ModalExitCode.COMPLETED, data: {name: name.trim(), color}}) } const resetColor = () => { diff --git a/src/renderer/components/nav-bar/bsmanager-icon.component.tsx b/src/renderer/components/nav-bar/bsmanager-icon.component.tsx index 56a0efba..f938016d 100644 --- a/src/renderer/components/nav-bar/bsmanager-icon.component.tsx +++ b/src/renderer/components/nav-bar/bsmanager-icon.component.tsx @@ -1,24 +1,32 @@ import { memo } from "react" +import { useThemeColor } from "renderer/hooks/use-theme-color.hook"; +import { motion } from "framer-motion"; + +export const BsManagerIcon = memo(({className}: {className?: string}) => { + + const {firstColor, secondColor} = useThemeColor(); -export const BsManagerIcon = memo(({className, color1, color2}: {className: string, color1: string, color2: string}) => { return ( - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + ) }) diff --git a/src/renderer/components/nav-bar/nav-bar.component.tsx b/src/renderer/components/nav-bar/nav-bar.component.tsx index 80c1232f..714e45e9 100644 --- a/src/renderer/components/nav-bar/nav-bar.component.tsx +++ b/src/renderer/components/nav-bar/nav-bar.component.tsx @@ -4,7 +4,6 @@ import { BSVersionManagerService } from '../../services/bs-version-manager.servi import { Link } from 'react-router-dom'; import { BsmIcon } from '../svgs/bsm-icon.component'; import { useObservable } from 'renderer/hooks/use-observable.hook'; -import { useThemeColor } from 'renderer/hooks/use-theme-color.hook'; import { BsManagerIcon } from './bsmanager-icon.component'; export function NavBar() { @@ -12,12 +11,11 @@ export function NavBar() { const bsVersionServoce = BSVersionManagerService.getInstance(); const installedVersions = useObservable(bsVersionServoce.installedVersions$); - const {firstColor, secondColor} = useThemeColor(); return (