[qol] install only mods not already installed (not finished)

This commit is contained in:
MathieuG-P
2024-07-28 21:40:09 +02:00
parent c96ad6f7cd
commit 152313289c
5 changed files with 51 additions and 42 deletions
@@ -346,15 +346,12 @@ export class BsModsManagerService {
throw CustomError.fromError(new Error("No mods to install"), "no-mods");
}
const deps = await this.resolveDependencies(mods, version);
mods.push(...deps);
const bsipa = mods.find(mod => mod.name.toLowerCase() === "bsipa");
if (bsipa) {
mods = mods.filter(mod => mod.name.toLowerCase() !== "bsipa");
}
this.nbModsToInstall = mods.length + (bsipa && 1);
this.nbModsToInstall = mods.length + (bsipa ? 1 : 0);
this.nbInstalledMods = 0;
if (bsipa) {
@@ -11,6 +11,7 @@ export type BsmButtonType = "primary" | "secondary" | "success" | "cancel" | "er
type Props = {
className?: string;
style?: CSSProperties;
iconStyle?: CSSProperties;
imgClassName?: string;
iconClassName?: string;
icon?: BsmIconType;
@@ -29,7 +30,7 @@ type Props = {
textClassName?: string;
};
export const BsmButton = forwardRef<unknown, Props>(({ className, style, imgClassName, iconClassName, icon, image, text, type, active, withBar = true, disabled, onClickOutside, onClick, typeColor, color, title, iconColor, textClassName }, forwardedRef) => {
export const BsmButton = forwardRef<unknown, Props>(({ className, style, iconStyle, imgClassName, iconClassName, icon, image, text, type, active, withBar = true, disabled, onClickOutside, onClick, typeColor, color, title, iconColor, textClassName }, forwardedRef) => {
const t = useTranslation();
const { firstColor, secondColor } = useThemeColor();
const ref = useRef<HTMLDivElement>(null);
@@ -86,10 +87,10 @@ export const BsmButton = forwardRef<unknown, Props>(({ className, style, imgClas
return (
<div ref={setRef} onClick={handleClick} title={t(title)} className={`${className} overflow-hidden group ${!withBar && !disabled && (!!typeColor || !!color) && "hover:brightness-[1.15]"} ${disabled ? "brightness-75 cursor-not-allowed" : "cursor-pointer"} ${renderTypeColor}`} style={{ ...style, backgroundColor: primaryColor || color }}>
{image && <BsmImage image={image} className={imgClassName} />}
{icon && <BsmIcon icon={icon} className={iconClassName ?? "h-full w-full text-gray-800 dark:text-white"} style={{ color: iconColor || textColor }} />}
{icon && <BsmIcon icon={icon} className={iconClassName ?? "size-full text-gray-800 dark:text-white"} style={{ ...(iconStyle ?? {}), color: iconColor || textColor }} />}
{text &&
(type === "submit" ? (
<button type="submit" className={textClassName || "h-full w-full"} style={{ ...(!!textColor && { color: textColor }) }}>
<button type="submit" className={textClassName || "size-full"} style={{ ...(!!textColor && { color: textColor }) }}>
{t(text)}
</button>
) : (
@@ -99,8 +100,8 @@ export const BsmButton = forwardRef<unknown, Props>(({ className, style, imgClas
))}
{withBar && (
<div className="absolute bottom-0 left-0 w-full h-1 bg-current" style={{ color: secondColor }}>
<div className="absolute top-0 left-0 h-full w-full bg-current brightness-50" />
<div className={`absolute top-0 left-0 h-full w-full bg-inherit -translate-x-full group-hover:translate-x-0 transition-transform shadow-center shadow-current ${active && "translate-x-0"}`} />
<div className="absolute top-0 left-0 size-full bg-current brightness-50" />
<div className={`absolute top-0 left-0 size-full bg-inherit -translate-x-full group-hover:translate-x-0 transition-transform shadow-center shadow-current ${active && "translate-x-0"}`} />
</div>
)}
</div>
@@ -1,7 +1,7 @@
import { BsmButton } from "renderer/components/shared/bsm-button.component";
import { BsmCheckbox } from "renderer/components/shared/bsm-checkbox.component";
import { Mod } from "shared/models/mods/mod.interface";
import { CSSProperties, MouseEvent, useRef } from "react";
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";
@@ -9,6 +9,7 @@ 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 };
@@ -20,6 +21,8 @@ export function ModItem({ className, mod, installedVersion, isDependency, isSele
const uninstalling = useObservable(() => modsManager.isUninstalling$);
const clickRef = useRef();
const isChecked = useMemo(() => isDependency || isSelected || mod.required, [isDependency, isSelected, mod.required]);
useDoubleClick({
onSingleClick: e => handleWantInfo(e),
onDoubleClick: e => handleOnChange(e),
@@ -27,6 +30,10 @@ export function ModItem({ className, mod, installedVersion, isDependency, isSele
latency: 175,
});
useOnUpdate(() => {
onChange(isChecked);
}, [isChecked]);
const wantInfoStyle: CSSProperties = wantInfo ? { borderColor: themeColor } : { borderColor: "transparent" };
const isOutDated = installedVersion ? gt(mod.version, installedVersion) : false;
@@ -34,21 +41,19 @@ export function ModItem({ className, mod, installedVersion, isDependency, isSele
modsManager.uninstallMod(mod, pageState.getState());
};
const handleWantInfo = (e: MouseEvent<Element, MouseEvent>) => {
const handleWantInfo = (e: MouseEvent) => {
e.preventDefault();
onWantInfo(mod);
};
const handleOnChange = (e: MouseEvent<Element, MouseEvent>) => {
const handleOnChange = (e: MouseEvent) => {
e.preventDefault();
onChange(!isChecked);
};
const isChecked = isDependency || isSelected || mod.required;
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} disabled={mod.required || isDependency} checked={isChecked} />
<BsmCheckbox className="h-full aspect-square z-[1] relative bg-inherit" onChange={() => onChange(!isChecked)} disabled={mod.required || isDependency} 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}
@@ -24,7 +24,7 @@ export function ModsGrid({ modsMap, installed, modsSelected, onModChange, moreIn
if (!installed?.get(key)) {
return undefined;
}
const installedMod = installed.get(key).find(m => m.name === mod.name);
const installedMod = installed.get(key).find(m => m._id === mod._id);
if (!installedMod) {
return undefined;
}
@@ -36,16 +36,16 @@ export function ModsGrid({ modsMap, installed, modsSelected, onModChange, moreIn
const deps = m.dependencies.map(dep =>
Array.from(modsMap.values())
.flat()
.find(m => dep.name === m.name)
.find(m => dep._id === m._id)
);
if (deps.some(depMod => depMod.name === mod.name)) {
if (deps.some(depMod => depMod._id === mod._id)) {
return true;
}
return deps.some(depMod => depMod.dependencies.some(depModDep => depModDep.name === mod.name));
return deps.some(depMod => depMod.dependencies.some(depModDep => depModDep._id === mod._id));
});
};
const isSelected = (mod: Mod): boolean => modsSelected.some(m => m.name === mod.name);
const isSelected = (mod: Mod): boolean => modsSelected.some(m => m._id === mod._id);
const handleInput = (val: string) => setFilter(val.toLowerCase());
@@ -19,12 +19,14 @@ import { ModsDisclaimerModal } from "renderer/components/modal/modal-types/mods-
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";
export function ModsSlide({ version, onDisclamerDecline }: { version: BSVersion; onDisclamerDecline: () => void }) {
const ACCEPTED_DISCLAIMER_KEY = "accepted-mods-disclaimer";
const modsManager = useService(BsModsManagerService);
const configService = useService(ConfigurationService);
const notification = useService(NotificationService);
const linkOpener = useService(LinkOpenerService);
const modals = useService(ModalService);
const os = useService(OsDiagnosticService);
@@ -52,15 +54,17 @@ export function ModsSlide({ version, onDisclamerDecline }: { version: BSVersion;
};
const handleModChange = (selected: boolean, mod: Mod) => {
if (selected) {
return setModsSelected([...modsSelected, mod]);
return setModsSelected(mods => {
if (mods.some(m => m._id === mod._id)) {
return mods;
}
return [...mods, mod];
});
}
const mods = [...modsSelected];
mods.splice(
mods.findIndex(m => m.name === mod.name),
1
);
setModsSelected(mods);
setModsSelected(mods => mods.filter(m => m._id !== mod._id));
};
const handleMoreInfo = (mod: Mod) => {
@@ -77,26 +81,28 @@ export function ModsSlide({ version, onDisclamerDecline }: { version: BSVersion;
linkOpener.open(moreInfoMod.link);
};
const installMods = () => {
const installMods = (reinstallAll: boolean): void => {
setReinstallAllMods(() => false);
if (installing) {
return;
}
const modsToInstall = modsSelected.filter(mod => {
const corespondingMod = modsAvailable.get(mod.category).find(availabeMod => availabeMod._id === mod._id);
const installedMod = modsInstalled.get(mod.category)?.find(installedMod => installedMod.name === mod.name);
const installedMod = modsInstalled.get(mod.category)?.find(installedMod => installedMod._id === mod._id);
if (corespondingMod?.version && lt(corespondingMod.version, mod.version)) {
return false;
}
if(reinstallAll || !installedMod){ return true; }
if(installedMod?.version && lt(mod.version, installedMod?.version)){
return false;
}
return true;
return lt(installedMod.version, mod.version);
});
if (!modsToInstall.length) {
notification.notifyInfo({ title: "Mods déjà installées", desc: "Tous les mods séléctionnées sont déjà installées" });
loadMods();
return;
}
modsManager.installMods(modsToInstall, version).then(() => {
loadMods();
});
@@ -192,13 +198,13 @@ export function ModsSlide({ version, onDisclamerDecline }: { version: BSVersion;
<ModsGrid modsMap={modsAvailable} installed={modsInstalled} modsSelected={modsSelected} onModChange={handleModChange} moreInfoMod={moreInfoMod} onWantInfos={handleMoreInfo} />
</div>
<div className="shrink-0 flex items-center justify-between px-3 py-2">
<BsmButton className="text-center rounded-md px-2 py-[2px]" text="pages.version-viewer.mods.buttons.more-infos" typeColor="cancel" withBar={false} disabled={!moreInfoMod} onClick={handleOpenMoreInfo} style={{ width: downloadWith }} />
<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 }}/>
<div ref={downloadRef} className="flex h-8 justify-center items-center gap-px overflow-hidden rounded-md">
<div className="grow h-full w-36 relative">
<BsmButton className="absolute top-0 left-0 flex items-center justify-center px-1 size-full transition-transform duration-200 ease-in-out" text="Install or update" typeColor="primary" withBar={false} onClick={installMods} style={{ transform: reinstallAllMods ? "translateY(-100%)" : "" }} />
<BsmButton className="absolute top-full left-0 flex items-center justify-center px-1 size-full transition-transform duration-200 ease-in-out" text="Reinstall all" typeColor="primary" withBar={false} onClick={installMods} style={{ transform: reinstallAllMods ? "translateY(-100%)" : "" }}/>
<BsmButton className="absolute left-0 flex items-center justify-center px-1 size-full transition-[top] duration-200 ease-in-out" text="Install or update" typeColor="primary" withBar={false} onClick={() => installMods(false)} style={{ top: reinstallAllMods ? "-100%" : "0" }} />
<BsmButton className="absolute left-0 flex items-center justify-center px-1 size-full transition-[top] duration-200 ease-in-out " text="Reinstall all" typeColor="primary" withBar={false} onClick={() => installMods(true)} style={{ top: reinstallAllMods ? "0" : "100%" }}/>
</div>
<BsmButton className="flex items-center justify-center shrink-0 h-full" icon="chevron-top" typeColor="primary" withBar={false} onClick={() => setReinstallAllMods(prev => !prev)}/>
<BsmButton className="flex items-center justify-center shrink-0 h-full" iconClassName="transition-transform size-full ease-in-out duration-200" iconStyle={{ transform: reinstallAllMods ? "360deg" : "180deg" }} icon="chevron-top" typeColor="primary" withBar={false} onClick={() => setReinstallAllMods(prev => !prev)}/>
</div>
</div>
</>