mirror of
https://github.com/Zagrios/bs-manager.git
synced 2026-07-03 14:08:25 +02:00
we can now see maps from bsaver
This commit is contained in:
@@ -231,6 +231,7 @@
|
||||
"electron-log": "^4.4.8",
|
||||
"electron-store": "^8.1.0",
|
||||
"electron-updater": "^5.2.4",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"framer-motion": "^7.2.1",
|
||||
"fs-extra": "^10.1.0",
|
||||
"history": "^5.3.0",
|
||||
|
||||
@@ -171,12 +171,12 @@ export function FilterPanel({className, ref, playlist = false, filter, onChange}
|
||||
<h2 className="uppercase text-sm mb-1">TAGS</h2>
|
||||
<div className="w-full flex flex-row flex-wrap items-start justify-start content-start gap-1 mb-2">
|
||||
{MAP_TYPES.map(tag => (
|
||||
<span key={tag} onClick={e => handleTagClick(tag)} className={`text-sm text-black rounded-md px-1 font-bold cursor-pointer ${(!isTagActivated(tag)) && "opacity-40 hover:opacity-100"}`} style={{backgroundColor: isTagExcluded(tag) ? diffColors.Expert : diffColors.Normal}}>{tag}</span>
|
||||
<span key={tag} onClick={e => handleTagClick(tag)} className={`text-[12.5px] text-black rounded-md px-1 font-bold cursor-pointer ${(!isTagActivated(tag)) && "opacity-40 hover:opacity-100"}`} style={{backgroundColor: isTagExcluded(tag) ? diffColors.Expert : diffColors.Normal}}>{tag}</span>
|
||||
))}
|
||||
</div>
|
||||
<div className="w-full flex flex-row flex-wrap items-start justify-start content-start gap-1">
|
||||
{MAP_STYLES.map(tag => (
|
||||
<span key={tag} onClick={e => handleTagClick(tag)} className={`text-sm text-black rounded-md px-1 font-bold cursor-pointer ${(!isTagActivated(tag)) && "opacity-40 hover:opacity-100"}`} style={{backgroundColor: isTagExcluded(tag) ? diffColors.Expert : diffColors.Easy}}>{tag}</span>
|
||||
<span key={tag} onClick={e => handleTagClick(tag)} className={`text-[12.5px] text-black rounded-md px-1 font-bold cursor-pointer ${(!isTagActivated(tag)) && "opacity-40 hover:opacity-100"}`} style={{backgroundColor: isTagExcluded(tag) ? diffColors.Expert : diffColors.Easy}}>{tag}</span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { motion, useInView } from "framer-motion";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { FilterPanel } from "renderer/components/maps-mangement-components/filter-panel.component";
|
||||
import { MapItem, ParsedMapDiff } from "renderer/components/maps-mangement-components/map-item.component";
|
||||
import { BsmButton } from "renderer/components/shared/bsm-button.component";
|
||||
import { BsmDropdownButton } from "renderer/components/shared/bsm-dropdown-button.component";
|
||||
import { BsmSelect, BsmSelectOption } from "renderer/components/shared/bsm-select.component";
|
||||
@@ -7,42 +9,116 @@ import { BSV_SORT_ORDER } from "renderer/partials/beat-saver/sort-order";
|
||||
import { BeatSaverService } from "renderer/services/beat-saver/beat-saver.service";
|
||||
import { ModalComponent } from "renderer/services/modale.service";
|
||||
import { BSVersion } from "shared/bs-version.interface";
|
||||
import { BsvMapDetail, MapFilter } from "shared/models/maps/beat-saver.model";
|
||||
import { BsvMapCharacteristic, BsvMapDetail, MapFilter, SearchParams } from "shared/models/maps/beat-saver.model";
|
||||
import BeatWaitingImg from "../../../../../assets/images/apngs/beat-waiting.png"
|
||||
|
||||
export const DownloadMapsModal: ModalComponent<void, BSVersion> = ({data}) => {
|
||||
|
||||
const beatSaver = BeatSaverService.getInstance();
|
||||
|
||||
const [filter, setFilter] = useState<MapFilter>(null);
|
||||
const [maps, setMaps] = useState<BsvMapDetail[]>(null);
|
||||
const [page, setPage] = useState(0);
|
||||
const [filter, setFilter] = useState<MapFilter>({});
|
||||
const [query, setQuery] = useState("");
|
||||
const [maps, setMaps] = useState<BsvMapDetail[]>([]);
|
||||
const [sortOrder, setSortOrder] = useState(BSV_SORT_ORDER.at(0));
|
||||
|
||||
const [searchParams, setSearchParams] = useState<SearchParams>({
|
||||
sortOrder: sortOrder,
|
||||
filter: filter,
|
||||
page: 0,
|
||||
q: query
|
||||
});
|
||||
|
||||
const loaderRef = useRef(null);
|
||||
|
||||
const sortOptions: BsmSelectOption[] = (() => {
|
||||
return BSV_SORT_ORDER.map(sort => ({text: sort, value: sort}));
|
||||
})();
|
||||
|
||||
console.log(maps);
|
||||
|
||||
const loadMaps = (params: SearchParams) => {
|
||||
beatSaver.searchMaps(params).then((maps => setMaps(prev => [...prev, ...maps])));
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
beatSaver.searchMaps({
|
||||
page, sortOrder
|
||||
}).then(setMaps);
|
||||
}, [])
|
||||
loadMaps(searchParams);
|
||||
}, [searchParams]);
|
||||
|
||||
|
||||
const extractMapDiffs = (map: BsvMapDetail): Map<BsvMapCharacteristic, ParsedMapDiff[]> => {
|
||||
const res = new Map<BsvMapCharacteristic, ParsedMapDiff[]>();
|
||||
if(map.versions.at(0).diffs){
|
||||
map.versions.at(0).diffs.forEach(diff => {
|
||||
const arr = res.get(diff.characteristic) || [];
|
||||
arr.push({name: diff.difficulty, type: diff.difficulty, stars: diff.stars});
|
||||
res.set(diff.characteristic, arr);
|
||||
});
|
||||
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
const renderMap = (map: BsvMapDetail) => {
|
||||
return (
|
||||
<MapItem
|
||||
autor={map.metadata.levelAuthorName}
|
||||
autorId={map.uploader.id}
|
||||
bpm={map.metadata.bpm}
|
||||
coverUrl={map.versions.at(0).coverURL}
|
||||
createdAt={map.createdAt}
|
||||
duration={map.metadata.duration}
|
||||
hash={map.versions.at(0).hash}
|
||||
likes={map.stats.upvotes}
|
||||
mapId={map.id}
|
||||
ranked={map.ranked}
|
||||
qualified={map.qualified}
|
||||
title={map.name}
|
||||
songAutor={map.metadata.songAuthorName}
|
||||
diffs={extractMapDiffs(map)}
|
||||
songUrl={map.versions.at(0).previewURL}
|
||||
key={map.id}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const handleSearch = () => {
|
||||
const searchParams: SearchParams = {
|
||||
sortOrder: sortOrder,
|
||||
filter: filter,
|
||||
q: query,
|
||||
page: 0,
|
||||
};
|
||||
|
||||
setMaps(() => []);
|
||||
setSearchParams(() => searchParams);
|
||||
}
|
||||
|
||||
const handleLoadMore = () => {
|
||||
setSearchParams((prev) => {
|
||||
return {...prev, page: prev.page + 1}
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<form className="text-gray-800 dark:text-gray-200 flex flex-col">
|
||||
<div className="flex h-7 gap-2">
|
||||
<BsmDropdownButton className="shrink-0 h-full relative z-[1] flex justify-center" buttonClassName="flex items-center justify-center h-full rounded-full px-2 py-1 !bg-main-color-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={filter} onChange={setFilter}/>
|
||||
<form className="text-gray-800 dark:text-gray-200 flex flex-col max-w-[95vw] w-[970px] h-[85vh] gap-3" onSubmit={e => {e.preventDefault(); handleSearch()}}>
|
||||
<div className="flex h-8 gap-2 shrink-0">
|
||||
<BsmDropdownButton className="shrink-0 h-full relative z-[1] flex justify-start" buttonClassName="flex items-center justify-center h-full rounded-full px-2 py-1 !bg-main-color-1" icon="search" text="Filtres" withBar={false}>
|
||||
<FilterPanel className="absolute top-[calc(100%+3px)] bg-main-color-3 origin-top w-[450px] h-fit p-2 rounded-md shadow-md shadow-black" filter={filter} onChange={setFilter}/>
|
||||
</BsmDropdownButton>
|
||||
<input className="h-full bg-main-color-1 rounded-full px-2 grow pb-0.5" type="text" name="" id="" placeholder="Rechercher"/>
|
||||
<BsmButton className="shrink-0 aspect-square rounded-full p-1 !bg-main-color-1" icon="search" withBar={false}/>
|
||||
<input className="h-full bg-main-color-1 rounded-full px-2 grow pb-0.5" type="text" name="" id="" placeholder="Rechercher" value={query} onChange={e => setQuery(e.target.value.trim())}/>
|
||||
<BsmButton className="shrink-0 aspect-square rounded-full p-1 !bg-main-color-1" icon="search" withBar={false} onClick={e => {e.preventDefault(); handleSearch()}}/>
|
||||
<BsmSelect className="bg-main-color-1 rounded-full px-2 pb-0.5" options={sortOptions} onChange={setSortOrder}/>
|
||||
</div>
|
||||
<ul>
|
||||
|
||||
<ul className="w-full grow flex content-start flex-wrap gap-2 px-2 overflow-y-scroll overflow-x-hidden scrollbar-thin scrollbar-thumb-rounded-full scrollbar-thumb-neutral-900" >
|
||||
{maps.length === 0 ? (
|
||||
<div className="w-full h-full flex flex-col items-center justify-center">
|
||||
<img className="w-32 h-32 spin-loading" src={BeatWaitingImg}></img>
|
||||
<span className="text-lg">Chargement des maps...</span> {/* TODO TRANSLATE */}
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{maps.map(renderMap)}
|
||||
<motion.span onViewportEnter={handleLoadMore} ref={loaderRef} className="block w-full h-8"/>
|
||||
</>
|
||||
)}
|
||||
</ul>
|
||||
</form>
|
||||
)
|
||||
|
||||
@@ -19,28 +19,28 @@ export class BeatSaverApiService {
|
||||
|
||||
if(!filter){ return new URLSearchParams(); }
|
||||
|
||||
const enbledTagsString = Array.from(filter.enabledTags);
|
||||
const excludedTagsString = Array.from(filter.excludedTags).map(tag => `!${tag}`);
|
||||
const enbledTagsString = filter.enabledTags ? Array.from(filter.enabledTags) : null;
|
||||
const excludedTagsString = filter.excludedTags ? Array.from(filter.excludedTags).map(tag => `!${tag}`) : null;
|
||||
|
||||
const tags = [...enbledTagsString, excludedTagsString].join("|");
|
||||
const tags = (enbledTagsString || excludedTagsString) ? [...enbledTagsString, excludedTagsString].join("|") : null;
|
||||
|
||||
const params = {
|
||||
automapper: String(filter.automapper ?? ""),
|
||||
chroma: String(filter.chroma ?? ""),
|
||||
cinema: String(filter.cinema ?? ""),
|
||||
me: String(filter.me ?? ""),
|
||||
noodle: String(filter.noodle ?? ""),
|
||||
ranked: String(filter.ranked ?? ""),
|
||||
verified: String(filter.ranked ?? ""),
|
||||
curated: String(filter.curated ?? ""),
|
||||
fullSpread : String(filter.fullSpread ?? ""),
|
||||
from: String(filter.from ?? ""),
|
||||
to: String(filter.to ?? ""),
|
||||
tags: tags,
|
||||
minDuration: String(filter.minDuration ?? ""),
|
||||
maxDuration: String(filter.maxDuration ?? ""),
|
||||
minNps: String(filter.minNps ?? ""),
|
||||
maxNps: String(filter.maxNps ?? ""),
|
||||
...(filter.automapper && {automapper: String(filter.automapper)}),
|
||||
...(filter.chroma && {chroma: String(filter.chroma)}),
|
||||
...(filter.cinema && {cinema: String(filter.cinema)}),
|
||||
...(filter.me && {me: String(filter.me)}),
|
||||
...(filter.noodle && {noodle: String(filter.noodle)}),
|
||||
...(filter.ranked && {ranked: String(filter.ranked)}),
|
||||
...(filter.verified && {verified: String(filter.verified)}),
|
||||
...(filter.curated && {curated: String(filter.curated)}),
|
||||
...(filter.fullSpread && {fullSpread: String(filter.fullSpread)}),
|
||||
...(filter.from && {from: String(filter.from)}),
|
||||
...(filter.to && {to: String(filter.to)}),
|
||||
...(tags && {tags: String(tags)}),
|
||||
...(filter.minDuration && {minDuration: String(filter.minDuration)}),
|
||||
...(filter.maxDuration && {maxDuration: String(filter.maxDuration)}),
|
||||
...(filter.minNps && {minNps: String(filter.minNps)}),
|
||||
...(filter.maxNps && {maxNps: String(filter.maxNps)})
|
||||
};
|
||||
|
||||
return new URLSearchParams(params);
|
||||
@@ -52,9 +52,9 @@ export class BeatSaverApiService {
|
||||
if(!search){ return new URLSearchParams(); }
|
||||
|
||||
const searchParams = {
|
||||
includeEmpty: String(search.includeEmpty ?? ""),
|
||||
sortOrder: String(search.sortOrder ?? ""),
|
||||
q: String(search.q ?? "")
|
||||
...(search.includeEmpty && {includeEmpty: String(search.includeEmpty)}),
|
||||
...(search.sortOrder && {sortOrder: search.sortOrder}),
|
||||
...(search.q && {q: search.q})
|
||||
};
|
||||
|
||||
const filterUrlParms = this.mapFilterToUrlParams(search.filter);
|
||||
|
||||
Reference in New Issue
Block a user