diff --git a/src/renderer/components/progress-bar/bsm-progress-bar.component.tsx b/src/renderer/components/progress-bar/bsm-progress-bar.component.tsx index 13318f0a..daa42d83 100644 --- a/src/renderer/components/progress-bar/bsm-progress-bar.component.tsx +++ b/src/renderer/components/progress-bar/bsm-progress-bar.component.tsx @@ -18,14 +18,12 @@ export function BsmProgressBar() {
{ !!progress && ( - <>
{`${Math.floor(progress)}%`}
- )} { !progress && }
diff --git a/src/renderer/components/shared/bsm-button.component.tsx b/src/renderer/components/shared/bsm-button.component.tsx index cdcc4090..2ba47a97 100644 --- a/src/renderer/components/shared/bsm-button.component.tsx +++ b/src/renderer/components/shared/bsm-button.component.tsx @@ -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 ( onClickOutside && onClickOutside(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})}}> +
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 && } { icon && } {text && (type === "submit" ? : {t(text)})} diff --git a/src/renderer/components/shared/bsm-checkbox.component.tsx b/src/renderer/components/shared/bsm-checkbox.component.tsx index 204c0979..4cc83d64 100644 --- a/src/renderer/components/shared/bsm-checkbox.component.tsx +++ b/src/renderer/components/shared/bsm-checkbox.component.tsx @@ -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 (
{!disabled &&
} -
- +
{e.stopPropagation(); handleClick()}}> +
diff --git a/src/renderer/components/version-viewer/slides/mods/mod-item.component.tsx b/src/renderer/components/version-viewer/slides/mods/mod-item.component.tsx index 5c891652..55e00964 100644 --- a/src/renderer/components/version-viewer/slides/mods/mod-item.component.tsx +++ b/src/renderer/components/version-viewer/slides/mods/mod-item.component.tsx @@ -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 ( <> -
- +
+ +
+ {mod.name} + {installedVersion || "-"} + {mod.version} + {mod.description} +
+ {(installedVersion && !mod.required) && }
- {mod.name} - {installed?.version || "-"} - {mod.version} - {mod.description} ) } diff --git a/src/renderer/components/version-viewer/slides/mods/mods-grid.component.tsx b/src/renderer/components/version-viewer/slides/mods/mods-grid.component.tsx index b5415382..4d3cc044 100644 --- a/src/renderer/components/version-viewer/slides/mods/mods-grid.component.tsx +++ b/src/renderer/components/version-viewer/slides/mods/mods-grid.component.tsx @@ -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, installed: Map} +type Props = {modsMap: Map, installed: Map, 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 && ( - <> -
+
+
-
+ {(filterEnabled ? ( handleInput(e.target.value)}/> ):( Nom ))} -
+
Installé - Dernière + Récent Description + { Array.from(modsMap.keys()).map(key => modsMap.get(key).some(mod => mod.name.toLowerCase().includes(filter)) && (
{key} - {modsMap.get(key).map(mod => mod.name.toLowerCase().includes(filter) && )} + {modsMap.get(key).map(mod => mod.name.toLowerCase().includes(filter) && ( +
onWantInfos(mod)} key={mod.name}> + onModChange(val, mod)} wantInfo={mod.name === moreInfoMod?.name}/> +
+ + ))}
)) }
- ) } diff --git a/src/renderer/components/version-viewer/slides/mods/mods-slide.component.tsx b/src/renderer/components/version-viewer/slides/mods/mods-slide.component.tsx index 658f1a4a..96fce442 100644 --- a/src/renderer/components/version-viewer/slides/mods/mods-slide.component.tsx +++ b/src/renderer/components/version-viewer/slides/mods/mods-slide.component.tsx @@ -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); const [modsInstalled, setModsInstalled] = useState(null as Map); + const [modsSelected, setModsSelected] = useState([] as Mod[]); + const [moreInfoMod, setMoreInfoMod] = useState(null as Mod); const modsToCategoryMap = (mods: Mod[]): Map => { if(!mods){ return new Map(); } @@ -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("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 ( -
-
+
+
- +
-
- +
+ +
diff --git a/src/renderer/config/default-configuration.config.ts b/src/renderer/config/default-configuration.config.ts index 8034f70c..5aac4dfb 100644 --- a/src/renderer/config/default-configuration.config.ts +++ b/src/renderer/config/default-configuration.config.ts @@ -4,8 +4,9 @@ export const defaultConfiguration: {[key in DefaultConfigKey]: any} = { "theme": "os", "language": window.navigator.language.length <= 2 ? `${window.navigator.language}-${window.navigator.language.toLocaleUpperCase()}` : window.navigator.language, "supported_languages": ["en-US", "en-EN", "fr-FR", "es-ES"], + "default_mods": ["SongCore", "ScoreSaber", "BeatSaverDownloader", "BeatSaverVoting", "PlaylistManager", "ModelDownloader"] } -export type DefaultConfigKey = "first-color" | "second-color" | "theme" | "language" | "supported_languages"; +export type DefaultConfigKey = "first-color" | "second-color" | "theme" | "language" | "supported_languages" | "default_mods"; export type ThemeConfig = "dark" | "light" | "os" \ No newline at end of file diff --git a/src/renderer/helpers/correct-text-color.ts b/src/renderer/helpers/correct-text-color.ts new file mode 100644 index 00000000..dac68b0b --- /dev/null +++ b/src/renderer/helpers/correct-text-color.ts @@ -0,0 +1,15 @@ +/** + * Return text color for best constrast with background color + * @param {string} bgHex + * @returns {string} + */ +export function getCorrectTextColor(bgHex: string): string{ + 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.128) ? "#000" : "fff"; +} \ No newline at end of file diff --git a/src/renderer/hooks/use-best-text-color.hook.ts b/src/renderer/hooks/use-best-text-color.hook.ts deleted file mode 100644 index 5039cba5..00000000 --- a/src/renderer/hooks/use-best-text-color.hook.ts +++ /dev/null @@ -1,14 +0,0 @@ -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"; - })(); - -} \ No newline at end of file