mirror of
https://github.com/Zagrios/bs-manager.git
synced 2026-07-03 14:08:25 +02:00
Merge pull request #594 from Zagrios/chore/some-reworks-to-remove-watch-functions
[chore] Some reworks to remove watch functions
This commit is contained in:
@@ -27,6 +27,11 @@ ipc.on("download-map", async (args, reply) => {
|
||||
reply(from(maps.downloadMap(args.map, args.version)));
|
||||
});
|
||||
|
||||
ipc.on("last-downloaded-map", (_, reply) => {
|
||||
const maps = LocalMapsManagerService.getInstance();
|
||||
reply(maps.lastDownloadedMap$);
|
||||
})
|
||||
|
||||
ipc.on("one-click-install-map", (args, reply) => {
|
||||
const maps = LocalMapsManagerService.getInstance();
|
||||
reply(from(maps.oneClickDownloadMap(args)))
|
||||
|
||||
@@ -16,15 +16,15 @@ ipc.on("get-installed-mods", (args, reply) => {
|
||||
|
||||
ipc.on("install-mods", (args, reply) => {
|
||||
const modsManager = BsModsManagerService.getInstance();
|
||||
reply(from(modsManager.installMods(args.mods, args.version)));
|
||||
reply(modsManager.installMods(args.mods, args.version));
|
||||
});
|
||||
|
||||
ipc.on("uninstall-mods", (args, reply) => {
|
||||
const modsManager = BsModsManagerService.getInstance();
|
||||
reply(from(modsManager.uninstallMods(args.mods, args.version)));
|
||||
reply(modsManager.uninstallMods(args.mods, args.version));
|
||||
});
|
||||
|
||||
ipc.on("uninstall-all-mods", (args, reply) => {
|
||||
const modsManager = BsModsManagerService.getInstance();
|
||||
reply(from(modsManager.uninstallAllMods(args)));
|
||||
reply(modsManager.uninstallAllMods(args));
|
||||
});
|
||||
|
||||
@@ -6,7 +6,7 @@ const ipc = IpcService.getInstance();
|
||||
|
||||
ipc.on("download-update", (_, reply) => {
|
||||
const updaterService = AutoUpdaterService.getInstance();
|
||||
reply(from(updaterService.downloadUpdate()));
|
||||
reply(updaterService.downloadUpdate());
|
||||
});
|
||||
|
||||
ipc.on("check-update", (_, reply) => {
|
||||
|
||||
@@ -14,7 +14,7 @@ import sanitize from "sanitize-filename";
|
||||
import { DeepLinkService } from "../../deep-link.service";
|
||||
import log from "electron-log";
|
||||
import { WindowManagerService } from "../../window-manager.service";
|
||||
import { Observable, lastValueFrom } from "rxjs";
|
||||
import { Observable, Subject, lastValueFrom } from "rxjs";
|
||||
import { Archive } from "../../../models/archive.class";
|
||||
import { Progression, deleteFolder, ensureFolderExist, getFilesInFolder, getFoldersInFolder, pathExist } from "../../../helpers/fs.helpers";
|
||||
import { readFile } from "fs/promises";
|
||||
@@ -23,7 +23,6 @@ import { allSettled } from "../../../../shared/helpers/promise.helpers";
|
||||
import { splitIntoChunk } from "../../../../shared/helpers/array.helpers";
|
||||
import { SongDetailsCacheService } from "./song-details-cache.service";
|
||||
import { SongCacheService } from "./song-cache.service";
|
||||
import { IpcService } from "../../ipc.service";
|
||||
import { pathToFileURL } from "url";
|
||||
import { sToMs } from "../../../../shared/helpers/time.helpers";
|
||||
import { FieldRequired } from "shared/helpers/type.helpers";
|
||||
@@ -57,7 +56,8 @@ export class LocalMapsManagerService {
|
||||
private readonly linker: FolderLinkerService;
|
||||
private readonly songDetailsCache: SongDetailsCacheService;
|
||||
private readonly songCache: SongCacheService;
|
||||
private readonly ipc: IpcService;
|
||||
|
||||
private readonly _lastDownloadedMap = new Subject<{ map: BsmLocalMap, version?: BSVersion }>();
|
||||
|
||||
private constructor() {
|
||||
this.localVersion = BSLocalVersionService.getInstance();
|
||||
@@ -69,7 +69,6 @@ export class LocalMapsManagerService {
|
||||
this.linker = FolderLinkerService.getInstance();
|
||||
this.songDetailsCache = SongDetailsCacheService.getInstance();
|
||||
this.songCache = SongCacheService.getInstance();
|
||||
this.ipc = IpcService.getInstance();
|
||||
|
||||
const handleOneClick = (mapId: string, isHash = false) => {
|
||||
this.windows.openWindow(`oneclick-download-map.html?mapId=${mapId}&isHash=${isHash}`);
|
||||
@@ -344,7 +343,7 @@ export class LocalMapsManagerService {
|
||||
const localMap = await this.loadMapInfoFromPath(mapPath);
|
||||
localMap.songDetails = this.songDetailsCache.getSongDetails(localMap.hash);
|
||||
|
||||
this.ipc.send<{map: BsmLocalMap, version?: BSVersion}>("map-downloaded", this.windows.getWindows("index.html").at(0), { map: localMap, version });
|
||||
this._lastDownloadedMap.next({ map: localMap, version });
|
||||
|
||||
return localMap;
|
||||
}
|
||||
@@ -392,4 +391,8 @@ export class LocalMapsManagerService {
|
||||
public isDeepLinksEnabled(): boolean {
|
||||
return Array.from(Object.values(this.DEEP_LINKS)).every(link => this.deepLink.isDeepLinkRegistered(link));
|
||||
}
|
||||
|
||||
public get lastDownloadedMap$(): Observable<{ map: BsmLocalMap, version?: BSVersion }> {
|
||||
return this._lastDownloadedMap.asObservable();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { autoUpdater } from "electron-updater";
|
||||
import { autoUpdater, CancellationToken, ProgressInfo } from "electron-updater";
|
||||
import log from "electron-log";
|
||||
import { UtilsService } from "./utils.service";
|
||||
import { gt } from "semver";
|
||||
import { Progression } from "main/helpers/fs.helpers";
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
export class AutoUpdaterService {
|
||||
private static instance: AutoUpdaterService;
|
||||
|
||||
private readonly utilsService: UtilsService;
|
||||
|
||||
public static getInstance(): AutoUpdaterService {
|
||||
if (!AutoUpdaterService.instance) {
|
||||
AutoUpdaterService.instance = new AutoUpdaterService();
|
||||
@@ -18,37 +17,46 @@ export class AutoUpdaterService {
|
||||
constructor() {
|
||||
autoUpdater.logger = log;
|
||||
autoUpdater.autoDownload = false;
|
||||
|
||||
this.utilsService = UtilsService.getInstance();
|
||||
}
|
||||
|
||||
public isUpdateAvailable(): Promise<boolean> {
|
||||
return new Promise(resolve => {
|
||||
autoUpdater
|
||||
.checkForUpdates()
|
||||
.then(info => {
|
||||
const needUpdate = (() => {
|
||||
if (!info?.updateInfo) {
|
||||
return false;
|
||||
}
|
||||
return gt(info.updateInfo.version, autoUpdater.currentVersion.version);
|
||||
})();
|
||||
resolve(needUpdate);
|
||||
})
|
||||
.catch(() => resolve(false));
|
||||
});
|
||||
return autoUpdater.checkForUpdates().then(info => {
|
||||
return !!info?.updateInfo && gt(info.updateInfo.version, autoUpdater.currentVersion.version);
|
||||
}).catch(() => false);
|
||||
}
|
||||
|
||||
public downloadUpdate(): Promise<boolean> {
|
||||
autoUpdater.removeAllListeners("download-progress");
|
||||
autoUpdater.addListener("download-progress", info => {
|
||||
this.utilsService.ipcSend("update-download-progress", { success: true, data: info.percent });
|
||||
});
|
||||
public downloadUpdate(): Observable<Progression> {
|
||||
return new Observable<Progression>(observer => {
|
||||
|
||||
return autoUpdater.downloadUpdate().then(res => !!res && !!res.length);
|
||||
observer.next({ current: 0, total: 0 });
|
||||
|
||||
const progressListener = (progress: ProgressInfo) => {
|
||||
observer.next({ current: progress.transferred, total: progress.total });
|
||||
};
|
||||
|
||||
const downloadedListener = () => {
|
||||
observer.next({ current: 100, total: 100 });
|
||||
};
|
||||
|
||||
autoUpdater.addListener("download-progress", progressListener);
|
||||
autoUpdater.addListener("update-downloaded", downloadedListener);
|
||||
|
||||
const cancelToken = new CancellationToken();
|
||||
|
||||
autoUpdater.downloadUpdate(cancelToken)
|
||||
.catch(err => observer.error(err))
|
||||
.finally(() => observer.complete());
|
||||
|
||||
return () => {
|
||||
cancelToken.cancel();
|
||||
autoUpdater.removeListener("download-progress", progressListener);
|
||||
autoUpdater.removeListener("update-downloaded", downloadedListener);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public quitAndInstall() {
|
||||
autoUpdater.quitAndInstall();
|
||||
log.info("Quit and install");
|
||||
return autoUpdater.quitAndInstall();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +1,32 @@
|
||||
import { BSVersion } from "shared/bs-version.interface";
|
||||
import { DownloadLink, InstallModsResult, Mod, ModInstallProgression, UninstallModsResult } from "shared/models/mods";
|
||||
import { DownloadLink, Mod } from "shared/models/mods";
|
||||
import { BeatModsApiService } from "./beat-mods-api.service";
|
||||
import { BSLocalVersionService } from "../bs-local-version.service";
|
||||
import path from "path";
|
||||
import { UtilsService } from "../utils.service";
|
||||
import md5File from "md5-file";
|
||||
import { RequestService } from "../request.service";
|
||||
import { spawn } from "child_process";
|
||||
import { BS_EXECUTABLE } from "../../constants";
|
||||
import log from "electron-log";
|
||||
import { deleteFolder, pathExist, unlinkPath } from "../../helpers/fs.helpers";
|
||||
import { lastValueFrom } from "rxjs";
|
||||
import { deleteFolder, pathExist, Progression, unlinkPath } from "../../helpers/fs.helpers";
|
||||
import { lastValueFrom, Observable } from "rxjs";
|
||||
import JSZip from "jszip";
|
||||
import { extractZip } from "../../helpers/zip.helpers";
|
||||
import recursiveReadDir from "recursive-readdir";
|
||||
import { sToMs } from "../../../shared/helpers/time.helpers";
|
||||
import { ensureDir, pathExistsSync } from "fs-extra";
|
||||
import { CustomError } from "shared/models/exceptions/custom-error.class";
|
||||
import { popElement } from "shared/helpers/array.helpers";
|
||||
|
||||
export class BsModsManagerService {
|
||||
private static instance: BsModsManagerService;
|
||||
|
||||
private readonly beatModsApi: BeatModsApiService;
|
||||
private readonly bsLocalService: BSLocalVersionService;
|
||||
private readonly utilsService: UtilsService;
|
||||
private readonly requestService: RequestService;
|
||||
|
||||
private manifestMatches: Mod[];
|
||||
|
||||
private nbModsToInstall = 0;
|
||||
private nbInstalledMods = 0;
|
||||
|
||||
private nbModsToUninstall = 0;
|
||||
private nbUninstalledMods = 0;
|
||||
|
||||
public static getInstance(): BsModsManagerService {
|
||||
if (!BsModsManagerService.instance) {
|
||||
BsModsManagerService.instance = new BsModsManagerService();
|
||||
@@ -44,7 +37,6 @@ export class BsModsManagerService {
|
||||
private constructor() {
|
||||
this.beatModsApi = BeatModsApiService.getInstance();
|
||||
this.bsLocalService = BSLocalVersionService.getInstance();
|
||||
this.utilsService = UtilsService.getInstance();
|
||||
this.requestService = RequestService.getInstance();
|
||||
}
|
||||
|
||||
@@ -187,7 +179,6 @@ export class BsModsManagerService {
|
||||
|
||||
private async installMod(mod: Mod, version: BSVersion): Promise<boolean> {
|
||||
log.info("INSTALL MOD", mod.name, "for version", `${version.BSVersion} - ${version.name}`);
|
||||
this.utilsService.ipcSend<ModInstallProgression>("mod-installed", { success: true, data: { name: mod.name, progression: ((this.nbInstalledMods + 1) / this.nbModsToInstall) * 100 } });
|
||||
|
||||
const download = this.getModDownload(mod, version);
|
||||
|
||||
@@ -246,10 +237,6 @@ export class BsModsManagerService {
|
||||
}))
|
||||
: extracted;
|
||||
|
||||
if(res){
|
||||
this.nbInstalledMods++;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -275,9 +262,6 @@ export class BsModsManagerService {
|
||||
}
|
||||
|
||||
private async uninstallMod(mod: Mod, version: BSVersion): Promise<void> {
|
||||
this.nbUninstalledMods++;
|
||||
this.utilsService.ipcSend<ModInstallProgression>("mod-uninstalled", { success: true, data: { name: mod.name, progression: (this.nbUninstalledMods / this.nbModsToUninstall) * 100 } });
|
||||
|
||||
if (mod.name.toLowerCase() === "bsipa") {
|
||||
return this.uninstallBSIPA(mod, version);
|
||||
}
|
||||
@@ -322,81 +306,92 @@ export class BsModsManagerService {
|
||||
return Array.from(modsDict.values());
|
||||
}
|
||||
|
||||
public async installMods(mods: Mod[], version: BSVersion): Promise<InstallModsResult> {
|
||||
if (!mods?.length) {
|
||||
throw CustomError.fromError(new Error("No mods to install"), "no-mods");
|
||||
}
|
||||
public installMods(mods: Mod[], version: BSVersion): Observable<Progression> {
|
||||
const progress = { current: 0, total: mods.length };
|
||||
|
||||
const bsipa = mods.find(mod => mod.name.toLowerCase() === "bsipa");
|
||||
if (bsipa) {
|
||||
mods = mods.filter(mod => mod.name.toLowerCase() !== "bsipa");
|
||||
}
|
||||
return new Observable<Progression>(obs => {
|
||||
(async () => {
|
||||
if (!mods?.length) {
|
||||
throw CustomError.throw(new Error("No mods to install"), "no-mods", mods);
|
||||
}
|
||||
|
||||
this.nbModsToInstall = mods.length + (bsipa ? 1 : 0);
|
||||
this.nbInstalledMods = 0;
|
||||
obs.next(progress);
|
||||
|
||||
if (bsipa) {
|
||||
const installed = await this.installMod(bsipa, version).catch(err => {
|
||||
log.error("INSTALL BSIPA", err);
|
||||
return false;
|
||||
});
|
||||
if (!installed) {
|
||||
throw CustomError.fromError(new Error("Unable to install BSIPA"), "cannot-install-bsipa");
|
||||
}
|
||||
}
|
||||
const bsipa = popElement(mod => mod.name.toLowerCase() === "bsipa", mods);
|
||||
|
||||
for (const mod of mods) {
|
||||
await this.installMod(mod, version);
|
||||
}
|
||||
if(bsipa){
|
||||
const bsipaInstalled = await this.installMod(bsipa, version).catch(err => {
|
||||
log.error("Error while installing BSIPA", err);
|
||||
});
|
||||
|
||||
return {
|
||||
nbModsToInstall: this.nbModsToInstall,
|
||||
nbInstalledMods: this.nbInstalledMods,
|
||||
};
|
||||
if(!bsipaInstalled){
|
||||
throw CustomError.throw(new Error("BSIPA failed to install"), "cannot-install-bsipa");
|
||||
}
|
||||
|
||||
progress.current++;
|
||||
obs.next(progress);
|
||||
}
|
||||
|
||||
for (const mod of mods) {
|
||||
await this.installMod(mod, version);
|
||||
progress.current++;
|
||||
obs.next(progress);
|
||||
}
|
||||
})()
|
||||
.catch(err => obs.error(err))
|
||||
.finally(() => obs.complete());
|
||||
});
|
||||
}
|
||||
|
||||
public async uninstallMods(mods: Mod[], version: BSVersion): Promise<UninstallModsResult> {
|
||||
if (!mods?.length) {
|
||||
throw CustomError.fromError(new Error("No mods to uninstall"), "no-mods");
|
||||
}
|
||||
public uninstallMods(mods: Mod[], version: BSVersion): Observable<Progression> {
|
||||
const progress = { current: 0, total: mods.length };
|
||||
|
||||
this.nbModsToUninstall = mods.length;
|
||||
this.nbUninstalledMods = 0;
|
||||
return new Observable<Progression>(obs => {
|
||||
(async () => {
|
||||
if (!mods?.length) {
|
||||
throw CustomError.throw(new Error("No mods to uninstall"), "no-mods", mods);
|
||||
}
|
||||
|
||||
for (const mod of mods) {
|
||||
await this.uninstallMod(mod, version);
|
||||
}
|
||||
obs.next(progress);
|
||||
|
||||
return {
|
||||
nbModsToUninstall: this.nbModsToUninstall,
|
||||
nbUninstalledMods: this.nbUninstalledMods,
|
||||
};
|
||||
for (const mod of mods) {
|
||||
await this.uninstallMod(mod, version);
|
||||
progress.current++;
|
||||
obs.next(progress);
|
||||
}
|
||||
})()
|
||||
.catch(err => obs.error(err))
|
||||
.finally(() => obs.complete());
|
||||
});
|
||||
}
|
||||
|
||||
public async uninstallAllMods(version: BSVersion): Promise<UninstallModsResult> {
|
||||
const mods = await this.getInstalledMods(version);
|
||||
public uninstallAllMods(version: BSVersion): Observable<Progression> {
|
||||
return new Observable<Progression>(obs => {
|
||||
(async () => {
|
||||
const mods = await this.getInstalledMods(version).catch(err => {
|
||||
log.error(err);
|
||||
return [];
|
||||
});
|
||||
|
||||
if (!mods?.length) {
|
||||
throw CustomError.fromError(new Error("This version has to mods to uninstall"), "no-mods");
|
||||
}
|
||||
const progress = { current: 0, total: mods.length };
|
||||
|
||||
this.nbModsToUninstall = mods.length;
|
||||
this.nbUninstalledMods = 0;
|
||||
obs.next(progress);
|
||||
|
||||
for (const mod of mods) {
|
||||
await this.uninstallMod(mod, version);
|
||||
}
|
||||
for (const mod of mods) {
|
||||
await this.uninstallMod(mod, version);
|
||||
progress.current++;
|
||||
obs.next(progress);
|
||||
}
|
||||
|
||||
const versionPath = await this.bsLocalService.getVersionPath(version);
|
||||
const versionPath = await this.bsLocalService.getVersionPath(version);
|
||||
|
||||
await deleteFolder(path.join(versionPath, ModsInstallFolder.PLUGINS));
|
||||
await deleteFolder(path.join(versionPath, ModsInstallFolder.LIBS));
|
||||
await deleteFolder(path.join(versionPath, ModsInstallFolder.IPA));
|
||||
|
||||
return {
|
||||
nbModsToUninstall: this.nbModsToUninstall,
|
||||
nbUninstalledMods: this.nbUninstalledMods,
|
||||
};
|
||||
await deleteFolder(path.join(versionPath, ModsInstallFolder.PLUGINS));
|
||||
await deleteFolder(path.join(versionPath, ModsInstallFolder.LIBS));
|
||||
await deleteFolder(path.join(versionPath, ModsInstallFolder.IPA));
|
||||
})()
|
||||
.catch(err => obs.error(err))
|
||||
.finally(() => obs.complete());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+8
-6
@@ -87,25 +87,27 @@ export const LocalMapsListPanel = forwardRef<unknown, Props>(({ version, classNa
|
||||
setMaps(null);
|
||||
loadPercent$.next(0);
|
||||
subs.forEach(s => s.unsubscribe());
|
||||
mapsDownloader.removeOnMapDownloadedListener(loadMaps);
|
||||
};
|
||||
}, [isActiveOnce, version, linked]);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
let sub: Subscription;
|
||||
|
||||
if(isActiveOnce){
|
||||
mapsDownloader.addOnMapDownloadedListener((map, targetVersion) => {
|
||||
sub = mapsDownloader.lastDownloadedMap$.subscribe({ next: ({map, version: targetVersion}) => {
|
||||
if (!equal(targetVersion, version)) {
|
||||
return;
|
||||
}
|
||||
setMaps((maps ? [map, ...maps] : [map]));
|
||||
});
|
||||
setMaps((maps$.value ? [map, ...maps$.value] : [map]));
|
||||
}});
|
||||
}
|
||||
|
||||
return () => {
|
||||
mapsDownloader.removeOnMapDownloadedListener(loadMaps);
|
||||
sub?.unsubscribe();
|
||||
}
|
||||
|
||||
}, [isActiveOnce, version, maps])
|
||||
}, [isActiveOnce, version])
|
||||
|
||||
const loadMaps = () => {
|
||||
setMaps(null);
|
||||
|
||||
@@ -67,22 +67,21 @@ export const DownloadMapsModal: ModalComponent<void, { version: BSVersion; owned
|
||||
}, [searchParams]);
|
||||
|
||||
useEffect(() => {
|
||||
const onMapDownloaded = (map: BsmLocalMap, targetVersion: BSVersion) => {
|
||||
|
||||
const sub = mapsDownloader.lastDownloadedMap$.subscribe({ next: ({ map, version: targetVersion }) => {
|
||||
if (!equal(targetVersion, version)) {
|
||||
return;
|
||||
}
|
||||
const downloadedHash = map.hash;
|
||||
setOwnedMapHashs(prev => [...prev, downloadedHash]);
|
||||
};
|
||||
|
||||
mapsDownloader.addOnMapDownloadedListener(onMapDownloaded);
|
||||
}});
|
||||
|
||||
if (mapsDownloader.isDownloading) {
|
||||
progressBar.setStyle(mapsDownloader.progressBarStyle);
|
||||
}
|
||||
|
||||
return () => {
|
||||
mapsDownloader.removeOnMapDownloadedListener(onMapDownloaded);
|
||||
sub.unsubscribe();
|
||||
progressBar.setStyle(null);
|
||||
};
|
||||
}, []);
|
||||
|
||||
@@ -3,22 +3,26 @@ import { BsmCheckbox } from "renderer/components/shared/bsm-checkbox.component";
|
||||
import { Mod } from "shared/models/mods/mod.interface";
|
||||
import { CSSProperties, MouseEvent, useMemo, useRef } from "react";
|
||||
import { useThemeColor } from "renderer/hooks/use-theme-color.hook";
|
||||
import { BsModsManagerService } from "renderer/services/bs-mods-manager.service";
|
||||
import { useObservable } from "renderer/hooks/use-observable.hook";
|
||||
import { PageStateService } from "renderer/services/page-state.service";
|
||||
import useDoubleClick from "use-double-click";
|
||||
import { gt } from "semver";
|
||||
import { useService } from "renderer/hooks/use-service.hook";
|
||||
import { useOnUpdate } from "renderer/hooks/use-on-update.hook";
|
||||
|
||||
type Props = { className?: string; mod: Mod; installedVersion: string; isDependency?: boolean; isSelected?: boolean; onChange?: (val: boolean) => void; wantInfo?: boolean; onWantInfo?: (mod: Mod) => void };
|
||||
type Props = {
|
||||
className?: string;
|
||||
mod: Mod;
|
||||
installedVersion: string;
|
||||
isDependency?: boolean;
|
||||
isSelected?: boolean;
|
||||
onChange?: (val: boolean) => void;
|
||||
wantInfo?: boolean;
|
||||
onWantInfo?: (mod: Mod) => void;
|
||||
disabled?: boolean;
|
||||
onUninstall?: () => void;
|
||||
};
|
||||
|
||||
export function ModItem({ className, mod, installedVersion, isDependency, isSelected, onChange, wantInfo, onWantInfo }: Props) {
|
||||
const modsManager = useService(BsModsManagerService);
|
||||
const pageState = useService(PageStateService);
|
||||
export function ModItem({ className, mod, installedVersion, isDependency, isSelected, onChange, wantInfo, onWantInfo, disabled, onUninstall }: Props) {
|
||||
|
||||
const themeColor = useThemeColor("second-color");
|
||||
const uninstalling = useObservable(() => modsManager.isUninstalling$);
|
||||
const clickRef = useRef();
|
||||
|
||||
const isChecked = useMemo(() => isDependency || isSelected || mod.required, [isDependency, isSelected, mod.required]);
|
||||
@@ -37,10 +41,6 @@ export function ModItem({ className, mod, installedVersion, isDependency, isSele
|
||||
const wantInfoStyle: CSSProperties = wantInfo ? { borderColor: themeColor } : { borderColor: "transparent" };
|
||||
const isOutDated = installedVersion ? gt(mod.version, installedVersion) : false;
|
||||
|
||||
const uninstall = () => {
|
||||
modsManager.uninstallMod(mod, pageState.getState());
|
||||
};
|
||||
|
||||
const handleWantInfo = (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
onWantInfo(mod);
|
||||
@@ -53,7 +53,7 @@ export function ModItem({ className, mod, installedVersion, isDependency, isSele
|
||||
return (
|
||||
<li ref={clickRef} className={`${className} group`}>
|
||||
<div className="h-full aspect-square flex items-center justify-center p-[7px] rounded-l-md bg-inherit ml-3 border-2 border-r-0 z-[1] group-hover:brightness-90" style={wantInfoStyle}>
|
||||
<BsmCheckbox className="h-full aspect-square z-[1] relative bg-inherit" onChange={() => onChange(!isChecked)} disabled={mod.required || isDependency} checked={isChecked} />
|
||||
<BsmCheckbox className="h-full aspect-square z-[1] relative bg-inherit" onChange={() => onChange(!isChecked)} disabled={mod.required || isDependency || disabled} checked={isChecked} />
|
||||
</div>
|
||||
<span className="bg-inherit py-2 pl-3 font-bold text-sm whitespace-nowrap border-t-2 border-b-2 blur-none group-hover:brightness-90" style={wantInfoStyle}>
|
||||
{mod.name}
|
||||
@@ -72,11 +72,11 @@ export function ModItem({ className, mod, installedVersion, isDependency, isSele
|
||||
<BsmButton
|
||||
className="z-[1] h-7 w-7 p-[5px] rounded-full group-hover:brightness-90"
|
||||
icon="trash"
|
||||
disabled={uninstalling}
|
||||
disabled={disabled}
|
||||
withBar={false}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
uninstall();
|
||||
onUninstall?.();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -3,18 +3,22 @@ import { useState } from "react";
|
||||
import { BsmButton } from "renderer/components/shared/bsm-button.component";
|
||||
import { BsmDropdownButton } from "renderer/components/shared/bsm-dropdown-button.component";
|
||||
import { useTranslation } from "renderer/hooks/use-translation.hook";
|
||||
import { BsModsManagerService } from "renderer/services/bs-mods-manager.service";
|
||||
import { PageStateService } from "renderer/services/page-state.service";
|
||||
import { Mod } from "shared/models/mods/mod.interface";
|
||||
import { ModItem } from "./mod-item.component";
|
||||
import { useService } from "renderer/hooks/use-service.hook";
|
||||
|
||||
type Props = { modsMap: Map<string, Mod[]>; installed: Map<string, Mod[]>; modsSelected: Mod[]; onModChange: (selected: boolean, mod: Mod) => void; moreInfoMod?: Mod; onWantInfos: (mod: Mod) => void };
|
||||
type Props = {
|
||||
modsMap: Map<string, Mod[]>;
|
||||
installed: Map<string, Mod[]>;
|
||||
modsSelected: Mod[];
|
||||
onModChange: (selected: boolean, mod: Mod) => void;
|
||||
moreInfoMod?: Mod;
|
||||
onWantInfos: (mod: Mod) => void
|
||||
disabled?: boolean;
|
||||
uninstallMod?: (mods: Mod) => void;
|
||||
uninstallAllMods?: () => void;
|
||||
};
|
||||
|
||||
export function ModsGrid({ modsMap, installed, modsSelected, onModChange, moreInfoMod, onWantInfos }: Props) {
|
||||
|
||||
const pageState = useService(PageStateService);
|
||||
const modsManager = useService(BsModsManagerService);
|
||||
export function ModsGrid({ modsMap, installed, modsSelected, onModChange, moreInfoMod, onWantInfos, disabled, uninstallMod, uninstallAllMods }: Props) {
|
||||
|
||||
const [filter, setFilter] = useState("");
|
||||
const [filterEnabled, setFilterEnabled] = useState(false);
|
||||
@@ -50,10 +54,6 @@ export function ModsGrid({ modsMap, installed, modsSelected, onModChange, moreIn
|
||||
setFilterEnabled(b => !b);
|
||||
};
|
||||
|
||||
const handleUninstallAll = () => {
|
||||
modsManager.uninstallAllMods(pageState.getState());
|
||||
};
|
||||
|
||||
return (
|
||||
modsMap && (
|
||||
<div className="grid gap-y-1 grid-cols-[40px_min-content_min-content_min-content_1fr_min-content] bg-light-main-color-2 dark:bg-main-color-2 text-main-color-1 dark:text-light-main-color-1">
|
||||
@@ -66,7 +66,7 @@ export function ModsGrid({ modsMap, installed, modsSelected, onModChange, moreIn
|
||||
<span className="z-10 sticky flex items-center justify-center top-0 bg-inherit border-b-2 border-main-color-1 h-8 px-2 whitespace-nowrap">{t("pages.version-viewer.mods.mods-grid.header-bar.latest")}</span>
|
||||
<span className="z-10 sticky flex items-center justify-center top-0 bg-inherit border-b-2 border-main-color-1 h-8 whitespace-nowrap">{t("pages.version-viewer.mods.mods-grid.header-bar.description")}</span>
|
||||
<span className="z-10 sticky top-0 bg-inherit border-b-2 border-main-color-1 h-8 flex justify-start items-center py-1 pl-[3px] min-w-[50px]">
|
||||
<BsmDropdownButton className="h-full aspect-square relative rounded-full bg-light-main-color-1 dark:bg-main-color-3" withBar={false} icon="three-dots" buttonClassName="!rounded-full !p-[2px] !bg-light-main-color-2 dark:!bg-main-color-2 hover:!bg-light-main-color-1 dark:hover:!bg-main-color-3" menuTranslationY="5px" items={[{ text: "pages.version-viewer.mods.mods-grid.header-bar.dropdown.uninstall-all", icon: "trash", onClick: handleUninstallAll }]} />
|
||||
<BsmDropdownButton className="h-full aspect-square relative rounded-full bg-light-main-color-1 dark:bg-main-color-3" withBar={false} icon="three-dots" buttonClassName="!rounded-full !p-[2px] !bg-light-main-color-2 dark:!bg-main-color-2 hover:!bg-light-main-color-1 dark:hover:!bg-main-color-3" menuTranslationY="5px" items={[{ text: "pages.version-viewer.mods.mods-grid.header-bar.dropdown.uninstall-all", icon: "trash", onClick: () => uninstallAllMods?.() }]} />
|
||||
</span>
|
||||
|
||||
{Array.from(modsMap.keys()).map(
|
||||
@@ -74,7 +74,19 @@ export function ModsGrid({ modsMap, installed, modsSelected, onModChange, moreIn
|
||||
modsMap.get(key).some(mod => mod.name.toLowerCase().includes(filter)) && (
|
||||
<ul key={key} className="contents">
|
||||
<h2 className="col-span-full py-1 font-bold pl-3">{key}</h2>
|
||||
{modsMap.get(key).map(mod => mod.name?.toLowerCase().includes(filter) && <ModItem key={mod.name} className="contents bg-light-main-color-3 dark:bg-main-color-1 text-main-color-1 dark:text-light-main-color-1 hover:cursor-pointer" mod={mod} installedVersion={installedModVersion(key, mod)} isDependency={isDependency(mod)} isSelected={isSelected(mod)} onChange={val => onModChange(val, mod)} onWantInfo={onWantInfos} wantInfo={mod.name === moreInfoMod?.name} />)}
|
||||
{modsMap.get(key).map(mod => mod.name?.toLowerCase().includes(filter) && (
|
||||
<ModItem
|
||||
key={mod.name}
|
||||
className="contents bg-light-main-color-3 dark:bg-main-color-1 text-main-color-1 dark:text-light-main-color-1 hover:cursor-pointer"
|
||||
mod={mod} installedVersion={installedModVersion(key, mod)}
|
||||
isDependency={isDependency(mod)}
|
||||
isSelected={isSelected(mod)}
|
||||
onChange={val => onModChange(val, mod)}
|
||||
onWantInfo={onWantInfos}
|
||||
wantInfo={mod.name === moreInfoMod?.name}
|
||||
disabled={disabled}
|
||||
onUninstall={() => uninstallMod?.(mod)} />
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
)}
|
||||
|
||||
@@ -9,8 +9,7 @@ import { BsmButton } from "renderer/components/shared/bsm-button.component";
|
||||
import BeatWaitingImg from "../../../../../../assets/images/apngs/beat-waiting.png";
|
||||
import BeatConflictImg from "../../../../../../assets/images/apngs/beat-conflict.png";
|
||||
import { useObservable } from "renderer/hooks/use-observable.hook";
|
||||
import { skip, filter } from "rxjs/operators";
|
||||
import { Subscription, lastValueFrom, noop } from "rxjs";
|
||||
import { lastValueFrom } from "rxjs";
|
||||
import { useTranslation } from "renderer/hooks/use-translation.hook";
|
||||
import { LinkOpenerService } from "renderer/services/link-opener.service";
|
||||
import { useInView } from "framer-motion";
|
||||
@@ -20,6 +19,8 @@ import { OsDiagnosticService } from "renderer/services/os-diagnostic.service";
|
||||
import { lt } from "semver";
|
||||
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";
|
||||
|
||||
export function ModsSlide({ version, onDisclamerDecline }: { version: BSVersion; onDisclamerDecline: () => void }) {
|
||||
const ACCEPTED_DISCLAIMER_KEY = "accepted-mods-disclaimer";
|
||||
@@ -39,7 +40,8 @@ export function ModsSlide({ version, onDisclamerDecline }: { version: BSVersion;
|
||||
const [moreInfoMod, setMoreInfoMod] = useState(null as Mod);
|
||||
const [reinstallAllMods, setReinstallAllMods] = useState(false);
|
||||
const isOnline = useObservable(() => os.isOnline$);
|
||||
const installing = useObservable(() => modsManager.isInstalling$);
|
||||
const [installing, setInstalling] = useState(false);
|
||||
const [uninstalling, setUninstalling] = useState(false);
|
||||
|
||||
const downloadRef = useRef(null);
|
||||
const [downloadWith, setDownloadWidth] = useState(0);
|
||||
@@ -103,11 +105,34 @@ export function ModsSlide({ version, onDisclamerDecline }: { version: BSVersion;
|
||||
return;
|
||||
}
|
||||
|
||||
modsManager.installMods(modsToInstall, version).then(() => {
|
||||
setInstalling(() => true)
|
||||
lastValueFrom(modsManager.installMods(modsToInstall, version)).then(() => {
|
||||
loadMods();
|
||||
}).catch(noop).finally(() => setInstalling(() => false));
|
||||
};
|
||||
|
||||
const uninstallMod = (mod: Mod): void => {
|
||||
setUninstalling(() => true);
|
||||
lastValueFrom(modsManager.uninstallMod(mod, version)).catch(noop).finally(() => {
|
||||
loadMods();
|
||||
setUninstalling(() => false);
|
||||
});
|
||||
};
|
||||
|
||||
const uninstallAllMods = async () => {
|
||||
const res = await modals.openModal(UninstallAllModsModal, {data: version});
|
||||
|
||||
if (res.exitCode !== ModalExitCode.COMPLETED) {
|
||||
return;
|
||||
}
|
||||
|
||||
setUninstalling(() => true);
|
||||
lastValueFrom(modsManager.uninstallAllMods(version)).catch(noop).finally(() => {
|
||||
loadMods();
|
||||
setUninstalling(() => false);
|
||||
})
|
||||
};
|
||||
|
||||
const loadMods = (): Promise<void> => {
|
||||
if (os.isOffline) {
|
||||
return Promise.resolve();
|
||||
@@ -128,7 +153,6 @@ export function ModsSlide({ version, onDisclamerDecline }: { version: BSVersion;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const subs: Subscription[] = [];
|
||||
|
||||
if(!isVisible || !isOnline){
|
||||
return noop();
|
||||
@@ -153,22 +177,12 @@ export function ModsSlide({ version, onDisclamerDecline }: { version: BSVersion;
|
||||
}
|
||||
|
||||
loadMods();
|
||||
|
||||
subs.push(
|
||||
modsManager.isUninstalling$.pipe(
|
||||
skip(1),
|
||||
filter(uninstalling => !uninstalling)
|
||||
).subscribe(() => {
|
||||
loadMods();
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
return () => {
|
||||
setMoreInfoMod(null);
|
||||
setModsAvailable(null);
|
||||
setModsInstalled(null);
|
||||
subs.forEach(s => s.unsubscribe());
|
||||
};
|
||||
}, [isVisible, isOnline, version]);
|
||||
|
||||
@@ -195,7 +209,7 @@ export function ModsSlide({ version, onDisclamerDecline }: { version: BSVersion;
|
||||
return (
|
||||
<>
|
||||
<div className="grow overflow-y-scroll w-full min-h-0 scrollbar-default p-0 m-0">
|
||||
<ModsGrid modsMap={modsAvailable} installed={modsInstalled} modsSelected={modsSelected} onModChange={handleModChange} moreInfoMod={moreInfoMod} onWantInfos={handleMoreInfo} />
|
||||
<ModsGrid modsMap={modsAvailable} installed={modsInstalled} modsSelected={modsSelected} onModChange={handleModChange} moreInfoMod={moreInfoMod} onWantInfos={handleMoreInfo} disabled={uninstalling || installing} uninstallMod={uninstallMod} uninstallAllMods={uninstallAllMods}/>
|
||||
</div>
|
||||
<div className="shrink-0 flex items-center justify-between px-3 py-2">
|
||||
<BsmButton className="flex items-center justify-center rounded-md px-1 h-8" text="pages.version-viewer.mods.buttons.more-infos" typeColor="cancel" withBar={false} disabled={!moreInfoMod} onClick={handleOpenMoreInfo} style={{ width: downloadWith }}/>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { map } from "rxjs/operators";
|
||||
import { IpcService } from "./ipc.service";
|
||||
import { ProgressBarService } from "./progress-bar.service";
|
||||
import { ModalService } from "renderer/services/modale.service";
|
||||
import { ChangelogModal } from "renderer/components/modal/modal-types/chabgelog-modal/changelog-modal.component";
|
||||
import { ConfigurationService } from "./configuration.service";
|
||||
import { Observable, lastValueFrom } from "rxjs";
|
||||
import { Progression } from "main/helpers/fs.helpers";
|
||||
|
||||
|
||||
export interface Changelog {
|
||||
@@ -22,8 +22,6 @@ export class AutoUpdaterService {
|
||||
private progressService: ProgressBarService;
|
||||
private ipcService: IpcService;
|
||||
|
||||
private downloadProgress$: Observable<number>;
|
||||
|
||||
private modal: ModalService;
|
||||
|
||||
private configurationService: ConfigurationService;
|
||||
@@ -42,22 +40,28 @@ export class AutoUpdaterService {
|
||||
this.ipcService = IpcService.getInstance();
|
||||
this.modal = ModalService.getInstance();
|
||||
this.configurationService = ConfigurationService.getInstance();
|
||||
|
||||
this.downloadProgress$ = this.ipcService.watch<number>("update-download-progress").pipe(map(res => (res.success ? res.data : 0)));
|
||||
}
|
||||
|
||||
public isUpdateAvailable(): Promise<boolean> {
|
||||
return lastValueFrom(this.ipcService.sendV2("check-update")).catch(() => false);
|
||||
}
|
||||
|
||||
public downloadUpdate(): Promise<boolean> {
|
||||
const promise = lastValueFrom(this.ipcService.sendV2("download-update")).then(() => true).catch(() => false);
|
||||
this.progressService.show(this.downloadProgress$, true);
|
||||
return promise;
|
||||
public downloadUpdate(): Observable<Progression> {
|
||||
return new Observable<Progression>(obs => {
|
||||
const download$ = this.ipcService.sendV2("download-update");
|
||||
this.progressService.show(download$, true);
|
||||
|
||||
const sub = download$.subscribe(obs);
|
||||
|
||||
return () => {
|
||||
sub.unsubscribe();
|
||||
this.progressService.hide(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public quitAndInstall() {
|
||||
lastValueFrom(this.ipcService.sendV2("install-update"));
|
||||
public quitAndInstall(): Promise<void> {
|
||||
return lastValueFrom(this.ipcService.sendV2("install-update"));
|
||||
}
|
||||
|
||||
public getLastAppVersion(): string {
|
||||
|
||||
@@ -1,17 +1,11 @@
|
||||
import { UninstallAllModsModal } from "renderer/components/modal/modal-types/uninstall-all-mods-modal.component";
|
||||
import { UninstallModModal } from "renderer/components/modal/modal-types/uninstall-mod-modal.component";
|
||||
import { Observable, BehaviorSubject, lastValueFrom } from "rxjs";
|
||||
import { map } from "rxjs/operators";
|
||||
import { Observable, BehaviorSubject, throwError, of } from "rxjs";
|
||||
import { catchError, tap } from "rxjs/operators";
|
||||
import { BSVersion } from "shared/bs-version.interface";
|
||||
import { Mod, ModInstallProgression } from "shared/models/mods";
|
||||
import { ProgressionInterface } from "shared/models/progress-bar";
|
||||
import { Mod } from "shared/models/mods";
|
||||
import { IpcService } from "./ipc.service";
|
||||
import { ModalExitCode, ModalService } from "./modale.service";
|
||||
import { NotificationType } from "../../shared/models/notification/notification.model";
|
||||
import { OsDiagnosticService } from "./os-diagnostic.service";
|
||||
import { ProgressBarService } from "./progress-bar.service";
|
||||
import { NotificationService } from "./notification.service";
|
||||
import { CustomError } from "shared/models/exceptions/custom-error.class";
|
||||
import { Progression } from "main/helpers/fs.helpers";
|
||||
|
||||
export class BsModsManagerService {
|
||||
private static instance: BsModsManagerService;
|
||||
@@ -20,11 +14,8 @@ export class BsModsManagerService {
|
||||
|
||||
private readonly ipcService: IpcService;
|
||||
private readonly progressBar: ProgressBarService;
|
||||
private readonly modals: ModalService;
|
||||
private readonly notifications: NotificationService;
|
||||
private readonly os: OsDiagnosticService;
|
||||
|
||||
public readonly isInstalling$: BehaviorSubject<boolean> = new BehaviorSubject(false);
|
||||
public readonly isUninstalling$: BehaviorSubject<boolean> = new BehaviorSubject(false);
|
||||
|
||||
public static getInstance(): BsModsManagerService {
|
||||
@@ -37,9 +28,7 @@ export class BsModsManagerService {
|
||||
private constructor() {
|
||||
this.ipcService = IpcService.getInstance();
|
||||
this.progressBar = ProgressBarService.getInstance();
|
||||
this.modals = ModalService.getInstance();
|
||||
this.notifications = NotificationService.getInstance();
|
||||
this.os = OsDiagnosticService.getInstance();
|
||||
}
|
||||
|
||||
public getAvailableMods(version: BSVersion): Observable<Mod[]> {
|
||||
@@ -50,91 +39,98 @@ export class BsModsManagerService {
|
||||
return this.ipcService.sendV2("get-installed-mods", version);
|
||||
}
|
||||
|
||||
public installMods(mods: Mod[], version: BSVersion): Promise<void> {
|
||||
if (this.os.isOffline) {
|
||||
this.notifications.notifyError({
|
||||
title: "notifications.shared.errors.titles.no-internet",
|
||||
desc: "notifications.shared.errors.msg.no-internet",
|
||||
});
|
||||
return Promise.resolve();
|
||||
}
|
||||
public installMods(mods: Mod[], version: BSVersion): Observable<Progression> {
|
||||
|
||||
if (!this.progressBar.require()) {
|
||||
return Promise.resolve();
|
||||
return throwError(() => new Error("Action already in progress"));
|
||||
}
|
||||
|
||||
const progress$: Observable<ProgressionInterface> = this.ipcService.watch<ModInstallProgression>("mod-installed").pipe(
|
||||
map(res => {
|
||||
return { progression: res.data.progression, label: res.data.name } as ProgressionInterface;
|
||||
})
|
||||
);
|
||||
return new Observable<Progression>(obs => {
|
||||
const install$ = this.ipcService.sendV2("install-mods", { mods, version });
|
||||
this.progressBar.show(install$.pipe(catchError(() => of({ current: 0, total: 0} as Progression))), true, { paddingLeft: "190px", paddingRight: "190px", bottom: "20px" });
|
||||
|
||||
this.progressBar.show(progress$, true, { paddingLeft: "190px", paddingRight: "190px", bottom: "20px" });
|
||||
const sub = install$.pipe(
|
||||
tap({
|
||||
error: err => {
|
||||
if(err?.code){
|
||||
this.notifications.notifyError({ title: "notifications.types.error", desc: `notifications.mods.install-mods.msg.errors.${err?.code}`, duration: this.NOTIFICATION_DURATION });
|
||||
} else {
|
||||
this.notifications.notifyError({ title: "notifications.types.error", desc: err?.message ?? err, duration: this.NOTIFICATION_DURATION });
|
||||
}
|
||||
},
|
||||
complete: () => {
|
||||
this.notifications.notifySuccess({ title: "notifications.mods.install-mods.titles.success", desc: "notifications.mods.install-mods.msg.success", duration: this.NOTIFICATION_DURATION });
|
||||
},
|
||||
})
|
||||
).subscribe(obs);
|
||||
|
||||
this.isInstalling$.next(true);
|
||||
|
||||
return lastValueFrom(this.ipcService.sendV2("install-mods", { mods, version })).then(res => {
|
||||
const isFullyInstalled = res.nbInstalledMods === res.nbModsToInstall;
|
||||
|
||||
const title = `notifications.mods.install-mods.titles.${isFullyInstalled ? "success" : "warning"}`;
|
||||
const desc = `notifications.mods.install-mods.msg.${isFullyInstalled ? "success" : "warning"}`;
|
||||
|
||||
this.notifications.notify({ type: isFullyInstalled ? NotificationType.SUCCESS : NotificationType.WARNING, title, desc, duration: this.NOTIFICATION_DURATION });
|
||||
}).catch((e: CustomError) => {
|
||||
this.notifications.notifyError({ title: "notifications.types.error", desc: `notifications.mods.install-mods.msg.errors.${e?.code}`, duration: this.NOTIFICATION_DURATION });
|
||||
}).finally(() => {
|
||||
this.isInstalling$.next(false);
|
||||
this.progressBar.hide();
|
||||
})
|
||||
}
|
||||
public async uninstallMod(mod: Mod, version: BSVersion): Promise<void> {
|
||||
if (!this.progressBar.require()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const modalRes = await this.modals.openModal(UninstallModModal, {data: mod});
|
||||
|
||||
if (modalRes.exitCode !== ModalExitCode.COMPLETED) {
|
||||
return;
|
||||
}
|
||||
|
||||
const progress$ = this.ipcService.watch<ModInstallProgression>("mod-uninstalled").pipe(map(res => res.data.progression));
|
||||
this.progressBar.show(progress$, true, { paddingLeft: "190px", paddingRight: "190px", bottom: "20px" });
|
||||
|
||||
this.isUninstalling$.next(true);
|
||||
|
||||
return lastValueFrom(this.ipcService.sendV2("uninstall-mods", { mods: [mod], version })).then(() => {
|
||||
this.notifications.notifySuccess({ title: "notifications.mods.uninstall-mod.titles.success", duration: this.NOTIFICATION_DURATION });
|
||||
}).catch((e: CustomError) => {
|
||||
this.notifications.notifyError({ title: "notifications.types.error", desc: `notifications.mods.uninstall-mod.msg.errors.${e?.code}`, duration: this.NOTIFICATION_DURATION });
|
||||
}).finally(() => {
|
||||
this.isUninstalling$.next(false);
|
||||
this.progressBar.hide();
|
||||
})
|
||||
return () => {
|
||||
sub.unsubscribe();
|
||||
this.progressBar.hide(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async uninstallAllMods(version: BSVersion) {
|
||||
public uninstallMod(mod: Mod, version: BSVersion): Observable<Progression> {
|
||||
if (!this.progressBar.require()) {
|
||||
return;
|
||||
return throwError(() => new Error("Action already in progress"));
|
||||
}
|
||||
|
||||
const modalRes = await this.modals.openModal(UninstallAllModsModal, {data: version});
|
||||
return new Observable<Progression>(obs => {
|
||||
const uninstall$ = this.ipcService.sendV2("uninstall-mods", { mods: [mod], version });
|
||||
this.progressBar.show(uninstall$.pipe(catchError(() => of({ current: 0, total: 0} as Progression))), true, { paddingLeft: "190px", paddingRight: "190px", bottom: "20px" });
|
||||
|
||||
if (modalRes.exitCode !== ModalExitCode.COMPLETED) {
|
||||
return;
|
||||
const sub = uninstall$.pipe(
|
||||
tap({
|
||||
error: err => {
|
||||
if(err?.code){
|
||||
this.notifications.notifyError({ title: "notifications.types.error", desc: `notifications.mods.uninstall-mod.msg.errors.${err?.code}`, duration: this.NOTIFICATION_DURATION });
|
||||
} else {
|
||||
this.notifications.notifyError({ title: "notifications.types.error", desc: err?.message ?? err, duration: this.NOTIFICATION_DURATION });
|
||||
}
|
||||
},
|
||||
complete: () => {
|
||||
this.notifications.notifySuccess({ title: "notifications.mods.uninstall-mod.titles.success", duration: this.NOTIFICATION_DURATION });
|
||||
},
|
||||
})
|
||||
).subscribe(obs);
|
||||
|
||||
return () => {
|
||||
sub.unsubscribe();
|
||||
this.progressBar.hide(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public uninstallAllMods(version: BSVersion): Observable<Progression> {
|
||||
if (!this.progressBar.require()) {
|
||||
return throwError(() => new Error("Action already in progress"));
|
||||
}
|
||||
|
||||
const progress$ = this.ipcService.watch<ModInstallProgression>("mod-uninstalled").pipe(map(res => res.data.progression));
|
||||
this.progressBar.show(progress$, true, { paddingLeft: "190px", paddingRight: "190px", bottom: "20px" });
|
||||
return new Observable<Progression>(obs => {
|
||||
const uninstall$ = this.ipcService.sendV2("uninstall-all-mods", version);
|
||||
this.progressBar.show(uninstall$.pipe(catchError(() => of({ current: 0, total: 0} as Progression))), true, { paddingLeft: "190px", paddingRight: "190px", bottom: "20px" });
|
||||
|
||||
this.isUninstalling$.next(true);
|
||||
return lastValueFrom(this.ipcService.sendV2("uninstall-all-mods", version)).then(() => {
|
||||
this.notifications.notifySuccess({ title: "notifications.mods.uninstall-all-mods.titles.success", desc: "notifications.mods.uninstall-all-mods.msg.success", duration: this.NOTIFICATION_DURATION });
|
||||
}).catch((e: CustomError) => {
|
||||
this.notifications.notifyError({ title: "notifications.types.error", desc: `notifications.mods.uninstall-all-mods.msg.errors.${e?.code}`, duration: this.NOTIFICATION_DURATION });
|
||||
}).finally(() => {
|
||||
this.isUninstalling$.next(false);
|
||||
this.progressBar.hide();
|
||||
})
|
||||
const sub = uninstall$.pipe(
|
||||
tap({
|
||||
error: err => {
|
||||
if(err?.code){
|
||||
this.notifications.notifyError({ title: "notifications.types.error", desc: `notifications.mods.uninstall-all-mods.msg.errors.${err?.code}`, duration: this.NOTIFICATION_DURATION });
|
||||
} else {
|
||||
this.notifications.notifyError({ title: "notifications.types.error", desc: err?.message ?? err, duration: this.NOTIFICATION_DURATION });
|
||||
}
|
||||
},
|
||||
complete: () => {
|
||||
this.notifications.notifySuccess({ title: "notifications.mods.uninstall-all-mods.titles.success", desc: "notifications.mods.uninstall-all-mods.msg.success", duration: this.NOTIFICATION_DURATION });
|
||||
},
|
||||
})
|
||||
).subscribe(obs);
|
||||
|
||||
return () => {
|
||||
sub.unsubscribe();
|
||||
this.progressBar.hide(true);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { DownloadMapsModal } from "renderer/components/modal/modal-types/download-maps-modal.component";
|
||||
import { map, filter } from "rxjs/operators";
|
||||
import { map, filter, share } from "rxjs/operators";
|
||||
import { BehaviorSubject, timer, Observable, lastValueFrom } from "rxjs";
|
||||
import { BSVersion } from "shared/bs-version.interface";
|
||||
import { ModalResponse, ModalService } from "./modale.service";
|
||||
@@ -30,9 +30,10 @@ export class MapsDownloaderService {
|
||||
private readonly mapsQueue$: BehaviorSubject<MapDownload[]> = new BehaviorSubject([]);
|
||||
private readonly currentDownload$: BehaviorSubject<MapDownload> = new BehaviorSubject(null);
|
||||
private queueMaxLenght = 0;
|
||||
private downloadedListerners: ((map: BsmLocalMap, version: BSVersion) => void)[] = [];
|
||||
public readonly progressBarStyle: CSSProperties = { zIndex: 100000, position: "fixed", bottom: "10px", right: 0 };
|
||||
|
||||
private _lastDownloadedMap$: Observable<{map: BsmLocalMap, version?: BSVersion}>;
|
||||
|
||||
private constructor() {
|
||||
this.modals = ModalService.getInstance();
|
||||
this.progressBar = ProgressBarService.getInstance();
|
||||
@@ -43,10 +44,6 @@ export class MapsDownloaderService {
|
||||
this.mapsQueue$.pipe(filter(queue => queue.length === 0)).subscribe(() => {
|
||||
this.queueMaxLenght = 0;
|
||||
});
|
||||
|
||||
this.ipc.watch<{map: BsmLocalMap, version?: BSVersion}>("map-downloaded").subscribe((data) => {
|
||||
this.downloadedListerners.forEach(func => func(data.map, data.version));
|
||||
});
|
||||
}
|
||||
|
||||
private async startDownloadMaps() {
|
||||
@@ -119,16 +116,19 @@ export class MapsDownloaderService {
|
||||
return this.mapsQueue$.asObservable();
|
||||
}
|
||||
|
||||
public addOnMapDownloadedListener(func: (map: BsmLocalMap, version: BSVersion) => void) {
|
||||
this.downloadedListerners.push(func);
|
||||
}
|
||||
public get lastDownloadedMap$(): Observable<{map: BsmLocalMap, version?: BSVersion}> {
|
||||
if(!this._lastDownloadedMap$){
|
||||
this._lastDownloadedMap$ = new Observable<{map: BsmLocalMap, version?: BSVersion}>(observer => {
|
||||
const sub = this.ipc.sendV2("last-downloaded-map").subscribe(observer);
|
||||
|
||||
public removeOnMapDownloadedListener(func: (map: BsmLocalMap, version: BSVersion) => void) {
|
||||
const funcIndex = this.downloadedListerners.indexOf(func);
|
||||
if (funcIndex < 0) {
|
||||
return;
|
||||
return () => {
|
||||
this._lastDownloadedMap$ = null;
|
||||
sub.unsubscribe();
|
||||
}
|
||||
}).pipe(share());
|
||||
}
|
||||
this.downloadedListerners.splice(funcIndex, 1);
|
||||
|
||||
return this._lastDownloadedMap$;
|
||||
}
|
||||
|
||||
public async oneClickInstallMap(map: BsvMapDetail): Promise<void> {
|
||||
@@ -145,3 +145,5 @@ export interface MapDownload {
|
||||
map: BsvMapDetail;
|
||||
version: BSVersion;
|
||||
}
|
||||
|
||||
export type MapsDownloadedListener = (map: BsmLocalMap, version: BSVersion) => void;
|
||||
|
||||
@@ -7,6 +7,8 @@ import { useTranslation } from "../hooks/use-translation.hook";
|
||||
import { AutoUpdaterService } from "../services/auto-updater.service";
|
||||
import { WindowManagerService } from "../services/window-manager.service";
|
||||
import { useService } from "renderer/hooks/use-service.hook";
|
||||
import { lastValueFrom } from "rxjs";
|
||||
import { logRenderError } from "renderer";
|
||||
|
||||
export default function Launcher() {
|
||||
const updaterService = useService(AutoUpdaterService);
|
||||
@@ -24,13 +26,11 @@ export default function Launcher() {
|
||||
return windowService.openThenCloseAll("index.html");
|
||||
}
|
||||
setText("auto-update.downloading");
|
||||
updaterService.downloadUpdate().then(installed => {
|
||||
if (!installed) {
|
||||
return windowService.openThenCloseAll("index.html");
|
||||
}
|
||||
updaterService.quitAndInstall();
|
||||
});
|
||||
});
|
||||
|
||||
return lastValueFrom(updaterService.downloadUpdate())
|
||||
.then(() => updaterService.quitAndInstall())
|
||||
.catch((err) => {logRenderError("omg", err); windowService.openThenCloseAll("index.html")});
|
||||
}).catch(() => windowService.openThenCloseAll("index.html"));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
||||
@@ -11,7 +11,7 @@ import { DepotDownloaderEvent } from "../bs-version-download/depot-downloader.mo
|
||||
import { MSGetQuery, MSModel, MSModelType } from "../models/model-saber.model";
|
||||
import { ModelDownload } from "renderer/services/models-management/models-downloader.service";
|
||||
import { BsmLocalModel } from "../models/bsm-local-model.interface";
|
||||
import { InstallModsResult, Mod, UninstallModsResult } from "../mods";
|
||||
import { Mod } from "../mods";
|
||||
import { BPList, DownloadPlaylistProgressionData } from "../playlists/playlist.interface";
|
||||
import { VersionLinkerAction } from "renderer/services/version-folder-linker.service";
|
||||
import { FileFilter, OpenDialogReturnValue } from "electron";
|
||||
@@ -59,6 +59,7 @@ export interface IpcChannelMapping {
|
||||
"delete-maps": { request: BsmLocalMap[], response: DeleteMapsProgress };
|
||||
"export-maps": { request: { version: BSVersion; maps: BsmLocalMap[]; outPath: string }, response: Progression };
|
||||
"download-map": { request: { map: BsvMapDetail; version: BSVersion }, response: BsmLocalMap };
|
||||
"last-downloaded-map": { request: void, response: { version?: BSVersion, map: BsmLocalMap } };
|
||||
"one-click-install-map": { request: BsvMapDetail, response: void };
|
||||
"register-maps-deep-link": { request: void, response: boolean };
|
||||
"unregister-maps-deep-link": { request: void, response: boolean };
|
||||
@@ -78,9 +79,9 @@ export interface IpcChannelMapping {
|
||||
/* ** 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: InstallModsResult };
|
||||
"uninstall-mods": { request: { mods: Mod[]; version: BSVersion }, response: UninstallModsResult };
|
||||
"uninstall-all-mods": { request: BSVersion, response: UninstallModsResult };
|
||||
"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-playlist-ipcs ** */
|
||||
"one-click-install-playlist": { request: string, response: Progression<DownloadPlaylistProgressionData> };
|
||||
@@ -110,7 +111,7 @@ export interface IpcChannelMapping {
|
||||
"relink-all-versions-folders": { request: void, response: void };
|
||||
|
||||
/* ** launcher-ipcs ** */
|
||||
"download-update": { request: void, response: boolean };
|
||||
"download-update": { request: void, response: Progression };
|
||||
"check-update": { request: void, response: boolean };
|
||||
"install-update": { request: void, response: void };
|
||||
|
||||
|
||||
Reference in New Issue
Block a user