mirror of
https://github.com/Zagrios/bs-manager.git
synced 2026-07-03 14:08:25 +02:00
Merge pull request #349 from Zagrios/bugfix/cannot-download-playlist-with-one-click/342
[bugfix-342] Fix unable to download playlist with OneClick from certain websites
This commit is contained in:
@@ -2,21 +2,13 @@ import { ipcMain } from "electron";
|
||||
import { IpcRequest } from "shared/models/ipc";
|
||||
import { LocalPlaylistsManagerService } from "../services/additional-content/local-playlists-manager.service";
|
||||
import { UtilsService } from "../services/utils.service";
|
||||
import log from "electron-log";
|
||||
import { IpcService } from "../services/ipc.service";
|
||||
|
||||
ipcMain.on("one-click-install-playlist", async (event, request: IpcRequest<string>) => {
|
||||
const utils = UtilsService.getInstance();
|
||||
const playlists = LocalPlaylistsManagerService.getInstance();
|
||||
const ipc = IpcService.getInstance();
|
||||
|
||||
playlists
|
||||
.oneClickInstallPlaylist(request.args)
|
||||
.then(() => {
|
||||
utils.ipcSend(request.responceChannel, { success: true });
|
||||
})
|
||||
.catch(e => {
|
||||
log.error(e);
|
||||
utils.ipcSend(request.responceChannel, { success: false, error: e });
|
||||
});
|
||||
ipc.on<string>("one-click-install-playlist", (req, reply) => {
|
||||
const mapsManager = LocalPlaylistsManagerService.getInstance();
|
||||
reply(mapsManager.oneClickInstallPlaylist(req.args));
|
||||
});
|
||||
|
||||
ipcMain.on("register-playlists-deep-link", async (event, request: IpcRequest<void>) => {
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
import path from "path";
|
||||
import { BehaviorSubject, Observable, lastValueFrom, of } from "rxjs";
|
||||
import { Observable, lastValueFrom, tap } from "rxjs";
|
||||
import { BSVersion } from "shared/bs-version.interface";
|
||||
import { BSLocalVersionService } from "../bs-local-version.service";
|
||||
import { DeepLinkService } from "../deep-link.service";
|
||||
import { RequestService } from "../request.service";
|
||||
import { UtilsService } from "../utils.service";
|
||||
import { LocalMapsManagerService } from "./local-maps-manager.service";
|
||||
import log from "electron-log";
|
||||
import { isValidUrl } from "../../../shared/helpers/url.helpers";
|
||||
import { WindowManagerService } from "../window-manager.service";
|
||||
import { BPList, DownloadPlaylistProgression } from "shared/models/playlists/playlist.interface";
|
||||
import { copyFileSync, readFileSync } from "fs";
|
||||
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, realpath } from "fs-extra";
|
||||
import { ensureFolderExist, pathExist } from "../../helpers/fs.helpers";
|
||||
import { copy, copyFile, pathExists, realpath } from "fs-extra";
|
||||
import { Progression, ensureFolderExist, pathExist } from "../../helpers/fs.helpers";
|
||||
import { IpcService } from "../ipc.service";
|
||||
|
||||
export class LocalPlaylistsManagerService {
|
||||
@@ -33,7 +31,6 @@ export class LocalPlaylistsManagerService {
|
||||
|
||||
private readonly versions: BSLocalVersionService;
|
||||
private readonly maps: LocalMapsManagerService;
|
||||
private readonly utils: UtilsService;
|
||||
private readonly request: RequestService;
|
||||
private readonly deepLink: DeepLinkService;
|
||||
private readonly windows: WindowManagerService;
|
||||
@@ -43,7 +40,6 @@ export class LocalPlaylistsManagerService {
|
||||
private constructor() {
|
||||
this.maps = LocalMapsManagerService.getInstance();
|
||||
this.versions = BSLocalVersionService.getInstance();
|
||||
this.utils = UtilsService.getInstance();
|
||||
this.request = RequestService.getInstance();
|
||||
this.deepLink = DeepLinkService.getInstance();
|
||||
this.windows = WindowManagerService.getInstance();
|
||||
@@ -54,26 +50,10 @@ export class LocalPlaylistsManagerService {
|
||||
log.info("DEEP-LINK RECEIVED FROM", this.DEEP_LINKS.BeatSaver, link);
|
||||
const url = new URL(link);
|
||||
const bplistUrl = url.host === "playlist" ? url.pathname.replace("/", "") : "";
|
||||
|
||||
this.openOneClickDownloadPlaylistWindow(bplistUrl);
|
||||
});
|
||||
}
|
||||
|
||||
private getPlaylistIdFromDownloadUrl(url: string): string {
|
||||
if (!isValidUrl(url)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const splited = url.split("/");
|
||||
const idIndex = splited.indexOf("id");
|
||||
|
||||
if (idIndex < 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return splited[idIndex + 1];
|
||||
}
|
||||
|
||||
private async getPlaylistsFolder(version?: BSVersion) {
|
||||
if (!version) {
|
||||
throw "Playlists are not available to be linked yet";
|
||||
@@ -88,19 +68,18 @@ export class LocalPlaylistsManagerService {
|
||||
return folder;
|
||||
}
|
||||
|
||||
private async installBPListFile(bpListUrlOrPath: string, version: BSVersion): Promise<string> {
|
||||
private async installBPListFile(bslistSource: string, version: BSVersion): Promise<string> {
|
||||
const playlistFolder = await this.getPlaylistsFolder(version);
|
||||
const isLocalFile = await pathExists(bslistSource).catch(e => { log.error(e); return false; });
|
||||
const filename = isLocalFile ? path.basename(bslistSource) : new URL(bslistSource).pathname.split('/').pop()
|
||||
const destFile = path.join(playlistFolder, filename);
|
||||
|
||||
const bpListDest = path.join(playlistFolder, path.basename(bpListUrlOrPath));
|
||||
|
||||
if (await pathExist(bpListUrlOrPath)) {
|
||||
copyFileSync(bpListUrlOrPath, bpListDest);
|
||||
} else {
|
||||
await lastValueFrom(this.request.downloadFile(bpListUrlOrPath, bpListDest));
|
||||
if (isLocalFile) {
|
||||
return copyFile(bslistSource, destFile).then(() => destFile);
|
||||
}
|
||||
|
||||
return bpListDest;
|
||||
return lastValueFrom(this.request.downloadFile(bslistSource, destFile)).then(res => res.data);
|
||||
}
|
||||
|
||||
|
||||
private async readPlaylistFile(path: string): Promise<BPList> {
|
||||
if (!(await pathExist(path))) {
|
||||
@@ -113,99 +92,87 @@ export class LocalPlaylistsManagerService {
|
||||
}
|
||||
|
||||
private openOneClickDownloadPlaylistWindow(downloadUrl: string): void {
|
||||
this.windows.openWindow("oneclick-download-playlist.html").then(window => {
|
||||
|
||||
this.ipc.once("one-click-playlist-info", (_, reply) => {
|
||||
reply(of({ bpListUrl: downloadUrl, id: this.getPlaylistIdFromDownloadUrl(downloadUrl) }))
|
||||
}, window.webContents.ipc);
|
||||
|
||||
});
|
||||
this.windows.openWindow(`oneclick-download-playlist.html?playlistUrl=${downloadUrl}`);
|
||||
}
|
||||
|
||||
public downloadPlaylist(bpListUrl: string, version: BSVersion): Observable<DownloadPlaylistProgression> {
|
||||
const res = new BehaviorSubject<DownloadPlaylistProgression>({ progression: 0, current: null, downloadedMaps: [], mapsPath: [], bpListPath: "" });
|
||||
public downloadPlaylist(bpListUrl: string, version: BSVersion): Observable<Progression<DownloadPlaylistProgressionData>> {
|
||||
|
||||
const sub = res.subscribe(
|
||||
process => {
|
||||
this.utils.ipcSend("download-playlist-progress", { success: true, data: process });
|
||||
},
|
||||
err => {
|
||||
this.utils.ipcSend("download-playlist-progress", { success: false, error: err });
|
||||
}
|
||||
);
|
||||
return new Observable<Progression<DownloadPlaylistProgressionData>>(obs => {
|
||||
(async () => {
|
||||
|
||||
const observer = async () => {
|
||||
try {
|
||||
const bpListPath = await this.installBPListFile(bpListUrl, version);
|
||||
const bpListFilePath = await this.installBPListFile(bpListUrl, version);
|
||||
const bpList = await this.readPlaylistFile(bpListFilePath);
|
||||
|
||||
const progress: Progression<DownloadPlaylistProgressionData> = {
|
||||
total: bpList.songs.length,
|
||||
current: 0,
|
||||
data: {
|
||||
downloadedMaps: [],
|
||||
currentDownload: null,
|
||||
playlistInfos: bpList,
|
||||
playlistPath: bpListFilePath,
|
||||
}
|
||||
};
|
||||
|
||||
res.next({ ...res.value, bpListPath });
|
||||
|
||||
const bpList = await this.readPlaylistFile(bpListPath);
|
||||
obs.next(progress);
|
||||
|
||||
for (const song of bpList.songs) {
|
||||
if (!song.key) {
|
||||
const [ mapDetail ] = await this.bsaver.getMapDetailsFromHashs([song.hash]);
|
||||
|
||||
if(!mapDetail) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const map = await this.bsaver.getMapDetailsById(song.key);
|
||||
const downloadedMap = await this.maps.downloadMap(mapDetail, version);
|
||||
|
||||
res.next({
|
||||
...res.value,
|
||||
current: map,
|
||||
progression: ((res.value.downloadedMaps.length + 0.5) / bpList.songs.length) * 100,
|
||||
});
|
||||
|
||||
const mapPath = await this.maps.downloadMap(map, version);
|
||||
|
||||
const progression = ((res.value.downloadedMaps.length + 1) / bpList.songs.length) * 100;
|
||||
|
||||
res.next({
|
||||
...res.value,
|
||||
current: null,
|
||||
downloadedMaps: [...res.value.downloadedMaps, map],
|
||||
mapsPath: [...res.value.mapsPath, mapPath.path],
|
||||
progression,
|
||||
});
|
||||
progress.data.downloadedMaps.push(downloadedMap);
|
||||
progress.data.currentDownload = mapDetail;
|
||||
progress.current += 1;
|
||||
obs.next(progress);
|
||||
}
|
||||
} catch (e) {
|
||||
res.error(e);
|
||||
}
|
||||
|
||||
res.complete();
|
||||
};
|
||||
})()
|
||||
.catch(err => obs.error(err))
|
||||
.finally(() => obs.complete());
|
||||
});
|
||||
|
||||
observer().finally(() => sub.unsubscribe());
|
||||
return res.asObservable();
|
||||
}
|
||||
|
||||
public async oneClickInstallPlaylist(bpListUrl: string): Promise<void> {
|
||||
const versions = await this.versions.getInstalledVersions();
|
||||
public oneClickInstallPlaylist(bpListUrl: string): Observable<Progression<DownloadPlaylistProgressionData>> {
|
||||
|
||||
const { bpListPath, mapsPath } = await lastValueFrom(this.downloadPlaylist(bpListUrl, versions.pop()));
|
||||
return new Observable<Progression<DownloadPlaylistProgressionData>>(obs => {
|
||||
(async () => {
|
||||
const versions = await this.versions.getInstalledVersions();
|
||||
|
||||
if(mapsPath?.length === 0 || !bpListPath) {
|
||||
return;
|
||||
}
|
||||
const download$ = this.downloadPlaylist(bpListUrl, versions.pop()).pipe(tap({
|
||||
next: progress => obs.next(progress),
|
||||
error: err => obs.error(err),
|
||||
}));
|
||||
|
||||
const realSourceMapsFolder = await realpath(path.dirname(mapsPath[0]));
|
||||
const { data: {downloadedMaps, playlistPath} } = await lastValueFrom(download$);
|
||||
|
||||
for (const version of versions) {
|
||||
await this.installBPListFile(bpListPath, version);
|
||||
if(downloadedMaps?.length === 0 || !playlistPath) { return; }
|
||||
|
||||
const versionMapsFolder = await this.maps.getMapsFolderPath(version);
|
||||
const realDestMapsFolder = await realpath(versionMapsFolder);
|
||||
const realSourceMapsFolder = await realpath(path.dirname(downloadedMaps[0].path));
|
||||
|
||||
if(realSourceMapsFolder === realDestMapsFolder) {
|
||||
continue;
|
||||
}
|
||||
for (const version of versions) {
|
||||
await this.installBPListFile(playlistPath, version);
|
||||
|
||||
for (const mapPath of mapsPath) {
|
||||
const versionMapsFolder = await this.maps.getMapsFolderPath(version);
|
||||
const realDestMapsFolder = await realpath(versionMapsFolder);
|
||||
|
||||
const mapDest = path.join(versionMapsFolder, path.basename(mapPath));
|
||||
if(realSourceMapsFolder === realDestMapsFolder) { continue; }
|
||||
|
||||
await copy(mapPath, mapDest, { overwrite: true });
|
||||
}
|
||||
}
|
||||
for (const mapPath of downloadedMaps) {
|
||||
const mapDest = path.join(versionMapsFolder, path.basename(mapPath.path));
|
||||
await copy(mapPath.path, mapDest, { overwrite: true });
|
||||
}
|
||||
}
|
||||
|
||||
})()
|
||||
.catch(err => obs.error(err))
|
||||
.finally(() => obs.complete());
|
||||
});
|
||||
}
|
||||
|
||||
public enableDeepLinks(): boolean {
|
||||
|
||||
@@ -59,7 +59,7 @@ export class RequestService {
|
||||
req.on("error", err => {
|
||||
subscriber.error(err);
|
||||
});
|
||||
}).pipe(tap({ error: e => log.error(e) }), shareReplay(1));
|
||||
}).pipe(tap({ error: e => log.error(e, url, dest) }), shareReplay(1));
|
||||
}
|
||||
|
||||
public downloadBuffer(url: string): Observable<Progression<Buffer>> {
|
||||
|
||||
@@ -66,9 +66,10 @@ export class WindowManagerService {
|
||||
return promise.then(() => window);
|
||||
}
|
||||
|
||||
public openWindow(windowType: AppWindow, options?: BrowserWindowConstructorOptions): Promise<BrowserWindow> {
|
||||
public openWindow(url: AppWindow, options?: BrowserWindowConstructorOptions): Promise<BrowserWindow> {
|
||||
const windowType = url.split("?")[0];
|
||||
const window = new BrowserWindow({ ...(this.appWindowsOptions[windowType] ?? {}), ...this.baseWindowOption, ...options });
|
||||
return this.handleNewWindow(windowType, window);
|
||||
return this.handleNewWindow(url, window);
|
||||
}
|
||||
|
||||
public closeAllWindows(except?: AppWindow) {
|
||||
|
||||
+2
-3
@@ -7,9 +7,8 @@ export let resolveHtmlPath: (htmlFileName: string) => string;
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
const port = process.env.PORT || 1212;
|
||||
resolveHtmlPath = (htmlFileName: string) => {
|
||||
const url = new URL(`http://localhost:${port}`);
|
||||
url.pathname = htmlFileName;
|
||||
return url.href;
|
||||
const url = new URL(`http://localhost:${port}/${htmlFileName}`);
|
||||
return url.toString();
|
||||
};
|
||||
} else {
|
||||
resolveHtmlPath = (htmlFileName: string) => {
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { map } from "rxjs/operators";
|
||||
import { Observable, Subject } from "rxjs";
|
||||
import { DownloadPlaylistProgression } from "shared/models/playlists/playlist.interface";
|
||||
import { Observable, map, tap, throwError } from "rxjs";
|
||||
import { DownloadPlaylistProgressionData } from "shared/models/playlists/playlist.interface";
|
||||
import { IpcService } from "./ipc.service";
|
||||
import { ProgressBarService } from "./progress-bar.service";
|
||||
import { ProgressionInterface } from "shared/models/progress-bar";
|
||||
import { Progression } from "main/helpers/fs.helpers";
|
||||
|
||||
export class PlaylistDownloaderService {
|
||||
private static instance: PlaylistDownloaderService;
|
||||
@@ -15,7 +14,6 @@ export class PlaylistDownloaderService {
|
||||
return PlaylistDownloaderService.instance;
|
||||
}
|
||||
|
||||
private readonly progressWatcher$ = new Subject<DownloadPlaylistProgression>();
|
||||
|
||||
private readonly progress: ProgressBarService;
|
||||
private readonly ipc: IpcService;
|
||||
@@ -23,51 +21,22 @@ export class PlaylistDownloaderService {
|
||||
private constructor() {
|
||||
this.progress = ProgressBarService.getInstance();
|
||||
this.ipc = IpcService.getInstance();
|
||||
|
||||
this.ipc
|
||||
.watch<DownloadPlaylistProgression>("download-playlist-progress")
|
||||
.pipe(map(val => val.data))
|
||||
.subscribe(progress => {
|
||||
this.progressWatcher$.next(progress);
|
||||
});
|
||||
}
|
||||
|
||||
private get downloadProgression$(): Observable<ProgressionInterface> {
|
||||
return this.progressWatcher$.pipe(
|
||||
map(value => {
|
||||
return { progression: value?.progression ?? 0, label: value?.current?.name ?? "" };
|
||||
})
|
||||
);
|
||||
}
|
||||
public oneClickInstallPlaylist(bpListUrl: string): Observable<Progression<DownloadPlaylistProgressionData>> {
|
||||
|
||||
public get progress$(): Observable<DownloadPlaylistProgression> {
|
||||
return this.progressWatcher$.asObservable();
|
||||
}
|
||||
if(!this.progress.require()){
|
||||
return throwError(() => new Error("Download already in progress"));
|
||||
}
|
||||
|
||||
public oneClickInstallPlaylist(bpListUrl: string): Observable<DownloadPlaylistProgression> {
|
||||
this.progressWatcher$.next(null);
|
||||
const download$ = this.ipc.sendV2<Progression<DownloadPlaylistProgressionData>, string>("one-click-install-playlist", { args: bpListUrl });
|
||||
const progress$ = download$.pipe(map(data => (data.current / data.total) * 100));
|
||||
|
||||
const res = new Subject<DownloadPlaylistProgression>();
|
||||
this.progress.show(progress$, true);
|
||||
|
||||
this.progress.show(this.downloadProgression$, true);
|
||||
|
||||
const sub = this.progressWatcher$.subscribe(progress => {
|
||||
res.next(progress);
|
||||
if (progress.progression === 100) {
|
||||
this.progress.showFake(0.08);
|
||||
}
|
||||
});
|
||||
|
||||
this.ipc.send<void, string>("one-click-install-playlist", { args: bpListUrl }).then(oneClickRes => {
|
||||
sub.unsubscribe();
|
||||
this.progress.hide(true);
|
||||
this.progressWatcher$.next(null);
|
||||
if (!oneClickRes.success) {
|
||||
return res.error(oneClickRes.error);
|
||||
}
|
||||
res.complete();
|
||||
});
|
||||
|
||||
return res.asObservable();
|
||||
return download$.pipe(tap({
|
||||
error: () => this.progress.hide(true),
|
||||
complete: () => this.progress.hide(true)
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,85 +1,69 @@
|
||||
import { motion } from "framer-motion";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { BsmProgressBar } from "renderer/components/progress-bar/bsm-progress-bar.component";
|
||||
import { BsmImage } from "renderer/components/shared/bsm-image.component";
|
||||
import TitleBar from "renderer/components/title-bar/title-bar.component";
|
||||
import { useObservable } from "renderer/hooks/use-observable.hook";
|
||||
import { useTranslation } from "renderer/hooks/use-translation.hook";
|
||||
import { IpcService } from "renderer/services/ipc.service";
|
||||
import { NotificationService } from "renderer/services/notification.service";
|
||||
import { PlaylistDownloaderService } from "renderer/services/playlist-downloader.service";
|
||||
import { BeatSaverService } from "renderer/services/thrird-partys/beat-saver.service";
|
||||
import { map, filter } from "rxjs/operators";
|
||||
import { BsvPlaylist } from "shared/models/maps/beat-saver.model";
|
||||
import { map, filter, take, lastValueFrom } from "rxjs";
|
||||
import defaultImage from "../../../../assets/images/default-version-img.jpg";
|
||||
import { useService } from "renderer/hooks/use-service.hook";
|
||||
import { lastValueFrom } from "rxjs";
|
||||
import { useConstant } from "renderer/hooks/use-constant.hook";
|
||||
import { BPList } from "shared/models/playlists/playlist.interface";
|
||||
import { useOnUpdate } from "renderer/hooks/use-on-update.hook";
|
||||
|
||||
export default function OneClickDownloadPlaylist() {
|
||||
|
||||
|
||||
const ipc = useService(IpcService);
|
||||
const bSaver = useService(BeatSaverService);
|
||||
const playlistDownloader = useService(PlaylistDownloaderService);
|
||||
const mapsContainer = useRef<HTMLDivElement>(null);
|
||||
const notification = useService(NotificationService);
|
||||
|
||||
const [playlist, setPlaylist] = useState<BsvPlaylist>(null);
|
||||
const downloadedMap = useObservable(
|
||||
playlistDownloader.progress$.pipe(
|
||||
filter(download => !!download),
|
||||
map(download => [...(download.downloadedMaps ?? []), download?.current])
|
||||
),
|
||||
[]
|
||||
);
|
||||
const t = useTranslation();
|
||||
|
||||
const cover = playlist ? playlist.playlistImage : null;
|
||||
const title = playlist ? playlist.name : null;
|
||||
const playlistDownloader = useService(PlaylistDownloaderService);
|
||||
const notification = useService(NotificationService);
|
||||
|
||||
const mapsContainer = useRef<HTMLDivElement>(null);
|
||||
const playlistUrl = useConstant(() => new URLSearchParams(window.location.search).get("playlistUrl"));
|
||||
const download$ = useConstant(() => playlistDownloader.oneClickInstallPlaylist(playlistUrl));
|
||||
const playlistInfos = useObservable<BPList>(download$.pipe(filter(progress => !!progress.data?.playlistInfos), map(progress => progress.data.playlistInfos), take(1)));
|
||||
const downloadedMaps = useObservable(download$.pipe(filter(progress => !!progress.data?.downloadedMaps), map(progress => progress.data.downloadedMaps)));
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
const promise = (async () => {
|
||||
const infos = await lastValueFrom(ipc.sendV2<{ bpListUrl: string; id: string }>("one-click-playlist-info"));
|
||||
|
||||
bSaver.getPlaylistDetailsById(infos.id).then(details => setPlaylist(details));
|
||||
|
||||
return lastValueFrom(playlistDownloader.oneClickInstallPlaylist(infos.bpListUrl));
|
||||
})();
|
||||
|
||||
promise.catch(() => {
|
||||
notification.notifySystem({ title: t("notifications.types.error"), body: t("notifications.playlists.one-click-install.error") });
|
||||
});
|
||||
|
||||
promise.then(() => {
|
||||
lastValueFrom(download$).then(() => {
|
||||
notification.notifySystem({ title: "OneClick", body: t("notifications.playlists.one-click-install.success") });
|
||||
});
|
||||
|
||||
promise.finally(() => {
|
||||
}).catch(() => {
|
||||
notification.notifySystem({ title: t("notifications.types.error"), body: t("notifications.playlists.one-click-install.error") });
|
||||
}).finally(() => {
|
||||
window.electron.window.close();
|
||||
});
|
||||
|
||||
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(
|
||||
() =>
|
||||
mapsContainer.current.scrollTo({
|
||||
left: mapsContainer.current.scrollWidth,
|
||||
behavior: "smooth",
|
||||
}),
|
||||
500
|
||||
);
|
||||
}, [downloadedMap]);
|
||||
useOnUpdate(() => {
|
||||
setTimeout(() => {
|
||||
mapsContainer.current.scrollTo({ left: mapsContainer.current.scrollWidth, behavior: "smooth",});
|
||||
}, 500);
|
||||
}, [downloadedMaps]);
|
||||
|
||||
const playlistImage = () => {
|
||||
if(!playlistInfos?.image) return defaultImage;
|
||||
if (playlistInfos?.image.startsWith("data:image")) {
|
||||
return playlistInfos?.image;
|
||||
}
|
||||
return `data:image/png;base64,${playlistInfos?.image}`;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative w-screen h-screen overflow-hidden">
|
||||
{playlist && <BsmImage className="absolute top-0 left-0 w-full h-full" image={playlist.playlistImage} />}
|
||||
{playlistInfos && <BsmImage className="absolute top-0 left-0 w-full h-full" placeholder={defaultImage} image={playlistImage()} errorImage={defaultImage} />}
|
||||
<div className="w-full h-full backdrop-brightness-50 backdrop-blur-md flex flex-col justify-start items-center">
|
||||
<TitleBar template="oneclick-download-playlist.html" />
|
||||
<BsmImage className="mt-2 aspect-square w-1/2 object-cover rounded-md shadow-black shadow-lg" placeholder={defaultImage} image={cover} errorImage={defaultImage} />
|
||||
<h1 className="mt-4 overflow-hidden font-bold italic text-xl text-gray-200 tracking-wide w-full text-center whitespace-nowrap text-ellipsis px-2">{title}</h1>
|
||||
<BsmImage className="mt-2 aspect-square w-1/2 object-cover rounded-md shadow-black shadow-lg" placeholder={defaultImage} image={playlistImage()} errorImage={defaultImage} />
|
||||
<h1 className="mt-4 overflow-hidden font-bold italic text-xl text-gray-200 tracking-wide w-full text-center whitespace-nowrap text-ellipsis px-2">{playlistInfos?.playlistTitle}</h1>
|
||||
<div className="w-full py-3 flex items-center justify-center max-w-full overflow-x-scroll overflow-y-hidden scrollbar scrollbar-thin scrollbar-track-transparent scrollbar-thumb-neutral-900" ref={mapsContainer}>
|
||||
<div className="flex justify-start items-start gap-2.5">{downloadedMap?.map(map => map?.versions?.at(0)?.coverURL && <motion.img layout="position" key={map.id} className="block aspect-square w-14 object-cover rounded-md shadow-black shadow-md" src={map?.versions?.at(0)?.coverURL} initial={{ scale: 0 }} animate={{ scale: 1 }} whileHover={{ rotate: 5 }} />)}</div>
|
||||
<div className="flex justify-start items-start gap-2.5">{downloadedMaps?.map(map => map?.coverUrl &&
|
||||
<motion.img layout="position" key={map.hash} className="block aspect-square w-14 object-cover rounded-md shadow-black shadow-md" src={map?.coverUrl} initial={{ scale: 0 }} animate={{ scale: 1 }} whileHover={{ rotate: 5 }} />
|
||||
)}</div>
|
||||
</div>
|
||||
</div>
|
||||
<BsmProgressBar />
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
export { RawMapInfoData, RawMapDifficulty, RawDifficultySet } from "./raw-map.model";
|
||||
|
||||
export { BsvInstant, BsvMapDetail, BsvMapDetailMetadata, BsvMapDifficulty, BsvMapParitySummary, BsvMapStats, BsvMapTestplay, BsvMapVersion, BsvUserDetail } from "./beat-saver.model";
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { BsvMapDetail } from "../maps";
|
||||
import { BsmLocalMap } from "../maps/bsm-local-map.interface";
|
||||
|
||||
export interface BPList {
|
||||
playlistTitle: string;
|
||||
@@ -16,10 +17,9 @@ export interface PlaylistSong {
|
||||
uploader?: string;
|
||||
}
|
||||
|
||||
export interface DownloadPlaylistProgression {
|
||||
mapsPath: string[];
|
||||
downloadedMaps: BsvMapDetail[];
|
||||
bpListPath: string;
|
||||
current: BsvMapDetail;
|
||||
progression: number;
|
||||
export interface DownloadPlaylistProgressionData {
|
||||
downloadedMaps: BsmLocalMap[];
|
||||
currentDownload: BsvMapDetail;
|
||||
playlistInfos: BPList;
|
||||
playlistPath: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user