we can now delete mutliple maps at the same time

This commit is contained in:
MathieuG-P
2022-12-07 23:10:59 +01:00
parent 77bc6236f8
commit 6df2c98955
4 changed files with 59 additions and 28 deletions
@@ -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>
)
}
})
@@ -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"/>}