mirror of
https://github.com/Zagrios/bs-manager.git
synced 2026-07-03 14:08:25 +02:00
Merge pull request #122 from Zagrios/feature/import-external-version/121
feature/import-external-version/121
This commit is contained in:
@@ -85,7 +85,11 @@
|
||||
},
|
||||
"available-versions": {
|
||||
"title": "Select a version",
|
||||
"steam-release": "Release page"
|
||||
"steam-release": "Release page",
|
||||
"dropdown": {
|
||||
"refresh": "Refresh list",
|
||||
"import-version" :"Import a version"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"steam": {
|
||||
@@ -233,6 +237,22 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"bs-import-version":{
|
||||
"success":{
|
||||
"start-import": {
|
||||
"title": "Import in progress 👌",
|
||||
"desc": "The import may take several minutes depending on your configuration."
|
||||
},
|
||||
"imported": {
|
||||
"title": "Imported version 🎉"
|
||||
}
|
||||
},
|
||||
"errors":{
|
||||
"import-error":{
|
||||
"desc": "Check that the selected folder is a BeatSaber installation."
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"move-folder": {
|
||||
"success": {
|
||||
@@ -427,6 +447,13 @@
|
||||
"submit": "Login"
|
||||
}
|
||||
},
|
||||
"bs-import-version":{
|
||||
"title": "Import a version",
|
||||
"description": "Imports a version of BeatSaber to use with BSManager. The import will copy the selected BeatSaber installation folder to the BSManager versions folder.",
|
||||
"buttons":{
|
||||
"submit": "Import a version"
|
||||
}
|
||||
},
|
||||
"bs-uninstall": {
|
||||
"title": "Uninstall",
|
||||
"description": "Are you sure you want to uninstall BeatSaber {version} ? You will have to download it again to play it.",
|
||||
|
||||
@@ -85,7 +85,11 @@
|
||||
},
|
||||
"available-versions": {
|
||||
"title": "Seleccione una versión",
|
||||
"steam-release": "Publicación Página"
|
||||
"steam-release": "Publicación Página",
|
||||
"dropdown": {
|
||||
"refresh": "Actualizar la lista",
|
||||
"import-version" :"Importar una versión"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"steam": {
|
||||
@@ -233,6 +237,22 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"bs-import-version":{
|
||||
"success":{
|
||||
"start-import": {
|
||||
"title": "Importación en curso 👌",
|
||||
"desc": "La importación puede tardar varios minutos dependiendo de su configuración."
|
||||
},
|
||||
"imported": {
|
||||
"title": "Versión importada 🎉"
|
||||
}
|
||||
},
|
||||
"errors":{
|
||||
"import-error":{
|
||||
"desc": "Compruebe que la carpeta seleccionada es una instalación de BeatSaber."
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"move-folder": {
|
||||
"success": {
|
||||
@@ -427,6 +447,13 @@
|
||||
"submit": "Inicio de sesión"
|
||||
}
|
||||
},
|
||||
"bs-import-version":{
|
||||
"title": "Importar una versión",
|
||||
"description": "Importa una versión de BeatSaber para usar con BSManager. La importación copiará la carpeta de instalación seleccionada de BeatSaber en la carpeta de versiones de BSManager.",
|
||||
"buttons":{
|
||||
"submit": "Importar una versión"
|
||||
}
|
||||
},
|
||||
"bs-uninstall": {
|
||||
"title": "Desinstalar",
|
||||
"description": "¿Estás seguro de que quieres desinstalar BeatSaber {version}? Tendrás que descargarlo de nuevo para poder jugar.",
|
||||
|
||||
@@ -85,7 +85,11 @@
|
||||
},
|
||||
"available-versions": {
|
||||
"title": "Choisi une version",
|
||||
"steam-release": "Notes de version"
|
||||
"steam-release": "Notes de version",
|
||||
"dropdown": {
|
||||
"refresh": "Rafraichir la liste",
|
||||
"import-version" :"Importer une version"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"steam": {
|
||||
@@ -233,6 +237,22 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"bs-import-version":{
|
||||
"success":{
|
||||
"start-import": {
|
||||
"title": "Importation en cours 👌",
|
||||
"desc": "L'importation peut durer plusieurs minutes selon votre configuration."
|
||||
},
|
||||
"imported": {
|
||||
"title": "Version importée 🎉"
|
||||
}
|
||||
},
|
||||
"errors":{
|
||||
"import-error":{
|
||||
"desc": "Vérifier que le dossier sélectionné est une installation de BeatSaber."
|
||||
}
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"move-folder": {
|
||||
"success": {
|
||||
@@ -427,6 +447,13 @@
|
||||
"submit": "Se connecter"
|
||||
}
|
||||
},
|
||||
"bs-import-version":{
|
||||
"title": "Importer une version",
|
||||
"description": "Importe une version de BeatSaber pour profiter des fonctionnalités de BSManager. L'importation copiera le dossier d'installation de BeatSaber sélectionné dans le dossier de versions de BSManager.",
|
||||
"buttons":{
|
||||
"submit": "Importer une version"
|
||||
}
|
||||
},
|
||||
"bs-uninstall": {
|
||||
"title": "Désinstaller",
|
||||
"description": "Est-tu sûr de vouloir désinstaller BeatSaber {version} ? Tu vas devoir la retélécharger pour y jouer.",
|
||||
|
||||
@@ -65,4 +65,14 @@ ipcMain.on('bs-download.set-installation-folder', (event, request: IpcRequest<st
|
||||
});
|
||||
})
|
||||
|
||||
ipcMain.on('bs-download.import-version', (event, request: IpcRequest<string>) => {
|
||||
const utils = UtilsService.getInstance();
|
||||
const installer = BSInstallerService.getInstance();
|
||||
installer.importVersion(request.args).catch(e => {
|
||||
utils.ipcSend(request.responceChannel, {success: false, error: e});
|
||||
}).then(() => {
|
||||
utils.ipcSend(request.responceChannel, {success: true});
|
||||
});
|
||||
})
|
||||
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ ipcMain.on('new-window', async (event, request: IpcRequest<string>) => {
|
||||
});
|
||||
|
||||
ipcMain.on('choose-folder', async (event, request: IpcRequest<void>) => {
|
||||
dialog.showOpenDialog({properties: ['openDirectory']}).then(res => {
|
||||
dialog.showOpenDialog({properties: ['openDirectory'],}).then(res => {
|
||||
UtilsService.getInstance().ipcSend(request.responceChannel, {success: true, data: res});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { BS_APP_ID, BS_DEPOT } from "../constants";
|
||||
import path from "path";
|
||||
import { BSVersion } from 'shared/bs-version.interface';
|
||||
import { BSVersion, PartialBSVersion } from 'shared/bs-version.interface';
|
||||
import { UtilsService } from "./utils.service";
|
||||
import { ChildProcessWithoutNullStreams, spawn, spawnSync } from "child_process";
|
||||
import log from "electron-log";
|
||||
@@ -9,6 +9,7 @@ import { ctrlc } from "ctrlc-windows";
|
||||
import { BSLocalVersionService } from "./bs-local-version.service";
|
||||
import isOnline from 'is-online';
|
||||
import { WindowManagerService } from "./window-manager.service";
|
||||
import { copy, copySync } from "fs-extra";
|
||||
|
||||
export class BSInstallerService{
|
||||
|
||||
@@ -159,6 +160,29 @@ export class BSInstallerService{
|
||||
})
|
||||
}
|
||||
|
||||
public async importVersion(path: string): Promise<PartialBSVersion>{
|
||||
|
||||
const rawBsVersion = await this.localVersionService.getVersionOfBSFolder(path);
|
||||
|
||||
if(!rawBsVersion){ throw new Error("NOT_BS_FOLDER"); }
|
||||
|
||||
const originalPath = await this.localVersionService.getVersionPath(rawBsVersion);
|
||||
let destPath = originalPath;
|
||||
let folderExist = this.utils.pathExist(destPath);
|
||||
let i = 0;
|
||||
|
||||
while(folderExist){
|
||||
i++;
|
||||
destPath = `${originalPath} (${i})`;
|
||||
folderExist = this.utils.pathExist(destPath);
|
||||
}
|
||||
|
||||
await copy(path, destPath, {dereference: true});
|
||||
|
||||
return rawBsVersion;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { BSVersionLibService } from "./bs-version-lib.service";
|
||||
import { BSVersion } from 'shared/bs-version.interface';
|
||||
import { BSVersion, PartialBSVersion } from 'shared/bs-version.interface';
|
||||
import { InstallationLocationService } from "./installation-location.service";
|
||||
import { SteamService } from "./steam.service";
|
||||
import { UtilsService } from "./utils.service";
|
||||
@@ -33,27 +33,32 @@ export class BSLocalVersionService{
|
||||
return BSLocalVersionService.instance;
|
||||
}
|
||||
|
||||
private constructor(){
|
||||
this.installLocationService = InstallationLocationService.getInstance();
|
||||
this.utilsService = UtilsService.getInstance();
|
||||
this.steamService = SteamService.getInstance();
|
||||
this.oculusService = OculusService.getInstance();
|
||||
this.remoteVersionService = BSVersionLibService.getInstance();
|
||||
this.configService = ConfigurationService.getInstance();
|
||||
}
|
||||
private constructor(){
|
||||
this.installLocationService = InstallationLocationService.getInstance();
|
||||
this.utilsService = UtilsService.getInstance();
|
||||
this.steamService = SteamService.getInstance();
|
||||
this.oculusService = OculusService.getInstance();
|
||||
this.remoteVersionService = BSVersionLibService.getInstance();
|
||||
this.configService = ConfigurationService.getInstance();
|
||||
}
|
||||
|
||||
private async getVersionOfBSFolder(bsPath: string): Promise<string>{
|
||||
|
||||
|
||||
public async getVersionOfBSFolder(bsPath: string): Promise<PartialBSVersion>{
|
||||
const versionFilePath = path.join(bsPath, 'Beat Saber_Data', 'globalgamemanagers');
|
||||
if(!this.utilsService.pathExist(versionFilePath)){ return null; }
|
||||
const versionsAvailable = await this.remoteVersionService.getAvailableVersions();
|
||||
return new Promise<string>(resolve => {
|
||||
return new Promise<PartialBSVersion>(resolve => {
|
||||
const stream = createReadStream(versionFilePath);
|
||||
let findVersion: string = null;
|
||||
let findVersion: PartialBSVersion = null;
|
||||
stream.on('data', (line) => {
|
||||
line = line.toString();
|
||||
for(const version of versionsAvailable){
|
||||
if(line.includes(version.BSVersion)){
|
||||
findVersion = version.BSVersion;
|
||||
findVersion = {BSVersion: version.BSVersion};
|
||||
if(findVersion.BSVersion !== path.basename(bsPath)){
|
||||
findVersion.name = path.basename(bsPath);
|
||||
}
|
||||
stream.close();
|
||||
stream.destroy();
|
||||
break;
|
||||
@@ -118,7 +123,7 @@ export class BSLocalVersionService{
|
||||
const steamBsVersion = await this.getVersionOfBSFolder(steamBsFolder);
|
||||
|
||||
if(!steamBsVersion){ return null; }
|
||||
const version = await this.remoteVersionService.getVersionDetails(steamBsVersion);
|
||||
const version = await this.remoteVersionService.getVersionDetails(steamBsVersion.BSVersion);
|
||||
if(!version){ return null; }
|
||||
return {...version, steam: true};
|
||||
}
|
||||
@@ -128,7 +133,7 @@ export class BSLocalVersionService{
|
||||
if(!oculusBsFolder){ return null; }
|
||||
const oculusBsVersion = await this.getVersionOfBSFolder(oculusBsFolder);
|
||||
if(!oculusBsVersion){ return null; }
|
||||
const version = await this.remoteVersionService.getVersionDetails(oculusBsVersion);
|
||||
const version = await this.remoteVersionService.getVersionDetails(oculusBsVersion.BSVersion);
|
||||
if(!version){ return null; }
|
||||
return {...version, oculus: true};
|
||||
}
|
||||
@@ -151,7 +156,7 @@ export class BSLocalVersionService{
|
||||
log.info("try get version from folder", f);
|
||||
const rawVersion = await this.getVersionOfBSFolder(f);
|
||||
if(!rawVersion){ continue; }
|
||||
const bsVersion = {...await this.remoteVersionService.getVersionDetails(rawVersion)};
|
||||
const bsVersion = {...await this.remoteVersionService.getVersionDetails(rawVersion.BSVersion)};
|
||||
if(!bsVersion){ continue; }
|
||||
bsVersion.name = path.basename(f) !== bsVersion.BSVersion ? path.basename(f) : undefined;
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ import { BsmImage } from "../shared/bsm-image.component";
|
||||
import { useTranslation } from "renderer/hooks/use-translation.hook";
|
||||
import { BsmIcon } from "../svgs/bsm-icon.component";
|
||||
import { LinkOpenerService } from 'renderer/services/link-opener.service';
|
||||
import { motion } from 'framer-motion';
|
||||
import { GlowEffect } from '../shared/glow-effect.component';
|
||||
|
||||
export const AvailableVersionItem = memo(function AvailableVersionItem(props: {version: BSVersion}) {
|
||||
|
||||
@@ -15,6 +17,7 @@ export const AvailableVersionItem = memo(function AvailableVersionItem(props: {v
|
||||
const linkOpener = LinkOpenerService.getInstance();
|
||||
|
||||
const [selected, setSelected] = useState(false);
|
||||
const [hovered, setHovered] = useState(false);
|
||||
const t = useTranslation();
|
||||
|
||||
const formatedDate = (() => { return dateFormat(+props.version.ReleaseDate*1000, "ddd. d mmm yyyy"); })()
|
||||
@@ -38,8 +41,8 @@ export const AvailableVersionItem = memo(function AvailableVersionItem(props: {v
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<li className="group relative w-72 h-60 transition-transform active:scale-[.98]" onClick={toggleSelect}>
|
||||
<span className={`absolute glow-on-hover group-hover:opacity-100 ${selected && "!opacity-100"}`}/>
|
||||
<motion.li className="group relative w-72 h-60 transition-transform active:scale-[.98]" onClick={toggleSelect} onHoverStart={() => setHovered(true)} onHoverEnd={() => setHovered(false)}>
|
||||
<GlowEffect visible={hovered} className="absolute"/>
|
||||
<div className={`relative flex flex-col overflow-hidden rounded-md w-72 h-60 cursor-pointer group-hover:shadow-none duration-300 bg-light-main-color-2 dark:bg-main-color-2 ${!selected && "shadow-lg shadow-gray-900"}`}>
|
||||
<BsmImage image={props.version.ReleaseImg ? props.version.ReleaseImg : defaultImage} errorImage={defaultImage} placeholder={defaultImage} className="absolute top-0 right-0 w-full h-full opacity-40 blur-xl object-cover" loading="lazy"/>
|
||||
<BsmImage image={props.version.ReleaseImg ? props.version.ReleaseImg : defaultImage} errorImage={defaultImage} placeholder={defaultImage} className="bg-black w-full h-3/4 object-cover" loading="lazy"/>
|
||||
@@ -56,6 +59,6 @@ export const AvailableVersionItem = memo(function AvailableVersionItem(props: {v
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</motion.li>
|
||||
)
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { BSVersion } from 'shared/bs-version.interface';
|
||||
import { useEffect, useState } from "react"
|
||||
import { BSVersionManagerService } from "renderer/services/bs-version-manager.service";
|
||||
import { filter, take } from "rxjs/operators";
|
||||
import { filter } from "rxjs/operators";
|
||||
import { AvailableVersionItem } from "./available-version-item.component";
|
||||
|
||||
export function AvailableVersionsSlide(props: {year: string}) {
|
||||
@@ -11,7 +11,7 @@ export function AvailableVersionsSlide(props: {year: string}) {
|
||||
const versionsService = BSVersionManagerService.getInstance();
|
||||
|
||||
useEffect(() => {
|
||||
versionsService.availableVersions$.pipe(filter(versions => !!versions?.length), take(1)).subscribe(() => {
|
||||
versionsService.availableVersions$.pipe(filter(versions => !!versions?.length)).subscribe(() => {
|
||||
setAvailableVersions(versionsService.getAvaibleVersionsOfYear(props.year));
|
||||
});
|
||||
}, [])
|
||||
|
||||
+14
-14
@@ -1,29 +1,29 @@
|
||||
import { useEffect, useState } from "react"
|
||||
import { BSVersionManagerService } from "../../services/bs-version-manager.service";
|
||||
import { AvailableVersionsSlide } from "./available-versions-slide.component";
|
||||
import { filter, take } from "rxjs/operators";
|
||||
import { filter} from "rxjs/operators";
|
||||
import { TabNavBar } from "../shared/tab-nav-bar.component";
|
||||
|
||||
export function AvailableVersionsSlider() {
|
||||
|
||||
const [availableYears, setAvailableYears] = useState([]);
|
||||
const [yearIndex, setYearIndex] = useState(0);
|
||||
const versionManagerService = BSVersionManagerService.getInstance();
|
||||
|
||||
const versionManagerService = BSVersionManagerService.getInstance();
|
||||
const [availableYears, setAvailableYears] = useState([]);
|
||||
const [yearIndex, setYearIndex] = useState(0);
|
||||
|
||||
const setSelectedYear = (index: number) => {
|
||||
setYearIndex(index);
|
||||
}
|
||||
const setSelectedYear = (index: number) => {
|
||||
setYearIndex(index);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
versionManagerService.availableVersions$.pipe(filter(versions => !!versions?.length), take(1)).subscribe(() => {
|
||||
setAvailableYears(versionManagerService.getAvailableYears());
|
||||
});
|
||||
}, [])
|
||||
useEffect(() => {
|
||||
versionManagerService.availableVersions$.pipe(filter(versions => !!versions?.length)).subscribe(() => {
|
||||
setAvailableYears(versionManagerService.getAvailableYears());
|
||||
});
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="w-full h-fit max-h-full flex flex-col items-center grow min-h-0">
|
||||
<TabNavBar className="mb-3" tabIndex={yearIndex} tabsText={availableYears} onTabChange={setSelectedYear}/>
|
||||
<div className="w-full h-fit max-h-full flex flex-col items-center grow min-h-0 gap-3">
|
||||
<TabNavBar tabIndex={yearIndex} tabsText={availableYears} onTabChange={setSelectedYear}/>
|
||||
<ol className="w-full min-h-0 flex transition-transform duration-300" style={{transform: `translate(${-(yearIndex * 100)}%, 0)`}}>
|
||||
{ availableYears.map(year =>
|
||||
<AvailableVersionsSlide key={year} year={year}/>
|
||||
|
||||
@@ -14,6 +14,7 @@ import { MAP_DIFFICULTIES_COLORS } from "renderer/partials/maps/map-difficulties
|
||||
import { BsmButton } from "../shared/bsm-button.component"
|
||||
import equal from "fast-deep-equal/es6";
|
||||
import clone from "rfdc";
|
||||
import { GlowEffect } from "../shared/glow-effect.component"
|
||||
|
||||
export type Props = {
|
||||
className?: string,
|
||||
@@ -214,7 +215,7 @@ export function FilterPanel({className, ref, playlist = false, filter, onChange,
|
||||
{onApply && (
|
||||
<div className="inline float-right relative w-fit h-fit mt-2">
|
||||
<BsmButton className="inline float-right rounded-md font-bold px-1 py-0.5 text-sm" text="Appliquer" typeColor="primary" withBar={false} onClick={handleApply}/>
|
||||
{haveChanged && <div className="glow-on-hover !opacity-100"></div>}
|
||||
<GlowEffect visible={haveChanged}/>
|
||||
</div>
|
||||
)}
|
||||
</motion.div>
|
||||
|
||||
+15
-7
@@ -11,6 +11,7 @@ import { BsmImage } from "../shared/bsm-image.component"
|
||||
import BeatConflict from "../../../../assets/images/apngs/beat-conflict.png"
|
||||
import { BsmButton } from "../shared/bsm-button.component"
|
||||
import { useTranslation } from "renderer/hooks/use-translation.hook"
|
||||
import BeatWaitingImg from "../../../../assets/images/apngs/beat-waiting.png"
|
||||
|
||||
type Props = {
|
||||
version: BSVersion,
|
||||
@@ -26,7 +27,7 @@ export const LocalMapsListPanel = forwardRef(({version, className, filter, searc
|
||||
|
||||
const ref = useRef(null)
|
||||
const isVisible = useInView(ref, {once: true});
|
||||
const [maps, setMaps] = useState<BsmLocalMap[]>([]);
|
||||
const [maps, setMaps] = useState<BsmLocalMap[]>(null);
|
||||
const [subs] = useState<Subscription[]>([]);
|
||||
const [selectedMaps, setSelectedMaps] = useState([]);
|
||||
const t = useTranslation();
|
||||
@@ -51,7 +52,7 @@ export const LocalMapsListPanel = forwardRef(({version, className, filter, searc
|
||||
}
|
||||
|
||||
return () => {
|
||||
setMaps(() => []);
|
||||
setMaps(() => null);
|
||||
subs.forEach(s => s.unsubscribe());
|
||||
mapsDownloader.removeOnMapDownloadedListene(loadMaps);
|
||||
}
|
||||
@@ -248,11 +249,18 @@ export const LocalMapsListPanel = forwardRef(({version, className, filter, searc
|
||||
<div ref={ref} className={className}>
|
||||
<ul className="p-3 w-full grow flex flex-wrap justify-center content-start gap-2 overflow-y-scroll scrollbar-thin scrollbar-thumb-rounded-full scrollbar-thumb-neutral-900">
|
||||
{maps?.length ? renderMaps() : (
|
||||
<div className="h-full flex flex-col items-center justify-center flex-wrap gap-1">
|
||||
<BsmImage className="h-32" image={BeatConflict}/>
|
||||
<span className="font-bold">{t("pages.version-viewer.maps.tabs.maps.empty-maps.text")}</span>
|
||||
<BsmButton className="font-bold rounded-md p-2" text="pages.version-viewer.maps.tabs.maps.empty-maps.button" typeColor="primary" withBar={false} onClick={e => {e.preventDefault(); mapsDownloader.openDownloadMapModal(version)}}/>
|
||||
</div>
|
||||
maps === null ? (
|
||||
<div className="h-full flex flex-col items-center justify-center flex-wrap gap-1">
|
||||
<img className="w-32 h-32 spin-loading" src={BeatWaitingImg} alt=" "/>
|
||||
<span className="font-bold">{t("modals.download-maps.loading-maps")}</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="h-full flex flex-col items-center justify-center flex-wrap gap-1">
|
||||
<BsmImage className="h-32" image={BeatConflict}/>
|
||||
<span className="font-bold">{t("pages.version-viewer.maps.tabs.maps.empty-maps.text")}</span>
|
||||
<BsmButton className="font-bold rounded-md p-2" text="pages.version-viewer.maps.tabs.maps.empty-maps.button" typeColor="primary" withBar={false} onClick={e => {e.preventDefault(); mapsDownloader.openDownloadMapModal(version)}}/>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -20,6 +20,7 @@ import { useTranslation } from "renderer/hooks/use-translation.hook";
|
||||
import { MAP_DIFFICULTIES } from "renderer/partials/maps/map-difficulties/map-difficulties";
|
||||
import { MAP_DIFFICULTIES_COLORS } from "renderer/partials/maps/map-difficulties/map-difficulties-colors"
|
||||
import useDoubleClick from "use-double-click";
|
||||
import { GlowEffect } from "../shared/glow-effect.component";
|
||||
|
||||
export type ParsedMapDiff = {type: BsvMapDifficultyType, name: string, stars: number}
|
||||
|
||||
@@ -148,7 +149,7 @@ export const MapItem = memo(({hash, title, autor, songAutor, coverUrl, songUrl,
|
||||
|
||||
return (
|
||||
<motion.li className="relative h-[100px] min-w-[370px] shrink-0 grow basis-0 text-white group cursor-pointer" onHoverStart={() => setHovered(true)} onHoverEnd={() => setHovered(false)} style={{zIndex: hovered && 5, transform: "translateZ(0) scale(1.0, 1.0)", backfaceVisibility: "hidden"}}>
|
||||
{(hovered || selected) && onSelected && <motion.span className="glow-on-hover !transition-none" animate={{opacity: 1}} transition={{duration: .2, ease: "linear"}}/>}
|
||||
<GlowEffect visible={hovered || selected && !!onSelected}/>
|
||||
<AnimatePresence>
|
||||
{(diffsPanelHovered || bottomBarHovered) && (
|
||||
<motion.ul key={hash} className="absolute top-[calc(100%-10px)] w-full h-fit max-h-[200%] pt-4 pb-2 px-2 overflow-y-scroll bg-light-main-color-3 dark:bg-main-color-3 text-main-color-1 dark:text-current brightness-125 rounded-md flex flex-col gap-3 scrollbar-thin scrollbar-thumb-rounded-full scrollbar-thumb-neutral-900 shadow-sm shadow-black" initial={{opacity: 0}} animate={{opacity: 1}} exit={{opacity: 0}} transition={{duration: .15}} onHoverStart={diffsPanelHoverStart} onHoverEnd={diffsPanelHoverEnd}>
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { BsmButton } from 'renderer/components/shared/bsm-button.component'
|
||||
import { BsmImage } from 'renderer/components/shared/bsm-image.component'
|
||||
import { useTranslation } from 'renderer/hooks/use-translation.hook'
|
||||
import { ModalComponent, ModalExitCode } from 'renderer/services/modale.service'
|
||||
import BeatImpatient from "../../../../../assets/images/apngs/beat-impatient.png"
|
||||
|
||||
export const ImportVersionModal: ModalComponent<void> = ({resolver}) => {
|
||||
|
||||
const t = useTranslation();
|
||||
|
||||
return (
|
||||
<form>
|
||||
<h1 className="text-3xl uppercase tracking-wide w-full text-center text-gray-800 dark:text-gray-200">{t("modals.bs-import-version.title")}</h1>
|
||||
<BsmImage className="mx-auto h-20" image={BeatImpatient}/>
|
||||
<p className="max-w-sm text-gray-800 dark:text-gray-200">{t("modals.bs-import-version.description")}</p>
|
||||
<div className="grid grid-flow-col grid-cols-2 gap-4 mt-4">
|
||||
<BsmButton typeColor="cancel" className="rounded-md text-center transition-all" onClick={() => resolver({exitCode: ModalExitCode.CANCELED})} withBar={false} text="misc.cancel"/>
|
||||
<BsmButton typeColor="primary" className="rounded-md text-center transition-all" onClick={() => resolver({exitCode: ModalExitCode.COMPLETED})} withBar={false} text="modals.bs-import-version.buttons.submit"/>
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
@@ -20,7 +20,7 @@ export function NavBar() {
|
||||
const color = useThemeColor("first-color");
|
||||
const t = useTranslation();
|
||||
|
||||
const _ = useObservable(I18nService.getInstance().currentLanguage$)
|
||||
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'>
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import { BsmIcon } from "../svgs/bsm-icon.component"
|
||||
import { useThemeColor } from "renderer/hooks/use-theme-color.hook";
|
||||
import { getCorrectTextColor } from "renderer/helpers/correct-text-color"
|
||||
import { GlowEffect } from "./glow-effect.component";
|
||||
import { motion } from "framer-motion";
|
||||
import { useState } from "react";
|
||||
|
||||
type Props = {className?:string, checked?: boolean, onChange?: (val: boolean) => void, disabled?: boolean}
|
||||
|
||||
export function BsmCheckbox({className, checked, onChange, disabled} : Props) {
|
||||
|
||||
const [hovered, setHovered] = useState(false);
|
||||
const checkedColor = useThemeColor("first-color");
|
||||
const iconColor = getCorrectTextColor(checkedColor);
|
||||
|
||||
@@ -15,12 +19,12 @@ export function BsmCheckbox({className, checked, onChange, disabled} : Props) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`${className} group-one`}>
|
||||
{!disabled && <span className="glow-on-hover !w-[calc(100%+6px)] !h-[calc(100%+6px)] !-top-[3px] !-left-[3px] group-one-hover:opacity-100"/>}
|
||||
<motion.div className={`${className} group-one`} onHoverStart={() => setHovered(true)} onHoverEnd={() => setHovered(false)}>
|
||||
<GlowEffect visible={!disabled && hovered} className="!w-[calc(100%+6px)] !h-[calc(100%+6px)] !-top-[3px] !-left-[3px]"/>
|
||||
|
||||
<span className={`w-full h-full flex items-center justify-center rounded-md border-2 border-current overflow-hidden bg-light-main-color-3 dark:bg-main-color-1 ${disabled ? "opacity-30 cursor-not-allowed" : "cursor-pointer"}`} onClickCapture={e => {e.stopPropagation(); handleClick()}}>
|
||||
<BsmIcon className="w-full h-full" icon="check" style={{color: iconColor, backgroundColor: checkedColor, visibility: checked ? "visible" : "hidden"}}/>
|
||||
</span>
|
||||
</div>
|
||||
</motion.div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { AnimatePresence, motion } from "framer-motion"
|
||||
|
||||
type Props = {
|
||||
visible?: boolean,
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function GlowEffect({visible, className}: Props) {
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{visible && <motion.div transition={{duration: .1}} initial={{opacity: 0}} animate={{opacity: 1}} exit={{opacity: 0}} className={`${className} glow-on-hover`}/>}
|
||||
</AnimatePresence>
|
||||
)
|
||||
}
|
||||
@@ -47,10 +47,11 @@ 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";
|
||||
import { SyncIcon } from "./icons/sync-icon.component";
|
||||
|
||||
export type BsmIconType = BsvMapCharacteristic | (
|
||||
"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"|
|
||||
"terminal"|"desktop"|"oculus"|"add"|"cross"|"task"|"github"|"close"|"thumbUpFill"|"timerFill"|"pause"|"twitter"|"sync"|
|
||||
"copy"|"steam"|"edit"|"export"|"patreon"|"search"|"bsMapDifficulty"|"link"|"unlink"|"download"|"filter"|"mee6"|
|
||||
"fr-FR-flag"|"es-ES-flag"|"en-US-flag"|"en-EN-flag"
|
||||
);
|
||||
@@ -105,6 +106,7 @@ export const BsmIcon = memo(({className, icon, style}: {className?: string, icon
|
||||
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}/> }
|
||||
if(icon === "sync"){ return <SyncIcon className={className} style={style}/> }
|
||||
return <TrashIcon className={className} style={style}/>
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { CSSProperties } from "react";
|
||||
|
||||
export function SyncIcon(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">
|
||||
<path fill="currentColor" d="M25.625 32.542q-.917.416-1.667-.084t-.75-1.625q0-.5.354-1 .355-.5.855-.75 2.666-1.25 4.187-3.75 1.521-2.5 1.521-5.458 0-1.833-.854-3.708-.854-1.875-2.313-3.375l-.791-.709v2.75q0 .667-.459 1.105-.458.437-1.083.437-.667 0-1.104-.437-.438-.438-.438-1.105V8.125q0-.792.542-1.312.542-.521 1.292-.521h6.75q.666 0 1.104.437.437.438.437 1.104 0 .667-.437 1.084-.438.416-1.104.416h-3l.375.417q2.416 2.292 3.604 4.938 1.187 2.645 1.187 5.187 0 4.208-2.229 7.583t-5.979 5.084ZM8.417 33.708q-.667 0-1.084-.437-.416-.438-.416-1.104 0-.667.416-1.084.417-.416 1.084-.416h2.958L11 30.333q-2.5-2.208-3.667-4.729-1.166-2.521-1.166-5.479 0-4.208 2.271-7.604 2.27-3.396 5.979-5.063.916-.416 1.645.105.73.52.73 1.604 0 .5-.334 1-.333.5-.833.75-2.625 1.25-4.187 3.75-1.563 2.5-1.563 5.458 0 2.25.854 4.042.854 1.791 2.396 3.208l.792.542v-2.75q0-.667.458-1.105.458-.437 1.125-.437.625 0 1.062.437.438.438.438 1.105v6.708q0 .792-.542 1.313-.541.52-1.291.52Z"/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
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 { useTranslation } from "renderer/hooks/use-translation.hook";
|
||||
|
||||
@@ -16,7 +17,7 @@ export function LaunchModToogle({onClick, active, text, icon, infoText}: props)
|
||||
|
||||
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]"}`}/>
|
||||
<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'/>
|
||||
<span className='w-fit min-w-fit text-lg font-bold uppercase tracking-wide italic '>{t(text)}</span>
|
||||
|
||||
@@ -50,8 +50,6 @@
|
||||
width: calc(100% + 8px);
|
||||
height: calc(100% + 8px);
|
||||
animation: glowing 20s linear infinite;
|
||||
opacity: 0;
|
||||
transition: opacity .2s ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes glowing {
|
||||
|
||||
@@ -7,11 +7,23 @@ 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 { BsmDropdownButton } from "renderer/components/shared/bsm-dropdown-button.component";
|
||||
import { ProgressBarService } from "renderer/services/progress-bar.service";
|
||||
import { ModalExitCode, ModalService } from "renderer/services/modale.service";
|
||||
import { ImportVersionModal } from "renderer/components/modal/modal-types/import-version-modal.component";
|
||||
import { IpcService } from "renderer/services/ipc.service";
|
||||
import { NotificationService } from "renderer/services/notification.service";
|
||||
import { timer } from "rxjs";
|
||||
|
||||
export function AvailableVersionsList() {
|
||||
|
||||
const bsDownloaderService: BsDownloaderService = BsDownloaderService.getInstance();
|
||||
const versionManagerService: BSVersionManagerService = BSVersionManagerService.getInstance();
|
||||
const bsDownloaderService = BsDownloaderService.getInstance();
|
||||
const versionManagerService = BSVersionManagerService.getInstance();
|
||||
const progressBar = ProgressBarService.getInstance();
|
||||
const modal = ModalService.getInsance();
|
||||
const ipc = IpcService.getInstance();
|
||||
const installer = BsDownloaderService.getInstance();
|
||||
const notification = NotificationService.getInstance();
|
||||
|
||||
const [versionSelected, setVersionSelected] = useState(null as BSVersion);
|
||||
const [downloading, setDownloading] = useState(false);
|
||||
@@ -24,30 +36,70 @@ export function AvailableVersionsList() {
|
||||
});
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const versionSelectedSub = bsDownloaderService.selectedBsVersion$.subscribe(version => {
|
||||
setVersionSelected(version);
|
||||
});
|
||||
useEffect(() => {
|
||||
const versionSelectedSub = bsDownloaderService.selectedBsVersion$.subscribe(version => {
|
||||
setVersionSelected(version);
|
||||
});
|
||||
|
||||
return () => {
|
||||
versionSelectedSub.unsubscribe();
|
||||
}
|
||||
}, [])
|
||||
|
||||
|
||||
const importVersion = async () => {
|
||||
if(!progressBar.require()){ return; }
|
||||
|
||||
const modalRes = await modal.openModal(ImportVersionModal);
|
||||
|
||||
if(modalRes.exitCode !== ModalExitCode.COMPLETED){ return; }
|
||||
|
||||
const folderRes = await ipc.send<{canceled: boolean, filePaths: string[]}>("choose-folder");
|
||||
|
||||
if(!folderRes.success || folderRes.data.canceled || !folderRes.data.filePaths?.length){ return; }
|
||||
|
||||
notification.notifySuccess({title: "notifications.bs-import-version.success.start-import.title", desc: "notifications.bs-import-version.success.start-import.desc", duration: 6_000});
|
||||
|
||||
progressBar.showFake(.008);
|
||||
|
||||
const toImport = folderRes.data.filePaths.at(0);
|
||||
|
||||
const imported = await installer.importVersion(toImport);
|
||||
|
||||
if(imported){
|
||||
versionManagerService.askInstalledVersions();
|
||||
notification.notifySuccess({title: "notifications.bs-import-version.success.imported.title", duration: 3_000});
|
||||
progressBar.complete();
|
||||
await timer(400).toPromise();
|
||||
}
|
||||
else{
|
||||
notification.notifyError({title: "notifications.types.error", desc: "notifications.bs-import-version.errors.import-error.desc"})
|
||||
}
|
||||
|
||||
progressBar.hide(true);
|
||||
|
||||
return () => {
|
||||
versionSelectedSub.unsubscribe();
|
||||
}
|
||||
}, [])
|
||||
|
||||
|
||||
return (
|
||||
<div className="relative h-full w-full flex items-center flex-col pt-2">
|
||||
<Slideshow className="absolute w-full h-full top-0"/>
|
||||
<h1 className="text-gray-100 text-2xl mb-4 z-[1]">{t("pages.available-versions.title")}</h1>
|
||||
<AvailableVersionsSlider/>
|
||||
<Slideshow className="absolute w-full h-full top-0"/>
|
||||
<h1 className="text-gray-100 text-2xl mb-4 z-[1]">{t("pages.available-versions.title")}</h1>
|
||||
|
||||
<AnimatePresence>
|
||||
{ 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>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
<AvailableVersionsSlider/>
|
||||
|
||||
<AnimatePresence>
|
||||
{ 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>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
<BsmDropdownButton className="absolute top-5 right-5 h-9 w-9 bg-light-main-color-2 dark:bg-main-color-2 rounded-md" icon="settings" items={[
|
||||
{icon: "sync", text: "pages.available-versions.dropdown.refresh", onClick: () => versionManagerService.askAvailableVersions()},
|
||||
{icon: "download", text: "pages.available-versions.dropdown.import-version", onClick: importVersion}
|
||||
]}/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -156,4 +156,9 @@ export class BsDownloaderService{
|
||||
return this.ipcService.send<string>("bs-download.set-installation-folder", {args: path});
|
||||
}
|
||||
|
||||
public async importVersion(pathToImport: string): Promise<boolean>{
|
||||
const res = await this.ipcService.send<void>("bs-download.import-version", {args: pathToImport});
|
||||
return res.success;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
export interface BSVersion {
|
||||
BSVersion: string,
|
||||
export interface PartialBSVersion {
|
||||
BSVersion: string,
|
||||
name?: string
|
||||
}
|
||||
|
||||
export interface BSVersion extends PartialBSVersion {
|
||||
BSManifest?: string,
|
||||
ReleaseURL?: string,
|
||||
ReleaseImg?: string,
|
||||
@@ -7,6 +11,5 @@ export interface BSVersion {
|
||||
year?: string,
|
||||
steam?: boolean,
|
||||
oculus?: boolean,
|
||||
name?: string,
|
||||
color?: string
|
||||
}
|
||||
Reference in New Issue
Block a user