[chore] rework advanced launch

This commit is contained in:
MathieuG-P
2024-12-21 17:46:34 +01:00
parent 8776b036af
commit 3efcd40cdc
15 changed files with 221 additions and 87 deletions
@@ -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]}>
@@ -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)}
/>