diff --git a/src/renderer/components/modal/modal-types/playlist/download-playlist-modal/download-playlist-modal-header.component.tsx b/src/renderer/components/modal/modal-types/playlist/download-playlist-modal/download-playlist-modal-header.component.tsx index c15027c4..6b0bbc4f 100644 --- a/src/renderer/components/modal/modal-types/playlist/download-playlist-modal/download-playlist-modal-header.component.tsx +++ b/src/renderer/components/modal/modal-types/playlist/download-playlist-modal/download-playlist-modal-header.component.tsx @@ -55,7 +55,7 @@ export function DownloadPlaylistModalHeader({ className, value, onSubmit }: Prop setQuery(e.target.value)} /> - + submit(searchParams)} /> ) diff --git a/src/renderer/components/modal/modal-types/playlist/download-playlist-modal/download-playlist-modal.component.tsx b/src/renderer/components/modal/modal-types/playlist/download-playlist-modal/download-playlist-modal.component.tsx index dc060352..1218c554 100644 --- a/src/renderer/components/modal/modal-types/playlist/download-playlist-modal/download-playlist-modal.component.tsx +++ b/src/renderer/components/modal/modal-types/playlist/download-playlist-modal/download-playlist-modal.component.tsx @@ -1,24 +1,24 @@ import { useObservable } from "renderer/hooks/use-observable.hook" import { ModalComponent, ModalService } from "renderer/services/modale.service" -import { Observable, lastValueFrom } from "rxjs" +import { Observable, lastValueFrom, take } from "rxjs" import { BSVersion } from "shared/bs-version.interface" import { BsmLocalMap } from "shared/models/maps/bsm-local-map.interface" import { LocalBPListsDetails } from "shared/models/playlists/local-playlist.models" import { DownloadPlaylistModalHeader } from "./download-playlist-modal-header.component" import { BsvPlaylist, BsvSearchOrder, PlaylistSearchParams } from "shared/models/maps/beat-saver.model" -import { useState } from "react" +import { useCallback, useState } from "react" import { useOnUpdate } from "renderer/hooks/use-on-update.hook" import { useService } from "renderer/hooks/use-service.hook" import { BeatSaverService } from "renderer/services/thrird-partys/beat-saver.service" import { PlaylistItem } from "renderer/components/maps-playlists-panel/playlists/playlist-item.component" import { PlaylistItemComponentPropsMapper } from "shared/mappers/playlist/playlist-item-component-props.mapper" -import { motion } from "framer-motion" import { PlaylistDownloaderService } from "renderer/services/playlist-downloader.service" import { BsvPlaylistDetailsModal } from "../playlist-details-modal/bsv-playlist-details-modal.component" import { BsmImage } from "renderer/components/shared/bsm-image.component" import BeatWaiting from "../../../../../../../assets/images/apngs/beat-waiting.png" import BeatConflict from "../../../../../../../assets/images/apngs/beat-conflict.png" import { cn } from "renderer/helpers/css-class.helpers" +import { VirtualScroll } from "renderer/components/shared/virtual-scroll/virtual-scroll.component" // TODO : Translate @@ -32,8 +32,8 @@ export const DownloadPlaylistModal: ModalComponent(null); const ownedPlaylists = useObservable(() => ownedPlaylists$, []); - const ownedMaps = useObservable(() => ownedMaps$, []); + const [loading, setLoading] = useState(false); const [error, setError] = useState(false); const [searchParams, setSearchParams] = useState({ q: "", @@ -42,24 +42,44 @@ export const DownloadPlaylistModal: ModalComponent { + setLoading(() => true); beatSaver.searchPlaylists(searchParams) .then(playlists => setPlaylists(prev => [...(prev ?? []), ...(playlists ?? [])] )) - .catch(() => setError(() => true)); - }, [searchParams]) + .catch(() => setError(() => true)) + .finally(() => setLoading(() => false)); + }, [searchParams]); const handleNewSearch = (value: Omit) => { setPlaylists(() => []); setSearchParams(() => ({ ...value, page: 0})); }; - const loadMorePlaylists = () => { - setSearchParams(prev => ({ ...prev, page: prev.page + 1 })); - }; - const openPlaylist = (playlist: BsvPlaylist) => { modal.openModal(BsvPlaylistDetailsModal, { data: { playlist, version, installedMaps$: ownedMaps$ }, noStyle: true }) }; + const loadMorePlaylists = () => { + if(loading){ return; } + setSearchParams(prev => ({ ...prev, page: prev.page + 1 })); + }; + + const renderPlaylist = useCallback((playlist: BsvPlaylist) => { + + const onClickDownload = async () => { + const ownedMaps = await lastValueFrom(ownedMaps$.pipe(take(1))); + await lastValueFrom(playlistDownloader.downloadPlaylist({ downloadSource: playlist.downloadURL, ignoreSongsHashs: ownedMaps.map(map => map.hash), version })); + } + + return ( + openPlaylist(playlist)} + onClickDownload={onClickDownload} + /> + ); + }, [version]); + return (
@@ -85,17 +105,22 @@ export const DownloadPlaylistModal: ModalComponent - {playlists.map(playlist => ( - openPlaylist(playlist)} - onClickDownload={() => lastValueFrom(playlistDownloader.downloadPlaylist({ downloadSource: playlist.downloadURL, ignoreSongsHashs: ownedMaps.map(map => map.hash), version }))} - /> - ))} - - + items.map(item => item.playlistId).join("-")} + /> ) })()}
diff --git a/src/renderer/components/shared/virtual-scroll/virtual-row.component.tsx b/src/renderer/components/shared/virtual-scroll/virtual-row.component.tsx index a54f9286..de473f73 100644 --- a/src/renderer/components/shared/virtual-scroll/virtual-row.component.tsx +++ b/src/renderer/components/shared/virtual-scroll/virtual-row.component.tsx @@ -1,4 +1,3 @@ -import equal from "fast-deep-equal"; import { CSSProperties, memo } from "react"; import { cn } from "renderer/helpers/css-class.helpers"; @@ -10,9 +9,10 @@ type Props = { } function VirtualRowComponent({ className, style, items, renderItem }: Props) { + return (
    - {items.map((item) => ( + {items?.map((item) => ( renderItem(item) ))}
@@ -21,4 +21,4 @@ function VirtualRowComponent({ className, style, items, renderItem }: Props(c: T, propsAreEqual?: (prevProps: Readonly

, nextProps: Readonly

) => boolean) => T = memo; -export const VirtualRow = typedMemo(VirtualRowComponent, equal); +export const VirtualRow = typedMemo(VirtualRowComponent); diff --git a/src/renderer/components/shared/virtual-scroll/virtual-scroll.component.tsx b/src/renderer/components/shared/virtual-scroll/virtual-scroll.component.tsx index d68fc126..1bc74f34 100644 --- a/src/renderer/components/shared/virtual-scroll/virtual-scroll.component.tsx +++ b/src/renderer/components/shared/virtual-scroll/virtual-scroll.component.tsx @@ -1,5 +1,5 @@ -import { useLayoutEffect, useRef, useState } from "react"; -import { VariableSizeList } from "react-window"; +import { useCallback, useLayoutEffect, useRef, useState } from "react"; +import { ListChildComponentProps, ListOnScrollProps, VariableSizeList } from "react-window"; import { cn } from "renderer/helpers/css-class.helpers"; import { useOnUpdate } from "renderer/hooks/use-on-update.hook"; import { VirtualRow } from "./virtual-row.component"; @@ -14,6 +14,11 @@ type ClassNames = { rows?: string; } +type ScrollEndHandler = { + onScrollEnd: () => void; + margin?: number; +} + type Props = { className?: string; classNames?: ClassNames; @@ -23,13 +28,14 @@ type Props = { itemHeight: number; items: T[]; renderItem: (item: T) => JSX.Element; + itemKey: (item: T[]) => string; + scrollEnd?: ScrollEndHandler; } -export function VirtualScroll({ className, classNames, minItemWidth, maxColumns, minColumns, itemHeight, items, renderItem}: Props) { - - console.log("omg les classes", className); +export function VirtualScroll({ className, classNames, minItemWidth, maxColumns, minColumns, itemHeight, items, scrollEnd, renderItem, itemKey}: Props) { const ref = useRef(null); + const listRef = useRef(null); const [itemPerRow, setItemPerRow] = useState(1); const [itemsToRender, setItemsToRender] = useState([]); @@ -48,7 +54,6 @@ export function VirtualScroll({ className, classNames, minItemWidth const observer = new ResizeObserver(() => { updateItemPerRow(ref.current?.clientWidth || 0); listHeight$.next(ref.current?.clientHeight || 0); - console.log("list height", ref.current?.clientHeight); }); observer.observe(ref.current); @@ -61,10 +66,41 @@ export function VirtualScroll({ className, classNames, minItemWidth setItemsToRender(() => splitedItems); }, [itemPerRow, items]) + const handleScroll = (e: ListOnScrollProps) => { + if(!scrollEnd?.onScrollEnd || !listRef.current){ return; } + + const { scrollDirection, scrollOffset, scrollUpdateWasRequested } = e; + const { scrollHeight } = listRef.current; + + if(!scrollHeight || !listHeight){ return; } + + const margin = scrollEnd.margin ?? 0; + + if (scrollDirection === "forward" && !scrollUpdateWasRequested && scrollOffset + listHeight + margin >= scrollHeight) { + scrollEnd.onScrollEnd(); + } + }; + + const renderRow = useCallback((props: ListChildComponentProps) => { + return ; + }, [renderItem, classNames?.rows]); + return (

- i} itemSize={() => itemHeight} itemData={itemsToRender} style={{ scrollbarGutter: "stable both-edges" }}> - {props => } + itemKey?.(itemsToRender[i]) ?? i} + itemSize={() => itemHeight} + itemData={itemsToRender} + style={{ scrollbarGutter: "stable both-edges" }} + onScroll={handleScroll} + + > + {renderRow}
)