mirror of
https://github.com/Zagrios/bs-manager.git
synced 2026-07-03 14:08:25 +02:00
we can now delete mutliple maps at the same time
This commit is contained in:
+27
-19
@@ -1,6 +1,6 @@
|
||||
import { MapsManagerService } from "renderer/services/maps-manager.service"
|
||||
import { BSVersion } from "shared/bs-version.interface"
|
||||
import { useCallback, useEffect, useRef, useState } from "react"
|
||||
import { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from "react"
|
||||
import { BsmLocalMap } from "shared/models/maps/bsm-local-map.interface"
|
||||
import { Subscription } from "rxjs"
|
||||
import { MapItem, ParsedMapDiff } from "./map-item.component"
|
||||
@@ -15,16 +15,26 @@ type Props = {
|
||||
search?: string,
|
||||
}
|
||||
|
||||
export function LocalMapsListPanel({version, className, filter, search} : Props) {
|
||||
export const LocalMapsListPanel = forwardRef(({version, className, filter, search} : Props, forwardRef) => {
|
||||
|
||||
const mapsManager = MapsManagerService.getInstance();
|
||||
const mapsDownloader = MapsDownloaderService.getInstance();
|
||||
|
||||
const ref = useRef()
|
||||
const ref = useRef(null)
|
||||
const isVisible = useInView(ref, {once: true});
|
||||
const [maps, setMaps] = useState([] as BsmLocalMap[]);
|
||||
const [selectedMaps, setSelectedMaps] = useState([] as string[]);
|
||||
const [maps, setMaps] = useState<BsmLocalMap[]>([]);
|
||||
const [subs] = useState<Subscription[]>([]);
|
||||
const [selectedMaps, setSelectedMaps] = useState([]);
|
||||
|
||||
useImperativeHandle(forwardRef ,()=>({
|
||||
deleteMaps(){
|
||||
const mapsToDelete = selectedMaps.length === 0 ? maps : selectedMaps
|
||||
mapsManager.deleteMaps(mapsToDelete).finally(loadMaps);
|
||||
},
|
||||
exportMaps(){
|
||||
console.log("TODO EXPORT MAPS");
|
||||
}
|
||||
}), [selectedMaps, maps]);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -46,11 +56,9 @@ export function LocalMapsListPanel({version, className, filter, search} : Props)
|
||||
subs.push(mapsManager.getMaps(version).subscribe(localMaps => setMaps(() => [...localMaps])));
|
||||
}
|
||||
|
||||
const handleDelete = useCallback((hash: string) => {
|
||||
const map = maps.find(map => map.hash === hash);
|
||||
console.log(maps);
|
||||
const handleDelete = useCallback((map: BsmLocalMap) => {
|
||||
mapsManager.deleteMaps([map], version).then(res => res && loadMaps())
|
||||
}, [maps]);
|
||||
}, []);
|
||||
|
||||
const extractMapDiffs = (map: BsmLocalMap): Map<BsvMapCharacteristic, ParsedMapDiff[]> => {
|
||||
const res = new Map<BsvMapCharacteristic, ParsedMapDiff[]>();
|
||||
@@ -75,16 +83,16 @@ export function LocalMapsListPanel({version, className, filter, search} : Props)
|
||||
return res;
|
||||
}
|
||||
|
||||
const onMapSelected = useCallback((hash: string) => {
|
||||
const hashs = [...selectedMaps];
|
||||
if(hashs.some(selectedHash => selectedHash === hash)){
|
||||
const i = hashs.findIndex(selectedHash => selectedHash === hash);
|
||||
hashs.splice(i, 1);
|
||||
const onMapSelected = useCallback((map: BsmLocalMap) => {
|
||||
const maps = [...selectedMaps];
|
||||
if(maps.some(selectedMap => selectedMap.hash === map.hash)){
|
||||
const i = maps.findIndex(selectedMap => selectedMap.hash === map.hash);
|
||||
maps.splice(i, 1);
|
||||
}
|
||||
else{
|
||||
hashs.push(hash);
|
||||
maps.push(map);
|
||||
}
|
||||
setSelectedMaps(() => hashs);
|
||||
setSelectedMaps(() => maps);
|
||||
}, [selectedMaps]);
|
||||
|
||||
const isMapFitFilter = (map: BsmLocalMap): boolean => {
|
||||
@@ -199,11 +207,11 @@ export function LocalMapsListPanel({version, className, filter, search} : Props)
|
||||
songAutor={map.rawInfo._songAuthorName}
|
||||
bpm={map.rawInfo._beatsPerMinute}
|
||||
duration={map.bsaverInfo?.metadata?.duration}
|
||||
selected={selectedMaps.some(hash => hash === map.hash)}
|
||||
selected={selectedMaps.some(_map => _map.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={handleDelete}
|
||||
onSelected={onMapSelected}
|
||||
callBackParam={map.hash}
|
||||
callBackParam={map}
|
||||
/>;
|
||||
}
|
||||
|
||||
@@ -214,4 +222,4 @@ export function LocalMapsListPanel({version, className, filter, search} : Props)
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
+22
-7
@@ -1,8 +1,8 @@
|
||||
import { DetailedHTMLProps, useEffect, useState } from "react"
|
||||
import { createRef, DetailedHTMLProps, useEffect, useRef, useState } from "react"
|
||||
import { BSVersion } from "shared/bs-version.interface"
|
||||
import { TabNavBar } from "../shared/tab-nav-bar.component"
|
||||
import { LocalMapsListPanel } from "./local-maps-list-panel.component"
|
||||
import { BsmDropdownButton } from "../shared/bsm-dropdown-button.component"
|
||||
import { BsmDropdownButton, DropDownItem } from "../shared/bsm-dropdown-button.component"
|
||||
import { FilterPanel } from "./filter-panel.component"
|
||||
import { MapFilter, MapTag } from "shared/models/maps/beat-saver.model"
|
||||
import { BsmButton } from "../shared/bsm-button.component"
|
||||
@@ -11,6 +11,8 @@ import { MapsManagerService } from "renderer/services/maps-manager.service"
|
||||
import { motion, Variants } from "framer-motion";
|
||||
import ReactTooltip from 'react-tooltip';
|
||||
import { MapsDownloaderService } from "renderer/services/maps-downloader.service"
|
||||
import { BehaviorSubject } from "rxjs"
|
||||
import { BsmLocalMap } from "shared/models/maps/bsm-local-map.interface"
|
||||
|
||||
type Props = {
|
||||
version?: BSVersion
|
||||
@@ -20,6 +22,7 @@ export function MapsPlaylistsPanel({version}: Props) {
|
||||
|
||||
const mapsService = MapsManagerService.getInstance();
|
||||
const mapsDownloader = MapsDownloaderService.getInstance();
|
||||
const mapsManager = MapsManagerService.getInstance();
|
||||
|
||||
const [tabIndex, setTabIndex] = useState(0);
|
||||
const [mapFilter, setMapFilter] = useState<MapFilter>({
|
||||
@@ -29,8 +32,9 @@ export function MapsPlaylistsPanel({version}: Props) {
|
||||
const [mapSearch, setMapSearch] = useState("");
|
||||
const [playlistSearch, setPlaylistSearch] = useState("");
|
||||
const [mapsLinked, setMapsLinked] = useState(false);
|
||||
const color = useThemeColor("first-color")
|
||||
const color = useThemeColor("first-color");
|
||||
|
||||
const mapsRef = useRef<any>();
|
||||
|
||||
useEffect(() => {
|
||||
loadMapIsLinked();
|
||||
@@ -90,6 +94,19 @@ export function MapsPlaylistsPanel({version}: Props) {
|
||||
)
|
||||
}
|
||||
|
||||
const dropDownItems = ((): DropDownItem[] => {
|
||||
if(tabIndex === 1){
|
||||
return [
|
||||
|
||||
]
|
||||
}
|
||||
return [
|
||||
{icon:"export", text: "Exporter les maps", onClick: () => mapsRef.current.exportMaps?.()},
|
||||
{icon: "trash", text: "Supprimer les maps", onClick: () => mapsRef.current.deleteMaps?.()}
|
||||
]
|
||||
|
||||
})()
|
||||
|
||||
return (
|
||||
<div className="w-full h-full flex flex-col items-center justify-center">
|
||||
<nav className="w-full shrink-0 flex h-[35px] justify-center px-40 gap-2 mb-3">
|
||||
@@ -99,14 +116,12 @@ export function MapsPlaylistsPanel({version}: Props) {
|
||||
<BsmDropdownButton className="h-full relative z-[1] flex justify-center" buttonClassName="flex items-center justify-center h-full rounded-full px-2 py-1" icon="search" text="Filtres" withBar={false}>
|
||||
<FilterPanel className="absolute top-[calc(100%+3px)] bg-main-color-3 origin-top w-[500px] h-fit p-2 rounded-md shadow-md shadow-black" filter={mapFilter} onChange={setMapFilter}/>
|
||||
</BsmDropdownButton>
|
||||
<BsmDropdownButton className="h-full flex aspect-square relative rounded-full z-[1]" buttonClassName="rounded-full h-full w-full p-[6px]" icon="three-dots" withBar={false}>
|
||||
<></>
|
||||
</BsmDropdownButton>
|
||||
<BsmDropdownButton className="h-full flex aspect-square relative rounded-full z-[1] bg-main-color-3" buttonClassName="rounded-full h-full w-full p-[6px]" icon="three-dots" withBar={false} items={dropDownItems} menuTranslationY="6px" align="center"/>
|
||||
</nav>
|
||||
<div className="w-full h-full flex flex-col bg-main-color-2 rounded-md shadow-black shadow-md overflow-hidden">
|
||||
<TabNavBar className="!rounded-none shadow-sm" tabsText={["misc.maps", "Playlists"]} onTabChange={setTabIndex} renderTab={renderTab}/>
|
||||
<div className="w-full grow min-h-0 flex flex-row items-center transition-transform duration-300" style={{transform: `translate(${-(tabIndex * 100)}%, 0)`}}>
|
||||
<LocalMapsListPanel className="w-full h-full shrink-0 flex flex-col" version={version} filter={mapFilter} search={mapSearch}/>
|
||||
<LocalMapsListPanel ref={mapsRef} className="w-full h-full shrink-0 flex flex-col" version={version} filter={mapFilter} search={mapSearch}/>
|
||||
<div>b</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -16,6 +16,8 @@ export const DeleteMapsModal: ModalComponent<void, {linked: boolean, maps: BsmLo
|
||||
const infoText = multiple ? "modals.maps-actions.delete-maps.info.desc.multiple" : "modals.maps-actions.delete-maps.info.desc.single";
|
||||
const infoTitleText = multiple ? "modals.maps-actions.delete-maps.info.title.multiple" : "modals.maps-actions.delete-maps.info.title.single";
|
||||
|
||||
console.log(maps);
|
||||
|
||||
return (
|
||||
<form className="text-gray-800 dark:text-gray-200">
|
||||
<h1 className="text-3xl uppercase tracking-wide w-full text-center">{t(titleText)}</h1>
|
||||
|
||||
@@ -11,7 +11,7 @@ export interface DropDownItem {text: string, icon?: BsmIconType, onClick?: () =>
|
||||
type Props = {
|
||||
className?: string,
|
||||
items?: DropDownItem[],
|
||||
align?: "left"|"right",
|
||||
align?: "left"|"right"|"center",
|
||||
withBar?: boolean,
|
||||
icon?: BsmIconType,
|
||||
buttonClassName?: string,
|
||||
@@ -35,10 +35,16 @@ export function BsmDropdownButton({className, items, align, withBar = true, icon
|
||||
setExpanded(false);
|
||||
}
|
||||
|
||||
const alignClass = (() => {
|
||||
if(align === "center"){ return "right-1/2 origin-top-right translate-x-[50%]" }
|
||||
if(align === "left"){ return "left-0 origin-top-left"; }
|
||||
return "right-0 origin-top-right";
|
||||
})()
|
||||
|
||||
return (
|
||||
<div ref={ref} className={className}>
|
||||
<BsmButton onClick={() => setExpanded(!expanded)} className={buttonClassName ?? defaultButtonClassName} icon={icon} active={expanded} onClickOutside={handleClickOutside} withBar={withBar} text={text}/>
|
||||
<div className={`py-1 w-fit absolute cursor-pointer top-[calc(100%-4px)] rounded-md bg-inherit text-sm text-gray-800 dark:text-gray-200 shadow-md shadow-black transition-[scale] ease-in-out ${align === "left" ? "left-0 origin-top-left" : "right-0 origin-top-right"}`} style={{scale: expanded ? "1" : "0", translate: `0 ${menuTranslationY}`}}>
|
||||
<div className={`py-1 w-fit absolute cursor-pointer top-[calc(100%-4px)] rounded-md bg-inherit text-sm text-gray-800 dark:text-gray-200 shadow-md shadow-black transition-[scale] ease-in-out ${alignClass}`} style={{scale: expanded ? "1" : "0", translate: `0 ${menuTranslationY}`}}>
|
||||
{ items?.map((i) => (
|
||||
<div key={uuidv4()} onClick={() => i.onClick?.()} className="flex w-full px-3 py-2 hover:backdrop-brightness-150">
|
||||
{i.icon && <BsmIcon icon={i.icon} className="h-5 w-5 mr-1 text-inherit"/>}
|
||||
|
||||
Reference in New Issue
Block a user