From 4fb963d68941c944dc5849ae8ee59a902e157494 Mon Sep 17 00:00:00 2001 From: jeffvli Date: Fri, 28 Oct 2022 13:11:29 -0700 Subject: [PATCH] Add initial queue/album list routes --- src/renderer/features/albums/index.ts | 3 + .../albums/queries/use-album-detail.ts | 21 ++ .../features/albums/queries/use-album-list.ts | 33 +++ .../albums/routes/album-list-route.tsx | 213 ++++++++++++++++++ src/renderer/features/now-playing/index.ts | 1 + .../now-playing/routes/now-playing-route.tsx | 123 ++++++++++ src/renderer/router/app-router.tsx | 10 +- src/renderer/router/routes.ts | 2 + 8 files changed, 402 insertions(+), 4 deletions(-) create mode 100644 src/renderer/features/albums/index.ts create mode 100644 src/renderer/features/albums/queries/use-album-detail.ts create mode 100644 src/renderer/features/albums/queries/use-album-list.ts create mode 100644 src/renderer/features/albums/routes/album-list-route.tsx create mode 100644 src/renderer/features/now-playing/index.ts create mode 100644 src/renderer/features/now-playing/routes/now-playing-route.tsx diff --git a/src/renderer/features/albums/index.ts b/src/renderer/features/albums/index.ts new file mode 100644 index 000000000..fdab96433 --- /dev/null +++ b/src/renderer/features/albums/index.ts @@ -0,0 +1,3 @@ +export * from './queries/use-album-detail'; +export * from './queries/use-album-list'; +export * from './routes/album-list-route'; diff --git a/src/renderer/features/albums/queries/use-album-detail.ts b/src/renderer/features/albums/queries/use-album-detail.ts new file mode 100644 index 000000000..f01aeb96b --- /dev/null +++ b/src/renderer/features/albums/queries/use-album-detail.ts @@ -0,0 +1,21 @@ +import { useQuery } from '@tanstack/react-query'; +import { api } from '@/renderer/api'; +import { queryKeys } from '@/renderer/api/query-keys'; +import { QueryOptions } from '@/renderer/lib/react-query'; +import { useAuthStore } from '@/renderer/store'; +import { AlbumDetailResponse } from 'renderer/api/types'; + +export const useAlbumDetail = ( + query: { albumId: string }, + options: QueryOptions +) => { + const serverId = useAuthStore((state) => state.currentServer?.id) || ''; + + return useQuery({ + enabled: !!serverId, + queryFn: ({ signal }) => + api.albums.getAlbumDetail({ albumId: query.albumId, serverId }, signal), + queryKey: queryKeys.albums.detail(query.albumId), + ...options, + }); +}; diff --git a/src/renderer/features/albums/queries/use-album-list.ts b/src/renderer/features/albums/queries/use-album-list.ts new file mode 100644 index 000000000..fa5b5c659 --- /dev/null +++ b/src/renderer/features/albums/queries/use-album-list.ts @@ -0,0 +1,33 @@ +import { useInfiniteQuery, useQuery } from '@tanstack/react-query'; +import { api } from '@/renderer/api'; +import { AlbumListParams } from '@/renderer/api/albums.api'; +import { queryKeys } from '@/renderer/api/query-keys'; +import { useAuthStore } from '@/renderer/store'; +import { AlbumListResponse } from 'renderer/api/types'; + +export const useAlbumList = (params: AlbumListParams) => { + const serverId = useAuthStore((state) => state.currentServer?.id) || ''; + + return useQuery({ + enabled: !!serverId, + queryFn: () => api.albums.getAlbumList({ serverId }, params), + queryKey: queryKeys.albums.list(serverId, params), + }); +}; + +export const useAlbumListInfinite = (params: AlbumListParams) => { + const serverId = useAuthStore((state) => state.currentServer?.id) || ''; + + return useInfiniteQuery({ + enabled: !!serverId, + getNextPageParam: (lastPage: AlbumListResponse) => { + return !!lastPage.pagination.nextPage; + }, + getPreviousPageParam: (firstPage: AlbumListResponse) => { + return !!firstPage.pagination.prevPage; + }, + queryFn: ({ pageParam }) => + api.albums.getAlbumList({ serverId }, { ...(pageParam || params) }), + queryKey: queryKeys.albums.list(serverId, params), + }); +}; diff --git a/src/renderer/features/albums/routes/album-list-route.tsx b/src/renderer/features/albums/routes/album-list-route.tsx new file mode 100644 index 000000000..61717a7eb --- /dev/null +++ b/src/renderer/features/albums/routes/album-list-route.tsx @@ -0,0 +1,213 @@ +/* eslint-disable no-plusplus */ +import { useState, useCallback, useMemo } from 'react'; +import { Group, Checkbox } from '@mantine/core'; +import { useSetState } from '@mantine/hooks'; +import { useQueryClient } from '@tanstack/react-query'; +import { RiArrowDownSLine } from 'react-icons/ri'; +import AutoSizer from 'react-virtualized-auto-sizer'; +import { api } from '@/renderer/api'; +import { AlbumSort } from '@/renderer/api/albums.api'; +import { queryKeys } from '@/renderer/api/query-keys'; +import { SortOrder } from '@/renderer/api/types'; +import { + Button, + DropdownMenu, + Text, + VirtualGridAutoSizerContainer, + VirtualGridContainer, + VirtualInfiniteGrid, +} from '@/renderer/components'; +import { useAlbumList } from '@/renderer/features/albums/queries/use-album-list'; +import { useServerList } from '@/renderer/features/servers'; +import { AnimatedPage, useServerCredential } from '@/renderer/features/shared'; +import { AppRoute } from '@/renderer/router/routes'; +import { useAuthStore } from '@/renderer/store'; +import { Font } from '@/renderer/styles'; +import { LibraryItem } from '@/renderer/types'; +import { + ViewType, + ViewTypeButton, +} from '../../library/components/ViewTypeButton'; + +const FILTERS = [ + { name: 'Date added', value: AlbumSort.DATE_ADDED }, + { + name: 'Date added (remote)', + value: AlbumSort.DATE_ADDED_REMOTE, + }, + { name: 'Date released', value: AlbumSort.DATE_RELEASED }, + { name: 'Favorites', value: AlbumSort.FAVORITE }, + { name: 'Random', value: AlbumSort.RANDOM }, + { name: 'Rating', value: AlbumSort.RATING }, + { name: 'Title', value: AlbumSort.NAME }, + { name: 'Year', value: AlbumSort.DATE_RELEASED_YEAR }, +]; + +const SORT = [ + { name: 'Ascending', value: SortOrder.ASC }, + { name: 'Descending', value: SortOrder.DESC }, +]; + +export const AlbumListRoute = () => { + const queryClient = useQueryClient(); + const { serverToken, isImageTokenRequired } = useServerCredential(); + const serverId = useAuthStore((state) => state.currentServer?.id) || ''; + const { data: servers } = useServerList({ enabled: true }); + const [viewType, setViewType] = useState(ViewType.Grid); + const [filters, setFilters] = useSetState({ + orderBy: SortOrder.ASC, + serverFolderId: [] as string[], + sortBy: AlbumSort.NAME, + }); + + const serverFolders = useMemo(() => { + const server = servers?.data.find((server) => server.id === serverId); + return server?.serverFolders; + }, [serverId, servers]); + + const { data: albums } = useAlbumList({ + orderBy: filters.orderBy, + serverFolderId: filters.serverFolderId, + skip: 0, + sortBy: filters.sortBy, + take: 0, + }); + + const fetch = useCallback( + async ({ skip, take }) => { + const albums = await queryClient.fetchQuery( + queryKeys.albums.list(serverId, { skip, take, ...filters }), + async () => + api.albums.getAlbumList({ serverId }, { skip, take, ...filters }) + ); + + // * Adds server token + if (isImageTokenRequired) { + const t = albums.data.map((album) => { + return { + ...album, + imageUrl: album?.imageUrl + serverToken!, + }; + }); + + return { ...albums, data: t }; + } + + return albums; + }, + [filters, isImageTokenRequired, queryClient, serverId, serverToken] + ); + + return ( + + + + + + Albums + + + + + + + {FILTERS.map((filter) => ( + setFilters({ sortBy: filter.value })} + > + {filter.name} + + ))} + + + + + + + + {SORT.map((sort) => ( + setFilters({ orderBy: sort.value })} + > + {sort.name} + + ))} + + + + + + + + setFilters({ serverFolderId: e })} + > + {serverFolders?.map((folder) => ( + + ))} + + + + + + + + + {({ height, width }) => ( + + )} + + + + + ); +}; diff --git a/src/renderer/features/now-playing/index.ts b/src/renderer/features/now-playing/index.ts new file mode 100644 index 000000000..3e3dd13de --- /dev/null +++ b/src/renderer/features/now-playing/index.ts @@ -0,0 +1 @@ +export * from './routes/now-playing-route'; diff --git a/src/renderer/features/now-playing/routes/now-playing-route.tsx b/src/renderer/features/now-playing/routes/now-playing-route.tsx new file mode 100644 index 000000000..c64a52446 --- /dev/null +++ b/src/renderer/features/now-playing/routes/now-playing-route.tsx @@ -0,0 +1,123 @@ +import { useEffect, useMemo, useRef, useState } from 'react'; +import { ColDef, RowClassRules } from 'ag-grid-community'; +import { + VirtualGridAutoSizerContainer, + VirtualGridContainer, +} from '@/renderer/components'; +import { VirtualTable } from '@/renderer/components/virtual-table'; +import { mpvPlayer } from '@/renderer/features/player/utils/mpvPlayer'; +import { AnimatedPage } from '@/renderer/features/shared'; +import { usePlayerStore } from '@/renderer/store'; + +const selector = (state: any) => state.queue.default; + +export const NowPlayingRoute = () => { + const gridRef = useRef(null); + const queue = usePlayerStore(selector); + const currentPlayerIndex = usePlayerStore((state) => state.current.index); + const current = usePlayerStore((state) => state.getQueueData().current); + const previous = usePlayerStore((state) => state.queue.previousNode); + const setCurrentIndex = usePlayerStore((state) => state.setCurrentIndex); + + const [columnDefs] = useState([ + { + field: 'index', + headerName: '-', + initialWidth: 50, + rowDrag: true, + suppressSizeToFit: true, + }, + { + headerName: '#', + initialWidth: 50, + suppressSizeToFit: true, + valueGetter: 'node.rowIndex + 1', + }, + { field: 'name' }, + { + field: 'duration', + initialWidth: 100, + suppressSizeToFit: true, + }, + { field: 'album.id', initialWidth: 100 }, + ]); + + const defaultColumnDefs: ColDef = useMemo(() => { + return { + lockPinned: true, + lockVisible: true, + resizable: true, + }; + }, []); + + const rowClassRules = useMemo(() => { + return { + 'current-song': (params) => { + return params.rowIndex === currentPlayerIndex; + }, + }; + }, [currentPlayerIndex]); + + useEffect(() => { + const { api, columnApi } = gridRef.current; + if (api == null || columnApi == null) { + return; + } + + const currentNode = api.getRowNode(current?.uniqueId); + const previousNode = api.getRowNode(previous?.uniqueId); + + const rowNodes = [currentNode, previousNode]; + + if (rowNodes) { + api.redrawRows({ rowNodes }); + api.ensureNodeVisible(currentNode, 'middle'); + } + }, [current, previous]); + + const handlePlayByRowClick = (e: any) => { + const playerData = setCurrentIndex(e.rowIndex); + mpvPlayer.setQueue(playerData); + }; + + return ( + + + + { + return data.data.uniqueId; + }} + rowBuffer={30} + rowClassRules={rowClassRules} + rowData={queue} + rowSelection="multiple" + onCellClicked={(e) => console.log('clicked', e)} + onCellContextMenu={(e) => console.log(e)} + onCellDoubleClicked={handlePlayByRowClick} + onDragStarted={(e) => { + console.log('ddrag move', e); + }} + onGridSizeChanged={() => { + console.log('size'); + gridRef.current.api.sizeColumnsToFit(); + }} + onRowDragEnd={(e) => { + console.log('dragend', e); + }} + /> + + + + ); +}; diff --git a/src/renderer/router/app-router.tsx b/src/renderer/router/app-router.tsx index e67467c81..240b5d5f9 100644 --- a/src/renderer/router/app-router.tsx +++ b/src/renderer/router/app-router.tsx @@ -1,11 +1,12 @@ /* eslint-disable sort-keys-fix/sort-keys-fix */ import { Routes, Route } from 'react-router-dom'; -import { AlbumListRoute } from '@/renderer/features/albums/routes/album-list-route'; +import { AlbumListRoute } from '@/renderer/features/albums'; +import { LoginRoute } from '@/renderer/features/auth'; +import { DashboardRoute } from '@/renderer/features/dashboard'; +import { NowPlayingRoute } from '@/renderer/features/now-playing'; +import { AuthLayout, DefaultLayout } from '@/renderer/layouts'; import { AuthOutlet } from '@/renderer/router/auth-outlet'; import { PrivateOutlet } from '@/renderer/router/private-outlet'; -import { LoginRoute } from '../features/auth'; -import { DashboardRoute } from '../features/dashboard'; -import { AuthLayout, DefaultLayout } from '../layouts'; import { AppRoute } from './routes'; export const AppRouter = () => { @@ -22,6 +23,7 @@ export const AppRouter = () => { > }> } path={AppRoute.HOME} /> + } path={AppRoute.NOW_PLAYING} /> } path={AppRoute.LIBRARY_ALBUMS} /> } path={AppRoute.LIBRARY_ARTISTS} /> diff --git a/src/renderer/router/routes.ts b/src/renderer/router/routes.ts index 975401404..82ef096e3 100644 --- a/src/renderer/router/routes.ts +++ b/src/renderer/router/routes.ts @@ -12,6 +12,7 @@ export enum AppRoute { LIBRARY_FOLDERS = '/library/folders', LIBRARY_SONGS = '/library/songs', LOGIN = '/login', + NOW_PLAYING = '/now-playing', PLAYING = '/playing', PLAYLISTS = '/playlists', PLAYLISTS_DETAIL = '/playlists/:playlistId', @@ -21,6 +22,7 @@ export enum AppRoute { type TArgs = | { path: AppRoute.HOME } + | { path: AppRoute.NOW_PLAYING } | { path: AppRoute.EXPLORE } | { path: AppRoute.LOGIN } | { path: AppRoute.PLAYING }