mirror of
https://github.com/Zagrios/bs-manager.git
synced 2026-07-03 14:08:25 +02:00
[qol] install only mods not already installed (not finished)
This commit is contained in:
@@ -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>
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user