mirror of
https://github.com/Zagrios/bs-manager.git
synced 2026-07-03 14:08:25 +02:00
[feature-75] rework fontend functions to import maps
This commit is contained in:
@@ -14,7 +14,7 @@ import { BsmButton } from "../shared/bsm-button.component";
|
||||
import { MapIcon } from "../svgs/icons/map-icon.component";
|
||||
import { PlaylistIcon } from "../svgs/icons/playlist-icon.component";
|
||||
import { useObservable } from "renderer/hooks/use-observable.hook";
|
||||
import { BehaviorSubject, of } from "rxjs";
|
||||
import { BehaviorSubject, lastValueFrom, of } from "rxjs";
|
||||
import { LocalPlaylistsListPanel, LocalPlaylistsListRef } from "./playlists/local-playlists-list-panel.component";
|
||||
import { PlaylistsManagerService } from "renderer/services/playlists-manager.service";
|
||||
import { BsmLocalMap } from "shared/models/maps/bsm-local-map.interface";
|
||||
@@ -25,6 +25,7 @@ import { LocalPlaylistFilter, LocalPlaylistFilterPanel } from "./playlists/local
|
||||
import { noop } from "shared/helpers/function.helpers";
|
||||
import { Dropzone } from "../shared/dropzone.component";
|
||||
import { NotificationService } from "renderer/services/notification.service";
|
||||
import { logRenderError } from "renderer";
|
||||
|
||||
type Props = {
|
||||
readonly version?: BSVersion;
|
||||
@@ -122,7 +123,8 @@ export function MapsPlaylistsPanel({ version, isActive }: Props) {
|
||||
return;
|
||||
}
|
||||
|
||||
await mapsManager.importMaps(paths, version);
|
||||
const import$ = mapsManager.importMaps(paths, version);
|
||||
return lastValueFrom(import$).catch(logRenderError);
|
||||
}
|
||||
|
||||
const dropDownItems = ((): DropDownItem[] => {
|
||||
|
||||
+10
-28
@@ -5,7 +5,7 @@ import { BsmLocalMap } from "shared/models/maps/bsm-local-map.interface";
|
||||
import { Subscription, BehaviorSubject, lastValueFrom } from "rxjs";
|
||||
import { MapFilter } from "shared/models/maps/beat-saver.model";
|
||||
import { MapsDownloaderService } from "renderer/services/maps-downloader.service";
|
||||
import { last, tap } from "rxjs/operators";
|
||||
import { bufferTime, last, tap } from "rxjs/operators";
|
||||
import { useTranslation } from "renderer/hooks/use-translation.hook";
|
||||
import BeatConflict from "../../../../../assets/images/apngs/beat-conflict.png";
|
||||
import { BsmImage } from "../../shared/bsm-image.component";
|
||||
@@ -104,46 +104,28 @@ export const LocalMapsListPanel = forwardRef<LocalMapsListPanelRef, Props>(({ ve
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
let sub: Subscription;
|
||||
const subs: Subscription[] = [];
|
||||
|
||||
if(isActiveOnce){
|
||||
sub = mapsDownloader.lastDownloadedMap$.subscribe({ next: ({map, version: targetVersion}) => {
|
||||
subs.push(mapsDownloader.lastDownloadedMap$.subscribe({ next: ({map, version: targetVersion}) => {
|
||||
if (!equal(targetVersion, version)) {
|
||||
return;
|
||||
}
|
||||
setMaps((maps$.value ? [map, ...maps$.value] : [map]));
|
||||
}});
|
||||
mapsManager.addImportListener(importListener);
|
||||
}}));
|
||||
|
||||
subs.push(mapsManager.$onMapImported(version).pipe(bufferTime(500)).subscribe({ next: newMaps => {
|
||||
const maps = (maps$.value ?? []).filter(map => !newMaps.some(newMap => newMap.path === map.path));
|
||||
setMaps([...newMaps, ...maps]);
|
||||
}}));
|
||||
}
|
||||
|
||||
return () => {
|
||||
sub?.unsubscribe();
|
||||
mapsManager.removeImportListener(importListener);
|
||||
subs.forEach(s => s.unsubscribe());
|
||||
}
|
||||
|
||||
}, [isActiveOnce, version])
|
||||
|
||||
const importListener = (importMaps: BsmLocalMap[], targetVersion?: BSVersion) => {
|
||||
if (!equal(targetVersion, version)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!maps$.value) {
|
||||
setMaps(importMaps);
|
||||
return;
|
||||
}
|
||||
|
||||
const mapsCopy = [ ...maps$.value ];
|
||||
for (const importMap of importMaps) {
|
||||
const index = mapsCopy.findIndex(map => map.hash === importMap.hash);
|
||||
if (index > -1) {
|
||||
mapsCopy.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
setMaps([...importMaps, ...mapsCopy]);
|
||||
};
|
||||
|
||||
const loadMaps = () => {
|
||||
setMaps(null);
|
||||
loadPercent$.next(0);
|
||||
|
||||
@@ -9,12 +9,13 @@ import { DeleteMapsModal } from "renderer/components/modal/modal-types/delete-ma
|
||||
import { ProgressBarService } from "./progress-bar.service";
|
||||
import { NotificationService } from "./notification.service";
|
||||
import { ConfigurationService } from "./configuration.service";
|
||||
import { map, last, catchError, tap } from "rxjs/operators";
|
||||
import { map, last, catchError, filter, distinctUntilChanged } from "rxjs/operators";
|
||||
import { ProgressionInterface } from "shared/models/progress-bar";
|
||||
import { FolderLinkState, VersionFolderLinkerService } from "./version-folder-linker.service";
|
||||
import { SongDetails } from "shared/models/maps";
|
||||
import { Progression } from "main/helpers/fs.helpers";
|
||||
import { DeleteDuplicateMapsModal } from "renderer/components/modal/modal-types/delete-duplicate-maps-modal.component";
|
||||
import equal from "fast-deep-equal";
|
||||
|
||||
export class MapsManagerService {
|
||||
private static instance: MapsManagerService;
|
||||
@@ -29,8 +30,6 @@ export class MapsManagerService {
|
||||
public static readonly REMEMBER_CHOICE_DELETE_MAP_KEY = "not-confirm-delete-map";
|
||||
public static readonly RELATIVE_MAPS_FOLDER = window.electron.path.join("Beat Saber_Data", "CustomLevels");
|
||||
|
||||
private readonly IMPORT_BATCH_SIZE = 8;
|
||||
|
||||
private readonly ipcService: IpcService;
|
||||
private readonly modal: ModalService;
|
||||
private readonly progressBar: ProgressBarService;
|
||||
@@ -41,7 +40,7 @@ export class MapsManagerService {
|
||||
private readonly lastLinkedVersion$: Subject<BSVersion> = new Subject();
|
||||
private readonly lastUnlinkedVersion$: Subject<BSVersion> = new Subject();
|
||||
|
||||
private importListeners = new Set<((maps: BsmLocalMap[], version?: BSVersion) => void)>();
|
||||
private readonly lastImportedMap$: Subject<{ version: BSVersion, map: BsmLocalMap }> = new Subject();
|
||||
|
||||
private constructor() {
|
||||
this.ipcService = IpcService.getInstance();
|
||||
@@ -200,73 +199,62 @@ export class MapsManagerService {
|
||||
})
|
||||
}
|
||||
|
||||
public async importMaps(paths: string[], version?: BSVersion): Promise<void> {
|
||||
try {
|
||||
if (!this.progressBar.require()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const importObserver$ = this.ipcService.sendV2(
|
||||
"bs-maps.import-maps",
|
||||
{ paths, version }
|
||||
);
|
||||
|
||||
this.progressBar.show(importObserver$.pipe(
|
||||
catchError(() => of()),
|
||||
map(progress => ({
|
||||
progression: (progress.current / progress.total) * 100,
|
||||
label: progress.data?.songDetails?.name
|
||||
} as ProgressionInterface))
|
||||
));
|
||||
|
||||
// Processing
|
||||
let importCount = 0;
|
||||
let importTotal = 0;
|
||||
const mapBatch: BsmLocalMap[] = []; // For batching
|
||||
|
||||
await lastValueFrom(importObserver$.pipe(
|
||||
tap(progress => {
|
||||
if (!progress.data) {
|
||||
importTotal = progress.total;
|
||||
return;
|
||||
}
|
||||
|
||||
++importCount;
|
||||
mapBatch.push(progress.data);
|
||||
if (mapBatch.length >= this.IMPORT_BATCH_SIZE) {
|
||||
const currentBatch = mapBatch.splice(0, this.IMPORT_BATCH_SIZE);
|
||||
this.importListeners.forEach(
|
||||
listeners => listeners(currentBatch, version)
|
||||
);
|
||||
}
|
||||
}),
|
||||
));
|
||||
if (mapBatch.length > 0) {
|
||||
this.importListeners.forEach(listeners => listeners(mapBatch, version));
|
||||
}
|
||||
|
||||
// Done processing
|
||||
if (importCount === importTotal) {
|
||||
this.notifications.notifySuccess({
|
||||
title: "notifications.maps.import-map.titles.success",
|
||||
desc: "notifications.maps.import-map.msgs.success",
|
||||
});
|
||||
} else if (importCount > 0) {
|
||||
this.notifications.notifySuccess({
|
||||
title: "notifications.maps.import-map.titles.success",
|
||||
desc: "notifications.maps.import-map.msgs.some-success",
|
||||
});
|
||||
}
|
||||
} catch (error: any) {
|
||||
this.notifications.notifyError({
|
||||
title: "notifications.maps.import-map.titles.error",
|
||||
desc: ["invalid-zip"].includes(error?.code)
|
||||
? `notifications.maps.import-map.msgs.${error.code}`
|
||||
: "misc.unknown"
|
||||
});
|
||||
} finally {
|
||||
this.progressBar.hide();
|
||||
public importMaps(paths: string[], version?: BSVersion): Observable<Progression<BsmLocalMap>> {
|
||||
if(!this.progressBar.require()){
|
||||
return throwError(() => new Error("Another operation is already in progress (importMaps)"));
|
||||
}
|
||||
|
||||
return new Observable<Progression<BsmLocalMap>>(obs => {
|
||||
const subs: Subscription[] = [];
|
||||
(async () => {
|
||||
const import$ = this.ipcService.sendV2("bs-maps.import-maps", { paths, version });
|
||||
|
||||
this.progressBar.show(import$.pipe(
|
||||
catchError(() => of()),
|
||||
map(progress => ({
|
||||
progression: (progress.current / progress.total) * 100,
|
||||
label: progress.data?.songDetails?.name
|
||||
} as ProgressionInterface))
|
||||
));
|
||||
|
||||
subs.push(import$.subscribe(obs));
|
||||
|
||||
subs.push(import$.pipe(catchError(() => of()), map(progress => progress?.data), filter(Boolean), distinctUntilChanged((a, b) => equal(a, b))).subscribe({ next: map => {
|
||||
this.lastImportedMap$.next({ version, map });
|
||||
}}));
|
||||
|
||||
return lastValueFrom(import$);
|
||||
})()
|
||||
.then(progress => {
|
||||
if (progress.current === progress.total) {
|
||||
this.notifications.notifySuccess({
|
||||
title: "notifications.maps.import-map.titles.success",
|
||||
desc: "notifications.maps.import-map.msgs.success",
|
||||
});
|
||||
} else if (progress.current > 0) {
|
||||
this.notifications.notifySuccess({
|
||||
title: "notifications.maps.import-map.titles.success",
|
||||
desc: "notifications.maps.import-map.msgs.some-success",
|
||||
});
|
||||
}
|
||||
}).catch(err => {
|
||||
this.notifications.notifyError({
|
||||
title: "notifications.maps.import-map.titles.error",
|
||||
desc: ["invalid-zip"].includes(err?.code)
|
||||
? `notifications.maps.import-map.msgs.${err.code}`
|
||||
: "misc.unknown"
|
||||
});
|
||||
obs.error(err);
|
||||
})
|
||||
.finally(() => obs.complete());
|
||||
|
||||
return () => {
|
||||
if(subs.length){
|
||||
this.progressBar.hide();
|
||||
subs.forEach(s => s.unsubscribe());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public getMapsInfoFromHashs(hashs: string[]): Observable<SongDetails[]> {
|
||||
@@ -285,14 +273,6 @@ export class MapsManagerService {
|
||||
return lastValueFrom(this.ipcService.sendV2("unregister-maps-deep-link"));
|
||||
}
|
||||
|
||||
public addImportListener(listener: (maps: BsmLocalMap[], version?: BSVersion) => void): void {
|
||||
this.importListeners.add(listener);
|
||||
}
|
||||
|
||||
public removeImportListener(listener: (maps: BsmLocalMap[], version?: BSVersion) => void): void {
|
||||
this.importListeners.delete(listener);
|
||||
}
|
||||
|
||||
public get versionLinked$(): Observable<BSVersion> {
|
||||
return this.lastLinkedVersion$.asObservable();
|
||||
}
|
||||
@@ -301,6 +281,10 @@ export class MapsManagerService {
|
||||
return this.lastUnlinkedVersion$.asObservable();
|
||||
}
|
||||
|
||||
public $onMapImported(version: BSVersion): Observable<BsmLocalMap> {
|
||||
return this.lastImportedMap$.pipe(filter(newMap => equal(newMap.version, version)), map(m => m.map));
|
||||
}
|
||||
|
||||
public $mapsFolderLinkState(version: BSVersion): Observable<FolderLinkState> {
|
||||
return this.linker.$folderLinkedState(version, MapsManagerService.RELATIVE_MAPS_FOLDER);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user