mirror of
https://github.com/Zagrios/bs-manager.git
synced 2026-07-03 14:08:25 +02:00
fix dependency checkment, add mod more infos
This commit is contained in:
@@ -18,14 +18,12 @@ export function BsmProgressBar() {
|
||||
<motion.div initial={{y: "120%"}} animate={{y:"0%"}} exit={{y:"120%"}} className="w-full absolute h-14 flex justify-center items-center bottom-2">
|
||||
<div className={`flex items-center content-center justify-center bottom-9 z-10 rounded-lg bg-light-main-color-2 dark:bg-main-color-2 shadow-center shadow-black cursor-pointer transition-all duration-300 ${!progress && "h-14 w-14 !rounded-full"} ${!!progress && "h-5 w-3/4 !rounded-full p-[6px]"}`}>
|
||||
{ !!progress && (
|
||||
<>
|
||||
<div className="relative flex items-center h-full w-full rounded-full bg-black">
|
||||
<div className="w-0 h-full relative rounded-full download-progress flex items-center transition-all" style={{width: `${progress}%`, background: `linear-gradient(90deg, ${firstColor}, ${secondColor}, ${firstColor}, ${secondColor})`}}>
|
||||
<img className="h-[70px] w-[70px] min-w-fit absolute z-[1] -translate-y-1 -right-8 transition-all" src={BeatRunningImg} />
|
||||
</div>
|
||||
<span className="absolute w-full text-center text-white -top-[3px] left-0 text-[10px]">{`${Math.floor(progress)}%`}</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{ !progress && <img className="w-12 h-12 spin-loading" src={BeatWaitingImg}></img> }
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,7 @@ import React from "react";
|
||||
import { BsmImage } from "./bsm-image.component";
|
||||
import { useTranslation } from "renderer/hooks/use-translation.hook";
|
||||
import { useThemeColor } from "renderer/hooks/use-theme-color.hook";
|
||||
import { useBestTextColor } from "renderer/hooks/use-best-text-color.hook";
|
||||
import { getCorrectTextColor } from "renderer/helpers/correct-text-color";
|
||||
|
||||
type BsmButtonType = "primary"|"success"|"cancel"|"error";
|
||||
|
||||
@@ -33,7 +33,7 @@ export function BsmButton({className, style, imgClassName, iconClassName, icon,
|
||||
|
||||
const primaryColor = typeColor === "primary" ? useThemeColor("first-color") : null;
|
||||
|
||||
const textColor = !primaryColor ? null : useBestTextColor(primaryColor);
|
||||
const textColor = !primaryColor ? null : getCorrectTextColor(primaryColor);
|
||||
|
||||
const renderTypeColor = (() => {
|
||||
if(typeColor === "primary"){ return ""; }
|
||||
@@ -46,7 +46,7 @@ export function BsmButton({className, style, imgClassName, iconClassName, icon,
|
||||
|
||||
return (
|
||||
<OutsideClickHandler onOutsideClick={e => onClickOutside && onClickOutside(e)}>
|
||||
<div onClick={e => onClick && onClick(e)} className={`${className} overflow-hidden cursor-pointer group ${(!withBar && (!!typeColor || !!color)) && "hover:brightness-[1.15]"} ${disabled && "brightness-75 cursor-not-allowed"} ${renderTypeColor}`} style={{...style, ...((!!primaryColor || !!color) && {backgroundColor: primaryColor ?? color})}}>
|
||||
<div onClick={e => onClick && onClick(e)} className={`${className} overflow-hidden cursor-pointer group ${(!withBar && !disabled && (!!typeColor || !!color)) && "hover:brightness-[1.15]"} ${disabled && "brightness-75 cursor-not-allowed"} ${renderTypeColor}`} style={{...style, ...((!!primaryColor || !!color) && {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"}/> }
|
||||
{text && (type === "submit" ? <button type="submit" className="w-full h-full" style={{...(!!textColor && {color: textColor})}}>{t(text)}</button> : <span style={{...(!!textColor && {color: textColor})}} >{t(text)}</span>)}
|
||||
|
||||
@@ -1,29 +1,27 @@
|
||||
import { useState } from "react"
|
||||
import { BsmIcon } from "../svgs/bsm-icon.component"
|
||||
import { motion } from "framer-motion"
|
||||
import { useThemeColor } from "renderer/hooks/use-theme-color.hook";
|
||||
import { useBestTextColor } from "renderer/hooks/use-best-text-color.hook";
|
||||
import { getCorrectTextColor } from "renderer/helpers/correct-text-color"
|
||||
|
||||
type Props = {className?:string, checked?: boolean, onChange?: (val: boolean) => void, disabled?: boolean}
|
||||
|
||||
export function BsmCheckbox({className, checked, onChange, disabled} : Props) {
|
||||
|
||||
const [isChecked, setIsChecked] = useState(true);
|
||||
if(checked){ console.log(checked) }
|
||||
const checkedColor = useThemeColor("first-color");
|
||||
const iconColor = useBestTextColor(checkedColor);
|
||||
const iconColor = getCorrectTextColor(checkedColor);
|
||||
|
||||
const handleClick = () => {
|
||||
if(disabled){ return; }
|
||||
onChange && onChange(!checked);
|
||||
setIsChecked(val => !val);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`group ${className}`}>
|
||||
{!disabled && <div className="glow-on-hover !w-[calc(100%+6px)] !h-[calc(100%+6px)] !-top-[3px] !-left-[3px] group-hover:opacity-100"/>}
|
||||
|
||||
<div className={`w-full h-full bg-inherit border-2 rounded-md overflow-hidden flex items-center justify-center relative ${disabled ? "brightness-75 cursor-not-allowed" : "cursor-pointer"}`} onClick={handleClick}>
|
||||
<motion.span className="absolute w-full h-full flex items-center justify-center" style={{backgroundColor: checkedColor}} initial={isChecked ? {scale: 1} : {scale: 0}} animate={isChecked ? {scale: 1} : {scale: 0}} transition={{duration: .1}}>
|
||||
<div className={`w-full h-full bg-inherit border-2 rounded-md overflow-hidden flex items-center justify-center relative ${disabled ? "brightness-50 cursor-not-allowed" : "cursor-pointer"}`} onClickCapture={e => {e.stopPropagation(); handleClick()}}>
|
||||
<motion.span className="absolute w-full h-full flex items-center justify-center" style={{backgroundColor: checkedColor}} initial={checked ? {scale: 1} : {scale: 0}} animate={checked ? {scale: 1} : {scale: 0}} transition={{duration: .1}}>
|
||||
<BsmIcon icon="check" style={{color: iconColor}}/>
|
||||
</motion.span>
|
||||
</div>
|
||||
|
||||
@@ -1,18 +1,32 @@
|
||||
import { BsmCheckbox } from "renderer/components/shared/bsm-checkbox.component"
|
||||
import { Mod } from "shared/models/mods/mod.interface"
|
||||
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 } from "react"
|
||||
import { useThemeColor } from "renderer/hooks/use-theme-color.hook";
|
||||
|
||||
type Props = {mod: Mod, installed: Mod}
|
||||
type Props = {mod: Mod, installedVersion: string, isDependency?: boolean, isSelected?: boolean, onChange?: (val: boolean) => void, wantInfo?: boolean}
|
||||
|
||||
export function ModItem({mod, installedVersion, isDependency, isSelected, onChange, wantInfo}: Props) {
|
||||
|
||||
const themeColor = useThemeColor("second-color");
|
||||
|
||||
const wantInfoStyle: CSSProperties = wantInfo ? {borderColor: themeColor} : {borderColor: "transparent"};
|
||||
const isOutDated = (() => {
|
||||
return installedVersion < mod.version;
|
||||
})()
|
||||
|
||||
export function ModItem({mod, installed}: Props) {
|
||||
return (
|
||||
<>
|
||||
<div className="h-full aspect-square flex items-center justify-center p-[7px] rounded-l-md bg-main-color-1 ml-3">
|
||||
<BsmCheckbox className="h-full aspect-square z-[1] relative bg-main-color-1" disabled/>
|
||||
<div className="h-full aspect-square flex items-center justify-center p-[7px] rounded-l-md bg-main-color-1 ml-3 border-t-2 border-b-2 border-l-2" style={wantInfoStyle}>
|
||||
<BsmCheckbox className="h-full aspect-square z-[1] relative bg-main-color-1" onChange={onChange} disabled={mod.required || isDependency} checked={isDependency || isSelected || mod.required}/>
|
||||
</div>
|
||||
<span className="bg-main-color-1 py-2 pl-3 font-bold text-sm whitespace-nowrap border-t-2 border-b-2" style={wantInfoStyle}>{mod.name}</span>
|
||||
<span className={`min-w-0 text-center bg-main-color-1 py-2 px-1 text-sm border-t-2 border-b-2 ${(installedVersion && isOutDated) && "text-red-400 line-through"} ${installedVersion && !isOutDated && "text-green-400"}`} style={wantInfoStyle}>{installedVersion || "-"}</span>
|
||||
<span className="min-w-0 text-center bg-main-color-1 py-2 px-1 text-sm border-t-2 border-b-2" style={wantInfoStyle}>{mod.version}</span>
|
||||
<span title={mod.description} className="px-3 bg-main-color-1 whitespace-nowrap text-ellipsis overflow-hidden py-2 text-sm border-t-2 border-b-2" style={wantInfoStyle}>{mod.description}</span>
|
||||
<div className="h-full bg-main-color-1 flex items-center justify-center mr-3 rounded-r-md pr-2 border-t-2 border-b-2 border-r-2" style={wantInfoStyle}>
|
||||
{(installedVersion && !mod.required) && <BsmButton className="h-7 w-7 p-[5px] rounded-full" icon="trash" withBar={false}/>}
|
||||
</div>
|
||||
<span className="bg-main-color-1 py-2 pl-3 font-bold text-sm whitespace-nowrap">{mod.name}</span>
|
||||
<span className="min-w-0 text-center bg-main-color-1 py-2 px-1 text-sm">{installed?.version || "-"}</span>
|
||||
<span className="min-w-0 text-center bg-main-color-1 py-2 px-1 text-sm">{mod.version}</span>
|
||||
<span title={mod.description} className="px-3 bg-main-color-1 whitespace-nowrap text-ellipsis overflow-hidden py-2 text-sm rounded-r-md mr-3">{mod.description}</span>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,56 +1,74 @@
|
||||
import { motion } from "framer-motion";
|
||||
import { useRef, useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { BsmButton } from "renderer/components/shared/bsm-button.component";
|
||||
import { Mod } from "shared/models/mods/mod.interface"
|
||||
import { ModItem } from "./mod-item.component"
|
||||
|
||||
type Props = {modsMap: Map<string, Mod[]>, installed: Map<string, Mod[]>}
|
||||
type Props = {modsMap: Map<string, Mod[]>, installed: Map<string, Mod[]>, modsSelected: Mod[], onModChange: (selected: boolean, mod: Mod) => void, moreInfoMod?: Mod, onWantInfos: (mod: Mod) => void}
|
||||
|
||||
export function ModsGrid({modsMap, installed}: Props) {
|
||||
export function ModsGrid({modsMap, installed, modsSelected, onModChange, moreInfoMod, onWantInfos}: Props) {
|
||||
|
||||
const [filter, setFilter] = useState("");
|
||||
const [filterEnabled, setFilterEnabled] = useState(false);
|
||||
|
||||
const installedMod = (key: string, mod: Mod): Mod => {
|
||||
if(!installed || !installed.get(key)){ return; }
|
||||
return installed.get(key).find(m => m.name === mod.name);
|
||||
const installedModVersion = (key: string, mod: Mod): string => {
|
||||
if(!installed || !installed.get(key)){ return undefined; }
|
||||
const installedMod = installed.get(key).find(m => m.name === mod.name);
|
||||
if(!installedMod){ return undefined }
|
||||
return installedMod.version
|
||||
}
|
||||
|
||||
const isDependency = (mod: Mod): boolean => {
|
||||
return modsSelected.some(m => {
|
||||
const deps = m.dependencies.map(dep => Array.from(modsMap.values()).flat().find(m => dep.name === m.name));
|
||||
if(deps.some(depMod => depMod.name === mod.name)){ return true; }
|
||||
return deps.some(depMod => depMod.dependencies.some(depModDep => depModDep.name === mod.name));
|
||||
});
|
||||
}
|
||||
|
||||
const isSelected = (mod: Mod): boolean => modsSelected.some(m => m.name === mod.name);
|
||||
|
||||
const handleInput = (val: string) => setFilter(val.toLowerCase());
|
||||
|
||||
const handleToogleFilter = () => {
|
||||
setFilter("");
|
||||
setFilterEnabled(b => !b);
|
||||
}
|
||||
|
||||
return modsMap && (
|
||||
<>
|
||||
<div className="grid gap-y-1 grid-cols-[40px_min-content_min-content_min-content_1fr]">
|
||||
<div className="grid gap-y-1 grid-cols-[40px_min-content_min-content_min-content_1fr_min-content]">
|
||||
<span className="absolute z-10 top-0 w-full h-8 bg-main-color-2"/>
|
||||
<span className="z-10 sticky flex items-center justify-center top-0 bg-main-color-2 border-b-2 border-main-color-1">
|
||||
<div className="pl-4">
|
||||
<BsmButton className="rounded-full h-6 p-[2px]" withBar={false} icon="search" onClick={handleToogleFilter}/>
|
||||
</div>
|
||||
</span>
|
||||
<div className="z-10 sticky top-0 flex items-center bg-main-color-2 border-main-color-1 border-b-2 h-8 px-1">
|
||||
<span className="z-10 sticky top-0 flex items-center bg-main-color-2 border-main-color-1 border-b-2 h-8 px-1">
|
||||
{(filterEnabled ? (
|
||||
<motion.input autoFocus className="bg-main-color-1 rounded-md h-6 px-2" initial={{width: 0}} animate={{width: "250px"}} transition={{ease:"easeInOut", duration:.15}} onChange={e => handleInput(e.target.value)}/>
|
||||
):(
|
||||
<span className="w-full text-center">Nom</span>
|
||||
))}
|
||||
</div>
|
||||
</span>
|
||||
|
||||
<span className="z-10 sticky flex items-center justify-center text-base top-0 bg-main-color-2 border-b-2 border-main-color-1 h-8 px-2">Installé</span>
|
||||
<span className="z-10 sticky flex items-center justify-center text-base top-0 bg-main-color-2 border-b-2 border-main-color-1 h-8 px-2">Dernière</span>
|
||||
<span className="z-10 sticky flex items-center justify-center text-base top-0 bg-main-color-2 border-b-2 border-main-color-1 h-8 px-2">Récent</span>
|
||||
<span className="z-10 sticky flex items-center justify-center text-base top-0 bg-main-color-2 border-b-2 border-main-color-1 h-8">Description</span>
|
||||
<span className="z-10 sticky top-0 bg-main-color-2 border-b-2 border-main-color-1 h-8"></span>
|
||||
|
||||
{
|
||||
Array.from(modsMap.keys()).map(key => modsMap.get(key).some(mod => mod.name.toLowerCase().includes(filter)) && (
|
||||
<div key={key} className="contents">
|
||||
<span className="col-span-full py-1 font-bold pl-3">{key}</span>
|
||||
{modsMap.get(key).map(mod => mod.name.toLowerCase().includes(filter) && <ModItem key={mod.name} mod={mod} installed={installedMod(key, mod)}/>)}
|
||||
{modsMap.get(key).map(mod => mod.name.toLowerCase().includes(filter) && (
|
||||
<div className="contents cursor-pointer" onClick={() => onWantInfos(mod)} key={mod.name}>
|
||||
<ModItem mod={mod} installedVersion={installedModVersion(key, mod)} isDependency={isDependency(mod)} isSelected={isSelected(mod)} onChange={(val) => onModChange(val, mod)} wantInfo={mod.name === moreInfoMod?.name}/>
|
||||
</div>
|
||||
|
||||
))}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,16 +3,23 @@ import { BsModsManagerService } from "renderer/services/bs-mods-manager.service"
|
||||
import { BSVersion } from "shared/bs-version.interface";
|
||||
import VisibilitySensor from 'react-visibility-sensor';
|
||||
import { Mod } from "shared/models/mods/mod.interface";
|
||||
import { ModItem } from "./mod-item.component";
|
||||
import { ModsGrid } from "./mods-grid.component";
|
||||
import { ConfigurationService } from "renderer/services/configuration.service";
|
||||
import { DefaultConfigKey } from "renderer/config/default-configuration.config";
|
||||
import { BsmButton } from "renderer/components/shared/bsm-button.component";
|
||||
import { IpcService } from "renderer/services/ipc.service";
|
||||
|
||||
export function ModsSlide({version}: {version: BSVersion}) {
|
||||
|
||||
const modsManager = BsModsManagerService.getInstance();
|
||||
const configService = ConfigurationService.getInstance();
|
||||
const ipcServive = IpcService.getInstance();
|
||||
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const [modsAvailable, setModsAvailable] = useState(null as Map<string, Mod[]>);
|
||||
const [modsInstalled, setModsInstalled] = useState(null as Map<string, Mod[]>);
|
||||
const [modsSelected, setModsSelected] = useState([] as Mod[]);
|
||||
const [moreInfoMod, setMoreInfoMod] = useState(null as Mod);
|
||||
|
||||
const modsToCategoryMap = (mods: Mod[]): Map<string, Mod[]> => {
|
||||
if(!mods){ return new Map<string, Mod[]>(); }
|
||||
@@ -21,10 +28,37 @@ export function ModsSlide({version}: {version: BSVersion}) {
|
||||
return map;
|
||||
}
|
||||
|
||||
const handleModChange = (selected: boolean, mod: Mod) => {
|
||||
if(selected){ return setModsSelected([...modsSelected, mod]); }
|
||||
const mods = [...modsSelected];
|
||||
mods.splice(mods.findIndex(m => m.name === mod.name), 1);
|
||||
setModsSelected(mods);
|
||||
}
|
||||
|
||||
const handleMoreInfo = (mod: Mod) => {
|
||||
if(mod.name === moreInfoMod?.name){ return setMoreInfoMod(null); }
|
||||
setMoreInfoMod(mod);
|
||||
}
|
||||
|
||||
const handleOpenMoreInfo = () => {
|
||||
if(!moreInfoMod || !moreInfoMod.link){ return; }
|
||||
ipcServive.sendLazy("new-window", {args: moreInfoMod.link});
|
||||
}
|
||||
|
||||
const installMods = () => {
|
||||
// NEXT THING TO DO
|
||||
}
|
||||
|
||||
console.log(modsSelected);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
if(!isVisible){ return; }
|
||||
modsManager.getAvailableMods(version).then(mods => setModsAvailable(modsToCategoryMap(mods)));
|
||||
modsManager.getAvailableMods(version).then(mods => {
|
||||
setModsAvailable(modsToCategoryMap(mods));
|
||||
const defaultMods = configService.get<string[]>("default_mods" as DefaultConfigKey);
|
||||
setModsSelected(mods.filter(m => m.required || defaultMods.some(d => m.name === d)));
|
||||
});
|
||||
modsManager.getInstalledMods(version).then(mods => setModsInstalled(modsToCategoryMap(mods)));
|
||||
|
||||
}, [isVisible, version]);
|
||||
@@ -32,13 +66,14 @@ export function ModsSlide({version}: {version: BSVersion}) {
|
||||
|
||||
return (
|
||||
<VisibilitySensor onChange={setIsVisible}>
|
||||
<div className='shrink-0 w-full h-full px-8 pb-8 flex justify-center'>
|
||||
<div className='flex flex-col grow-0 bg-light-main-color-2 dark:bg-main-color-2 h-full w-full rounded-md shadow-black shadow-center overflow-hidden'>
|
||||
<div className='shrink-0 w-full h-full px-8 pb-7 flex justify-center'>
|
||||
<div className='relative flex flex-col grow-0 bg-light-main-color-2 dark:bg-main-color-2 h-full w-full rounded-md shadow-black shadow-center overflow-hidden'>
|
||||
<div className="overflow-scroll w-full shrink min-h-0 scrollbar-thin scrollbar-thumb-neutral-900 scrollbar-thumb-rounded-full">
|
||||
<ModsGrid modsMap={modsAvailable} installed={modsInstalled}/>
|
||||
<ModsGrid modsMap={modsAvailable} installed={modsInstalled} modsSelected={modsSelected} onModChange={handleModChange} moreInfoMod={moreInfoMod} onWantInfos={handleMoreInfo}/>
|
||||
</div>
|
||||
<div className=" h-10 shrink-0">
|
||||
|
||||
<div className="h-10 shrink-0 flex items-center justify-between px-3">
|
||||
<BsmButton className="rounded-md px-2 py-[2px]" text="Plus d'infos" typeColor="cancel" withBar={false} disabled={!moreInfoMod} onClick={handleOpenMoreInfo}/>
|
||||
<BsmButton className="rounded-md px-2 py-[2px]" text="Installer ou mettre à jour" withBar={false} typeColor="primary" onClick={installMods}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user