mirror of
https://github.com/Zagrios/bs-manager.git
synced 2026-07-03 14:08:25 +02:00
add checkbox
This commit is contained in:
@@ -1,22 +0,0 @@
|
||||
.glow-on-hover {
|
||||
border-radius: 10px;
|
||||
background: linear-gradient(45deg, #ff0000, #ff7300, #fffb00, #48ff00, #00ffd5, #002bff, #7a00ff, #ff00c8, #ff0000);
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
left:-4px;
|
||||
background-size: 400%;
|
||||
z-index: -1;
|
||||
filter: blur(3px);
|
||||
width: calc(100% + 8px);
|
||||
height: calc(100% + 8px);
|
||||
animation: glowing 20s linear infinite;
|
||||
opacity: 0;
|
||||
transition: opacity .2s ease-in-out;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
@keyframes glowing {
|
||||
0% { background-position: 0 0; }
|
||||
50% { background-position: 400% 0; }
|
||||
100% { background-position: 0 0; }
|
||||
}
|
||||
@@ -40,7 +40,7 @@ export const AvailableVersionItem = memo(function AvailableVersionItem(props: {v
|
||||
|
||||
return (
|
||||
<div className="group m-3 relative w-72 h-60 transition-transform active:scale-[.98]" onClick={toggleSelect}>
|
||||
<div className={`absolute glow-on-hover group-hover:opacity-100 ${selected && "opacity-100"}`}></div>
|
||||
<div className={`absolute glow-on-hover group-hover:opacity-100 ${selected && "opacity-100"}`}/>
|
||||
<div className={`relative flex flex-col overflow-hidden rounded-md w-72 h-60 cursor-pointer group-hover:shadow-none duration-300 bg-light-main-color-2 dark:bg-main-color-2 ${!selected && "shadow-lg shadow-gray-900"}`}>
|
||||
<BsmImage image={props.version.ReleaseImg ? props.version.ReleaseImg : defaultImage} errorImage={defaultImage} placeholder={defaultImage} className="absolute top-0 right-0 w-full h-full opacity-40 blur-xl object-cover" loading="lazy"/>
|
||||
<BsmImage image={props.version.ReleaseImg ? props.version.ReleaseImg : defaultImage} errorImage={defaultImage} placeholder={defaultImage} className="bg-black w-full h-3/4 object-cover" loading="lazy"/>
|
||||
|
||||
@@ -4,6 +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";
|
||||
|
||||
type BsmButtonType = "primary"|"success"|"cancel"|"error";
|
||||
|
||||
@@ -32,16 +33,7 @@ export function BsmButton({className, style, imgClassName, iconClassName, icon,
|
||||
|
||||
const primaryColor = typeColor === "primary" ? useThemeColor("first-color") : null;
|
||||
|
||||
const textColor = !primaryColor ? null : (() => {
|
||||
const hex = primaryColor.replaceAll("#", "");
|
||||
const uicolors = [parseInt(hex.substring(0, 2), 16) / 255, parseInt(hex.substring(0, 2), 16) / 255, parseInt(hex.substring(0, 2), 16) / 255];
|
||||
const c = uicolors.map(col => {
|
||||
if (col <= 0.03928) { return col / 12.92; }
|
||||
return Math.pow((col + 0.055) / 1.055, 2.4);
|
||||
});
|
||||
const L = (0.2126 * c[0]) + (0.7152 * c[1]) + (0.0722 * c[2]);
|
||||
return (L > 0.179) ? "#000" : "fff";
|
||||
})();
|
||||
const textColor = !primaryColor ? null : useBestTextColor(primaryColor);
|
||||
|
||||
const renderTypeColor = (() => {
|
||||
if(typeColor === "primary"){ return ""; }
|
||||
@@ -57,7 +49,7 @@ export function BsmButton({className, style, imgClassName, iconClassName, icon,
|
||||
<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})}}>
|
||||
{ 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 className="w-full h-full" style={{...(!!textColor && {color: textColor})}}>{t(text)}</button> : <span style={{...(!!textColor && {color: textColor})}} >{t(text)}</span>)}
|
||||
{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>)}
|
||||
{ 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"/>
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
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";
|
||||
|
||||
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);
|
||||
const checkedColor = useThemeColor("first-color");
|
||||
const iconColor = useBestTextColor(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}}>
|
||||
<BsmIcon icon="check" style={{color: iconColor}}/>
|
||||
</motion.span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -20,9 +20,10 @@ import EditIcon from "./icons/edit-icon.component";
|
||||
import { ExportIcon } from "./icons/export-icon.component";
|
||||
import PatreonIcon from "./icons/patreon-icon.component";
|
||||
import { SearchIcon } from "./icons/search-icon.component";
|
||||
import { CheckIcon } from "./icons/check-icon.component";
|
||||
|
||||
export type BsmIconType = (
|
||||
"settings"|"trash"|"favorite"|"folder"|"bsNote"|
|
||||
"settings"|"trash"|"favorite"|"folder"|"bsNote"|"check"|
|
||||
"terminal"|"desktop"|"oculus"|"add"|"cross"|"task"|
|
||||
"copy"|"steam"|"edit"|"export"|"patreon"|"search"|
|
||||
"fr-FR-flag"|"es-ES-flag"|"en-US-flag"|"en-EN-flag"
|
||||
@@ -52,6 +53,7 @@ export const BsmIcon = memo(({className, icon, style}: {className?: string, icon
|
||||
if(icon === "export"){ return <ExportIcon className={className} style={style}/> }
|
||||
if(icon === "patreon"){ return <PatreonIcon className={className} style={style}/> }
|
||||
if(icon === "search"){ return <SearchIcon className={className} style={style}/> }
|
||||
if(icon === "check"){ return <CheckIcon className={className} style={style}/> }
|
||||
return <TrashIcon className={className} style={style}/>
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { CSSProperties } from "react";
|
||||
|
||||
export function CheckIcon(props: {className?: string, style?: CSSProperties}) {
|
||||
return (
|
||||
<svg className={props.className} style={props.style} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40" height="40" width="40">
|
||||
<path fill="currentColor" d="M15.792 29.458q-.292 0-.563-.104-.271-.104-.521-.354l-7.416-7.417q-.459-.458-.459-1.145 0-.688.459-1.146.458-.459 1.104-.459.646 0 1.146.459l6.25 6.291 14.666-14.666q.459-.459 1.125-.459.667 0 1.125.459.459.458.459 1.145 0 .688-.459 1.146L16.917 29q-.25.25-.521.354-.271.104-.604.104Z"/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -31,7 +31,7 @@ export default function TitleBar({template = "main"} : {template?: "update"|"mai
|
||||
}
|
||||
|
||||
return (
|
||||
<header id="titlebar" className="min-h-[22px] bg-light-main-color-1 dark:bg-main-color-1 w-screen h-[22px] flex content-center items-center justify-start z-10">
|
||||
<header id="titlebar" className="min-h-[22px] bg-light-main-color-1 shrink-0 dark:bg-main-color-1 w-screen h-[22px] flex content-center items-center justify-start z-10">
|
||||
<div id="drag-region" className='grow basis-0 h-full'>
|
||||
{template !== "update" && (<div id="window-title" className='pl-1'>
|
||||
<span className='text-gray-800 dark:text-gray-100 font-bold text-xs italic'>BSManager</span>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { BsmCheckbox } from "renderer/components/shared/bsm-checkbox.component"
|
||||
import { Mod } from "shared/models/mods/mod.interface"
|
||||
|
||||
type Props = {mod: Mod, installed: Mod}
|
||||
@@ -5,8 +6,8 @@ type Props = {mod: Mod, installed: Mod}
|
||||
export function ModItem({mod, installed}: Props) {
|
||||
return (
|
||||
<>
|
||||
<div className="h-full aspect-square flex items-center justify-center p-2 rounded-l-md bg-main-color-1 ml-3">
|
||||
<span className="w-full h-full border-white border-2 rounded-md"></span>
|
||||
<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>
|
||||
<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>
|
||||
|
||||
@@ -11,8 +11,6 @@ export function ModsGrid({modsMap, installed}: Props) {
|
||||
const [filter, setFilter] = useState("");
|
||||
const [filterEnabled, setFilterEnabled] = useState(false);
|
||||
|
||||
const inputRef = useRef();
|
||||
|
||||
const installedMod = (key: string, mod: Mod): Mod => {
|
||||
if(!installed || !installed.get(key)){ return; }
|
||||
return installed.get(key).find(m => m.name === mod.name);
|
||||
@@ -27,29 +25,29 @@ export function ModsGrid({modsMap, installed}: Props) {
|
||||
return modsMap && (
|
||||
<>
|
||||
<div className="grid gap-y-1 grid-cols-[40px_min-content_min-content_min-content_1fr]">
|
||||
<span className="sticky flex items-center justify-center top-0 bg-main-color-2 border-b-2 border-main-color-1">
|
||||
<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="sticky top-0 flex items-center bg-main-color-2 border-main-color-1 border-b-2 h-8 w-52 px-1">
|
||||
<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">
|
||||
{(filterEnabled ? (
|
||||
<motion.input autoFocus ref={inputRef} className="bg-main-color-1 rounded-md h-6 px-2" initial={{width: 0}} animate={{width: "100%"}} onChange={e => handleInput(e.target.value)}/>
|
||||
<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 className="sticky flex items-center justify-center text-sm top-0 bg-main-color-2 border-b-2 border-main-color-1 h-8 px-2">Installé</span>
|
||||
<span className="sticky flex items-center justify-center text-sm top-0 bg-main-color-2 border-b-2 border-main-color-1 h-8 px-2">Dernière</span>
|
||||
<span className="sticky flex items-center justify-center text-sm top-0 bg-main-color-2 border-b-2 border-main-color-1 h-8">Description</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">Description</span>
|
||||
|
||||
{
|
||||
Array.from(modsMap.keys()).map(key => (
|
||||
<>
|
||||
<span className="col-span-full py-1 font-bold pl-3" key={key}>{key}</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)}/>)}
|
||||
</>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -22,14 +22,12 @@ export function ModsSlide({version}: {version: BSVersion}) {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if(!isVisible){ return; }
|
||||
|
||||
if(!modsAvailable){
|
||||
console.log("OUI");
|
||||
modsManager.getAvailableMods(version).then(mods => setModsAvailable(modsToCategoryMap(mods)));
|
||||
modsManager.getInstalledMods(version).then(mods => setModsInstalled(modsToCategoryMap(mods)));
|
||||
}
|
||||
}, [isVisible]);
|
||||
if(!isVisible){ return; }
|
||||
modsManager.getAvailableMods(version).then(mods => setModsAvailable(modsToCategoryMap(mods)));
|
||||
modsManager.getInstalledMods(version).then(mods => setModsInstalled(modsToCategoryMap(mods)));
|
||||
|
||||
}, [isVisible, version]);
|
||||
|
||||
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
export function useBestTextColor(bgHex: string): string{
|
||||
|
||||
return (() => {
|
||||
const hex = bgHex.replaceAll("#", "");
|
||||
const uicolors = [parseInt(hex.substring(0, 2), 16) / 255, parseInt(hex.substring(0, 2), 16) / 255, parseInt(hex.substring(0, 2), 16) / 255];
|
||||
const c = uicolors.map(col => {
|
||||
if (col <= 0.03928) { return col / 12.92; }
|
||||
return (col + 0.055) / 1.055 ** 2.4;
|
||||
});
|
||||
const L = (0.2126 * c[0]) + (0.7152 * c[1]) + (0.0722 * c[2]);
|
||||
return (L > 0.255) ? "#000" : "fff";
|
||||
})();
|
||||
|
||||
}
|
||||
@@ -33,6 +33,29 @@
|
||||
animation: spin-loading 2s cubic-bezier(.46,-0.55,.49,1.52) 0s forwards infinite;
|
||||
}
|
||||
|
||||
.glow-on-hover {
|
||||
border-radius: 10px;
|
||||
background: linear-gradient(45deg, #ff0000, #ff7300, #fffb00, #48ff00, #00ffd5, #002bff, #7a00ff, #ff00c8, #ff0000);
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
left:-4px;
|
||||
background-size: 400%;
|
||||
z-index: -1;
|
||||
filter: blur(3px);
|
||||
width: calc(100% + 8px);
|
||||
height: calc(100% + 8px);
|
||||
animation: glowing 20s linear infinite;
|
||||
opacity: 0;
|
||||
transition: opacity .2s ease-in-out;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
@keyframes glowing {
|
||||
0% { background-position: 0 0; }
|
||||
50% { background-position: 400% 0; }
|
||||
100% { background-position: 0 0; }
|
||||
}
|
||||
|
||||
@keyframes spin-loading{
|
||||
0%{ transform: rotate(-360deg); }
|
||||
50%{ transform: rotate(-360deg); }
|
||||
|
||||
@@ -53,11 +53,10 @@ export class I18nService {
|
||||
public translate(translationKey: string, args?: Record<string, string>): string{
|
||||
let translated = this.cache.get(translationKey);
|
||||
if(!translated){
|
||||
console.log(translationKey);
|
||||
translated = getProperty(this.dictionary, translationKey);
|
||||
translated ? this.cache.set(translated, translationKey) : translated = translationKey;
|
||||
}
|
||||
args && Object.keys(args).forEach(key => translated = translated.replaceAll(`{${key}}`, args[key]));
|
||||
args && Object.keys(args).forEach(key => {translated = translated.replaceAll(`{${key}}`, args[key])});
|
||||
return translated;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user