mirror of
https://github.com/Zagrios/bs-manager.git
synced 2026-07-03 14:08:25 +02:00
[feature-107] playlist creation and edition is finished
This commit is contained in:
@@ -258,6 +258,19 @@ export function resolveGUIDPath(guidPath: string): string {
|
||||
return path.join(driveLetter, path.relative(guidVolume, guidPath));
|
||||
}
|
||||
|
||||
export function getUniqueFileNamePath(filePath: string): string {
|
||||
const { dir, name, ext } = path.parse(filePath);
|
||||
let i = 0;
|
||||
let newFileName = `${name}${ext}`;
|
||||
|
||||
while (pathExistsSync(path.join(dir, newFileName))) {
|
||||
i++;
|
||||
newFileName = `${name} (${i})${ext}`;
|
||||
}
|
||||
|
||||
return path.join(dir, newFileName);
|
||||
}
|
||||
|
||||
export interface Progression<T = unknown, D = unknown> {
|
||||
total: number;
|
||||
current: number;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { LocalBPList } from "shared/models/playlists/local-playlist.models";
|
||||
import { LocalPlaylistsManagerService } from "../services/additional-content/local-playlists-manager.service";
|
||||
import { IpcService } from "../services/ipc.service";
|
||||
import { mergeMap, of } from "rxjs";
|
||||
import { from, lastValueFrom, mergeMap, of } from "rxjs";
|
||||
import { BPList } from "shared/models/playlists/playlist.interface";
|
||||
import path from "path";
|
||||
import { LocalMapsManagerService } from "../services/additional-content/maps/local-maps-manager.service";
|
||||
@@ -66,3 +66,14 @@ ipc.on("export-playlists", (args, reply) => {
|
||||
const playlists = LocalPlaylistsManagerService.getInstance();
|
||||
reply(playlists.exportPlaylists(args));
|
||||
});
|
||||
|
||||
ipc.on("install-playlist-file", (args, reply) => {
|
||||
const playlists = LocalPlaylistsManagerService.getInstance();
|
||||
|
||||
const promise = async () => {
|
||||
const playlist = await lastValueFrom(playlists.writeBPListFile({ bpList: args.bplist, version: args.version, dest: args.dest}));
|
||||
return playlists.getLocalBPListDetails(playlist);
|
||||
}
|
||||
|
||||
reply(from(promise()));
|
||||
})
|
||||
|
||||
@@ -11,7 +11,7 @@ import { BPList, DownloadPlaylistProgressionData } from "shared/models/playlists
|
||||
import { readFileSync } from "fs";
|
||||
import { BeatSaverService } from "../thrid-party/beat-saver/beat-saver.service";
|
||||
import { copy, ensureDir, pathExists, pathExistsSync, readdirSync, realpath, writeFileSync } from "fs-extra";
|
||||
import { Progression, unlinkPath } from "../../helpers/fs.helpers";
|
||||
import { Progression, getUniqueFileNamePath, 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";
|
||||
@@ -85,6 +85,27 @@ export class LocalPlaylistsManagerService {
|
||||
return fullPath;
|
||||
}
|
||||
|
||||
public writeBPListFile(opt: { bpList: BPList, version?: BSVersion, dest?: string }): Observable<LocalBPList> {
|
||||
return new Observable<LocalBPList>(obs => {
|
||||
(async () => {
|
||||
const dest = await (async () => {
|
||||
if(opt.dest && path.isAbsolute(opt.dest) && path.extname(opt.dest) === ".bplist") { return opt.dest; }
|
||||
const playlistFolder = await this.getPlaylistsFolder(opt.version);
|
||||
const playlistPath = path.join(playlistFolder, `${sanitize(opt.bpList.playlistTitle)}.bplist`);
|
||||
return getUniqueFileNamePath(playlistPath);
|
||||
})();
|
||||
|
||||
writeFileSync(dest, JSON.stringify(opt.bpList, null, 2));
|
||||
|
||||
const localBPList: LocalBPList = { ...opt.bpList, path: dest };
|
||||
|
||||
obs.next(localBPList);
|
||||
})()
|
||||
.catch(err => obs.error(err))
|
||||
.finally(() => obs.complete());
|
||||
});
|
||||
}
|
||||
|
||||
private async installBPListFile(opt: {
|
||||
bslistSource: string,
|
||||
version?: BSVersion,
|
||||
|
||||
+67
-1
@@ -38,6 +38,7 @@ import { enumerate } from "shared/helpers/array.helpers";
|
||||
import { SyncPlaylistModal } from "renderer/components/modal/modal-types/playlist/sync-playlist-modal.component";
|
||||
import { ExportPlaylistModal } from "renderer/components/modal/modal-types/playlist/export-playlist-modal.component";
|
||||
import { EditPlaylistModal } from "renderer/components/modal/modal-types/playlist/edit-playlist-modal/edit-playlist-modal.component";
|
||||
import { NeedCloneEditPlaylistModal } from "renderer/components/modal/modal-types/playlist/need-clone-edit-playlist-modal.component";
|
||||
|
||||
type Props = {
|
||||
version: BSVersion;
|
||||
@@ -89,6 +90,27 @@ export const LocalPlaylistsListPanel = forwardRef<LocalPlaylistsListRef, Props>(
|
||||
useImperativeHandle(forwardedRef, () => ({
|
||||
createPlaylist: async () => {
|
||||
const modalRes = await modals.openModal(EditPlaylistModal, { noStyle: true, data: { version, maps$ } });
|
||||
|
||||
if(modalRes.exitCode !== ModalExitCode.COMPLETED){ return; }
|
||||
|
||||
const { error, result } = await tryit(() => lastValueFrom(playlistDownloader.installPlaylistFile(modalRes.data, version)));
|
||||
|
||||
if(error){
|
||||
logRenderError("Error occured while creating playlist", error);
|
||||
notification.notifyError({ title: "Erreur lors de la création de la playlist", desc: "Une erreur est survenue lors de la création de la playlist." });
|
||||
return;
|
||||
}
|
||||
|
||||
setPlaylists([result, ...playlists$.value]);
|
||||
|
||||
const notifRes = await notification.notifySuccess({ title: "Playlist créée !", desc: "La playlist a été créée avec succès. Tu peut maintenant synchroniser ses maps !", duration: 8000, actions: [
|
||||
{ id: "sync", title: "Synchroniser les maps" }
|
||||
]});
|
||||
|
||||
if(notifRes === "sync"){
|
||||
await lastValueFrom(installPlaylist(result));
|
||||
}
|
||||
|
||||
},
|
||||
syncPlaylists: async () => {
|
||||
if(!isOnline){ return; }
|
||||
@@ -249,6 +271,7 @@ export const LocalPlaylistsListPanel = forwardRef<LocalPlaylistsListRef, Props>(
|
||||
};
|
||||
|
||||
const openPlaylistDetails = (playlistPath: string) => {
|
||||
|
||||
const localPlaylist$ = playlists$.pipe(map(playlists => playlists.find(p => p.path === playlistPath)));
|
||||
const installedMaps$ = combineLatest([maps$, localPlaylist$]).pipe(
|
||||
filter(([maps, playlist]) => !!maps && !!playlist),
|
||||
@@ -262,6 +285,48 @@ export const LocalPlaylistsListPanel = forwardRef<LocalPlaylistsListRef, Props>(
|
||||
})
|
||||
};
|
||||
|
||||
const editPlaylist = async (playlist: LocalBPList) => {
|
||||
const needClone = playlist?.customData?.syncURL;
|
||||
const res = await (needClone ? modals.openModal(NeedCloneEditPlaylistModal) : Promise.resolve());
|
||||
|
||||
if(res && res.exitCode !== ModalExitCode.COMPLETED){
|
||||
return;
|
||||
}
|
||||
|
||||
const tmpPlaylist: LocalBPList = { ...playlist, playlistTitle: needClone ? `${playlist.playlistTitle} (${t("Clone")})` : playlist.playlistTitle };
|
||||
const modalRes = await modals.openModal(EditPlaylistModal, { noStyle: true, data: { version, maps$, playlist: tmpPlaylist } });
|
||||
|
||||
if(modalRes.exitCode !== ModalExitCode.COMPLETED){ return; }
|
||||
|
||||
const { error, result } = await tryit(() => lastValueFrom(playlistDownloader.installPlaylistFile(modalRes.data, version, needClone ? undefined : playlist.path)));
|
||||
|
||||
if(error){
|
||||
logRenderError("Error occured while editing playlist", error);
|
||||
notification.notifyError({ title: "Erreur lors de la modification de la playlist", desc: "Une erreur est survenue lors de la modification de la playlist." });
|
||||
return;
|
||||
}
|
||||
|
||||
const newPlaylists = [...playlists$.value];
|
||||
|
||||
if(needClone){
|
||||
newPlaylists.unshift(result);
|
||||
}
|
||||
else {
|
||||
const index = newPlaylists.findIndex(p => p.path === playlist.path);
|
||||
newPlaylists[index] = result;
|
||||
}
|
||||
|
||||
setPlaylists(newPlaylists);
|
||||
|
||||
const notifRes = await notification.notifySuccess({ title: "Playlist modifiée !", desc: "La playlist a été modifiée avec succès. Tu peut maintenant synchroniser ses maps !", duration: 8000, actions: [
|
||||
{ id: "sync", title: "Synchroniser les maps" }
|
||||
]});
|
||||
|
||||
if(notifRes === "sync"){
|
||||
await lastValueFrom(installPlaylist(result));
|
||||
}
|
||||
}
|
||||
|
||||
const renderPlaylist = useCallback((playlist: LocalBPListsDetails) => {
|
||||
|
||||
|
||||
@@ -282,9 +347,10 @@ export const LocalPlaylistsListPanel = forwardRef<LocalPlaylistsListRef, Props>(
|
||||
}}
|
||||
onClickOpen={() => openPlaylistDetails(playlist.path)}
|
||||
onClickDelete={() => deletePlaylists([playlist])}
|
||||
onClickSync={isOnline && (() => handleClickSync(playlist))}
|
||||
onClickSync={(playlist?.songs?.length && isOnline) && (() => handleClickSync(playlist))}
|
||||
onClickOpenFile={() => viewPlaylistFile(playlist.path)}
|
||||
onClickCancelDownload={() => playlistDownloader.cancelDownload(playlist.customData?.syncURL ?? playlist.path, version)}
|
||||
onClickEdit={() => editPlaylist(playlist)}
|
||||
/>
|
||||
);
|
||||
}, [isOnline, version]);
|
||||
|
||||
+25
-11
@@ -35,6 +35,7 @@ export type PlaylistItemComponentProps = {
|
||||
onClickSync?: () => void;
|
||||
onClickDownload?: () => void;
|
||||
onClickCancelDownload?: () => void;
|
||||
onClickEdit?: () => void;
|
||||
}
|
||||
|
||||
export function PlaylistItem({ title,
|
||||
@@ -55,7 +56,8 @@ export function PlaylistItem({ title,
|
||||
onClickSync,
|
||||
onClickDownload,
|
||||
onClickDelete,
|
||||
onClickCancelDownload
|
||||
onClickCancelDownload,
|
||||
onClickEdit
|
||||
}: PlaylistItemComponentProps) {
|
||||
|
||||
const color = useThemeColor("first-color");
|
||||
@@ -112,7 +114,7 @@ export function PlaylistItem({ title,
|
||||
<span className="absolute size-2.5 bottom-0 right-full bg-inherit translate-x-px" style={{ clipPath: 'path("M11 11 L11 0 L10 0 A10 10 0 0 1 0 10 L 0 11 Z")' }} />
|
||||
|
||||
<motion.div className="flex flex-col justify-center items-center flex-wrap gap-0.5 size-full px-1 *:size-6 *:!bg-inherit *:p-0.5 *:rounded-md" animate={{opacity: hovered || isDownloading ? 1 : 0}} transition={{duration: 0}}>
|
||||
{(isDownloading || isInQueue) && onClickCancelDownload && (
|
||||
{(isDownloading || isInQueue) && onClickCancelDownload ? (
|
||||
<Tippy content={isDownloading ? "Arreter le téléchargement" : "Annuler le téléchargement"} placement="left" theme="default">
|
||||
<BsmButton
|
||||
icon="close"
|
||||
@@ -122,8 +124,8 @@ export function PlaylistItem({ title,
|
||||
withBar={false}
|
||||
/>
|
||||
</Tippy>
|
||||
)}
|
||||
{onClickSync && (
|
||||
) : (<></>)}
|
||||
{onClickSync ? (
|
||||
isDownloading ? (
|
||||
<BsmBasicSpinner className="hover:!bg-main-color-1" spinnerClassName="brightness-75 dark:brightness-200" style={{ color }} thikness="3px"/>
|
||||
) : !isInQueue ? (
|
||||
@@ -139,8 +141,8 @@ export function PlaylistItem({ title,
|
||||
</Tippy>
|
||||
) : (<></>)
|
||||
|
||||
)}
|
||||
{onClickDownload && (
|
||||
) : (<></>)}
|
||||
{onClickDownload ? (
|
||||
isDownloading ? (
|
||||
<BsmBasicSpinner className="hover:!bg-main-color-1" spinnerClassName="brightness-75 dark:brightness-200" style={{ color }} thikness="3px"/>
|
||||
) : !isInQueue ? (
|
||||
@@ -156,8 +158,20 @@ export function PlaylistItem({ title,
|
||||
</Tippy>
|
||||
) : (<></>)
|
||||
|
||||
)}
|
||||
{onClickOpenFile && <Tippy content="Afficher le fichier" placement="left" theme="default">
|
||||
) : (<></>)}
|
||||
{onClickEdit ? (
|
||||
<Tippy content="Editer la playlist" placement="left" theme="default">
|
||||
<BsmButton
|
||||
icon="edit"
|
||||
className="hover:!bg-main-color-1"
|
||||
iconClassName="size-full brightness-75 dark:brightness-200"
|
||||
style={{color}}
|
||||
onClick={onClickEdit}
|
||||
withBar={false}
|
||||
/>
|
||||
</Tippy>
|
||||
) : (<></>)}
|
||||
{onClickOpenFile ? <Tippy content="Afficher le fichier" placement="left" theme="default">
|
||||
<BsmButton
|
||||
icon="folder"
|
||||
className="hover:!bg-main-color-1"
|
||||
@@ -166,8 +180,8 @@ export function PlaylistItem({ title,
|
||||
onClick={onClickOpenFile}
|
||||
withBar={false}
|
||||
/>
|
||||
</Tippy>}
|
||||
{(onClickDelete && !isDownloading && !isInQueue) && <Tippy content="Supprimer" placement="left" theme="default">
|
||||
</Tippy> : (<></>)}
|
||||
{(onClickDelete && !isDownloading && !isInQueue) ? <Tippy content="Supprimer" placement="left" theme="default">
|
||||
<BsmButton
|
||||
icon="trash"
|
||||
className="hover:!bg-main-color-1 text-red-500"
|
||||
@@ -175,7 +189,7 @@ export function PlaylistItem({ title,
|
||||
onClick={onClickDelete}
|
||||
withBar={false}
|
||||
/>
|
||||
</Tippy>}
|
||||
</Tippy> : (<></>)}
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</div>
|
||||
|
||||
+9
-4
@@ -8,18 +8,21 @@ import { IpcService } from "renderer/services/ipc.service";
|
||||
import { lastValueFrom } from "rxjs";
|
||||
import { logRenderError } from "renderer";
|
||||
|
||||
type Props = {
|
||||
type OutProps = {
|
||||
playlistTitle: string;
|
||||
playlistDescription: string;
|
||||
playlistAuthor: string;
|
||||
base64Image: string;
|
||||
}
|
||||
|
||||
export const EditPlaylistInfosModal: ModalComponent<Props, Props> = ({ resolver, options: { data: {
|
||||
type Props = OutProps & { isEdit: boolean };
|
||||
|
||||
export const EditPlaylistInfosModal: ModalComponent<OutProps, Props> = ({ resolver, options: { data: {
|
||||
playlistTitle,
|
||||
playlistDescription,
|
||||
playlistAuthor,
|
||||
base64Image
|
||||
base64Image,
|
||||
isEdit
|
||||
}}}) => {
|
||||
|
||||
const steamDownloader = useService(SteamDownloaderService);
|
||||
@@ -49,7 +52,9 @@ export const EditPlaylistInfosModal: ModalComponent<Props, Props> = ({ resolver,
|
||||
|
||||
return (
|
||||
<div className="text-gray-800 dark:text-gray-200">
|
||||
<h1 className="text-3xl uppercase tracking-wide w-full text-center mb-4 px-4">Créer une playlist</h1>
|
||||
<h1 className="text-3xl uppercase tracking-wide w-full text-center mb-4 px-4">
|
||||
{isEdit ? "Modifier la playlist" : "Créer une playlist"}
|
||||
</h1>
|
||||
<div className="w-full flex flex-col justify-center items-center">
|
||||
<button className="flex justify-center items-center relative size-36 border-2 border-gray-400 bg-theme-1 rounded-md overflow-hidden" onClick={handleClickImage}>
|
||||
{base64 ? (
|
||||
|
||||
+26
-4
@@ -12,7 +12,7 @@ import { ModalComponent, ModalExitCode, ModalService } from "renderer/services/m
|
||||
import { BehaviorSubject, Observable, lastValueFrom, map, take } from "rxjs";
|
||||
import { BSVersion } from "shared/bs-version.interface";
|
||||
import { BsmLocalMap } from "shared/models/maps/bsm-local-map.interface";
|
||||
import { LocalBPList, LocalBPListsDetails } from "shared/models/playlists/local-playlist.models"
|
||||
import { LocalBPList } from "shared/models/playlists/local-playlist.models"
|
||||
import { BsvMapDetail, SongDetails } from "shared/models/maps";
|
||||
import { logRenderError } from "renderer";
|
||||
import { useService } from "renderer/hooks/use-service.hook";
|
||||
@@ -30,9 +30,9 @@ import { ClockIcon } from "renderer/components/svgs/icons/clock-icon.component";
|
||||
import { NpsIcon } from "renderer/components/svgs/icons/nps-icon.component";
|
||||
import dateFormat from 'dateformat';
|
||||
import { getCorrectTextColor } from "renderer/helpers/correct-text-color";
|
||||
import { DeleteModelsModal } from "../../models/delete-models-modal.component";
|
||||
import { BPList } from "shared/models/playlists/playlist.interface";
|
||||
import { EditPlaylistInfosModal } from "./edit-playlist-infos-modal.component";
|
||||
import { CrossIcon } from "renderer/components/svgs/icons/cross-icon.component";
|
||||
|
||||
type Props = {
|
||||
version?: BSVersion;
|
||||
@@ -354,12 +354,34 @@ export const EditPlaylistModal: ModalComponent<BPList, Props> = ({ resolver, opt
|
||||
playlistDescription: playlist?.playlistDescription ?? "",
|
||||
base64Image: playlist?.image ?? "",
|
||||
playlistAuthor: playlist?.playlistAuthor ?? "",
|
||||
isEdit: !!playlist
|
||||
}});
|
||||
resolver({ exitCode: ModalExitCode.CANCELED });
|
||||
|
||||
if(res.exitCode !== ModalExitCode.COMPLETED){ return; }
|
||||
|
||||
const bpList: BPList = {
|
||||
image: res.data.base64Image,
|
||||
playlistAuthor: res.data.playlistAuthor,
|
||||
playlistTitle: res.data.playlistTitle,
|
||||
playlistDescription: res.data.playlistDescription,
|
||||
songs: Object.values(playlistMaps$.value ?? []).map(map => {
|
||||
const props = MapItemComponentPropsMapper.from(map);
|
||||
return {
|
||||
key: props.mapId,
|
||||
hash: props.hash,
|
||||
songName: props.title
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
resolver({ exitCode: ModalExitCode.COMPLETED, data: bpList});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-screen h-screen max-h-[calc(100vh-2rem)] max-w-[55rem] lg:max-w-[66rem] xl:max-w-[77rem] bg-theme-3 p-4 rounded-md">
|
||||
<div className="w-screen h-screen max-h-[calc(100vh-2rem)] max-w-[55rem] lg:max-w-[66rem] xl:max-w-[77rem] 2xl:max-w-[88rem] bg-theme-3 p-4 rounded-md relative">
|
||||
<button className="absolute top-1.5 right-1.5 size-3" onClick={() => resolver({ exitCode: ModalExitCode.CANCELED })}>
|
||||
<CrossIcon className="size-full"/>
|
||||
</button>
|
||||
{(() => {
|
||||
if(!playlistMaps || !localMaps){
|
||||
return <div className="flex items-center justify-center w-full h-full">Loading...</div>
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import { ModalComponent, ModalExitCode } from "renderer/services/modale.service"
|
||||
import BeatConflict from "../../../../../../assets/images/apngs/beat-conflict.png";
|
||||
import { BsmImage } from "renderer/components/shared/bsm-image.component";
|
||||
import { BsmButton } from "renderer/components/shared/bsm-button.component";
|
||||
|
||||
// TODO : Translate
|
||||
|
||||
export const NeedCloneEditPlaylistModal: ModalComponent<void, void> = ({ resolver }) => {
|
||||
|
||||
return (
|
||||
<form className="text-gray-800 dark:text-gray-200 flex flex-col gap-2 max-w-sm">
|
||||
<h1 className="text-3xl uppercase tracking-wide w-full text-center">Attention</h1>
|
||||
<BsmImage className="mx-auto h-24" image={BeatConflict} />
|
||||
<p className="w-full">Cette playlist a été téléchargée depuis un site externe et contient un lien de synchronisation.</p>
|
||||
<p className="w-full">Pour éviter de perdre vos modifications lors d'une synchronisation, la playlist va être dupliquée et son lien de synchronisation supprimé.</p>
|
||||
<p className="w-full">Vous pourrez ensuite, si vous le souhaitez, supprimer la playlist originale.</p>
|
||||
<div className="grid grid-flow-col grid-cols-2 gap-2 mt-2 h-8">
|
||||
<BsmButton typeColor="cancel" className="rounded-md flex justify-center items-center transition-all h-full" onClick={() => resolver({ exitCode: ModalExitCode.CANCELED })} withBar={false} text="misc.cancel" />
|
||||
<BsmButton typeColor="primary" className="rounded-md flex justify-center items-center transition-all h-full" onClick={() => resolver({ exitCode: ModalExitCode.COMPLETED })} withBar={false} text="J'ai compris" />
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
+21
-2
@@ -85,7 +85,26 @@ export const LocalPlaylistDetailsModal: ModalComponent<void, Props> = ({resolver
|
||||
|
||||
const renderMaps = () => {
|
||||
|
||||
if(!installedMaps.length && !isInQueue) {
|
||||
if (!Array.isArray(installedMaps) || !localPlaylist) {
|
||||
return (
|
||||
<div className="grow bg-red-400">
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if(!localPlaylist.songs?.length){
|
||||
return (
|
||||
<div className="grow flex justify-center items-center flex-col">
|
||||
<BsmImage image={BeatConflict} className="size-28"/>
|
||||
<div className="text-white font-bold w-fit space-y-1.5 flex flex-col justify-center items-center -translate-y-5">
|
||||
<p>La Playlist ne contient aucune map</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if(!installedMaps.length && localPlaylist.songs?.length && !isInQueue) {
|
||||
return (
|
||||
<div className="grow flex justify-center items-center flex-col">
|
||||
<BsmImage image={BeatConflict} className="size-28"/>
|
||||
@@ -97,7 +116,7 @@ export const LocalPlaylistDetailsModal: ModalComponent<void, Props> = ({resolver
|
||||
);
|
||||
}
|
||||
|
||||
if(installedMaps.length === 0 && isInQueue) {
|
||||
if(!installedMaps.length && isInQueue) {
|
||||
return (
|
||||
<div className="grow flex justify-center items-center flex-col">
|
||||
<BsmImage image={BeatConflict} className="size-28"/>
|
||||
|
||||
@@ -4,12 +4,18 @@ import { useObservable } from "renderer/hooks/use-observable.hook";
|
||||
import { useEffect } from "react";
|
||||
import { BsmIcon } from "../svgs/bsm-icon.component";
|
||||
import { ThemeColorGradientSpliter } from "../shared/theme-color-gradient-spliter.component";
|
||||
import { map } from "rxjs";
|
||||
import { useConstant } from "renderer/hooks/use-constant.hook";
|
||||
|
||||
export function Modal() {
|
||||
const modalSevice = ModalService.getInstance();
|
||||
|
||||
const modals = useObservable(() => modalSevice.getModalToShow());
|
||||
const currentModal = modals?.at(-1);
|
||||
const modals$ = useConstant(() => modalSevice.getModalToShow());
|
||||
|
||||
const modals = useObservable(() => modals$);
|
||||
const currentModal = useObservable<ModalObject>(() =>modals$.pipe(map(modals => modals?.at(-1))));
|
||||
|
||||
console.log(currentModal, modals, !!currentModal);
|
||||
|
||||
useEffect(() => {
|
||||
const onEscape = (e: KeyboardEvent) => {
|
||||
@@ -56,9 +62,9 @@ export function Modal() {
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{currentModal && <motion.span key="modal-overlay" onClick={() => currentModal.resolver({ exitCode: ModalExitCode.NO_CHOICE })} className="fixed size-full bg-black z-[90]" initial={{ opacity: 0 }} animate={{ opacity: currentModal && 0.6 }} exit={{ opacity: 0 }} transition={{ duration: 0.2 }} />}
|
||||
{modals?.map((modal, index) => (
|
||||
<motion.div key={index} className="fixed z-[90] top-1/2 left-1/2" initial={{ y: "100vh", x: "-50%" }} animate={{y: "-50%", scale: modal === currentModal ? 1 : 0, opacity: modal === currentModal ? 1 : 0, display: modal === currentModal ? "block" : ["block", "none"]}} exit={{ y: "100vh" }}>
|
||||
{currentModal ? <motion.span key={crypto.randomUUID()} onClick={() => currentModal.resolver({ exitCode: ModalExitCode.NO_CHOICE })} className="fixed size-full bg-black z-[90]" initial={{ opacity: 0 }} animate={{ opacity: currentModal && 0.6 }} exit={{ opacity: 0 }} transition={{ duration: 0.2 }} /> : <></>}
|
||||
{modals?.map(modal => (
|
||||
<motion.div key={crypto.randomUUID()} className="fixed z-[90] top-1/2 left-1/2" initial={{ y: "100vh", x: "-50%" }} animate={{y: "-50%", scale: modal === currentModal ? 1 : 0, opacity: modal === currentModal ? 1 : 0, display: modal === currentModal ? "block" : ["block", "none"]}} exit={{ y: "100vh" }}>
|
||||
{renderModal(modal)}
|
||||
</motion.div>
|
||||
))}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { BehaviorSubject, Observable, Subject, Subscription, distinctUntilChanged, filter, lastValueFrom, map, shareReplay, take, takeUntil, takeWhile, tap } from "rxjs";
|
||||
import { DownloadPlaylistProgressionData } from "shared/models/playlists/playlist.interface";
|
||||
import { BPList, DownloadPlaylistProgressionData } from "shared/models/playlists/playlist.interface";
|
||||
import { IpcService } from "./ipc.service";
|
||||
import { ProgressBarService } from "./progress-bar.service";
|
||||
import { Progression } from "main/helpers/fs.helpers";
|
||||
@@ -126,6 +126,10 @@ export class PlaylistDownloaderService {
|
||||
public openDownloadPlaylistModal(version: BSVersion, ownedPlaylists$: Observable<LocalBPListsDetails[]>, ownedMaps$: Observable<BsmLocalMap[]>): Promise<ModalResponse<void>> {
|
||||
return this.modal.openModal(DownloadPlaylistModal, { data: { version, ownedPlaylists$, ownedMaps$ } })
|
||||
}
|
||||
|
||||
public installPlaylistFile(bplist: BPList, version?: BSVersion, dest?: string){
|
||||
return this.ipc.sendV2("install-playlist-file", { bplist, version, dest });
|
||||
}
|
||||
}
|
||||
|
||||
export type PlaylistQueueInfo = {
|
||||
|
||||
@@ -86,6 +86,7 @@ export interface IpcChannelMapping {
|
||||
"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, playlistsMaps?: BsmLocalMap[]}, response: Progression<string>};
|
||||
"install-playlist-file": {request: {bplist: BPList, version?: BSVersion, dest?: string}, response: LocalBPListsDetails};
|
||||
|
||||
/* ** bs-uninstall-ipcs ** */
|
||||
"bs.uninstall": { request: BSVersion, response: boolean };
|
||||
|
||||
Reference in New Issue
Block a user