mirror of
https://github.com/Zagrios/bs-manager.git
synced 2026-07-03 14:08:25 +02:00
[chore] rework advanced launch
This commit is contained in:
+5
-4
@@ -1,24 +1,25 @@
|
||||
import Tippy from "@tippyjs/react";
|
||||
import { GlowEffect } from "renderer/components/shared/glow-effect.component";
|
||||
import { BsmIcon, BsmIconType } from "renderer/components/svgs/bsm-icon.component";
|
||||
import { BsmIcon } from "renderer/components/svgs/bsm-icon.component";
|
||||
import { SvgIcon } from "renderer/components/svgs/svg-icon.type";
|
||||
import { useTranslation } from "renderer/hooks/use-translation.hook";
|
||||
|
||||
type Props = {
|
||||
onClick: (active: boolean) => void;
|
||||
active: boolean;
|
||||
text: string;
|
||||
icon: BsmIconType;
|
||||
icon?: SvgIcon;
|
||||
infoText?: string;
|
||||
};
|
||||
|
||||
export function LaunchModToogle({ onClick, active, text, icon, infoText }: Props) {
|
||||
export function LaunchModToogle({ onClick, active, text, icon: Icon, infoText }: Props) {
|
||||
const t = useTranslation();
|
||||
|
||||
return (
|
||||
<div className={`relative rounded-full cursor-pointer group active:scale-95 transition-transform ${!active && "shadow-md shadow-black"}`} onClick={() => onClick(!active)}>
|
||||
<GlowEffect visible={active} className="absolute !rounded-full blur-[2px]" />
|
||||
<div className="w-full h-full px-6 flex gap-1.5 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 dark:group-hover:bg-main-color-1">
|
||||
<BsmIcon icon={icon} className="h-7 shrink-0 text-gray-800 dark:text-white" />
|
||||
{Icon && <Icon className="h-7 shrink-0 text-gray-800 dark:text-white" />}
|
||||
<span className="w-fit min-w-fit text-lg font-bold uppercase tracking-wide italic ">{t(text)}</span>
|
||||
{infoText && (
|
||||
<Tippy content={t(infoText)} placement="bottom" delay={[400, 0]}>
|
||||
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
import Tippy from "@tippyjs/react";
|
||||
import { BsmCheckbox } from "renderer/components/shared/bsm-checkbox.component";
|
||||
import { PinIcon } from "renderer/components/svgs/icons/pin-icon.component";
|
||||
import { UnpinIcon } from "renderer/components/svgs/icons/unpin-icon.component";
|
||||
import { SvgIcon } from "renderer/components/svgs/svg-icon.type";
|
||||
import { cn } from "renderer/helpers/css-class.helpers";
|
||||
import { useTranslationV2 } from "renderer/hooks/use-translation.hook";
|
||||
import { LaunchMod } from "shared/models/bs-launch/launch-option.interface";
|
||||
|
||||
type Props = {
|
||||
readonly className?: string;
|
||||
readonly open: boolean;
|
||||
readonly launchArgs?: string;
|
||||
readonly launchMods?: LaunchModItemProps[];
|
||||
readonly onLaunchArgsChange?: (args: string) => void;
|
||||
};
|
||||
|
||||
export function LaunchOptionsPanel({ className, open, launchArgs, launchMods, onLaunchArgsChange }: Props) {
|
||||
|
||||
const {text: t} = useTranslationV2();
|
||||
|
||||
return (
|
||||
<div className={cn("grid grid-rows-[0fr] transition-[grid-template-rows] bg-theme-2 rounded-md shadow-md shadow-black overflow-hidden", open && "!grid-rows-[1fr]", className)}>
|
||||
<div className="flex flex-col overflow-y-scroll scrollbar-default">
|
||||
<div className="p-3.5">
|
||||
<input className="h-10 w-full rounded-md bg-theme-1 text-center mb-2" type="text" placeholder={t("pages.version-viewer.launch-mods.advanced-launch.placeholder")} value={launchArgs} onChange={e => onLaunchArgsChange(e.target.value)}/>
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
{launchMods?.map((mod) => (
|
||||
<LaunchModItem key={mod.id} {...mod}/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export type LaunchModItemProps = {
|
||||
readonly id: LaunchMod;
|
||||
readonly Icon?: SvgIcon;
|
||||
readonly label: string;
|
||||
readonly description: string;
|
||||
readonly active: boolean;
|
||||
readonly visible?: boolean;
|
||||
readonly pinned?: boolean;
|
||||
readonly onChange?: (val: boolean) => void;
|
||||
readonly onPinChange?: (val: boolean) => void;
|
||||
}
|
||||
|
||||
export function LaunchModItem({ id, Icon, label, description, active, visible, pinned, onChange, onPinChange }: LaunchModItemProps) {
|
||||
|
||||
return (
|
||||
<Tippy theme="default" placement="top" content={description}>
|
||||
<button id={id} className={cn("grow rounded-md bg-theme-1 relative flex justify-center items-center gap-1.5 h-10 py-1 px-3", visible === false && "hidden")} onClick={e => { e.preventDefault(); e.stopPropagation(); onChange?.(!active) }}>
|
||||
<BsmCheckbox className="h-4 aspect-square z-[1] relative" checked={active} onChange={onChange}/>
|
||||
{Icon && <Icon className="h-full w-fit py-0.5"/>}
|
||||
<span className="font-bold">{label}</span>
|
||||
<Tippy theme="default" placement="right" content="Pin" hideOnClick>
|
||||
<button className="h-full py-2" onClick={e => { e.preventDefault(); e.stopPropagation(); onPinChange?.(!pinned) }}>
|
||||
{pinned ? <UnpinIcon className="size-full"/> : <PinIcon className="size-full"/>}
|
||||
</button>
|
||||
</Tippy>
|
||||
</button>
|
||||
</Tippy>
|
||||
);
|
||||
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ChangeEvent, useEffect, useMemo, useState } from "react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { BsmButton } from "renderer/components/shared/bsm-button.component";
|
||||
import { useObservable } from "renderer/hooks/use-observable.hook";
|
||||
import { useTranslationV2 } from "renderer/hooks/use-translation.hook";
|
||||
import { BSLauncherService, LaunchMods } from "renderer/services/bs-launcher.service";
|
||||
import { BSLauncherService } from "renderer/services/bs-launcher.service";
|
||||
import { ConfigurationService } from "renderer/services/configuration.service";
|
||||
import { BSVersion } from "shared/bs-version.interface";
|
||||
import { LaunchModToogle } from "./launch-mod-toogle.component";
|
||||
@@ -19,6 +19,12 @@ import { BSVersionManagerService } from "renderer/services/bs-version-manager.se
|
||||
import { safeLt } from "shared/helpers/semver.helpers";
|
||||
import { WarningIcon } from "renderer/components/svgs/icons/warning-icon.component";
|
||||
import Tippy from "@tippyjs/react";
|
||||
import { LaunchModItemProps, LaunchOptionsPanel } from "./launch-options-panel.component";
|
||||
import { LaunchMod, LaunchMods } from "shared/models/bs-launch/launch-option.interface";
|
||||
import { OculusIcon } from "renderer/components/svgs/icons/oculus-icon.component";
|
||||
import { DesktopIcon } from "renderer/components/svgs/icons/desktop-icon.component";
|
||||
import { TerminalIcon } from "renderer/components/svgs/icons/terminal-icon.component";
|
||||
import { DefaultConfigKey } from "renderer/config/default-configuration.config";
|
||||
|
||||
type Props = { version: BSVersion };
|
||||
|
||||
@@ -30,13 +36,12 @@ export function LaunchSlide({ version }: Props) {
|
||||
const bsDownloader = useService(BsDownloaderService);
|
||||
const versions = useService(BSVersionManagerService);
|
||||
|
||||
const [oculusMode, setOculusMode] = useState(!!configService.get<boolean>(LaunchMods.OCULUS_MOD));
|
||||
const [desktopMode, setDesktopMode] = useState(!!configService.get<boolean>(LaunchMods.DESKTOP_MOD));
|
||||
const [debugMode, setDebugMode] = useState(!!configService.get<boolean>(LaunchMods.DEBUG_MOD));
|
||||
const [advancedLaunch, setAdvancedLaunch] = useState(false);
|
||||
const [additionalArgsString, setAdditionalArgsString] = useState<string>(configService.get<string>("additionnal-args") || "");
|
||||
const [skipSteamMode, setSkipSteamMode] = useState(!!configService.get<boolean>(LaunchMods.SKIPSTEAM_MOD));
|
||||
const versionDownloading = useObservable(() => bsDownloader.downloadingVersion$);
|
||||
const [activeLaunchMods, setActiveLaunchMods] = useState<LaunchMod[]>(configService.get("launch-mods") ?? []);
|
||||
const [pinnedLaunchMods, setPinnedLaunchMods] = useState<LaunchMod[]>(configService.get("pinned-launch-mods" as DefaultConfigKey) ?? []);
|
||||
const [launchModItems, setLaunchModItems] = useState<LaunchModItemProps[]>([]);
|
||||
|
||||
const versionRunning = useObservable(() => bsLauncherService.versionRunning$);
|
||||
|
||||
@@ -45,36 +50,69 @@ export function LaunchSlide({ version }: Props) {
|
||||
}, [additionalArgsString]);
|
||||
|
||||
useEffect(() => {
|
||||
if(desktopMode){ return; }
|
||||
bsLauncherService.restoreSteamVR();
|
||||
}, [desktopMode]);
|
||||
configService.set("pinned-launch-mods", pinnedLaunchMods);
|
||||
}, [pinnedLaunchMods])
|
||||
|
||||
const setMode = (mode: LaunchMods, value: boolean) => {
|
||||
useEffect(() => {
|
||||
configService.set("launch-mods", activeLaunchMods);
|
||||
|
||||
if (mode === LaunchMods.DEBUG_MOD) {
|
||||
setDebugMode(value);
|
||||
} else if (mode === LaunchMods.OCULUS_MOD) {
|
||||
setOculusMode(value);
|
||||
} else if (mode === LaunchMods.DESKTOP_MOD) {
|
||||
setDesktopMode(value);
|
||||
} else if (mode === LaunchMods.SKIPSTEAM_MOD) {
|
||||
setSkipSteamMode(value);
|
||||
if(!activeLaunchMods.includes("fpfc")){
|
||||
bsLauncherService.restoreSteamVR();
|
||||
}
|
||||
configService.set(mode, value);
|
||||
};
|
||||
}, [activeLaunchMods]);
|
||||
|
||||
const handleAdditionalArgsChange = (e: ChangeEvent<HTMLInputElement>) => setAdditionalArgsString(() => e.target.value);
|
||||
useEffect(() => {
|
||||
setLaunchModItems(() => [
|
||||
{
|
||||
id: LaunchMods.OCULUS,
|
||||
Icon: OculusIcon,
|
||||
label: t("pages.version-viewer.launch-mods.oculus"),
|
||||
description: t("pages.version-viewer.launch-mods.oculus-description"),
|
||||
active: activeLaunchMods.includes(LaunchMods.OCULUS),
|
||||
visible: !(version.metadata?.store === BsStore.OCULUS),
|
||||
pinned: pinnedLaunchMods.includes(LaunchMods.OCULUS),
|
||||
onChange: checked => checked ? setActiveLaunchMods(prev => [...prev, LaunchMods.OCULUS]) : setActiveLaunchMods(prev => prev.filter(mod => mod !== LaunchMods.OCULUS)),
|
||||
onPinChange: pinned => pinned ? setPinnedLaunchMods(prev => [...prev, LaunchMods.OCULUS]) : setPinnedLaunchMods(prev => prev.filter(mod => mod !== LaunchMods.OCULUS)),
|
||||
},
|
||||
{
|
||||
id: LaunchMods.FPFC,
|
||||
Icon: DesktopIcon,
|
||||
label: t("pages.version-viewer.launch-mods.desktop"),
|
||||
description: t("pages.version-viewer.launch-mods.desktop-description"),
|
||||
active: activeLaunchMods.includes(LaunchMods.FPFC),
|
||||
pinned: pinnedLaunchMods.includes(LaunchMods.FPFC),
|
||||
onChange: checked => checked ? setActiveLaunchMods(prev => [...prev, LaunchMods.FPFC]) : setActiveLaunchMods(prev => prev.filter(mod => mod !== LaunchMods.FPFC)),
|
||||
onPinChange: pinned => pinned ? setPinnedLaunchMods(prev => [...prev, LaunchMods.FPFC]) : setPinnedLaunchMods(prev => prev.filter(mod => mod !== LaunchMods.FPFC)),
|
||||
},
|
||||
{
|
||||
id: LaunchMods.DEBUG,
|
||||
Icon: TerminalIcon,
|
||||
label: t("pages.version-viewer.launch-mods.debug"),
|
||||
description: t("pages.version-viewer.launch-mods.debug-description"),
|
||||
active: activeLaunchMods.includes(LaunchMods.DEBUG),
|
||||
pinned: pinnedLaunchMods.includes(LaunchMods.DEBUG),
|
||||
onChange: checked => checked ? setActiveLaunchMods(prev => [...prev, LaunchMods.DEBUG]) : setActiveLaunchMods(prev => prev.filter(mod => mod !== LaunchMods.DEBUG)),
|
||||
onPinChange: pinned => pinned ? setPinnedLaunchMods(prev => [...prev, LaunchMods.DEBUG]) : setPinnedLaunchMods(prev => prev.filter(mod => mod !== LaunchMods.DEBUG)),
|
||||
},
|
||||
{
|
||||
id: LaunchMods.SKIP_STEAM,
|
||||
label: t("pages.version-viewer.launch-mods.skipsteam"),
|
||||
description: t("pages.version-viewer.launch-mods.skipsteam-description"),
|
||||
active: activeLaunchMods.includes(LaunchMods.SKIP_STEAM),
|
||||
pinned: pinnedLaunchMods.includes(LaunchMods.SKIP_STEAM),
|
||||
onChange: checked => checked ? setActiveLaunchMods(prev => [...prev, LaunchMods.SKIP_STEAM]) : setActiveLaunchMods(prev => prev.filter(mod => mod !== LaunchMods.SKIP_STEAM)),
|
||||
onPinChange: pinned => pinned ? setPinnedLaunchMods(prev => [...prev, LaunchMods.SKIP_STEAM]) : setPinnedLaunchMods(prev => prev.filter(mod => mod !== LaunchMods.SKIP_STEAM)),
|
||||
}
|
||||
]);
|
||||
}, [activeLaunchMods, pinnedLaunchMods, version]);
|
||||
|
||||
const launch = () => {
|
||||
const additionalArgs = additionalArgsString?.split(";").map(arg => arg.trim()).filter(arg => arg.length > 0);
|
||||
|
||||
const launch$ = bsLauncherService.launch({
|
||||
version,
|
||||
oculus: version.oculus ? false : oculusMode,
|
||||
desktop: desktopMode,
|
||||
debug: debugMode,
|
||||
launchMods: activeLaunchMods,
|
||||
additionalArgs: advancedLaunch ? additionalArgs : [],
|
||||
skipSteam: skipSteamMode,
|
||||
});
|
||||
|
||||
return lastValueFrom(launch$).catch(() => {});
|
||||
@@ -87,7 +125,9 @@ export function LaunchSlide({ version }: Props) {
|
||||
return (
|
||||
<div className="w-full shrink-0 items-center relative flex flex-col justify-start">
|
||||
<div className="flex flex-col gap-3 justify-center items-center mb-5">
|
||||
<BsmImage className="relative object-cover h-28" image={BSLogo} />
|
||||
<div className="h-24 flex justify-center items-center">
|
||||
<BsmImage className="relative object-cover h-28" image={BSLogo} />
|
||||
</div>
|
||||
<h1 className="relative text-4xl font-bold italic -top-3">
|
||||
{version.name ? `${version.BSVersion} - ${version.name}` : version.BSVersion}
|
||||
{isOutdated && (
|
||||
@@ -100,9 +140,18 @@ export function LaunchSlide({ version }: Props) {
|
||||
</h1>
|
||||
</div>
|
||||
<div className="grid grid-flow-col gap-6">
|
||||
{!(version.oculus || version.metadata?.store === BsStore.OCULUS) && <LaunchModToogle infoText="pages.version-viewer.launch-mods.oculus-description" icon="oculus" onClick={() => setMode(LaunchMods.OCULUS_MOD, !oculusMode)} active={oculusMode} text="pages.version-viewer.launch-mods.oculus" />}
|
||||
<LaunchModToogle infoText="pages.version-viewer.launch-mods.desktop-description" icon="desktop" onClick={() => setMode(LaunchMods.DESKTOP_MOD, !desktopMode)} active={desktopMode} text="pages.version-viewer.launch-mods.desktop" />
|
||||
<LaunchModToogle infoText="pages.version-viewer.launch-mods.debug-description" icon="terminal" onClick={() => setMode(LaunchMods.DEBUG_MOD, !debugMode)} active={debugMode} text="pages.version-viewer.launch-mods.debug" />
|
||||
{pinnedLaunchMods.map(id => launchModItems.find(mod => mod.id === id)).map(launchMod => {
|
||||
if(launchMod?.visible === false || !launchMod?.pinned) { return undefined; }
|
||||
return (
|
||||
<LaunchModToogle
|
||||
icon={launchMod.Icon}
|
||||
infoText={launchMod.description}
|
||||
text={launchMod.label}
|
||||
active={launchMod.active}
|
||||
onClick={() => launchMod.onChange(!launchMod.active)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="pt-4 w-2/3 flex flex-col items-center gap-3">
|
||||
<div className="relative">
|
||||
@@ -117,18 +166,13 @@ export function LaunchSlide({ version }: Props) {
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="bg-light-main-color-2 dark:bg-main-color-2 h-9 rounded-full overflow-hidden flex items-center justify-center transition-all duration-100 ease-in-out w-full origin-top shadow-black shadow-sm" style={{ scale: advancedLaunch ? "100% 100%" : "0 0" }}>
|
||||
<input className="w-[calc(100%-12px)] h-[calc(100%-12px)] bg-light-main-color-1 dark:bg-main-color-1 text-black dark:text-white rounded-full outline-none text-center" type="text" placeholder={t("pages.version-viewer.launch-mods.advanced-launch.placeholder")} value={additionalArgsString} onChange={handleAdditionalArgsChange} />
|
||||
</div>
|
||||
<div className="grid grid-cols-3 gap-3 transition-all duration-300 ease-in-out origin-top" style={{ scale: advancedLaunch ? "75% 75%" : "0% 0%" }}>
|
||||
{advancedLaunch && !(version.oculus || version.metadata?.store === BsStore.OCULUS) && <LaunchModToogle infoText="pages.version-viewer.launch-mods.skipsteam-description" icon="null" onClick={() => setMode(LaunchMods.SKIPSTEAM_MOD, !skipSteamMode)} active={skipSteamMode} text="pages.version-viewer.launch-mods.skipsteam" />}
|
||||
</div>
|
||||
</div>
|
||||
<div className='grow flex justify-center items-center'>
|
||||
<LaunchOptionsPanel open={advancedLaunch} launchMods={launchModItems} className="w-full max-w-3xl mt-3" onLaunchArgsChange={setAdditionalArgsString}/>
|
||||
<div className='grow flex justify-center items-center p-2'>
|
||||
<BsmButton
|
||||
onClick={launch}
|
||||
active={JSON.stringify(version) === JSON.stringify(versionRunning)}
|
||||
className='relative -translate-y-1/2 text-5xl text-gray-800 dark:text-gray-200 font-bold tracking-wide pt-1 pb-3 px-7 rounded-lg shadow-md italic shadow-black active:scale-90 transition-transform'
|
||||
className='relative text-5xl text-gray-800 dark:text-gray-200 font-bold tracking-wide pt-1 pb-3 px-7 rounded-lg shadow-md italic shadow-black active:scale-90 transition-transform'
|
||||
text="misc.launch"
|
||||
disabled={equal(version, versionDownloading)}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user