mirror of
https://github.com/Zagrios/bs-manager.git
synced 2026-07-03 14:08:25 +02:00
Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b5a08584a0 | |||
| 78568cd235 | |||
| 8e86d4a7c5 | |||
| 7a2639f772 | |||
| 535430d0c5 | |||
| b82ddc1c59 | |||
| f4dc6ab838 | |||
| 62dde63206 | |||
| 5c034013fe | |||
| 23717c15d1 | |||
| 09acc3ccaa | |||
| 7c3f4f6a64 | |||
| 8dc48b20cb | |||
| 165b17e034 | |||
| 4255e2ad85 | |||
| 16b7c1ed76 | |||
| d179bde348 | |||
| e2234b19ea | |||
| 45259f304b | |||
| 220c7940a8 | |||
| 9665dbcb4e | |||
| 8d88b59e3d | |||
| ae9d1dfc9b | |||
| 6930df940f | |||
| fd523f4017 | |||
| 4c8f903256 | |||
| d2ba6aadc1 | |||
| 0023066e32 | |||
| 39bcf63e3c | |||
| 656f32e755 | |||
| f66279980b | |||
| 1f65841e62 |
@@ -0,0 +1,9 @@
|
||||
# Reporting Security Issues
|
||||
|
||||
If you discover a security issue in BSManager, please report it by sending an
|
||||
email to [contact@bsmanager.io](mailto:contact@bsmanager.io).
|
||||
|
||||
This will allow us to assess the risk, and make a fix available before we add a
|
||||
bug report to the GitHub repository.
|
||||
|
||||
Thanks for helping make BSManager safe for everyone.
|
||||
@@ -13,12 +13,19 @@
|
||||
"refuse": "Refuse",
|
||||
"apply": "Apply"
|
||||
},
|
||||
"nav-bar":{
|
||||
"add-version": "Add a version",
|
||||
"settings": "Settings"
|
||||
},
|
||||
"pages": {
|
||||
"version-viewer": {
|
||||
"launch-mods": {
|
||||
"oculus": "Oculus Mod",
|
||||
"oculus-description": "If you are running Beat Saber through Steam, this allows you to play the game on an Oculus headset.",
|
||||
"desktop": "Desktop Mod",
|
||||
"debug": "Debug Mod"
|
||||
"desktop-description": "This allows you to use WASD and the mouse to navigate around the menu in game. This makes testing much easier, because you don't have to put on your headset!",
|
||||
"debug": "Debug Mod",
|
||||
"debug-description": "Enables the output log window for IPA. This will show the debug console that mods use."
|
||||
},
|
||||
"maps": {
|
||||
"search-bar": {
|
||||
@@ -51,6 +58,7 @@
|
||||
},
|
||||
"mods": {
|
||||
"loading-mods": "Loading mods...",
|
||||
"no-internet": "No internet",
|
||||
"buttons": {
|
||||
"more-infos": "More info",
|
||||
"install-or-update": "Install or update"
|
||||
@@ -164,10 +172,12 @@
|
||||
"shared": {
|
||||
"errors": {
|
||||
"titles": {
|
||||
"operation-running": "Operation running"
|
||||
"operation-running": "Operation running",
|
||||
"no-internet": "No internet"
|
||||
},
|
||||
"msg": {
|
||||
"operation-running": "Wait for the end of the current operation then start again."
|
||||
"operation-running": "Wait for the end of the current operation then start again.",
|
||||
"no-internet": "Check your connection and try again."
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -505,7 +515,9 @@
|
||||
},
|
||||
"download-maps": {
|
||||
"search-btn": "Search",
|
||||
"loading-maps": "Loading maps..."
|
||||
"loading-maps": "Loading maps...",
|
||||
"no-maps-found": "No maps found",
|
||||
"no-internet": "No internet"
|
||||
},
|
||||
"mods-disclaimer":{
|
||||
"title": "disclaimer",
|
||||
|
||||
@@ -13,12 +13,19 @@
|
||||
"refuse": "Rechazar",
|
||||
"apply": "Aplicar"
|
||||
},
|
||||
"nav-bar":{
|
||||
"add-version": "Agregar una versión",
|
||||
"settings": "Ajustes"
|
||||
},
|
||||
"pages": {
|
||||
"version-viewer": {
|
||||
"launch-mods": {
|
||||
"oculus": "Oculus Mod",
|
||||
"oculus-description": "Si estás ejecutando Beat Saber a través de Steam, esto te permite jugar en un auricular Oculus.",
|
||||
"desktop": "Desktop Mod",
|
||||
"debug": "Debug Mod"
|
||||
"desktop-description": "Esto te permite usar WASD y el mouse para navegar por el menú en juego. Esto hace que las pruebas sean mucho más fáciles, ¡porque no tienes que ponerte el auricular!",
|
||||
"debug": "Debug Mod",
|
||||
"debug-description": "Habilita la ventana de registro de salida para IPA. Esto mostrará la consola de depuración que usan los mods."
|
||||
},
|
||||
"maps": {
|
||||
"search-bar": {
|
||||
@@ -51,6 +58,7 @@
|
||||
},
|
||||
"mods": {
|
||||
"loading-mods": "Cargando mods...",
|
||||
"no-internet": "Sin internet",
|
||||
"buttons": {
|
||||
"more-infos": "Más información",
|
||||
"install-or-update": "Instalar o actualizar"
|
||||
@@ -164,10 +172,12 @@
|
||||
"shared": {
|
||||
"errors": {
|
||||
"titles": {
|
||||
"operation-running": "Operación en curso"
|
||||
"operation-running": "Operación en curso",
|
||||
"no-internet": "Sin internet"
|
||||
},
|
||||
"msg": {
|
||||
"operation-running": "Espere a que termine la operación actual y vuelva a empezar."
|
||||
"operation-running": "Espere a que termine la operación actual y vuelva a empezar.",
|
||||
"no-internet": "Comprueba tu conexión a Internet e inténtalo de nuevo."
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -505,7 +515,9 @@
|
||||
},
|
||||
"download-maps": {
|
||||
"search-btn": "buscar",
|
||||
"loading-maps": "Cargando mapas..."
|
||||
"loading-maps": "Cargando mapas...",
|
||||
"no-maps-found": "No se encontraron mapas",
|
||||
"no-internet": "Sin internet"
|
||||
},
|
||||
"mods-disclaimer":{
|
||||
"title": "descargo de responsabilidad",
|
||||
|
||||
@@ -13,12 +13,19 @@
|
||||
"refuse": "Refuser",
|
||||
"apply": "Appliquer"
|
||||
},
|
||||
"nav-bar":{
|
||||
"add-version": "Ajouter une version",
|
||||
"settings": "Paramètres"
|
||||
},
|
||||
"pages": {
|
||||
"version-viewer": {
|
||||
"launch-mods": {
|
||||
"oculus": "Mode Oculus",
|
||||
"oculus-description": "Si vous exécutez Beat Saber via Steam, cela vous permet de jouer au jeu sur un casque Oculus.",
|
||||
"desktop": "Mode Bureau",
|
||||
"debug": "Mode Debug"
|
||||
"desktop-description": "Cela vous permet d'utiliser WASD et la souris pour naviguer dans le menu en jeu. Cela rend les tests beaucoup plus faciles, car vous n'avez pas à mettre votre casque!",
|
||||
"debug": "Mode Debug",
|
||||
"debug-description": "Active la fenêtre de log pour IPA. Cela affichera la console de débogage utilisée par les mods."
|
||||
},
|
||||
"maps": {
|
||||
"search-bar": {
|
||||
@@ -51,6 +58,7 @@
|
||||
},
|
||||
"mods": {
|
||||
"loading-mods": "Chargement des mods...",
|
||||
"no-internet": "Pas d'internet",
|
||||
"buttons": {
|
||||
"more-infos": "Plus d'infos",
|
||||
"install-or-update": "Installer ou mettre à jour"
|
||||
@@ -164,10 +172,12 @@
|
||||
"shared": {
|
||||
"errors": {
|
||||
"titles": {
|
||||
"operation-running": "Opération en cours"
|
||||
"operation-running": "Opération en cours",
|
||||
"no-internet": "Pas d'internet"
|
||||
},
|
||||
"msg": {
|
||||
"operation-running": "Attend la fin de l'opération en cours puis recommences."
|
||||
"operation-running": "Attend la fin de l'opération en cours puis recommences.",
|
||||
"no-internet": "Verifie t'a connexion internet et recommence."
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -505,7 +515,9 @@
|
||||
},
|
||||
"download-maps": {
|
||||
"search-btn": "Rechercher",
|
||||
"loading-maps": "Chargement des maps..."
|
||||
"loading-maps": "Chargement des maps...",
|
||||
"no-maps-found": "Aucune map trouvée",
|
||||
"no-internet": "Pas d'internet"
|
||||
},
|
||||
"mods-disclaimer":{
|
||||
"title": "avis de non-responsabilité",
|
||||
|
||||
Generated
+18877
-154
File diff suppressed because it is too large
Load Diff
@@ -246,6 +246,7 @@
|
||||
"react-range": "^1.8.14",
|
||||
"react-router-dom": "^6.3.0",
|
||||
"regedit": "^5.1.1",
|
||||
"rfdc": "^1.3.0",
|
||||
"rxjs": "^7.5.6",
|
||||
"sanitize-filename": "^1.6.3",
|
||||
"semver": "^7.3.8",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bs-manager",
|
||||
"version": "1.0.0-alpha.3",
|
||||
"version": "1.0.0-alpha.6",
|
||||
"description": "A foundation for scalable desktop apps",
|
||||
"main": "./dist/main/main.js",
|
||||
"author": {
|
||||
|
||||
@@ -5,7 +5,6 @@ import { SteamService } from "./steam.service";
|
||||
import { UtilsService } from "./utils.service";
|
||||
import { BS_APP_ID, OCULUS_BS_DIR } from "../constants";
|
||||
import path from "path";
|
||||
import { createInterface } from "readline";
|
||||
import { createReadStream } from "fs";
|
||||
import fs from "fs-extra";
|
||||
import { ConfigurationService } from "./configuration.service";
|
||||
@@ -14,6 +13,7 @@ import { BsmException } from "shared/models/bsm-exception.model";
|
||||
import log from "electron-log";
|
||||
import { OculusService } from "./oculus.service";
|
||||
import { DownloadLinkType } from "shared/models/mods";
|
||||
import sanitize from "sanitize-filename";
|
||||
|
||||
export class BSLocalVersionService{
|
||||
|
||||
@@ -47,21 +47,27 @@ export class BSLocalVersionService{
|
||||
if(!this.utilsService.pathExist(versionFilePath)){ return null; }
|
||||
const versionsAvailable = await this.remoteVersionService.getAvailableVersions();
|
||||
return new Promise<string>(resolve => {
|
||||
const readLine = createInterface({ input: createReadStream(versionFilePath) });
|
||||
const stream = createReadStream(versionFilePath);
|
||||
let findVersion: string = null;
|
||||
readLine.on('line', (line) => {
|
||||
stream.on('data', (line) => {
|
||||
line = line.toString();
|
||||
for(const version of versionsAvailable){
|
||||
if(line.includes(version.BSVersion)){
|
||||
findVersion = version.BSVersion;
|
||||
readLine.close();
|
||||
stream.close();
|
||||
stream.destroy();
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
readLine.on('close', () => {
|
||||
stream.on('end', () => {
|
||||
if(findVersion){ resolve(findVersion) }
|
||||
resolve(findVersion);
|
||||
});
|
||||
stream.on('close', () => {
|
||||
if(findVersion){ resolve(findVersion) }
|
||||
resolve(findVersion);
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -92,11 +98,11 @@ export class BSLocalVersionService{
|
||||
}
|
||||
|
||||
private removeSpecialChar(seq: string): string{
|
||||
return seq.replace( /[<>:"\/\\|?*]+/g, '' );
|
||||
return sanitize(seq);
|
||||
}
|
||||
|
||||
public getVersionFolder(version: BSVersion){
|
||||
return version.name ? `${version.BSVersion}-${version.name}` : version.BSVersion;
|
||||
public getVersionFolder(version: BSVersion): string{
|
||||
return version.name ?? version.BSVersion;
|
||||
}
|
||||
|
||||
public getVersionType(version: BSVersion): DownloadLinkType{
|
||||
@@ -107,8 +113,10 @@ export class BSLocalVersionService{
|
||||
|
||||
private async getSteamVersion(): Promise<BSVersion>{
|
||||
const steamBsFolder = await this.steamService.getGameFolder(BS_APP_ID, "Beat Saber");
|
||||
|
||||
if(!steamBsFolder || !this.utilsService.pathExist(steamBsFolder)){ return null; }
|
||||
const steamBsVersion = await this.getVersionOfBSFolder(steamBsFolder);
|
||||
|
||||
if(!steamBsVersion){ return null; }
|
||||
const version = await this.remoteVersionService.getVersionDetails(steamBsVersion);
|
||||
if(!version){ return null; }
|
||||
@@ -127,30 +135,37 @@ export class BSLocalVersionService{
|
||||
|
||||
public async getInstalledVersions(): Promise<BSVersion[]>{
|
||||
|
||||
const versions: BSVersion[] = [];
|
||||
const steamVersion = await this.getSteamVersion();
|
||||
if(steamVersion){ versions.push(steamVersion); }
|
||||
const oculusVersion = await this.getOculusVersion();
|
||||
if(oculusVersion){ versions.push(oculusVersion); }
|
||||
const versions: BSVersion[] = [];
|
||||
|
||||
if(!this.utilsService.pathExist(this.installLocationService.versionsDirectory)){ return versions }
|
||||
const steamVersion = await this.getSteamVersion();
|
||||
if(steamVersion){ versions.push(steamVersion); }
|
||||
|
||||
const folderInInstallation = this.utilsService.listDirsInDir(this.installLocationService.versionsDirectory);
|
||||
for(const f of folderInInstallation){
|
||||
log.info("try get version from folder", f);
|
||||
let version = await 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;
|
||||
const oculusVersion = await this.getOculusVersion();
|
||||
if(oculusVersion){ versions.push(oculusVersion); }
|
||||
|
||||
if(!this.utilsService.pathExist(this.installLocationService.versionsDirectory)){ return versions }
|
||||
|
||||
const folderInInstallation = this.utilsService.listDirsInDir(this.installLocationService.versionsDirectory, true);
|
||||
|
||||
for(const f of folderInInstallation){
|
||||
log.info("try get version from folder", f);
|
||||
const rawVersion = await this.getVersionOfBSFolder(f);
|
||||
if(!rawVersion){ continue; }
|
||||
const bsVersion = {...await this.remoteVersionService.getVersionDetails(rawVersion)};
|
||||
if(!bsVersion){ continue; }
|
||||
bsVersion.name = path.basename(f) !== bsVersion.BSVersion ? path.basename(f) : undefined;
|
||||
|
||||
const customVersion = this.getCustomVersions().find(custom => custom.BSVersion === bsVersion.BSVersion && custom.name === bsVersion.name);
|
||||
|
||||
if(customVersion){
|
||||
bsVersion.color = customVersion.color;
|
||||
}
|
||||
|
||||
versions.push(bsVersion);
|
||||
};
|
||||
this.setCustomVersions(versions.filter(v => !!v.color));
|
||||
|
||||
return versions;
|
||||
}
|
||||
|
||||
public async deleteVersion(version: BSVersion): Promise<boolean>{
|
||||
@@ -177,13 +192,14 @@ export class BSLocalVersionService{
|
||||
return editedVersion;
|
||||
}
|
||||
|
||||
if(this.utilsService.pathExist(newPath)){ throw {title: "VersionAlreadExist"} as BsmException; }
|
||||
if(this.utilsService.pathExist(newPath) && newPath === oldPath){ throw {title: "VersionAlreadExist"} as BsmException; }
|
||||
|
||||
return rename(oldPath, newPath).then(() => {
|
||||
this.deleteCustomVersion(version);
|
||||
this.addCustomVersion(editedVersion);
|
||||
return editedVersion;
|
||||
}).catch((err: Error) => {
|
||||
log.error("edit version error", err, version, name, color);
|
||||
throw {title: "CantRename", error: err} as BsmException;
|
||||
});
|
||||
}
|
||||
@@ -198,7 +214,6 @@ export class BSLocalVersionService{
|
||||
if(originPath === newPath){
|
||||
this.deleteCustomVersion(version);
|
||||
this.addCustomVersion(cloneVersion);
|
||||
return cloneVersion;
|
||||
}
|
||||
|
||||
if(this.utilsService.pathExist(newPath)){ throw {title: "VersionAlreadExist"} as BsmException; }
|
||||
@@ -207,7 +222,7 @@ export class BSLocalVersionService{
|
||||
this.addCustomVersion(cloneVersion);
|
||||
return cloneVersion;
|
||||
}).catch((err: Error) => {
|
||||
log.error("CLONE", err, version);
|
||||
log.error("clone version error", err, version, name, color);
|
||||
throw {title: "CantClone", error: err} as BsmException
|
||||
})
|
||||
}
|
||||
|
||||
@@ -50,6 +50,9 @@ export class OculusService {
|
||||
public async getGameFolder(gameFolder: string): Promise<string>{
|
||||
|
||||
const libsFolders = await this.getOculusLibsPath();
|
||||
|
||||
if(!libsFolders){ return null; }
|
||||
|
||||
const rootLibDir = "Software"
|
||||
|
||||
for(const lib of libsFolders){
|
||||
|
||||
@@ -12,6 +12,8 @@ import { MAP_SPECIFICITIES } from "renderer/partials/maps/map-general/map-specif
|
||||
import { MAP_REQUIREMENTS } from "renderer/partials/maps/map-requirements/map-requirements"
|
||||
import { MAP_DIFFICULTIES_COLORS } from "renderer/partials/maps/map-difficulties/map-difficulties-colors"
|
||||
import { BsmButton } from "../shared/bsm-button.component"
|
||||
import equal from "fast-deep-equal/es6";
|
||||
import clone from "rfdc";
|
||||
|
||||
export type Props = {
|
||||
className?: string,
|
||||
@@ -28,6 +30,7 @@ export function FilterPanel({className, ref, playlist = false, filter, onChange,
|
||||
const t = useTranslation();
|
||||
|
||||
const [haveChanged, setHaveChanged] = useState(false);
|
||||
const [firstFilter,] = useState(clone()(filter));
|
||||
const firstRun = useRef(true);
|
||||
|
||||
const MIN_NPS = 0;
|
||||
@@ -47,7 +50,8 @@ export function FilterPanel({className, ref, playlist = false, filter, onChange,
|
||||
firstRun.current = false;
|
||||
return;
|
||||
}
|
||||
setHaveChanged(() => true);
|
||||
console.log(filter, firstFilter);
|
||||
setHaveChanged(() => !equal(filter, firstFilter));
|
||||
}, [filter])
|
||||
|
||||
|
||||
@@ -89,6 +93,9 @@ export function FilterPanel({className, ref, playlist = false, filter, onChange,
|
||||
if(max === MAX_NPS){
|
||||
delete newFilter["maxNps"];
|
||||
}
|
||||
if(min === MIN_NPS){
|
||||
delete newFilter["minNps"];
|
||||
}
|
||||
onChange(newFilter);
|
||||
}
|
||||
|
||||
@@ -97,6 +104,9 @@ export function FilterPanel({className, ref, playlist = false, filter, onChange,
|
||||
if(max === MAX_DURATION){
|
||||
delete newFilter["maxDuration"];
|
||||
}
|
||||
if(min === MIN_DURATION){
|
||||
delete newFilter["minDuration"];
|
||||
}
|
||||
onChange(newFilter);
|
||||
}
|
||||
|
||||
@@ -116,11 +126,17 @@ export function FilterPanel({className, ref, playlist = false, filter, onChange,
|
||||
enabledTags.add(tag);
|
||||
}
|
||||
|
||||
onChange({
|
||||
...filter,
|
||||
enabledTags,
|
||||
excludedTags
|
||||
});
|
||||
const newFilter = {...filter, enabledTags, excludedTags};
|
||||
|
||||
if(newFilter.enabledTags.size === 0){
|
||||
delete newFilter["enabledTags"];
|
||||
}
|
||||
|
||||
if(newFilter.excludedTags.size === 0){
|
||||
delete newFilter["excludedTags"];
|
||||
}
|
||||
|
||||
onChange(newFilter);
|
||||
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -120,7 +120,7 @@ export function MapsPlaylistsPanel({version}: Props) {
|
||||
<div className="w-full h-full flex flex-col items-center justify-center">
|
||||
<nav className="w-full shrink-0 flex h-9 justify-center px-40 gap-2 mb-3 text-main-color-1 dark:text-white">
|
||||
<div className="h-full rounded-full bg-light-main-color-2 dark:bg-main-color-2 grow p-[6px]">
|
||||
<input type="text" className="h-full w-full bg-light-main-color-1 dark:bg-main-color-1 rounded-full px-2" placeholder={t("pages.version-viewer.maps.search-bar.search-placeholder")} value={tabIndex === 0 ? mapSearch : playlistSearch} onChange={e => handleSearch(e.target.value)}/>
|
||||
<input type="text" className="h-full w-full bg-light-main-color-1 dark:bg-main-color-1 rounded-full px-2" placeholder={t("pages.version-viewer.maps.search-bar.search-placeholder")} value={tabIndex === 0 ? mapSearch : playlistSearch} onChange={e => handleSearch(e.target.value)} tabIndex={-1}/>
|
||||
</div>
|
||||
<BsmDropdownButton className="h-full relative z-[1] flex justify-center" buttonClassName="flex items-center justify-center h-full rounded-full px-2 py-1" icon="filter" text="pages.version-viewer.maps.search-bar.filters-btn" withBar={false}>
|
||||
<FilterPanel className="absolute top-[calc(100%+3px)] origin-top w-[500px] h-fit p-2 rounded-md shadow-md shadow-black" filter={mapFilter} onChange={setMapFilter}/>
|
||||
|
||||
@@ -14,9 +14,11 @@ import { ModalComponent } from "renderer/services/modale.service";
|
||||
import { BSVersion } from "shared/bs-version.interface";
|
||||
import { BsvMapCharacteristic, BsvMapDetail, MapFilter, SearchOrder, SearchParams } from "shared/models/maps/beat-saver.model";
|
||||
import BeatWaitingImg from "../../../../../assets/images/apngs/beat-waiting.png";
|
||||
import BeatConflictImg from "../../../../../assets/images/apngs/beat-conflict.png";
|
||||
import equal from "fast-deep-equal/es6";
|
||||
import { ProgressBarService } from "renderer/services/progress-bar.service";
|
||||
import { useTranslation } from "renderer/hooks/use-translation.hook";
|
||||
import { OsDiagnosticService } from "renderer/services/os-diagnostic.service";
|
||||
|
||||
export const DownloadMapsModal: ModalComponent<void, BSVersion> = ({data}) => {
|
||||
|
||||
@@ -24,6 +26,7 @@ export const DownloadMapsModal: ModalComponent<void, BSVersion> = ({data}) => {
|
||||
const mapsManager = MapsManagerService.getInstance();
|
||||
const mapsDownloader = MapsDownloaderService.getInstance();
|
||||
const progressBar = ProgressBarService.getInstance();
|
||||
const os = OsDiagnosticService.getInstance();
|
||||
|
||||
const currentDownload = useObservable(mapsDownloader.currentMapDownload$);
|
||||
const mapsInQueue = useObservable(mapsDownloader.mapsInQueue$);
|
||||
@@ -34,6 +37,8 @@ export const DownloadMapsModal: ModalComponent<void, BSVersion> = ({data}) => {
|
||||
const [maps, setMaps] = useState<BsvMapDetail[]>([]);
|
||||
const [sortOrder, setSortOrder] = useState<SearchOrder>(BSV_SORT_ORDER.at(0));
|
||||
const [ownedMapHashs, setOwnedMapHashs] = useState<string[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const isOnline = useObservable(os.isOnline$);
|
||||
const [searchParams, setSearchParams] = useState<SearchParams>({
|
||||
sortOrder,
|
||||
filter,
|
||||
@@ -73,7 +78,8 @@ export const DownloadMapsModal: ModalComponent<void, BSVersion> = ({data}) => {
|
||||
}, [])
|
||||
|
||||
const loadMaps = (params: SearchParams) => {
|
||||
beatSaver.searchMaps(params).then((maps => setMaps(prev => [...prev, ...maps])));
|
||||
setLoading(() => true);
|
||||
beatSaver.searchMaps(params).then((maps => setMaps(prev => [...prev, ...maps]))).finally(() => setLoading(() => false));
|
||||
}
|
||||
|
||||
const extractMapDiffs = (map: BsvMapDetail): Map<BsvMapCharacteristic, ParsedMapDiff[]> => {
|
||||
@@ -167,8 +173,8 @@ export const DownloadMapsModal: ModalComponent<void, BSVersion> = ({data}) => {
|
||||
<ul className="w-full grow flex content-start flex-wrap gap-2 px-2 overflow-y-scroll overflow-x-hidden scrollbar-thin scrollbar-thumb-rounded-full scrollbar-thumb-neutral-900 z-0" >
|
||||
{maps.length === 0 ? (
|
||||
<div className="w-full h-full flex flex-col items-center justify-center">
|
||||
<img className="w-32 h-32 spin-loading" src={BeatWaitingImg} alt=" "/>
|
||||
<span className="text-lg">{t("modals.download-maps.loading-maps")}</span>
|
||||
<img className={`w-32 h-32 ${loading && "spin-loading"}`} src={loading ? BeatWaitingImg : BeatConflictImg} alt=" "/>
|
||||
<span className="text-lg">{t(loading ? "modals.download-maps.loading-maps" : isOnline ? "modals.download-maps.no-maps-found" : "modals.download-maps.no-internet")}</span>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
|
||||
@@ -34,7 +34,7 @@ export const BsManagerIcon = memo(({className}: {className?: string}) => {
|
||||
<path d="M626.75,485.91l20.82,351.94a35.21,35.21,0,0,1-15.49,31.36L487.37,965.35l-.06,0A35.56,35.56,0,0,1,476,970.12a34.85,34.85,0,0,1-23.51-2.87L136.58,809.9a115.45,115.45,0,0,1-63.75-96.49L52,361.46a35.26,35.26,0,0,1,15.23-31.21l145.05-96.36a35.72,35.72,0,0,1,11.27-4.7,34.87,34.87,0,0,1,23.51,2.87L563,389.42a115.41,115.41,0,0,1,63.75,96.49Z"/>
|
||||
<path filter="brightness(30%)" d="M613.78,486.67l20.82,352a22.34,22.34,0,0,1-32.26,21.31L286.4,702.57A102.43,102.43,0,0,1,229.83,617L209,265a22.35,22.35,0,0,1,32.26-21.32L557.2,401.05A102.42,102.42,0,0,1,613.78,486.67Z"/>
|
||||
</g>
|
||||
<g fill="#fff">
|
||||
<g className="text-black dark:text-white" fill="currentColor">
|
||||
<path d="M718.29,776.48,700,804.18c-40.63,39.5-87.06,67.25-133.81,90.75-87.32,43.9-203.67,77.19-249,90.34a48,48,0,0,1-26.17.16c-64-17.72-188.62-72-236.28-125.49a4.23,4.23,0,0,1,5.3-6.45c35.64,20.87,111.22,57.64,156.37,74.22,49.18,18.06,81.29,8.74,92.28-3.26a4.77,4.77,0,0,1,7.24.21c19.51,24,107.26,4.6,166.64-13C546.91,892.66,668.42,835.1,718.29,776.48Z"/>
|
||||
<path d="M522.09,933.81,374.64,989.49a3.92,3.92,0,0,0,1.44,7.58l54.62-.77a35.63,35.63,0,0,0,19-5.79l76-49.55A4,4,0,0,0,522.09,933.81Z"/>
|
||||
<path d="M218.92,195.56c-21.14.07-33.55,35.86-33.16,89.08.2,28,2.58,92.74,4.79,147.73.23,5.71-8.18,6.48-9,.81L143.13,162.47a4.38,4.38,0,0,1,7.12-4,63.63,63.63,0,0,0,21.93,11.85,4.61,4.61,0,0,0,5.9-3.72c4.2-27.7,16.63-58.74,37.35-88.74a4.36,4.36,0,0,1,7.56.69c10.86,23.87,49,103.8,93.34,152.79a4.65,4.65,0,0,1-5.62,7.22C274.14,219.18,227.7,195.54,218.92,195.56Z"/>
|
||||
|
||||
@@ -81,11 +81,9 @@ export function BsVersionItem(props: {version: BSVersion}) {
|
||||
const renderVersionText = () => {
|
||||
if(props.version.name){
|
||||
return (
|
||||
<Tippy content={props.version.BSVersion} placement="right-end" arrow={false} className="font-bold !bg-main-color-3" duration={[200, 0]}>
|
||||
<div className="h-8 flex items-center dark:text-gray-200 text-gray-800 overflow-hidden">
|
||||
<div ref={ref} className='whitespace-nowrap font-bold tracking-wide w-full text-center' style={{fontSize}}>{props.version.name}</div>
|
||||
</div>
|
||||
</Tippy>
|
||||
<div className="h-8 flex items-center dark:text-gray-200 text-gray-800 overflow-hidden">
|
||||
<div ref={ref} className='whitespace-nowrap font-bold tracking-wide w-full text-center' style={{fontSize}}>{props.version.name}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return (
|
||||
@@ -98,10 +96,20 @@ export function BsVersionItem(props: {version: BSVersion}) {
|
||||
|
||||
return (
|
||||
<NavBarItem onCancel={cancel} progress={downloading ? downloadPercent : 0} isActive={isActive() && !downloading} isDownloading={downloading}>
|
||||
<Link onDoubleClick={handleDoubleClick} to={`/bs-version/${props.version.BSVersion}`} state={props.version} className="w-full flex items-center justify-start content-center max-w-full">
|
||||
{renderIcon()}
|
||||
{renderVersionText()}
|
||||
</Link>
|
||||
{props.version.name ? (
|
||||
<Tippy content={props.version.BSVersion} placement="right-end" arrow={false} className="font-bold !bg-main-color-3" duration={[100, 0]} animation="shift-away-subtle">
|
||||
<Link onDoubleClick={handleDoubleClick} to={`/bs-version/${props.version.BSVersion}`} state={props.version} className="w-full flex items-center justify-start content-center max-w-full">
|
||||
{renderIcon()}
|
||||
{renderVersionText()}
|
||||
</Link>
|
||||
</Tippy>
|
||||
) : (
|
||||
<Link onDoubleClick={handleDoubleClick} to={`/bs-version/${props.version.BSVersion}`} state={props.version} className="w-full flex items-center justify-start content-center max-w-full">
|
||||
{renderIcon()}
|
||||
{renderVersionText()}
|
||||
</Link>
|
||||
)}
|
||||
|
||||
</NavBarItem>
|
||||
)
|
||||
}
|
||||
@@ -7,12 +7,20 @@ import { useObservable } from 'renderer/hooks/use-observable.hook';
|
||||
import { BsManagerIcon } from './bsmanager-icon.component';
|
||||
import { MapsNavBarItem } from './nav-bar-items/maps-nav-bar-item.component';
|
||||
import { NavBarSpliter } from './nav-bar-spliter.component';
|
||||
import { useThemeColor } from 'renderer/hooks/use-theme-color.hook';
|
||||
import Tippy from '@tippyjs/react';
|
||||
import { useTranslation } from 'renderer/hooks/use-translation.hook';
|
||||
import { I18nService } from 'renderer/services/i18n.service';
|
||||
|
||||
export function NavBar() {
|
||||
|
||||
const bsVersionServoce = BSVersionManagerService.getInstance();
|
||||
const bsVersionServoce = BSVersionManagerService.getInstance();
|
||||
|
||||
const installedVersions = useObservable(bsVersionServoce.installedVersions$);
|
||||
const installedVersions = useObservable(bsVersionServoce.installedVersions$);
|
||||
const color = useThemeColor("first-color");
|
||||
const t = useTranslation();
|
||||
|
||||
const _ = useObservable(I18nService.getInstance().currentLanguage$)
|
||||
|
||||
return (
|
||||
<nav id='nav-bar' className='z-10 flex flex-col h-full max-h-full items-center p-1 bg-light-main-color-1 dark:bg-main-color-1'>
|
||||
@@ -23,13 +31,17 @@ export function NavBar() {
|
||||
{installedVersions && installedVersions.map((version) => <BsVersionItem key={JSON.stringify(version)} version={version}/>)}
|
||||
</ol>
|
||||
<NavBarSpliter/>
|
||||
<div className='w-full pb-2 flex flex-col items-center content-center justify-start gap-2'>
|
||||
<Link to="blah">
|
||||
<BsmIcon icon='add' className='text-blue-500 h-[34px]'/>
|
||||
</Link>
|
||||
<Link to="settings">
|
||||
<BsmIcon icon='settings' className='text-blue-500 h-7'/>
|
||||
</Link>
|
||||
<div className='w-full pb-2 flex flex-col items-center content-center justify-start gap-1'>
|
||||
<Tippy placement="right" content={t("nav-bar.add-version")}>
|
||||
<Link className='rounded-md w-9 h-9 flex justify-center items-center hover:bg-light-main-color-3 dark:hover:bg-main-color-3' to="blah">
|
||||
<BsmIcon icon='add' className='text-blue-500 h-[34px]' style={{color}}/>
|
||||
</Link>
|
||||
</Tippy>
|
||||
<Tippy placement="right" content={t("nav-bar.settings")}>
|
||||
<Link className='rounded-md w-9 h-9 flex justify-center items-center hover:bg-light-main-color-3 dark:hover:bg-main-color-3' to="settings">
|
||||
<BsmIcon icon='settings' className='text-blue-500 h-7' style={{color}}/>
|
||||
</Link>
|
||||
</Tippy>
|
||||
</div>
|
||||
</nav>
|
||||
)
|
||||
|
||||
@@ -18,7 +18,7 @@ export function SupportersBlock({className, title, supporters}: Props) {
|
||||
<div className={`mb-10 flex flex-col items-center ${className}`}>
|
||||
<h2 className="uppercase text-3xl font-bold">{t(title)}</h2>
|
||||
<div className="flex justify-center flex-wrap py-5 px-20">
|
||||
{supporters.map(s => <SupporterItem supporter={s} delay={someDelay()}/>)}
|
||||
{supporters.map(s => <SupporterItem key={crypto.randomUUID()} supporter={s} delay={someDelay()}/>)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ export function BsmIframeView() {
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{iframeUrl && (
|
||||
<div className="top-0 absolute w-screen h-screen flex content-center items-center justify-center z-50">
|
||||
<div className="top-0 absolute w-screen h-screen flex content-center items-center justify-center z-[150]">
|
||||
<motion.span className="absolute w-full h-full bg-black top-0 right-0" initial={{opacity: 0}} animate={{opacity: .60}} exit={{opacity: 0}} transition={{duration: .2}}/>
|
||||
<motion.div ref={ref} className="z-[1] w-[calc(100vw-250px)] h-[calc(100vh-250px)] shadow-black shadow-md rounded-md overflow-hidden bg-black" initial={{y: "100vh"}} animate={{y: 0}} exit={{y: "100vh"}}>
|
||||
<iframe className="w-full h-full" src={iframeUrl}/>
|
||||
@@ -46,9 +46,10 @@ import { FilterIcon } from "./icons/filter-icon.component";
|
||||
import DiscordIcon from "./icons/discord-icon.component";
|
||||
import { TwitterIcon } from "./icons/twitter-icon.component";
|
||||
import { Mee6Icon } from "./icons/mee6-icon.component";
|
||||
import { InfoIcon } from "./icons/info-icon.component";
|
||||
|
||||
export type BsmIconType = BsvMapCharacteristic | (
|
||||
"settings"|"trash"|"favorite"|"folder"|"bsNote"|"check"|"three-dots"|"twitch"|"eye"|"play"|"checkCircleIcon"|"discord"|
|
||||
"settings"|"trash"|"favorite"|"folder"|"bsNote"|"check"|"three-dots"|"twitch"|"eye"|"play"|"checkCircleIcon"|"discord"|"info"|
|
||||
"terminal"|"desktop"|"oculus"|"add"|"cross"|"task"|"github"|"close"|"thumbUpFill"|"timerFill"|"pause"|"twitter"|
|
||||
"copy"|"steam"|"edit"|"export"|"patreon"|"search"|"bsMapDifficulty"|"link"|"unlink"|"download"|"filter"|"mee6"|
|
||||
"fr-FR-flag"|"es-ES-flag"|"en-US-flag"|"en-EN-flag"
|
||||
@@ -103,6 +104,7 @@ export const BsmIcon = memo(({className, icon, style}: {className?: string, icon
|
||||
if(icon === "discord"){ return <DiscordIcon className={className} style={style}/> }
|
||||
if(icon === "twitter"){ return <TwitterIcon className={className} style={style}/> }
|
||||
if(icon === "mee6"){ return <Mee6Icon className={className} style={style}/> }
|
||||
if(icon === "info"){ return <InfoIcon className={className} style={style}/> }
|
||||
return <TrashIcon className={className} style={style}/>
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { CSSProperties } from "react";
|
||||
|
||||
export function InfoIcon(props: {className?: string, style?: CSSProperties}) {
|
||||
return (
|
||||
<svg className={props.className} style={props.style} viewBox="0 0 400 400">
|
||||
<path stroke="none" fill="currentColor" fillRule="evenodd" d="M225 1.3c-34.8 9.2-48.5 49.6-25.4 75 30.8 34 89 14.6 88.8-29.8 0-31-31.9-53.7-63.4-45.3m-45 134.4c-13.6 2.1-31.1 7.3-52.3 15.5l-10.9 4.2L114 167c-1.5 6.4-2.7 11.7-2.6 11.9.1.1 4-1 8.7-2.5 23.6-7.6 42.2-5.7 47.6 5 6.3 12.3 4.2 25.2-14.4 91.4-6 21.2-11.7 42.8-12.7 48-10 51.7 11.2 78.7 62.1 78.9 23 0 26-.7 61-14.1L278 380l2.8-11.7c3.3-13.6 3.5-12.5-1.8-10.5-24.3 9.3-48.5 6.9-53.6-5.3-5.2-12.4-3-25.6 14.5-87.8 13.3-47 12.5-44.1 13.8-52.7 6.5-42.6-6-66.5-38.8-74.6-6.5-1.6-28.5-2.8-34.7-1.9"/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import { CSSProperties } from "react";
|
||||
|
||||
export function SettingIcon(props: {className?: string, style?: CSSProperties}) {
|
||||
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 className={props.className} style={props.style} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40">
|
||||
<path fill="currentColor" d="M16.5 35.833 15.792 30.792Q15 30.542 14.104 30.042Q13.208 29.542 12.542 28.958L7.875 31.042L4.375 24.875L8.542 21.792Q8.458 21.375 8.438 20.896Q8.417 20.417 8.417 20Q8.417 19.625 8.438 19.146Q8.458 18.667 8.542 18.208L4.375 15.125L7.875 9L12.542 11.042Q13.25 10.458 14.125 9.979Q15 9.5 15.792 9.208L16.5 4.167H23.5L24.208 9.208Q25.042 9.542 25.875 10Q26.708 10.458 27.375 11.042L32.125 9L35.625 15.125L31.375 18.208Q31.5 18.667 31.521 19.125Q31.542 19.583 31.542 20Q31.542 20.417 31.5 20.854Q31.458 21.292 31.375 21.792L35.583 24.875L32.083 31.042L27.375 28.917Q26.667 29.542 25.854 30.042Q25.042 30.542 24.208 30.792L23.5 35.833ZM19.958 24.792Q21.958 24.792 23.375 23.396Q24.792 22 24.792 20Q24.792 18 23.375 16.604Q21.958 15.208 19.958 15.208Q17.958 15.208 16.562 16.604Q15.167 18 15.167 20Q15.167 22 16.562 23.396Q17.958 24.792 19.958 24.792Z"/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
+19
-5
@@ -1,18 +1,32 @@
|
||||
import Tippy from "@tippyjs/react";
|
||||
import { BsmIcon, BsmIconType } from "renderer/components/svgs/bsm-icon.component"
|
||||
import { useTranslation } from "renderer/hooks/use-translation.hook";
|
||||
|
||||
type props = {onClick: (active: boolean) => void, active: boolean, text: string, icon: BsmIconType}
|
||||
type props = {
|
||||
onClick: (active: boolean) => void,
|
||||
active: boolean,
|
||||
text: string,
|
||||
icon: BsmIconType,
|
||||
infoText?: string,
|
||||
}
|
||||
|
||||
export function LaunchModToogle({onClick, active, text, icon}: props) {
|
||||
export function LaunchModToogle({onClick, active, text, 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)}>
|
||||
<div className={`absolute glow-on-hover !rounded-full ${active && "!opacity-100 blur-[2px]"}`}/>
|
||||
<div className='w-full h-full px-6 flex 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='mr-1 h-7 text-gray-800 dark:text-white'/>
|
||||
<span className='w-fit min-w-fit h-full text-lg font-bold uppercase tracking-wide italic'>{t(text)}</span>
|
||||
<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'/>
|
||||
<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">
|
||||
<BsmIcon className="w-full h-full" icon="info"/>
|
||||
</div>
|
||||
</Tippy>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -39,9 +39,9 @@ export function LaunchSlide({version}: Props) {
|
||||
return (
|
||||
<div className="w-full shrink-0 items-center relative flex flex-col justify-start">
|
||||
<div className='grid grid-flow-col gap-6'>
|
||||
{!version.oculus && <LaunchModToogle icon='oculus' onClick={() => setMode(LaunchMods.OCULUS_MOD, !oculusMode)} active={oculusMode} text="pages.version-viewer.launch-mods.oculus"/>}
|
||||
<LaunchModToogle icon='desktop' onClick={() => setMode(LaunchMods.DESKTOP_MOD, !desktopMode)} active={desktopMode} text="pages.version-viewer.launch-mods.desktop"/>
|
||||
<LaunchModToogle icon='terminal' onClick={() => setMode(LaunchMods.DEBUG_MOD, !debugMode)} active={debugMode} text="pages.version-viewer.launch-mods.debug"/>
|
||||
{!version.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>
|
||||
<div className='grow flex justify-center items-center'>
|
||||
<BsmButton onClick={launch} active={JSON.stringify(version) === JSON.stringify(launchState)} 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"/>
|
||||
|
||||
@@ -7,6 +7,7 @@ import { ConfigurationService } from "renderer/services/configuration.service";
|
||||
import { DefaultConfigKey } from "renderer/config/default-configuration.config";
|
||||
import { BsmButton } from "renderer/components/shared/bsm-button.component";
|
||||
import BeatWaitingImg from "../../../../../../assets/images/apngs/beat-waiting.png"
|
||||
import BeatConflictImg from "../../../../../../assets/images/apngs/beat-conflict.png"
|
||||
import { useObservable } from "renderer/hooks/use-observable.hook";
|
||||
import { skip, filter } from "rxjs/operators";
|
||||
import { Subscription } from "rxjs";
|
||||
@@ -15,6 +16,7 @@ import { LinkOpenerService } from "renderer/services/link-opener.service";
|
||||
import { useInView } from "framer-motion";
|
||||
import { ModalExitCode, ModalService } from "renderer/services/modale.service";
|
||||
import { ModsDisclaimerModal } from "renderer/components/modal/modal-types/mods-disclaimer-modal.component";
|
||||
import { OsDiagnosticService } from "renderer/services/os-diagnostic.service";
|
||||
|
||||
export function ModsSlide({version, onDisclamerDecline}: {version: BSVersion, onDisclamerDecline: () => void}) {
|
||||
|
||||
@@ -24,6 +26,7 @@ export function ModsSlide({version, onDisclamerDecline}: {version: BSVersion, on
|
||||
const configService = ConfigurationService.getInstance();
|
||||
const linkOpener = LinkOpenerService.getInstance();
|
||||
const modals = ModalService.getInsance();
|
||||
const os = OsDiagnosticService.getInstance();
|
||||
|
||||
const ref = useRef(null);
|
||||
const isVisible = useInView(ref, {amount: .5});
|
||||
@@ -31,11 +34,13 @@ export function ModsSlide({version, onDisclamerDecline}: {version: BSVersion, on
|
||||
const [modsInstalled, setModsInstalled] = useState(null as Map<string, Mod[]>);
|
||||
const [modsSelected, setModsSelected] = useState([] as Mod[]);
|
||||
const [moreInfoMod, setMoreInfoMod] = useState(null as Mod);
|
||||
const isOnline = useObservable(os.isOnline$);
|
||||
const installing = useObservable(modsManager.isInstalling$);
|
||||
const t = useTranslation();
|
||||
|
||||
const downloadRef = useRef(null);
|
||||
const [downloadWith, setDownloadWidth] = useState(0);
|
||||
|
||||
|
||||
const modsToCategoryMap = (mods: Mod[]): Map<string, Mod[]> => {
|
||||
if(!mods){ return new Map<string, Mod[]>(); }
|
||||
@@ -69,6 +74,8 @@ export function ModsSlide({version, onDisclamerDecline}: {version: BSVersion, on
|
||||
}
|
||||
|
||||
const loadMods = () => {
|
||||
if(os.isOffline){ return; }
|
||||
|
||||
Promise.all([
|
||||
modsManager.getAvailableMods(version),
|
||||
modsManager.getInstalledMods(version)
|
||||
@@ -84,7 +91,7 @@ export function ModsSlide({version, onDisclamerDecline}: {version: BSVersion, on
|
||||
|
||||
const subs: Subscription[] = [];
|
||||
|
||||
if(isVisible){
|
||||
if(isVisible && isOnline){
|
||||
|
||||
const promise = new Promise<boolean>(async resolve => {
|
||||
|
||||
@@ -115,7 +122,7 @@ export function ModsSlide({version, onDisclamerDecline}: {version: BSVersion, on
|
||||
subs.forEach(s => s.unsubscribe());
|
||||
}
|
||||
|
||||
}, [isVisible, version]);
|
||||
}, [isVisible, isOnline, version]);
|
||||
|
||||
|
||||
useLayoutEffect(() => {
|
||||
@@ -128,7 +135,7 @@ export function ModsSlide({version, onDisclamerDecline}: {version: BSVersion, on
|
||||
return (
|
||||
<div ref={ref} className='shrink-0 w-full h-full px-8 pb-7 flex justify-center'>
|
||||
<div className='relative flex flex-col grow-0 bg-light-main-color-2 dark:bg-main-color-2 h-full w-full rounded-md shadow-black shadow-center overflow-hidden'>
|
||||
{modsAvailable ? (
|
||||
{isOnline && modsAvailable ? (
|
||||
<>
|
||||
<div className="overflow-scroll w-full min-h-0 scrollbar-thin scrollbar-thumb-neutral-900 scrollbar-thumb-rounded-full">
|
||||
<ModsGrid modsMap={modsAvailable} installed={modsInstalled} modsSelected={modsSelected} onModChange={handleModChange} moreInfoMod={moreInfoMod} onWantInfos={handleMoreInfo}/>
|
||||
@@ -142,8 +149,8 @@ export function ModsSlide({version, onDisclamerDecline}: {version: BSVersion, on
|
||||
</>
|
||||
) : (
|
||||
<div className="w-full h-full flex flex-col items-center justify-center">
|
||||
<img className="w-32 h-32 spin-loading" src={BeatWaitingImg} alt=" "/>
|
||||
<span className="text-xl mt-3 h-0 italic">{t("pages.version-viewer.mods.loading-mods")}</span>
|
||||
<img className={`w-32 h-32 ${isOnline && "spin-loading"}`} src={isOnline ? BeatWaitingImg : BeatConflictImg} alt=" "/>
|
||||
<span className="text-xl mt-3 h-0 italic">{t(isOnline ? "pages.version-viewer.mods.loading-mods" : "pages.version-viewer.mods.no-internet")}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -7,7 +7,6 @@ import { BsmButton } from "renderer/components/shared/bsm-button.component";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { useTranslation } from "renderer/hooks/use-translation.hook";
|
||||
import { BSVersionManagerService } from "renderer/services/bs-version-manager.service";
|
||||
import { useObservable } from "renderer/hooks/use-observable.hook";
|
||||
|
||||
export function AvailableVersionsList() {
|
||||
|
||||
@@ -15,12 +14,15 @@ export function AvailableVersionsList() {
|
||||
const versionManagerService: BSVersionManagerService = BSVersionManagerService.getInstance();
|
||||
|
||||
const [versionSelected, setVersionSelected] = useState(null as BSVersion);
|
||||
const currentVersionDownloading = useObservable(bsDownloaderService.currentBsVersionDownload$);
|
||||
const [downloading, setDownloading] = useState(false);
|
||||
const t = useTranslation();
|
||||
|
||||
const startDownload = () => {
|
||||
bsDownloaderService.download(versionSelected, versionManagerService.isVersionInstalled(versionSelected));
|
||||
}
|
||||
const startDownload = () => {
|
||||
setDownloading(() => true)
|
||||
bsDownloaderService.download(versionSelected, versionManagerService.isVersionInstalled(versionSelected)).finally(() => {
|
||||
setDownloading(() => false);
|
||||
});
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const versionSelectedSub = bsDownloaderService.selectedBsVersion$.subscribe(version => {
|
||||
@@ -40,7 +42,7 @@ export function AvailableVersionsList() {
|
||||
<AvailableVersionsSlider/>
|
||||
|
||||
<AnimatePresence>
|
||||
{ versionSelected && !currentVersionDownloading && (
|
||||
{ versionSelected && !downloading && (
|
||||
<motion.div initial={{y:"150%"}} animate={{y:"0%"}} exit={{y:"150%"}} className="absolute bottom-5" onClick={startDownload}>
|
||||
<BsmButton text={versionManagerService.isVersionInstalled(versionSelected) ? "misc.verify" : "misc.download"} className="relative text-gray-800 dark:text-gray-100 rounded-md text-3xl font-bold italic tracking-wide px-3 pb-2 pt-1 shadow-md shadow-black"/>
|
||||
</motion.div>
|
||||
|
||||
@@ -294,7 +294,7 @@ export function SettingsPage() {
|
||||
<SettingContainer title="pages.settings.patreon.title" description="pages.settings.patreon.description">
|
||||
<div className="flex gap-2">
|
||||
<BsmButton className="flex w-fit rounded-md h-8 px-2 font-bold py-1 whitespace-nowrap !bg-red-500 !text-white" text="pages.settings.patreon.buttons.support" withBar={false} onClick={openSupportPage}/>
|
||||
<BsmButton className="flex w-fit rounded-md h-8 px-2 font-bold py-1 !text-white" withBar={false} text="pages.settings.patreon.buttons.supporters" color="#6c5ce7" onClick={() => toogleShowSupporters}/>
|
||||
<BsmButton className="flex w-fit rounded-md h-8 px-2 font-bold py-1 !text-white" withBar={false} text="pages.settings.patreon.buttons.supporters" color="#6c5ce7" onClick={toogleShowSupporters}/>
|
||||
</div>
|
||||
<SettingContainer className="mt-3" description="pages.settings.discord.description">
|
||||
<div className="flex gap-2">
|
||||
|
||||
@@ -28,7 +28,13 @@ export function VersionViewer() {
|
||||
const navigate = useNavigate();
|
||||
const [currentTabIndex, setCurrentTabIndex] = useState(0);
|
||||
|
||||
const navigateToVersion = (version: BSVersion) => navigate(`/bs-version/${version.BSVersion}`, {state: version});
|
||||
const navigateToVersion = (version?: BSVersion) => {
|
||||
console.log(version);
|
||||
if(!version){
|
||||
return navigate("/available-versions");
|
||||
}
|
||||
navigate(`/bs-version/${version.BSVersion}`, {state: version});
|
||||
}
|
||||
const openFolder = () => ipcService.sendLazy("bs-version.open-folder", {args: state});
|
||||
const verifyFiles = () => bsDownloaderService.download(state, true);
|
||||
|
||||
@@ -36,9 +42,9 @@ export function VersionViewer() {
|
||||
const modalCompleted = await modalService.openModal(UninstallModal, state);
|
||||
if(modalCompleted.exitCode === ModalExitCode.COMPLETED){
|
||||
bsUninstallerService.uninstall(state).then(() => {
|
||||
bsVersionManagerService.askInstalledVersions();
|
||||
const newVersionPage = bsVersionManagerService.getInstalledVersions()[0];
|
||||
navigateToVersion(newVersionPage);
|
||||
bsVersionManagerService.askInstalledVersions().then(versions => {
|
||||
navigateToVersion(versions?.at(0));
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import { ProgressionInterface } from "shared/models/progress-bar";
|
||||
import { IpcService } from "./ipc.service";
|
||||
import { ModalExitCode, ModalService } from "./modale.service";
|
||||
import { NotificationService, NotificationType } from "./notification.service";
|
||||
import { OsDiagnosticService } from "./os-diagnostic.service";
|
||||
import { ProgressBarService } from "./progress-bar.service";
|
||||
|
||||
export class BsModsManagerService {
|
||||
@@ -22,6 +23,7 @@ export class BsModsManagerService {
|
||||
private readonly progressBar: ProgressBarService;
|
||||
private readonly modals: ModalService;
|
||||
private readonly notifications: NotificationService;
|
||||
private readonly os: OsDiagnosticService;
|
||||
|
||||
public readonly isInstalling$: BehaviorSubject<boolean> = new BehaviorSubject(false);
|
||||
public readonly isUninstalling$: BehaviorSubject<boolean> = new BehaviorSubject(false);
|
||||
@@ -36,17 +38,27 @@ export class BsModsManagerService {
|
||||
this.progressBar = ProgressBarService.getInstance();
|
||||
this.modals = ModalService.getInsance();
|
||||
this.notifications = NotificationService.getInstance();
|
||||
this.os = OsDiagnosticService.getInstance();
|
||||
}
|
||||
|
||||
public getAvailableMods(version: BSVersion): Promise<Mod[]>{
|
||||
public async getAvailableMods(version: BSVersion): Promise<Mod[]>{
|
||||
return this.ipcService.send<Mod[], BSVersion>("get-available-mods", {args: version}).then(res => res.data);
|
||||
}
|
||||
|
||||
public getInstalledMods(version: BSVersion): Promise<Mod[]>{
|
||||
public async getInstalledMods(version: BSVersion): Promise<Mod[]>{
|
||||
return this.ipcService.send<Mod[], BSVersion>("get-installed-mods", {args: version}).then(res => res.data);
|
||||
}
|
||||
|
||||
public installMods(mods: Mod[], version: BSVersion): Promise<void>{
|
||||
|
||||
if(this.os.isOffline){
|
||||
this.notifications.notifyError({
|
||||
title: "notifications.shared.errors.titles.no-internet",
|
||||
desc: "notifications.shared.errors.msg.no-internet"
|
||||
});
|
||||
return new Promise(res => res());
|
||||
}
|
||||
|
||||
if(!this.progressBar.require()){ return new Promise(res => res()); }
|
||||
|
||||
const progress$: Observable<ProgressionInterface> = this.ipcService.watch<ModInstallProgression>("mod-installed").pipe(map(res => {
|
||||
|
||||
@@ -9,7 +9,7 @@ export class OsDiagnosticService {
|
||||
return OsDiagnosticService.instance;
|
||||
}
|
||||
|
||||
private _isOnline$ = new BehaviorSubject(true);
|
||||
private _isOnline$ = new BehaviorSubject(navigator.onLine);
|
||||
|
||||
private constructor(){
|
||||
|
||||
|
||||
@@ -12,14 +12,16 @@ import { NotificationOverlay } from "../components/notification/notification-ove
|
||||
import { PageStateService } from "../services/page-state.service";
|
||||
import { MapsPage } from "../pages/maps-page.component";
|
||||
import "tailwindcss/tailwind.css";
|
||||
import { BsmIframeView } from "../components/shared/bsm-map-preview.component";
|
||||
import { BsmIframeView } from "../components/shared/iframe-view.component";
|
||||
import 'tippy.js/dist/tippy.css';
|
||||
import 'tippy.js/animations/shift-away-subtle.css';
|
||||
import { MapsManagerService } from "renderer/services/maps-manager.service";
|
||||
import { PlaylistsManagerService } from "renderer/services/playlists-manager.service";
|
||||
import { ModelsManagerService } from "renderer/services/models-manager.service";
|
||||
import { NotificationService } from "renderer/services/notification.service";
|
||||
import { timer } from "rxjs";
|
||||
import { ConfigurationService } from "renderer/services/configuration.service";
|
||||
import { OsDiagnosticService } from "renderer/services/os-diagnostic.service";
|
||||
|
||||
export default function App() {
|
||||
|
||||
@@ -30,6 +32,7 @@ export default function App() {
|
||||
const models = ModelsManagerService.getInstance();
|
||||
const notification = NotificationService.getInstance();
|
||||
const config = ConfigurationService.getInstance();
|
||||
const os = OsDiagnosticService.getInstance();
|
||||
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
Reference in New Issue
Block a user