mirror of
https://github.com/Zagrios/bs-manager.git
synced 2026-07-03 14:08:25 +02:00
support light theme
This commit is contained in:
+15
-2
@@ -6,16 +6,29 @@ import { VersionViewer } from "./pages/version-viewer.component";
|
||||
import { Modal } from "./components/modal/modal.component";
|
||||
import { SettingsPage } from "./pages/settings-page.component";
|
||||
import { BsmProgressBar } from "./components/progress-bar/bsm-progress-bar.component";
|
||||
import { useEffect } from "react";
|
||||
import { ThemeService } from "./services/theme.service";
|
||||
import { ThemeConfig } from "./config/default-configuration.config";
|
||||
|
||||
export default function App() {
|
||||
|
||||
const themeService = ThemeService.getInstance();
|
||||
|
||||
useEffect(() => {
|
||||
themeService.theme$.subscribe(() => {
|
||||
if(themeService.isDark || (themeService.isOS && window.matchMedia('(prefers-color-scheme: dark)').matches)){ document.documentElement.classList.add('dark'); }
|
||||
else { document.documentElement.classList.remove('dark'); }
|
||||
});
|
||||
}, [])
|
||||
|
||||
|
||||
return (
|
||||
<div className="relative w-screen h-screen overflow-hidden flex dark:bg-main-color-1 z-0 max-w-full">
|
||||
<div className="relative w-screen h-screen overflow-hidden flex bg-light-main-color-1 dark:bg-main-color-1 z-0 max-w-full">
|
||||
<Modal/>
|
||||
<NavBar/>
|
||||
<div className="relative flex flex-col grow max-w-full min-w-0">
|
||||
<TitleBar/>
|
||||
<div className="bg-main-color-2 relative rounded-tl-lg grow overflow-hidden max-w-full">
|
||||
<div className="bg-light-main-color-2 dark:bg-main-color-2 relative rounded-tl-lg grow overflow-hidden max-w-full">
|
||||
<Routes>
|
||||
<Route path={"/bs-version/:versionNumber"} element={<VersionViewer/>}/>
|
||||
<Route path={"/settings"} element={<SettingsPage/>}/>
|
||||
|
||||
@@ -37,7 +37,7 @@ export function AvailableVersionItem(props: {version: BSVersion}) {
|
||||
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={`relative flex flex-col overflow-hidden rounded-md w-72 h-60 bg-main-color-2 cursor-pointer group-hover:shadow-none duration-300 ${!selected && "shadow-lg shadow-gray-900"}`}>
|
||||
<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} 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} className="bg-black w-full h-3/4 object-cover" loading="lazy"/>
|
||||
<div className="relative z-[1] p-2 w-full flex items-center justify-between grow">
|
||||
|
||||
@@ -15,11 +15,11 @@ export function GuardModal({resolver}: {resolver: (x: ModalResponse) => void}) {
|
||||
|
||||
return (
|
||||
<form onSubmit={(e) => {e.preventDefault(); login();}}>
|
||||
<h1 className="text-3xl uppercase tracking-wide w-full text-center">Steam Guard</h1>
|
||||
<h1 className="text-3xl uppercase tracking-wide w-full text-center text-gray-800 dark:text-gray-200">Steam Guard</h1>
|
||||
<BsmImage className="mx-auto h-24" image={BeatConflict}/>
|
||||
<div className="mb-4">
|
||||
<label className="block font-bold tracking-wide" htmlFor="guard">Guard Code</label>
|
||||
<input className="w-full bg-main-color-1 px-2 rounded-md py-[2px]" onChange={e => setGuardCode(e.target.value.toUpperCase())} value={guardCode} type="guard" name="guard" id="guard" placeholder="Enter your Guard code"/>
|
||||
<label className="block font-bold tracking-wide text-gray-800 dark:text-gray-200" htmlFor="guard">Guard Code</label>
|
||||
<input className="w-full bg-light-main-color-1 dark:bg-main-color-1 px-2 rounded-md py-[2px]" onChange={e => setGuardCode(e.target.value.toUpperCase())} value={guardCode} type="guard" name="guard" id="guard" placeholder="Enter your Guard code"/>
|
||||
</div>
|
||||
<div className="grid grid-flow-col grid-cols-2 gap-4">
|
||||
<BsmButton className="rounded-md text-center bg-gray-500 hover:brightness-110 transition-all" onClick={() => {resolver({exitCode: ModalExitCode.CANCELED})}} withBar={false} text="Cancel"/>
|
||||
|
||||
@@ -10,9 +10,9 @@ export function InstallationFolderModal({resolver}: {resolver: (x: ModalResponse
|
||||
return (
|
||||
<>
|
||||
<span className="absolute bg-gradient-to-r from-blue-500 to-red-500 top-0 w-full left-0 h-1"></span>
|
||||
<h1 className="text-3xl uppercase tracking-wide w-full text-center">installation folder</h1>
|
||||
<h1 className="text-3xl uppercase tracking-wide w-full text-center text-gray-800 dark:text-gray-200">installation folder</h1>
|
||||
<BsmImage className="mx-auto h-24" image={BeatConflict}/>
|
||||
<p className="max-w-sm">Changing the default installation folder for Beat Saber instances will cause all installed data to be moved to the new folder.</p>
|
||||
<p className="max-w-sm text-gray-800 dark:text-gray-200">Changing the default installation folder for Beat Saber instances will cause all installed data to be moved to the new folder.</p>
|
||||
<div className="grid grid-flow-col grid-cols-2 gap-4 mt-4">
|
||||
<BsmButton className="rounded-md text-center bg-gray-500 hover:brightness-110 transition-all" onClick={() => resolver({exitCode: ModalExitCode.CANCELED})} withBar={false} text="Cancel"/>
|
||||
<BsmButton className="rounded-md text-center bg-blue-500 hover:brightness-110 transition-all" onClick={() => resolver({exitCode: ModalExitCode.COMPLETED})} withBar={false} text="Choose Folder"/>
|
||||
|
||||
@@ -17,19 +17,19 @@ export function LoginModal({resolver}: {resolver: (x: ModalResponse) => void}) {
|
||||
|
||||
return (
|
||||
<form onSubmit={(e) => {e.preventDefault(); loggin();}}>
|
||||
<h1 className="text-3xl uppercase tracking-wide w-full text-center">Steam Login</h1>
|
||||
<h1 className="text-3xl uppercase tracking-wide w-full text-center text-gray-800 dark:text-gray-200">Steam Login</h1>
|
||||
<BsmImage className="mx-auto h-20" image={BeatImpatient} />
|
||||
<div className="mb-2">
|
||||
<label className="block font-bold cursor-pointer tracking-wide" htmlFor="username">Username</label>
|
||||
<input className="w-full bg-main-color-1 px-1 py-[2px] rounded-md outline-none" onChange={e => setUsername(e.target.value)} value={username} type="text" name="username" id="username" placeholder="Enter your username"/>
|
||||
<label className="block font-bold cursor-pointer tracking-wide text-gray-800 dark:text-gray-200" htmlFor="username">Username</label>
|
||||
<input className="w-full bg-light-main-color-1 dark:bg-main-color-1 px-1 py-[2px] rounded-md outline-none" onChange={e => setUsername(e.target.value)} value={username} type="text" name="username" id="username" placeholder="Enter your username"/>
|
||||
</div>
|
||||
<div className="mb-2">
|
||||
<label className="block font-bold cursor-pointer tracking-wide" htmlFor="password">Password</label>
|
||||
<input className="w-full bg-main-color-1 px-1 py-[2px] rounded-md outline-none" onChange={e => setPassword(e.target.value)} value={password} type="password" name="password" id="password" placeholder="Enter your password"/>
|
||||
<label className="block font-bold cursor-pointer tracking-wide text-gray-800 dark:text-gray-200" htmlFor="password">Password</label>
|
||||
<input className="w-full bg-light-main-color-1 dark:bg-main-color-1 px-1 py-[2px] rounded-md outline-none" onChange={e => setPassword(e.target.value)} value={password} type="password" name="password" id="password" placeholder="Enter your password"/>
|
||||
</div>
|
||||
<div className="flex items-center content-center justify-start mb-3">
|
||||
<input onChange={e => setStay(e.target.checked)} checked={stay} className="mr-1" type="checkbox" name="stay" id="stay" />
|
||||
<label className="cursor-pointer" htmlFor="stay">Stay connected</label>
|
||||
<label className="cursor-pointer text-gray-800 dark:text-gray-200" htmlFor="stay">Stay connected</label>
|
||||
</div>
|
||||
<div className="grid grid-flow-col grid-cols-2 gap-4">
|
||||
<BsmButton className="rounded-md text-center bg-gray-500 hover:brightness-110 transition-all" onClick={() => {resolver({exitCode: ModalExitCode.CANCELED})}} withBar={false} text="Cancel"/>
|
||||
|
||||
@@ -10,9 +10,9 @@ export function UninstallModal({resolver}: {resolver: (x: ModalResponse) => void
|
||||
|
||||
return (
|
||||
<form onSubmit={(e) => {e.preventDefault(); resolver({exitCode: ModalExitCode.COMPLETED})}}>
|
||||
<h1 className="text-3xl uppercase tracking-wide w-full text-center">Uninstall</h1>
|
||||
<h1 className="text-3xl uppercase tracking-wide w-full text-center text-gray-800 dark:text-gray-200">Uninstall</h1>
|
||||
<BsmImage className="mx-auto h-24" image={BeatConflict}/>
|
||||
<p className="max-w-sm">Are you sure you want to uninstall BeatSaber {version.BSVersion} ? You will have to re-download it to play it.</p>
|
||||
<p className="max-w-sm text-gray-800 dark:text-gray-200">Are you sure you want to uninstall BeatSaber {version.BSVersion} ? You will have to re-download it to play it.</p>
|
||||
<div className="grid grid-flow-col grid-cols-2 gap-4 mt-4">
|
||||
<BsmButton className="rounded-md text-center bg-gray-500 hover:brightness-110 transition-all" onClick={() => {resolver({exitCode: ModalExitCode.CANCELED})}} withBar={false} text="Cancel"/>
|
||||
<BsmButton className="rounded-md text-center bg-blue-500 hover:brightness-110 transition-all" type="submit" withBar={false} text="Uninstall"/>
|
||||
|
||||
@@ -35,7 +35,7 @@ export function Modal() {
|
||||
<div className="top-0 absolute w-screen h-screen flex content-center items-center justify-center z-50">
|
||||
<motion.span key="modal-overlay" onClick={() => modalSevice.resolve({exitCode: ModalExitCode.NO_CHOICE})} className="absolute top-0 bottom-0 right-0 left-0 bg-black" initial={{opacity: 0}} animate={{opacity: modalType && .60}} exit={{opacity: 0}} transition={{duration: .2}}/>
|
||||
<motion.div key="modal" initial={{y: modalYOut}} animate={{y: 0}} exit={{y: modalYOut}}>
|
||||
<div className="relative p-4 text-gray-200 overflow-hidden rounded-md shadow-lg shadow-black bg-gradient-to-br from-main-color-3 to-main-color-2">
|
||||
<div className="relative p-4 text-gray-200 overflow-hidden rounded-md shadow-lg shadow-black bg-gradient-to-br from-light-main-color-3 to-light-main-color-2 dark:from-main-color-3 dark:to-main-color-2">
|
||||
<span className="absolute bg-gradient-to-r from-blue-500 to-red-500 top-0 w-full left-0 h-1"></span>
|
||||
{modalType === ModalType.STEAM_LOGIN && <LoginModal resolver={modalSevice.getResolver()}/>}
|
||||
{modalType === ModalType.GUARD_CODE && <GuardModal resolver={modalSevice.getResolver()}/>}
|
||||
|
||||
@@ -30,16 +30,16 @@ export default function BsVersionItem(props: {version: BSVersion}) {
|
||||
setDownloadPercent(0);
|
||||
}
|
||||
})
|
||||
}, [])
|
||||
}, []);
|
||||
|
||||
|
||||
return (
|
||||
<div className={`outline-none relative p-[1px] overflow-hidden rounded-full flex justify-center content-center items-center mb-1 ${downloading && "nav-item-download"}`}>
|
||||
<div className="absolute top-0 w-full h-full" style={{transform: `translate(${-(100 - downloadPercent)}%, 0)`}}></div>
|
||||
<Link to={`/bs-version/${props.version.BSVersion}`} state={props.version} className={`z-[1] flex cursor-pointer w-full justify-center content-center rounded-full items-center p-[3px] pl-2 pr-2 hover:bg-main-color-3 ${downloading && 'bg-black'} ${(isActive() && !downloading) && "bg-main-color-3"}`}>
|
||||
<Link to={`/bs-version/${props.version.BSVersion}`} state={props.version} className={`z-[1] flex cursor-pointer w-full justify-center content-center rounded-full items-center p-[3px] pl-2 pr-2 hover:bg-light-main-color-3 dark:hover:bg-main-color-3 ${downloading && 'bg-black'} ${(isActive() && !downloading) && "bg-light-main-color-3 dark:bg-main-color-3"}`}>
|
||||
{props.version.steam && <SteamIcon className="w-[19px] h-[19px] mr-1"/>}
|
||||
{!props.version.steam && <BsNoteFill className="w-[19px] h-[19px] mr-1 text-red-600"/>}
|
||||
<span className="flex items-center justify-center content-center shrink-0 grow text-lg text-gray-200 font-bold min-w-0 tracking-wide">{props.version.BSVersion}</span>
|
||||
<span className="flex items-center justify-center content-center shrink-0 grow text-lg dark:text-gray-200 text-gray-800 font-bold min-w-0 tracking-wide">{props.version.BSVersion}</span>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ export function NavBar() {
|
||||
const secondColor = useObservable(configService.watch<string>("second-color"));
|
||||
|
||||
return (
|
||||
<div id='nav-bar' className='z-10 flex flex-col h-full max-h-full items-center p-1 bg-gray-200 dark:bg-main-color-1'>
|
||||
<div id='nav-bar' className='z-10 flex flex-col h-full max-h-full items-center p-1 bg-light-main-color-1 dark:bg-main-color-1'>
|
||||
<div className='w-full flex items-start content-start justify-center relative mb-3'>
|
||||
<div className='relative aspect-square w-16'>
|
||||
<span id='logo-bottom' className='aspect-square w-16' style={{backgroundColor: firstColor}}> </span>
|
||||
|
||||
@@ -1,39 +1,27 @@
|
||||
import { useEffect, useState } from "react"
|
||||
import { ProgressBarService } from "renderer/services/progress-bar.service";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import BeatRunningImg from "../../../../assets/beat-running.png"
|
||||
import BeatWaitingImg from "../../../../assets/beat-waiting.png"
|
||||
import { useObservable } from "renderer/hooks/use-observable.hook";
|
||||
|
||||
export function BsmProgressBar() {
|
||||
|
||||
const progressBarService = ProgressBarService.getInstance();
|
||||
|
||||
const [progress, setProgress] = useState(0);
|
||||
const [visible, setVisible] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
const progressSub = progressBarService.progression$.subscribe(val => setProgress(val));
|
||||
const visibleSub = progressBarService.visible$.subscribe(state => setVisible(state));
|
||||
|
||||
return () => {
|
||||
progressSub.unsubscribe();
|
||||
visibleSub.unsubscribe();
|
||||
}
|
||||
}, [])
|
||||
|
||||
const progress= useObservable(progressBarService.progression$);
|
||||
const visible = useObservable(progressBarService.visible$);
|
||||
|
||||
return (
|
||||
<AnimatePresence> { visible &&
|
||||
<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-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]"}`}>
|
||||
<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}%`}}>
|
||||
<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]">{`${progress}%`}</span>
|
||||
<span className="absolute w-full text-center text-white -top-[3px] left-0 text-[10px]">{`${Math.floor(progress)}%`}</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -14,7 +14,7 @@ export default function SettingColorChooser({color, onChange}: {color?: string,
|
||||
<AnimatePresence>
|
||||
{colorVisible &&
|
||||
<motion.div initial={{opacity: 0}} animate={{opacity: 1}} transition={{duration: .1}} exit={{opacity: 0}} className="absolute flex items-center justify-center translate-y-9 shadow-lg rounded-lg shadow-black">
|
||||
<div className="absolute w-2/4 aspect-square rotate-45 bg-main-color-3 -translate-y-12"></div>
|
||||
<div className="absolute w-2/4 aspect-square rotate-45 bg-light-main-color-3 dark:bg-main-color-3 -translate-y-12"></div>
|
||||
<HexColorPicker color={color} onChange={onChange} className="" />
|
||||
</motion.div>
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@ export function SettingContainer({className, title, minorTitle, description, chi
|
||||
return (
|
||||
<div className={className || "relative mb-5"}>
|
||||
{ title && <h1 className="font-bold text-2xl mb-1 tracking-wide">{title}</h1> }
|
||||
{ minorTitle && <h2 className="font-bold mb-1 tracking-wide text-gray-400">{minorTitle}</h2> }
|
||||
{ description && <p className="mb-3 text-sm text-gray-400">{description}</p> }
|
||||
{ minorTitle && <h2 className="font-bold mb-1 tracking-wide text-gray-600 dark:text-gray-400">{minorTitle}</h2> }
|
||||
{ description && <p className="mb-3 text-sm text-gray-600 dark:text-gray-400">{description}</p> }
|
||||
{ children }
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -6,10 +6,10 @@ export function SettingRadioArray({items, selectedItem = items[0].id, onItemSele
|
||||
return (
|
||||
<div className="w-full">
|
||||
{ items.map(i => (
|
||||
<div onClick={() => onItemSelected(i.id)} key={i.id} className={`py-3 my-[6px] w-full flex cursor-pointer justify-between items-center rounded-md px-2 transition-colors duration-200 ${i.id === selectedItem ? "bg-main-color-3" : "bg-main-color-1"}`}>
|
||||
<div onClick={() => onItemSelected(i.id)} key={i.id} className={`py-3 my-[6px] w-full flex cursor-pointer justify-between items-center rounded-md px-2 transition-colors duration-200 ${i.id === selectedItem ? "bg-light-main-color-3 dark:bg-main-color-3" : "bg-light-main-color-1 dark:bg-main-color-1"}`}>
|
||||
<div className="flex items-center">
|
||||
<div className="h-5 rounded-full aspect-square border-2 border-white p-[3px] mr-2">
|
||||
<motion.span initial={{scale: 0}} animate={{scale: i.id === selectedItem ? 1 : 0}} className="h-full w-full block bg-white rounded-full"/>
|
||||
<div className="h-5 rounded-full aspect-square border-2 border-gray-800 dark:border-white p-[3px] mr-2">
|
||||
<motion.span initial={{scale: 0}} animate={{scale: i.id === selectedItem ? 1 : 0}} className="h-full w-full block bg-gray-800 dark:bg-white rounded-full"/>
|
||||
</div>
|
||||
<h2 className="font-extrabold">{i.text}</h2>
|
||||
</div>
|
||||
@@ -27,4 +27,4 @@ export function SettingRadioArray({items, selectedItem = items[0].id, onItemSele
|
||||
)
|
||||
}
|
||||
|
||||
export interface RadioItem { id: number, text: string, icon?: BsmIconType, textIcon?: string }
|
||||
export interface RadioItem { id: number, text: string, icon?: BsmIconType, textIcon?: string, value?: any }
|
||||
|
||||
@@ -6,9 +6,9 @@ import { BsmImage } from "./bsm-image.component";
|
||||
export function BsmButton({className, style, imgClassName, icon, image, text, type, active, withBar = true, onClickOutside, onClick}: {className?: string, style?: React.CSSProperties, imgClassName?: string, icon?: BsmIconType, image?: string, text?: string, type?: string, active?: boolean, withBar?: boolean, onClickOutside?: (e: MouseEvent) => void, onClick?: (e: React.MouseEvent) => void}) {
|
||||
return (
|
||||
<OutsideClickHandler onOutsideClick={e => onClickOutside && onClickOutside(e)}>
|
||||
<div onClick={e => onClick && onClick(e)} className={`${className} overflow-hidden cursor-pointer bg-main-color-2 text-white group`} style={style}>
|
||||
<div onClick={e => onClick && onClick(e)} className={`${className} overflow-hidden cursor-pointer group`} style={style}>
|
||||
{ image && <BsmImage image={image} className={imgClassName}/> }
|
||||
{ icon && <BsmIcon icon={icon} className="h-full w-full"/> }
|
||||
{ icon && <BsmIcon icon={icon} className="h-full w-full text-gray-800 dark:text-white"/> }
|
||||
{text && (type === "submit" ? <button className="w-full h-full">{text}</button> : <span>{text}</span>)}
|
||||
{ withBar && (
|
||||
<div className="absolute bottom-0 left-0 w-full h-1 bg-red-500">
|
||||
|
||||
@@ -11,12 +11,12 @@ export function BsmDropdownButton({className, items, align, onItemClick}: {class
|
||||
|
||||
return (
|
||||
<div className={`${className}`}>
|
||||
<BsmButton onClick={() => setExpanded(!expanded)} className='relative z-[1] p-1 rounded-md text-inherit w-full h-full shadow-md shadow-black' icon="settings" active={expanded} onClickOutside={() => {setExpanded(false)}}/>
|
||||
<div className={`pt-1 pb-1 w-fit absolute cursor-pointer top-[calc(100%-4px)] rounded-md overflow-hidden bg-main-color-2 text-sm text-gray-200 shadow-md shadow-black transition-transform ease-in-out ${align === "left" ? "left-0 origin-top-left" : "right-0 origin-top-right"}`} style={{transform: expanded ? "scale(1)" : "scale(0)"}}>
|
||||
<BsmButton onClick={() => setExpanded(!expanded)} className='relative bg-light-main-color-2 dark:bg-main-color-2 z-[1] p-1 rounded-md text-inherit w-full h-full shadow-md shadow-black' icon="settings" active={expanded} onClickOutside={() => {setExpanded(false)}}/>
|
||||
<div className={`pt-1 pb-1 w-fit absolute cursor-pointer top-[calc(100%-4px)] rounded-md overflow-hidden bg-light-main-color-2 dark:bg-main-color-2 text-sm text-gray-800 dark:text-gray-200 shadow-md shadow-black transition-transform ease-in-out ${align === "left" ? "left-0 origin-top-left" : "right-0 origin-top-right"}`} style={{transform: expanded ? "scale(1)" : "scale(0)"}}>
|
||||
{ items?.map(i =>
|
||||
<div key={i.id} onClick={() => onItemClick(i.id)} className="flex justify-start items-center w-full pr-3 pl-3 pt-2 pb-2 hover:bg-main-color-3">
|
||||
{i.icon && <BsmIcon icon={i.icon} className="h-5 w-5 mr-1"></BsmIcon>}
|
||||
<span className="w-max hover:bg-main-color-3">{i.text}</span>
|
||||
<div key={i.id} onClick={() => onItemClick(i.id)} className="flex justify-start items-center w-full pr-3 pl-3 pt-2 pb-2 hover:bg-light-main-color-3 dark:hover:bg-main-color-3">
|
||||
{i.icon && <BsmIcon icon={i.icon} className="h-5 w-5 mr-1 text-gray-800 dark:text-white"></BsmIcon>}
|
||||
<span className="w-max">{i.text}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -26,7 +26,7 @@ export function TabNavBar(props: {tabsText: string[], onTabChange: Function, cla
|
||||
</div>
|
||||
<div ref={tabsWrapper} className="grid" style={{gridTemplateColumns: `repeat(${props.tabsText.length}, minmax(0, 1fr))`}}>
|
||||
{ props.tabsText.map((y, index) =>
|
||||
<span className="pr-4 pl-4 h-full inline-block bg-main-color-2 text-gray-200 text-lg font-bold text-center hover:bg-main-color-1" key={index} onClick={() => selectYear(y)}>{y}</span>
|
||||
<span className="pr-4 pl-4 h-full inline-block bg-light-main-color-2 text-gray-800 dark:bg-main-color-2 dark:text-gray-200 text-lg font-bold text-center hover:bg-light-main-color-1 dark:hover:bg-main-color-1" key={index} onClick={() => selectYear(y)}>{y}</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -28,20 +28,20 @@ export default function TitleBar() {
|
||||
}
|
||||
|
||||
return (
|
||||
<header id="titlebar" className="min-h-[22px] dark:bg-main-color-1 w-screen h-10 flex content-center items-center justify-start z-10">
|
||||
<header id="titlebar" className="min-h-[22px] bg-light-main-color-1 dark:bg-main-color-1 w-screen h-10 flex content-center items-center justify-start z-10">
|
||||
<div id="drag-region" className='grow basis-0'>
|
||||
<div id="window-title" className='pl-1'>
|
||||
<span className='text-gray-100 font-bold text-xs'>BSManager</span>
|
||||
<span className='text-gray-800 dark:text-gray-100 font-bold text-xs italic'>BSManager</span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="window-controls" className=''>
|
||||
<div onClick={minimizeWindow} className="button button text-gray-200 hover:bg-[#4F545C]" id="min-button" >
|
||||
<div onClick={minimizeWindow} className="button button text-gray-800 dark:text-gray-200 hover:bg-gray-300 dark:hover:bg-[#4F545C] cursor-pointer" id="min-button" >
|
||||
<svg aria-hidden="false" width="12" height="12" viewBox="0 0 12 12"><rect fill="currentColor" width="10" height="1" x="1" y="6"> </rect></svg>
|
||||
</div>
|
||||
<div onClick={toogleMaximize} className="button text-gray-200 hover:bg-[#4F545C]" id="max-button">
|
||||
<div onClick={toogleMaximize} className="button text-gray-800 dark:text-gray-200 hover:bg-gray-300 dark:hover:bg-[#4F545C] cursor-pointer" id="max-button">
|
||||
<svg aria-hidden="false" width="12" height="12" viewBox="0 0 12 12"><rect width="9" height="9" x="1.5" y="1.5" fill="none" stroke="currentColor"> </rect></svg>
|
||||
</div>
|
||||
<div onClick={closeWindow} className="button text-gray-200 cursor-pointer" id="close-button" draggable="false">
|
||||
<div onClick={closeWindow} className="button text-gray-800 dark:text-gray-200 cursor-pointer" id="close-button" draggable="false">
|
||||
<svg aria-hidden="false" width="12" height="12" viewBox="0 0 12 12"><polygon fill="currentColor" fillRule="evenodd" points="11 1.576 6.583 6 11 10.424 10.424 11 6 6.583 1.576 11 1 10.424 5.417 6 1 1.576 1.576 1 6 5.417 10.424 1"> </polygon></svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
export const defaultConfiguration: {[key in DefaultConfigKey]: any} = {
|
||||
"first-color": "#3b82ff",
|
||||
"second-color": "#ff4444"
|
||||
"second-color": "#ff4444",
|
||||
"theme": "os"
|
||||
}
|
||||
|
||||
export type DefaultConfigKey = "first-color" | "second-color";
|
||||
export type DefaultConfigKey = "first-color" | "second-color" | "theme";
|
||||
|
||||
export type ThemeConfig = "dark" | "light" | "os"
|
||||
@@ -3,37 +3,39 @@ import SettingColorChooser from "renderer/components/settings/setting-color-choo
|
||||
import { SettingContainer } from "renderer/components/settings/setting-container.component";
|
||||
import { RadioItem, SettingRadioArray } from "renderer/components/settings/setting-radio-array.component";
|
||||
import { BsmButton } from "renderer/components/shared/bsm-button.component";
|
||||
import { DefaultConfigKey } from "renderer/config/default-configuration.config";
|
||||
import { DefaultConfigKey, ThemeConfig } from "renderer/config/default-configuration.config";
|
||||
import { useObservable } from "renderer/hooks/use-observable.hook";
|
||||
import { BsDownloaderService } from "renderer/services/bs-downloader.service";
|
||||
import { ConfigurationService } from "renderer/services/configuration.service"
|
||||
import { IpcService } from "renderer/services/ipc.service";
|
||||
import { ModalExitCode, ModalService, ModalType } from "renderer/services/modale.service";
|
||||
import { ProgressBarService } from "renderer/services/progress-bar.service";
|
||||
import { ThemeService } from "renderer/services/theme.service";
|
||||
|
||||
export function SettingsPage() {
|
||||
|
||||
const configService = ConfigurationService.getInstance();
|
||||
const themeService = ThemeService.getInstance();
|
||||
const ipcService = IpcService.getInstance();
|
||||
const modalService = ModalService.getInsance();
|
||||
const downloaderService = BsDownloaderService.getInstance();
|
||||
const progressBarService = ProgressBarService.getInstance();
|
||||
|
||||
const themeItem: RadioItem[] = [
|
||||
{id: 0, text: "Dark", value: "dark" as ThemeConfig},
|
||||
{id: 1, text: "Light", value: "light" as ThemeConfig},
|
||||
{id: 3, text: "Operating System", value: "os" as ThemeConfig}
|
||||
];
|
||||
|
||||
const firstColor = useObservable(configService.watch<string>("first-color"));
|
||||
const secondColor = useObservable(configService.watch<string>("second-color"));
|
||||
const[themeIdSelected, setThemeIdSelected]= useState(0);
|
||||
const[themeIdSelected, setThemeIdSelected]= useState(themeItem.find(e => e.value === themeService.getTheme()).id);
|
||||
const [installationFolder, setInstallationFolder] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
loadInstallationFolder();
|
||||
}, []);
|
||||
|
||||
const themeItem: RadioItem[] = [
|
||||
{id: 0, text: "Dark"},
|
||||
{id: 1, text: "Light"},
|
||||
{id: 3, text: "Operating System"}
|
||||
]
|
||||
|
||||
const resetColors = () => {
|
||||
configService.delete("first-color" as DefaultConfigKey);
|
||||
configService.delete("second-color" as DefaultConfigKey);
|
||||
@@ -46,14 +48,23 @@ export function SettingsPage() {
|
||||
const setFirstColorSetting = (hex: string) => configService.set("first-color", hex);
|
||||
const setSecondColorSetting = (hex: string) => configService.set("second-color", hex);
|
||||
|
||||
const handleChangeTheme = (id: number) => {
|
||||
setThemeIdSelected(id);
|
||||
themeService.setTheme(themeItem.find(e => e.id === id).value);
|
||||
}
|
||||
|
||||
const setDefaultInstallationFolder = () => {
|
||||
modalService.openModal(ModalType.INSTALLATION_FOLDER).then(async res => {
|
||||
if(res.exitCode !== ModalExitCode.COMPLETED){ return; }
|
||||
|
||||
const fileChooserRes = await ipcService.send<{canceled: boolean, filePaths: string[]}>("choose-folder");
|
||||
|
||||
if(fileChooserRes.success && !fileChooserRes.data.canceled && fileChooserRes.data.filePaths?.length){
|
||||
progressBarService.showFake(.008);
|
||||
downloaderService.setInstallationFolder(fileChooserRes.data.filePaths[0]).then(res => {
|
||||
setTimeout(() => {
|
||||
progressBarService.complete();
|
||||
setTimeout(() => progressBarService.hide(true), 1000);
|
||||
}, 2000);
|
||||
if(res.success){ setInstallationFolder(res.data); }
|
||||
});
|
||||
}
|
||||
@@ -62,31 +73,31 @@ export function SettingsPage() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full flex justify-center px-40 pt-10 text-gray-200">
|
||||
<div className="w-full flex justify-center px-40 pt-10 text-gray-800 dark:text-gray-200">
|
||||
|
||||
<div className="w-fit max-w-full">
|
||||
|
||||
<SettingContainer title="Steam" description="If you logout of your Steam account, you must reconect for download a new BS instance">
|
||||
<BsmButton className="bg-red-500 w-fit px-3 py-[2px] rounded-md hover:brightness-75" withBar={false} text="Logout of Steam"/>
|
||||
<BsmButton className="bg-red-500 w-fit px-3 py-[2px] text-white rounded-md hover:brightness-90" withBar={false} text="Logout of Steam"/>
|
||||
</SettingContainer>
|
||||
|
||||
<SettingContainer title="Appearance" description="Choose the two primary colors of BSManager">
|
||||
<div className="relative w-full h-8 bg-main-color-1 flex justify-center rounded-md py-1">
|
||||
<div className="relative w-full h-8 bg-light-main-color-1 dark:bg-main-color-1 flex justify-center rounded-md py-1">
|
||||
<SettingColorChooser color={firstColor} onChange={setFirstColorSetting}/>
|
||||
<SettingColorChooser color={secondColor} onChange={setSecondColorSetting}/>
|
||||
<div className="absolute right-2 top-0 h-full flex items-center">
|
||||
<BsmButton onClick={resetColors} className="px-2 font-bold italic text-sm rounded-md hover:bg-main-color-3" text="Reset" withBar={false}/>
|
||||
<BsmButton onClick={resetColors} className="px-2 font-bold italic text-sm rounded-md bg-light-main-color-2 dark:bg-main-color-2 hover:bg-light-main-color-3 dark:hover:bg-main-color-3" text="Reset" withBar={false}/>
|
||||
</div>
|
||||
</div>
|
||||
<SettingContainer minorTitle="Theme (WIP)" className="mt-3">
|
||||
<SettingRadioArray items={themeItem} selectedItem={themeIdSelected} onItemSelected={(id) => setThemeIdSelected(id)}/>
|
||||
<SettingContainer minorTitle="Theme" className="mt-3">
|
||||
<SettingRadioArray items={themeItem} selectedItem={themeIdSelected} onItemSelected={handleChangeTheme}/>
|
||||
</SettingContainer>
|
||||
</SettingContainer>
|
||||
|
||||
<SettingContainer title="Installation Folder" description="Change the default installation directory for Beat Saber instances, and other upcoming features.">
|
||||
<div className="relative flex items-center justify-between w-full h-8 bg-main-color-1 rounded-md pl-2 py-1">
|
||||
<div className="relative flex items-center justify-between w-full h-8 bg-light-main-color-1 dark:bg-main-color-1 rounded-md pl-2 py-1">
|
||||
<span className="block text-ellipsis overflow-hidden min-w-0" title={installationFolder}>{installationFolder}</span>
|
||||
<BsmButton onClick={setDefaultInstallationFolder} className="shrink-0 whitespace-nowrap mr-2 px-2 font-bold italic text-sm rounded-md hover:bg-main-color-3" text="Choose Folder" withBar={false}/>
|
||||
<BsmButton onClick={setDefaultInstallationFolder} className="shrink-0 whitespace-nowrap mr-2 px-2 font-bold italic text-sm rounded-md bg-light-main-color-2 dark:bg-main-color-2 hover:bg-light-main-color-3 dark:hover:bg-main-color-3" text="Choose Folder" withBar={false}/>
|
||||
</div>
|
||||
</SettingContainer>
|
||||
|
||||
|
||||
@@ -82,8 +82,8 @@ export function VersionViewer() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<BsmImage className="absolute w-full h-full top-0 left-0 blur-lg object-cover brightness-100" image={state.ReleaseImg || DefautVersionImage} errorImage={defaultImageVersion}/>
|
||||
<div className="relative flex items-center flex-col w-full h-full text-gray-200">
|
||||
<BsmImage className="absolute w-full h-full top-0 left-0 object-cover" image={state.ReleaseImg || DefautVersionImage} errorImage={defaultImageVersion}/>
|
||||
<div className="relative flex items-center flex-col w-full h-full text-gray-200 backdrop-blur-lg">
|
||||
<BsmImage className='relative object-cover h-28' image={BSLogo}/>
|
||||
<h1 className='relative text-4xl font-bold italic -top-3'>{state.BSVersion}</h1>
|
||||
<TabNavBar className='mt-3' tabsText={["Launch", "Maps", "Mods"]} onTabChange={(i : number) => setCurrentTabIndex(i)}/>
|
||||
@@ -95,19 +95,19 @@ export function VersionViewer() {
|
||||
<ToogleLunchMod icon='terminal' onClick={() => setMode(LaunchMods.DEBUG_MOD, !debugMode)} active={debugMode} text="DEBUG MOD"/>
|
||||
</div>
|
||||
<div className='grow flex justify-center items-center'>
|
||||
<BsmButton onClick={launchBs} className='relative flex items-center text-5xl font-bold tracking-wide pt-1 pb-3 pl-7 pr-7 rounded-lg shadow-md italic shadow-black active:scale-90 transition-transform' text='Launch'/>
|
||||
<BsmButton onClick={launchBs} className='relative text-5xl text-gray-800 dark:text-gray-200 bg-light-main-color-2 dark:bg-main-color-2 font-bold tracking-wide pt-1 pb-3 px-7 rounded-lg shadow-md italic shadow-black active:scale-90 transition-transform' text='Launch'/>
|
||||
</div>
|
||||
</div>
|
||||
<div className='shrink-0 w-full h-full flex justify-center'>
|
||||
<div className='p-4 bg-main-color-2 h-fit rounded-md'>
|
||||
<div className='p-4 bg-light-main-color-2 dark:bg-main-color-2 h-fit rounded-md'>
|
||||
<img src={wipGif} alt="" />
|
||||
<span className='block w-full text-center font-bold mt-2'>Work In Progress</span>
|
||||
<span className='block w-full text-center font-bold mt-2 text-gray-800 dark:text-white'>Work In Progress</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className='shrink-0 w-full h-full flex justify-center'>
|
||||
<div className='p-4 bg-main-color-2 h-fit rounded-md'>
|
||||
<div className='p-4 bg-light-main-color-2 dark:bg-main-color-2 h-fit rounded-md'>
|
||||
<img src={wipGif} alt="" />
|
||||
<span className='block w-full text-center font-bold mt-2'>Work In Progress</span>
|
||||
<span className='block w-full text-center font-bold mt-2 text-gray-800 dark:text-white'>Work In Progress</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -126,8 +126,8 @@ function ToogleLunchMod(props: {onClick: Function, active: boolean, text: string
|
||||
return (
|
||||
<div className={`relative rounded-full cursor-pointer group active:scale-95 transition-transform ${!props.active && "shadow-md shadow-black"}`} onClick={() => props.onClick()}>
|
||||
<div className={`absolute glow-on-hover rounded-full ${props.active && "opacity-100 blur-[2px]"}`}></div>
|
||||
<div className='w-full h-full pl-6 pr-6 flex justify-center items-center bg-main-color-2 p-3 rounded-full text-white group-hover:bg-main-color-1'>
|
||||
<BsmIcon icon={props.icon} className='mr-1 h-7'/>
|
||||
<div className='w-full h-full pl-6 pr-6 flex justify-center items-center bg-light-main-color-2 dark:bg-main-color-2 p-3 rounded-full text-gray-800 dark:text-white group-hover:bg-light-main-color-1 group-hover:dark:bg-main-color-1'>
|
||||
<BsmIcon icon={props.icon} className='mr-1 h-7 text-gray-800 dark:text-white'/>
|
||||
<span className='w-fit min-w-fit h-full text-lg font-bold uppercase tracking-wide italic'>{props.text}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,6 +7,7 @@ export class ModalService{
|
||||
private _modalType$: BehaviorSubject<ModalType> = new BehaviorSubject<ModalType>(null);
|
||||
private modalData: any = null;
|
||||
private resolver: any;
|
||||
|
||||
private constructor(){}
|
||||
|
||||
public static getInsance(){
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { DefaultConfigKey, ThemeConfig } from "renderer/config/default-configuration.config";
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
import { ConfigurationService } from "./configuration.service";
|
||||
|
||||
export class ThemeService{
|
||||
|
||||
private static instance: ThemeService;
|
||||
|
||||
private readonly configService: ConfigurationService;
|
||||
|
||||
public readonly theme$: BehaviorSubject<ThemeConfig>;
|
||||
|
||||
public static getInstance(): ThemeService{
|
||||
if(!ThemeService.instance){ ThemeService.instance = new ThemeService(); }
|
||||
return ThemeService.instance;
|
||||
}
|
||||
|
||||
private constructor(){
|
||||
this.configService = ConfigurationService.getInstance();
|
||||
this.theme$ = this.configService.watch<ThemeConfig>("theme" as DefaultConfigKey);
|
||||
}
|
||||
|
||||
public setTheme(theme: ThemeConfig): void{
|
||||
this.configService.set("theme" as DefaultConfigKey, theme);
|
||||
}
|
||||
|
||||
public getTheme(): ThemeConfig{
|
||||
return this.configService.get("theme" as DefaultConfigKey);
|
||||
}
|
||||
|
||||
public get isLight(){ return this.configService.get("theme" as DefaultConfigKey) === "light" as ThemeConfig }
|
||||
public get isDark(){ return this.configService.get("theme" as DefaultConfigKey) === "dark" as ThemeConfig }
|
||||
public get isOS(){ return this.configService.get("theme" as DefaultConfigKey) === "os" as ThemeConfig }
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user