mirror of
https://github.com/Zagrios/bs-manager.git
synced 2026-07-03 14:08:25 +02:00
Merge remote-tracking branch 'origin/master' into feat/702-respect-system-proxy-windows
This commit is contained in:
@@ -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,16 +25,20 @@ 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.launchMods?.includes(LaunchMods.EDITOR)) {
|
||||
launchArgs.push("editor");
|
||||
}
|
||||
|
||||
if (launchOptions.additionalArgs) {
|
||||
launchArgs.push(...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,10 +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
|
||||
additionalArgs: params.additionalArgs,
|
||||
launchMods,
|
||||
};
|
||||
|
||||
return res;
|
||||
@@ -119,10 +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.launchMods?.includes(LaunchMods.SKIP_STEAM)){ res.skipSteam = "true"; }
|
||||
|
||||
return res;
|
||||
}
|
||||
@@ -239,6 +247,7 @@ type ShortcutParams = {
|
||||
desktopMode?: string;
|
||||
debug?: string;
|
||||
additionalArgs?: string[];
|
||||
skipSteam?: string;
|
||||
version: string;
|
||||
versionName?: string;
|
||||
versionIno?: string;
|
||||
|
||||
@@ -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,8 +74,11 @@ export class SteamLauncherService extends AbstractLauncherService implements Sto
|
||||
throw CustomError.fromError(new Error(`Path not exist : ${bsExePath}`), BSLaunchError.BS_NOT_FOUND);
|
||||
}
|
||||
|
||||
const skipSteam: boolean = launchOptions.launchMods?.includes(LaunchMods.SKIP_STEAM) ?? false;
|
||||
|
||||
// Open Steam if not running
|
||||
if(!(await this.steam.isSteamRunning())){
|
||||
if(!skipSteam && !(await this.steam.isSteamRunning())){
|
||||
|
||||
obs.next({type: BSLaunchEvent.STEAM_LAUNCHING});
|
||||
|
||||
await this.steam.openSteam().then(() => {
|
||||
@@ -84,9 +88,12 @@ export class SteamLauncherService extends AbstractLauncherService implements Sto
|
||||
obs.next({type: BSLaunchWarning.UNABLE_TO_LAUNCH_STEAM});
|
||||
});
|
||||
}
|
||||
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();
|
||||
});
|
||||
|
||||
@@ -42,7 +42,7 @@ export class BSVersionLibService {
|
||||
|
||||
private async shouldLoadFromConfig(): Promise<boolean> {
|
||||
// Some special cases of readonly memory installations
|
||||
return IS_FLATPAK || this.linuxService.isNixOS();
|
||||
return process.platform === "linux" && (IS_FLATPAK || this.linuxService.isNixOS());
|
||||
}
|
||||
|
||||
private async getLocalVersions(): Promise<BSVersion[]> {
|
||||
|
||||
@@ -21,19 +21,24 @@ export class FolderLinkerService {
|
||||
private readonly installLocationService = InstallationLocationService.getInstance();
|
||||
private readonly staticConfig: StaticConfigurationService;
|
||||
|
||||
private linkingType: "junction" | "symlink" = "junction";
|
||||
// Only Windows support "junction", this is disregarded in other os'es
|
||||
private linkingType: "junction" | "symlink" =
|
||||
process.platform === "win32" ? "junction" : "symlink";
|
||||
|
||||
private constructor() {
|
||||
this.installLocationService = InstallationLocationService.getInstance();
|
||||
this.staticConfig = StaticConfigurationService.getInstance();
|
||||
|
||||
this.linkingType = this.staticConfig.get("use-symlinks") === true ? "symlink" : "junction";
|
||||
log.info(`Linking type is set to ${this.linkingType}`);
|
||||
if (process.platform === "win32") {
|
||||
// Only Windows support "junction", this is disregarded in other os'es
|
||||
this.linkingType = this.staticConfig.get("use-symlinks") === true ? "symlink" : "junction";
|
||||
log.info(`Linking type is set to ${this.linkingType}`);
|
||||
|
||||
this.staticConfig.$watch("use-symlinks").subscribe((useSymlink) => {
|
||||
this.linkingType = useSymlink === true ? "symlink" : "junction";
|
||||
log.info(`Linking type set to ${this.linkingType}`);
|
||||
});
|
||||
this.staticConfig.$watch("use-symlinks").subscribe((useSymlink) => {
|
||||
this.linkingType = useSymlink === true ? "symlink" : "junction";
|
||||
log.info(`Linking type set to ${this.linkingType}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public sharedFolder(): string {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { BsmButton, BsmButtonType } from "renderer/components/shared/bsm-button.component";
|
||||
import { BsmImage } from "renderer/components/shared/bsm-image.component";
|
||||
import { cn } from "renderer/helpers/css-class.helpers";
|
||||
import { useTranslation } from "renderer/hooks/use-translation.hook";
|
||||
import { useTranslationV2 } from "renderer/hooks/use-translation.hook";
|
||||
import { ModalComponent, ModalExitCode } from "renderer/services/modale.service";
|
||||
|
||||
type BasicModalOptions = {
|
||||
@@ -12,7 +12,11 @@ type BasicModalOptions = {
|
||||
id: string;
|
||||
text: string;
|
||||
type: BsmButtonType,
|
||||
isCancel?: boolean;
|
||||
/**
|
||||
* true - ModalExitCode.COMPLETED
|
||||
* undefined/false - ModalExitCode.CANCELED
|
||||
*/
|
||||
onClick?: () => boolean;
|
||||
}[];
|
||||
buttonsLayout?: "row" | "column";
|
||||
};
|
||||
@@ -21,20 +25,32 @@ export const BasicModal: ModalComponent<BasicModalOptions["buttons"][0]["id"], B
|
||||
data: { title, image, body, buttons, buttonsLayout = "column" }
|
||||
} }) => {
|
||||
|
||||
const t = useTranslation();
|
||||
const t = useTranslationV2();
|
||||
|
||||
const handleClick = (button: BasicModalOptions["buttons"][0]) => {
|
||||
resolver({ exitCode: button.isCancel ? ModalExitCode.CANCELED : ModalExitCode.COMPLETED, data: button.id });
|
||||
resolver({
|
||||
exitCode: button.onClick?.()
|
||||
? ModalExitCode.COMPLETED
|
||||
: ModalExitCode.CANCELED,
|
||||
data: button.id
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<form className="text-gray-900 dark:text-white">
|
||||
<h1 className="text-3xl uppercase tracking-wide w-full text-center">{t(title)}</h1>
|
||||
<h1 className="text-3xl uppercase tracking-wide w-full text-center">{t.text(title)}</h1>
|
||||
<BsmImage className="mx-auto h-24" image={image} />
|
||||
{ body && <p className="w-full">{t(body)}</p> }
|
||||
{ body && <p className="w-full">{t.text(body)}</p> }
|
||||
<div className={cn("grid gap-2 mt-4")} style={{ gridAutoFlow: buttonsLayout, ...(buttonsLayout === "row" ? { gridTemplateRows: `repeat(${buttons.length}, 1fr)` } : { gridTemplateColumns: `repeat(${buttons.length}, 1fr)` }) }}>
|
||||
{buttons.map(button => (
|
||||
<BsmButton key={button.id} typeColor={button.type} className="h-8 rounded-md text-center flex justify-center items-center" onClick={() => handleClick(button)} withBar={false} text={button.text} />
|
||||
<BsmButton
|
||||
key={button.id}
|
||||
typeColor={button.type}
|
||||
className="h-8 rounded-md text-center flex justify-center items-center"
|
||||
onClick={() => handleClick(button)}
|
||||
withBar={false}
|
||||
text={button.text}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -6,30 +6,45 @@ import { BsmButton } from "renderer/components/shared/bsm-button.component";
|
||||
import { useObservable } from "renderer/hooks/use-observable.hook";
|
||||
import { useService } from "renderer/hooks/use-service.hook";
|
||||
import { useThemeColor } from "renderer/hooks/use-theme-color.hook";
|
||||
import { useTranslation } from "renderer/hooks/use-translation.hook";
|
||||
import { useTranslationV2 } from "renderer/hooks/use-translation.hook";
|
||||
import { BSVersionManagerService } from "renderer/services/bs-version-manager.service";
|
||||
import { ConfigurationService } from "renderer/services/configuration.service";
|
||||
import { IpcService } from "renderer/services/ipc.service";
|
||||
import { ModalComponent } from "renderer/services/modale.service";
|
||||
import { ModalComponent, ModalService } from "renderer/services/modale.service";
|
||||
import { FolderLinkState, VersionFolderLinkerService, VersionLinkerActionType } from "renderer/services/version-folder-linker.service";
|
||||
import { lastValueFrom } from "rxjs";
|
||||
import { BSVersion } from "shared/bs-version.interface";
|
||||
import { NotificationService } from "renderer/services/notification.service";
|
||||
import { BasicModal } from "../basic-modal.component";
|
||||
import BeatConflict from "../../../../../assets/images/apngs/beat-conflict.png";
|
||||
|
||||
export const ShareFoldersModal: ModalComponent<void, BSVersion> = ({ options: {data} }) => {
|
||||
const SHARED_FOLDERS_KEY = "default-shared-folders";
|
||||
const SHARED_FOLDERS_KEY = "default-shared-folders";
|
||||
const SHARED_FOLDER_BLACKLIST = {
|
||||
error: [
|
||||
".DepotDownloader",
|
||||
"Beat Saber_Data",
|
||||
"IPA",
|
||||
"Libs",
|
||||
"Plugins",
|
||||
"MonoBleedingEdge",
|
||||
],
|
||||
warn: [
|
||||
"DLC",
|
||||
"Logs",
|
||||
],
|
||||
};
|
||||
|
||||
export const ShareFoldersModal: ModalComponent<void, BSVersion> = ({ options: { data: version } }) => {
|
||||
const config = useService(ConfigurationService);
|
||||
const ipc = useService(IpcService);
|
||||
const linker = useService(VersionFolderLinkerService);
|
||||
const versionManager = useService(BSVersionManagerService);
|
||||
|
||||
const t = useTranslation();
|
||||
const t = useTranslationV2();
|
||||
|
||||
const [folders, setFolders] = useState<string[]>(Array.from(new Set([...config.get<string[]>(SHARED_FOLDERS_KEY)]).values()));
|
||||
|
||||
useEffect(() => {
|
||||
linker
|
||||
.getLinkedFolders(data, { relative: true })
|
||||
.getLinkedFolders(version, { relative: true })
|
||||
.toPromise()
|
||||
.then(linkedFolders => {
|
||||
setFolders(prev => Array.from(new Set([...prev, ...linkedFolders]).values()));
|
||||
@@ -45,42 +60,24 @@ export const ShareFoldersModal: ModalComponent<void, BSVersion> = ({ options: {d
|
||||
config.set(SHARED_FOLDERS_KEY, folders);
|
||||
}, [folders]);
|
||||
|
||||
const addFolder = async () => {
|
||||
const versionPath = await lastValueFrom(versionManager.getVersionPath(data));
|
||||
const folder = await lastValueFrom(ipc.sendV2("choose-folder", {
|
||||
defaultPath: versionPath
|
||||
}));
|
||||
|
||||
if (!folder || folder.canceled || !folder.filePaths?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const relativeFolder = await lastValueFrom(ipc.sendV2("full-version-path-to-relative", { version: data, fullPath: folder.filePaths[0] }));
|
||||
|
||||
if (folders.includes(relativeFolder)) {
|
||||
return;
|
||||
}
|
||||
|
||||
setFolders(pre => [...pre, relativeFolder]);
|
||||
};
|
||||
|
||||
const removeFolder = (index: number) => {
|
||||
setFolders(prev => prev.filter((_, i) => i !== index));
|
||||
};
|
||||
|
||||
const linkAll = () => {
|
||||
folders.forEach(relativeFolder => linker.linkVersionFolder({ version: data, relativeFolder, type: VersionLinkerActionType.Link }));
|
||||
folders.forEach(relativeFolder => linker.linkVersionFolder({ version, relativeFolder, type: VersionLinkerActionType.Link }));
|
||||
};
|
||||
|
||||
return (
|
||||
<form className="w-full max-w-md ">
|
||||
<h1 className="text-3xl uppercase tracking-wide w-full text-center text-gray-800 dark:text-gray-200">{t("modals.shared-folders.title")}</h1>
|
||||
<p className="my-3">{t("modals.shared-folders.description")}</p>
|
||||
<h1 className="text-3xl uppercase tracking-wide w-full text-center text-gray-800 dark:text-gray-200">{t.text("modals.shared-folders.title")}</h1>
|
||||
<p className="my-3">{t.text("modals.shared-folders.description")}</p>
|
||||
<ul className="flex flex-col gap-1 mb-2 h-[300px] max-h-[300px] overflow-scroll scrollbar-default px-1">
|
||||
{folders.map((folder, index) => (
|
||||
<FolderItem
|
||||
key={folder}
|
||||
version={data}
|
||||
version={version}
|
||||
relativeFolder={folder}
|
||||
onDelete={() => {
|
||||
removeFolder(index);
|
||||
@@ -89,13 +86,93 @@ export const ShareFoldersModal: ModalComponent<void, BSVersion> = ({ options: {d
|
||||
))}
|
||||
</ul>
|
||||
<div className="grid grid-flow-col gap-3 grid-cols-2">
|
||||
<BsmButton icon="add" className="h-8 rounded-md flex justify-center items-center font-bold bg-light-main-color-1 dark:bg-main-color-1" iconClassName="h-6 aspect-square text-current" onClick={addFolder} withBar={false} text="modals.shared-folders.buttons.add-folder" />
|
||||
<AddFolderButton
|
||||
version={version}
|
||||
folders={folders}
|
||||
setFolders={setFolders}
|
||||
/>
|
||||
<BsmButton icon="link" className="h-8 rounded-md flex justify-center items-center font-bold" typeColor="primary" iconClassName="h-6 aspect-square text-current -rotate-45" onClick={linkAll} withBar={false} text="modals.shared-folders.buttons.link-all" />
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
function AddFolderButton({
|
||||
version,
|
||||
folders,
|
||||
setFolders,
|
||||
}: Readonly<{
|
||||
version: BSVersion;
|
||||
folders: string[];
|
||||
setFolders: (value: string[]) => void;
|
||||
}>) {
|
||||
const config = useService(ConfigurationService);
|
||||
const ipc = useService(IpcService);
|
||||
const versionManager = useService(BSVersionManagerService);
|
||||
const notification = useService(NotificationService);
|
||||
const modal = useService(ModalService);
|
||||
|
||||
const t = useTranslationV2();
|
||||
|
||||
const addFolder = async () => {
|
||||
const versionPath = await lastValueFrom(versionManager.getVersionPath(version));
|
||||
const folder = await lastValueFrom(ipc.sendV2("choose-folder", {
|
||||
defaultPath: versionPath
|
||||
}));
|
||||
|
||||
if (!folder || folder.canceled || !folder.filePaths?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const relativeFolder = await lastValueFrom(ipc.sendV2("full-version-path-to-relative", { version, fullPath: folder.filePaths[0] }));
|
||||
if (folders.includes(relativeFolder)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (SHARED_FOLDER_BLACKLIST.error.includes(relativeFolder)) {
|
||||
notification.notifyError({
|
||||
title: "notifications.shared-folder.adding-error.title",
|
||||
desc: t.text("notifications.shared-folder.adding-error.msg", {
|
||||
folder: relativeFolder
|
||||
}),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (SHARED_FOLDER_BLACKLIST.warn.includes(relativeFolder)) {
|
||||
await modal.openModal(BasicModal, { data: {
|
||||
title: "modals.adding-shared-folder.title",
|
||||
body: t.text("modals.adding-shared-folder.description", {
|
||||
folder: relativeFolder
|
||||
}),
|
||||
image: BeatConflict,
|
||||
buttons: [
|
||||
{ id: "cancel", text: "misc.cancel", type: "cancel" },
|
||||
{
|
||||
id: "confirm", text: "misc.confirm", type: "primary",
|
||||
onClick() {
|
||||
config.set(SHARED_FOLDERS_KEY, [...folders, relativeFolder]);
|
||||
return true;
|
||||
},
|
||||
}
|
||||
]
|
||||
}});
|
||||
return;
|
||||
}
|
||||
|
||||
setFolders([...folders, relativeFolder]);
|
||||
};
|
||||
|
||||
return <BsmButton
|
||||
icon="add"
|
||||
className="h-8 rounded-md flex justify-center items-center font-bold bg-light-main-color-1 dark:bg-main-color-1"
|
||||
iconClassName="h-6 aspect-square text-current"
|
||||
onClick={addFolder}
|
||||
withBar={false}
|
||||
text="modals.shared-folders.buttons.add-folder"
|
||||
/>;
|
||||
}
|
||||
|
||||
// -------- FOLDER ITEM --------
|
||||
|
||||
type FolderProps = {
|
||||
@@ -107,7 +184,7 @@ type FolderProps = {
|
||||
const FolderItem = ({ version, relativeFolder, onDelete }: FolderProps) => {
|
||||
const linker = useService(VersionFolderLinkerService);
|
||||
|
||||
const t = useTranslation();
|
||||
const t = useTranslationV2();
|
||||
|
||||
const color = useThemeColor("first-color");
|
||||
const state = useObservable(() => linker.$folderLinkedState(version, relativeFolder), FolderLinkState.Unlinked, [version, relativeFolder]);
|
||||
@@ -139,7 +216,7 @@ const FolderItem = ({ version, relativeFolder, onDelete }: FolderProps) => {
|
||||
{name}
|
||||
</span>
|
||||
<div className="flex flex-row gap-1.5">
|
||||
<Tippy placement="left" content={t(`modals.shared-folders.buttons.${state === FolderLinkState.Linked ? "unlink-folder" : "link-folder"}`)} arrow={false}>
|
||||
<Tippy placement="top" theme="default" content={t.text(`modals.shared-folders.buttons.${state === FolderLinkState.Linked ? "unlink-folder" : "link-folder"}`)}>
|
||||
<LinkButton
|
||||
className="p-0.5 h-7 shrink-0 aspect-square blur-0 cursor-pointer hover:brightness-75"
|
||||
state={state}
|
||||
@@ -164,15 +241,17 @@ const FolderItem = ({ version, relativeFolder, onDelete }: FolderProps) => {
|
||||
);
|
||||
}
|
||||
return (
|
||||
<BsmButton
|
||||
className="aspect-square h-7 rounded-md p-1"
|
||||
icon="trash"
|
||||
withBar={false}
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
onDelete?.();
|
||||
}}
|
||||
/>
|
||||
<Tippy content={t.text("modals.shared-folders.buttons.remove-from-the-list")} theme="default" hideOnClick={false} placement="top">
|
||||
<BsmButton
|
||||
className="aspect-square h-7 rounded-md p-1"
|
||||
icon="trash"
|
||||
withBar={false}
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
onDelete?.();
|
||||
}}
|
||||
/>
|
||||
</Tippy>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@ import { BSVersion } from "shared/bs-version.interface";
|
||||
import { Link, useLocation } from "react-router-dom";
|
||||
import { useState } from "react";
|
||||
import { distinctUntilChanged, lastValueFrom, map, of, Subscription, switchMap, take } from "rxjs";
|
||||
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 { BSUninstallerService } from "renderer/services/bs-uninstaller.service";
|
||||
import { BSVersionManagerService } from "renderer/services/bs-version-manager.service";
|
||||
@@ -35,7 +35,7 @@ export function BsVersionItem(props: { version: BSVersion }) {
|
||||
|
||||
useOnUpdate(() => {
|
||||
const subs: Subscription[] = []
|
||||
|
||||
|
||||
subs.push(bsDownloader.downloadingVersion$.pipe(map(download => equal(download, props.version)), distinctUntilChanged()).subscribe(isDownloading => {
|
||||
setIsDownloading(() => isDownloading);
|
||||
}));
|
||||
@@ -60,9 +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),
|
||||
launchMods: configService.get("launch-mods") ?? [],
|
||||
});
|
||||
return lastValueFrom(launch$).catch(() => {});
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ReactNode } from "react";
|
||||
import { useTranslation } from "renderer/hooks/use-translation.hook";
|
||||
import { useTranslationV2 } from "renderer/hooks/use-translation.hook";
|
||||
|
||||
type Props = {
|
||||
id?: string;
|
||||
@@ -8,11 +8,11 @@ type Props = {
|
||||
minorTitle?: string;
|
||||
description?: string;
|
||||
children?: ReactNode;
|
||||
os?: string;
|
||||
os?: "win32" | "linux";
|
||||
};
|
||||
|
||||
export function SettingContainer({ id, className, title, minorTitle, description, children, os }: Props) {
|
||||
const t = useTranslation();
|
||||
const t = useTranslationV2();
|
||||
|
||||
if (os && os !== window.electron.platform) {
|
||||
return undefined;
|
||||
@@ -20,9 +20,9 @@ export function SettingContainer({ id, className, title, minorTitle, description
|
||||
|
||||
return (
|
||||
<div id={id} className={className || "relative mb-5"}>
|
||||
{title && <h1 className="mb-1 text-2xl font-bold tracking-wide">{t(title)}</h1>}
|
||||
{minorTitle && <h2 className="mb-1 font-bold tracking-wide text-gray-600 dark:text-gray-300">{t(minorTitle)}</h2>}
|
||||
{description && <p className="mb-3 text-sm text-gray-600 dark:text-gray-400">{t(description)}</p>}
|
||||
{title && <h1 className="mb-1 text-2xl font-bold tracking-wide">{t.text(title)}</h1>}
|
||||
{minorTitle && <h2 className="mb-1 font-bold tracking-wide text-gray-600 dark:text-gray-300">{t.text(minorTitle)}</h2>}
|
||||
{description && <p className="mb-3 text-sm text-gray-600 dark:text-gray-400">{t.text(description)}</p>}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ToogleSwitch } from "../shared/toogle-switch.component";
|
||||
|
||||
type Item = {
|
||||
export type Item = {
|
||||
text: string;
|
||||
desc?: string;
|
||||
checked?: boolean;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { motion } from "framer-motion";
|
||||
import { useThemeColor } from "renderer/hooks/use-theme-color.hook";
|
||||
import { BsmIcon } from "../svgs/bsm-icon.component";
|
||||
import { useTranslation } from "renderer/hooks/use-translation.hook";
|
||||
import { useTranslationV2 } from "renderer/hooks/use-translation.hook";
|
||||
import { FolderLinkState } from "renderer/services/version-folder-linker.service";
|
||||
import React from "react";
|
||||
import React, { forwardRef } from "react";
|
||||
|
||||
export type LinkBtnProps = {
|
||||
className?: string;
|
||||
@@ -12,8 +12,8 @@ export type LinkBtnProps = {
|
||||
onClick?: () => unknown;
|
||||
};
|
||||
|
||||
export function LinkButton({className, title, state, onClick}: LinkBtnProps) {
|
||||
const t = useTranslation();
|
||||
export const LinkButton = forwardRef<HTMLDivElement, LinkBtnProps>(({className, title, state, onClick}, forwardedRef) => {
|
||||
const { text: t } = useTranslationV2();
|
||||
|
||||
const color = useThemeColor("first-color");
|
||||
const disabled = state === FolderLinkState.Processing || state === FolderLinkState.Pending;
|
||||
@@ -41,6 +41,7 @@ export function LinkButton({className, title, state, onClick}: LinkBtnProps) {
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
ref={forwardedRef}
|
||||
variants={{ hover: { rotate: 22.5 }, tap: { rotate: 45 } }}
|
||||
whileHover="hover"
|
||||
whileTap="tap"
|
||||
@@ -55,4 +56,4 @@ export function LinkButton({className, title, state, onClick}: LinkBtnProps) {
|
||||
<BsmIcon className="p-1 absolute top-0 left-0 h-full w-full !bg-transparent -rotate-45 brightness-150" icon={state === FolderLinkState.Linked ? "link" : "unlink"} style={{ color: btnColor() }} />
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -16,7 +16,7 @@ import { TaskIcon } from "./icons/task-icon.component";
|
||||
import { CopyIcon } from "./icons/copy-icon.component";
|
||||
import { SteamIcon } from "./icons/steam-icon.component";
|
||||
import { CSSProperties, memo } from "react";
|
||||
import EditIcon from "./icons/edit-icon.component";
|
||||
import { EditIcon } from "./icons/edit-icon.component";
|
||||
import { ExportIcon } from "./icons/export-icon.component";
|
||||
import PatreonIcon from "./icons/patreon-icon.component";
|
||||
import { SearchIcon } from "./icons/search-icon.component";
|
||||
@@ -69,7 +69,8 @@ import { CancelIcon } from "./icons/cancel-icon.component";
|
||||
import { WarningIcon } from "./icons/warning-icon.component";
|
||||
|
||||
|
||||
export type BsmIconType = SongDetailDiffCharactertistic | ("settings" | "trash" | "favorite" | "folder" | "bsNote" | "check" | "three-dots" | "twitch" | "eye" | "play" | "checkCircleIcon" | "discord" | "info" | "eye-cross" | "terminal" | "desktop" | "oculus" | "add" | "cross" | "task" | "github" | "close" | "thumbUpFill" | "timerFill" | "pause" | "twitter" | "sync" | "chevron-top" | "copy" | "steam" | "edit" | "export" | "patreon" | "search" | "bsMapDifficulty" | "link" | "unlink" | "download" | "filter" | "mee6" | "volume-up" | "volume-off" | "volume-down" | "shortcut" | "backup-restore" | "web-site" | "clean" | "browse" | "add-file" | "cancel" | "warning" | "fr-FR-flag" | "es-ES-flag" | "en-US-flag" | "en-EN-flag" | "de-DE-flag" | "ru-RU-flag" | "zh-CN-flag" | "zh-TW-flag" | "ja-JP-flag" | "ko-KR-flag");
|
||||
|
||||
export type BsmIconType = SongDetailDiffCharactertistic | ("settings" | "trash" | "favorite" | "folder" | "bsNote" | "check" | "three-dots" | "twitch" | "eye" | "play" | "checkCircleIcon" | "discord" | "info" | "eye-cross" | "terminal" | "desktop" | "oculus" | "add" | "cross" | "task" | "github" | "close" | "thumbUpFill" | "timerFill" | "pause" | "twitter" | "sync" | "chevron-top" | "copy" | "steam" | "edit" | "export" | "patreon" | "search" | "bsMapDifficulty" | "link" | "unlink" | "download" | "filter" | "mee6" | "volume-up" | "volume-off" | "volume-down" | "shortcut" | "backup-restore" | "web-site" | "clean" | "browse" | "add-file" | "cancel" | "warning" | "fr-FR-flag" | "es-ES-flag" | "en-US-flag" | "en-EN-flag" | "de-DE-flag" | "ru-RU-flag" | "zh-CN-flag" | "zh-TW-flag" | "ja-JP-flag" | "ko-KR-flag" | "null" );
|
||||
|
||||
export const BsmIcon = memo(({ className, icon, style }: { className?: string; icon: BsmIconType; style?: CSSProperties }) => {
|
||||
// TODO : Very ugly very messy, need to find a better way to do this
|
||||
@@ -277,13 +278,14 @@ export const BsmIcon = memo(({ className, icon, style }: { className?: string; i
|
||||
if(icon === "add-file") {
|
||||
return <AddFileIcon className={className} style={style} />;
|
||||
}
|
||||
|
||||
if(icon === "cancel"){
|
||||
if(icon === "null") {
|
||||
return null;
|
||||
}
|
||||
if(icon === "cancel") {
|
||||
return <CancelIcon className={className} style={style} />;
|
||||
}
|
||||
|
||||
if(icon === "warning"){
|
||||
return <WarningIcon className={className} style={style} />;
|
||||
if(icon === "warning") {
|
||||
return <WarningIcon className={className} style={style} />
|
||||
}
|
||||
|
||||
return <TrashIcon className={className} style={style} />;
|
||||
|
||||
@@ -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,9 +1,9 @@
|
||||
import { CSSProperties } from "react";
|
||||
import { createSvgIcon } from "../svg-icon.type";
|
||||
|
||||
export default function EditIcon(props: { className?: string; style?: CSSProperties }) {
|
||||
export const EditIcon = 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="M7.833 32.25h1.875L26.75 15.208l-1.875-1.875L7.833 30.375Zm24.959-19.042-5.959-5.916 1.917-1.917q.833-.833 2.042-.833 1.208 0 2.083.875l1.833 1.833q.875.833.854 2.021-.02 1.187-.854 2.021ZM6.542 35.125q-.667 0-1.125-.458-.459-.459-.459-1.125v-3.709q0-.333.104-.604.105-.271.355-.521L24.875 9.25l5.958 5.958-19.458 19.459q-.25.25-.542.354-.291.104-.583.104Zm19.25-20.833-.917-.959 1.875 1.875Z" />
|
||||
</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>
|
||||
)
|
||||
})
|
||||
+9
-8
@@ -1,33 +1,34 @@
|
||||
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)}>
|
||||
<button className={`shrink-0 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 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]}>
|
||||
<div className="h-[25px] w-[25px] p-1.5 rounded-full cursor-help bg-light-main-color-1 dark:bg-main-color-3 hover:brightness-110">
|
||||
<Tippy content={t(infoText)} placement="top" theme="default" delay={[400, 0]}>
|
||||
<div className="h-[25px] w-[25px] shrink-0 p-1.5 rounded-full cursor-help bg-light-main-color-1 dark:bg-main-color-3 hover:brightness-110">
|
||||
<BsmIcon className="w-full h-full" icon="info" />
|
||||
</div>
|
||||
</Tippy>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
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={cn("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: Icon, label, description, active, visible, pinned, onChange, onPinChange }: LaunchModItemProps) {
|
||||
|
||||
const { text: t } = useTranslationV2();
|
||||
|
||||
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 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 mr-1.5" checked={active} onChange={onChange}/>
|
||||
{Icon && <Icon className="h-full w-fit py-0.5 mr-1.5"/>}
|
||||
<span className="font-bold">{label}</span>
|
||||
{onPinChange && (
|
||||
<Tippy theme="default" placement="right" content={pinned ? t("misc.unpin") : t("misc.pin")} hideOnClick>
|
||||
<button className="h-full py-2 px-1.5" 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,13 @@ 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";
|
||||
import { EditIcon } from "renderer/components/svgs/icons/edit-icon.component";
|
||||
|
||||
type Props = { version: BSVersion };
|
||||
|
||||
@@ -30,12 +37,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 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$);
|
||||
|
||||
@@ -44,32 +51,80 @@ 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);
|
||||
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)),
|
||||
},
|
||||
{
|
||||
id: LaunchMods.EDITOR,
|
||||
icon: EditIcon,
|
||||
label: t("pages.version-viewer.launch-mods.map-editor"),
|
||||
description: t("pages.version-viewer.launch-mods.map-editor-description"),
|
||||
active: activeLaunchMods.includes(LaunchMods.EDITOR),
|
||||
pinned: pinnedLaunchMods.includes(LaunchMods.EDITOR),
|
||||
visible: !safeLt(version.BSVersion, "1.23.0"),
|
||||
onChange: checked => checked ? setActiveLaunchMods(prev => [...prev, LaunchMods.EDITOR]) : setActiveLaunchMods(prev => prev.filter(mod => mod !== LaunchMods.EDITOR)),
|
||||
onPinChange: pinned => pinned ? setPinnedLaunchMods(prev => [...prev, LaunchMods.EDITOR]) : setPinnedLaunchMods(prev => prev.filter(mod => mod !== LaunchMods.EDITOR)),
|
||||
|
||||
}
|
||||
]);
|
||||
}, [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 : [],
|
||||
});
|
||||
|
||||
@@ -81,9 +136,11 @@ export function LaunchSlide({ version }: Props) {
|
||||
}, [version]);
|
||||
|
||||
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="w-full shrink-0 items-center relative flex flex-col justify-start overflow-hidden">
|
||||
<div className="flex flex-col gap-3 justify-center items-center mb-4">
|
||||
<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 && (
|
||||
@@ -95,14 +152,23 @@ 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" />
|
||||
<div className="w-full flex justify-center items-center gap-4 flex-wrap scrollbar-default">
|
||||
{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="mt-4 flex flex-col items-center justify-center gap-3">
|
||||
<div className="relative">
|
||||
<GlowEffect className="!rounded-full" visible={!!(advancedLaunch && additionalArgsString)}/>
|
||||
<GlowEffect className="!rounded-full" visible={!!(activeLaunchMods?.length || additionalArgsString)}/>
|
||||
<BsmButton
|
||||
className={cn("rounded-full w-fit text-lg py-1 px-7 bg-theme-2 text-gray-800 dark:text-white", (advancedLaunch && additionalArgsString) ? "" : "shadow-md shadow-black")}
|
||||
text="pages.version-viewer.launch-mods.advanced-launch.button"
|
||||
@@ -113,15 +179,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>
|
||||
<div className='grow flex justify-center items-center'>
|
||||
<LaunchOptionsPanel open={advancedLaunch} launchMods={launchModItems} launchArgs={additionalArgsString} 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,8 @@ 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";
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ import Tippy from "@tippyjs/react";
|
||||
import { MapsManagerService } from "renderer/services/maps-manager.service";
|
||||
import { PlaylistsManagerService } from "renderer/services/playlists-manager.service";
|
||||
import { ModelsManagerService } from "renderer/services/models-management/models-manager.service";
|
||||
import { useTranslation } from "renderer/hooks/use-translation.hook";
|
||||
import { useTranslation, useTranslationV2 } from "renderer/hooks/use-translation.hook";
|
||||
import { VersionFolderLinkerService } from "renderer/services/version-folder-linker.service";
|
||||
import { useService } from "renderer/hooks/use-service.hook";
|
||||
import { lastValueFrom } from "rxjs";
|
||||
@@ -40,7 +40,7 @@ import { SteamIcon } from "renderer/components/svgs/icons/steam-icon.component";
|
||||
import { OculusIcon } from "renderer/components/svgs/icons/oculus-icon.component";
|
||||
import { BsDownloaderService } from "renderer/services/bs-version-download/bs-downloader.service";
|
||||
import BeatConflict from "../../../assets/images/apngs/beat-conflict.png";
|
||||
import { SettingToogleSwitchGrid } from "renderer/components/settings/setting-toogle-switch-grid.component";
|
||||
import { Item, SettingToogleSwitchGrid } from "renderer/components/settings/setting-toogle-switch-grid.component";
|
||||
import { BasicModal } from "renderer/components/modal/basic-modal.component";
|
||||
import { StaticConfigurationService } from "renderer/services/static-configuration.service";
|
||||
import { tryit } from "shared/helpers/error.helpers";
|
||||
@@ -96,8 +96,6 @@ export function SettingsPage() {
|
||||
const [playlistsDeepLinkEnabled, setPlaylistsDeepLinkEnabled] = useState(false);
|
||||
const [modelsDeepLinkEnabled, setModelsDeepLinkEnabled] = useState(false);
|
||||
const [hasDownloaderSession, setHasDownloaderSession] = useState(false);
|
||||
const [hardwareAccelerationEnabled, setHardwareAccelerationEnabled] = useState(true);
|
||||
const [useSymlink, setUseSymlink] = useState(false);
|
||||
const appVersion = useObservable(() => autoUpdater.getAppVersion());
|
||||
|
||||
useEffect(() => {
|
||||
@@ -107,8 +105,6 @@ export function SettingsPage() {
|
||||
playlistsManager.isDeepLinksEnabled().then(enabled => setPlaylistsDeepLinkEnabled(() => enabled));
|
||||
modelsManager.isDeepLinksEnabled().then(enabled => setModelsDeepLinkEnabled(() => enabled));
|
||||
|
||||
staticConfig.get("disable-hadware-acceleration").then(disabled =>setHardwareAccelerationEnabled(() => disabled !== true));
|
||||
staticConfig.get("use-symlinks").then(useSymlinks => setUseSymlink(() => useSymlinks));
|
||||
staticConfig.get("proton-folder").then(setProtonFolder);
|
||||
}, []);
|
||||
|
||||
@@ -233,66 +229,6 @@ export function SettingsPage() {
|
||||
});
|
||||
};
|
||||
|
||||
const onChangeHardwareAcceleration = async (newHardwareAccelerationEnabled: boolean) => {
|
||||
if(newHardwareAccelerationEnabled === hardwareAccelerationEnabled){ return; }
|
||||
|
||||
const res = await modalService.openModal(BasicModal, { data: {
|
||||
title: "pages.settings.advanced.hardware-acceleration.modal.title",
|
||||
body: "pages.settings.advanced.hardware-acceleration.modal.body",
|
||||
image: BeatConflict,
|
||||
buttons: [
|
||||
{ id: "cancel", text: "misc.cancel", type: "cancel", isCancel: true },
|
||||
{ id: "confirm", text: "pages.settings.advanced.hardware-acceleration.modal.confirm-btn", type: "error" }
|
||||
]
|
||||
}});
|
||||
|
||||
if(res.exitCode !== ModalExitCode.COMPLETED || res.data !== "confirm"){ return; }
|
||||
|
||||
const { error } = await tryit(() => staticConfig.set("disable-hadware-acceleration", !newHardwareAccelerationEnabled));
|
||||
|
||||
if(error){
|
||||
notificationService.notifyError({ title: "notifications.types.error", desc: "pages.settings.advanced.hardware-acceleration.error-notification.message" });
|
||||
setHardwareAccelerationEnabled(() => !newHardwareAccelerationEnabled);
|
||||
return;
|
||||
}
|
||||
|
||||
setHardwareAccelerationEnabled(() => newHardwareAccelerationEnabled);
|
||||
|
||||
if(!progressBarService.require()){
|
||||
return;
|
||||
}
|
||||
|
||||
await lastValueFrom(ipcService.sendV2("restart-app"));
|
||||
};
|
||||
|
||||
const onChangeUseSymlinks = async (newUseSymlink: boolean) => {
|
||||
|
||||
if(newUseSymlink === useSymlink){ return; }
|
||||
|
||||
if(newUseSymlink){
|
||||
const res = await modalService.openModal(BasicModal, { data: {
|
||||
title: "pages.settings.advanced.use-symlinks.modal.title",
|
||||
body: "pages.settings.advanced.use-symlinks.modal.body",
|
||||
image: BeatConflict,
|
||||
buttons: [
|
||||
{ id: "cancel", text: "misc.cancel", type: "cancel", isCancel: true },
|
||||
{ id: "confirm", text: "pages.settings.advanced.use-symlinks.modal.confirm-btn", type: "error" }
|
||||
]
|
||||
}});
|
||||
|
||||
if(res.exitCode !== ModalExitCode.COMPLETED || res.data !== "confirm"){ return; }
|
||||
}
|
||||
|
||||
const { error } = await tryit(() => staticConfig.set("use-symlinks", newUseSymlink));
|
||||
|
||||
if(error){
|
||||
notificationService.notifyError({ title: "notifications.types.error", desc: "pages.settings.advanced.use-symlinks.error-notification.message" });
|
||||
return;
|
||||
}
|
||||
|
||||
setUseSymlink(() => newUseSymlink);
|
||||
}
|
||||
|
||||
const toogleShowSupporters = () => {
|
||||
setShowSupporters(show => !show);
|
||||
};
|
||||
@@ -585,12 +521,7 @@ export function SettingsPage() {
|
||||
</SettingContainer>
|
||||
</SettingContainer>
|
||||
|
||||
<SettingContainer title="pages.settings.advanced.title" description="pages.settings.advanced.description">
|
||||
<SettingToogleSwitchGrid items={[
|
||||
{ checked: hardwareAccelerationEnabled, text: t("pages.settings.advanced.hardware-acceleration.title"), desc: t("pages.settings.advanced.hardware-acceleration.description"), onChange: onChangeHardwareAcceleration },
|
||||
{ checked: useSymlink, text: t("pages.settings.advanced.use-symlinks.title"), desc: t("pages.settings.advanced.use-symlinks.description"), onChange: onChangeUseSymlinks },
|
||||
]}/>
|
||||
</SettingContainer>
|
||||
<AdvancedSettings />
|
||||
|
||||
<span className="bg-light-main-color-1 dark:bg-main-color-1 rounded-md py-1 px-2 font-bold float-right mb-5">v{appVersion}</span>
|
||||
</div>
|
||||
@@ -598,3 +529,104 @@ export function SettingsPage() {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function AdvancedSettings() {
|
||||
const ipc = useService(IpcService);
|
||||
const modal = useService(ModalService);
|
||||
const notification = useService(NotificationService);
|
||||
const progressBar = useService(ProgressBarService);
|
||||
const staticConfig = useService(StaticConfigurationService);
|
||||
|
||||
const t = useTranslationV2();
|
||||
|
||||
const [hardwareAccelerationEnabled, setHardwareAccelerationEnabled] = useState(true);
|
||||
const [useSymlink, setUseSymlink] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
staticConfig.get("disable-hadware-acceleration").then(disabled =>setHardwareAccelerationEnabled(() => disabled !== true));
|
||||
staticConfig.get("use-symlinks").then(useSymlinks => setUseSymlink(() => useSymlinks));
|
||||
}, []);
|
||||
|
||||
const onChangeHardwareAcceleration = async (newHardwareAccelerationEnabled: boolean) => {
|
||||
if(newHardwareAccelerationEnabled === hardwareAccelerationEnabled){ return; }
|
||||
|
||||
const res = await modal.openModal(BasicModal, { data: {
|
||||
title: "pages.settings.advanced.hardware-acceleration.modal.title",
|
||||
body: "pages.settings.advanced.hardware-acceleration.modal.body",
|
||||
image: BeatConflict,
|
||||
buttons: [
|
||||
{ id: "cancel", text: "misc.cancel", type: "cancel" },
|
||||
{ id: "confirm", text: "pages.settings.advanced.hardware-acceleration.modal.confirm-btn", type: "error", onClick: () => true },
|
||||
]
|
||||
}});
|
||||
|
||||
if(res.exitCode !== ModalExitCode.COMPLETED || res.data !== "confirm"){ return; }
|
||||
|
||||
const { error } = await tryit(() => staticConfig.set("disable-hadware-acceleration", !newHardwareAccelerationEnabled));
|
||||
|
||||
if(error){
|
||||
notification.notifyError({ title: "notifications.types.error", desc: "pages.settings.advanced.hardware-acceleration.error-notification.message" });
|
||||
setHardwareAccelerationEnabled(() => !newHardwareAccelerationEnabled);
|
||||
return;
|
||||
}
|
||||
|
||||
setHardwareAccelerationEnabled(() => newHardwareAccelerationEnabled);
|
||||
|
||||
if(!progressBar.require()){
|
||||
return;
|
||||
}
|
||||
|
||||
await lastValueFrom(ipc.sendV2("restart-app"));
|
||||
};
|
||||
|
||||
const onChangeUseSymlinks = async (newUseSymlink: boolean) => {
|
||||
|
||||
if (window.electron.platform !== "win32" || newUseSymlink === useSymlink) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(newUseSymlink){
|
||||
const res = await modal.openModal(BasicModal, { data: {
|
||||
title: "pages.settings.advanced.use-symlinks.modal.title",
|
||||
body: "pages.settings.advanced.use-symlinks.modal.body",
|
||||
image: BeatConflict,
|
||||
buttons: [
|
||||
{ id: "cancel", text: "misc.cancel", type: "cancel" },
|
||||
{ id: "confirm", text: "pages.settings.advanced.use-symlinks.modal.confirm-btn", type: "error", onClick: () => true }
|
||||
]
|
||||
}});
|
||||
|
||||
if(res.exitCode !== ModalExitCode.COMPLETED || res.data !== "confirm"){ return; }
|
||||
}
|
||||
|
||||
const { error } = await tryit(() => staticConfig.set("use-symlinks", newUseSymlink));
|
||||
|
||||
if(error){
|
||||
notification.notifyError({ title: "notifications.types.error", desc: "pages.settings.advanced.use-symlinks.error-notification.message" });
|
||||
return;
|
||||
}
|
||||
|
||||
setUseSymlink(() => newUseSymlink);
|
||||
}
|
||||
|
||||
const advancedItems: Item[] = [{
|
||||
checked: hardwareAccelerationEnabled,
|
||||
text: t.text("pages.settings.advanced.hardware-acceleration.title"),
|
||||
desc: t.text("pages.settings.advanced.hardware-acceleration.description"),
|
||||
onChange: onChangeHardwareAcceleration
|
||||
}];
|
||||
if (window.electron.platform === "win32") {
|
||||
advancedItems.push({
|
||||
checked: useSymlink,
|
||||
text: t.text("pages.settings.advanced.use-symlinks.title"),
|
||||
desc: t.text("pages.settings.advanced.use-symlinks.description"),
|
||||
onChange: onChangeUseSymlinks
|
||||
});
|
||||
}
|
||||
|
||||
return <SettingContainer title="pages.settings.advanced.title" description="pages.settings.advanced.description">
|
||||
<SettingToogleSwitchGrid items={advancedItems}/>
|
||||
</SettingContainer>
|
||||
|
||||
}
|
||||
|
||||
|
||||
Vendored
+1
-1
@@ -3,7 +3,7 @@ import { webUtils } from "electron";
|
||||
declare global {
|
||||
interface Window {
|
||||
electron: {
|
||||
platform: "win32"|"linux"|"darwin",
|
||||
platform: "win32" | "linux",
|
||||
ipcRenderer: {
|
||||
sendMessage(channel: string, args: any): void;
|
||||
on(channel: string, func: (...args: any) => void): (() => void) | undefined;
|
||||
|
||||
@@ -47,10 +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),
|
||||
additionalArgs: (this.config.get<string>("additionnal-args") || "").split(";").map(arg => arg.trim()).filter(arg => arg.length > 0)
|
||||
launchMods: this.config.get("launch-mods") ?? [],
|
||||
additionalArgs: (this.config.get<string>("additionnal-args") || "").split(";").map(arg => arg.trim()).filter(arg => arg.length > 0),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,9 +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",
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ export enum BSLaunchError{
|
||||
export enum BSLaunchEvent{
|
||||
STEAM_LAUNCHING = "STEAM_LAUNCHING",
|
||||
STEAM_LAUNCHED = "STEAM_LAUNCHED",
|
||||
SKIPPING_STEAM_LAUNCH = "SKIPPING_STEAM_LAUNCH",
|
||||
BS_LAUNCHING = "BS_LAUNCHING",
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
import { BSVersion } from "shared/bs-version.interface";
|
||||
|
||||
export const LaunchMods = {
|
||||
OCULUS: "oculus",
|
||||
FPFC: "fpfc",
|
||||
DEBUG: "debug",
|
||||
SKIP_STEAM: "skip_steam",
|
||||
EDITOR: "editor",
|
||||
} 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,
|
||||
admin?: boolean
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user