support light theme

This commit is contained in:
MathieuG-P
2022-06-28 20:01:53 +02:00
parent c891b1d61c
commit 020e33f42b
23 changed files with 143 additions and 86 deletions
+15 -2
View File
@@ -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"
+28 -17
View File
@@ -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>
+1
View File
@@ -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(){
+35
View File
@@ -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 }
}