mirror of
https://github.com/Zagrios/bs-manager.git
synced 2026-07-03 14:08:25 +02:00
[feature-107] minor fixes + use virtual scroll in playlists details view
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import path from "path";
|
||||
import { Observable, Subject, from, lastValueFrom, mergeMap, take, takeUntil, tap } from "rxjs";
|
||||
import { Observable, Subject, from, lastValueFrom, takeUntil, tap } from "rxjs";
|
||||
import { BSVersion } from "shared/bs-version.interface";
|
||||
import { BSLocalVersionService } from "../bs-local-version.service";
|
||||
import { DeepLinkService } from "../deep-link.service";
|
||||
@@ -7,11 +7,11 @@ import { RequestService } from "../request.service";
|
||||
import { LocalMapsManagerService } from "./maps/local-maps-manager.service";
|
||||
import log from "electron-log";
|
||||
import { WindowManagerService } from "../window-manager.service";
|
||||
import { BPList, DownloadPlaylistProgressionData, PlaylistSong } from "shared/models/playlists/playlist.interface";
|
||||
import { BPList, DownloadPlaylistProgressionData } from "shared/models/playlists/playlist.interface";
|
||||
import { readFileSync } from "fs";
|
||||
import { BeatSaverService } from "../thrid-party/beat-saver/beat-saver.service";
|
||||
import { copy, copyFile, ensureDir, pathExists, pathExistsSync, readdirSync, realpath, writeFile, writeFileSync } from "fs-extra";
|
||||
import { Progression, ensurePathNotAlreadyExist, ensurePathNotAlreadyExistSync, pathExist, unlinkPath } from "../../helpers/fs.helpers";
|
||||
import { copy, ensureDir, pathExists, pathExistsSync, readdirSync, realpath, writeFileSync } from "fs-extra";
|
||||
import { Progression, unlinkPath } from "../../helpers/fs.helpers";
|
||||
import { FileAssociationService } from "../file-association.service";
|
||||
import { SongDetailsCacheService } from "./maps/song-details-cache.service";
|
||||
import { sToMs } from "shared/helpers/time.helpers";
|
||||
@@ -20,9 +20,9 @@ import { SongCacheService } from "./maps/song-cache.service";
|
||||
import { InstallationLocationService } from "../installation-location.service";
|
||||
import sanitize from "sanitize-filename";
|
||||
import { isValidUrl } from "shared/helpers/url.helpers";
|
||||
import { allSettled } from "shared/helpers/promise.helpers";
|
||||
import { Archive } from "main/models/archive.class";
|
||||
import { CustomError } from "shared/models/exceptions/custom-error.class";
|
||||
import { BsmLocalMap } from "shared/models/maps/bsm-local-map.interface";
|
||||
|
||||
export class LocalPlaylistsManagerService {
|
||||
private static instance: LocalPlaylistsManagerService;
|
||||
@@ -291,7 +291,7 @@ export class LocalPlaylistsManagerService {
|
||||
return from(unlinkPath(bpList.path));
|
||||
}
|
||||
|
||||
public exportPlaylists(opt: {version?: BSVersion, bpLists: LocalBPList[], dest: string, exportMaps?: boolean}): Observable<Progression<string>> {
|
||||
public exportPlaylists(opt: {version?: BSVersion, bpLists: LocalBPList[], dest: string, playlistsMaps?: BsmLocalMap[]}): Observable<Progression<string>> {
|
||||
|
||||
if(!pathExistsSync(opt.dest)) {
|
||||
throw new CustomError(`Destination folder not found ${opt.dest}`, "DEST_ENOENT");
|
||||
@@ -310,55 +310,31 @@ export class LocalPlaylistsManagerService {
|
||||
for(const bpList of opt.bpLists) {
|
||||
|
||||
if(!pathExistsSync(bpList.path)) {
|
||||
throw new CustomError(`Playlist file not found ${bpList.path}`, "PLAYLIST_ENOENT");
|
||||
log.warn(`Playlist file not found for export`, bpList.path);
|
||||
continue;
|
||||
}
|
||||
|
||||
archive.addFile(bpList.path, path.join(this.PLAYLISTS_FOLDER, path.basename(bpList.path)));
|
||||
}
|
||||
|
||||
if(!opt.exportMaps) {
|
||||
if(!Array.isArray(opt.playlistsMaps) || opt.playlistsMaps.length === 0){
|
||||
return archive.finalize();
|
||||
}
|
||||
|
||||
const mapsHashsToExport = Array.from(
|
||||
new Set<string>(opt.bpLists.reduce((acc, bpList) => acc.concat((bpList.songs ?? []).map(s => s.hash)), [])).values()
|
||||
);
|
||||
for(const map of opt.playlistsMaps) {
|
||||
|
||||
const zipMaps$ = new Observable<Progression<string>>(obs => {
|
||||
(async () => {
|
||||
const progress: Progression<string> = { total: mapsHashsToExport.length, current: 0, data: zipDest };
|
||||
if(!map?.path || !pathExistsSync(map.path)) {
|
||||
log.warn(`Map file not found for playlist export`, map?.path);
|
||||
continue;
|
||||
}
|
||||
|
||||
for(const hash of mapsHashsToExport) {
|
||||
const mapInfo = await this.maps.getMapInfoFromHash(hash, opt.version);
|
||||
|
||||
if(!mapInfo || !pathExistsSync(mapInfo.path)) { continue; }
|
||||
|
||||
archive.addDirectory(
|
||||
mapInfo.path,
|
||||
path.join("Maps", path.basename(mapInfo.path)) // Dont't know why, but "CustomLevels" not work
|
||||
);
|
||||
progress.current += 1;
|
||||
|
||||
obs.next(progress);
|
||||
}
|
||||
|
||||
})()
|
||||
.catch(err => obs.error(err))
|
||||
.finally(() => obs.complete());
|
||||
});
|
||||
|
||||
return new Observable<Progression<string>>(obs => {
|
||||
(async () => {
|
||||
const maps$ = zipMaps$.pipe(tap({ next: p => obs.next(p) }));
|
||||
const archive$ = archive.finalize().pipe(tap({ next: p => obs.next(p) }));
|
||||
|
||||
await lastValueFrom(maps$);
|
||||
await lastValueFrom(archive$);
|
||||
})()
|
||||
.catch(err => obs.error(err))
|
||||
.finally(() => obs.complete());
|
||||
})
|
||||
archive.addDirectory(
|
||||
map.path,
|
||||
path.join("Maps", path.basename(map.path)) // Dont't know why, but "CustomLevels" not work
|
||||
);
|
||||
}
|
||||
|
||||
return archive.finalize();
|
||||
}
|
||||
|
||||
public oneClickInstallPlaylist(bpListUrl: string): Observable<Progression<DownloadPlaylistProgressionData>> {
|
||||
|
||||
@@ -287,9 +287,9 @@ export class LocalMapsManagerService {
|
||||
const versionMapsPath = await this.getMapsFolderPath(version);
|
||||
const mapInfo = this.songCache.getMapInfoFromHash(hash);
|
||||
|
||||
const cachedMapPath = path.join(versionMapsPath, mapInfo.dirname);
|
||||
const cachedMapPath = (versionMapsPath && mapInfo?.dirname) && path.join(versionMapsPath, mapInfo.dirname);
|
||||
|
||||
if(pathExistsSync(cachedMapPath)){
|
||||
if(cachedMapPath && pathExistsSync(cachedMapPath)){
|
||||
return this.loadMapInfoFromPath(cachedMapPath);
|
||||
}
|
||||
|
||||
|
||||
+9
-4
@@ -53,6 +53,8 @@ export type LocalPlaylistsListRef = {
|
||||
exportPlaylists: () => Promise<void>;
|
||||
}
|
||||
|
||||
// TODO : Translate
|
||||
|
||||
export const LocalPlaylistsListPanel = forwardRef<LocalPlaylistsListRef, Props>(({ version, className, filter: playlistFiler, search, isActive, linkedState }, forwardedRef) => {
|
||||
|
||||
const t = useTranslation();
|
||||
@@ -113,14 +115,17 @@ export const LocalPlaylistsListPanel = forwardRef<LocalPlaylistsListRef, Props>(
|
||||
|
||||
if(!toExport.length){ return; }
|
||||
|
||||
const modalRes = await modals.openModal(ExportPlaylistModal, { data: toExport });
|
||||
if(modalRes.exitCode !== ModalExitCode.COMPLETED){ return; }
|
||||
const { exitCode, data: exportMaps } = await modals.openModal(ExportPlaylistModal, { data: toExport });
|
||||
if(exitCode !== ModalExitCode.COMPLETED){ return; }
|
||||
|
||||
const folderRes = await lastValueFrom(ipc.sendV2("choose-folder"));
|
||||
if(!folderRes || folderRes.canceled || !folderRes.filePaths?.length){ return; }
|
||||
|
||||
if(modalRes.exitCode !== ModalExitCode.COMPLETED){ return; }
|
||||
const obs$ = playlistService.exportPlaylists({ version, bpLists: toExport, dest: folderRes.filePaths.at(0), exportMaps: modalRes.data });
|
||||
const mapsToExport = exportMaps ? (
|
||||
maps$?.value?.filter(m => toExport.some(p => p.songs.some(s => s.hash.toLocaleLowerCase() === m.hash.toLocaleLowerCase()))) ?? []
|
||||
) : [];
|
||||
|
||||
const obs$ = playlistService.exportPlaylists({ version, bpLists: toExport, dest: folderRes.filePaths.at(0), playlistsMaps: mapsToExport });
|
||||
|
||||
progess.show(obs$, true);
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ export const DownloadMapsModal: ModalComponent<void, { version: BSVersion; owned
|
||||
|
||||
return { map, isOwned: isMapOwned, idDownloading: isDownloading, isInQueue: inQueue };
|
||||
}));
|
||||
}, [maps, currentDownload, mapsInQueue, ownedMapHashs, currentDownload])
|
||||
}, [maps, currentDownload, mapsInQueue, ownedMapHashs])
|
||||
|
||||
useEffect(() => {
|
||||
loadMaps(searchParams);
|
||||
|
||||
+106
-38
@@ -5,19 +5,19 @@ import { BsmLocalMap } from "shared/models/maps/bsm-local-map.interface";
|
||||
import { Observable } from "rxjs";
|
||||
import { useObservable } from "renderer/hooks/use-observable.hook";
|
||||
import { ModalComponent, ModalExitCode } from "renderer/services/modale.service";
|
||||
import { useState } from "react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { MapItem, extractMapDiffs } from "renderer/components/maps-playlists-panel/maps/map-item.component";
|
||||
import { useOnUpdate } from "renderer/hooks/use-on-update.hook";
|
||||
import { useService } from "renderer/hooks/use-service.hook";
|
||||
import { BeatSaverService } from "renderer/services/thrird-partys/beat-saver.service";
|
||||
import { getLocalTimeZone, parseAbsolute, toCalendarDateTime } from "@internationalized/date";
|
||||
import { motion } from "framer-motion";
|
||||
import { MapsDownloaderService } from "renderer/services/maps-downloader.service";
|
||||
import equal from "fast-deep-equal";
|
||||
import BeatWaiting from "../../../../../../../assets/images/apngs/beat-waiting.png"
|
||||
import BeatConflict from "../../../../../../../assets/images/apngs/beat-conflict.png"
|
||||
import { BsmImage } from "renderer/components/shared/bsm-image.component";
|
||||
import { cn } from "renderer/helpers/css-class.helpers";
|
||||
import { VirtualScroll } from "renderer/components/shared/virtual-scroll/virtual-scroll.component";
|
||||
|
||||
type Props = {
|
||||
version: BSVersion;
|
||||
@@ -32,10 +32,12 @@ export const BsvPlaylistDetailsModal: ModalComponent<void, Props> = ({ resolver,
|
||||
const beatsaver = useService(BeatSaverService);
|
||||
const mapsDownloader = useService(MapsDownloaderService);
|
||||
|
||||
const currentMapDownload = useObservable(() => mapsDownloader.currentMapDownload$, null);
|
||||
const downloadingMaps = useObservable(() => mapsDownloader.mapsInQueue$, []);
|
||||
const installedMaps = useObservable(() => installedMaps$, null);
|
||||
const [page, setPage] = useState(0);
|
||||
const [playlistMaps, setPlaylistMaps] = useState<BsvMapDetail[]>(null);
|
||||
const [downloadbleMaps, setDownloadbleMaps] = useState<DownloadableMap[]>([]);
|
||||
const [error, setError] = useState<boolean>(false);
|
||||
|
||||
useOnUpdate(() => {
|
||||
@@ -44,17 +46,61 @@ export const BsvPlaylistDetailsModal: ModalComponent<void, Props> = ({ resolver,
|
||||
.catch(() => setError(() => true));
|
||||
}, [page]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const ownedMapHashs = installedMaps?.map(map => map.hash) ?? [];
|
||||
|
||||
if(!Array.isArray(playlistMaps)){
|
||||
return setDownloadbleMaps([]);
|
||||
}
|
||||
|
||||
setDownloadbleMaps(() => playlistMaps.map(map => {
|
||||
const isMapOwned = map.versions.some(version => ownedMapHashs.includes(version.hash));
|
||||
const isDownloading = map.id === currentMapDownload?.map?.id;
|
||||
const inQueue = downloadingMaps.some(toDownload => equal(toDownload.version, version) && toDownload.map.id === map.id);
|
||||
|
||||
return { map, isOwned: isMapOwned, idDownloading: isDownloading, isInQueue: inQueue };
|
||||
}));
|
||||
}, [playlistMaps, currentMapDownload, downloadingMaps, installedMaps])
|
||||
|
||||
const renderMapItem = useCallback((downloadableMap: DownloadableMap) => {
|
||||
const map = downloadableMap.map;
|
||||
|
||||
const downloadable = !downloadableMap.isOwned && !downloadableMap.isInQueue;
|
||||
const cancelable = downloadableMap.isInQueue && !downloadableMap.idDownloading;
|
||||
|
||||
return <MapItem
|
||||
autor={map.metadata.levelAuthorName}
|
||||
autorId={map.uploader.id}
|
||||
bpm={map.metadata.bpm}
|
||||
coverUrl={map.versions.at(0).coverURL}
|
||||
createdAt={map.createdAt && toCalendarDateTime(parseAbsolute(map.createdAt, getLocalTimeZone()))}
|
||||
duration={map.metadata.duration}
|
||||
hash={map.versions.at(0).hash}
|
||||
likes={map.stats.upvotes}
|
||||
mapId={map.id} ranked={map.ranked}
|
||||
title={map.name}
|
||||
songAutor={map.metadata.songAuthorName}
|
||||
diffs={extractMapDiffs({ bsvMap: map })}
|
||||
songUrl={map.versions.at(0).previewURL}
|
||||
key={map.id}
|
||||
onDownload={downloadable && (() => mapsDownloader.addMapToDownload({version, map}))}
|
||||
onCancelDownload={cancelable && (() => mapsDownloader.removeMapToDownload({version, map}))}
|
||||
downloading={downloadableMap.idDownloading}
|
||||
callBackParam={map} />;
|
||||
}, [version]);
|
||||
|
||||
return (
|
||||
<PlaylistDetailsTemplate
|
||||
title={playlist.name}
|
||||
author={playlist.owner.name}
|
||||
description={playlist.description}
|
||||
duration={playlist.stats.totalDuration}
|
||||
duration={playlist.stats?.totalDuration}
|
||||
imageUrl={playlist.playlistImage}
|
||||
maxNps={playlist.stats.maxNps}
|
||||
minNps={playlist.stats.minNps}
|
||||
nbMappers={playlist.stats.mapperCount}
|
||||
nbMaps={playlist.stats.totalMaps}
|
||||
maxNps={playlist.stats?.maxNps}
|
||||
minNps={playlist.stats?.minNps}
|
||||
nbMappers={playlist.stats?.mapperCount}
|
||||
nbMaps={playlist.stats?.totalMaps}
|
||||
onClose={() => resolver({ exitCode: ModalExitCode.CLOSED })}
|
||||
>
|
||||
{(() => {
|
||||
@@ -82,41 +128,63 @@ export const BsvPlaylistDetailsModal: ModalComponent<void, Props> = ({ resolver,
|
||||
}
|
||||
|
||||
return (
|
||||
<ul className="min-h-0 w-full grow space-y-2 pl-2.5 pr-2 py-3 overflow-y-scroll overflow-x-hidden scrollbar-default">
|
||||
{playlistMaps.map(map => {
|
||||
<VirtualScroll
|
||||
maxColumns={1}
|
||||
minItemWidth={300}
|
||||
itemHeight={110}
|
||||
items={downloadbleMaps}
|
||||
renderItem={renderMapItem}
|
||||
classNames={{
|
||||
mainDiv: "min-h-0 w-full grow",
|
||||
rows: "my-2.5 px-2.5"
|
||||
}}
|
||||
scrollEnd={{
|
||||
onScrollEnd: () => setPage(prev => prev + 1),
|
||||
margin: 100
|
||||
}}
|
||||
/>
|
||||
// <ul className="min-h-0 w-full grow space-y-2 pl-2.5 pr-2 py-3 overflow-y-scroll overflow-x-hidden scrollbar-default">
|
||||
// {playlistMaps.map(map => {
|
||||
|
||||
const downloadingMap = downloadingMaps.at(0);
|
||||
const isMapOwned = installedMaps?.some(installedMap => installedMap.hash === map.versions.at(0).hash);
|
||||
const isMapInQueue = downloadingMaps.some(downloadingMap => downloadingMap.map.versions.at(0).hash === map.versions.at(0).hash);
|
||||
const isMapDownloading = (isMapInQueue && downloadingMap) ? equal(downloadingMap.version, version) && downloadingMap.map.versions.at(0).hash === map.versions.at(0).hash : false;
|
||||
// const downloadingMap = downloadingMaps.at(0);
|
||||
// const isMapOwned = installedMaps?.some(installedMap => installedMap.hash === map.versions.at(0).hash);
|
||||
// const isMapInQueue = downloadingMaps.some(downloadingMap => downloadingMap.map.versions.at(0).hash === map.versions.at(0).hash);
|
||||
// const isMapDownloading = (isMapInQueue && downloadingMap) ? equal(downloadingMap.version, version) && downloadingMap.map.versions.at(0).hash === map.versions.at(0).hash : false;
|
||||
|
||||
return (
|
||||
<MapItem
|
||||
key={map.id}
|
||||
title={map.name}
|
||||
autor={map.uploader.name}
|
||||
autorId={map.uploader.id}
|
||||
bpm={map.metadata.bpm}
|
||||
coverUrl={map.versions.at(0).coverURL}
|
||||
createdAt={map.createdAt && toCalendarDateTime(parseAbsolute(map.createdAt, getLocalTimeZone()))}
|
||||
duration={map.metadata.duration}
|
||||
hash={map.versions.at(0).hash}
|
||||
mapId={map.id}
|
||||
likes={map.stats.upvotes}
|
||||
ranked={map.ranked}
|
||||
songAutor={map.metadata.songAuthorName}
|
||||
songUrl={map.versions.at(0).previewURL}
|
||||
diffs={extractMapDiffs({ bsvMap: map })}
|
||||
downloading={isMapDownloading}
|
||||
onDownload={!isMapOwned && !isMapInQueue && (() => mapsDownloader.addMapToDownload({version, map}))}
|
||||
onCancelDownload={isMapInQueue && !isMapDownloading && (() => mapsDownloader.removeMapToDownload({version, map}))}
|
||||
callBackParam={map}
|
||||
/>);
|
||||
})}
|
||||
<motion.span className="w-full h-8" onViewportEnter={() => setPage(prev => prev + 1)} />
|
||||
</ul>
|
||||
// return (
|
||||
// <MapItem
|
||||
// key={map.id}
|
||||
// title={map.name}
|
||||
// autor={map.uploader.name}
|
||||
// autorId={map.uploader.id}
|
||||
// bpm={map.metadata.bpm}
|
||||
// coverUrl={map.versions.at(0).coverURL}
|
||||
// createdAt={map.createdAt && toCalendarDateTime(parseAbsolute(map.createdAt, getLocalTimeZone()))}
|
||||
// duration={map.metadata.duration}
|
||||
// hash={map.versions.at(0).hash}
|
||||
// mapId={map.id}
|
||||
// likes={map.stats.upvotes}
|
||||
// ranked={map.ranked}
|
||||
// songAutor={map.metadata.songAuthorName}
|
||||
// songUrl={map.versions.at(0).previewURL}
|
||||
// diffs={extractMapDiffs({ bsvMap: map })}
|
||||
// downloading={isMapDownloading}
|
||||
// onDownload={!isMapOwned && !isMapInQueue && (() => mapsDownloader.addMapToDownload({version, map}))}
|
||||
// onCancelDownload={isMapInQueue && !isMapDownloading && (() => mapsDownloader.removeMapToDownload({version, map}))}
|
||||
// callBackParam={map}
|
||||
// />);
|
||||
// })}
|
||||
// <motion.span className="w-full h-8" onViewportEnter={() => setPage(prev => prev + 1)} />
|
||||
// </ul>
|
||||
)
|
||||
})()}
|
||||
</PlaylistDetailsTemplate>
|
||||
)
|
||||
}
|
||||
|
||||
type DownloadableMap = {
|
||||
map: BsvMapDetail;
|
||||
isOwned: boolean;
|
||||
idDownloading: boolean;
|
||||
isInQueue: boolean;
|
||||
};
|
||||
|
||||
+39
-23
@@ -1,4 +1,4 @@
|
||||
import { ModalComponent } from "renderer/services/modale.service"
|
||||
import { ModalComponent, ModalExitCode } from "renderer/services/modale.service"
|
||||
import { PlaylistDetailsTemplate } from "./playlist-details-template.component"
|
||||
import { Observable, combineLatest, lastValueFrom, map, switchMap } from "rxjs"
|
||||
import { BsmLocalMap } from "shared/models/maps/bsm-local-map.interface"
|
||||
@@ -14,6 +14,8 @@ import { LocalBPListsDetails } from "shared/models/playlists/local-playlist.mode
|
||||
import { PlaylistDownloaderService } from "renderer/services/playlist-downloader.service";
|
||||
import { PlaylistHeaderState } from "./playlist-header-state.component";
|
||||
import { useConstant } from "renderer/hooks/use-constant.hook";
|
||||
import { useCallback } from "react";
|
||||
import { VirtualScroll } from "renderer/components/shared/virtual-scroll/virtual-scroll.component";
|
||||
|
||||
// TODO : Translate
|
||||
|
||||
@@ -58,6 +60,29 @@ export const LocalPlaylistDetailsModal: ModalComponent<void, Props> = ({resolver
|
||||
return lastValueFrom(obs$);
|
||||
}
|
||||
|
||||
const renderMapItem = useCallback((map: BsmLocalMap) => {
|
||||
return (
|
||||
<MapItem
|
||||
key={map.path}
|
||||
hash={map.hash}
|
||||
title={map.rawInfo._songName}
|
||||
coverUrl={map.coverUrl}
|
||||
songUrl={map.songUrl}
|
||||
autor={map.rawInfo._levelAuthorName}
|
||||
songAutor={map.rawInfo._songAuthorName}
|
||||
bpm={map.rawInfo._beatsPerMinute}
|
||||
duration={map.songDetails?.duration}
|
||||
diffs={extractMapDiffs({ rawMapInfo: map.rawInfo, songDetails: map.songDetails })}
|
||||
mapId={map.songDetails?.id}
|
||||
ranked={map.songDetails?.ranked}
|
||||
autorId={map.songDetails?.uploader.id}
|
||||
likes={map.songDetails?.upVotes}
|
||||
createdAt={map.songDetails?.uploadedAt}
|
||||
callBackParam={null}
|
||||
/>
|
||||
);
|
||||
}, []);
|
||||
|
||||
const renderMaps = () => {
|
||||
if (!Array.isArray(installedMaps) && !isInQueue) {
|
||||
return (
|
||||
@@ -98,28 +123,18 @@ export const LocalPlaylistDetailsModal: ModalComponent<void, Props> = ({resolver
|
||||
isPlaylistInQueue$={isPlaylistInQueue$}
|
||||
installPlaylist={installPlaylist}
|
||||
/>
|
||||
<ul className="min-h-0 w-full grow space-y-2 pl-2.5 pr-2 py-3 overflow-y-scroll overflow-x-hidden scrollbar-default">
|
||||
{installedMaps.map(map => (
|
||||
<MapItem
|
||||
key={map.path}
|
||||
hash={map.hash}
|
||||
title={map.rawInfo._songName}
|
||||
coverUrl={map.coverUrl}
|
||||
songUrl={map.songUrl}
|
||||
autor={map.rawInfo._levelAuthorName}
|
||||
songAutor={map.rawInfo._songAuthorName}
|
||||
bpm={map.rawInfo._beatsPerMinute}
|
||||
duration={map.songDetails?.duration}
|
||||
diffs={extractMapDiffs({ rawMapInfo: map.rawInfo, songDetails: map.songDetails })}
|
||||
mapId={map.songDetails?.id}
|
||||
ranked={map.songDetails?.ranked}
|
||||
autorId={map.songDetails?.uploader.id}
|
||||
likes={map.songDetails?.upVotes}
|
||||
createdAt={map.songDetails?.uploadedAt}
|
||||
callBackParam={null}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
<VirtualScroll
|
||||
maxColumns={1}
|
||||
minItemWidth={300}
|
||||
itemHeight={110}
|
||||
items={installedMaps}
|
||||
renderItem={renderMapItem}
|
||||
classNames={{
|
||||
mainDiv: "min-h-0 w-full grow",
|
||||
rows: "my-2.5 px-2.5"
|
||||
}}
|
||||
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -135,6 +150,7 @@ export const LocalPlaylistDetailsModal: ModalComponent<void, Props> = ({resolver
|
||||
nbMaps={localPlaylist?.nbMaps}
|
||||
nbMappers={localPlaylist?.nbMappers}
|
||||
title={localPlaylist?.playlistTitle}
|
||||
onClose={() => resolver({ exitCode: ModalExitCode.CLOSED })}
|
||||
>
|
||||
{renderMaps()}
|
||||
</PlaylistDetailsTemplate>
|
||||
|
||||
@@ -7,6 +7,7 @@ import { LocalBPList, LocalBPListsDetails } from "shared/models/playlists/local-
|
||||
import { ModalExitCode, ModalService } from "./modale.service";
|
||||
import { UnlinkPlaylistModal } from "renderer/components/modal/modal-types/unlink-playlist-modal.component";
|
||||
import { LinkPlaylistModal } from "renderer/components/modal/modal-types/link-playlist-modal.component";
|
||||
import { BsmLocalMap } from "shared/models/maps/bsm-local-map.interface";
|
||||
|
||||
export class PlaylistsManagerService {
|
||||
private static instance: PlaylistsManagerService;
|
||||
@@ -38,12 +39,12 @@ export class PlaylistsManagerService {
|
||||
return this.ipc.sendV2("delete-playlist", opt);
|
||||
}
|
||||
|
||||
public exportPlaylists(opt: {version: BSVersion, bpLists: LocalBPList[], dest: string, exportMaps?: boolean}): Observable<Progression<string>> {
|
||||
public exportPlaylists(opt: {version: BSVersion, bpLists: LocalBPList[], dest: string, playlistsMaps?: BsmLocalMap[]}): Observable<Progression<string>> {
|
||||
return this.ipc.sendV2("export-playlists", {
|
||||
version: opt.version,
|
||||
bpLists: opt.bpLists,
|
||||
dest: opt.dest,
|
||||
exportMaps: opt.exportMaps
|
||||
playlistsMaps: opt.playlistsMaps
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ export interface IpcChannelMapping {
|
||||
"download-playlist": {request: {downloadSource: string, dest?: string, version?: BSVersion, ignoreSongsHashs?: string[]}, response: Progression<DownloadPlaylistProgressionData>};
|
||||
"get-version-playlists-details": {request: BSVersion, response: Progression<LocalBPListsDetails[]>};
|
||||
"delete-playlist": {request: {version: BSVersion, bpList: LocalBPList, deleteMaps?: boolean}, response: Progression};
|
||||
"export-playlists": {request: {version?: BSVersion, bpLists: LocalBPList[], dest: string, exportMaps?: boolean}, response: Progression<string>};
|
||||
"export-playlists": {request: {version?: BSVersion, bpLists: LocalBPList[], dest: string, playlistsMaps?: BsmLocalMap[]}, response: Progression<string>};
|
||||
|
||||
/* ** bs-uninstall-ipcs ** */
|
||||
"bs.uninstall": { request: BSVersion, response: boolean };
|
||||
|
||||
Reference in New Issue
Block a user