From bdbdcfb1b4d68ca70f328bb71469bd1022c43b87 Mon Sep 17 00:00:00 2001 From: MathieuG-P <40181755+Zagrios@users.noreply.github.com> Date: Sat, 29 Apr 2023 15:24:45 +0200 Subject: [PATCH] [feature-136] Models panel + models linkage + models linkage modals + some cleaning --- assets/jsons/translations/de.json | 82 +++++++++++++++++ assets/jsons/translations/en.json | 82 +++++++++++++++++ assets/jsons/translations/es.json | 82 +++++++++++++++++ assets/jsons/translations/fr.json | 82 +++++++++++++++++ .../local-models-manager.service.ts | 13 +-- .../models/link-models-modal.component.tsx | 32 +++++++ .../models/unlink-models-modal.component.tsx | 30 ++++++ .../share-folders-modal.component.tsx | 2 - .../models-panel.component.tsx | 26 ++++++ .../models-tabs-navbar.component.tsx | 92 +++++++++++++++++++ src/renderer/hooks/use-constant.hook.ts | 13 +++ src/renderer/hooks/use-on-update.hook.ts | 7 ++ .../pages/settings-page.component.tsx | 2 +- .../pages/version-viewer.component.tsx | 6 +- src/renderer/services/maps-manager.service.ts | 4 +- .../models-manager.service.ts | 82 +++++++++++++++++ .../services/models-manager.service.ts | 34 ------- .../services/version-folder-linker.service.ts | 9 +- src/renderer/windows/App.tsx | 2 +- .../models/model-saber/model-saber.model.ts | 14 ++- .../models/model-saber/models-constants.ts | 8 ++ 21 files changed, 649 insertions(+), 55 deletions(-) create mode 100644 src/renderer/components/modal/modal-types/models/link-models-modal.component.tsx create mode 100644 src/renderer/components/modal/modal-types/models/unlink-models-modal.component.tsx create mode 100644 src/renderer/components/models-management/models-panel.component.tsx create mode 100644 src/renderer/components/models-management/models-tabs-navbar.component.tsx create mode 100644 src/renderer/hooks/use-constant.hook.ts create mode 100644 src/renderer/hooks/use-on-update.hook.ts create mode 100644 src/renderer/services/models-management/models-manager.service.ts delete mode 100644 src/renderer/services/models-manager.service.ts create mode 100644 src/shared/models/model-saber/models-constants.ts diff --git a/assets/jsons/translations/de.json b/assets/jsons/translations/de.json index f7e211b3..36a61941 100644 --- a/assets/jsons/translations/de.json +++ b/assets/jsons/translations/de.json @@ -597,6 +597,88 @@ "unlink-folder": "Ordner Verknüpfung Aufheben", "link-all": "Alle verlinken" } + }, + "models": { + "link-models": { + "avatar": { + "title": "Avatare verbinden", + "desc": "Das Verbinden von Avataren ermöglicht das Teilen von Avataren zwischen allen Versionen. Sobald verbunden, wird diese Version von den geteilten Avataren profitieren", + "info": "Das Hinzufügen und Entfernen von Avataren wird ebenfalls geteilt", + "keep-models": { + "label": "Avatare behalten", + "title": "Das Behalten der Avatare wird die Avatare der aktuellen Version in den Ordner der geteilten Avatare verschieben. Andernfalls gehen sie verloren" + }, + "valid-btn": "Avatare verbinden" + }, + "saber": { + "title": "Säbel verbinden", + "desc": "Das Verbinden von Säbeln ermöglicht das Teilen von Säbeln zwischen allen Versionen. Sobald verbunden, wird diese Version von den geteilten Säbeln profitieren", + "info": "Das Hinzufügen und Entfernen von Säbeln wird ebenfalls geteilt", + "keep-models": { + "label": "Säbel behalten", + "title": "Das Behalten der Säbel wird die Säbel der aktuellen Version in den Ordner der geteilten Säbel verschieben. Andernfalls gehen sie verloren" + }, + "valid-btn": "Säbel verbinden" + }, + "platform": { + "title": "Plattformen verbinden", + "desc": "Das Verbinden von Plattformen ermöglicht das Teilen von Plattformen zwischen allen Versionen. Sobald verbunden, wird diese Version von den geteilten Plattformen profitieren", + "info": "Das Hinzufügen und Entfernen von Plattformen wird ebenfalls geteilt", + "keep-models": { + "label": "Plattformen behalten", + "title": "Das Behalten der Plattformen wird die Plattformen der aktuellen Version in den Ordner der geteilten Plattformen verschieben. Andernfalls gehen sie verloren" + }, + "valid-btn": "Plattformen verbinden" + }, + "bloq": { + "title": "Bloqs verbinden", + "desc": "Das Verbinden von Bloqs ermöglicht das Teilen von Bloqs zwischen allen Versionen. Sobald verbunden, wird diese Version von den geteilten Bloqs profitieren", + "info": "Das Hinzufügen und Entfernen von Bloqs wird ebenfalls geteilt", + "keep-models": { + "label": "Bloqs behalten", + "title": "Das Behalten der Bloqs wird die Bloqs der aktuellen Version in den Ordner der geteilten Bloqs verschieben. Andernfalls gehen sie verloren" + }, + "valid-btn": "Bloqs verbinden" + } + }, + "unlink-models": { + "avatar": { + "title": "Avatare trennen", + "desc": "Achtung, das Trennen der Avatare wird die Nutzung der geteilten Avatare für diese Version nicht mehr ermöglichen.", + "keep-models": { + "label": "Avatare behalten", + "title": "Das Behalten der Avatare wird eine Kopie der geteilten Avatare für die aktuelle Version erstellen. Andernfalls wird kein Avatar für diese Version aufbewahrt." + }, + "valid-btn": "Avatare trennen" + }, + "saber": { + "title": "Säbel trennen", + "desc": "Achtung, das Trennen der Säbel wird die Nutzung der geteilten Säbel für diese Version nicht mehr ermöglichen.", + "keep-models": { + "label": "Säbel behalten", + "title": "Das Behalten der Säbel wird eine Kopie der geteilten Säbel für die aktuelle Version erstellen. Andernfalls wird kein Säbel für diese Version aufbewahrt." + }, + "valid-btn": "Säbel trennen" + }, + "platform": { + "title": "Plattformen trennen", + "desc": "Achtung, das Trennen der Plattformen wird die Nutzung der geteilten Plattformen für diese Version nicht mehr ermöglichen.", + "keep-models": { + "label": "Plattformen behalten", + "title": "Das Behalten der Plattformen wird eine Kopie der geteilten Plattformen für die aktuelle Version erstellen. Andernfalls wird keine Plattform für diese Version aufbewahrt." + }, + "valid-btn": "Plattformen trennen" + }, + "bloq": { + "title": "Bloqs trennen", + "desc": "Achtung, das Trennen der Bloqs wird die Nutzung der geteilten Bloqs für diese Version nicht mehr ermöglichen.", + "keep-models": { + "label": "Bloqs behalten", + "title": "Das Behalten der Bloqs wird eine Kopie der geteilten Bloqs für die aktuelle Version erstellen. Andernfalls wird kein Bloq für diese Version aufbewahrt." + }, + "valid-btn": "Bloqs trennen" + } + } } }, "maps": { diff --git a/assets/jsons/translations/en.json b/assets/jsons/translations/en.json index f8cd62ee..5e9fe0b5 100644 --- a/assets/jsons/translations/en.json +++ b/assets/jsons/translations/en.json @@ -597,6 +597,88 @@ "unlink-folder": "Unlink Folder", "link-all": "Link all" } + }, + "models": { + "link-models": { + "avatar": { + "title": "Link Avatars", + "desc": "Linking avatars allows sharing avatars between all versions. Once linked, this version will benefit from the shared avatars", + "info": "Adding and deleting avatars will also be shared", + "keep-models": { + "label": "Keep Avatars", + "title": "Keeping the avatars will move the avatars from the current version to the shared avatars folder. Otherwise, they will be lost" + }, + "valid-btn": "Link Avatars" + }, + "saber": { + "title": "Link Sabers", + "desc": "Linking sabers allows sharing sabers between all versions. Once linked, this version will benefit from the shared sabers", + "info": "Adding and deleting sabers will also be shared", + "keep-models": { + "label": "Keep Sabers", + "title": "Keeping the sabers will move the sabers from the current version to the shared sabers folder. Otherwise, they will be lost" + }, + "valid-btn": "Link Sabers" + }, + "platform": { + "title": "Link Platforms", + "desc": "Linking platforms allows sharing platforms between all versions. Once linked, this version will benefit from the shared platforms", + "info": "Adding and deleting platforms will also be shared", + "keep-models": { + "label": "Keep Platforms", + "title": "Keeping the platforms will move the platforms from the current version to the shared platforms folder. Otherwise, they will be lost" + }, + "valid-btn": "Link Platforms" + }, + "bloq": { + "title": "Link Bloqs", + "desc": "Linking bloqs allows sharing bloqs between all versions. Once linked, this version will benefit from the shared bloqs", + "info": "Adding and deleting bloqs will also be shared", + "keep-models": { + "label": "Keep Bloqs", + "title": "Keeping the bloqs will move the bloqs from the current version to the shared bloqs folder. Otherwise, they will be lost" + }, + "valid-btn": "Link Bloqs" + } + }, + "unlink-models": { + "avatar": { + "title": "Unlink Avatars", + "desc": "Be careful, unlinking avatars will no longer allow the use of shared avatars for this version.", + "keep-models": { + "label": "Keep Avatars", + "title": "Keeping the avatars will create a copy of the shared avatars for the current version. Otherwise, no avatars will be kept for this version." + }, + "valid-btn": "Unlink Avatars" + }, + "saber": { + "title": "Unlink Sabers", + "desc": "Be careful, unlinking sabers will no longer allow the use of shared sabers for this version.", + "keep-models": { + "label": "Keep Sabers", + "title": "Keeping the sabers will create a copy of the shared sabers for the current version. Otherwise, no sabers will be kept for this version." + }, + "valid-btn": "Unlink Sabers" + }, + "platform": { + "title": "Unlink Platforms", + "desc": "Be careful, unlinking platforms will no longer allow the use of shared platforms for this version.", + "keep-models": { + "label": "Keep Platforms", + "title": "Keeping the platforms will create a copy of the shared platforms for the current version. Otherwise, no platforms will be kept for this version." + }, + "valid-btn": "Unlink Platforms" + }, + "bloq": { + "title": "Unlink Bloqs", + "desc": "Be careful, unlinking bloqs will no longer allow the use of shared bloqs for this version.", + "keep-models": { + "label": "Keep Bloqs", + "title": "Keeping the bloqs will create a copy of the shared bloqs for the current version. Otherwise, no bloqs will be kept for this version." + }, + "valid-btn": "Unlink Bloqs" + } + } } }, "maps": { diff --git a/assets/jsons/translations/es.json b/assets/jsons/translations/es.json index d66c78bc..d2265272 100644 --- a/assets/jsons/translations/es.json +++ b/assets/jsons/translations/es.json @@ -596,6 +596,88 @@ "unlink-folder": "Desenlazar Carpeta", "link-all": "Enlazar todo" } + }, + "models": { + "link-models": { + "avatar": { + "title": "Vincular avatares", + "desc": "La vinculación de avatares permite compartir avatares entre todas las versiones. Una vez vinculada, esta versión se beneficiará de los avatares compartidos", + "info": "La adición y eliminación de avatares también se compartirá", + "keep-models": { + "label": "Conservar los avatares", + "title": "Conservar los avatares moverá los avatares de la versión actual a la carpeta de avatares compartidos. De lo contrario, se perderán" + }, + "valid-btn": "Vincular avatares" + }, + "saber": { + "title": "Vincular sables", + "desc": "La vinculación de sables permite compartir sables entre todas las versiones. Una vez vinculada, esta versión se beneficiará de los sables compartidos", + "info": "La adición y eliminación de sables también se compartirá", + "keep-models": { + "label": "Conservar los sables", + "title": "Conservar los sables moverá los sables de la versión actual a la carpeta de sables compartidos. De lo contrario, se perderán" + }, + "valid-btn": "Vincular sables" + }, + "platform": { + "title": "Vincular plataformas", + "desc": "La vinculación de plataformas permite compartir plataformas entre todas las versiones. Una vez vinculada, esta versión se beneficiará de las plataformas compartidas", + "info": "La adición y eliminación de plataformas también se compartirá", + "keep-models": { + "label": "Conservar las plataformas", + "title": "Conservar las plataformas moverá las plataformas de la versión actual a la carpeta de plataformas compartidas. De lo contrario, se perderán" + }, + "valid-btn": "Vincular plataformas" + }, + "bloq": { + "title": "Vincular bloqs", + "desc": "La vinculación de bloqs permite compartir bloqs entre todas las versiones. Una vez vinculada, esta versión se beneficiará de los bloqs compartidos", + "info": "La adición y eliminación de bloqs también se compartirá", + "keep-models": { + "label": "Conservar los bloqs", + "title": "Conservar los bloqs moverá los bloqs de la versión actual a la carpeta de bloqs compartidos. De lo contrario, se perderán" + }, + "valid-btn": "Vincular bloqs" + } + }, + "unlink-models": { + "avatar": { + "title": "Desvincular avatares", + "desc": "Atención, desvincular los avatares ya no permitirá el uso de avatares compartidos para esta versión.", + "keep-models": { + "label": "Conservar los avatares", + "title": "Conservar los avatares creará una copia de los avatares compartidos para la versión actual. En caso contrario, ningún avatar se conservará para esta versión." + }, + "valid-btn": "Desvincular avatares" + }, + "saber": { + "title": "Desvincular sables", + "desc": "Atención, desvincular los sables ya no permitirá el uso de sables compartidos para esta versión.", + "keep-models": { + "label": "Conservar los sables", + "title": "Conservar los sables creará una copia de los sables compartidos para la versión actual. En caso contrario, ningún sable se conservará para esta versión." + }, + "valid-btn": "Desvincular sables" + }, + "platform": { + "title": "Desvincular plataformas", + "desc": "Atención, desvincular las plataformas ya no permitirá el uso de plataformas compartidas para esta versión.", + "keep-models": { + "label": "Conservar las plataformas", + "title": "Conservar las plataformas creará una copia de las plataformas compartidas para la versión actual. En caso contrario, ninguna plataforma se conservará para esta versión." + }, + "valid-btn": "Desvincular plataformas" + }, + "bloq": { + "title": "Desvincular bloqs", + "desc": "Atención, desvincular los bloqs ya no permitirá el uso de bloqs compartidos para esta versión.", + "keep-models": { + "label": "Conservar los bloqs", + "title": "Conservar los bloqs creará una copia de los bloqs compartidos para la versión actual. En caso contrario, ningún bloq se conservará para esta versión." + }, + "valid-btn": "Desvincular bloqs" + } + } } }, "maps": { diff --git a/assets/jsons/translations/fr.json b/assets/jsons/translations/fr.json index 0dd59d84..079a564b 100644 --- a/assets/jsons/translations/fr.json +++ b/assets/jsons/translations/fr.json @@ -596,6 +596,88 @@ "unlink-folder": "Délier le dossier", "link-all": "Tout lier" } + }, + "models": { + "link-models":{ + "avatar": { + "title": "Lier les avatars", + "desc": "La liaison des avatars permet de partager les avatars entre toute les version. Une fois liée, cette version profitera des avatars partagés", + "info": "L'ajout et la suppression d'avatars sera également partagé", + "keep-models": { + "label": "Conserver les avatars", + "title": "Conserver les avatars déplacera les avatars de la version actuelle dans le dossier des avatars partagés. Dans le cas contraire ils seront perdus" + }, + "valid-btn": "Lier les avatars" + }, + "saber": { + "title": "Lier les sabres", + "desc": "La liaison des sabres permet de partager les sabres entre toute les version. Une fois liée, cette version profitera des sabres partagés", + "info": "L'ajout et la suppression de sabres sera également partagé", + "keep-models": { + "label": "Conserver les sabres", + "title": "Conserver les sabres déplacera les sabres de la version actuelle dans le dossier des sabres partagés. Dans le cas contraire ils seront perdus" + }, + "valid-btn": "Lier les sabres" + }, + "platform": { + "title": "Lier les plateformes", + "desc": "La liaison des plateformes permet de partager les plateformes entre toute les version. Une fois liée, cette version profitera des plateformes partagées", + "info": "L'ajout et la suppression de plateformes sera également partagé", + "keep-models": { + "label": "Conserver les plateformes", + "title": "Conserver les plateformes déplacera les plateformes de la version actuelle dans le dossier des plateformes partagées. Dans le cas contraire ils seront perdus" + }, + "valid-btn": "Lier les plateformes" + }, + "bloq": { + "title": "Lier les bloqs", + "desc": "La liaison des bloqs permet de partager les bloqs entre toute les version. Une fois liée, cette version profitera des bloqs partagés", + "info": "L'ajout et la suppression de bloqs sera également partagé", + "keep-models": { + "label": "Conserver les bloqs", + "title": "Conserver les bloqs déplacera les bloqs de la version actuelle dans le dossier des bloqs partagés. Dans le cas contraire ils seront perdus" + }, + "valid-btn": "Lier les bloqs" + } + }, + "unlink-models":{ + "avatar": { + "title": "Délier les avatars", + "desc": "Attention, délier les avatars ne permettra plus l'utilisation des avatars paratagés pour cette version.", + "keep-models": { + "label": "Conserver les avatars", + "title": "Conserver les avatars créera une copie des avatars partagés pour la version actuelle. Dans le cas contraire, aucun avatar ne sera conservé pour cette version." + }, + "valid-btn": "Délier les avatars" + }, + "saber": { + "title": "Délier les sabres", + "desc": "Attention, délier les sabres ne permettra plus l'utilisation des sabres paratagés pour cette version.", + "keep-models": { + "label": "Conserver les sabres", + "title": "Conserver les sabres créera une copie des sabres partagés pour la version actuelle. Dans le cas contraire, aucun sabre ne sera conservé pour cette version." + }, + "valid-btn": "Délier les sabres" + }, + "platform": { + "title": "Délier les plateformes", + "desc": "Attention, délier les plateformes ne permettra plus l'utilisation des plateformes paratagées pour cette version.", + "keep-models": { + "label": "Conserver les plateformes", + "title": "Conserver les plateformes créera une copie des plateformes partagées pour la version actuelle. Dans le cas contraire, aucune plateforme ne sera conservée pour cette version." + }, + "valid-btn": "Délier les plateformes" + }, + "bloq": { + "title": "Délier les bloqs", + "desc": "Attention, délier les bloqs ne permettra plus l'utilisation des bloqs paratagés pour cette version.", + "keep-models": { + "label": "Conserver les bloqs", + "title": "Conserver les bloqs créera une copie des bloqs partagés pour la version actuelle. Dans le cas contraire, aucun bloq ne sera conservé pour cette version." + }, + "valid-btn": "Délier les bloqs" + } + } } }, "maps": { 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 df329b9b..212732fe 100644 --- a/src/main/services/additional-content/local-models-manager.service.ts +++ b/src/main/services/additional-content/local-models-manager.service.ts @@ -4,7 +4,7 @@ import { ipcMain } from "electron"; import { IpcRequest } from "shared/models/ipc"; import { UtilsService } from "../utils.service"; import { WindowManagerService } from "../window-manager.service"; -import { MSModel, MSModelType } from "shared/models/model-saber/model-saber.model"; +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"; @@ -12,6 +12,7 @@ import { RequestService } from "../request.service"; import { copyFileSync } from "fs-extra"; import sanitize from "sanitize-filename"; import { ensureFolderExist } from "../../helpers/fs.helpers"; +import { MODEL_TYPE_FOLDERS } from "../../../shared/models/model-saber/models-constants"; export class LocalModelsManagerService { @@ -26,13 +27,6 @@ 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; @@ -70,10 +64,9 @@ export class LocalModelsManagerService { 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]); + const modelFolderPath = path.join(versionPath, MODEL_TYPE_FOLDERS[type]); await ensureFolderExist(modelFolderPath); diff --git a/src/renderer/components/modal/modal-types/models/link-models-modal.component.tsx b/src/renderer/components/modal/modal-types/models/link-models-modal.component.tsx new file mode 100644 index 00000000..ac57ada9 --- /dev/null +++ b/src/renderer/components/modal/modal-types/models/link-models-modal.component.tsx @@ -0,0 +1,32 @@ +import { useState } from "react" +import { BsmButton } from "renderer/components/shared/bsm-button.component" +import { BsmCheckbox } from "renderer/components/shared/bsm-checkbox.component" +import { BsmImage } from "renderer/components/shared/bsm-image.component" +import { useTranslation } from "renderer/hooks/use-translation.hook" +import { ModalComponent, ModalExitCode } from "renderer/services/modale.service" +import { MSModelType } from "shared/models/model-saber/model-saber.model" +import BeatRunning from '../../../../../../assets/images/apngs/beat-running.png' + +export const LinkModelsModal: ModalComponent = ({resolver, data}) => { + + const t = useTranslation(); + + const [keepMaps, setKeepMaps] = useState(true) + + return ( +
+

{t(`modals.models.link-models.${data}.title`)}

+ +

{t(`modals.models.link-models.${data}.desc`)}

+

{t(`modals.models.link-models.${data}.info`)}

+
+ + {t(`modals.models.link-models.${data}.keep-models.label`)} +
+
+ resolver({exitCode: ModalExitCode.CANCELED})} withBar={false} text="misc.cancel"/> + resolver({exitCode: ModalExitCode.COMPLETED, data: keepMaps})} withBar={false} text={`modals.models.link-models.${data}.valid-btn`}/> +
+ + ) +} diff --git a/src/renderer/components/modal/modal-types/models/unlink-models-modal.component.tsx b/src/renderer/components/modal/modal-types/models/unlink-models-modal.component.tsx new file mode 100644 index 00000000..ad3266eb --- /dev/null +++ b/src/renderer/components/modal/modal-types/models/unlink-models-modal.component.tsx @@ -0,0 +1,30 @@ +import { useState } from "react"; +import { BsmButton } from "renderer/components/shared/bsm-button.component"; +import { BsmCheckbox } from "renderer/components/shared/bsm-checkbox.component"; +import { BsmImage } from "renderer/components/shared/bsm-image.component"; +import { useTranslation } from "renderer/hooks/use-translation.hook"; +import { ModalComponent, ModalExitCode } from "renderer/services/modale.service"; +import BeatConflict from '../../../../../../assets/images/apngs/beat-conflict.png' +import { MSModelType } from "shared/models/model-saber/model-saber.model"; + +export const UnlinkModelsModal: ModalComponent = ({resolver, data}) => { + + const t = useTranslation(); + const [keepMaps, setKeepMaps] = useState(true); + + return ( +
+

{t(`modals.models.unlink-models.${data}.title`)}

+ +

{t(`modals.models.unlink-models.${data}.desc`)}

+
+ + {t(`modals.models.unlink-models.${data}.keep-models.label`)} +
+
+ resolver({exitCode: ModalExitCode.CANCELED})} withBar={false} text="misc.cancel"/> + resolver({exitCode: ModalExitCode.COMPLETED, data: keepMaps})} withBar={false} text={`modals.models.unlink-models.${data}.valid-btn`}/> +
+ + ) +} \ No newline at end of file diff --git a/src/renderer/components/modal/modal-types/share-folders-modal.component.tsx b/src/renderer/components/modal/modal-types/share-folders-modal.component.tsx index bed4e578..8fb18622 100644 --- a/src/renderer/components/modal/modal-types/share-folders-modal.component.tsx +++ b/src/renderer/components/modal/modal-types/share-folders-modal.component.tsx @@ -1,11 +1,9 @@ import Tippy from "@tippyjs/react"; import { Variants } from "framer-motion"; -import { version } from "os"; import { useEffect, useState } from "react"; import { LinkButton } from "renderer/components/maps-mangement-components/link-button.component"; import { BsmBasicSpinner } from "renderer/components/shared/bsm-basic-spinner/bsm-basic-spinner.component"; import { BsmButton } from "renderer/components/shared/bsm-button.component"; -import { BsmIcon } from "renderer/components/svgs/bsm-icon.component"; import { useObservable } from "renderer/hooks/use-observable.hook"; import { useThemeColor } from "renderer/hooks/use-theme-color.hook"; import { useTranslation } from "renderer/hooks/use-translation.hook"; diff --git a/src/renderer/components/models-management/models-panel.component.tsx b/src/renderer/components/models-management/models-panel.component.tsx new file mode 100644 index 00000000..758e818a --- /dev/null +++ b/src/renderer/components/models-management/models-panel.component.tsx @@ -0,0 +1,26 @@ +import { BSVersion } from "shared/bs-version.interface"; +import { useState } from "react"; +import { ModelsTabsNavbar } from "./models-tabs-navbar.component"; + +export function ModelsPanel({version}: {version?: BSVersion}) { + + const [modelTypeTab, setModelTypeTab] = useState(ModelTabType.Avatars); + + return ( +
+
+ +
+
+ +
+
+ ) +} + +enum ModelTabType { + Avatars = 0, + Sabers = 1, + Platforms = 2, + Bloqs = 3, +} diff --git a/src/renderer/components/models-management/models-tabs-navbar.component.tsx b/src/renderer/components/models-management/models-tabs-navbar.component.tsx new file mode 100644 index 00000000..1d682abd --- /dev/null +++ b/src/renderer/components/models-management/models-tabs-navbar.component.tsx @@ -0,0 +1,92 @@ +import { BSVersion } from "shared/bs-version.interface" +import { TabNavBar } from "../shared/tab-nav-bar.component" +import { MSModelType } from "shared/models/model-saber/model-saber.model" +import { LinkButton } from "../maps-mangement-components/link-button.component" +import { Variants } from "framer-motion" +import { DetailedHTMLProps, useEffect, useState } from "react" +import { useTranslation } from "renderer/hooks/use-translation.hook" +import { useThemeColor } from "renderer/hooks/use-theme-color.hook" +import { useOnUpdate } from "renderer/hooks/use-on-update.hook" +import { useConstant } from "renderer/hooks/use-constant.hook" +import { ModelsManagerService } from "renderer/services/models-management/models-manager.service" + +type Props = { + version?: BSVersion + tabIndex: number, + onTabChange: (index: number) => void +} + +export function ModelsTabsNavbar({version, tabIndex, onTabChange}: Props) { + + const renderTab = (props: DetailedHTMLProps, any>, text: string, index?: number) => { + return + } + + return ( + + ) +} + +type TabProps = { + version?: BSVersion, + modelType: MSModelType, + index: number, + onLink?: (type: MSModelType) => void, + onUnlink?: (type: MSModelType) => void, +} & DetailedHTMLProps, HTMLLIElement>; + +function ModelTab({version, modelType, onClick, onLink, onUnlink}: TabProps){ + + const modelsManager = useConstant(() => ModelsManagerService.getInstance()); + + const [modelsAreLinked, setModelsAreLinked] = useState(false); + const [linkBtnDisabled, setLinkBtnDisabled] = useState(false); + + useOnUpdate(() => { + if(!version){ return; } + + const sub = modelsManager.$modelsLinkingPending(version, modelType).subscribe(async (pending) => { + if(pending){ return setLinkBtnDisabled(() => pending); } + const modelsLinked = await modelsManager.isModelsLinked(version, modelType); + setModelsAreLinked(() => modelsLinked); + setLinkBtnDisabled(() => pending); + }); + + return () => { + sub.unsubscribe(); + } + }, [version]); + + const linkModels = () => modelsManager.linkModels(modelType, version).then(() => onLink?.(modelType)); + const unlinkModels = () => modelsManager.unlinkModels(modelType, version).then(() => onUnlink?.(modelType)); + + const onClickLink = () => { + if(!version){ return Promise.resolve(); } + if(modelsAreLinked){ return unlinkModels(); } + return linkModels(); + } + + return ( +
  • + {modelType} +
    + {!!version && ( + + )} +
    +
  • + ) + +} diff --git a/src/renderer/hooks/use-constant.hook.ts b/src/renderer/hooks/use-constant.hook.ts new file mode 100644 index 00000000..5f851c4b --- /dev/null +++ b/src/renderer/hooks/use-constant.hook.ts @@ -0,0 +1,13 @@ +import { useRef } from "react" + +type ResultBox = { v: T } + +export function useConstant(fn: () => T): T { + const ref = useRef>() + + if (!ref.current) { + ref.current = { v: fn() } + } + + return ref.current.v +} \ No newline at end of file diff --git a/src/renderer/hooks/use-on-update.hook.ts b/src/renderer/hooks/use-on-update.hook.ts new file mode 100644 index 00000000..a4ef1160 --- /dev/null +++ b/src/renderer/hooks/use-on-update.hook.ts @@ -0,0 +1,7 @@ +import { useEffect, EffectCallback, DependencyList } from "react"; + +export function useOnUpdate(func: EffectCallback, deps: DependencyList = []) { + useEffect(() => { + func(); + }, deps); +} diff --git a/src/renderer/pages/settings-page.component.tsx b/src/renderer/pages/settings-page.component.tsx index 6859d442..2162d917 100644 --- a/src/renderer/pages/settings-page.component.tsx +++ b/src/renderer/pages/settings-page.component.tsx @@ -28,7 +28,7 @@ import beatleaderIcon from "../../../assets/images/third-party-icons/beat-leader 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 { ModelsManagerService } from "renderer/services/models-management/models-manager.service"; import { useTranslation } from "renderer/hooks/use-translation.hook"; import { VersionFolderLinkerService } from "renderer/services/version-folder-linker.service"; diff --git a/src/renderer/pages/version-viewer.component.tsx b/src/renderer/pages/version-viewer.component.tsx index b8dbd722..41925830 100644 --- a/src/renderer/pages/version-viewer.component.tsx +++ b/src/renderer/pages/version-viewer.component.tsx @@ -16,6 +16,7 @@ import { ModsSlide } from 'renderer/components/version-viewer/slides/mods/mods-s import { UninstallModal } from 'renderer/components/modal/modal-types/uninstall-modal.component'; import { MapsPlaylistsPanel } from 'renderer/components/maps-mangement-components/maps-playlists-panel.component'; import { ShareFoldersModal } from 'renderer/components/modal/modal-types/share-folders-modal.component'; +import { ModelsPanel } from 'renderer/components/models-management/models-panel.component'; export function VersionViewer() { @@ -78,12 +79,15 @@ export function VersionViewer() {

    {state.name ? `${state.BSVersion} - ${state.name}` : state.BSVersion}

    - setCurrentTabIndex(i)}/> + setCurrentTabIndex(i)}/>
    +
    + +
    diff --git a/src/renderer/services/maps-manager.service.ts b/src/renderer/services/maps-manager.service.ts index 45e3f56a..f49c9cde 100644 --- a/src/renderer/services/maps-manager.service.ts +++ b/src/renderer/services/maps-manager.service.ts @@ -159,9 +159,7 @@ export class MapsManagerService { } public $mapsLinkingPending(version: BSVersion): Observable{ - return this.linker.queue$.pipe(mergeMap(async queue => { - return queue.some(q => q.relativeFolder.includes(MapsManagerService.RELATIVE_MAPS_FOLDER) && equal(q.version, version)); - }), distinctUntilChanged()); + return this.linker.$isVersionFolderPending(version, MapsManagerService.RELATIVE_MAPS_FOLDER); } } \ No newline at end of file diff --git a/src/renderer/services/models-management/models-manager.service.ts b/src/renderer/services/models-management/models-manager.service.ts new file mode 100644 index 00000000..fddfc55e --- /dev/null +++ b/src/renderer/services/models-management/models-manager.service.ts @@ -0,0 +1,82 @@ +import { MSModelType } from "shared/models/model-saber/model-saber.model"; +import { IpcService } from "../ipc.service"; +import { VersionFolderLinkerService, VersionLinkerActionType } from "../version-folder-linker.service"; +import { MODEL_TYPE_FOLDERS } from "shared/models/model-saber/models-constants"; +import { Observable, distinctUntilChanged, lastValueFrom, mergeMap, share } from "rxjs"; +import { BSVersion } from "shared/bs-version.interface"; +import { ModalExitCode, ModalService } from "../modale.service"; +import { LinkModelsModal } from "renderer/components/modal/modal-types/models/link-models-modal.component"; +import { UnlinkModelsModal } from "renderer/components/modal/modal-types/models/unlink-models-modal.component"; + +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 readonly versionFolderLinked: VersionFolderLinkerService; + private readonly modalService: ModalService; + + private constructor(){ + this.ipc = IpcService.getInstance(); + this.versionFolderLinked = VersionFolderLinkerService.getInstance(); + this.modalService = ModalService.getInsance(); + } + + public isModelsLinked(version: BSVersion, type: MSModelType): Promise{ + return lastValueFrom(this.versionFolderLinked.isVersionFolderLinked(version, MODEL_TYPE_FOLDERS[type])); + } + + public $modelsLinkingPending(version: BSVersion, type: MSModelType): Observable{ + return this.versionFolderLinked.$isVersionFolderPending(version, MODEL_TYPE_FOLDERS[type]); + } + + public async linkModels(type: MSModelType, version?: BSVersion): Promise{ + + const res = await this.modalService.openModal(LinkModelsModal, type); + + if(res.exitCode !== ModalExitCode.COMPLETED){ return null; } + + return this.versionFolderLinked.linkVersionFolder({ + version, + relativeFolder: MODEL_TYPE_FOLDERS[type], + type: VersionLinkerActionType.Link, + options: { keepContents: res.data !== false } + }); + } + + public async unlinkModels(type: MSModelType, version?: BSVersion): Promise{ + + const res = await this.modalService.openModal(UnlinkModelsModal, type); + + if(res.exitCode !== ModalExitCode.COMPLETED){ return null; } + + return this.versionFolderLinked.unlinkVersionFolder({ + version, + relativeFolder: MODEL_TYPE_FOLDERS[type], + type: VersionLinkerActionType.Unlink, + options: { keepContents: res.data !== false } + }); + } + + public isDeepLinksEnabled(): Promise{ + return this.ipc.send("is-models-deep-links-enabled").then(res => ( + res.success ? res.data : false + )); + } + + public async enableDeepLink(): Promise{ + const res = await this.ipc.send("register-models-deep-link"); + return res.success ? res.data : false; + } + + public async disableDeepLink(): Promise{ + const res = await this.ipc.send("unregister-models-deep-link"); + return res.success ? res.data : false; + } + +} \ No newline at end of file diff --git a/src/renderer/services/models-manager.service.ts b/src/renderer/services/models-manager.service.ts deleted file mode 100644 index d70942f0..00000000 --- a/src/renderer/services/models-manager.service.ts +++ /dev/null @@ -1,34 +0,0 @@ -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 enableDeepLink(): Promise{ - const res = await this.ipc.send("register-models-deep-link"); - return res.success ? res.data : false; - } - - public async disableDeepLink(): Promise{ - const res = await this.ipc.send("unregister-models-deep-link"); - return res.success ? res.data : false; - } - -} \ No newline at end of file diff --git a/src/renderer/services/version-folder-linker.service.ts b/src/renderer/services/version-folder-linker.service.ts index fd14fb2b..f4dcfd1b 100644 --- a/src/renderer/services/version-folder-linker.service.ts +++ b/src/renderer/services/version-folder-linker.service.ts @@ -1,9 +1,10 @@ import { LinkOptions, UnlinkOptions } from "main/services/folder-linker.service"; -import { map, distinctUntilChanged, filter } from "rxjs/operators"; +import { map, distinctUntilChanged, filter, mergeMap, share } from "rxjs/operators"; import { BehaviorSubject, Observable } from "rxjs"; import { BSVersion } from "shared/bs-version.interface"; import { IpcService } from "./ipc.service"; import { ProgressBarService } from "./progress-bar.service"; +import equal from "fast-deep-equal"; export class VersionFolderLinkerService{ @@ -137,6 +138,12 @@ export class VersionFolderLinkerService{ return this.ipcService.sendV2("relink-all-versions-folders"); } + public $isVersionFolderPending(version: BSVersion, relativeFolder: string): Observable{ + return this.queue$.pipe(mergeMap(async queue => { + return queue.some(q => q.relativeFolder.includes(relativeFolder) && equal(q.version, version)); + }), distinctUntilChanged(), share()); + } + public get currentAction$(): Observable{ return this._queue$.pipe(map(actions => actions.at(0)), distinctUntilChanged()); } diff --git a/src/renderer/windows/App.tsx b/src/renderer/windows/App.tsx index 964a82a1..71e8d748 100644 --- a/src/renderer/windows/App.tsx +++ b/src/renderer/windows/App.tsx @@ -17,7 +17,7 @@ import 'tippy.js/dist/tippy.css'; import 'tippy.js/animations/shift-away-subtle.css'; 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 { ModelsManagerService } from "renderer/services/models-management/models-manager.service"; import { NotificationService } from "renderer/services/notification.service"; import { timer } from "rxjs"; import { ConfigurationService } from "renderer/services/configuration.service"; diff --git a/src/shared/models/model-saber/model-saber.model.ts b/src/shared/models/model-saber/model-saber.model.ts index e5c40bc4..a44d3b01 100644 --- a/src/shared/models/model-saber/model-saber.model.ts +++ b/src/shared/models/model-saber/model-saber.model.ts @@ -17,8 +17,18 @@ export interface MSModel { date: string } -export type MSModelType = "saber"|"platform"|"bloq"|"misc"|"avatar"; -export type MSModelPlatform = "pc"|"quest"|"all"; +export enum MSModelType { + Avatar = "avatar", + Saber = "saber", + Platfrom = "platform", + Bloq = "bloq" +}; + +export enum MSModelPlatform { + PC = "pc", + QUEST = "quest", + ALL = "all" +}; export interface MSGetQuery { type?: MSModelType; diff --git a/src/shared/models/model-saber/models-constants.ts b/src/shared/models/model-saber/models-constants.ts new file mode 100644 index 00000000..d504ec87 --- /dev/null +++ b/src/shared/models/model-saber/models-constants.ts @@ -0,0 +1,8 @@ +import { MSModelType } from "./model-saber.model"; + +export const MODEL_TYPE_FOLDERS = { + [MSModelType.Avatar]: "CustomAvatars", + [MSModelType.Bloq]: "CustomNotes", + [MSModelType.Platfrom]: "CustomPlatforms", + [MSModelType.Saber]: "CustomSabers" +} \ No newline at end of file