diff --git a/assets/jsons/translations/de.json b/assets/jsons/translations/de.json index 4117e8177..d314cc5e5 100644 --- a/assets/jsons/translations/de.json +++ b/assets/jsons/translations/de.json @@ -106,6 +106,7 @@ "latest": "Neueste", "description": "Beschreibung", "dropdown": { + "import-mods": "Mods importieren", "uninstall-all": "Alle deinstallieren", "unselect-all": "Alle abwählen" } @@ -116,6 +117,10 @@ "title": "Mods bereits installiert", "description": "Alle ausgewählten Mods sind bereits installiert" } + }, + "drop-zone": { + "text": "Importiere deine Mods", + "subtext": "Ziehe deine \"zip\"- oder \"dll\"-Dateien hierher, um sie zu importieren" } }, "dropdown": { @@ -526,6 +531,17 @@ "no-mods": "Keine Mods sind in dieser Version installiert 😑" } } + }, + "import-mod": { + "titles": { + "success": "Mod-Import abgeschlossen", + "error": "Es ist ein Fehler beim Importieren der Mods aufgetreten" + }, + "msgs": { + "success": "Mods erfolgreich importiert.", + "some-success": "Einige Mods wurden erfolgreich importiert.", + "no-dlls": "Die Datei(en) enthalten keine \"dll\"-Dateien." + } } }, "maps": { diff --git a/assets/jsons/translations/en.json b/assets/jsons/translations/en.json index 7f061f640..699fd52ae 100644 --- a/assets/jsons/translations/en.json +++ b/assets/jsons/translations/en.json @@ -106,6 +106,7 @@ "latest": "Latest", "description": "Description", "dropdown": { + "import-mods": "Import mods", "uninstall-all": "Uninstall all", "unselect-all": "Unselect all" } @@ -116,6 +117,10 @@ "title": "Mods already installed", "description": "All selected mods are already installed" } + }, + "drop-zone": { + "text": "Import your mods", + "subtext": "Drop your \"zip\" or \"dll\" files here to import them" } }, "dropdown": { @@ -526,6 +531,17 @@ "no-mods": "No mods are installed in this version 😑" } } + }, + "import-mod": { + "titles": { + "success": "Mods import completed", + "error": "An error occurred during the mods import" + }, + "msgs": { + "success": "Mods successfully imported.", + "some-success": "Some mods were successfully imported.", + "no-dlls": "The file(s) do not contain any \"dll\" files." + } } }, "maps": { diff --git a/assets/jsons/translations/es.json b/assets/jsons/translations/es.json index 9e621c75b..5fc70a53f 100644 --- a/assets/jsons/translations/es.json +++ b/assets/jsons/translations/es.json @@ -106,6 +106,7 @@ "latest": "Último", "description": "Descripción", "dropdown": { + "import-mods": "Importar mods", "uninstall-all": "Desinstalar todos", "unselect-all": "Deseleccionar todo" } @@ -116,6 +117,10 @@ "title": "Mods ya instalados", "description": "Todos los mods seleccionados ya están instalados" } + }, + "drop-zone": { + "text": "Importa tus mods", + "subtext": "Coloca tus archivos \"zip\" o \"dll\" aquí para importarlos" } }, "dropdown": { @@ -526,6 +531,17 @@ "no-mods": "No hay mods instalados en esta versión 😑" } } + }, + "import-mod": { + "titles": { + "success": "Importación de mods completada", + "error": "Ocurrió un error durante la importación de mods" + }, + "msgs": { + "success": "Mods importados con éxito.", + "some-success": "Algunos mods fueron importados con éxito.", + "no-dlls": "El(los) archivo(s) no contienen archivos \"dll\"." + } } }, "maps": { diff --git a/assets/jsons/translations/fr.json b/assets/jsons/translations/fr.json index 8dd40666c..c377941b2 100644 --- a/assets/jsons/translations/fr.json +++ b/assets/jsons/translations/fr.json @@ -106,6 +106,7 @@ "latest": "Récent", "description": "Description", "dropdown": { + "import-mods": "Importer des mods", "uninstall-all": "Tout désinstaller", "unselect-all": "Tout désélectionner" } @@ -116,6 +117,10 @@ "title": "Mods déjà installées", "description": "Tous les mods séléctionnées sont déjà installées" } + }, + "drop-zone": { + "text": "Importez vos mods", + "subtext": "Déposez vos fichiers \"zip\" ou \"dll\" ici pour les importer" } }, "dropdown": { @@ -526,6 +531,17 @@ "no-mods": "Aucun mod n'est installé dans dans cette version 😑" } } + }, + "import-mod": { + "titles": { + "success": "Importation des mods terminée", + "error": "Une erreur est survenue lors de l'importation des mods" + }, + "msgs": { + "success": "Mods importés avec succès.", + "some-success": "Certains mods ont été importés avec succès.", + "no-dlls": "Le(s) fichier(s) ne contient(ent) aucun fichier \"dll\"." + } } }, "maps": { diff --git a/assets/jsons/translations/ja.json b/assets/jsons/translations/ja.json index d5b1ac2fd..4e3b9ff38 100644 --- a/assets/jsons/translations/ja.json +++ b/assets/jsons/translations/ja.json @@ -106,6 +106,7 @@ "latest": "最新", "description": "說明", "dropdown": { + "import-mods": "モッドをインポート", "uninstall-all": "全てアンインストールする", "unselect-all": "すべて選択解除" } @@ -116,6 +117,10 @@ "title": "すでにインストール済みのMOD", "description": "選択したすべてのMODはすでにインストールされています" } + }, + "drop-zone": { + "text": "モッドをインポートする", + "subtext": "「zip」または「dll」ファイルをここにドロップしてインポートする" } }, "dropdown": { @@ -526,6 +531,17 @@ "no-mods": "このバージョンのModはインストールされていません 😑" } } + }, + "import-mod": { + "titles": { + "success": "モッドのインポートが完了しました", + "error": "モッドのインポート中にエラーが発生しました" + }, + "msgs": { + "success": "モッドが正常にインポートされました。", + "some-success": "一部のモッドが正常にインポートされました。", + "no-dlls": "ファイルに \"dll\" ファイルが含まれていません。" + } } }, "maps": { diff --git a/assets/jsons/translations/ko.json b/assets/jsons/translations/ko.json index 039995c15..b76e94907 100644 --- a/assets/jsons/translations/ko.json +++ b/assets/jsons/translations/ko.json @@ -106,6 +106,7 @@ "latest": "최신", "description": "설명", "dropdown": { + "import-mods": "모드 가져오기", "uninstall-all": "모두 제거", "unselect-all": "모두 선택 해제" } @@ -116,6 +117,10 @@ "title": "이미 설치된 모드", "description": "선택한 모든 모드가 이미 설치되었습니다." } + }, + "drop-zone": { + "text": "모드를 가져오기", + "subtext": "\"zip\" 또는 \"dll\" 파일을 여기에 드롭하여 가져오기" } }, "dropdown": { @@ -526,6 +531,17 @@ "no-mods": "이 버전에 설치된 Mod가 없습니다 😑" } } + }, + "import-mod": { + "titles": { + "success": "모드 임포트 완료", + "error": "모드 임포트 중 오류가 발생했습니다" + }, + "msgs": { + "success": "모드가 성공적으로 임포트되었습니다.", + "some-success": "일부 모드가 성공적으로 임포트되었습니다.", + "no-dlls": "파일에 \"dll\" 파일이 포함되어 있지 않습니다." + } } }, "maps": { diff --git a/assets/jsons/translations/ru.json b/assets/jsons/translations/ru.json index e40394dd5..71af43342 100644 --- a/assets/jsons/translations/ru.json +++ b/assets/jsons/translations/ru.json @@ -106,6 +106,7 @@ "latest": "Latest", "description": "Описание", "dropdown": { + "import-mods": "Импортировать моды", "uninstall-all": "Удалить всё", "unselect-all": "Снять все выделения" } @@ -116,6 +117,10 @@ "title": "Моды уже установлены", "description": "Все выбранные моды уже установлены" } + }, + "drop-zone": { + "text": "Импортируйте свои моды", + "subtext": "Перетащите ваши файлы \"zip\" или \"dll\" сюда для импорта" } }, "dropdown": { @@ -526,6 +531,17 @@ "no-mods": "Для этой версии не было установлено модов 😑" } } + }, + "import-mod": { + "titles": { + "success": "Импорт модов завершен", + "error": "Произошла ошибка при импорте модов" + }, + "msgs": { + "success": "Моды успешно импортированы.", + "some-success": "Некоторые моды были успешно импортированы.", + "no-dlls": "Файл(ы) не содержат файлов \"dll\"." + } } }, "maps": { diff --git a/assets/jsons/translations/zh-tw.json b/assets/jsons/translations/zh-tw.json index dd6358a00..6511eb09a 100644 --- a/assets/jsons/translations/zh-tw.json +++ b/assets/jsons/translations/zh-tw.json @@ -106,6 +106,7 @@ "latest": "最新", "description": "描述", "dropdown": { + "import-mods": "導入模組", "uninstall-all": "全部移除", "unselect-all": "取消全選" } @@ -116,6 +117,10 @@ "title": "模組已安裝", "description": "所有選中的模組已經安裝" } + }, + "drop-zone": { + "text": "匯入你的模組", + "subtext": "將你的 \"zip\" 或 \"dll\" 檔案拖放到這裡以進行匯入" } }, "dropdown": { @@ -526,6 +531,17 @@ "no-mods": "該版本中沒有安裝 Mod 😑" } } + }, + "import-mod": { + "titles": { + "success": "模組匯入完成", + "error": "模組匯入過程中發生錯誤" + }, + "msgs": { + "success": "模組成功匯入。", + "some-success": "一些模組已成功匯入。", + "no-dlls": "檔案中不包含任何 \"dll\" 檔案。" + } } }, "maps": { diff --git a/assets/jsons/translations/zh.json b/assets/jsons/translations/zh.json index ef4d146cc..a35dfca15 100644 --- a/assets/jsons/translations/zh.json +++ b/assets/jsons/translations/zh.json @@ -106,6 +106,7 @@ "latest": "最新", "description": "描述", "dropdown": { + "import-mods": "导入模组", "uninstall-all": "全部卸载", "unselect-all": "取消全选" } @@ -116,6 +117,10 @@ "title": "模组已安装", "description": "所有选中的模组已经安装" } + }, + "drop-zone": { + "text": "导入你的模组", + "subtext": "将你的 \"zip\" 或 \"dll\" 文件拖放到这里进行导入" } }, "dropdown": { @@ -526,6 +531,17 @@ "no-mods": "该版本中没有安装 Mod 😑" } } + }, + "import-mod": { + "titles": { + "success": "模组导入完成", + "error": "模组导入过程中发生错误" + }, + "msgs": { + "success": "模组成功导入。", + "some-success": "一些模组已成功导入。", + "no-dlls": "文件中不包含任何 \"dll\" 文件。" + } } }, "maps": { diff --git a/src/main/ipcs/bs-mods-ipcs.ts b/src/main/ipcs/bs-mods-ipcs.ts index f53869a8f..0792538a8 100644 --- a/src/main/ipcs/bs-mods-ipcs.ts +++ b/src/main/ipcs/bs-mods-ipcs.ts @@ -4,27 +4,32 @@ import { from } from "rxjs"; const ipc = IpcService.getInstance(); -ipc.on("get-available-mods", (args, reply) => { +ipc.on("bs-mods.get-available-mods", (args, reply) => { const modsManager = BsModsManagerService.getInstance(); reply(from(modsManager.getAvailableMods(args))); }); -ipc.on("get-installed-mods", (args, reply) => { +ipc.on("bs-mods.get-installed-mods", (args, reply) => { const modsManager = BsModsManagerService.getInstance(); reply(from(modsManager.getInstalledMods(args))); }); -ipc.on("install-mods", (args, reply) => { +ipc.on("bs-mods.import-mods", (args, reply) => { + const modsManager = BsModsManagerService.getInstance(); + reply(modsManager.importMods(args.paths, args.version)); +}); + +ipc.on("bs-mods.install-mods", (args, reply) => { const modsManager = BsModsManagerService.getInstance(); reply(modsManager.installMods(args.mods, args.version)); }); -ipc.on("uninstall-mods", (args, reply) => { +ipc.on("bs-mods.uninstall-mods", (args, reply) => { const modsManager = BsModsManagerService.getInstance(); reply(modsManager.uninstallMods(args.mods, args.version)); }); -ipc.on("uninstall-all-mods", (args, reply) => { +ipc.on("bs-mods.uninstall-all-mods", (args, reply) => { const modsManager = BsModsManagerService.getInstance(); reply(modsManager.uninstallAllMods(args)); }); diff --git a/src/main/services/mods/bs-mods-manager.service.ts b/src/main/services/mods/bs-mods-manager.service.ts index c93b8daab..b26bda5e1 100644 --- a/src/main/services/mods/bs-mods-manager.service.ts +++ b/src/main/services/mods/bs-mods-manager.service.ts @@ -11,7 +11,7 @@ import { deleteFolder, pathExist, Progression, unlinkPath } from "../../helpers/ import { lastValueFrom, Observable } from "rxjs"; import recursiveReadDir from "recursive-readdir"; import { sToMs } from "../../../shared/helpers/time.helpers"; -import { pathExistsSync } from "fs-extra"; +import { copyFile, ensureDir, pathExistsSync } from "fs-extra"; import { CustomError } from "shared/models/exceptions/custom-error.class"; import { popElement } from "shared/helpers/array.helpers"; import { LinuxService } from "../linux.service"; @@ -19,6 +19,7 @@ import { tryit } from "shared/helpers/error.helpers"; import crypto from "crypto"; import { BsmZipExtractor } from "main/models/bsm-zip-extractor.class"; import { bsmSpawn } from "main/helpers/os.helpers"; +import { ExternalMod } from "shared/models/mods/mod.interface"; export class BsModsManagerService { private static instance: BsModsManagerService; @@ -320,6 +321,108 @@ export class BsModsManagerService { return Array.from(modsDict.values()); } + private async importMod(modPath: string, destination: string): Promise { + const extensionName = path.extname(modPath).toLowerCase(); + + if (extensionName === ".dll") { + const filename = path.basename(modPath); + // Defaultly copy the ".dll" to the "IPA/Pending/Plugins" folder + const fileFolder = path.join(destination, ModsInstallFolder.PLUGINS); + + log.info("Copying", `${modPath}`, "to", `"${fileFolder}"`); + await ensureDir(fileFolder); + const copied = await copyFile(modPath, path.join(fileFolder, filename)) + .then(() => true) + .catch(error => { + log.warn("Could not copy", `"${modPath}"`, error); + return false; + }); + return copied ? [ filename ] : []; + } + + if (extensionName !== ".zip") { + log.warn("Mod file is not a dll or zip file"); + return []; + } + + log.info("Extracting", `"${modPath}"`, "to", `"${destination}"`); + const zip = await BsmZipExtractor.fromPath(modPath); + + try { + const dll = await zip.findEntry(entry => entry.fileName.endsWith(".dll")); + + if (!dll) { + log.warn("No \"dll\" found in zip", modPath); + return []; + } + + return await zip.extract(destination); + } catch (error) { + log.warn("Could not extract", `"${modPath}"`, error); + return []; + } finally { + zip.close(); + } + } + + public importMods(paths: string[], version: BSVersion): Observable> { + return new Observable>(obs => { + const progress: Progression = { total: 0, current: 0 }; + const externalMod: ExternalMod = { + name: "", + files: [], + }; + let modsInstalledCount = 0; + let modFilesCount = 0; + const abortController = new AbortController(); + + (async () => { + const versionPath = await this.bsLocalService.getVersionPath(version); + const modsPendingFolder = path.join(versionPath, ModsInstallFolder.PENDING); + + for (const modPath of paths) { + if (abortController.signal?.aborted) { + log.info("Mods import has been cancelled"); + return; + } + + progress.total = paths.length; + obs.next(progress); + + if (!pathExistsSync(modPath)) { continue; } + + const modFiles = await this.importMod(modPath, modsPendingFolder); + if (modFiles.length > 0) { + ++modsInstalledCount; + modFilesCount += modFiles.length; + + externalMod.name = path.basename(modPath, path.extname(modPath)); + externalMod.files = modFiles; + progress.data = externalMod; + } else { + progress.data = undefined; + } + ++progress.current; + obs.next(progress); + } + })() + .then(() => { + if (modsInstalledCount === 0) { + throw new CustomError("No \"dll\" found in any of the files dropped", "no-dlls"); + } + log.info("Successfully imported", modsInstalledCount, "mods from", paths.length, "files paths. Total files/folders added", modFilesCount); + }) + .catch(error => { + obs.error(error); + }) + .finally(() => obs.complete()); + + return () => { + abortController.abort(); + } + }); + } + public installMods(mods: Mod[], version: BSVersion): Observable { const progress = { current: 0, total: mods.length }; diff --git a/src/renderer/components/version-viewer/slides/mods/mods-grid.component.tsx b/src/renderer/components/version-viewer/slides/mods/mods-grid.component.tsx index e498b33ef..ef5f82707 100644 --- a/src/renderer/components/version-viewer/slides/mods/mods-grid.component.tsx +++ b/src/renderer/components/version-viewer/slides/mods/mods-grid.component.tsx @@ -17,9 +17,10 @@ type Props = { uninstallMod?: (mods: Mod) => void; uninstallAllMods?: () => void; unselectAllMods?: () => void; + openModsDropZone?: () => void; }; -export function ModsGrid({ modsMap, installed, modsSelected, onModChange, moreInfoMod, onWantInfos, disabled, uninstallMod, uninstallAllMods, unselectAllMods }: Props) { +export function ModsGrid({ modsMap, installed, modsSelected, onModChange, moreInfoMod, onWantInfos, disabled, uninstallMod, uninstallAllMods, unselectAllMods, openModsDropZone }: Props) { const [filter, setFilter] = useState(""); const [filterEnabled, setFilterEnabled] = useState(false); @@ -68,6 +69,7 @@ export function ModsGrid({ modsMap, installed, modsSelected, onModChange, moreIn {t("pages.version-viewer.mods.mods-grid.header-bar.description")} openModsDropZone?.() }, { text: "pages.version-viewer.mods.mods-grid.header-bar.dropdown.unselect-all", icon: "cancel", onClick: () => unselectAllMods?.() }, { text: "pages.version-viewer.mods.mods-grid.header-bar.dropdown.uninstall-all", icon: "trash", onClick: () => uninstallAllMods?.() } ]} /> diff --git a/src/renderer/components/version-viewer/slides/mods/mods-slide.component.tsx b/src/renderer/components/version-viewer/slides/mods/mods-slide.component.tsx index 7f54df2f6..24367846e 100644 --- a/src/renderer/components/version-viewer/slides/mods/mods-slide.component.tsx +++ b/src/renderer/components/version-viewer/slides/mods/mods-slide.component.tsx @@ -21,10 +21,13 @@ import { useService } from "renderer/hooks/use-service.hook"; import { NotificationService } from "renderer/services/notification.service"; import { noop } from "shared/helpers/function.helpers"; import { UninstallAllModsModal } from "renderer/components/modal/modal-types/uninstall-all-mods-modal.component"; +import { Dropzone } from "renderer/components/shared/dropzone.component"; export function ModsSlide({ version, onDisclamerDecline }: { version: BSVersion; onDisclamerDecline: () => void }) { const ACCEPTED_DISCLAIMER_KEY = "accepted-mods-disclaimer"; + const t = useTranslation(); + const modsManager = useService(BsModsManagerService); const configService = useService(ConfigurationService); const notification = useService(NotificationService); @@ -42,6 +45,7 @@ export function ModsSlide({ version, onDisclamerDecline }: { version: BSVersion; const isOnline = useObservable(() => os.isOnline$); const [installing, setInstalling] = useState(false); const [uninstalling, setUninstalling] = useState(false); + const [modsDropZoneOpen, setModsDropZoneOpen] = useState(false); const downloadRef = useRef(null); const [downloadWith, setDownloadWidth] = useState(0); @@ -166,6 +170,11 @@ export function ModsSlide({ version, onDisclamerDecline }: { version: BSVersion; }).catch(noop).finally(() => setInstalling(() => false)); }; + const importMods = (files: string[]): void => { + setModsDropZoneOpen(false); + modsManager.importMods(files, version); + }; + const uninstallMod = (mod: Mod): void => { setUninstalling(() => true); lastValueFrom(modsManager.uninstallMod(mod, version)).catch(noop).finally(() => { @@ -192,7 +201,7 @@ export function ModsSlide({ version, onDisclamerDecline }: { version: BSVersion; setModsSelected(() => []); } - const loadMods = (): Promise => { + const loadMods = async (): Promise => { if (os.isOffline) { return Promise.resolve(); } @@ -265,6 +274,7 @@ export function ModsSlide({ version, onDisclamerDecline }: { version: BSVersion; ); } + return ( <>
@@ -279,6 +289,7 @@ export function ModsSlide({ version, onDisclamerDecline }: { version: BSVersion; uninstallMod={uninstallMod} uninstallAllMods={uninstallAllMods} unselectAllMods={unselectAllMods} + openModsDropZone={() => setModsDropZoneOpen(true)} />
@@ -297,7 +308,23 @@ export function ModsSlide({ version, onDisclamerDecline }: { version: BSVersion; return (
-
{renderContent()}
+ setModsDropZoneOpen(false) : undefined} + filters={[ + { name: ".zip", extensions: ["zip"] }, + { name: ".dll", extensions: ["dll"] } + ]} + dialogOptions={{ dialog: { + properties: ["openFile", "multiSelections"], + }}} + > +
{renderContent()}
+
); } diff --git a/src/renderer/services/bs-mods-manager.service.ts b/src/renderer/services/bs-mods-manager.service.ts index ef449208b..83ad64c57 100644 --- a/src/renderer/services/bs-mods-manager.service.ts +++ b/src/renderer/services/bs-mods-manager.service.ts @@ -1,11 +1,12 @@ -import { Observable, BehaviorSubject, throwError, of } from "rxjs"; -import { catchError, tap } from "rxjs/operators"; +import { Observable, BehaviorSubject, throwError, of, lastValueFrom } from "rxjs"; +import { catchError, map, tap } from "rxjs/operators"; import { BSVersion } from "shared/bs-version.interface"; import { Mod } from "shared/models/mods"; import { IpcService } from "./ipc.service"; import { ProgressBarService } from "./progress-bar.service"; import { NotificationService } from "./notification.service"; import { Progression } from "main/helpers/fs.helpers"; +import { ProgressionInterface } from "shared/models/progress-bar"; export class BsModsManagerService { private static instance: BsModsManagerService; @@ -32,11 +33,11 @@ export class BsModsManagerService { } public getAvailableMods(version: BSVersion): Observable { - return this.ipcService.sendV2("get-available-mods", version); + return this.ipcService.sendV2("bs-mods.get-available-mods", version); } public getInstalledMods(version: BSVersion): Observable { - return this.ipcService.sendV2("get-installed-mods", version); + return this.ipcService.sendV2("bs-mods.get-installed-mods", version); } public installMods(mods: Mod[], version: BSVersion): Observable { @@ -46,7 +47,7 @@ export class BsModsManagerService { } return new Observable(obs => { - const install$ = this.ipcService.sendV2("install-mods", { mods, version }); + const install$ = this.ipcService.sendV2("bs-mods.install-mods", { mods, version }); this.progressBar.show(install$.pipe(catchError(() => of({ current: 0, total: 0} as Progression))), { paddingLeft: "190px", paddingRight: "190px", bottom: "20px" }); const sub = install$.pipe( @@ -77,7 +78,7 @@ export class BsModsManagerService { } return new Observable(obs => { - const uninstall$ = this.ipcService.sendV2("uninstall-mods", { mods: [mod], version }); + const uninstall$ = this.ipcService.sendV2("bs-mods.uninstall-mods", { mods: [mod], version }); this.progressBar.show(uninstall$.pipe(catchError(() => of({ current: 0, total: 0} as Progression))), { paddingLeft: "190px", paddingRight: "190px", bottom: "20px" }); const sub = uninstall$.pipe( @@ -102,13 +103,57 @@ export class BsModsManagerService { }); } + public async importMods(paths: string[], version: BSVersion): Promise { + if (!this.progressBar.require()) { + throw new Error("Action already in progress"); + } + + // TODO: Check for conflicting mod files, and ask the user if they want to overwrite the files + + const import$ = this.ipcService.sendV2("bs-mods.import-mods", { + paths, version + }); + + this.progressBar.show(import$.pipe( + catchError(() => of()), + map(progress => ({ + progression: (progress.current / progress.total) * 100, + label: progress.data?.name + }) as ProgressionInterface) + )); + + return lastValueFrom(import$) + .then(progress => { + if (progress.current === 0) { + return; + } + this.notifications.notifySuccess({ + title: "notifications.mods.import-mod.titles.success", + desc: progress.current === progress.total + ? "notifications.mods.import-mod.msgs.success" + : "notifications.mods.import-mod.msgs.some-success", + }); + }) + .catch(error => { + this.notifications.notifyError({ + title: "notifications.mods.import-mod.titles.error", + desc: ["no-dlls"].includes(error?.code) + ? `notifications.mods.import-mod.msgs.${error.code}` + : "misc.unknown", + }); + }) + .finally(() => { + this.progressBar.hide(); + }); + } + public uninstallAllMods(version: BSVersion): Observable { if (!this.progressBar.require()) { return throwError(() => new Error("Action already in progress")); } return new Observable(obs => { - const uninstall$ = this.ipcService.sendV2("uninstall-all-mods", version); + const uninstall$ = this.ipcService.sendV2("bs-mods.uninstall-all-mods", version); this.progressBar.show(uninstall$.pipe(catchError(() => of({ current: 0, total: 0} as Progression))), { paddingLeft: "190px", paddingRight: "190px", bottom: "20px" }); const sub = uninstall$.pipe( @@ -133,4 +178,5 @@ export class BsModsManagerService { }); } + } diff --git a/src/shared/models/ipc/ipc-routes.ts b/src/shared/models/ipc/ipc-routes.ts index 8ab6e082b..bd961326b 100644 --- a/src/shared/models/ipc/ipc-routes.ts +++ b/src/shared/models/ipc/ipc-routes.ts @@ -20,6 +20,7 @@ import { Supporter } from "../supporters"; import { AppWindow } from "../window-manager/app-window.model"; import { LocalBPList, LocalBPListsDetails } from "../playlists/local-playlist.models"; import { StaticConfigGetIpcRequestResponse, StaticConfigKeys, StaticConfigSetIpcRequest } from "main/services/static-configuration.service"; +import { ExternalMod } from "../mods/mod.interface"; export type IpcReplier = (data: Observable) => void; @@ -78,11 +79,12 @@ export interface IpcChannelMapping { "delete-models": { request: BsmLocalModel[], response: Progression }; /* ** bs-mods-ipcs ** */ - "get-available-mods": { request: BSVersion, response: Mod[] }; - "get-installed-mods": { request: BSVersion, response: Mod[] }; - "install-mods": { request: { mods: Mod[]; version: BSVersion }, response: Progression }; - "uninstall-mods": { request: { mods: Mod[]; version: BSVersion }, response: Progression }; - "uninstall-all-mods": { request: BSVersion, response: Progression }; + "bs-mods.get-available-mods": { request: BSVersion, response: Mod[] }; + "bs-mods.get-installed-mods": { request: BSVersion, response: Mod[] }; + "bs-mods.import-mods": { request: { paths: string[]; version: BSVersion; }, response: Progression }; + "bs-mods.install-mods": { request: { mods: Mod[]; version: BSVersion }, response: Progression }; + "bs-mods.uninstall-mods": { request: { mods: Mod[]; version: BSVersion }, response: Progression }; + "bs-mods.uninstall-all-mods": { request: BSVersion, response: Progression }; /* ** bs-playlist-ipcs ** */ "one-click-install-playlist": { request: string, response: Progression }; diff --git a/src/shared/models/mods/mod.interface.ts b/src/shared/models/mods/mod.interface.ts index 02bb21b25..51ad0d7a2 100644 --- a/src/shared/models/mods/mod.interface.ts +++ b/src/shared/models/mods/mod.interface.ts @@ -34,3 +34,9 @@ export interface FileHashes { hash: string; file: string; } + +// Any mods that are not supported in beatmods +export interface ExternalMod { + name: string; + files: string[]; +}