From 9d7cd708c7ea168e0b7dc047c234eca6832cb322 Mon Sep 17 00:00:00 2001 From: MathieuG-P Date: Sun, 25 Dec 2022 22:18:28 +0100 Subject: [PATCH] deep-links models fully work AND add translations --- .eslintrc.js | 3 +- assets/jsons/translations/en.json | 8 +++ assets/jsons/translations/es.json | 8 +++ assets/jsons/translations/fr.json | 8 +++ src/main/ipcs/bs-model-ipcs.ts | 45 ++++++++++++ src/main/ipcs/index.ts | 3 +- src/main/main.ts | 2 +- .../local-models-manager.service.ts | 71 +++++++++++++++++-- .../title-bar/title-bar.component.tsx | 4 +- .../pages/settings-page.component.tsx | 31 +++++--- .../services/model-downloader.service.ts | 2 +- .../services/models-manager.service.ts | 38 ++++++++++ .../windows/OneClick/OneClickDownloadMap.tsx | 3 +- .../OneClick/OneClickDownloadModel.tsx | 10 ++- 14 files changed, 210 insertions(+), 26 deletions(-) create mode 100644 src/renderer/services/models-manager.service.ts diff --git a/.eslintrc.js b/.eslintrc.js index 2c33df04..97c4632c 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -34,7 +34,8 @@ module.exports = { "no-param-reassign": "off", "no-useless-escape": "off", "jsx-a11y/no-noninteractive-element-interactions": "off", - "no-async-promise-executor": "off" + "no-async-promise-executor": "off", + "new-cap": "off" }, parserOptions: { ecmaVersion: 2020, diff --git a/assets/jsons/translations/en.json b/assets/jsons/translations/en.json index 77732884..cc8521c9 100644 --- a/assets/jsons/translations/en.json +++ b/assets/jsons/translations/en.json @@ -6,6 +6,7 @@ "mods": "Mods", "maps": "Maps", "playlists":"Playlists", + "models": "Models", "cancel": "Cancel", "delete": "Delete" }, @@ -94,6 +95,13 @@ "description":"Change the default folder for Beat Saber versions and other upcoming features.", "choose-folder":"Choose folder" }, + "additional-content":{ + "title":"Additional content", + "description":"Additional content allows you to customize BeatSaber!", + "deep-links":{ + "sub-title":"OneClick installations" + } + }, "language":{ "title":"Language", "description":"Select a language.", diff --git a/assets/jsons/translations/es.json b/assets/jsons/translations/es.json index 71d813ab..6dbf1ed3 100644 --- a/assets/jsons/translations/es.json +++ b/assets/jsons/translations/es.json @@ -6,6 +6,7 @@ "mods": "Mods", "maps": "Mapas", "playlists":"Playlists", + "models": "Modelos", "cancel": "Cancelar", "delete": "Borrar" }, @@ -94,6 +95,13 @@ "description":"Cambia la carpeta por defecto para las versiones de Beat Saber y otras características próximas.", "choose-folder":"Elija la carpeta" }, + "additional-content":{ + "title":"Contenido adicional", + "description":"¡El contenido adicional te permite personalizar BeatSaber!", + "deep-links":{ + "sub-title":"Instalaciones OneClick" + } + }, "language":{ "title":"Idioma", "description":"Selecciona un idioma.", diff --git a/assets/jsons/translations/fr.json b/assets/jsons/translations/fr.json index a95365bb..a4ab8f94 100644 --- a/assets/jsons/translations/fr.json +++ b/assets/jsons/translations/fr.json @@ -6,6 +6,7 @@ "mods": "Mods", "maps": "Maps", "playlists":"Playlists", + "models": "Modèles", "cancel": "Annuler", "delete": "Supprimer" }, @@ -94,6 +95,13 @@ "description":"Change le dossier par défaut pour les versions de Beat Saber et d'autres fonctionnalités à venir.", "choose-folder":"Choisir un dossier" }, + "additional-content":{ + "title":"Contenus additionnels", + "description":"Les contenus additionnels te permet de personaliser BeatSaber !", + "deep-links":{ + "sub-title":"Installations OneClick" + } + }, "language":{ "title":"Langage", "description":"Sélectionne un langage.", diff --git a/src/main/ipcs/bs-model-ipcs.ts b/src/main/ipcs/bs-model-ipcs.ts index e1f08aec..a56c9a81 100644 --- a/src/main/ipcs/bs-model-ipcs.ts +++ b/src/main/ipcs/bs-model-ipcs.ts @@ -13,4 +13,49 @@ ipcMain.on("one-click-install-model", async (event, request: IpcRequest }).catch(e => { utils.ipcSend(request.responceChannel, {success: false, error: e}); }) +}); + +ipcMain.on("register-models-deep-link", async (event, request: IpcRequest) => { + + const maps = LocalModelsManagerService.getInstance(); + const utils = UtilsService.getInstance(); + + try{ + const res = maps.enableDeepLinks(); + utils.ipcSend(request.responceChannel, {success: true, data: res}); + } + catch(e){ + utils.ipcSend(request.responceChannel, {success: false}); + } + +}); + +ipcMain.on("unregister-models-deep-link", async (event, request: IpcRequest) => { + + const maps = LocalModelsManagerService.getInstance(); + const utils = UtilsService.getInstance(); + + try{ + const res = maps.disableDeepLinks(); + utils.ipcSend(request.responceChannel, {success: true, data: res}); + } + catch(e){ + utils.ipcSend(request.responceChannel, {success: false}); + } + +}); + +ipcMain.on("is-models-deep-links-enabled", async (event, request: IpcRequest) => { + + const maps = LocalModelsManagerService.getInstance(); + const utils = UtilsService.getInstance(); + + try{ + const res = maps.isDeepLinksEnabled(); + utils.ipcSend(request.responceChannel, {success: true, data: res}); + } + catch(e){ + utils.ipcSend(request.responceChannel, {success: false}); + } + }); \ No newline at end of file diff --git a/src/main/ipcs/index.ts b/src/main/ipcs/index.ts index 9889d131..0fc184bf 100644 --- a/src/main/ipcs/index.ts +++ b/src/main/ipcs/index.ts @@ -10,4 +10,5 @@ import './bs-mods-ipcs'; import './bs-maps-ipcs'; import './beat-saver-ipcs'; import './bs-playlist-ipcs'; -import './model-saber.ipcs'; \ No newline at end of file +import './model-saber.ipcs'; +import './bs-model-ipcs'; \ No newline at end of file diff --git a/src/main/main.ts b/src/main/main.ts index 5fd5c7f7..c368799c 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -89,7 +89,7 @@ else{ app.whenReady().then(() => { - process.argv.push("modelsaber://saber/1670981586/r_Happy Crimbo!.saber"); // to force deep-link (oneClick map) + // process.argv.push("modelsaber://platform/1671435302/The grinch mountain.plat"); // to force deep-link (oneClick map) initServicesMustBeInitialized(); diff --git a/src/main/services/additional-content/local-models-manager.service.ts b/src/main/services/additional-content/local-models-manager.service.ts index a5838708..268ec776 100644 --- a/src/main/services/additional-content/local-models-manager.service.ts +++ b/src/main/services/additional-content/local-models-manager.service.ts @@ -6,6 +6,10 @@ import { UtilsService } from "../utils.service"; import { WindowManagerService } from "../window-manager.service"; import { MSModel, MSModelType } from "shared/models/model-saber/model-saber.model"; import { BSVersion } from "shared/bs-version.interface"; +import { BSLocalVersionService } from "../bs-local-version.service"; +import path from "path"; +import { RequestService } from "../request.service"; +import { copyFileSync, constants } from "fs-extra"; export class LocalModelsManagerService { @@ -20,14 +24,25 @@ export class LocalModelsManagerService { ModelSaber: "modelsaber", }; + private readonly MODEL_TYPE_FOLDER: Record, string> = { + avatar: "CustomAvatars", + bloq: "CustomNotes", + platform: "CustomPlatforms", + saber: "CustomSabers" + } + private readonly deepLink: DeepLinkService; private readonly utils: UtilsService; private readonly windows: WindowManagerService; + private readonly localVersion: BSLocalVersionService; + private readonly request: RequestService; private constructor(){ this.deepLink = DeepLinkService.getInstance(); this.utils = UtilsService.getInstance(); this.windows = WindowManagerService.getInstance(); + this.localVersion = BSLocalVersionService.getInstance(); + this.request = RequestService.getInstance(); this.deepLink.addLinkOpenedListener(this.DEEP_LINKS.ModelSaber, (link) => { log.info("DEEP-LINK RECEIVED FROM", this.DEEP_LINKS.ModelSaber, link); @@ -42,8 +57,7 @@ export class LocalModelsManagerService { private openOneClickDownloadModelWindow(id: string, type: string){ - // TODO make once - ipcMain.on("one-click-model-info", async (event, req: IpcRequest) => { + ipcMain.once("one-click-model-info", async (event, req: IpcRequest) => { this.utils.ipcSend(req.responceChannel, {success: true, data: {id, type}}); }); @@ -51,16 +65,61 @@ export class LocalModelsManagerService { } - private getModelFolderPath(type: MSModelType, version?: BSVersion): Promise{ - // TODO : return path of model type of version + private async getModelFolderPath(type: MSModelType, version?: BSVersion): Promise{ + + if(!version){ throw "will be implemented whith models management" } + if(type === "misc"){ throw "model type not supported"; } + + const versionPath = await this.localVersion.getVersionPath(version); + const modelFolderPath = path.join(versionPath, this.MODEL_TYPE_FOLDER[type]); + + this.utils.createFolderIfNotExist(modelFolderPath); + + return modelFolderPath; + } - public downloadModel(model: MSModel, version: BSVersion): Promise{ - // TODO : Download model to the version + public async downloadModel(model: MSModel, version: BSVersion): Promise{ + + const modelFolder = await this.getModelFolderPath(model.type, version); + const modelDest = path.join(modelFolder, path.basename(model.download)); + + return this.request.downloadFile(model.download, modelDest); + } public async oneClickDownloadModel(model: MSModel): Promise{ + if(!model){ return; } + + const versions = await this.localVersion.getInstalledVersions(); + + if(versions?.length === 0){ return; } + + const fisrtVersion = versions.shift(); + + const downloaded = await this.downloadModel(model, fisrtVersion); + + for(const version of versions){ + + const modelDest = path.join(await this.getModelFolderPath(model.type, version), path.basename(downloaded)); + + copyFileSync(downloaded, modelDest); + + } + + } + + public enableDeepLinks(): boolean{ + return Array.from(Object.values(this.DEEP_LINKS)).every(link => this.deepLink.registerDeepLink(link)); + } + + public disableDeepLinks(): boolean{ + return Array.from(Object.values(this.DEEP_LINKS)).every(link => this.deepLink.unRegisterDeepLink(link)); + } + + public isDeepLinksEnabled(): boolean{ + return Array.from(Object.values(this.DEEP_LINKS)).every(link => this.deepLink.isDeepLinkRegistred(link)); } } \ No newline at end of file diff --git a/src/renderer/components/title-bar/title-bar.component.tsx b/src/renderer/components/title-bar/title-bar.component.tsx index 6e5695b7..78998983 100644 --- a/src/renderer/components/title-bar/title-bar.component.tsx +++ b/src/renderer/components/title-bar/title-bar.component.tsx @@ -60,11 +60,11 @@ export default function TitleBar({template = "index.html"} : {template: AppWindo if(template === "launcher.html"){ return (
-
+
) } - if(template === "oneclick-download-map.html" || template === "oneclick-download-playlist.html"){ + if(template === "oneclick-download-map.html" || template === "oneclick-download-playlist.html" || template === "oneclick-download-model.html"){ return (
diff --git a/src/renderer/pages/settings-page.component.tsx b/src/renderer/pages/settings-page.component.tsx index 36055b2e..e66421ae 100644 --- a/src/renderer/pages/settings-page.component.tsx +++ b/src/renderer/pages/settings-page.component.tsx @@ -3,7 +3,7 @@ import SettingColorChooser from "renderer/components/settings/setting-color-choo import { SettingContainer } from "renderer/components/settings/setting-container.component"; import { RadioItem, SettingRadioArray } from "renderer/components/settings/setting-radio-array.component"; import { BsmButton } from "renderer/components/shared/bsm-button.component"; -import { BsmIcon, BsmIconType } from "renderer/components/svgs/bsm-icon.component"; +import { BsmIconType } from "renderer/components/svgs/bsm-icon.component"; import { DefaultConfigKey, ThemeConfig } from "renderer/config/default-configuration.config"; import { useObservable } from "renderer/hooks/use-observable.hook"; import { useThemeColor } from "renderer/hooks/use-theme-color.hook"; @@ -29,6 +29,8 @@ import scoreSaberIcon from "../../../assets/images/third-party-icons/score-saber import Tippy from '@tippyjs/react'; import { MapsManagerService } from "renderer/services/maps-manager.service"; import { PlaylistsManagerService } from "renderer/services/playlists-manager.service"; +import { ModelsManagerService } from "renderer/services/models-manager.service"; +import { useTranslation } from "renderer/hooks/use-translation.hook"; export function SettingsPage() { @@ -44,6 +46,7 @@ export function SettingsPage() { const linkOpener: LinkOpenerService = LinkOpenerService.getInstance(); const mapsManager = MapsManagerService.getInstance(); const playlistsManager = PlaylistsManagerService.getInstance(); + const modelsManager = ModelsManagerService.getInstance(); const {firstColor, secondColor} = useThemeColor(); const sessionExist = useObservable(authService.sessionExist$); @@ -63,15 +66,18 @@ export function SettingsPage() { const [installationFolder, setInstallationFolder] = useState(null); const [showSupporters, setShowSupporters] = useState(false); const [mapDeepLinksEnabled, setMapDeepLinksEnabled] = useState(false); - const [playlistsDeepLinkEnabled, setPlaylistsDeeppLinkEnabled] = useState(false) + const [playlistsDeepLinkEnabled, setPlaylistsDeepLinkEnabled] = useState(false); + const [modelsDeepLinkEnabled, setModelsDeepLinkEnabled] = useState(false); const [appVersion, setAppVersion] = useState(""); const nav = useNavigate(); + const t = useTranslation(); useEffect(() => { loadInstallationFolder(); ipcService.send("current-version").then(res => setAppVersion(res.data)); mapsManager.isDeepLinksEnabled().then(enabled => setMapDeepLinksEnabled(() => enabled)); - playlistsManager.isDeepLinksEnabled().then(enabled => setPlaylistsDeeppLinkEnabled(() => enabled)); + playlistsManager.isDeepLinksEnabled().then(enabled => setPlaylistsDeepLinkEnabled(() => enabled)); + modelsManager.isDeepLinksEnabled().then(enabled => setModelsDeepLinkEnabled(() => enabled)) }, []); const resetColors = () => { @@ -149,7 +155,11 @@ export function SettingsPage() { } const tooglePlaylistsDeepLinks = () => { - playlistsManager.toogleDeepLinks().finally(() => playlistsManager.isDeepLinksEnabled().then(enabled => setPlaylistsDeeppLinkEnabled(() => enabled))); + playlistsManager.toogleDeepLinks().finally(() => playlistsManager.isDeepLinksEnabled().then(enabled => setPlaylistsDeepLinkEnabled(() => enabled))); + } + + const toogleModelsDeepLinks = () => { + modelsManager.toogleDeepLinks().finally(() => modelsManager.isDeepLinksEnabled().then(enabled => setModelsDeepLinkEnabled(() => enabled))); } return ( @@ -187,14 +197,15 @@ export function SettingsPage() {
- + {/* TODO TRANSLATE */} + - +
  • - Maps + {t("misc.maps")}
    @@ -211,7 +222,7 @@ export function SettingsPage() {
  • - Playlists + {t("misc.playlists")}
    @@ -221,8 +232,8 @@ export function SettingsPage() {
  • - - Modèles + + {t("misc.models")}
    diff --git a/src/renderer/services/model-downloader.service.ts b/src/renderer/services/model-downloader.service.ts index d4827eb3..6cefcd2f 100644 --- a/src/renderer/services/model-downloader.service.ts +++ b/src/renderer/services/model-downloader.service.ts @@ -23,7 +23,7 @@ export class ModelDownloaderService { public async oneClickInstallModel(model: MSModel): Promise{ - this.progress.showFake(0.07); + this.progress.showFake(0.04); const res = await this.ipc.send("one-click-install-model", {args: model}); diff --git a/src/renderer/services/models-manager.service.ts b/src/renderer/services/models-manager.service.ts new file mode 100644 index 00000000..b3802253 --- /dev/null +++ b/src/renderer/services/models-manager.service.ts @@ -0,0 +1,38 @@ +import { IpcService } from "./ipc.service"; + +export class ModelsManagerService { + + private static instance: ModelsManagerService; + + public static getInstance(): ModelsManagerService{ + if(!ModelsManagerService.instance){ ModelsManagerService.instance = new ModelsManagerService(); } + return ModelsManagerService.instance; + } + + private readonly ipc: IpcService; + + private constructor(){ + this.ipc = IpcService.getInstance(); + } + + public isDeepLinksEnabled(): Promise{ + return this.ipc.send("is-models-deep-links-enabled").then(res => ( + res.success ? res.data : false + )); + } + + public async toogleDeepLinks(): Promise{ + + const isEnabled = await this.isDeepLinksEnabled(); + + const channel = isEnabled ? "unregister-models-deep-link" : "register-models-deep-link"; + + const res = await this.ipc.send(channel); + + return res.success ? res.data : false; + + } + + + +} \ No newline at end of file diff --git a/src/renderer/windows/OneClick/OneClickDownloadMap.tsx b/src/renderer/windows/OneClick/OneClickDownloadMap.tsx index 0987d879..ca4c7308 100644 --- a/src/renderer/windows/OneClick/OneClickDownloadMap.tsx +++ b/src/renderer/windows/OneClick/OneClickDownloadMap.tsx @@ -24,6 +24,7 @@ export default function OneClickDownloadMap() { const [mapInfo, setMapInfo] = useState(null); const cover = mapInfo ? mapInfo.versions.at(0).coverURL : null; + const title = mapInfo ? mapInfo.name : null; useEffect(() => { @@ -75,7 +76,7 @@ export default function OneClickDownloadMap() {
    -

    {mapInfo?.name ?? "Chargement de la map..."}

    +

    {title}

    diff --git a/src/renderer/windows/OneClick/OneClickDownloadModel.tsx b/src/renderer/windows/OneClick/OneClickDownloadModel.tsx index 6ed3be5c..bf7f7efa 100644 --- a/src/renderer/windows/OneClick/OneClickDownloadModel.tsx +++ b/src/renderer/windows/OneClick/OneClickDownloadModel.tsx @@ -3,9 +3,11 @@ import { BsmProgressBar } from "renderer/components/progress-bar/bsm-progress-ba 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 { ProgressBarService } from "renderer/services/progress-bar.service"; import { ThemeService } from "renderer/services/theme.service"; import { ModelSaberService } from "renderer/services/thrird-partys/model-saber.service"; +import { WindowManagerService } from "renderer/services/window-manager.service"; import { MSModel } from "shared/models/model-saber/model-saber.model"; import defaultImage from '../../../../assets/images/default-version-img.jpg' @@ -15,6 +17,8 @@ export default function OneClickDownloadModel() { const themeService = ThemeService.getInstance(); const modelSaber = ModelSaberService.getInstance(); const progress = ProgressBarService.getInstance(); + const modelDownloader = ModelDownloaderService.getInstance(); + const windows = WindowManagerService.getInstance(); const [model, setModel] = useState(null); @@ -40,11 +44,11 @@ export default function OneClickDownloadModel() { progress.open(); - // Download model + resolve(modelDownloader.oneClickInstallModel(model)); }); - promise.finally(() => console.log("close")); + promise.finally(() => windows.close("oneclick-download-model.html")); return () => { sub.unsubscribe(); @@ -56,7 +60,7 @@ export default function OneClickDownloadModel() {
    {cover && }
    - +

    {title}