diff --git a/src/main/constants.ts b/src/main/constants.ts index 24c72eda..74ca8198 100644 --- a/src/main/constants.ts +++ b/src/main/constants.ts @@ -2,3 +2,4 @@ export const BS_EXECUTABLE = "Beat Saber.exe"; export const OCULUS_BS_DIR = "hyperbolic-magnetism-beat-saber" export const BS_APP_ID = "620980"; export const BS_DEPOT = "620981"; +export const APP_NAME = "BSManager"; diff --git a/src/main/ipcs/os-controls-ipcs.ts b/src/main/ipcs/os-controls-ipcs.ts index 7b92c098..ba4d919e 100644 --- a/src/main/ipcs/os-controls-ipcs.ts +++ b/src/main/ipcs/os-controls-ipcs.ts @@ -1,6 +1,8 @@ import { ipcMain, shell, dialog, app } from 'electron'; import { UtilsService } from '../services/utils.service'; import { IpcRequest } from 'shared/models/ipc'; +import { SystemNotificationOptions } from 'shared/models/notification/system-notification.model'; +import { NotificationService } from '../services/notification.service'; // TODO IMPROVE WINDOW CONTROL BY USING WINDOW SERVICE @@ -55,3 +57,7 @@ ipcMain.on("current-version", async (event, request: IpcRequest) => { ipcMain.on("open-logs", async (event, request: IpcRequest) => { shell.openPath(app.getPath("logs")); }); + +ipcMain.on("notify-system", async (event, request: IpcRequest) => { + NotificationService.getInstance().notify(request.args) +}); \ No newline at end of file diff --git a/src/main/main.ts b/src/main/main.ts index c368799c..8908b482 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -19,6 +19,7 @@ import { AppWindow } from 'shared/models/window-manager/app-window.model'; import { LocalMapsManagerService } from './services/additional-content/local-maps-manager.service'; import { LocalPlaylistsManagerService } from './services/additional-content/local-playlists-manager.service'; import { LocalModelsManagerService } from './services/additional-content/local-models-manager.service'; +import { APP_NAME } from './constants'; const isDebug = process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD === 'true'; @@ -89,7 +90,9 @@ else{ app.whenReady().then(() => { - // process.argv.push("modelsaber://platform/1671435302/The grinch mountain.plat"); // to force deep-link (oneClick map) + app.setAppUserModelId(APP_NAME); + + process.argv.push("beatsaver://2d5bc"); // to force deep-link (oneClick map) initServicesMustBeInitialized(); diff --git a/src/main/services/notification.service.ts b/src/main/services/notification.service.ts new file mode 100644 index 00000000..74277bad --- /dev/null +++ b/src/main/services/notification.service.ts @@ -0,0 +1,27 @@ +import { Notification } from "electron"; +import { SystemNotificationOptions } from "shared/models/notification/system-notification.model"; +import { UtilsService } from "./utils.service"; + +export class NotificationService { + + private static instance: NotificationService; + + public static getInstance(): NotificationService{ + if(!NotificationService.instance){ NotificationService.instance = new NotificationService(); } + return NotificationService.instance; + } + + private readonly APP_ICON: string; + + private readonly utils: UtilsService; + + private constructor(){ + this.utils = UtilsService.getInstance(); + this.APP_ICON = this.utils.getAssetsPath("favicon.ico"); + } + + public notify(options: SystemNotificationOptions){ + new Notification({...options, icon: this.APP_ICON}).show(); + } + +} \ No newline at end of file diff --git a/src/main/services/window-manager.service.ts b/src/main/services/window-manager.service.ts index 1019a8c1..1f26bb05 100644 --- a/src/main/services/window-manager.service.ts +++ b/src/main/services/window-manager.service.ts @@ -3,6 +3,7 @@ import { resolveHtmlPath } from "../util"; import { UtilsService } from "./utils.service"; import { AppWindow } from "shared/models/window-manager/app-window.model"; import path from "path"; +import { APP_NAME } from "../constants"; export class WindowManagerService{ @@ -21,6 +22,7 @@ export class WindowManagerService{ } private readonly baseWindowOption: BrowserWindowConstructorOptions = { + title: APP_NAME, icon: this.utilsService.getAssetsPath("favicon.ico"), show: false, frame: false, diff --git a/src/renderer/pages/settings-page.component.tsx b/src/renderer/pages/settings-page.component.tsx index e66421ae..111a9602 100644 --- a/src/renderer/pages/settings-page.component.tsx +++ b/src/renderer/pages/settings-page.component.tsx @@ -150,16 +150,51 @@ export function SettingsPage() { const openLogs = () => ipcService.sendLazy("open-logs"); + // TODO TRANSLATE + const showDeepLinkError = (isDesactivation: boolean) => { + const desc = isDesactivation ? "Une erreur inconnue c'est produite" : "Impossible d'activer les installations OneClick"; + notificationService.notifyError({title: "notifications.types.error", desc, duration: 3000}); + } + + const showDeepLinkSuccess = (isDesactivation: boolean) => { + const desc = isDesactivation ? "Les installations OneClick ont été désactivées" : "Les installations OneClick ont été activées"; + const title = isDesactivation ? "OneClick désactivée !" : "OneClick activée !"; + notificationService.notifySuccess({title, desc, duration: 3000}); + } + + const showDeepLinkWarning = (isDesactivation: boolean) => { + const desc = isDesactivation ? "Un problème est survenu lors de la désactivation des OneClicks, veuillez réessayez" : "Un problème est survenu lors de l'activation des OneClicks, veuillez réessayez"; + notificationService.notifyWarning({title: "Attention !", desc, duration: 3000}); + } + const toogleMapDeepLinks = () => { - mapsManager.toogleDeepLinks().finally(() => mapsManager.isDeepLinksEnabled().then(enabled => setMapDeepLinksEnabled(() => enabled))); + const isDesactivation = mapDeepLinksEnabled; + mapsManager.toogleDeepLinks().catch(() => { + showDeepLinkError(isDesactivation); + }).then(res => { + if(!res){ return showDeepLinkWarning(isDesactivation); } + showDeepLinkSuccess(isDesactivation) + }).finally(() => mapsManager.isDeepLinksEnabled().then(enabled => setMapDeepLinksEnabled(() => enabled))); } const tooglePlaylistsDeepLinks = () => { - playlistsManager.toogleDeepLinks().finally(() => playlistsManager.isDeepLinksEnabled().then(enabled => setPlaylistsDeepLinkEnabled(() => enabled))); + const isDesactivation = playlistsDeepLinkEnabled; + playlistsManager.toogleDeepLinks().catch(() => { + showDeepLinkError(isDesactivation); + }).then(res => { + if(!res){ return showDeepLinkWarning(isDesactivation); } + showDeepLinkSuccess(isDesactivation) + }).finally(() => playlistsManager.isDeepLinksEnabled().then(enabled => setPlaylistsDeepLinkEnabled(() => enabled))); } const toogleModelsDeepLinks = () => { - modelsManager.toogleDeepLinks().finally(() => modelsManager.isDeepLinksEnabled().then(enabled => setModelsDeepLinkEnabled(() => enabled))); + const isDesactivation = modelsDeepLinkEnabled; + modelsManager.toogleDeepLinks().catch(() => { + showDeepLinkError(isDesactivation) + }).then(res => { + if(!res){ return showDeepLinkWarning(isDesactivation); } + showDeepLinkSuccess(isDesactivation) + }).finally(() => modelsManager.isDeepLinksEnabled().then(enabled => setModelsDeepLinkEnabled(() => enabled))); } return ( diff --git a/src/renderer/services/notification.service.ts b/src/renderer/services/notification.service.ts index 3da31c79..19d15f51 100644 --- a/src/renderer/services/notification.service.ts +++ b/src/renderer/services/notification.service.ts @@ -1,17 +1,27 @@ import { BehaviorSubject } from "rxjs"; +import { SystemNotificationOptions } from "shared/models/notification/system-notification.model"; +import { I18nService } from "./i18n.service"; +import { IpcService } from "./ipc.service"; export class NotificationService{ private static instance: NotificationService; - public readonly notifications$: BehaviorSubject; + public static getInstance(): NotificationService{ if(!NotificationService.instance){ NotificationService.instance = new NotificationService(); } return NotificationService.instance; } + public readonly notifications$: BehaviorSubject; + + private readonly ipc: IpcService; + private readonly i18n: I18nService; + private constructor(){ + this.ipc = IpcService.getInstance(); + this.i18n = I18nService.getInstance(); this.notifications$ = new BehaviorSubject([]); } @@ -46,6 +56,14 @@ export class NotificationService{ return this.notify(notification); } + public notifySystem(options: SystemNotificationOptions){ + + options.body = options.body && this.i18n.translate(options.body); + options.title = options.title && this.i18n.translate(options.title); + + this.ipc.sendLazy("notify-system", {args: options}); + } + } export interface Notification { diff --git a/src/renderer/windows/OneClick/OneClickDownloadMap.tsx b/src/renderer/windows/OneClick/OneClickDownloadMap.tsx index ca4c7308..4fbd0932 100644 --- a/src/renderer/windows/OneClick/OneClickDownloadMap.tsx +++ b/src/renderer/windows/OneClick/OneClickDownloadMap.tsx @@ -4,6 +4,7 @@ import { BsmImage } from "renderer/components/shared/bsm-image.component"; import TitleBar from "renderer/components/title-bar/title-bar.component"; import { IpcService } from "renderer/services/ipc.service"; import { MapsDownloaderService } from "renderer/services/maps-downloader.service"; +import { NotificationService } from "renderer/services/notification.service"; import { ProgressBarService } from "renderer/services/progress-bar.service"; import { ThemeService } from "renderer/services/theme.service"; import { BeatSaverService } from "renderer/services/thrird-partys/beat-saver.service"; @@ -20,6 +21,7 @@ export default function OneClickDownloadMap() { const themeService = ThemeService.getInstance(); const progressBar = ProgressBarService.getInstance(); const windows = WindowManagerService.getInstance(); + const notification = NotificationService.getInstance(); const [mapInfo, setMapInfo] = useState(null); @@ -38,6 +40,7 @@ export default function OneClickDownloadMap() { const promise = new Promise(async (resolve, reject) => { try{ + const ipcRes = await ipc.send<{id: string, isHash: boolean}>("one-click-map-info"); if(!ipcRes.success){ return reject(ipcRes.error); } @@ -60,6 +63,16 @@ export default function OneClickDownloadMap() { }); + // TODO TRANSLATE + + promise.catch(() => { + notification.notifySystem({title: "Erreur", body: "Une erreur c'est produite lors de l'installation de la map"}); + }) + + promise.then(() => { + notification.notifySystem({title: "OneClick", body: "Installation de la map terminé"}); + }); + promise.finally(() =>{ windows.close("oneclick-download-map.html"); }); diff --git a/src/renderer/windows/OneClick/OneClickDownloadModel.tsx b/src/renderer/windows/OneClick/OneClickDownloadModel.tsx index bf7f7efa..d078196b 100644 --- a/src/renderer/windows/OneClick/OneClickDownloadModel.tsx +++ b/src/renderer/windows/OneClick/OneClickDownloadModel.tsx @@ -4,6 +4,7 @@ import { BsmImage } from "renderer/components/shared/bsm-image.component"; import TitleBar from "renderer/components/title-bar/title-bar.component"; import { IpcService } from "renderer/services/ipc.service"; import { ModelDownloaderService } from "renderer/services/model-downloader.service"; +import { NotificationService } from "renderer/services/notification.service"; import { ProgressBarService } from "renderer/services/progress-bar.service"; import { ThemeService } from "renderer/services/theme.service"; import { ModelSaberService } from "renderer/services/thrird-partys/model-saber.service"; @@ -19,6 +20,7 @@ export default function OneClickDownloadModel() { const progress = ProgressBarService.getInstance(); const modelDownloader = ModelDownloaderService.getInstance(); const windows = WindowManagerService.getInstance(); + const notification = NotificationService.getInstance(); const [model, setModel] = useState(null); @@ -48,6 +50,16 @@ export default function OneClickDownloadModel() { }); + // TODO TRANSLATE + + promise.catch(() => { + notification.notifySystem({title: "Erreur", body: "Une erreur c'est produite lors de l'installation du model"}); + }) + + promise.then(() => { + notification.notifySystem({title: "OneClick", body: "Installation du model terminé"}); + }); + promise.finally(() => windows.close("oneclick-download-model.html")); return () => { diff --git a/src/renderer/windows/OneClick/OneClickDownloadPlaylist.tsx b/src/renderer/windows/OneClick/OneClickDownloadPlaylist.tsx index 250f20b3..89fb3056 100644 --- a/src/renderer/windows/OneClick/OneClickDownloadPlaylist.tsx +++ b/src/renderer/windows/OneClick/OneClickDownloadPlaylist.tsx @@ -5,6 +5,7 @@ import { BsmImage } from "renderer/components/shared/bsm-image.component"; import TitleBar from "renderer/components/title-bar/title-bar.component"; import { useObservable } from "renderer/hooks/use-observable.hook"; import { IpcService } from "renderer/services/ipc.service" +import { NotificationService } from "renderer/services/notification.service"; import { PlaylistDownloaderService } from "renderer/services/playlist-downloader.service"; import { ThemeService } from "renderer/services/theme.service"; import { BeatSaverService } from "renderer/services/thrird-partys/beat-saver.service"; @@ -21,6 +22,7 @@ export default function OneClickDownloadPlaylist() { const playlistDownloader = PlaylistDownloaderService.getInstance(); const mapsContainer = useRef(null); const windows = WindowManagerService.getInstance(); + const notification = NotificationService.getInstance(); const [playlist, setPlaylist] = useState(null); const downloadedMap = useObservable(playlistDownloader.progress$.pipe(filter(download => !!download), map(download => [...(download.downloadedMaps ?? []), download?.current])), []); @@ -49,6 +51,16 @@ export default function OneClickDownloadPlaylist() { }); + // TODO TRANSLATE + + promise.catch(() => { + notification.notifySystem({title: "Erreur", body: "Une erreur c'est produite lors de l'installation de la playlist"}); + }) + + promise.then(() => { + notification.notifySystem({title: "OneClick", body: "Installation de la playlist terminé"}); + }); + promise.finally(() => { windows.close("oneclick-download-playlist.html"); }); diff --git a/src/shared/models/notification/system-notification.model.ts b/src/shared/models/notification/system-notification.model.ts new file mode 100644 index 00000000..66f9e0c2 --- /dev/null +++ b/src/shared/models/notification/system-notification.model.ts @@ -0,0 +1,4 @@ +export interface SystemNotificationOptions { + title?: string, + body?: string +} \ No newline at end of file