import { useCallback, useEffect, useRef, useState } from "react"; import { FilterPanel } from "renderer/components/maps-playlists-panel/maps/filter-panel.component"; import { MapItem } from "renderer/components/maps-playlists-panel/maps/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"; import { useObservable } from "renderer/hooks/use-observable.hook"; import { BeatSaverService } from "renderer/services/thrird-partys/beat-saver.service"; import { MapsDownloaderService } from "renderer/services/maps-downloader.service"; import { ModalComponent } from "renderer/services/modale.service"; import { BSVersion } from "shared/bs-version.interface"; import { BsvMapDetail, MapFilter, BsvSearchOrder, SearchParams } from "shared/models/maps/beat-saver.model"; import BeatWaitingImg from "../../../../../assets/images/apngs/beat-waiting.png"; import BeatConflictImg from "../../../../../assets/images/apngs/beat-conflict.png"; import equal from "fast-deep-equal/es6"; import { ProgressBarService } from "renderer/services/progress-bar.service"; import { useTranslation } from "renderer/hooks/use-translation.hook"; import { OsDiagnosticService } from "renderer/services/os-diagnostic.service"; import { BsmLocalMap } from "shared/models/maps/bsm-local-map.interface"; import { useService } from "renderer/hooks/use-service.hook"; import { useConstant } from "renderer/hooks/use-constant.hook"; import { getLocalTimeZone, parseAbsolute, toCalendarDateTime } from "@internationalized/date"; import { VirtualScroll } from "renderer/components/shared/virtual-scroll/virtual-scroll.component"; import { MapItemComponentPropsMapper } from "shared/mappers/map/map-item-component-props.mapper"; export const DownloadMapsModal: ModalComponent = ({ options: {data : { ownedMaps, version }} }) => { const beatSaver = useService(BeatSaverService); const mapsDownloader = useService(MapsDownloaderService); const progressBar = useService(ProgressBarService); const os = useService(OsDiagnosticService); const currentDownload = useObservable(() => mapsDownloader.currentMapDownload$); const mapsInQueue = useObservable(() => mapsDownloader.mapsInQueue$); const t = useTranslation(); const filterContainerRef = useRef(null); const [filter, setFilter] = useState({}); const [query, setQuery] = useState(""); const [maps, setMaps] = useState([]); const [downloadbleMaps, setDownloadbleMaps] = useState([]); const [sortOrder, setSortOrder] = useState(BsvSearchOrder.Latest); const [ownedMapHashs, setOwnedMapHashs] = useState(ownedMaps?.map(map => map.hash) ?? []); const [loading, setLoading] = useState(false); const isOnline = useObservable(() => os.isOnline$); const [searchParams, setSearchParams] = useState({ sortOrder, filter, page: 0, q: query, }); const sortOptions: BsmSelectOption[] = useConstant(() => { return Object.values(BsvSearchOrder).map(sort => ({ text: `beat-saver.maps-sorts.${sort}`, value: sort })); }); useEffect(() => { setDownloadbleMaps(() => maps.map(map => { const isMapOwned = map.versions.some(version => ownedMapHashs.includes(version.hash)); const isDownloading = map.id === currentDownload?.map?.id; const inQueue = mapsInQueue.some(toDownload => equal(toDownload.version, version) && toDownload.map.id === map.id); return { map, isOwned: isMapOwned, idDownloading: isDownloading, isInQueue: inQueue }; })); }, [maps, currentDownload, mapsInQueue, ownedMapHashs]) useEffect(() => { loadMaps(searchParams); }, [searchParams]); useEffect(() => { const sub = mapsDownloader.lastDownloadedMap$.subscribe({ next: ({ map, version: targetVersion }) => { if (!equal(targetVersion, version)) { return; } const downloadedHash = map.hash; setOwnedMapHashs(prev => [...prev, downloadedHash]); }}); if (mapsDownloader.isDownloading) { progressBar.setStyle(mapsDownloader.progressBarStyle); } return () => { sub.unsubscribe(); progressBar.setStyle(null); }; }, []); const applyInstalledFilter = (maps: BsvMapDetail[]): BsvMapDetail[] => { return maps.filter(map => { if (!filter.installed) { return true; } return !map.versions.some(version => ownedMapHashs.includes(version.hash)); }); } const loadMaps = (params: SearchParams, tryToLoad = 5) => { setLoading(() => true); const searchResult = beatSaver.searchMaps(params); if (!filter.installed) { searchResult .then(maps => setMaps(prev => [...prev, ...maps])) .finally(() => setLoading(() => false)); return; } searchResult .then(maps => { if (!maps.length) { setLoading(() => false); return; } maps = applyInstalledFilter(maps); setMaps(prev => [...prev, ...maps]) if (maps.length < tryToLoad) { handleLoadMore(); return; } setLoading(() => false); }) }; const renderMap = useCallback((downloadableMap: DownloadableMap) => { const { map } = downloadableMap; const downloadable = !downloadableMap.isOwned && !downloadableMap.isInQueue; const cancelable = downloadableMap.isInQueue && !downloadableMap.idDownloading; return ; }, [version]); const handleDownloadMap = useCallback((map: BsvMapDetail) => { mapsDownloader.addMapToDownload({ map, version }); }, []); const handleCancelDownload = useCallback((map: BsvMapDetail) => { mapsDownloader.removeMapToDownload({ map, version }); }, []); const handleSortChange = (newSort: BsvSearchOrder) => { setSortOrder(() => newSort); setMaps(() => []); setSearchParams(() => ({ ...searchParams, sortOrder: newSort })); }; const handleSearch = () => { const searchParams: SearchParams = { sortOrder, filter, q: query.trim(), page: 0, }; setMaps(() => []); setSearchParams(() => searchParams); }; const handleLoadMore = () => { if(loading){ return; } setSearchParams(prev => { return { ...prev, page: prev.page + 1 }; }); }; return (
{ e.preventDefault(); handleSearch(); }} >
filterContainerRef.current.close()} /> setQuery(e.target.value)} /> { e.preventDefault(); handleSearch(); }} /> handleSortChange(sort)} />
{(() => { if(downloadbleMaps?.length) { return ( mapsInRow.map(map => map.map.id).join("-")} renderItem={renderMap} scrollEnd={{ onScrollEnd: handleLoadMore, margin: 100 }} /> ) } return (
 {(() => { if (loading) { return t("modals.download-maps.loading-maps"); } if (isOnline) { return t("modals.download-maps.no-maps-found"); } return t("modals.download-maps.no-internet"); })()}
) })()} ); }; type DownloadableMap = { map: BsvMapDetail; isOwned: boolean; idDownloading: boolean; isInQueue: boolean; };