mirror of
https://github.com/Zagrios/bs-manager.git
synced 2026-07-03 14:08:25 +02:00
add link unlink maps modals AND start maps deletion
This commit is contained in:
@@ -47,4 +47,16 @@ ipcMain.on("unlink-version-maps", async (event, request: IpcRequest<{version: BS
|
||||
utils.ipcSend<void>(request.responceChannel, {success: true, error: err});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
ipcMain.on("delete-maps", async (event, request: IpcRequest<{version: BSVersion, maps: BsmLocalMap[]}>) => {
|
||||
const utils = UtilsService.getInstance();
|
||||
const maps = LocalMapsManagerService.getInstance();
|
||||
|
||||
maps.deleteMaps(request.args.maps, request.args.version).then(() => {
|
||||
utils.ipcSend<void>(request.responceChannel, {success: true});
|
||||
}).catch(err => {
|
||||
utils.ipcSend<void>(request.responceChannel, {success: true, error: err});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -127,6 +127,10 @@ export class LocalMapsManagerService {
|
||||
|
||||
}
|
||||
|
||||
public async deleteMaps(maps: BsmLocalMap[], verion?: BSVersion){
|
||||
// TODO
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
+11
-9
@@ -22,16 +22,10 @@ export function LocalMapsListPanel({version, className, filter, search} : Props)
|
||||
const isVisible = useInView(ref, {once: true});
|
||||
const [maps, setMaps] = useState([] as BsmLocalMap[]);
|
||||
const [selectedMaps, setSelectedMaps] = useState([] as string[]);
|
||||
const [subs] = useState<Subscription[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
const subs: Subscription[] = [];
|
||||
|
||||
const loadMaps = () => {
|
||||
console.log("LOAD MAPS");
|
||||
subs.push(mapsManager.getMaps(version).subscribe(localMaps => setMaps(() => [...localMaps])));
|
||||
}
|
||||
|
||||
if(isVisible){
|
||||
loadMaps();
|
||||
subs.push(mapsManager.versionLinked$.subscribe(loadMaps));
|
||||
@@ -42,7 +36,15 @@ export function LocalMapsListPanel({version, className, filter, search} : Props)
|
||||
setMaps(() => []);
|
||||
subs.forEach(s => s.unsubscribe());
|
||||
}
|
||||
}, [isVisible, version])
|
||||
}, [isVisible, version]);
|
||||
|
||||
const loadMaps = () => {
|
||||
subs.push(mapsManager.getMaps(version).subscribe(localMaps => setMaps(() => [...localMaps])));
|
||||
}
|
||||
|
||||
const handleDelete = (map: BsmLocalMap) => {
|
||||
mapsManager.deleteMaps([map], version).then(res => res && loadMaps())
|
||||
}
|
||||
|
||||
const extractMapDiffs = (map: BsmLocalMap): Map<BsvMapCharacteristic, ParsedMapDiff[]> => {
|
||||
const res = new Map<BsvMapCharacteristic, ParsedMapDiff[]>();
|
||||
@@ -193,7 +195,7 @@ export function LocalMapsListPanel({version, className, filter, search} : Props)
|
||||
duration={map.bsaverInfo?.metadata?.duration}
|
||||
selected={selectedMaps.some(hash => hash === map.hash)}
|
||||
diffs={extractMapDiffs(map)} mapId={map.bsaverInfo?.id} qualified={null} ranked={map.bsaverInfo?.ranked} autorId={map.bsaverInfo?.uploader?.id} likes={map.bsaverInfo?.stats?.upvotes} createdAt={map.bsaverInfo?.createdAt}
|
||||
onDelete={() => {}}
|
||||
onDelete={() => handleDelete(map)}
|
||||
onSelected={onMapSelected}
|
||||
/>;
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ export function MapItem({hash, title, autor, songAutor, coverUrl, songUrl, autor
|
||||
const songPlaying = useObservable(audioPlayer.playing$.pipe(map(playing => playing && audioPlayer.src === songUrl)));
|
||||
|
||||
const zipUrl = `https://r2cdn.beatsaver.com/${hash}.zip`;
|
||||
const previewUrl = mapId ? `https://skystudioapps.com/bs-viewer/?id=${mapId}` : null;
|
||||
const previewUrl = mapId ? `https://skystudioapps.com/bs-viewer/?url=${zipUrl}` : null;
|
||||
const mapUrl = mapId ? `https://beatsaver.com/maps/${mapId}` : null;
|
||||
const authorUrl = autorId ? `https://beatsaver.com/profile/${autorId}` : null;
|
||||
const createdDate = createdAt ? dateFormat(createdAt, "d mmm yyyy") : null;
|
||||
@@ -204,7 +204,7 @@ export function MapItem({hash, title, autor, songAutor, coverUrl, songUrl, autor
|
||||
<span className="absolute w-[10px] h-[10px] bottom-0 right-full bg-inherit" style={{clipPath: 'path("M11 11 L11 0 L10 0 A10 10 0 0 1 0 10 L 0 11 Z")'}}/>
|
||||
|
||||
<div className="flex flex-col justify-center items-center gap-1 w-full h-full overflow-hidden opacity-0 group-hover:opacity-100">
|
||||
{onDelete && <BsmButton className="w-6 h-6 p-[2px] rounded-md !bg-inherit hover:!bg-main-color-2" iconClassName="w-full h-full brightness-150" iconColor={color} icon="trash" withBar={false} onClick={e => {e.stopPropagation(); openPreview()}}/>}
|
||||
{onDelete && <BsmButton className="w-6 h-6 p-[2px] rounded-md !bg-inherit hover:!bg-main-color-2" iconClassName="w-full h-full brightness-150" iconColor={color} icon="trash" withBar={false} onClick={e => {e.stopPropagation(); onDelete(hash)}}/>}
|
||||
{previewUrl && <BsmButton className="w-6 h-6 p-[2px] rounded-md !bg-inherit hover:!bg-main-color-2" iconClassName="w-full h-full brightness-150" iconColor={color} icon="eye" withBar={false} onClick={e => {e.stopPropagation(); openPreview()}}/>}
|
||||
{mapId && <BsmButton className="w-6 h-6 p-1 rounded-md !bg-inherit hover:!bg-main-color-2" iconClassName="w-full h-full brightness-150" iconColor={color} icon="twitch" withBar={false} onClick={e => {e.stopPropagation(); copyBsr()}}/>}
|
||||
</div>
|
||||
|
||||
+5
-8
@@ -49,27 +49,24 @@ export function MapsPlaylistsPanel({version, oneBlock = false}: Props) {
|
||||
if(!mapsLinked){
|
||||
return mapsService.linkVersion(version).then(loadMapIsLinked);
|
||||
}
|
||||
mapsService.unlinkVersion(version).then(loadMapIsLinked);
|
||||
return mapsService.unlinkVersion(version).then(loadMapIsLinked);
|
||||
}
|
||||
|
||||
const handlePlaylistLinkClick = () => {
|
||||
|
||||
//TODO
|
||||
}
|
||||
|
||||
const renderTab = (props: DetailedHTMLProps<React.HTMLAttributes<HTMLLIElement>, HTMLLIElement>, text: string, index: number): JSX.Element => {
|
||||
|
||||
const mainColor = mapsLinked ? color : "red";
|
||||
|
||||
const variants: Variants = {
|
||||
hover: {rotate: 22.5},
|
||||
tap: {rotate: 45}
|
||||
}
|
||||
|
||||
const onClickLink = (index: number) => {
|
||||
if(index === 0){ return handleMapsLinkClick() };
|
||||
handlePlaylistLinkClick();
|
||||
return handlePlaylistLinkClick();
|
||||
}
|
||||
|
||||
const variants: Variants = { hover: {rotate: 22.5}, tap: {rotate: 45} };
|
||||
|
||||
return (
|
||||
<li className="relative text-center text-lg font-bold hover:backdrop-brightness-75 flex justify-center items-center content-center" onClick={props.onClick}>
|
||||
<span>{text}</span>
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
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 { BsmLocalMap } from "shared/models/maps/bsm-local-map.interface";
|
||||
import BeatConflict from '../../../../../assets/images/apngs/beat-conflict.png'
|
||||
|
||||
export const DeleteMapsModal: ModalComponent<void, {linked: boolean, maps: BsmLocalMap[]}> = ({resolver, data:{linked, maps}}) => {
|
||||
|
||||
const t = useTranslation();
|
||||
|
||||
const multiple = maps.length > 1;
|
||||
|
||||
const titleText = multiple ? "Supprimer les maps" : "Supprimer la map";
|
||||
const descText = multiple ? `Est-tu sur de vouloir supprimer les ${maps.length} maps ?` : `Est-tu sur de vouloir supprimer la map ${maps.at(0).rawInfo._songName} ?`;
|
||||
const linkDescText = multiple ? "Ces maps font parties des maps partagées" : "Cette map fait partie des maps paratagées";
|
||||
|
||||
return (
|
||||
<form className="text-gray-800 dark:text-gray-200">
|
||||
<h1 className="text-3xl uppercase tracking-wide w-full text-center">{titleText}</h1>
|
||||
<BsmImage className="mx-auto h-24" image={BeatConflict} />
|
||||
<p className="max-w-sm w-full">{descText}</p>
|
||||
{linked && <p className="max-w-sm w-full text-sm italic mt-2">{linkDescText}</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="Supprimer"/>
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { useState } from "react";
|
||||
import { BsmButton } from "renderer/components/shared/bsm-button.component";
|
||||
import { BsmCheckbox } from "renderer/components/shared/bsm-checkbox.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 BeatRunning from '../../../../../assets/images/apngs/beat-running.png'
|
||||
|
||||
export const LinkMapsModal: ModalComponent<boolean> = ({resolver}) => {
|
||||
|
||||
const t = useTranslation();
|
||||
const [keepMaps, setKeepMaps] = useState(true);
|
||||
|
||||
|
||||
return (
|
||||
<form className="text-gray-800 dark:text-gray-200">
|
||||
<h1 className="text-3xl uppercase tracking-wide w-full text-center">Lier les maps</h1>
|
||||
<BsmImage className="mx-auto h-24" image={BeatRunning} />
|
||||
<p className="max-w-sm w-full">La liaison des maps permet de partager les maps entre toute les version. Lier cette version permettra l'utilisation des maps partager</p>
|
||||
<p className="max-w-sm w-full text-sm italic">L'ajout et la suppression de maps sera également partagés</p>
|
||||
<div className="relative h-5 flex my-3 items-center">
|
||||
<BsmCheckbox className="h-full aspect-square relative bg-inherit mr-1.5 z-[1]" checked={keepMaps} onChange={setKeepMaps}/>
|
||||
<span className="text-sm mb-0.5 cursor-help" title="Conserver les maps déplacera les maps de la version actuelle dans le dossier des maps partagées. Dans le cas contraire elles seront perdues">Conserver les maps</span>
|
||||
</div>
|
||||
<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, data: keepMaps})} withBar={false} text="Lier les maps"/>
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { useState } from "react";
|
||||
import { BsmButton } from "renderer/components/shared/bsm-button.component";
|
||||
import { BsmCheckbox } from "renderer/components/shared/bsm-checkbox.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 BeatConflict from '../../../../../assets/images/apngs/beat-conflict.png'
|
||||
|
||||
export const UnlinkMapsModal: ModalComponent<boolean> = ({resolver}) => {
|
||||
|
||||
const t = useTranslation();
|
||||
const [keepMaps, setKeepMaps] = useState(true);
|
||||
|
||||
return (
|
||||
<form className="text-gray-800 dark:text-gray-200">
|
||||
<h1 className="text-3xl uppercase tracking-wide w-full text-center">Délier les maps</h1>
|
||||
<BsmImage className="mx-auto h-24" image={BeatConflict} />
|
||||
<p className="max-w-sm w-full">Attention, délier les maps ne permettra plus l'utilisation des maps paratagés pour cette version.</p>
|
||||
<div className="relative h-5 flex my-3 items-center">
|
||||
<BsmCheckbox className="h-full aspect-square relative bg-inherit mr-1.5 z-[1]" checked={keepMaps} onChange={setKeepMaps}/>
|
||||
<span className="text-sm mb-0.5 cursor-help" title="Conserver les maps créera une copie des maps partagées pour la version actuelle. Dans le cas contraire, aucune map ne sera conservée pour cette version.">Conserver les maps</span>
|
||||
</div>
|
||||
<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="error" className="rounded-md text-center transition-all" onClick={() => resolver({exitCode: ModalExitCode.COMPLETED, data: keepMaps})} withBar={false} text="délier les maps"/>
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
@@ -1,8 +1,13 @@
|
||||
import { LinkMapsModal } from "renderer/components/modal/modal-types/link-maps-modal.component";
|
||||
import { UnlinkMapsModal } from "renderer/components/modal/modal-types/unlink-maps-modal.component";
|
||||
import { finalize } from "rxjs/operators";
|
||||
import { Subject, Observable } from "rxjs";
|
||||
import { BSVersion } from "shared/bs-version.interface";
|
||||
import { BsmLocalMap } from "shared/models/maps/bsm-local-map.interface";
|
||||
import { BeatSaverService } from "./beat-saver/beat-saver.service";
|
||||
import { IpcService } from "./ipc.service";
|
||||
import { ModalExitCode, ModalService } from "./modale.service";
|
||||
import { DeleteMapsModal } from "renderer/components/modal/modal-types/delete-maps-modal.component";
|
||||
|
||||
export class MapsManagerService {
|
||||
|
||||
@@ -15,6 +20,7 @@ export class MapsManagerService {
|
||||
|
||||
private readonly ipcService: IpcService;
|
||||
private readonly bsaver: BeatSaverService;
|
||||
private readonly modal: ModalService
|
||||
|
||||
private readonly lastLinkedVersion$: Subject<BSVersion> = new Subject();
|
||||
private readonly lastUnlinkedVersion$: Subject<BSVersion> = new Subject();
|
||||
@@ -22,6 +28,7 @@ export class MapsManagerService {
|
||||
private constructor(){
|
||||
this.ipcService = IpcService.getInstance();
|
||||
this.bsaver = BeatSaverService.getInstance();
|
||||
this.modal = ModalService.getInsance();
|
||||
}
|
||||
|
||||
public getMaps(version?: BSVersion): Observable<BsmLocalMap[]>{
|
||||
@@ -30,7 +37,7 @@ export class MapsManagerService {
|
||||
if(!res.success){ return obs.next(null);}
|
||||
obs.next(res.data);
|
||||
|
||||
this.bsaver.getMapDetailsFromHashs(res.data.map(localMap => localMap.hash)).subscribe(mapsDetails => {
|
||||
this.bsaver.getMapDetailsFromHashs(res.data.map(localMap => localMap.hash)).pipe(finalize(() => obs.complete())).subscribe(mapsDetails => {
|
||||
mapsDetails.forEach(details => {
|
||||
res.data.find(localMap => localMap.hash === details.versions.find(details => details?.hash === localMap.hash)?.hash).bsaverInfo = details
|
||||
});
|
||||
@@ -47,22 +54,44 @@ export class MapsManagerService {
|
||||
})
|
||||
}
|
||||
|
||||
public linkVersion(version: BSVersion): Promise<void>{
|
||||
return this.ipcService.send<void, {version: BSVersion, keepMaps: boolean}>("link-version-maps", {args: {version, keepMaps: true}}).then(res => {
|
||||
if(res.success){
|
||||
return this.lastLinkedVersion$.next(version);
|
||||
}
|
||||
throw res.error;
|
||||
});
|
||||
public async linkVersion(version: BSVersion): Promise<void>{
|
||||
|
||||
const modalRes = await this.modal.openModal(LinkMapsModal);
|
||||
|
||||
if(modalRes.exitCode !== ModalExitCode.COMPLETED){ return; }
|
||||
|
||||
const res = await this.ipcService.send<void, {version: BSVersion, keepMaps: boolean}>("link-version-maps", {args: {version, keepMaps: !!modalRes.data}});
|
||||
|
||||
if(res.success){
|
||||
this.lastLinkedVersion$.next(version);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public unlinkVersion(version: BSVersion): Promise<void>{
|
||||
return this.ipcService.send<void, {version: BSVersion, keepMaps: boolean}>("unlink-version-maps", {args: {version, keepMaps: true}}).then(res => {
|
||||
if(res.success){
|
||||
return this.lastUnlinkedVersion$.next(version);
|
||||
}
|
||||
throw res.error;
|
||||
});
|
||||
public async unlinkVersion(version: BSVersion): Promise<void>{
|
||||
|
||||
const modalRes = await this.modal.openModal(UnlinkMapsModal);
|
||||
|
||||
if(modalRes.exitCode !== ModalExitCode.COMPLETED){ return; }
|
||||
|
||||
const res = await this.ipcService.send<void, {version: BSVersion, keepMaps: boolean}>("unlink-version-maps", {args: {version, keepMaps: !!modalRes.data}});
|
||||
|
||||
if(res.success){
|
||||
return this.lastUnlinkedVersion$.next(version);
|
||||
}
|
||||
}
|
||||
|
||||
public async deleteMaps(maps: BsmLocalMap[], version?: BSVersion): Promise<boolean>{
|
||||
|
||||
const versionLinked = await this.versionHaveMapsLinked(version);
|
||||
|
||||
const modalRes = await this.modal.openModal(DeleteMapsModal, {linked: versionLinked, maps});
|
||||
|
||||
if(modalRes.exitCode !== ModalExitCode.COMPLETED){ return false; }
|
||||
|
||||
const res = await this.ipcService.send<void, {version: BSVersion, maps: BsmLocalMap[]}>("delete-maps", {args: {version, maps}});
|
||||
|
||||
return res.success;
|
||||
}
|
||||
|
||||
public get versionLinked$(): Observable<BSVersion>{
|
||||
|
||||
Reference in New Issue
Block a user