From ab3236230be235ed106865294ed81668f4f5ebce Mon Sep 17 00:00:00 2001 From: jeffvli Date: Sun, 5 Feb 2023 18:59:39 -0800 Subject: [PATCH] Use virtualized list on sidebar playlists --- .../sidebar/components/sidebar-item.tsx | 62 +--- .../components/sidebar-playlist-list.tsx | 155 ++++++++ .../features/sidebar/components/sidebar.tsx | 335 ++++++++---------- 3 files changed, 300 insertions(+), 252 deletions(-) create mode 100644 src/renderer/features/sidebar/components/sidebar-playlist-list.tsx diff --git a/src/renderer/features/sidebar/components/sidebar-item.tsx b/src/renderer/features/sidebar/components/sidebar-item.tsx index 64dd238c..41816b23 100644 --- a/src/renderer/features/sidebar/components/sidebar-item.tsx +++ b/src/renderer/features/sidebar/components/sidebar-item.tsx @@ -1,9 +1,8 @@ import type { ReactNode } from 'react'; -import { createPolymorphicComponent, Flex, FlexProps, Group } from '@mantine/core'; +import { createPolymorphicComponent, Flex, FlexProps } from '@mantine/core'; import type { LinkProps } from 'react-router-dom'; import { Link } from 'react-router-dom'; import styled, { css } from 'styled-components'; -import { textEllipsis } from '/@/renderer/styles'; interface ListItemProps extends FlexProps { children: ReactNode; @@ -13,8 +12,8 @@ interface ListItemProps extends FlexProps { const StyledItem = styled(Flex)` width: 100%; + font-weight: 600; font-family: var(--content-font-family); - letter-spacing: 0.5px; &:focus-visible { border: 1px solid var(--primary-color); @@ -75,60 +74,3 @@ SidebarItem.defaultProps = { disabled: false, to: undefined, }; - -const _PlaylistItemLink = styled(StyledItem)` - display: block; - padding: 0.3rem 0; - overflow: hidden; - color: var(--sidebar-fg); - cursor: default; - opacity: ${(props) => (props.disabled ? 0.6 : 0.8)}; - transition: color 0.2s ease-in-out; - transition: opacity 0.2s ease-in-out; - pointer-events: ${(props) => props.disabled && 'none'}; - ${textEllipsis} - - &:hover { - color: var(--sidebar-fg-hover); - opacity: 1; - } - - &:focus-visible { - border: 1px solid var(--primary-color); - } -`; - -const PlaylistItemLink = createPolymorphicComponent<'a', ListItemProps>(_PlaylistItemLink); - -export const PlaylistSidebarItem = ({ - handlePlay, - to, - children, - ...props -}: ListItemProps & { handlePlay: () => void }) => { - if (to) { - return ( - - - {children} - - - ); - } - - return ( - - {children} - - ); -}; diff --git a/src/renderer/features/sidebar/components/sidebar-playlist-list.tsx b/src/renderer/features/sidebar/components/sidebar-playlist-list.tsx new file mode 100644 index 00000000..81301e96 --- /dev/null +++ b/src/renderer/features/sidebar/components/sidebar-playlist-list.tsx @@ -0,0 +1,155 @@ +import { Group } from '@mantine/core'; +import { useVirtualizer } from '@tanstack/react-virtual'; +import { useRef } from 'react'; +import { RiAddBoxFill, RiAddCircleFill, RiPlayFill } from 'react-icons/ri'; +import { generatePath } from 'react-router'; +import { Link } from 'react-router-dom'; +import { LibraryItem } from '/@/renderer/api/types'; +import { Button, NativeScrollArea, Text } from '/@/renderer/components'; +import { usePlayQueueAdd } from '/@/renderer/features/player'; +import { usePlaylistList } from '/@/renderer/features/playlists'; +import { AppRoute } from '/@/renderer/router/routes'; +import { Play } from '/@/renderer/types'; + +interface SidebarPlaylistListProps { + data: ReturnType['data']; +} + +export const SidebarPlaylistList = ({ data }: SidebarPlaylistListProps) => { + const scrollAreaRef = useRef(null); + + const handlePlayQueueAdd = usePlayQueueAdd(); + + const handlePlayPlaylist = (id: string, play: Play) => { + handlePlayQueueAdd?.({ + byItemType: { + id: [id], + type: LibraryItem.PLAYLIST, + }, + play, + }); + }; + + const rowVirtualizer = useVirtualizer({ + count: data?.items?.length || 0, + estimateSize: () => 25, + getScrollElement: () => { + return scrollAreaRef.current; + }, + overscan: 5, + }); + + return ( + +
+ {rowVirtualizer.getVirtualItems().map((virtualRow) => ( +
+ + + {data?.items?.[virtualRow.index].name} + + + + + + + +
+ ))} +
+
+ ); +}; diff --git a/src/renderer/features/sidebar/components/sidebar.tsx b/src/renderer/features/sidebar/components/sidebar.tsx index 57d606d7..93c61198 100644 --- a/src/renderer/features/sidebar/components/sidebar.tsx +++ b/src/renderer/features/sidebar/components/sidebar.tsx @@ -1,10 +1,8 @@ import { MouseEvent } from 'react'; -import { Stack, Grid, Accordion, Center, Group } from '@mantine/core'; +import { Stack, Grid, Accordion, Center, Group, Divider, Box } from '@mantine/core'; import { closeAllModals, openModal } from '@mantine/modals'; import { AnimatePresence, motion } from 'framer-motion'; -import { BsCollection } from 'react-icons/bs'; -import { Button, MotionStack, ScrollArea, TextInput } from '/@/renderer/components'; -import { MdOutlineFeaturedPlayList, MdFeaturedPlayList } from 'react-icons/md'; +import { Button, MotionStack, Spinner, TextInput } from '/@/renderer/components'; import { RiAddFill, RiAlbumFill, @@ -17,8 +15,8 @@ import { RiDiscLine, RiFlag2Line, RiFolder3Line, - RiHome4Fill, - RiHome4Line, + RiHome5Fill, + RiHome5Line, RiListUnordered, RiMusic2Fill, RiMusic2Line, @@ -26,12 +24,9 @@ import { RiUserVoiceFill, RiUserVoiceLine, } from 'react-icons/ri'; -import { useNavigate, Link, useLocation, generatePath } from 'react-router-dom'; +import { useNavigate, Link, useLocation } from 'react-router-dom'; import styled from 'styled-components'; -import { - PlaylistSidebarItem, - SidebarItem, -} from '/@/renderer/features/sidebar/components/sidebar-item'; +import { SidebarItem } from '/@/renderer/features/sidebar/components/sidebar-item'; import { AppRoute } from '/@/renderer/router/routes'; import { useSidebarStore, @@ -41,9 +36,8 @@ import { } from '/@/renderer/store'; import { fadeIn } from '/@/renderer/styles'; import { CreatePlaylistForm, usePlaylistList } from '/@/renderer/features/playlists'; -import { LibraryItem, PlaylistListSort, ServerType, SortOrder } from '/@/renderer/api/types'; -import { usePlayQueueAdd } from '/@/renderer/features/player'; -import { usePlayButtonBehavior } from '/@/renderer/store/settings.store'; +import { PlaylistListSort, ServerType, SortOrder } from '/@/renderer/api/types'; +import { SidebarPlaylistList } from '/@/renderer/features/sidebar/components/sidebar-playlist-list'; const SidebarContainer = styled.div` height: 100%; @@ -92,25 +86,6 @@ export const Sidebar = () => { .replace(/height=\d+/, 'height=300'); const showImage = sidebar.image; - const handlePlayQueueAdd = usePlayQueueAdd(); - const playButtonBehavior = usePlayButtonBehavior(); - - const handlePlayPlaylist = (id: string) => { - handlePlayQueueAdd?.({ - byItemType: { - id: [id], - type: LibraryItem.PLAYLIST, - }, - play: playButtonBehavior, - }); - }; - - const playlistsQuery = usePlaylistList({ - limit: 100, - sortBy: PlaylistListSort.NAME, - sortOrder: SortOrder.ASC, - startIndex: 0, - }); const handleCreatePlaylistModal = (e: MouseEvent) => { e.stopPropagation(); @@ -122,6 +97,12 @@ export const Sidebar = () => { }); }; + const playlistsQuery = usePlaylistList({ + sortBy: PlaylistListSort.NAME, + sortOrder: SortOrder.ASC, + startIndex: 0, + }); + return ( { - - - - - {location.pathname === AppRoute.HOME ? ( - - ) : ( - - )} - Home - - - setSidebar({ expanded: e })} - > - - + + + + {location.pathname === AppRoute.HOME ? ( + + ) : ( + + )} + Home + + + setSidebar({ expanded: e })} + > + + + + {location.pathname.includes('/library/') ? ( + + ) : ( + + )} + Library + + + + - {location.pathname.includes('/library/') ? ( - + {location.pathname === AppRoute.LIBRARY_ALBUMS ? ( + ) : ( - + )} - Library + Albums - - - - - {location.pathname === AppRoute.LIBRARY_ALBUMS ? ( - - ) : ( - - )} - Albums - - - - - {location.pathname === AppRoute.LIBRARY_SONGS ? ( - - ) : ( - - )} - Tracks - - - - - {location.pathname === AppRoute.LIBRARY_ALBUM_ARTISTS ? ( - - ) : ( - - )} - Album Artists - - - - - - Genres - - - - - - Folders - - - - - - + + - - Collections + {location.pathname === AppRoute.LIBRARY_SONGS ? ( + + ) : ( + + )} + Tracks - - - - - - - - {location.pathname.includes('/playlists/') ? ( - - ) : ( - - )} - Playlists - - - - - + + + + {location.pathname === AppRoute.LIBRARY_ALBUM_ARTISTS ? ( + + ) : ( + + )} + Album Artists - - - {playlistsQuery?.data?.items?.map((playlist) => ( - handlePlayPlaylist(playlist.id)} - to={generatePath(AppRoute.PLAYLISTS_DETAIL, { playlistId: playlist.id })} - > - {playlist.name} - - ))} - - - - - + + + + + Genres + + + + + + Folders + + + + + + + + + + + Playlists + + {playlistsQuery.isLoading && } + + + + + + +