[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
@@ -7,6 +7,7 @@ import { sToMs } from "../../../shared/helpers/time.helpers";
import { LinuxService } from "../linux.service";
import { bsmSpawn } from "main/helpers/os.helpers";
import { IS_FLATPAK } from "main/constants";
import { LaunchMods } from "shared/models/bs-launch/launch-option.interface";
export abstract class AbstractLauncherService {
@@ -24,14 +25,14 @@ export abstract class AbstractLauncherService {
if(!launchOptions.version.steam && !launchOptions.version.oculus){
launchArgs.push("--no-yeet")
}
if (launchOptions.oculus) {
if (launchOptions.launchMods?.includes(LaunchMods.OCULUS)) {
launchArgs.push("-vrmode");
launchArgs.push("oculus");
}
if (launchOptions.desktop) {
if (launchOptions.launchMods?.includes(LaunchMods.FPFC)) {
launchArgs.push("fpfc");
}
if (launchOptions.debug) {
if (launchOptions.launchMods?.includes(LaunchMods.DEBUG)) {
launchArgs.push("--verbose");
}
if (launchOptions.additionalArgs) {
@@ -20,6 +20,7 @@ import { SteamLauncherService } from "./steam-launcher.service";
import { OculusLauncherService } from "./oculus-launcher.service";
import { BSVersion } from "shared/bs-version.interface";
import { BsStore } from "../../../shared/models/bs-store.enum";
import { LaunchMod, LaunchMods } from "shared/models/bs-launch/launch-option.interface";
export class BSLauncherService {
private static instance: BSLauncherService;
@@ -94,6 +95,14 @@ export class BSLauncherService {
}
private shortcutParamsToLaunchOption(params: ShortcutParams): LaunchOption{
const launchMods: LaunchMod[] = [];
if(params.oculusMode === "true"){ launchMods.push(LaunchMods.OCULUS); }
if(params.desktopMode === "true"){ launchMods.push(LaunchMods.FPFC); }
if(params.debug === "true"){ launchMods.push(LaunchMods.DEBUG); }
if(params.skipSteam === "true"){ launchMods.push(LaunchMods.SKIP_STEAM); }
const res: LaunchOption = {
version: {
BSVersion: params.version,
@@ -102,11 +111,8 @@ export class BSLauncherService {
oculus: params.versionOculus === "true",
ino: +params.versionIno
},
oculus: params.oculusMode === "true",
desktop: params.desktopMode === "true",
debug: params.debug === "true",
additionalArgs: params.additionalArgs,
skipSteam: params.skipSteam === "true",
launchMods,
};
return res;
@@ -120,11 +126,11 @@ export class BSLauncherService {
if(launchOptions.version.oculus){ res.versionOculus = `${launchOptions.version.oculus}`; }
if(launchOptions.version.ino){ res.versionIno = `${launchOptions.version.ino}`; }
if(launchOptions.oculus){ res.oculusMode = "true"; }
if(launchOptions.desktop){ res.desktopMode = "true"; }
if(launchOptions.debug){ res.debug = "true"; }
if(launchOptions.launchMods?.includes(LaunchMods.OCULUS)){ res.oculusMode = "true"; }
if(launchOptions.launchMods?.includes(LaunchMods.FPFC)){ res.desktopMode = "true"; }
if(launchOptions.launchMods?.includes(LaunchMods.DEBUG)){ res.debug = "true"; }
if(launchOptions.additionalArgs){ res.additionalArgs = launchOptions.additionalArgs; }
if(launchOptions.skipSteam){ res.skipSteam = "true"; }
if(launchOptions.launchMods?.includes(LaunchMods.SKIP_STEAM)){ res.skipSteam = "true"; }
return res;
}
@@ -10,6 +10,7 @@ import { AbstractLauncherService } from "./abstract-launcher.service";
import { CustomError } from "../../../shared/models/exceptions/custom-error.class";
import { UtilsService } from "../utils.service";
import { exec } from "child_process";
import { LaunchMods } from "shared/models/bs-launch/launch-option.interface";
export class SteamLauncherService extends AbstractLauncherService implements StoreLauncherInterface{
@@ -73,7 +74,7 @@ export class SteamLauncherService extends AbstractLauncherService implements Sto
throw CustomError.fromError(new Error(`Path not exist : ${bsExePath}`), BSLaunchError.BS_NOT_FOUND);
}
const skipSteam: boolean = launchOptions.skipSteam ?? false;
const skipSteam: boolean = launchOptions.launchMods?.includes(LaunchMods.SKIP_STEAM) ?? false;
// Open Steam if not running
if(!skipSteam && !(await this.steam.isSteamRunning())){
@@ -87,12 +88,12 @@ export class SteamLauncherService extends AbstractLauncherService implements Sto
obs.next({type: BSLaunchWarning.UNABLE_TO_LAUNCH_STEAM});
});
}
else if (launchOptions.skipSteam) {
else {
obs.next({ type: BSLaunchEvent.SKIPPING_STEAM_LAUNCH});
}
// Backup SteamVR when desktop mode is enabled
if(launchOptions.desktop){
if(launchOptions.launchMods?.includes(LaunchMods.FPFC)){
await this.backupSteamVR().catch(() => {
return this.restoreSteamVR();
});
@@ -60,10 +60,7 @@ export function BsVersionItem(props: { version: BSVersion }) {
if(equal(downloadingVersion, props.version)){ return; }
const launch$ = launcherService.launch({
version: state,
oculus: !!configService.get<boolean>(LaunchMods.OCULUS_MOD),
desktop: !!configService.get<boolean>(LaunchMods.DESKTOP_MOD),
debug: !!configService.get<boolean>(LaunchMods.DEBUG_MOD),
skipSteam: !!configService.get<boolean>(LaunchMods.SKIPSTEAM_MOD),
launchMods: configService.get("launch-mods") ?? [],
});
return lastValueFrom(launch$).catch(() => {});
};
@@ -1,9 +1,9 @@
import { CSSProperties } from "react";
import { createSvgIcon } from "../svg-icon.type";
export function DesktopIcon(props: { className?: string; style?: CSSProperties }) {
export const DesktopIcon = createSvgIcon((props, ref) => {
return (
<svg className={props.className} style={props.style} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40" height="40" width="40">
<svg ref={ref} {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40" height="40" width="40">
<path fill="currentColor" d="M13.417 38V33.75H16.875V29.875H6.708Q4.917 29.875 3.687 28.646Q2.458 27.417 2.458 25.667V7.25Q2.458 5.5 3.687 4.271Q4.917 3.042 6.708 3.042H33.375Q35.125 3.042 36.375 4.271Q37.625 5.5 37.625 7.25V25.667Q37.625 27.417 36.375 28.646Q35.125 29.875 33.375 29.875H23.167V33.75H26.667V38ZM6.708 25.667H33.375Q33.375 25.667 33.375 25.667Q33.375 25.667 33.375 25.667V7.25Q33.375 7.25 33.375 7.25Q33.375 7.25 33.375 7.25H6.708Q6.708 7.25 6.708 7.25Q6.708 7.25 6.708 7.25V25.667Q6.708 25.667 6.708 25.667Q6.708 25.667 6.708 25.667ZM6.708 25.667Q6.708 25.667 6.708 25.667Q6.708 25.667 6.708 25.667V7.25Q6.708 7.25 6.708 7.25Q6.708 7.25 6.708 7.25Q6.708 7.25 6.708 7.25Q6.708 7.25 6.708 7.25V25.667Q6.708 25.667 6.708 25.667Q6.708 25.667 6.708 25.667Z" />
</svg>
);
}
})
@@ -1,12 +1,12 @@
import { HTMLProps } from "react";
import { createSvgIcon } from "../svg-icon.type";
export function OculusIcon(props: HTMLProps<SVGSVGElement>) {
export const OculusIcon = createSvgIcon((props, ref) => {
return (
<svg {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<svg ref={ref} {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<path
fill="currentColor"
d="M24.182 18.599c-0.427 0.297-0.901 0.474-1.411 0.552-0.51 0.083-1.016 0.068-1.521 0.068h-10.5c-0.51 0-1.016 0.021-1.526-0.068-0.51-0.083-0.979-0.255-1.411-0.552-0.854-0.604-1.365-1.563-1.365-2.604 0-1.057 0.516-2.016 1.37-2.599 0.417-0.297 0.896-0.479 1.396-0.557 0.5-0.083 1-0.083 1.526-0.083h10.5c0.5 0 1.016-0.016 1.516 0.063s0.984 0.26 1.401 0.542c0.865 0.578 1.365 1.557 1.365 2.599 0 1.036-0.526 2-1.38 2.599zM28.411 8.526c-1.125-0.906-2.417-1.531-3.818-1.865-0.802-0.193-1.604-0.281-2.432-0.307-0.599-0.021-1.198-0.010-1.818-0.010h-8.661c-0.609 0-1.224-0.010-1.833 0.010-0.823 0.026-1.63 0.109-2.432 0.307-1.401 0.339-2.698 0.964-3.818 1.865-2.281 1.823-3.599 4.568-3.599 7.474 0 2.911 1.318 5.656 3.583 7.474 1.13 0.906 2.422 1.531 3.823 1.87 0.802 0.193 1.609 0.281 2.432 0.302 0.599 0.021 1.198 0.016 1.818 0.016h8.661c0.599 0 1.219 0.005 1.818-0.016 0.823-0.021 1.62-0.109 2.417-0.302 1.401-0.344 2.682-0.969 3.823-1.87 2.307-1.823 3.625-4.568 3.625-7.474s-1.318-5.656-3.589-7.474z"
/>
</svg>
);
}
})
@@ -0,0 +1,9 @@
import { createSvgIcon } from "../svg-icon.type";
export const PinIcon = createSvgIcon((props, ref) => {
return (
<svg ref={ref} {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="currentColor">
<path d="M648.13-763.11v274.74l66.57 66.57q6.71 6.71 10.07 15.05 3.36 8.34 3.36 17.53v26.59q0 19.15-13.17 32.33-13.18 13.17-32.33 13.17H525.5v232.09q0 19.15-13.17 32.32Q499.15-39.54 480-39.54t-32.33-13.18Q434.5-65.89 434.5-85.04v-232.09H277.37q-19.15 0-32.33-13.17-13.17-13.18-13.17-32.33v-26.59q0-9.19 3.36-17.53 3.36-8.34 10.07-15.05l66.57-66.57v-274.74q-17-1.67-28.5-14.25-11.5-12.57-11.5-30.29 0-19.15 13.17-32.33 13.18-13.17 32.33-13.17h325.26q19.15 0 32.33 13.17 13.17 13.18 13.17 32.33 0 17.72-11.5 30.29-11.5 12.58-28.5 14.25Z"/>
</svg>
)
});
@@ -1,9 +1,9 @@
import { CSSProperties } from "react";
import { createSvgIcon } from "../svg-icon.type";
export function TerminalIcon(props: { className?: string; style?: CSSProperties }) {
export const TerminalIcon = createSvgIcon((props, ref) => {
return (
<svg className={props.className} style={props.style} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40" height="40" width="40">
<svg ref={ref} {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40" height="40" width="40">
<path fill="currentColor" d="M6.333 33.375Q5.125 33.375 4.229 32.479Q3.333 31.583 3.333 30.333V9.667Q3.333 8.417 4.229 7.521Q5.125 6.625 6.333 6.625H33.667Q34.875 6.625 35.771 7.521Q36.667 8.417 36.667 9.667V30.333Q36.667 31.583 35.771 32.479Q34.875 33.375 33.667 33.375ZM6.333 30.333H33.667Q33.667 30.333 33.667 30.333Q33.667 30.333 33.667 30.333V13.042H6.333V30.333Q6.333 30.333 6.333 30.333Q6.333 30.333 6.333 30.333ZM20.375 28V25.375H29.667V28ZM12.5 27.875 10.667 26.042 14.958 21.708 10.625 17.375 12.5 15.5 18.667 21.708Z" />
</svg>
);
}
})
@@ -0,0 +1,9 @@
import { createSvgIcon } from "../svg-icon.type"
export const UnpinIcon = createSvgIcon((props, ref) => {
return (
<svg ref={ref} {...props} xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="currentColor">
<path d="M570.57-514.15 294.3-790.65q-5.47-5.72-8.21-12.2-2.74-6.48-2.74-12.95 0-14.2 10.07-25.78 10.08-11.57 26.51-11.57h322.7q19.15 0 32.33 13.77 13.17 13.77 13.17 37.23 0 16.24-13.78 23.93-13.78 7.7-26.22 16.07v215.41q0 30.59-27.97 42.4-27.96 11.82-49.59-9.81ZM434.5-78.09v-239.04H290.98q-27.39 0-44.07-19.05-16.67-19.06-16.67-43.21 0-11.72 4.62-23.32 4.62-11.59 14.86-22.07l62.15-63.59v-46.72L74.91-774.28Q63.2-786 62.93-803.1q-.26-17.1 12.22-29.57 11.72-11.72 29.2-11.72 17.48 0 29.19 11.72l690.72 690.71q12.48 12.48 12.1 29.58-.38 17.1-12.86 29.58-12.48 11.71-29.32 12.09-16.83.38-29.31-12.09L530.3-317.13l-4.8.48v238.56q0 19.16-13.17 32.33Q499.15-32.59 480-32.59t-32.33-13.17Q434.5-58.93 434.5-78.09Z"/>
</svg>
)
})
@@ -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)}
/>
@@ -1,3 +1,4 @@
import { LaunchMod, LaunchMods } from "shared/models/bs-launch/launch-option.interface";
import { BsvSearchOrder } from "shared/models/maps/beat-saver.model";
// NOTE: To refactor. Rename to LocalStorageConfigKeyValues since these are stored in the
@@ -12,6 +13,7 @@ export interface DefaultConfigKeyValues {
"default-shared-folders": string[];
"playlist-sort-order": BsvSearchOrder;
"map-sort-order": BsvSearchOrder;
"pinned-launch-mods": LaunchMod[];
};
export type DefaultConfigKey = keyof DefaultConfigKeyValues;
@@ -31,6 +33,7 @@ export const defaultConfiguration: {
],
"playlist-sort-order": BsvSearchOrder.Relevance,
"map-sort-order": BsvSearchOrder.Relevance,
"pinned-launch-mods": [LaunchMods.OCULUS, LaunchMods.DEBUG]
};
export type ThemeConfig = "dark" | "light" | "os";
+1 -11
View File
@@ -47,11 +47,8 @@ export class BSLauncherService {
public getLaunchOptions(version: BSVersion): LaunchOption{
return {
version,
oculus: this.config.get(LaunchMods.OCULUS_MOD),
desktop: this.config.get(LaunchMods.DESKTOP_MOD),
debug: this.config.get(LaunchMods.DEBUG_MOD),
launchMods: this.config.get("launch-mods") ?? [],
additionalArgs: (this.config.get<string>("additionnal-args") || "").split(";").map(arg => arg.trim()).filter(arg => arg.length > 0),
skipSteam: this.config.get(LaunchMods.SKIPSTEAM_MOD),
}
}
@@ -124,10 +121,3 @@ export class BSLauncherService {
return lastValueFrom(this.ipcService.sendV2("bs-launch.restore-steamvr"));
}
}
export enum LaunchMods {
OCULUS_MOD = "LAUNCH_OCULUS_MOD",
DESKTOP_MOD = "LAUNCH_DESKTOP_MOD",
DEBUG_MOD = "LAUNCH_DEBUG_MOD",
SKIPSTEAM_MOD = "LAUNCH_SKIPSTEAM_MOD",
}
@@ -1,11 +1,17 @@
import { BSVersion } from "shared/bs-version.interface";
export const LaunchMods = {
OCULUS: "oculus",
FPFC: "fpfc",
DEBUG: "debug",
SKIP_STEAM: "skip_steam",
} as const;
export type LaunchMod = typeof LaunchMods[keyof typeof LaunchMods];
export interface LaunchOption {
version: BSVersion,
oculus?: boolean,
desktop?: boolean,
debug?: boolean,
launchMods?: LaunchMod[],
additionalArgs?: string[],
admin?: boolean,
skipSteam?: boolean
admin?: boolean
}