mirror of
https://github.com/Zagrios/bs-manager.git
synced 2026-07-03 14:08:25 +02:00
[feature] Outdated mod notification
This commit is contained in:
@@ -148,6 +148,14 @@
|
||||
"all-mods-already-installed": {
|
||||
"title": "Mods bereits installiert",
|
||||
"description": "Alle ausgewählten Mods sind bereits installiert"
|
||||
},
|
||||
"outdated-mods": {
|
||||
"title": "Veraltetes Mod",
|
||||
"title-plural": "Veraltete Mods",
|
||||
"description": "Das Mod {name} ist veraltet. Möchten Sie es aktualisieren?",
|
||||
"description-plural": "Diese Version enthält {nb} veraltete Mods. Möchten Sie sie aktualisieren?",
|
||||
"dont-remind-me": "Nicht mehr daran erinnern",
|
||||
"update": "Aktualisieren"
|
||||
}
|
||||
},
|
||||
"drop-zone": {
|
||||
|
||||
@@ -148,6 +148,14 @@
|
||||
"all-mods-already-installed": {
|
||||
"title": "Mods already installed",
|
||||
"description": "All selected mods are already installed"
|
||||
},
|
||||
"outdated-mods": {
|
||||
"title": "Outdated Mod",
|
||||
"title-plural": "Outdated Mods",
|
||||
"description": "{name} mod is outdated. Do you want to update it?",
|
||||
"description-plural": "This version has {nb} outdated mods. Do you want to update them?",
|
||||
"dont-remind-me": "Don't remind me",
|
||||
"update": "Update"
|
||||
}
|
||||
},
|
||||
"drop-zone": {
|
||||
|
||||
@@ -148,6 +148,14 @@
|
||||
"all-mods-already-installed": {
|
||||
"title": "Mods ya instalados",
|
||||
"description": "Todos los mods seleccionados ya están instalados"
|
||||
},
|
||||
"outdated-mods": {
|
||||
"title": "Mod desactualizado",
|
||||
"title-plural": "Mods desactualizados",
|
||||
"description": "El mod {name} está desactualizado. ¿Quieres actualizarlo?",
|
||||
"description-plural": "Esta versión tiene {nb} mods desactualizados. ¿Quieres actualizarlos?",
|
||||
"dont-remind-me": "No me lo recuerdes",
|
||||
"update": "Actualizar"
|
||||
}
|
||||
},
|
||||
"drop-zone": {
|
||||
|
||||
@@ -148,6 +148,14 @@
|
||||
"all-mods-already-installed": {
|
||||
"title": "Mods déjà installées",
|
||||
"description": "Tous les mods séléctionnées sont déjà installées"
|
||||
},
|
||||
"outdated-mods": {
|
||||
"title": "Mod obsolète",
|
||||
"title-plural": "Mods obsolètes",
|
||||
"description": "Le mod {name} est obsolète. Voulez-vous le mettre à jour ?",
|
||||
"description-plural": "Cette version contient {nb} mods obsolètes. Voulez-vous les mettre à jour ?",
|
||||
"dont-remind-me": "Ne plus me rappeler",
|
||||
"update": "Mettre à jour"
|
||||
}
|
||||
},
|
||||
"drop-zone": {
|
||||
|
||||
@@ -148,6 +148,14 @@
|
||||
"all-mods-already-installed": {
|
||||
"title": "Mod già installata",
|
||||
"description": "Tutte le mod selezionate sono già installate"
|
||||
},
|
||||
"outdated-mods": {
|
||||
"title": "Mod obsoleto",
|
||||
"title-plural": "Mod obsoleti",
|
||||
"description": "Il mod {name} è obsoleto. Vuoi aggiornarlo?",
|
||||
"description-plural": "Questa versione ha {nb} mod obsoleti. Vuoi aggiornarli?",
|
||||
"dont-remind-me": "Non ricordarmelo",
|
||||
"update": "Aggiorna"
|
||||
}
|
||||
},
|
||||
"drop-zone": {
|
||||
|
||||
@@ -148,6 +148,14 @@
|
||||
"all-mods-already-installed": {
|
||||
"title": "すでにインストール済みのMOD",
|
||||
"description": "選択したすべてのMODはすでにインストールされています"
|
||||
},
|
||||
"outdated-mods": {
|
||||
"title": "古いMod",
|
||||
"title-plural": "古いMod",
|
||||
"description": "{name} Modが古くなっています。更新しますか?",
|
||||
"description-plural": "このバージョンには古いModが{nb}個あります。更新しますか?",
|
||||
"dont-remind-me": "もう知らせない",
|
||||
"update": "更新する"
|
||||
}
|
||||
},
|
||||
"drop-zone": {
|
||||
|
||||
@@ -148,7 +148,16 @@
|
||||
"all-mods-already-installed": {
|
||||
"title": "이미 설치된 모드",
|
||||
"description": "선택한 모든 모드가 이미 설치되었습니다."
|
||||
},
|
||||
"outdated-mods": {
|
||||
"title": "구식 모드",
|
||||
"title-plural": "구식 모드",
|
||||
"description": "{name} 모드가 오래되었습니다. 업데이트하시겠습니까?",
|
||||
"description-plural": "이 버전에는 {nb}개의 구식 모드가 있습니다. 업데이트하시겠습니까?",
|
||||
"dont-remind-me": "다시는 알리지 않기",
|
||||
"update": "업데이트"
|
||||
}
|
||||
|
||||
},
|
||||
"drop-zone": {
|
||||
"text": "모드를 가져오기",
|
||||
|
||||
@@ -148,6 +148,14 @@
|
||||
"all-mods-already-installed": {
|
||||
"title": "Моды уже установлены",
|
||||
"description": "Все выбранные моды уже установлены"
|
||||
},
|
||||
"outdated-mods": {
|
||||
"title": "Устаревший мод",
|
||||
"title-plural": "Устаревшие моды",
|
||||
"description": "Мод {name} устарел. Хотите его обновить?",
|
||||
"description-plural": "В этой версии есть {nb} устаревших модов. Хотите их обновить?",
|
||||
"dont-remind-me": "Не напоминать мне",
|
||||
"update": "Обновить"
|
||||
}
|
||||
},
|
||||
"drop-zone": {
|
||||
|
||||
@@ -148,6 +148,14 @@
|
||||
"all-mods-already-installed": {
|
||||
"title": "模組已安裝",
|
||||
"description": "所有選中的模組已經安裝"
|
||||
},
|
||||
"outdated-mods": {
|
||||
"title": "過時的模組",
|
||||
"title-plural": "過時的模組",
|
||||
"description": "{name} 模組已過時。您想更新嗎?",
|
||||
"description-plural": "此版本有 {nb} 個過時的模組。您想更新它們嗎?",
|
||||
"dont-remind-me": "不要提醒我",
|
||||
"update": "更新"
|
||||
}
|
||||
},
|
||||
"drop-zone": {
|
||||
|
||||
@@ -148,6 +148,14 @@
|
||||
"all-mods-already-installed": {
|
||||
"title": "模组已安装",
|
||||
"description": "所有选中的模组已经安装"
|
||||
},
|
||||
"outdated-mods": {
|
||||
"title": "过时的模组",
|
||||
"title-plural": "过时的模组",
|
||||
"description": "{name} 模组已过时。您想更新吗?",
|
||||
"description-plural": "此版本有 {nb} 个过时的模组。您想更新它们吗?",
|
||||
"dont-remind-me": "不要提醒我",
|
||||
"update": "更新"
|
||||
}
|
||||
},
|
||||
"drop-zone": {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { BbmFullMod, BbmMod, BbmModVersion, BbmPlatform } from "../../../shared/
|
||||
import { RequestService } from "../request.service";
|
||||
import { BsStore } from "../../../shared/models/bs-store.enum";
|
||||
import log from "electron-log"
|
||||
import { tryit } from "shared/helpers/error.helpers";
|
||||
|
||||
export class BeatModsApiService {
|
||||
private static instance: BeatModsApiService;
|
||||
@@ -59,17 +60,42 @@ export class BeatModsApiService {
|
||||
});
|
||||
}
|
||||
|
||||
public getModByHash(hash: string): Promise<BbmModVersion|undefined> {
|
||||
if (this.modsHashCache.has(hash)) {
|
||||
return Promise.resolve(this.modsHashCache.get(hash));
|
||||
public async getModByHash<T extends string>(hashs: T[]): Promise<Record<T, BbmModVersion>> {
|
||||
|
||||
const getModsFromCache = (hashs: T[]): Record<T, BbmModVersion> => {
|
||||
const mods = {} as Record<T, BbmModVersion>;
|
||||
|
||||
for (const hash of hashs) {
|
||||
const mod = this.modsHashCache.get(hash);
|
||||
if(mod){
|
||||
mods[hash] = mod;
|
||||
}
|
||||
}
|
||||
|
||||
return mods;
|
||||
}
|
||||
|
||||
return this.requestService.getJSON<{ modVersions: BbmModVersion[] }>(`${this.MODS_REPO_API_URL}/hashlookup?hash=${hash}`).then(({ data }) => {
|
||||
this.updateModsHashCache(data?.modVersions ?? []);
|
||||
return data?.modVersions?.at(0);
|
||||
}).catch((e): undefined => {
|
||||
log.error(`Failed to get mod by hash: ${hash}`, e);
|
||||
const mods = getModsFromCache(hashs);
|
||||
const missingHashs = hashs.filter(hash => !mods[hash]);
|
||||
|
||||
if(!missingHashs.length){
|
||||
return mods;
|
||||
}
|
||||
|
||||
const url = new URL(`${this.MODS_REPO_API_URL}/hashlookup`);
|
||||
missingHashs.forEach(hash => url.searchParams.append("hash", hash));
|
||||
|
||||
const res = await tryit(() => this.requestService.getJSON<{ modVersions: BbmModVersion[] }>(url.toString()));
|
||||
|
||||
if(res.error){
|
||||
log.error(`Failed to get mod by hashes`, res.error);
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
this.updateModsHashCache(res.result?.data?.modVersions ?? []);
|
||||
|
||||
const missingMods = getModsFromCache(missingHashs);
|
||||
|
||||
return {...mods, ...missingMods};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import crypto from "crypto";
|
||||
import { BsmZipExtractor } from "main/models/bsm-zip-extractor.class";
|
||||
import { BsmShellLog, bsmSpawn } from "main/helpers/os.helpers";
|
||||
import { BbmFullMod, BbmModVersion, ExternalMod } from "../../../shared/models/mods/mod.interface";
|
||||
import { Stats } from "fs";
|
||||
|
||||
export class BsModsManagerService {
|
||||
private static instance: BsModsManagerService;
|
||||
@@ -28,8 +29,6 @@ export class BsModsManagerService {
|
||||
private readonly linuxService: LinuxService;
|
||||
private readonly requestService: RequestService;
|
||||
|
||||
private manifestMatches: BbmModVersion[];
|
||||
|
||||
public static getInstance(): BsModsManagerService {
|
||||
if (!BsModsManagerService.instance) {
|
||||
BsModsManagerService.instance = new BsModsManagerService();
|
||||
@@ -44,14 +43,9 @@ export class BsModsManagerService {
|
||||
this.requestService = RequestService.getInstance();
|
||||
}
|
||||
|
||||
private async getModFromHash(hash: string): Promise<BbmModVersion | undefined> {
|
||||
const mod = await this.beatModsApi.getModByHash(hash);
|
||||
|
||||
if(mod?.contentHashes?.some(content => content.path.includes("IPA.exe"))){
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return mod;
|
||||
private async getModsFromHashes(hashes: string[]): Promise<BbmModVersion[] | undefined> {
|
||||
const mods = await this.beatModsApi.getModByHash(hashes)
|
||||
return Object.values(mods).filter(mod => !mod.contentHashes.some(content => content.path.includes("IPA")));
|
||||
|
||||
}
|
||||
|
||||
@@ -63,40 +57,16 @@ export class BsModsManagerService {
|
||||
return [];
|
||||
}
|
||||
|
||||
const files = await recursiveReadDir(modsPath);
|
||||
const ignoreFunc = (file: string, stats: Stats): boolean => {
|
||||
if(!stats.isFile()) { return true; }
|
||||
const ext = path.extname(file);
|
||||
return !(ext === ".dll" || ext === ".exe" || ext === ".manifest");
|
||||
}
|
||||
|
||||
const promises = files.map(async filePath => {
|
||||
const ext = path.extname(filePath);
|
||||
const files = await recursiveReadDir(modsPath, [ignoreFunc]);
|
||||
const hashes = await Promise.all(files.map(file => md5File(file)));
|
||||
|
||||
if (ext !== ".dll" && ext !== ".exe" && ext !== ".manifest") {
|
||||
return undefined;
|
||||
}
|
||||
const hash = await md5File(filePath);
|
||||
const mod = await this.getModFromHash(hash);
|
||||
|
||||
if (!mod) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (ext === ".manifest") {
|
||||
this.manifestMatches.push(mod);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (filePath.toLowerCase().includes("libs")) {
|
||||
const manifestIndex = this.manifestMatches.findIndex(m => m.id === mod.id);
|
||||
|
||||
if (manifestIndex < 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
this.manifestMatches.splice(manifestIndex, 1);
|
||||
}
|
||||
|
||||
return mod;
|
||||
});
|
||||
|
||||
const mods = await Promise.all(promises);
|
||||
const mods = await this.getModsFromHashes(hashes);
|
||||
return mods.filter(Boolean);
|
||||
}
|
||||
|
||||
@@ -107,7 +77,7 @@ export class BsModsManagerService {
|
||||
return undefined;
|
||||
}
|
||||
const injectorMd5 = await md5File(injectorPath);
|
||||
return this.beatModsApi.getModByHash(injectorMd5);
|
||||
return (await this.beatModsApi.getModByHash([injectorMd5]))[injectorMd5];
|
||||
}
|
||||
|
||||
private async downloadZip(zipUrl: string): Promise<BsmZipExtractor> {
|
||||
@@ -338,12 +308,10 @@ export class BsModsManagerService {
|
||||
}
|
||||
|
||||
public async getInstalledMods(version: BSVersion): Promise<BbmModVersion[]> {
|
||||
this.manifestMatches = [];
|
||||
|
||||
const bsipa = await this.getBsipaInstalled(version);
|
||||
|
||||
const pluginsMods = await Promise.all([this.getModsInDir(version, ModsInstallFolder.PLUGINS), this.getModsInDir(version, ModsInstallFolder.PLUGINS_PENDING)]);
|
||||
const libsMods = await Promise.all([this.getModsInDir(version, ModsInstallFolder.LIBS), this.getModsInDir(version, ModsInstallFolder.LIBS_PENDING)]);
|
||||
const pluginsMods = await Promise.all([this.getModsInDir(version, ModsInstallFolder.PLUGINS_PENDING), this.getModsInDir(version, ModsInstallFolder.PLUGINS)]);
|
||||
const libsMods = await Promise.all([this.getModsInDir(version, ModsInstallFolder.LIBS_PENDING), this.getModsInDir(version, ModsInstallFolder.LIBS)]);
|
||||
|
||||
const dirMods = pluginsMods.flat().concat(libsMods.flat());
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ReactNode, useEffect, useLayoutEffect, useRef, useState } from "react";
|
||||
import { forwardRef, ReactNode, useEffect, useImperativeHandle, useLayoutEffect, useRef, useState } from "react";
|
||||
import { BsModsManagerService } from "renderer/services/bs-mods-manager.service";
|
||||
import { BSVersion } from "shared/bs-version.interface";
|
||||
import { BbmCategories, BbmFullMod, BbmModVersion } from "shared/models/mods/mod.interface";
|
||||
import { BbmCategories, BbmFullMod } from "shared/models/mods/mod.interface";
|
||||
import { ModsGrid } from "./mods-grid.component";
|
||||
import { ConfigurationService } from "renderer/services/configuration.service";
|
||||
import { BsmButton } from "renderer/components/shared/bsm-button.component";
|
||||
@@ -19,11 +19,17 @@ 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";
|
||||
import Tippy from "@tippyjs/react";
|
||||
import { ProgressBarService } from "renderer/services/progress-bar.service";
|
||||
import { Dropzone } from "renderer/components/shared/dropzone.component";
|
||||
|
||||
export function ModsSlide({ version, isActive, onDisclamerDecline }: { version: BSVersion; isActive?: boolean, onDisclamerDecline: () => void }) {
|
||||
export type ModsSlideRef = {
|
||||
loadMods: () => Promise<void>;
|
||||
}
|
||||
|
||||
type Props = { version: BSVersion; isActive?: boolean, onDisclamerDecline: () => void };
|
||||
|
||||
export const ModsSlide = forwardRef<ModsSlideRef, Props>(({ version, isActive, onDisclamerDecline }, forwaredRef) => {
|
||||
const ACCEPTED_DISCLAIMER_KEY = "accepted-mods-disclaimer";
|
||||
|
||||
const { text: t } = useTranslationV2();
|
||||
@@ -100,14 +106,7 @@ export function ModsSlide({ version, isActive, onDisclamerDecline }: { version:
|
||||
return Array.from(collectedDependencies);
|
||||
};
|
||||
|
||||
const installMods = (reinstallAll: boolean): void => {
|
||||
|
||||
setReinstallAllMods(false);
|
||||
|
||||
if (installing) {
|
||||
return;
|
||||
}
|
||||
|
||||
const getModsToInstall = (reinstallAll?: boolean): BbmFullMod[] => {
|
||||
let modsToInstall = [
|
||||
...modsSelected,
|
||||
...getAllDependencies(modsSelected, Array.from(modsAvailable.values()).flat())
|
||||
@@ -127,18 +126,30 @@ export function ModsSlide({ version, isActive, onDisclamerDecline }: { version:
|
||||
set.delete(null);
|
||||
set.delete(undefined);
|
||||
|
||||
modsToInstall = Array.from(set); // Remove duplicates
|
||||
return Array.from(set); // Remove duplicates
|
||||
}
|
||||
|
||||
const installMods = (reinstallAll: boolean): Promise<void> => {
|
||||
|
||||
setReinstallAllMods(false);
|
||||
|
||||
if (installing) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const modsToInstall = getModsToInstall(reinstallAll);
|
||||
|
||||
if (!modsToInstall.length) {
|
||||
notification.notifyInfo({ title: "pages.version-viewer.mods.notifications.all-mods-already-installed.title", desc: "pages.version-viewer.mods.notifications.all-mods-already-installed.description" });
|
||||
loadMods();
|
||||
return;
|
||||
return loadMods();
|
||||
}
|
||||
|
||||
setInstalling(() => true)
|
||||
lastValueFrom(modsManager.installMods(modsToInstall, version)).then(() => {
|
||||
loadMods();
|
||||
}).catch(noop).finally(() => setInstalling(() => false));
|
||||
return lastValueFrom(modsManager.installMods(modsToInstall, version)).then(() => (
|
||||
loadMods()
|
||||
)).catch(noop).finally(() => (
|
||||
setInstalling(() => false)
|
||||
));
|
||||
};
|
||||
|
||||
const importMods = (files: string[]): void => {
|
||||
@@ -179,26 +190,19 @@ export function ModsSlide({ version, isActive, onDisclamerDecline }: { version:
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const promise = async (): Promise<[BbmFullMod[], BbmModVersion[]]> => {
|
||||
const available = await lastValueFrom(modsManager.getAvailableMods(version));
|
||||
const installed = await lastValueFrom(modsManager.getInstalledMods(version));
|
||||
return [available, installed];
|
||||
}
|
||||
|
||||
return promise().then(([available, installed]) => {
|
||||
return modsManager.getVersionModsState(version).then(({ available, installed }) => {
|
||||
const defaultMods = installed?.length ? [] : available.filter(m => m.mod.category === BbmCategories.Core || m.mod.category === BbmCategories.Essential);
|
||||
setModsAvailable(() => modsToCategoryMap(available));
|
||||
|
||||
const installedMods: BbmFullMod[] = installed.map(version => {
|
||||
const mod = available.find(m => m.mod.id === version.modId);
|
||||
return mod ? { ...mod, version } : null;
|
||||
}).filter(mod => mod);
|
||||
|
||||
setModsSelected(available.filter(m => m.mod.category === BbmCategories.Core || defaultMods.some(d => m.mod.name.toLowerCase() === d.mod.name.toLowerCase()) || installedMods.some(i => m.mod.id === i.mod.id)));
|
||||
setModsInstalled(modsToCategoryMap(installedMods));
|
||||
setModsSelected(available.filter(m => m.mod.category === BbmCategories.Core || defaultMods.some(d => m.mod.name.toLowerCase() === d.mod.name.toLowerCase()) || installed.some(i => m.mod.id === i.mod.id)));
|
||||
setModsInstalled(modsToCategoryMap(installed));
|
||||
});
|
||||
};
|
||||
|
||||
useImperativeHandle(forwaredRef, () => ({
|
||||
loadMods
|
||||
}), [version]);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
if(!isActive || !isOnline){
|
||||
@@ -325,7 +329,7 @@ export function ModsSlide({ version, isActive, onDisclamerDecline }: { version:
|
||||
</Dropzone>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
function ModStatus({ text, image, spin = false, children }: { text: string; image: string; spin?: boolean, children?: ReactNode}) {
|
||||
const t = useTranslation();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { BSVersion } from "shared/bs-version.interface";
|
||||
import { useState } from "react";
|
||||
import { useRef, useState } from "react";
|
||||
import { Navigate, useLocation, useNavigate } from "react-router-dom";
|
||||
import { TabNavBar } from "renderer/components/shared/tab-nav-bar.component";
|
||||
import { BsmDropdownButton } from "renderer/components/shared/bsm-dropdown-button.component";
|
||||
@@ -10,7 +10,7 @@ import { ModalExitCode, ModalService } from "../services/modale.service";
|
||||
import DefautVersionImage from "../../../assets/images/default-version-img.jpg";
|
||||
import { IpcService } from "renderer/services/ipc.service";
|
||||
import { LaunchSlide } from "renderer/components/version-viewer/slides/launch/launch-slide.component";
|
||||
import { ModsSlide } from "renderer/components/version-viewer/slides/mods/mods-slide.component";
|
||||
import { ModsSlide, ModsSlideRef } from "renderer/components/version-viewer/slides/mods/mods-slide.component";
|
||||
import { UninstallModal } from "renderer/components/modal/modal-types/uninstall-modal.component";
|
||||
import { MapsPlaylistsPanel } from "renderer/components/maps-playlists-panel/maps-playlists-panel.component";
|
||||
import { ShareFoldersModal } from "renderer/components/modal/modal-types/share-folders-modal.component";
|
||||
@@ -25,12 +25,18 @@ import { useOnUpdate } from "renderer/hooks/use-on-update.hook";
|
||||
import { safeLt } from "shared/helpers/semver.helpers";
|
||||
import { ConfigurationService } from "renderer/services/configuration.service";
|
||||
import { logRenderError } from "renderer";
|
||||
import { BsModsManagerService } from "renderer/services/bs-mods-manager.service";
|
||||
import { noop } from "shared/helpers/function.helpers";
|
||||
import { useTranslationV2 } from "renderer/hooks/use-translation.hook";
|
||||
|
||||
export function VersionViewer() {
|
||||
|
||||
const { text: t } = useTranslationV2();
|
||||
|
||||
const bsUninstallerService = useService(BSUninstallerService);
|
||||
const bsVersionManagerService = useService(BSVersionManagerService);
|
||||
const modalService = useService(ModalService);
|
||||
const modsService = useService(BsModsManagerService);
|
||||
const bsDownloader = useService(BsDownloaderService);
|
||||
const ipcService = useService(IpcService);
|
||||
const bsLauncher = useService(BSLauncherService);
|
||||
@@ -40,10 +46,12 @@ export function VersionViewer() {
|
||||
const { state, pathname: url } = useLocation() as { state: BSVersion; pathname: string };
|
||||
const navigate = useNavigate();
|
||||
const [currentTabIndex, setCurrentTabIndex] = useState(0);
|
||||
const modsSlideRef = useRef<ModsSlideRef>(null);
|
||||
|
||||
useOnUpdate(() => {
|
||||
|
||||
checkIsVersionOutaded();
|
||||
checkOutdatedMods();
|
||||
|
||||
}, [state]);
|
||||
|
||||
@@ -70,6 +78,42 @@ export function VersionViewer() {
|
||||
}
|
||||
}
|
||||
|
||||
const checkOutdatedMods = async () => {
|
||||
|
||||
if(config.get("not-show-outdated-mods-notification")){
|
||||
return;
|
||||
}
|
||||
|
||||
const { available: availableMods, installed: installedMods } = await modsService.getVersionModsState(state);
|
||||
|
||||
const modsToUpdate = availableMods.filter(availableMod => {
|
||||
const installedMod = installedMods.find(installedMod => installedMod.mod.id === availableMod.mod.id);
|
||||
return installedMod && safeLt(installedMod.version.modVersion, availableMod.version.modVersion);
|
||||
});
|
||||
|
||||
if(!modsToUpdate.length){
|
||||
return;
|
||||
}
|
||||
|
||||
const choice = await notification.notifyInfo({
|
||||
title: modsToUpdate.length > 1 ? t("pages.version-viewer.mods.notifications.outdated-mods.title-plural") : t("pages.version-viewer.mods.notifications.outdated-mods.title"),
|
||||
desc: modsToUpdate.length > 1 ? t("pages.version-viewer.mods.notifications.outdated-mods.description-plural", { nb: `${modsToUpdate.length}` }) : t("pages.version-viewer.mods.notifications.outdated-mods.description", { name: `${modsToUpdate[0].mod.name}` }),
|
||||
duration: 9000,
|
||||
actions: [
|
||||
{ id: "0", title: "pages.version-viewer.mods.notifications.outdated-mods.dont-remind-me", cancel: true },
|
||||
{ id: "1", title: "pages.version-viewer.mods.notifications.outdated-mods.update" }
|
||||
]
|
||||
});
|
||||
|
||||
if(choice === "1"){
|
||||
lastValueFrom(modsService.installMods(modsToUpdate, state)).then(() => {
|
||||
modsSlideRef?.current?.loadMods?.();
|
||||
}).catch(noop);
|
||||
} else if(choice === "0"){
|
||||
config.set("not-show-outdated-mods-notification", true);
|
||||
}
|
||||
}
|
||||
|
||||
const navigateToVersion = (version?: BSVersion) => {
|
||||
if (!version) {
|
||||
return navigate("/available-versions");
|
||||
@@ -153,7 +197,7 @@ export function VersionViewer() {
|
||||
<div className="w-full shrink-0 px-3 pb-3 flex flex-col items-center">
|
||||
<ModelsPanel version={state} isActive={currentTabIndex === 2} goToMods={() => setCurrentTabIndex(() => 3)} />
|
||||
</div>
|
||||
<ModsSlide version={state} isActive={currentTabIndex === 3} onDisclamerDecline={handleModsDisclaimerDecline} />
|
||||
<ModsSlide ref={modsSlideRef} version={state} isActive={currentTabIndex === 3} onDisclamerDecline={handleModsDisclaimerDecline} />
|
||||
</div>
|
||||
</div>
|
||||
<BsmDropdownButton className="absolute top-3 right-4 h-9 w-9 bg-light-main-color-2 dark:bg-main-color-2 rounded-md" items={[
|
||||
|
||||
@@ -7,6 +7,7 @@ import { NotificationService } from "./notification.service";
|
||||
import { Progression } from "main/helpers/fs.helpers";
|
||||
import { ProgressionInterface } from "shared/models/progress-bar";
|
||||
import { BbmFullMod, BbmModVersion } from "shared/models/mods/mod.interface";
|
||||
import { logRenderError } from "renderer";
|
||||
|
||||
export class BsModsManagerService {
|
||||
private static instance: BsModsManagerService;
|
||||
@@ -180,4 +181,27 @@ export class BsModsManagerService {
|
||||
});
|
||||
}
|
||||
|
||||
public async getVersionModsState(version: BSVersion): Promise<{available: BbmFullMod[], installed: BbmFullMod[]}> {
|
||||
const [available, installed]: [BbmFullMod[], BbmModVersion[]] = await (async () => {
|
||||
const available = await lastValueFrom(this.getAvailableMods(version));
|
||||
const installed = await lastValueFrom(this.getInstalledMods(version));
|
||||
return [available ?? [], installed ?? []] as [BbmFullMod[], BbmModVersion[]]; // Make TS happy
|
||||
})().catch(e => {
|
||||
logRenderError(e);
|
||||
return [[], []] as [BbmFullMod[], BbmModVersion[]]; // Make TS happy
|
||||
})
|
||||
|
||||
const installedMods: BbmFullMod[] = installed.reduce((acc, installedMod) => {
|
||||
const mod = available.find(m => m.mod.id === installedMod.modId);
|
||||
|
||||
if(mod){
|
||||
acc.push({ ...mod, version: installedMod } as BbmFullMod);
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
return { available: (available ?? []), installed: (installedMods ?? []) };
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user