mirror of
https://github.com/Zagrios/bs-manager.git
synced 2026-07-03 14:08:25 +02:00
add possibility to edit and clone versions
This commit is contained in:
@@ -9,6 +9,7 @@ import { BS_APP_ID } from '../constants';
|
||||
import { IpcRequest } from 'shared/models/ipc';
|
||||
import { BSLocalVersionService } from '../services/bs-local-version.service';
|
||||
import { InstallationLocationService } from '../services/installation-location.service';
|
||||
import { BsmException } from 'shared/models/bsm-exception.model';
|
||||
|
||||
ipcMain.on('bs-version.get-version-dict', (event, req: IpcRequest<void>) => {
|
||||
BSVersionLibService.getInstance().getAvailableVersions().then(versions => {
|
||||
@@ -31,3 +32,23 @@ ipcMain.on("bs-version.open-folder", async (event, req: IpcRequest<BSVersion>) =
|
||||
const versionFolder = req.args.steam ? await SteamService.getInstance().getGameFolder(BS_APP_ID, "Beat Saber") : path.join(locationService.versionsDirectory, req.args.BSVersion);
|
||||
UtilsService.getInstance().folderExist(versionFolder) && exec(`start "" "${versionFolder}"`);
|
||||
});
|
||||
|
||||
ipcMain.on("bs-version.rename", async (event, req: IpcRequest<{version: BSVersion, name: string, color: string}>) => {
|
||||
BSLocalVersionService.getInstance().editVersion(req.args.version, req.args.name, req.args.color).then(res => {
|
||||
console.log(res);
|
||||
UtilsService.getInstance().ipcSend(req.responceChannel, {success: !!res, data: res});
|
||||
}).catch((error: BsmException) => {
|
||||
UtilsService.getInstance().ipcSend(req.responceChannel, {success: false, error});
|
||||
console.log(error);
|
||||
});
|
||||
});
|
||||
|
||||
ipcMain.on("bs-version.clone", async (event, req: IpcRequest<{version: BSVersion, name: string, color: string}>) => {
|
||||
BSLocalVersionService.getInstance().cloneVersion(req.args.version, req.args.name, req.args.color).then(res => {
|
||||
console.log(res);
|
||||
UtilsService.getInstance().ipcSend(req.responceChannel, {success: !!res, data: res});
|
||||
}).catch((error: BsmException) => {
|
||||
UtilsService.getInstance().ipcSend(req.responceChannel, {success: false, error});
|
||||
console.log(error);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@ import { ChildProcessWithoutNullStreams, spawn } from "child_process";
|
||||
import log from "electron-log";
|
||||
import { InstallationLocationService } from "./installation-location.service";
|
||||
import { ctrlc } from "ctrlc-windows";
|
||||
import { BSLocalVersionService } from "./bs-local-version.service";
|
||||
|
||||
export class BSInstallerService{
|
||||
|
||||
@@ -15,6 +16,7 @@ export class BSInstallerService{
|
||||
private readonly utils: UtilsService;
|
||||
private readonly bsVersionService: BSVersionLibService;
|
||||
private readonly installLocationService: InstallationLocationService;
|
||||
private readonly localVersionService: BSLocalVersionService;
|
||||
|
||||
private downloadProcess: ChildProcessWithoutNullStreams;
|
||||
|
||||
@@ -22,6 +24,7 @@ export class BSInstallerService{
|
||||
this.bsVersionService = BSVersionLibService.getInstance();
|
||||
this.utils = UtilsService.getInstance();
|
||||
this.installLocationService = InstallationLocationService.getInstance();
|
||||
this.localVersionService = BSLocalVersionService.getInstance();
|
||||
}
|
||||
|
||||
public static getInstance(){
|
||||
@@ -58,7 +61,7 @@ export class BSInstallerService{
|
||||
|
||||
public async downloadBsVersion(downloadInfos: DownloadInfo): Promise<DownloadEvent>{
|
||||
if(this.downloadProcess && !this.downloadProcess?.killed){ console.log("*** AlreadyDownloading ***"); return {type: "[AlreadyDownloading]"}; }
|
||||
const bsVersion = this.bsVersionService.getVersionDetails(downloadInfos.bsVersion.BSVersion);
|
||||
const bsVersion = downloadInfos.bsVersion;
|
||||
if(!bsVersion){ return {type: "[Error]"}; }
|
||||
|
||||
this.utils.createFolderIfNotExist(this.installLocationService.versionsDirectory);
|
||||
@@ -69,7 +72,7 @@ export class BSInstallerService{
|
||||
`-depot ${BS_DEPOT}`,
|
||||
`-manifest ${bsVersion.BSManifest}`,
|
||||
`-username ${downloadInfos.username}`,
|
||||
`-dir ${bsVersion.BSVersion}`,
|
||||
`-dir \"${this.localVersionService.getVersionFolder(bsVersion)}\"`,
|
||||
(downloadInfos.stay || !downloadInfos.password) && "-remember-password"
|
||||
],
|
||||
{shell: true, cwd: this.installLocationService.versionsDirectory}
|
||||
|
||||
@@ -7,15 +7,22 @@ import { BS_APP_ID } from "../constants";
|
||||
import path from "path";
|
||||
import { createInterface } from "readline";
|
||||
import { createReadStream } from "fs";
|
||||
import fs from "fs-extra";
|
||||
import { ConfigurationService } from "./configuration.service";
|
||||
import { rename } from "fs/promises";
|
||||
import { BsmException } from "shared/models/bsm-exception.model";
|
||||
|
||||
export class BSLocalVersionService{
|
||||
|
||||
private static instance: BSLocalVersionService;
|
||||
|
||||
private readonly CUSTOM_VERSIONS_KEY = "custom-versions";
|
||||
|
||||
private readonly installLocationService: InstallationLocationService;
|
||||
private readonly utilsService: UtilsService;
|
||||
private readonly steamService: SteamService;
|
||||
private readonly remoteVersionService: BSVersionLibService;
|
||||
private readonly configService: ConfigurationService;
|
||||
|
||||
public static getInstance(): BSLocalVersionService{
|
||||
if(!BSLocalVersionService.instance){ BSLocalVersionService.instance = new BSLocalVersionService(); }
|
||||
@@ -27,6 +34,7 @@ export class BSLocalVersionService{
|
||||
this.utilsService = UtilsService.getInstance();
|
||||
this.steamService = SteamService.getInstance();
|
||||
this.remoteVersionService = BSVersionLibService.getInstance();
|
||||
this.configService = ConfigurationService.getInstance();
|
||||
}
|
||||
|
||||
private async getVersionOfBSFolder(bsPath: string): Promise<string>{
|
||||
@@ -48,6 +56,40 @@ export class BSLocalVersionService{
|
||||
})
|
||||
}
|
||||
|
||||
private setCustomVersions(versions: BSVersion[]): void{
|
||||
this.configService.set(this.CUSTOM_VERSIONS_KEY, versions);
|
||||
}
|
||||
|
||||
private addCustomVersion(version: BSVersion): void{
|
||||
this.setCustomVersions([...this.getCustomVersions() ?? [], version]);
|
||||
}
|
||||
|
||||
private getCustomVersions(): BSVersion[]{
|
||||
return this.configService.get<BSVersion[]>(this.CUSTOM_VERSIONS_KEY);
|
||||
}
|
||||
|
||||
private deleteCustomVersion(version: BSVersion): void{
|
||||
const customVersions = this.getCustomVersions() || [];
|
||||
this.setCustomVersions(customVersions.filter(v => (v.name !== version.name || v.BSVersion !== version.BSVersion || v.color !== version.color)));
|
||||
}
|
||||
|
||||
private async getVersionPath(version: BSVersion): Promise<string>{
|
||||
if(version.steam){ return await this.steamService.getGameFolder(BS_APP_ID, "Beat Saber") }
|
||||
return path.join(
|
||||
this.installLocationService.versionsDirectory,
|
||||
this.getVersionFolder(version)
|
||||
);
|
||||
}
|
||||
|
||||
private removeSpecialChar(seq: string): string{
|
||||
return seq.replace( /[<>:"\/\\|?*]+/g, '' );
|
||||
}
|
||||
|
||||
public getVersionFolder(version: BSVersion){
|
||||
console.log(version.name ? `${version.BSVersion}-${version.name}` : version.BSVersion)
|
||||
return version.name ? `${version.BSVersion}-${version.name}` : version.BSVersion;
|
||||
}
|
||||
|
||||
public async getInstalledVersions(): Promise<BSVersion[]>{
|
||||
const versions: BSVersion[] = [];
|
||||
const steamBsFolder = await this.steamService.getGameFolder(BS_APP_ID, "Beat Saber")
|
||||
@@ -64,15 +106,24 @@ export class BSLocalVersionService{
|
||||
const folderInInstallation = this.utilsService.listDirsInDir(this.installLocationService.versionsDirectory);
|
||||
|
||||
folderInInstallation.forEach(f => {
|
||||
const version = this.remoteVersionService.getVersionDetails(f);
|
||||
versions.push(version);
|
||||
})
|
||||
let version = this.remoteVersionService.getVersionDetails(f);
|
||||
if(version){ version = this.getCustomVersions().find(v => v.BSVersion === version.BSVersion && v.name === version.name) ?? version; }
|
||||
else { version = this.getCustomVersions().find(v => {
|
||||
const [version, ...rest] = f.split("-");
|
||||
if(rest.length < 1){ return false; }
|
||||
const name = rest.join("-");
|
||||
return name === v.name && version === v.BSVersion;
|
||||
})
|
||||
}
|
||||
version && versions.push(version);
|
||||
});
|
||||
this.setCustomVersions(versions.filter(v => !!v.name || !!v.color));
|
||||
return versions;
|
||||
}
|
||||
|
||||
public async deleteVersion(version: BSVersion): Promise<boolean>{
|
||||
if(version.steam){ return false; }
|
||||
const versionFolder = path.join(this.installLocationService.versionsDirectory, version.BSVersion);
|
||||
const versionFolder = await this.getVersionPath(version);
|
||||
if(!this.utilsService.folderExist(versionFolder)){ return true; }
|
||||
|
||||
return this.utilsService.deleteFolder(versionFolder)
|
||||
@@ -80,8 +131,51 @@ export class BSLocalVersionService{
|
||||
.catch(() => { return false; })
|
||||
}
|
||||
|
||||
public cloneVersion(version: BSVersion, name: string): boolean{
|
||||
return true;
|
||||
public async editVersion(version: BSVersion, name: string, color: string): Promise<BSVersion>{
|
||||
if(version.steam){ throw {title: "CantEditSteam"} as BsmException; }
|
||||
const oldPath = await this.getVersionPath(version);
|
||||
const editedVersion: BSVersion = version.BSVersion === name
|
||||
? {...version, name: undefined, color}
|
||||
: {...version, name: this.removeSpecialChar(name), color};
|
||||
const newPath = await this.getVersionPath(editedVersion);
|
||||
|
||||
if(oldPath === newPath){
|
||||
this.deleteCustomVersion(version);
|
||||
this.addCustomVersion(editedVersion);
|
||||
return editedVersion;
|
||||
}
|
||||
|
||||
if(this.utilsService.pathExist(newPath)){ throw {title: "VersionAlreadExist"} as BsmException; }
|
||||
|
||||
return rename(oldPath, newPath).then(() => {
|
||||
this.deleteCustomVersion(version);
|
||||
this.addCustomVersion(editedVersion);
|
||||
return editedVersion;
|
||||
}).catch((err: Error) => {
|
||||
throw {title: "CantRename", error: err} as BsmException;
|
||||
});
|
||||
}
|
||||
|
||||
public async cloneVersion(version: BSVersion, name: string, color: string): Promise<BSVersion>{
|
||||
const originPath = await this.getVersionPath(version);
|
||||
const cloneVersion: BSVersion = version.BSVersion === name
|
||||
? {...version, name: undefined, color}
|
||||
: {...version, name: this.removeSpecialChar(name), color};
|
||||
const newPath = await this.getVersionPath(cloneVersion);
|
||||
|
||||
if(originPath === newPath){
|
||||
this.deleteCustomVersion(version);
|
||||
this.addCustomVersion(cloneVersion);
|
||||
return cloneVersion;
|
||||
}
|
||||
|
||||
return fs.copy(originPath, newPath).then(() => {
|
||||
this.deleteCustomVersion(version);
|
||||
this.addCustomVersion(cloneVersion);
|
||||
return cloneVersion;
|
||||
}).catch((err: Error) => {
|
||||
throw {title: "CantClone", error: err} as BsmException
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,8 +21,8 @@ export class ConfigurationService {
|
||||
this.store.set(key, value);
|
||||
}
|
||||
|
||||
public get(key: string): any{
|
||||
return this.store.get(key);
|
||||
public get<T>(key: string): T{
|
||||
return this.store.get(key) as T;
|
||||
}
|
||||
|
||||
public delete(key: string): void{
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import { useState } from "react";
|
||||
import SettingColorChooser from "renderer/components/settings/setting-color-chooser.component";
|
||||
import { BsmButton } from "renderer/components/shared/bsm-button.component";
|
||||
import { BsmIcon } from "renderer/components/svgs/bsm-icon.component";
|
||||
import { DefaultConfigKey } from "renderer/config/default-configuration.config";
|
||||
import { useTranslation } from "renderer/hooks/use-translation.hook";
|
||||
import { ConfigurationService } from "renderer/services/configuration.service";
|
||||
import { ModalExitCode, ModalResponse, ModalService } from "renderer/services/modale.service";
|
||||
import { BSVersion } from "shared/bs-version.interface";
|
||||
|
||||
export function EditVersionModal({resolver, clone = false}: {resolver: (x: ModalResponse<{name: string, color: string}>) => void, clone?: boolean}) {
|
||||
|
||||
const configService = ConfigurationService.getInstance();
|
||||
const modalService = ModalService.getInsance();
|
||||
|
||||
const modalData: BSVersion = modalService.getModalData();
|
||||
|
||||
const [name, setName] = useState(modalData.name || modalData.BSVersion);
|
||||
const [color, setColor] = useState(modalData.color ?? configService.get<string>("second-color" as DefaultConfigKey));
|
||||
const t = useTranslation();
|
||||
|
||||
const rename = () => {
|
||||
if(!name){ return; }
|
||||
resolver({exitCode: ModalExitCode.COMPLETED, data: {name, color}})
|
||||
}
|
||||
|
||||
const resetColor = () => {
|
||||
setColor(configService.get("second-color" as DefaultConfigKey))
|
||||
}
|
||||
|
||||
return (
|
||||
<form className="static" onSubmit={(e) => {e.preventDefault(); rename();}}>
|
||||
<h1 className="text-3xl uppercase tracking-wide w-full text-center text-gray-800 dark:text-gray-200">{t(!clone ? "Editer la version" : "Cloner_la_version")}</h1>
|
||||
<BsmIcon className="w-full h-11 my-3" icon="bsNote" style={{color: color}}/>
|
||||
{ clone && (
|
||||
<p className="max-w-sm mb-2 text-gray-800 dark:text-gray-200">Cloner la version te permet de séparer le contenu additionnel de BeatSaber entre deux même versions</p>
|
||||
)}
|
||||
<div className="mb-3">
|
||||
<label className="block font-bold cursor-pointer tracking-wide text-gray-800 dark:text-gray-200" htmlFor="name">{t("Nom")}</label>
|
||||
<input className="w-full bg-light-main-color-1 dark:bg-main-color-1 px-1 py-[2px] rounded-md outline-none" onChange={e => setName(e.target.value)} value={name} type="text" name="name" id="name" minLength={2} maxLength={15} placeholder={t("Nom de la version")}/>
|
||||
</div>
|
||||
<div>
|
||||
<span className="block font-bold tracking-wide text-gray-800 dark:text-gray-200">Couleur</span>
|
||||
<div className="relative w-full h-7 mb-4 bg-light-main-color-1 dark:bg-main-color-1 flex justify-center rounded-md py-1 z-[1]">
|
||||
<SettingColorChooser color={color} onChange={setColor} pickerClassName="!h-32 !w-32"/>
|
||||
<div className="absolute right-2 top-0 h-full flex items-center">
|
||||
<BsmButton onClick={resetColor} className="px-2 font-bold italic text-sm rounded-md bg-light-main-color-2 dark:bg-main-color-2 hover:bg-light-main-color-3 dark:hover:bg-main-color-3" text="pages.settings.appearance.reset" withBar={false}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-flow-col grid-cols-2 gap-4">
|
||||
<BsmButton className="rounded-md text-center bg-gray-500 hover:brightness-110 transition-all" onClick={() => {resolver({exitCode: ModalExitCode.CANCELED})}} withBar={false} text="misc.cancel"/>
|
||||
<BsmButton typeColor="primary" className="z-0 px-1 rounded-md text-center hover:brightness-110 transition-all" type="submit" withBar={false} text={t("Editer")}/>
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { LoginModal } from "./modal-types/login-modal.component";
|
||||
import { GuardModal } from "./modal-types/guard-modal.component";
|
||||
import { UninstallModal } from "./modal-types/uninstall-modal.component";
|
||||
import { InstallationFolderModal } from "./modal-types/installation-folder-modal.component";
|
||||
import { EditVersionModal } from "./modal-types/edit-version-modal.component";
|
||||
|
||||
export function Modal() {
|
||||
|
||||
@@ -41,6 +42,8 @@ export function Modal() {
|
||||
{modalType === ModalType.GUARD_CODE && <GuardModal resolver={modalSevice.getResolver()}/>}
|
||||
{modalType === ModalType.UNINSTALL && <UninstallModal resolver={modalSevice.getResolver()}/>}
|
||||
{modalType === ModalType.INSTALLATION_FOLDER && <InstallationFolderModal resolver={modalSevice.getResolver()}/>}
|
||||
{modalType === ModalType.EDIT_VERSION && <EditVersionModal resolver={modalSevice.getResolver()}/>}
|
||||
{modalType === ModalType.CLONE_VERSION && <EditVersionModal resolver={modalSevice.getResolver()} clone/>}
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
@@ -2,29 +2,32 @@ import { BSVersion } from 'shared/bs-version.interface';
|
||||
import { Link, useLocation } from "react-router-dom";
|
||||
import { BsDownloaderService } from "renderer/services/bs-downloader.service";
|
||||
import { useEffect, useState } from "react";
|
||||
import { combineLatest } from "rxjs";
|
||||
import { combineLatest, Subscription } from "rxjs";
|
||||
import { BSLauncherService, LaunchMods } from "renderer/services/bs-launcher.service";
|
||||
import { ConfigurationService } from "renderer/services/configuration.service";
|
||||
import { BsmButton } from "../shared/bsm-button.component";
|
||||
import { BSUninstallerService } from "renderer/services/bs-uninstaller.service";
|
||||
import { BSVersionManagerService } from "renderer/services/bs-version-manager.service";
|
||||
import { BsmIcon } from "../svgs/bsm-icon.component";
|
||||
import { ReactFitty } from "react-fitty";
|
||||
import { DefaultConfigKey } from 'renderer/config/default-configuration.config';
|
||||
|
||||
export function BsVersionItem(props: {version: BSVersion}) {
|
||||
|
||||
const { state } = useLocation() as { state: BSVersion};
|
||||
|
||||
const [downloading, setDownloading] = useState(true);
|
||||
const [downloadPercent, setDownloadPercent] = useState(0);
|
||||
|
||||
const downloaderService = BsDownloaderService.getInstance();
|
||||
const verionManagerService = BSVersionManagerService.getInstance();
|
||||
const launcherService = BSLauncherService.getInstance();
|
||||
const configService = ConfigurationService.getInstance();
|
||||
const bsUninstallerService = BSUninstallerService.getInstance();
|
||||
|
||||
const { state } = useLocation() as { state: BSVersion};
|
||||
|
||||
const [downloading, setDownloading] = useState(false);
|
||||
const [downloadPercent, setDownloadPercent] = useState(0);
|
||||
const [color, setColor] = useState("");
|
||||
|
||||
const isActive = (): boolean => {
|
||||
return props.version?.BSVersion === state?.BSVersion && props?.version.steam === state?.steam;
|
||||
return props.version?.BSVersion === state?.BSVersion && props?.version.steam === state?.steam && props?.version.name === state?.name;
|
||||
}
|
||||
|
||||
const handleDoubleClick = () => {
|
||||
@@ -46,28 +49,39 @@ export function BsVersionItem(props: {version: BSVersion}) {
|
||||
});
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
combineLatest([downloaderService.currentBsVersionDownload$, downloaderService.downloadProgress$]).subscribe(vals => {
|
||||
if(vals[0]?.BSVersion === props.version.BSVersion && vals[0]?.steam === props.version.steam){
|
||||
setDownloading(true);
|
||||
setDownloadPercent(vals[1]);
|
||||
}
|
||||
useEffect(() => {
|
||||
const subs: Subscription[] = [];
|
||||
const downloadSub = combineLatest([downloaderService.currentBsVersionDownload$, downloaderService.downloadProgress$]).subscribe(vals => {
|
||||
if(vals[0]?.BSVersion === props.version.BSVersion && vals[0]?.steam === props.version.steam && vals[0]?.name === props.version.name){
|
||||
setDownloading(true);
|
||||
setDownloadPercent(vals[1]);
|
||||
}
|
||||
else{
|
||||
setDownloading(false);
|
||||
setDownloadPercent(0);
|
||||
}
|
||||
});
|
||||
subs.push(downloadSub);
|
||||
if(props.version?.color){ setColor(props.version.color); }
|
||||
else{
|
||||
setDownloading(false);
|
||||
setDownloadPercent(0);
|
||||
const colorSub = configService.watch<string>("second-color" as DefaultConfigKey).subscribe(color => setColor(color));
|
||||
subs.push(colorSub);
|
||||
}
|
||||
})
|
||||
|
||||
return () => { subs.forEach(s => s.unsubscribe()); }
|
||||
}, []);
|
||||
|
||||
|
||||
return (
|
||||
<div className={`outline-none relative p-[1px] overflow-hidden rounded-xl flex justify-center content-center items-center mb-1 ${downloading && "nav-item-download"} active:translate-y-[1px]`}>
|
||||
<div className={`outline-none relative p-[1px] overflow-hidden rounded-xl flex justify-center items-center mb-1 ${downloading && "nav-item-download"} active:translate-y-[1px]`}>
|
||||
<div className="progress absolute top-0 w-full h-full" style={{transform: `translate(${-(100 - downloadPercent)}%, 0)`}}></div>
|
||||
<div className={`wrapper z-[1] px-2 py-[3px] w-full rounded-xl ${downloading && 'bg-black'} ${!downloading && "hover:bg-light-main-color-3 dark:hover:bg-main-color-3"} ${(isActive() && !downloading) && "bg-light-main-color-3 dark:bg-main-color-3"}`}>
|
||||
<Link onDoubleClick={handleDoubleClick} to={`/bs-version/${props.version.BSVersion}`} state={props.version} className="flex justify-center items-center">
|
||||
{props.version.steam && <BsmIcon icon="steam" className="w-[19px] h-[19px] mr-1"/>}
|
||||
{!props.version.steam && <BsmIcon icon="bsNote" className="w-[19px] h-[19px] mr-1 text-red-600"/>}
|
||||
<span className="flex items-center justify-center content-center shrink-0 grow text-lg dark:text-gray-200 text-gray-800 font-bold min-w-0 tracking-wide">{props.version.BSVersion}</span>
|
||||
<div className={`wrapper z-[1] px-1 py-[3px] w-full rounded-xl ${downloading && 'bg-black'} ${!downloading && "hover:bg-light-main-color-3 dark:hover:bg-main-color-3"} ${(isActive() && !downloading) && "bg-light-main-color-3 dark:bg-main-color-3"}`}>
|
||||
<Link onDoubleClick={handleDoubleClick} to={`/bs-version/${props.version.BSVersion}`} state={props.version} title={props.version.name && `${props.version.BSVersion} - ${props.version.name}`} className="w-full flex items-center justify-start content-center max-w-full">
|
||||
{props.version.steam && <BsmIcon icon="steam" className="w-[19px] h-[19px] mr-[5px] shrink-0"/>}
|
||||
{!props.version.steam && <BsmIcon icon="bsNote" className="w-[19px] h-[19px] mr-[5px] shrink-0" style={{color: color}}/>}
|
||||
<div className="overflow-hidden whitespace-nowrap text-xl dark:text-gray-200 text-gray-800 font-bold tracking-wide">
|
||||
<ReactFitty maxSize={19} minSize={10} className='align-middle pb-[2px] max-w-full overflow-hidden text-ellipsis'>{props.version.name || props.version.BSVersion}</ReactFitty>
|
||||
</div>
|
||||
</Link>
|
||||
{downloading && <BsmButton onClick={cancel} className="my-1 text-xs text-white rounded-md text-center hover:brightness-125" withBar={false} text="misc.cancel" typeColor="error"/>}
|
||||
</div>
|
||||
|
||||
@@ -24,7 +24,7 @@ export function NavBar() {
|
||||
<span id='logo-top' className='bg-red-500 aspect-square w-16' style={{backgroundColor: secondColor}}> </span>
|
||||
</div>
|
||||
</div>
|
||||
<div id='versions' className='w-fit relative left-[2px] grow overflow-y-hidden scrollbar-track-transparent scrollbar-thin scrollbar-thumb-neutral-900 hover:overflow-y-scroll'>
|
||||
<div id='versions' className='w-fit max-w-[120px] relative left-[2px] grow overflow-y-hidden scrollbar-track-transparent scrollbar-thin scrollbar-thumb-neutral-900 hover:overflow-y-scroll'>
|
||||
{installedVersions && installedVersions.map((version) => <BsVersionItem key={JSON.stringify(version)} version={version}/>)}
|
||||
</div>
|
||||
<div className='w-full p-2 flex flex-col items-center content-center justify-start'>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { HexColorPicker } from "react-colorful";
|
||||
import { motion, AnimatePresence } from "framer-motion"
|
||||
import OutsideClickHandler from "react-outside-click-handler";
|
||||
|
||||
export default function SettingColorChooser({color, onChange}: {color?: string, onChange?: (color: string) => void}) {
|
||||
export default function SettingColorChooser({color, onChange, pickerClassName}: {color?: string, onChange?: (color: string) => void, pickerClassName?: string}) {
|
||||
|
||||
const [colorVisible, setColorVisible] = useState(false);
|
||||
|
||||
@@ -13,9 +13,9 @@ export default function SettingColorChooser({color, onChange}: {color?: string,
|
||||
<span className="z-[1] block h-full w-full border-2 border-white rounded-full" onClick={() => setColorVisible(!colorVisible)} style={{backgroundColor: color}}/>
|
||||
<AnimatePresence>
|
||||
{colorVisible &&
|
||||
<motion.div initial={{opacity: 0}} animate={{opacity: 1}} transition={{duration: .1}} exit={{opacity: 0}} className="absolute flex items-center justify-center translate-y-9 shadow-lg rounded-lg shadow-black">
|
||||
<motion.div initial={{opacity: 0}} animate={{opacity: 1}} transition={{duration: .1}} exit={{opacity: 0}} className="fixed flex items-center justify-center translate-y-9 shadow-lg rounded-lg shadow-black">
|
||||
<div className="absolute w-2/4 aspect-square rotate-45 bg-light-main-color-3 dark:bg-main-color-3 -translate-y-12"></div>
|
||||
<HexColorPicker color={color} onChange={onChange} className="" />
|
||||
<HexColorPicker color={color} onChange={onChange} className={pickerClassName} />
|
||||
</motion.div>
|
||||
}
|
||||
</AnimatePresence>
|
||||
|
||||
@@ -12,7 +12,7 @@ export function BsmButton({className, style, imgClassName, icon, image, text, ty
|
||||
|
||||
return (
|
||||
<OutsideClickHandler onOutsideClick={e => onClickOutside && onClickOutside(e)}>
|
||||
<div onClick={e => onClick && onClick(e)} className={`${className} overflow-hidden cursor-pointer group ${disabled && "brightness-75 cursor-not-allowed"} ${typeColor == "error" && 'bg-red-500'}`} style={style}>
|
||||
<div onClick={e => onClick && onClick(e)} className={`${className} overflow-hidden cursor-pointer group ${disabled && "brightness-75 cursor-not-allowed"} ${typeColor === "error" && 'bg-red-500'} ${typeColor == "primary" && 'bg-blue-500'}`} style={style}>
|
||||
{ image && <BsmImage image={image} className={imgClassName}/> }
|
||||
{ icon && <BsmIcon icon={icon} className="h-full w-full text-gray-800 dark:text-white"/> }
|
||||
{text && (type === "submit" ? <button className="w-full h-full">{t(text)}</button> : <span>{t(text)}</span>)}
|
||||
|
||||
@@ -40,9 +40,12 @@ export function VersionViewer() {
|
||||
setOculusMode(!!configService.get<boolean>(LaunchMods.OCULUS_MOD));
|
||||
setDesktopMode(!!configService.get<boolean>(LaunchMods.DESKTOP_MOD));
|
||||
setDebugMode(!!configService.get<boolean>(LaunchMods.DEBUG_MOD));
|
||||
}, [])
|
||||
|
||||
}, []);
|
||||
|
||||
const navigateToVersion = (version: BSVersion) => {
|
||||
navigate(`/bs-version/${version.BSVersion}`, {state: version});
|
||||
}
|
||||
|
||||
const setMode = (mode: LaunchMods, value: boolean) => {
|
||||
if(mode === LaunchMods.DEBUG_MOD){ setDebugMode(value); }
|
||||
else if(mode === LaunchMods.OCULUS_MOD){
|
||||
@@ -58,26 +61,22 @@ export function VersionViewer() {
|
||||
configService.set(mode, value);
|
||||
}
|
||||
|
||||
const dropDownActions = async (id: number) => {
|
||||
if(id === 4){
|
||||
const openFolder = () => {
|
||||
ipcService.sendLazy("bs-version.open-folder", {args: state});
|
||||
}
|
||||
|
||||
const uninstall = async () => {
|
||||
const modalCompleted = await modalService.openModal(ModalType.UNINSTALL, state)
|
||||
if(modalCompleted.exitCode === ModalExitCode.COMPLETED){
|
||||
bsUninstallerService.uninstall(state)
|
||||
.then(() => {
|
||||
bsVersionManagerService.askInstalledVersions();
|
||||
const newVersionPage = bsVersionManagerService.getInstalledVersions()[0];
|
||||
navigate("/bs-version/"+newVersionPage.BSVersion, {state: newVersionPage});
|
||||
navigateToVersion(newVersionPage);
|
||||
})
|
||||
.catch((e) => {console.log("*** ", e)})
|
||||
}
|
||||
}
|
||||
else if(id === 2){
|
||||
verifyFiles();
|
||||
}
|
||||
else if(id === 1){
|
||||
ipcService.sendLazy("bs-version.open-folder", {args: state});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const verifyFiles = () => {
|
||||
bsDownloaderService.download(state, true);
|
||||
@@ -87,12 +86,26 @@ export function VersionViewer() {
|
||||
bsLauncherService.launch(state, oculusMode, desktopMode, debugMode);
|
||||
}
|
||||
|
||||
const edit = () => {
|
||||
bsVersionManagerService.editVersion(state).then(newVersion => {
|
||||
if(!newVersion){ return; }
|
||||
navigateToVersion(newVersion);
|
||||
});
|
||||
}
|
||||
|
||||
const clone = () => {
|
||||
bsVersionManagerService.cloneVersion(state).then(newVersion => {
|
||||
if(!newVersion){ return; }
|
||||
navigateToVersion(newVersion);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<BsmImage className="absolute w-full h-full top-0 left-0 object-cover" image={state.ReleaseImg || DefautVersionImage} errorImage={DefautVersionImage}/>
|
||||
<div className="relative flex items-center flex-col w-full h-full text-gray-200 backdrop-blur-lg">
|
||||
<BsmImage className='relative object-cover h-28' image={BSLogo}/>
|
||||
<h1 className='relative text-4xl font-bold italic -top-3'>{state.BSVersion}</h1>
|
||||
<h1 className='relative text-4xl font-bold italic -top-3'>{state.name ? `${state.BSVersion} - ${state.name}` : state.BSVersion}</h1>
|
||||
<TabNavBar className='mt-3' tabsText={["misc.launch", "misc.maps", "misc.mods"]} onTabChange={(i : number) => setCurrentTabIndex(i)}/>
|
||||
<div className='mt-2 w-full grow flex transition-transform duration-300 pt-5' style={{transform: `translate(${-(currentTabIndex * 100)}%, 0)`}}>
|
||||
<div className='w-full shrink-0 items-center relative flex flex-col justify-start -top-2'>
|
||||
@@ -119,11 +132,12 @@ export function VersionViewer() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<BsmDropdownButton className='absolute top-5 right-5 h-9 w-9' onItemClick={dropDownActions} items={[
|
||||
{id: 1, text: "pages.version-viewer.dropdown.open-folder", icon: "folder"},
|
||||
{id: 2, text: "pages.version-viewer.dropdown.verify-files", icon: "task"},
|
||||
{id: 3, text: "pages.version-viewer.dropdown.clone (WIP)", icon: "copy"},
|
||||
{id: 4, text: "pages.version-viewer.dropdown.uninstall", icon:"trash"}
|
||||
<BsmDropdownButton className='absolute top-5 right-5 h-9 w-9' items={[
|
||||
{text: "pages.version-viewer.dropdown.open-folder", icon: "folder", onClick: openFolder},
|
||||
{text: "pages.version-viewer.dropdown.verify-files", icon: "task", onClick: verifyFiles},
|
||||
(!state.steam && {text: "Editer (WIP)", icon: "task", onClick: edit}),
|
||||
{text: "pages.version-viewer.dropdown.clone (WIP)", icon: "copy", onClick: clone},
|
||||
{text: "pages.version-viewer.dropdown.uninstall", icon:"trash", onClick: uninstall}
|
||||
]}/>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
import { BSVersion } from 'shared/bs-version.interface';
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
import { IpcService } from "./ipc.service";
|
||||
import { ModalExitCode, ModalService, ModalType } from './modale.service';
|
||||
import { NotificationService } from './notification.service';
|
||||
|
||||
export class BSVersionManagerService {
|
||||
|
||||
private static instance: BSVersionManagerService;
|
||||
|
||||
public readonly ipcService: IpcService;
|
||||
private readonly ipcService: IpcService;
|
||||
private readonly modalService: ModalService;
|
||||
private readonly notificationService: NotificationService;
|
||||
|
||||
public readonly installedVersions$: BehaviorSubject<BSVersion[]> = new BehaviorSubject([]);
|
||||
public readonly availableVersions$: BehaviorSubject<BSVersion[]> = new BehaviorSubject([]);
|
||||
|
||||
private constructor(){
|
||||
this.ipcService = IpcService.getInstance();
|
||||
this.modalService = ModalService.getInsance();
|
||||
this.notificationService = NotificationService.getInstance();
|
||||
this.askAvailableVersions();
|
||||
this.askInstalledVersions();
|
||||
}
|
||||
@@ -28,7 +34,7 @@ export class BSVersionManagerService {
|
||||
if(steamIndex > 0){
|
||||
[sorted[0], sorted[steamIndex]] = [sorted[steamIndex], sorted[0]];
|
||||
}
|
||||
const cleanedSort = [...new Map(sorted.map(version => [`${version.BSVersion}-${version.steam}`, version])).values()]
|
||||
const cleanedSort = [...new Map(sorted.map(version => [`${version.BSVersion}-${version.name}-${version.steam}`, version])).values()]
|
||||
this.installedVersions$.next(cleanedSort);
|
||||
}
|
||||
|
||||
@@ -56,4 +62,32 @@ export class BSVersionManagerService {
|
||||
return this.availableVersions$.value.filter(v => v.year === year).sort((a, b) => +b.ReleaseDate - +a.ReleaseDate);
|
||||
}
|
||||
|
||||
public async editVersion(version: BSVersion): Promise<BSVersion>{
|
||||
const modalRes = await this.modalService.openModal<{name: string, color: string}>(ModalType.EDIT_VERSION, version);
|
||||
if(modalRes.exitCode !== ModalExitCode.COMPLETED){ return null; }
|
||||
if(modalRes.data.name?.length < 2){ return null; }
|
||||
return this.ipcService.send<BSVersion>("bs-version.rename", {args: {version, name: modalRes.data.name, color: modalRes.data.color}}).then(res => {
|
||||
if(!res.success){
|
||||
this.notificationService.notifyError({title: res.error.title});
|
||||
return null;
|
||||
}
|
||||
this.askInstalledVersions();
|
||||
return res.data;
|
||||
});
|
||||
}
|
||||
|
||||
public async cloneVersion(version: BSVersion): Promise<BSVersion>{
|
||||
const modalRes = await this.modalService.openModal<{name: string, color: string}>(ModalType.CLONE_VERSION, version);
|
||||
if(modalRes.exitCode !== ModalExitCode.COMPLETED){ return null; }
|
||||
if(modalRes.data.name?.length < 2){ return null; }
|
||||
return this.ipcService.send<BSVersion>("bs-version.clone", {args: {version, name: modalRes.data.name, color: modalRes.data.color}}).then(res => {
|
||||
if(!res.success){
|
||||
this.notificationService.notifyError({title: res.error.title});
|
||||
return null;
|
||||
}
|
||||
this.askInstalledVersions();
|
||||
return res.data;
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,5 +5,7 @@ export interface BSVersion {
|
||||
ReleaseImg?: string,
|
||||
ReleaseDate?: string,
|
||||
year?: string,
|
||||
steam?: boolean
|
||||
steam?: boolean,
|
||||
name?: string,
|
||||
color?: string
|
||||
}
|
||||
Reference in New Issue
Block a user