From 6ff5affb58b61ae5a02612fc7daac03c28b78e15 Mon Sep 17 00:00:00 2001 From: jeffvli Date: Mon, 13 Oct 2025 20:17:36 -0700 Subject: [PATCH] temp commit for breaking changes --- src/renderer/components/card/poster-card.tsx | 5 +- .../virtual-table/hooks/use-virtual-table.ts | 4 +- .../components/virtual-table/index.tsx | 2 +- .../virtual-table/table-config-dropdown.tsx | 62 ++-- .../virtual-table/table-pagination.tsx | 2 +- ...m-artist-detail-top-songs-list-content.tsx | 81 ----- .../album-artist-list-grid-view.tsx | 177 ---------- .../album-artist-list-table-view.tsx | 45 --- .../components/artist-list-grid-view.tsx | 178 ---------- .../components/artist-list-table-view.tsx | 42 --- ...bum-artist-detail-top-songs-list-route.tsx | 5 +- .../routes/album-artist-list-route.tsx | 8 +- .../artists/routes/artist-list-route.tsx | 5 +- .../components/genre-list-grid-view.tsx | 157 --------- .../components/genre-list-table-view.tsx | 59 ---- .../genres/routes/genre-list-route.tsx | 6 +- .../components/play-queue-list-controls.tsx | 4 +- .../now-playing/components/play-queue.tsx | 17 +- .../components/sidebar-play-queue.tsx | 20 +- .../now-playing/routes/now-playing-route.tsx | 17 +- .../playlist-detail-song-list-content.tsx | 312 ------------------ .../components/playlist-list-grid-view.tsx | 174 ---------- .../components/playlist-list-table-view.tsx | 49 --- .../playlist-detail-song-list-route.tsx | 3 +- .../playlists/routes/playlist-list-route.tsx | 5 +- .../search/components/search-content.tsx | 21 +- .../components/similar-songs-list.tsx | 41 ++- src/renderer/store/album-artist.store.ts | 4 +- src/renderer/store/list.store.ts | 22 +- src/renderer/store/playlist.store.ts | 8 +- 30 files changed, 112 insertions(+), 1423 deletions(-) delete mode 100644 src/renderer/features/artists/components/album-artist-detail-top-songs-list-content.tsx delete mode 100644 src/renderer/features/artists/components/album-artist-list-grid-view.tsx delete mode 100644 src/renderer/features/artists/components/album-artist-list-table-view.tsx delete mode 100644 src/renderer/features/artists/components/artist-list-grid-view.tsx delete mode 100644 src/renderer/features/artists/components/artist-list-table-view.tsx delete mode 100644 src/renderer/features/genres/components/genre-list-grid-view.tsx delete mode 100644 src/renderer/features/genres/components/genre-list-table-view.tsx delete mode 100644 src/renderer/features/playlists/components/playlist-detail-song-list-content.tsx delete mode 100644 src/renderer/features/playlists/components/playlist-list-grid-view.tsx delete mode 100644 src/renderer/features/playlists/components/playlist-list-table-view.tsx diff --git a/src/renderer/components/card/poster-card.tsx b/src/renderer/components/card/poster-card.tsx index 8a2036957..8704edb43 100644 --- a/src/renderer/components/card/poster-card.tsx +++ b/src/renderer/components/card/poster-card.tsx @@ -4,7 +4,6 @@ import { generatePath, Link } from 'react-router-dom'; import styles from './poster-card.module.css'; import { CardRows } from '/@/renderer/components/card/card-rows'; -import { GridCardControls } from '/@/renderer/components/virtual-grid/grid-card/grid-card-controls'; import { Image } from '/@/shared/components/image/image'; import { Skeleton } from '/@/shared/components/skeleton/skeleton'; import { Stack } from '/@/shared/components/stack/stack'; @@ -57,12 +56,12 @@ export const PosterCard = ({ > - + /> */}
diff --git a/src/renderer/components/virtual-table/hooks/use-virtual-table.ts b/src/renderer/components/virtual-table/hooks/use-virtual-table.ts index ec105303d..d39d4b580 100644 --- a/src/renderer/components/virtual-table/hooks/use-virtual-table.ts +++ b/src/renderer/components/virtual-table/hooks/use-virtual-table.ts @@ -30,7 +30,7 @@ import { LibraryItem, ServerListItem, } from '/@/shared/types/domain-types'; -import { ListDisplayType, TablePagination } from '/@/shared/types/types'; +import { ListDisplayType, ListPagination } from '/@/shared/types/types'; export type AgGridFetchFn = ( args: { filter: TFilter; limit: number; startIndex: number }, @@ -227,7 +227,7 @@ export const useVirtualTable = >({ ); const setParamsTablePagination = useCallback( - (args: { data: Partial; key: ListKey }) => { + (args: { data: Partial; key: ListKey }) => { const { data } = args; setSearchParams( diff --git a/src/renderer/components/virtual-table/index.tsx b/src/renderer/components/virtual-table/index.tsx index 2e06b9f4b..23999304f 100644 --- a/src/renderer/components/virtual-table/index.tsx +++ b/src/renderer/components/virtual-table/index.tsx @@ -50,7 +50,7 @@ import { import { PlayerStatus, TableColumn, - TablePagination as TablePaginationType, + ListPagination as TablePaginationType, } from '/@/shared/types/types'; export * from './hooks/use-click-outside-deselect'; diff --git a/src/renderer/components/virtual-table/table-config-dropdown.tsx b/src/renderer/components/virtual-table/table-config-dropdown.tsx index bf6b56d1a..cce9a1a65 100644 --- a/src/renderer/components/virtual-table/table-config-dropdown.tsx +++ b/src/renderer/components/virtual-table/table-config-dropdown.tsx @@ -8,13 +8,17 @@ import { MultiSelect } from '/@/shared/components/multi-select/multi-select'; import { Option } from '/@/shared/components/option/option'; import { Slider } from '/@/shared/components/slider/slider'; import { Switch } from '/@/shared/components/switch/switch'; -import { TableColumn, TableType } from '/@/shared/types/types'; +import { ItemListKey, TableColumn } from '/@/shared/types/types'; export const SONG_TABLE_COLUMNS = [ { label: i18n.t('table.config.label.rowIndex', { postProcess: 'titleCase' }), value: TableColumn.ROW_INDEX, }, + { + label: i18n.t('table.config.label.image', { postProcess: 'titleCase' }), + value: TableColumn.IMAGE, + }, { label: i18n.t('table.config.label.title', { postProcess: 'titleCase' }), value: TableColumn.TITLE, @@ -43,6 +47,10 @@ export const SONG_TABLE_COLUMNS = [ label: i18n.t('table.config.label.genre', { postProcess: 'titleCase' }), value: TableColumn.GENRE, }, + { + label: i18n.t('table.config.label.genreBadge', { postProcess: 'titleCase' }), + value: TableColumn.GENRE_BADGE, + }, { label: i18n.t('table.config.label.year', { postProcess: 'titleCase' }), value: TableColumn.YEAR, @@ -119,6 +127,10 @@ export const ALBUM_TABLE_COLUMNS = [ label: i18n.t('table.config.label.rowIndex', { postProcess: 'titleCase' }), value: TableColumn.ROW_INDEX, }, + { + label: i18n.t('table.config.label.image', { postProcess: 'titleCase' }), + value: TableColumn.IMAGE, + }, { label: i18n.t('table.config.label.title', { postProcess: 'titleCase' }), value: TableColumn.TITLE, @@ -147,6 +159,10 @@ export const ALBUM_TABLE_COLUMNS = [ label: i18n.t('table.config.label.genre', { postProcess: 'titleCase' }), value: TableColumn.GENRE, }, + { + label: i18n.t('table.config.label.genreBadge', { postProcess: 'titleCase' }), + value: TableColumn.GENRE_BADGE, + }, { label: i18n.t('table.config.label.year', { postProcess: 'titleCase' }), value: TableColumn.YEAR, @@ -241,6 +257,10 @@ export const PLAYLIST_TABLE_COLUMNS = [ label: i18n.t('table.config.label.rowIndex', { postProcess: 'titleCase' }), value: TableColumn.ROW_INDEX, }, + { + label: i18n.t('table.config.label.image', { postProcess: 'titleCase' }), + value: TableColumn.IMAGE, + }, { label: i18n.t('table.config.label.title', { postProcess: 'titleCase' }), value: TableColumn.TITLE, @@ -284,23 +304,23 @@ export const GENRE_TABLE_COLUMNS = [ interface TableConfigDropdownProps { // tableRef?: MutableRefObject | null>; - type: TableType; + type: ItemListKey; } export const TableConfigDropdown = ({ type }: TableConfigDropdownProps) => { const { t } = useTranslation(); const { setSettings } = useSettingsStoreActions(); - const tableConfig = useSettingsStore((state) => state.tables); + const tableConfig = useSettingsStore((state) => state.lists); const handleAddOrRemoveColumns = (values: string[]) => { const existingColumns = tableConfig[type].columns; if (values.length === 0) { setSettings({ - tables: { - ...useSettingsStore.getState().tables, + lists: { + ...useSettingsStore.getState().lists, [type]: { - ...useSettingsStore.getState().tables[type], + ...useSettingsStore.getState().lists[type], columns: [], }, }, @@ -312,10 +332,10 @@ export const TableConfigDropdown = ({ type }: TableConfigDropdownProps) => { if (values.length > existingColumns.length) { const newColumn = { column: values[values.length - 1] }; setSettings({ - tables: { - ...useSettingsStore.getState().tables, + lists: { + ...useSettingsStore.getState().lists, [type]: { - ...useSettingsStore.getState().tables[type], + ...useSettingsStore.getState().lists[type], columns: [...existingColumns, newColumn], }, }, @@ -328,10 +348,10 @@ export const TableConfigDropdown = ({ type }: TableConfigDropdownProps) => { const newColumns = existingColumns.filter((column) => !removed.includes(column)); setSettings({ - tables: { - ...useSettingsStore.getState().tables, + lists: { + ...useSettingsStore.getState().lists, [type]: { - ...useSettingsStore.getState().tables[type], + ...useSettingsStore.getState().lists[type], columns: newColumns, }, }, @@ -341,10 +361,10 @@ export const TableConfigDropdown = ({ type }: TableConfigDropdownProps) => { const handleUpdateRowHeight = (value: number) => { setSettings({ - tables: { - ...useSettingsStore.getState().tables, + lists: { + ...useSettingsStore.getState().lists, [type]: { - ...useSettingsStore.getState().tables[type], + ...useSettingsStore.getState().lists[type], rowHeight: value, }, }, @@ -353,10 +373,10 @@ export const TableConfigDropdown = ({ type }: TableConfigDropdownProps) => { const handleUpdateAutoFit = (e: ChangeEvent) => { setSettings({ - tables: { - ...useSettingsStore.getState().tables, + lists: { + ...useSettingsStore.getState().lists, [type]: { - ...useSettingsStore.getState().tables[type], + ...useSettingsStore.getState().lists[type], autoFit: e.currentTarget.checked, }, }, @@ -365,10 +385,10 @@ export const TableConfigDropdown = ({ type }: TableConfigDropdownProps) => { const handleUpdateFollow = (e: ChangeEvent) => { setSettings({ - tables: { - ...useSettingsStore.getState().tables, + lists: { + ...useSettingsStore.getState().lists, [type]: { - ...useSettingsStore.getState().tables[type], + ...useSettingsStore.getState().lists[type], followCurrentSong: e.currentTarget.checked, }, }, diff --git a/src/renderer/components/virtual-table/table-pagination.tsx b/src/renderer/components/virtual-table/table-pagination.tsx index b59d22af7..14e62a852 100644 --- a/src/renderer/components/virtual-table/table-pagination.tsx +++ b/src/renderer/components/virtual-table/table-pagination.tsx @@ -14,7 +14,7 @@ import { NumberInput } from '/@/shared/components/number-input/number-input'; import { Pagination } from '/@/shared/components/pagination/pagination'; import { Popover } from '/@/shared/components/popover/popover'; import { Text } from '/@/shared/components/text/text'; -import { TablePagination as TablePaginationType } from '/@/shared/types/types'; +import { ListPagination as TablePaginationType } from '/@/shared/types/types'; interface TablePaginationProps { pageKey: ListKey; diff --git a/src/renderer/features/artists/components/album-artist-detail-top-songs-list-content.tsx b/src/renderer/features/artists/components/album-artist-detail-top-songs-list-content.tsx deleted file mode 100644 index 04fdcdbbb..000000000 --- a/src/renderer/features/artists/components/album-artist-detail-top-songs-list-content.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import type { RowDoubleClickedEvent } from '@ag-grid-community/core'; -import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact'; - -import { MutableRefObject } from 'react'; - -import { VirtualGridAutoSizerContainer } from '/@/renderer/components/virtual-grid/virtual-grid-wrapper'; -import { VirtualTable } from '/@/renderer/components/virtual-table'; -import { useCurrentSongRowStyles } from '/@/renderer/components/virtual-table/hooks/use-current-song-row-styles'; -import { useVirtualTable } from '/@/renderer/components/virtual-table/hooks/use-virtual-table'; -import { useListContext } from '/@/renderer/context/list-context'; -import { SONG_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/context-menu-items'; -import { usePlayQueueAdd } from '/@/renderer/features/player/hooks/use-playqueue-add'; -import { useCurrentServer } from '/@/renderer/store'; -import { usePlayButtonBehavior } from '/@/renderer/store/settings.store'; -import { LibraryItem, QueueSong, SongListQuery } from '/@/shared/types/domain-types'; - -interface AlbumArtistSongListContentProps { - data: QueueSong[]; - tableRef: MutableRefObject; -} - -export const AlbumArtistDetailTopSongsListContent = ({ - data, - tableRef, -}: AlbumArtistSongListContentProps) => { - const server = useCurrentServer(); - const { id, pageKey } = useListContext(); - - const handlePlayQueueAdd = usePlayQueueAdd(); - const playButtonBehavior = usePlayButtonBehavior(); - - const handleRowDoubleClick = (e: RowDoubleClickedEvent) => { - if (!e.data) return; - - const rowData: QueueSong[] = []; - e.api.forEachNode((node) => { - if (!node.data) return; - rowData.push(node.data); - }); - - handlePlayQueueAdd?.({ - byData: rowData, - initialSongId: e.data.id, - playType: playButtonBehavior, - }); - }; - - const customFilters: Partial = { - ...(id && { artistIds: [id] }), - }; - - const { rowClassRules } = useCurrentSongRowStyles({ tableRef }); - - const tableProps = useVirtualTable({ - contextMenu: SONG_CONTEXT_MENU_ITEMS, - customFilters, - itemType: LibraryItem.SONG, - pageKey, - server, - tableRef, - }); - - return ( - <> - - data.data.uniqueId} - onRowDoubleClicked={handleRowDoubleClick} - rowClassRules={rowClassRules} - rowData={data} - rowModelType="clientSide" - rowSelection="multiple" - /> - - - ); -}; diff --git a/src/renderer/features/artists/components/album-artist-list-grid-view.tsx b/src/renderer/features/artists/components/album-artist-list-grid-view.tsx deleted file mode 100644 index ae9116cbe..000000000 --- a/src/renderer/features/artists/components/album-artist-list-grid-view.tsx +++ /dev/null @@ -1,177 +0,0 @@ -import { QueryKey, useQueryClient } from '@tanstack/react-query'; -import { MutableRefObject, useCallback, useMemo } from 'react'; -import AutoSizer, { Size } from 'react-virtualized-auto-sizer'; -import { ListOnScrollProps } from 'react-window'; - -import { api } from '/@/renderer/api'; -import { queryKeys } from '/@/renderer/api/query-keys'; -import { ALBUMARTIST_CARD_ROWS } from '/@/renderer/components/card/card-rows'; -import { VirtualGridAutoSizerContainer } from '/@/renderer/components/virtual-grid/virtual-grid-wrapper'; -import { - VirtualInfiniteGrid, - VirtualInfiniteGridRef, -} from '/@/renderer/components/virtual-grid/virtual-infinite-grid'; -import { useListContext } from '/@/renderer/context/list-context'; -import { usePlayQueueAdd } from '/@/renderer/features/player/hooks/use-playqueue-add'; -import { useHandleFavorite } from '/@/renderer/features/shared/hooks/use-handle-favorite'; -import { AppRoute } from '/@/renderer/router/routes'; -import { useCurrentServer, useListStoreActions, useListStoreByKey } from '/@/renderer/store'; -import { - AlbumArtist, - AlbumArtistListQuery, - AlbumArtistListResponse, - AlbumArtistListSort, - LibraryItem, -} from '/@/shared/types/domain-types'; -import { CardRow, ListDisplayType } from '/@/shared/types/types'; - -interface AlbumArtistListGridViewProps { - gridRef: MutableRefObject; - itemCount?: number; -} - -export const AlbumArtistListGridView = ({ gridRef, itemCount }: AlbumArtistListGridViewProps) => { - const queryClient = useQueryClient(); - const server = useCurrentServer(); - const handlePlayQueueAdd = usePlayQueueAdd(); - - const { pageKey } = useListContext(); - const { display, filter, grid } = useListStoreByKey({ key: pageKey }); - const { setGrid } = useListStoreActions(); - const handleFavorite = useHandleFavorite({ gridRef }); - - const fetchInitialData = useCallback(() => { - const query: Omit = { - ...filter, - }; - - const queriesFromCache: [QueryKey, AlbumArtistListResponse | undefined][] = - queryClient.getQueriesData({ - exact: false, - fetchStatus: 'idle', - queryKey: queryKeys.albumArtists.list(server?.id || '', query), - stale: false, - }); - - const itemData: AlbumArtist[] = []; - - for (const [, data] of queriesFromCache) { - const { items, startIndex } = data || {}; - - if (items && items.length !== 1 && startIndex !== undefined) { - let itemIndex = 0; - for ( - let rowIndex = startIndex; - rowIndex < startIndex + items.length; - rowIndex += 1 - ) { - itemData[rowIndex] = items[itemIndex]; - itemIndex += 1; - } - } - } - - return itemData; - }, [filter, queryClient, server?.id]); - - const fetch = useCallback( - async ({ skip: startIndex, take: limit }: { skip: number; take: number }) => { - const query: AlbumArtistListQuery = { - ...filter, - limit, - startIndex, - }; - - const queryKey = queryKeys.albumArtists.list(server?.id || '', query); - - const albumArtistsRes = await queryClient.fetchQuery({ - gcTime: 1000 * 60 * 1, - queryFn: async ({ signal }) => - api.controller.getAlbumArtistList({ - apiClientProps: { - serverId: server?.id || '', - signal, - }, - query, - }), - queryKey, - }); - - return albumArtistsRes; - }, - [filter, queryClient, server], - ); - - const handleGridScroll = useCallback( - (e: ListOnScrollProps) => { - setGrid({ data: { scrollOffset: e.scrollOffset }, key: pageKey }); - }, - [pageKey, setGrid], - ); - - const cardRows = useMemo(() => { - const rows: CardRow[] = [ALBUMARTIST_CARD_ROWS.name]; - - switch (filter.sortBy) { - case AlbumArtistListSort.ALBUM_COUNT: - rows.push(ALBUMARTIST_CARD_ROWS.albumCount); - break; - case AlbumArtistListSort.DURATION: - rows.push(ALBUMARTIST_CARD_ROWS.duration); - break; - case AlbumArtistListSort.FAVORITED: - break; - case AlbumArtistListSort.NAME: - break; - case AlbumArtistListSort.PLAY_COUNT: - rows.push(ALBUMARTIST_CARD_ROWS.playCount); - break; - case AlbumArtistListSort.RANDOM: - break; - case AlbumArtistListSort.RATING: - rows.push(ALBUMARTIST_CARD_ROWS.rating); - break; - case AlbumArtistListSort.RECENTLY_ADDED: - break; - case AlbumArtistListSort.RELEASE_DATE: - break; - case AlbumArtistListSort.SONG_COUNT: - rows.push(ALBUMARTIST_CARD_ROWS.songCount); - break; - } - - return rows; - }, [filter.sortBy]); - - return ( - - - {({ height, width }: Size) => ( - - )} - - - ); -}; diff --git a/src/renderer/features/artists/components/album-artist-list-table-view.tsx b/src/renderer/features/artists/components/album-artist-list-table-view.tsx deleted file mode 100644 index d7f8e81cc..000000000 --- a/src/renderer/features/artists/components/album-artist-list-table-view.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact'; - -import { MutableRefObject } from 'react'; - -import { VirtualGridAutoSizerContainer } from '/@/renderer/components/virtual-grid/virtual-grid-wrapper'; -import { VirtualTable } from '/@/renderer/components/virtual-table'; -import { useVirtualTable } from '/@/renderer/components/virtual-table/hooks/use-virtual-table'; -import { useListContext } from '/@/renderer/context/list-context'; -import { ARTIST_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/context-menu-items'; -import { useCurrentServer } from '/@/renderer/store'; -import { LibraryItem } from '/@/shared/types/domain-types'; - -interface AlbumArtistListTableViewProps { - itemCount?: number; - tableRef: MutableRefObject; -} - -export const AlbumArtistListTableView = ({ - itemCount, - tableRef, -}: AlbumArtistListTableViewProps) => { - const server = useCurrentServer(); - const { pageKey } = useListContext(); - - const tableProps = useVirtualTable({ - contextMenu: ARTIST_CONTEXT_MENU_ITEMS, - itemCount, - itemType: LibraryItem.ALBUM_ARTIST, - pageKey, - server, - tableRef, - }); - - return ( - - - - ); -}; diff --git a/src/renderer/features/artists/components/artist-list-grid-view.tsx b/src/renderer/features/artists/components/artist-list-grid-view.tsx deleted file mode 100644 index 47900da28..000000000 --- a/src/renderer/features/artists/components/artist-list-grid-view.tsx +++ /dev/null @@ -1,178 +0,0 @@ -import { QueryKey, useQueryClient } from '@tanstack/react-query'; -import { MutableRefObject, useCallback, useMemo } from 'react'; -import AutoSizer, { Size } from 'react-virtualized-auto-sizer'; -import { ListOnScrollProps } from 'react-window'; - -import { api } from '/@/renderer/api'; -import { queryKeys } from '/@/renderer/api/query-keys'; -import { ALBUMARTIST_CARD_ROWS } from '/@/renderer/components/card/card-rows'; -import { VirtualGridAutoSizerContainer } from '/@/renderer/components/virtual-grid/virtual-grid-wrapper'; -import { - VirtualInfiniteGrid, - VirtualInfiniteGridRef, -} from '/@/renderer/components/virtual-grid/virtual-infinite-grid'; -import { useListContext } from '/@/renderer/context/list-context'; -import { usePlayQueueAdd } from '/@/renderer/features/player/hooks/use-playqueue-add'; -import { useHandleFavorite } from '/@/renderer/features/shared/hooks/use-handle-favorite'; -import { AppRoute } from '/@/renderer/router/routes'; -import { useCurrentServer, useListStoreActions } from '/@/renderer/store'; -import { useListStoreByKey } from '/@/renderer/store/list.store'; -import { - AlbumArtist, - ArtistListQuery, - ArtistListResponse, - ArtistListSort, - LibraryItem, -} from '/@/shared/types/domain-types'; -import { CardRow, ListDisplayType } from '/@/shared/types/types'; - -interface ArtistListGridViewProps { - gridRef: MutableRefObject; - itemCount?: number; -} - -export const ArtistListGridView = ({ gridRef, itemCount }: ArtistListGridViewProps) => { - const queryClient = useQueryClient(); - const server = useCurrentServer(); - const handlePlayQueueAdd = usePlayQueueAdd(); - - const { pageKey } = useListContext(); - const { display, filter, grid } = useListStoreByKey({ key: pageKey }); - const { setGrid } = useListStoreActions(); - const handleFavorite = useHandleFavorite({ gridRef }); - - const fetchInitialData = useCallback(() => { - const query: Omit = { - ...filter, - }; - - const queriesFromCache: [QueryKey, ArtistListResponse | undefined][] = - queryClient.getQueriesData({ - exact: false, - fetchStatus: 'idle', - queryKey: queryKeys.artists.list(server?.id || '', query), - stale: false, - }); - - const itemData: AlbumArtist[] = []; - - for (const [, data] of queriesFromCache) { - const { items, startIndex } = data || {}; - - if (items && items.length !== 1 && startIndex !== undefined) { - let itemIndex = 0; - for ( - let rowIndex = startIndex; - rowIndex < startIndex + items.length; - rowIndex += 1 - ) { - itemData[rowIndex] = items[itemIndex]; - itemIndex += 1; - } - } - } - - return itemData; - }, [filter, queryClient, server?.id]); - - const fetch = useCallback( - async ({ skip: startIndex, take: limit }: { skip: number; take: number }) => { - const query: ArtistListQuery = { - ...filter, - limit, - startIndex, - }; - - const queryKey = queryKeys.artists.list(server?.id || '', query); - - const artistsRes = await queryClient.fetchQuery({ - gcTime: 1000 * 60 * 1, - queryFn: async ({ signal }) => - api.controller.getArtistList({ - apiClientProps: { - serverId: server?.id || '', - signal, - }, - query, - }), - queryKey, - }); - - return artistsRes; - }, - [filter, queryClient, server], - ); - - const handleGridScroll = useCallback( - (e: ListOnScrollProps) => { - setGrid({ data: { scrollOffset: e.scrollOffset }, key: pageKey }); - }, - [pageKey, setGrid], - ); - - const cardRows = useMemo(() => { - const rows: CardRow[] = [ALBUMARTIST_CARD_ROWS.name]; - - switch (filter.sortBy) { - case ArtistListSort.ALBUM_COUNT: - rows.push(ALBUMARTIST_CARD_ROWS.albumCount); - break; - case ArtistListSort.DURATION: - rows.push(ALBUMARTIST_CARD_ROWS.duration); - break; - case ArtistListSort.FAVORITED: - break; - case ArtistListSort.NAME: - break; - case ArtistListSort.PLAY_COUNT: - rows.push(ALBUMARTIST_CARD_ROWS.playCount); - break; - case ArtistListSort.RANDOM: - break; - case ArtistListSort.RATING: - rows.push(ALBUMARTIST_CARD_ROWS.rating); - break; - case ArtistListSort.RECENTLY_ADDED: - break; - case ArtistListSort.RELEASE_DATE: - break; - case ArtistListSort.SONG_COUNT: - rows.push(ALBUMARTIST_CARD_ROWS.songCount); - break; - } - - return rows; - }, [filter.sortBy]); - - return ( - - - {({ height, width }: Size) => ( - - )} - - - ); -}; diff --git a/src/renderer/features/artists/components/artist-list-table-view.tsx b/src/renderer/features/artists/components/artist-list-table-view.tsx deleted file mode 100644 index d7c247cb2..000000000 --- a/src/renderer/features/artists/components/artist-list-table-view.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact'; - -import { MutableRefObject } from 'react'; - -import { VirtualGridAutoSizerContainer } from '/@/renderer/components/virtual-grid/virtual-grid-wrapper'; -import { VirtualTable } from '/@/renderer/components/virtual-table'; -import { useVirtualTable } from '/@/renderer/components/virtual-table/hooks/use-virtual-table'; -import { useListContext } from '/@/renderer/context/list-context'; -import { ARTIST_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/context-menu-items'; -import { useCurrentServer } from '/@/renderer/store'; -import { LibraryItem } from '/@/shared/types/domain-types'; - -interface ArtistListTableViewProps { - itemCount?: number; - tableRef: MutableRefObject; -} - -export const ArtistListTableView = ({ itemCount, tableRef }: ArtistListTableViewProps) => { - const server = useCurrentServer(); - const { pageKey } = useListContext(); - - const tableProps = useVirtualTable({ - contextMenu: ARTIST_CONTEXT_MENU_ITEMS, - itemCount, - itemType: LibraryItem.ARTIST, - pageKey, - server, - tableRef, - }); - - return ( - - - - ); -}; diff --git a/src/renderer/features/artists/routes/album-artist-detail-top-songs-list-route.tsx b/src/renderer/features/artists/routes/album-artist-detail-top-songs-list-route.tsx index dea8693b5..7078da72f 100644 --- a/src/renderer/features/artists/routes/album-artist-detail-top-songs-list-route.tsx +++ b/src/renderer/features/artists/routes/album-artist-detail-top-songs-list-route.tsx @@ -6,7 +6,6 @@ import { useParams } from 'react-router'; import { ListContext } from '/@/renderer/context/list-context'; import { artistsQueries } from '/@/renderer/features/artists/api/artists-api'; -import { AlbumArtistDetailTopSongsListContent } from '/@/renderer/features/artists/components/album-artist-detail-top-songs-list-content'; import { AlbumArtistDetailTopSongsListHeader } from '/@/renderer/features/artists/components/album-artist-detail-top-songs-list-header'; import { AnimatedPage } from '/@/renderer/features/shared/components/animated-page'; import { useCurrentServer } from '/@/renderer/store/auth.store'; @@ -54,10 +53,10 @@ const AlbumArtistDetailTopSongsListRoute = () => { itemCount={itemCount} title={detailQuery?.data?.name || 'Unknown'} /> - + /> */} ); diff --git a/src/renderer/features/artists/routes/album-artist-list-route.tsx b/src/renderer/features/artists/routes/album-artist-list-route.tsx index 2d1f4852c..235c598c3 100644 --- a/src/renderer/features/artists/routes/album-artist-list-route.tsx +++ b/src/renderer/features/artists/routes/album-artist-list-route.tsx @@ -3,10 +3,8 @@ import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/li import { useQuery } from '@tanstack/react-query'; import { useMemo, useRef } from 'react'; -import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid/virtual-infinite-grid'; import { ListContext } from '/@/renderer/context/list-context'; import { artistsQueries } from '/@/renderer/features/artists/api/artists-api'; -import { AlbumArtistListContent } from '/@/renderer/features/artists/components/album-artist-list-content'; import { AlbumArtistListHeader } from '/@/renderer/features/artists/components/album-artist-list-header'; import { AnimatedPage } from '/@/renderer/features/shared/components/animated-page'; import { useCurrentServer } from '/@/renderer/store/auth.store'; @@ -14,7 +12,7 @@ import { useListFilterByKey } from '/@/renderer/store/list.store'; import { AlbumArtistListQuery, LibraryItem } from '/@/shared/types/domain-types'; const AlbumArtistListRoute = () => { - const gridRef = useRef(null); + const gridRef = useRef(null); const tableRef = useRef(null); const pageKey = LibraryItem.ALBUM_ARTIST; const server = useCurrentServer(); @@ -48,11 +46,11 @@ const AlbumArtistListRoute = () => { itemCount={itemCount} tableRef={tableRef} /> - + /> */} ); diff --git a/src/renderer/features/artists/routes/artist-list-route.tsx b/src/renderer/features/artists/routes/artist-list-route.tsx index 9ee535aa0..53dbe5a49 100644 --- a/src/renderer/features/artists/routes/artist-list-route.tsx +++ b/src/renderer/features/artists/routes/artist-list-route.tsx @@ -3,7 +3,6 @@ import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/li import { useQuery } from '@tanstack/react-query'; import { useMemo, useRef } from 'react'; -import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid/virtual-infinite-grid'; import { ListContext } from '/@/renderer/context/list-context'; import { artistsQueries } from '/@/renderer/features/artists/api/artists-api'; import { ArtistListContent } from '/@/renderer/features/artists/components/artist-list-content'; @@ -14,7 +13,7 @@ import { useListFilterByKey } from '/@/renderer/store/list.store'; import { ArtistListQuery, LibraryItem } from '/@/shared/types/domain-types'; const ArtistListRoute = () => { - const gridRef = useRef(null); + const gridRef = useRef(null); const tableRef = useRef(null); const pageKey = LibraryItem.ARTIST; const server = useCurrentServer(); @@ -45,7 +44,7 @@ const ArtistListRoute = () => { - + {/* */} ); diff --git a/src/renderer/features/genres/components/genre-list-grid-view.tsx b/src/renderer/features/genres/components/genre-list-grid-view.tsx deleted file mode 100644 index 6747c1ac4..000000000 --- a/src/renderer/features/genres/components/genre-list-grid-view.tsx +++ /dev/null @@ -1,157 +0,0 @@ -import { QueryKey, useQueryClient } from '@tanstack/react-query'; -import { useCallback, useMemo } from 'react'; -import { useSearchParams } from 'react-router-dom'; -import AutoSizer, { Size } from 'react-virtualized-auto-sizer'; -import { ListOnScrollProps } from 'react-window'; - -import { api } from '/@/renderer/api'; -import { queryKeys } from '/@/renderer/api/query-keys'; -import { ALBUM_CARD_ROWS } from '/@/renderer/components/card/card-rows'; -import { VirtualGridAutoSizerContainer } from '/@/renderer/components/virtual-grid/virtual-grid-wrapper'; -import { VirtualInfiniteGrid } from '/@/renderer/components/virtual-grid/virtual-infinite-grid'; -import { useListContext } from '/@/renderer/context/list-context'; -import { usePlayQueueAdd } from '/@/renderer/features/player/hooks/use-playqueue-add'; -import { useGenreRoute } from '/@/renderer/hooks/use-genre-route'; -import { useCurrentServer, useListStoreActions, useListStoreByKey } from '/@/renderer/store'; -import { - Album, - Genre, - GenreListQuery, - GenreListResponse, - LibraryItem, -} from '/@/shared/types/domain-types'; -import { CardRow, ListDisplayType } from '/@/shared/types/types'; - -export const GenreListGridView = ({ gridRef, itemCount }: any) => { - const queryClient = useQueryClient(); - const server = useCurrentServer(); - const handlePlayQueueAdd = usePlayQueueAdd(); - const { id, pageKey } = useListContext(); - const { display, filter, grid } = useListStoreByKey({ key: pageKey }); - const { setGrid } = useListStoreActions(); - const genrePath = useGenreRoute(); - - const [searchParams, setSearchParams] = useSearchParams(); - const scrollOffset = searchParams.get('scrollOffset'); - const initialScrollOffset = Number(id ? scrollOffset : grid?.scrollOffset) || 0; - - const cardRows = useMemo(() => { - const rows: CardRow[] = [ALBUM_CARD_ROWS.name]; - return rows; - }, []); - - const handleGridScroll = useCallback( - (e: ListOnScrollProps) => { - if (id) { - setSearchParams( - (params) => { - params.set('scrollOffset', String(e.scrollOffset)); - return params; - }, - { replace: true }, - ); - } else { - setGrid({ data: { scrollOffset: e.scrollOffset }, key: pageKey }); - } - }, - [id, pageKey, setGrid, setSearchParams], - ); - - const fetchInitialData = useCallback(() => { - const query: Omit = { - ...filter, - }; - - const queriesFromCache: [QueryKey, GenreListResponse | undefined][] = - queryClient.getQueriesData({ - exact: false, - fetchStatus: 'idle', - queryKey: queryKeys.genres.list(server?.id || '', query), - stale: false, - }); - - const itemData: Genre[] = []; - - for (const [, data] of queriesFromCache) { - const { items, startIndex } = data || {}; - - if (items && items.length !== 1 && startIndex !== undefined) { - let itemIndex = 0; - for ( - let rowIndex = startIndex; - rowIndex < startIndex + items.length; - rowIndex += 1 - ) { - itemData[rowIndex] = items[itemIndex]; - itemIndex += 1; - } - } - } - - return itemData; - }, [filter, queryClient, server?.id]); - - const fetch = useCallback( - async ({ skip, take }: { skip: number; take: number }) => { - if (!server) { - return []; - } - - const query: GenreListQuery = { - ...filter, - limit: take, - startIndex: skip, - }; - - const queryKey = queryKeys.albums.list(server?.id || '', query); - - const albums = await queryClient.fetchQuery({ - queryFn: async ({ signal }) => { - return api.controller.getGenreList({ - apiClientProps: { - serverId: server?.id || '', - signal, - }, - query, - }); - }, - queryKey, - }); - - return albums; - }, - [filter, queryClient, server], - ); - - return ( - - - {({ height, width }: Size) => ( - - )} - - - ); -}; diff --git a/src/renderer/features/genres/components/genre-list-table-view.tsx b/src/renderer/features/genres/components/genre-list-table-view.tsx deleted file mode 100644 index 1ab131061..000000000 --- a/src/renderer/features/genres/components/genre-list-table-view.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact'; - -import { RowDoubleClickedEvent } from '@ag-grid-community/core'; -import { MutableRefObject, useCallback } from 'react'; -import { generatePath, useNavigate } from 'react-router'; - -import { VirtualGridAutoSizerContainer } from '/@/renderer/components/virtual-grid/virtual-grid-wrapper'; -import { VirtualTable } from '/@/renderer/components/virtual-table'; -import { useVirtualTable } from '/@/renderer/components/virtual-table/hooks/use-virtual-table'; -import { useListContext } from '/@/renderer/context/list-context'; -import { GENRE_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/context-menu-items'; -import { useGenreRoute } from '/@/renderer/hooks/use-genre-route'; -import { useCurrentServer } from '/@/renderer/store'; -import { LibraryItem } from '/@/shared/types/domain-types'; - -interface GenreListTableViewProps { - itemCount?: number; - tableRef: MutableRefObject; -} - -export const GenreListTableView = ({ itemCount, tableRef }: GenreListTableViewProps) => { - const server = useCurrentServer(); - const { customFilters, pageKey } = useListContext(); - const navigate = useNavigate(); - const genrePath = useGenreRoute(); - - const tableProps = useVirtualTable({ - contextMenu: GENRE_CONTEXT_MENU_ITEMS, - customFilters, - itemCount, - itemType: LibraryItem.GENRE, - pageKey, - server, - tableRef, - }); - - const onRowDoubleClicked = useCallback( - (e: RowDoubleClickedEvent) => { - const { data } = e; - if (!data) return; - - navigate(generatePath(genrePath, { genreId: data.id })); - }, - [genrePath, navigate], - ); - - return ( - - - - ); -}; diff --git a/src/renderer/features/genres/routes/genre-list-route.tsx b/src/renderer/features/genres/routes/genre-list-route.tsx index ccc5538f9..5c63b7942 100644 --- a/src/renderer/features/genres/routes/genre-list-route.tsx +++ b/src/renderer/features/genres/routes/genre-list-route.tsx @@ -3,10 +3,8 @@ import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/li import { useQuery } from '@tanstack/react-query'; import { useMemo, useRef } from 'react'; -import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid/virtual-infinite-grid'; import { ListContext } from '/@/renderer/context/list-context'; import { genresQueries } from '/@/renderer/features/genres/api/genres-api'; -import { GenreListContent } from '/@/renderer/features/genres/components/genre-list-content'; import { GenreListHeader } from '/@/renderer/features/genres/components/genre-list-header'; import { AnimatedPage } from '/@/renderer/features/shared/components/animated-page'; import { useCurrentServer } from '/@/renderer/store'; @@ -14,7 +12,7 @@ import { useListStoreByKey } from '/@/renderer/store/list.store'; import { GenreListQuery } from '/@/shared/types/domain-types'; const GenreListRoute = () => { - const gridRef = useRef(null); + const gridRef = useRef(null); const tableRef = useRef(null); const server = useCurrentServer(); const pageKey = 'genre'; @@ -46,7 +44,7 @@ const GenreListRoute = () => { - + {/* */} ); diff --git a/src/renderer/features/now-playing/components/play-queue-list-controls.tsx b/src/renderer/features/now-playing/components/play-queue-list-controls.tsx index f7bfdbaf0..4893a7810 100644 --- a/src/renderer/features/now-playing/components/play-queue-list-controls.tsx +++ b/src/renderer/features/now-playing/components/play-queue-list-controls.tsx @@ -12,7 +12,7 @@ import { ActionIcon } from '/@/shared/components/action-icon/action-icon'; import { Group } from '/@/shared/components/group/group'; import { Popover } from '/@/shared/components/popover/popover'; import { Song } from '/@/shared/types/domain-types'; -import { TableType } from '/@/shared/types/types'; +import { ItemListKey } from '/@/shared/types/types'; const mpvPlayer = isElectron() ? window.api.mpvPlayer : null; @@ -20,7 +20,7 @@ interface PlayQueueListOptionsProps { handleSearch: (value: string) => void; searchTerm?: string; tableRef: MutableRefObject }>; - type: TableType; + type: ItemListKey; } export const PlayQueueListControls = ({ diff --git a/src/renderer/features/now-playing/components/play-queue.tsx b/src/renderer/features/now-playing/components/play-queue.tsx index 72a508c93..880100031 100644 --- a/src/renderer/features/now-playing/components/play-queue.tsx +++ b/src/renderer/features/now-playing/components/play-queue.tsx @@ -8,7 +8,6 @@ import debounce from 'lodash/debounce'; import { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'; import { ErrorBoundary } from 'react-error-boundary'; -import { VirtualGridAutoSizerContainer } from '/@/renderer/components/virtual-grid/virtual-grid-wrapper'; import { getColumnDefs, VirtualTable } from '/@/renderer/components/virtual-table'; import { ErrorFallback } from '/@/renderer/features/action-required/components/error-fallback'; import { QUEUE_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/context-menu-items'; @@ -21,18 +20,16 @@ import { usePlayerStatus, } from '/@/renderer/store'; import { - PersistedTableColumn, useSettingsStore, useSettingsStoreActions, useTableSettings, } from '/@/renderer/store/settings.store'; import { searchSongs } from '/@/renderer/utils/search-songs'; import { LibraryItem, QueueSong } from '/@/shared/types/domain-types'; -import { TableType } from '/@/shared/types/types'; +import { ItemListKey } from '/@/shared/types/types'; type QueueProps = { - searchTerm?: string; - type: TableType; + type: ItemListKey; }; export const PlayQueue = forwardRef(({ searchTerm, type }: QueueProps, ref: Ref) => { @@ -138,7 +135,7 @@ export const PlayQueue = forwardRef(({ searchTerm, type }: QueueProps, ref: Ref< const columnsOrder = columnApi?.getAllGridColumns(); if (!columnsOrder) return; - const columnsInSettings = useSettingsStore.getState().tables[type].columns; + const columnsInSettings = useSettingsStore.getState().lists[type].columns; const updatedColumns: PersistedTableColumn[] = []; for (const column of columnsOrder) { @@ -149,7 +146,7 @@ export const PlayQueue = forwardRef(({ searchTerm, type }: QueueProps, ref: Ref< if (columnInSettings) { updatedColumns.push({ ...columnInSettings, - ...(!useSettingsStore.getState().tables[type].autoFit && { + ...(!useSettingsStore.getState().lists[type].autoFit && { width: column.getActualWidth(), }), }); @@ -157,10 +154,10 @@ export const PlayQueue = forwardRef(({ searchTerm, type }: QueueProps, ref: Ref< } setSettings({ - tables: { - ...useSettingsStore.getState().tables, + lists: { + ...useSettingsStore.getState().lists, [type]: { - ...useSettingsStore.getState().tables[type], + ...useSettingsStore.getState().lists[type], columns: updatedColumns, }, }, diff --git a/src/renderer/features/now-playing/components/sidebar-play-queue.tsx b/src/renderer/features/now-playing/components/sidebar-play-queue.tsx index 8202f1d19..ef7b03a24 100644 --- a/src/renderer/features/now-playing/components/sidebar-play-queue.tsx +++ b/src/renderer/features/now-playing/components/sidebar-play-queue.tsx @@ -2,12 +2,7 @@ import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/li import { useRef, useState } from 'react'; -import { PlayQueueListControls } from './play-queue-list-controls'; - -import { VirtualGridContainer } from '/@/renderer/components/virtual-grid/virtual-grid-wrapper'; -import { PlayQueue } from '/@/renderer/features/now-playing/components/play-queue'; import { useWindowSettings } from '/@/renderer/store/settings.store'; -import { Box } from '/@/shared/components/box/box'; import { Song } from '/@/shared/types/domain-types'; import { Platform } from '/@/shared/types/types'; @@ -17,17 +12,6 @@ export const SidebarPlayQueue = () => { const { windowBarStyle } = useWindowSettings(); const isWeb = windowBarStyle === Platform.WEB; - return ( - - - - - - - ); + + return null; }; diff --git a/src/renderer/features/now-playing/routes/now-playing-route.tsx b/src/renderer/features/now-playing/routes/now-playing-route.tsx index cd6e21eb7..c68f9c3f0 100644 --- a/src/renderer/features/now-playing/routes/now-playing-route.tsx +++ b/src/renderer/features/now-playing/routes/now-playing-route.tsx @@ -3,10 +3,6 @@ import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/li import { useRef, useState } from 'react'; -import { VirtualGridContainer } from '/@/renderer/components/virtual-grid/virtual-grid-wrapper'; -import { NowPlayingHeader } from '/@/renderer/features/now-playing/components/now-playing-header'; -import { PlayQueue } from '/@/renderer/features/now-playing/components/play-queue'; -import { PlayQueueListControls } from '/@/renderer/features/now-playing/components/play-queue-list-controls'; import { AnimatedPage } from '/@/renderer/features/shared/components/animated-page'; const NowPlayingRoute = () => { @@ -15,16 +11,11 @@ const NowPlayingRoute = () => { return ( - + {/* - - - + + + */} ); }; diff --git a/src/renderer/features/playlists/components/playlist-detail-song-list-content.tsx b/src/renderer/features/playlists/components/playlist-detail-song-list-content.tsx deleted file mode 100644 index c815a6f8a..000000000 --- a/src/renderer/features/playlists/components/playlist-detail-song-list-content.tsx +++ /dev/null @@ -1,312 +0,0 @@ -import type { - BodyScrollEvent, - ColDef, - GridReadyEvent, - PaginationChangedEvent, - RowDoubleClickedEvent, - RowDragEvent, -} from '@ag-grid-community/core'; -import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact'; - -import { useQuery, useQueryClient } from '@tanstack/react-query'; -import debounce from 'lodash/debounce'; -import { AnimatePresence } from 'motion/react'; -import { MutableRefObject, useCallback, useMemo } from 'react'; -import { useParams } from 'react-router'; - -import { api } from '/@/renderer/api'; -import { queryKeys } from '/@/renderer/api/query-keys'; -import { VirtualGridAutoSizerContainer } from '/@/renderer/components/virtual-grid/virtual-grid-wrapper'; -import { getColumnDefs, TablePagination, VirtualTable } from '/@/renderer/components/virtual-table'; -import { useCurrentSongRowStyles } from '/@/renderer/components/virtual-table/hooks/use-current-song-row-styles'; -import { - PLAYLIST_SONG_CONTEXT_MENU_ITEMS, - SMART_PLAYLIST_SONG_CONTEXT_MENU_ITEMS, -} from '/@/renderer/features/context-menu/context-menu-items'; -import { useHandleTableContextMenu } from '/@/renderer/features/context-menu/hooks/use-handle-context-menu'; -import { usePlayQueueAdd } from '/@/renderer/features/player/hooks/use-playqueue-add'; -import { playlistsQueries } from '/@/renderer/features/playlists/api/playlists-api'; -import { useAppFocus } from '/@/renderer/hooks'; -import { - useCurrentServer, - usePlayerSong, - usePlayerStatus, - usePlaylistDetailStore, - usePlaylistDetailTablePagination, - useSetPlaylistDetailTable, - useSetPlaylistDetailTablePagination, -} from '/@/renderer/store'; -import { PersistedTableColumn, usePlayButtonBehavior } from '/@/renderer/store/settings.store'; -import { toast } from '/@/shared/components/toast/toast'; -import { - LibraryItem, - PlaylistSongListQueryClientSide, - QueueSong, - ServerType, - Song, - SongListResponse, - SongListSort, - SortOrder, -} from '/@/shared/types/domain-types'; -import { ListDisplayType } from '/@/shared/types/types'; - -interface PlaylistDetailContentProps { - songs?: Song[]; - tableRef: MutableRefObject; -} - -export const PlaylistDetailSongListContent = ({ songs, tableRef }: PlaylistDetailContentProps) => { - const { playlistId } = useParams() as { playlistId: string }; - const queryClient = useQueryClient(); - const status = usePlayerStatus(); - const isFocused = useAppFocus(); - const currentSong = usePlayerSong(); - const server = useCurrentServer(); - const page = usePlaylistDetailStore(); - const filters: PlaylistSongListQueryClientSide = useMemo(() => { - return { - sortBy: page?.table.id[playlistId]?.filter?.sortBy || SongListSort.ID, - sortOrder: page?.table.id[playlistId]?.filter?.sortOrder || SortOrder.ASC, - }; - }, [page?.table.id, playlistId]); - - const detailQuery = useQuery( - playlistsQueries.detail({ query: { id: playlistId }, serverId: server?.id }), - ); - - const p = usePlaylistDetailTablePagination(playlistId); - const pagination = { - currentPage: p?.currentPage || 0, - itemsPerPage: p?.itemsPerPage || 100, - scrollOffset: p?.scrollOffset || 0, - totalItems: p?.totalItems || 1, - totalPages: p?.totalPages || 1, - }; - - const setPagination = useSetPlaylistDetailTablePagination(); - const setTable = useSetPlaylistDetailTable(); - const handlePlayQueueAdd = usePlayQueueAdd(); - const playButtonBehavior = usePlayButtonBehavior(); - - const isPaginationEnabled = page.display === ListDisplayType.TABLE_PAGINATED; - - const columnDefs: ColDef[] = useMemo( - () => getColumnDefs(page.table.columns, false, 'generic'), - [page.table.columns], - ); - - const onGridReady = useCallback( - (params: GridReadyEvent) => { - params.api?.ensureIndexVisible(pagination.scrollOffset, 'top'); - }, - [pagination.scrollOffset], - ); - - const handleDragEnd = useCallback( - async (e: RowDragEvent) => { - if (!e.nodes.length) return; - - const trackId = e.node.data?.playlistItemId; - if (trackId && e.node.rowIndex !== null && e.overIndex !== e.node.rowIndex) { - try { - await api.controller.movePlaylistItem({ - apiClientProps: { - serverId: server?.id || '', - }, - query: { - endingIndex: e.overIndex, - playlistId, - startingIndex: e.node.rowIndex + 1, - trackId, - }, - }); - - queryClient.setQueryData( - queryKeys.playlists.songList(server?.id || '', playlistId), - (previous) => { - if (previous?.items) { - const from = e.node.rowIndex!; - const to = e.overIndex; - - const item = previous.items[from]; - const remaining = previous.items.toSpliced(from, 1); - remaining.splice(to, 0, item); - - return { - error: previous.error, - items: remaining, - startIndex: previous.startIndex, - totalRecordCount: previous.totalRecordCount, - }; - } - - return previous; - }, - ); - - // Nodes have to be redrawn, otherwise the row indexes will be wrong - // Maybe it's possible to only redraw necessary rows to not be as expensive? - tableRef.current?.api.redrawRows(); - } catch (error) { - toast.error({ - message: (error as Error).message, - title: `Failed to move song ${e.node.data?.name} to ${e.overIndex}`, - }); - } - } - }, - [playlistId, queryClient, server, tableRef], - ); - - const handleGridSizeChange = () => { - if (page.table.autoFit) { - tableRef?.current?.api?.sizeColumnsToFit(); - } - }; - - const onPaginationChanged = useCallback( - (event: PaginationChangedEvent) => { - if (!isPaginationEnabled || !event.api) return; - - try { - // Scroll to top of page on pagination change - const currentPageStartIndex = pagination.currentPage * pagination.itemsPerPage; - event.api?.ensureIndexVisible(currentPageStartIndex, 'top'); - } catch (err) { - console.error(err); - } - - setPagination(playlistId, { - itemsPerPage: event.api.paginationGetPageSize(), - totalItems: event.api.paginationGetRowCount(), - totalPages: event.api.paginationGetTotalPages() + 1, - }); - }, - [ - isPaginationEnabled, - pagination.currentPage, - pagination.itemsPerPage, - playlistId, - setPagination, - ], - ); - - const handleColumnChange = useCallback(() => { - const { columnApi } = tableRef?.current || {}; - const columnsOrder = columnApi?.getAllGridColumns(); - - if (!columnsOrder) return; - - const columnsInSettings = page.table.columns; - const updatedColumns: PersistedTableColumn[] = []; - for (const column of columnsOrder) { - const columnInSettings = columnsInSettings.find( - (c) => c.column === column.getColDef().colId, - ); - - if (columnInSettings) { - updatedColumns.push({ - ...columnInSettings, - ...(!page.table.autoFit && { - width: column.getActualWidth(), - }), - }); - } - } - - setTable({ columns: updatedColumns }); - }, [page.table.autoFit, page.table.columns, setTable, tableRef]); - - const debouncedColumnChange = debounce(handleColumnChange, 200); - - const handleScroll = (e: BodyScrollEvent) => { - const scrollOffset = Number((e.top / page.table.rowHeight).toFixed(0)); - setPagination(playlistId, { scrollOffset }); - }; - - const contextMenuItems = useMemo(() => { - if (detailQuery?.data?.rules) { - return SMART_PLAYLIST_SONG_CONTEXT_MENU_ITEMS; - } - - return PLAYLIST_SONG_CONTEXT_MENU_ITEMS; - }, [detailQuery?.data?.rules]); - - const handleContextMenu = useHandleTableContextMenu(LibraryItem.SONG, contextMenuItems, { - playlistId, - tableRef, - }); - - const handleRowDoubleClick = (e: RowDoubleClickedEvent) => { - if (!e.data) return; - handlePlayQueueAdd?.({ - byItemType: { - id: [playlistId], - type: LibraryItem.PLAYLIST, - }, - initialSongId: e.data.id, - playType: playButtonBehavior, - }); - }; - - const { rowClassRules } = useCurrentSongRowStyles({ tableRef }); - - const canDrag = - filters.sortBy === SongListSort.ID && - !detailQuery?.data?.rules && - server?.type !== ServerType.SUBSONIC; - - return ( - <> - - data.data.uniqueId} - // https://github.com/ag-grid/ag-grid/issues/5284 - // Key is used to force remount of table when display, rowHeight, or server changes - key={`table-${page.display}-${page.table.rowHeight}-${server?.id}`} - onBodyScrollEnd={handleScroll} - onCellContextMenu={handleContextMenu} - onColumnMoved={handleColumnChange} - onColumnResized={debouncedColumnChange} - onGridReady={onGridReady} - onGridSizeChanged={handleGridSizeChange} - onPaginationChanged={onPaginationChanged} - onRowDoubleClicked={handleRowDoubleClick} - onRowDragEnd={handleDragEnd} - pagination={isPaginationEnabled} - paginationAutoPageSize={isPaginationEnabled} - paginationPageSize={pagination.itemsPerPage || 100} - ref={tableRef} - rowClassRules={rowClassRules} - rowData={songs} - rowDragEntireRow={canDrag} - rowHeight={page.table.rowHeight || 40} - rowModelType="clientSide" - shouldUpdateSong - /> - - {isPaginationEnabled && ( - - {page.display === ListDisplayType.TABLE_PAGINATED && ( - - )} - - )} - - ); -}; diff --git a/src/renderer/features/playlists/components/playlist-list-grid-view.tsx b/src/renderer/features/playlists/components/playlist-list-grid-view.tsx deleted file mode 100644 index 1952be18b..000000000 --- a/src/renderer/features/playlists/components/playlist-list-grid-view.tsx +++ /dev/null @@ -1,174 +0,0 @@ -import { QueryKey, useQueryClient } from '@tanstack/react-query'; -import { MutableRefObject, useCallback, useMemo } from 'react'; -import AutoSizer, { Size } from 'react-virtualized-auto-sizer'; -import { ListOnScrollProps } from 'react-window'; - -import { controller } from '/@/renderer/api/controller'; -import { queryKeys } from '/@/renderer/api/query-keys'; -import { PLAYLIST_CARD_ROWS } from '/@/renderer/components/card/card-rows'; -import { VirtualGridAutoSizerContainer } from '/@/renderer/components/virtual-grid/virtual-grid-wrapper'; -import { - VirtualInfiniteGrid, - VirtualInfiniteGridRef, -} from '/@/renderer/components/virtual-grid/virtual-infinite-grid'; -import { useListContext } from '/@/renderer/context/list-context'; -import { usePlayQueueAdd } from '/@/renderer/features/player/hooks/use-playqueue-add'; -import { useHandleFavorite } from '/@/renderer/features/shared/hooks/use-handle-favorite'; -import { AppRoute } from '/@/renderer/router/routes'; -import { useCurrentServer, useListStoreByKey } from '/@/renderer/store'; -import { useListStoreActions } from '/@/renderer/store/list.store'; -import { - LibraryItem, - Playlist, - PlaylistListQuery, - PlaylistListResponse, - PlaylistListSort, -} from '/@/shared/types/domain-types'; -import { CardRow, ListDisplayType } from '/@/shared/types/types'; - -interface PlaylistListGridViewProps { - gridRef: MutableRefObject; - itemCount?: number; -} - -export const PlaylistListGridView = ({ gridRef, itemCount }: PlaylistListGridViewProps) => { - const { pageKey } = useListContext(); - const queryClient = useQueryClient(); - const server = useCurrentServer(); - const handlePlayQueueAdd = usePlayQueueAdd(); - const { display, filter, grid } = useListStoreByKey({ key: pageKey }); - const { setGrid } = useListStoreActions(); - const handleFavorite = useHandleFavorite({ gridRef }); - - const cardRows = useMemo(() => { - const rows: CardRow[] = [PLAYLIST_CARD_ROWS.nameFull]; - - switch (filter.sortBy) { - case PlaylistListSort.DURATION: - rows.push(PLAYLIST_CARD_ROWS.duration); - break; - case PlaylistListSort.NAME: - rows.push(PLAYLIST_CARD_ROWS.songCount); - break; - case PlaylistListSort.OWNER: - rows.push(PLAYLIST_CARD_ROWS.owner); - break; - case PlaylistListSort.PUBLIC: - rows.push(PLAYLIST_CARD_ROWS.public); - break; - case PlaylistListSort.SONG_COUNT: - rows.push(PLAYLIST_CARD_ROWS.songCount); - break; - case PlaylistListSort.UPDATED_AT: - break; - } - - return rows; - }, [filter.sortBy]); - - const handleGridScroll = useCallback( - (e: ListOnScrollProps) => { - setGrid({ data: { scrollOffset: e.scrollOffset }, key: pageKey }); - }, - [pageKey, setGrid], - ); - - const fetchInitialData = useCallback(() => { - const query: Omit = { - ...filter, - }; - - const queriesFromCache: [QueryKey, PlaylistListResponse | undefined][] = - queryClient.getQueriesData({ - exact: false, - fetchStatus: 'idle', - queryKey: queryKeys.playlists.list(server?.id || '', query), - stale: false, - }); - - const itemData: Playlist[] = []; - - for (const [, data] of queriesFromCache) { - const { items, startIndex } = data || {}; - - if (items && items.length !== 1 && startIndex !== undefined) { - let itemIndex = 0; - for ( - let rowIndex = startIndex; - rowIndex < startIndex + items.length; - rowIndex += 1 - ) { - itemData[rowIndex] = items[itemIndex]; - itemIndex += 1; - } - } - } - - return itemData; - }, [filter, queryClient, server?.id]); - - const fetch = useCallback( - async ({ skip, take }: { skip: number; take: number }) => { - if (!server) { - return []; - } - - const query: PlaylistListQuery = { - limit: take, - ...filter, - _custom: {}, - startIndex: skip, - }; - - const queryKey = queryKeys.playlists.list(server?.id || '', query); - - const playlists = await queryClient.fetchQuery({ - queryFn: async ({ signal }) => - controller.getPlaylistList({ - apiClientProps: { - serverId: server?.id || '', - signal, - }, - query, - }), - queryKey, - }); - - return playlists; - }, - [filter, queryClient, server], - ); - - return ( - - - {({ height, width }: Size) => ( - - )} - - - ); -}; diff --git a/src/renderer/features/playlists/components/playlist-list-table-view.tsx b/src/renderer/features/playlists/components/playlist-list-table-view.tsx deleted file mode 100644 index 82468e49d..000000000 --- a/src/renderer/features/playlists/components/playlist-list-table-view.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact'; - -import { RowDoubleClickedEvent } from '@ag-grid-community/core'; -import { MutableRefObject } from 'react'; -import { generatePath, useNavigate } from 'react-router'; - -import { VirtualGridAutoSizerContainer } from '/@/renderer/components/virtual-grid/virtual-grid-wrapper'; -import { VirtualTable } from '/@/renderer/components/virtual-table'; -import { useVirtualTable } from '/@/renderer/components/virtual-table/hooks/use-virtual-table'; -import { PLAYLIST_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/context-menu-items'; -import { AppRoute } from '/@/renderer/router/routes'; -import { useCurrentServer } from '/@/renderer/store'; -import { LibraryItem } from '/@/shared/types/domain-types'; - -interface PlaylistListTableViewProps { - itemCount?: number; - tableRef: MutableRefObject; -} - -export const PlaylistListTableView = ({ itemCount, tableRef }: PlaylistListTableViewProps) => { - const navigate = useNavigate(); - const server = useCurrentServer(); - const pageKey = 'playlist'; - - const handleRowDoubleClick = (e: RowDoubleClickedEvent) => { - if (!e.data) return; - navigate(generatePath(AppRoute.PLAYLISTS_DETAIL_SONGS, { playlistId: e.data.id })); - }; - - const tableProps = useVirtualTable({ - contextMenu: PLAYLIST_CONTEXT_MENU_ITEMS, - itemCount, - itemType: LibraryItem.PLAYLIST, - pageKey, - server, - tableRef, - }); - - return ( - - - - ); -}; diff --git a/src/renderer/features/playlists/routes/playlist-detail-song-list-route.tsx b/src/renderer/features/playlists/routes/playlist-detail-song-list-route.tsx index 625add5d6..b2f3b25f1 100644 --- a/src/renderer/features/playlists/routes/playlist-detail-song-list-route.tsx +++ b/src/renderer/features/playlists/routes/playlist-detail-song-list-route.tsx @@ -9,7 +9,6 @@ import { generatePath, useNavigate, useParams } from 'react-router'; import { useHandlePlayQueueAdd } from '/@/renderer/features/player/hooks/use-handle-playqueue-add'; import { playlistsQueries } from '/@/renderer/features/playlists/api/playlists-api'; -import { PlaylistDetailSongListContent } from '/@/renderer/features/playlists/components/playlist-detail-song-list-content'; import { PlaylistDetailSongListHeader } from '/@/renderer/features/playlists/components/playlist-detail-song-list-header'; import { PlaylistQueryBuilder } from '/@/renderer/features/playlists/components/playlist-query-builder'; import { SaveAsPlaylistForm } from '/@/renderer/features/playlists/components/save-as-playlist-form'; @@ -228,7 +227,7 @@ const PlaylistDetailSongListRoute = () => { )} - + {/* */} ); }; diff --git a/src/renderer/features/playlists/routes/playlist-list-route.tsx b/src/renderer/features/playlists/routes/playlist-list-route.tsx index 312310f5d..7a02b414f 100644 --- a/src/renderer/features/playlists/routes/playlist-list-route.tsx +++ b/src/renderer/features/playlists/routes/playlist-list-route.tsx @@ -4,7 +4,6 @@ import { useQuery } from '@tanstack/react-query'; import { useMemo, useRef } from 'react'; import { useParams } from 'react-router'; -import { VirtualInfiniteGridRef } from '/@/renderer/components/virtual-grid/virtual-infinite-grid'; import { ListContext } from '/@/renderer/context/list-context'; import { playlistsQueries } from '/@/renderer/features/playlists/api/playlists-api'; import { PlaylistListContent } from '/@/renderer/features/playlists/components/playlist-list-content'; @@ -14,7 +13,7 @@ import { useCurrentServer, useListStoreByKey } from '/@/renderer/store'; import { PlaylistListSort, PlaylistSongListQuery, SortOrder } from '/@/shared/types/domain-types'; const PlaylistListRoute = () => { - const gridRef = useRef(null); + const gridRef = useRef(null); const tableRef = useRef(null); const server = useCurrentServer(); const { playlistId } = useParams(); @@ -53,7 +52,7 @@ const PlaylistListRoute = () => { - + {/* */} ); diff --git a/src/renderer/features/search/components/search-content.tsx b/src/renderer/features/search/components/search-content.tsx index bcf731280..c26b1cb86 100644 --- a/src/renderer/features/search/components/search-content.tsx +++ b/src/renderer/features/search/components/search-content.tsx @@ -5,8 +5,6 @@ import { MutableRefObject } from 'react'; import { generatePath, useNavigate } from 'react-router'; import { useParams, useSearchParams } from 'react-router-dom'; -import { VirtualGridAutoSizerContainer } from '/@/renderer/components/virtual-grid/virtual-grid-wrapper'; -import { VirtualTable } from '/@/renderer/components/virtual-table'; import { useCurrentSongRowStyles } from '/@/renderer/components/virtual-table/hooks/use-current-song-row-styles'; import { useVirtualTable } from '/@/renderer/components/virtual-table/hooks/use-virtual-table'; import { @@ -91,22 +89,5 @@ export const SearchContent = ({ tableRef }: SearchContentProps) => { tableRef, }); - return ( - - data.data.id} - infiniteInitialRowCount={25} - key={`table-${itemType}-${tableProps.rowHeight}-${server?.id}`} - onRowDoubleClicked={handleRowDoubleClick} - ref={tableRef} - rowClassRules={rowClassRules} - shouldUpdateSong={itemType === LibraryItem.SONG} - /> - - ); + return null; }; diff --git a/src/renderer/features/similar-songs/components/similar-songs-list.tsx b/src/renderer/features/similar-songs/components/similar-songs-list.tsx index 596241271..cc49e1476 100644 --- a/src/renderer/features/similar-songs/components/similar-songs-list.tsx +++ b/src/renderer/features/similar-songs/components/similar-songs-list.tsx @@ -4,7 +4,6 @@ import { useQuery } from '@tanstack/react-query'; import { useMemo, useRef } from 'react'; import { ErrorBoundary } from 'react-error-boundary'; -import { VirtualGridAutoSizerContainer } from '/@/renderer/components/virtual-grid/virtual-grid-wrapper'; import { getColumnDefs, VirtualTable } from '/@/renderer/components/virtual-table'; import { ErrorFallback } from '/@/renderer/features/action-required/components/error-fallback'; import { SONG_CONTEXT_MENU_ITEMS } from '/@/renderer/features/context-menu/context-menu-items'; @@ -59,27 +58,25 @@ export const SimilarSongsList = ({ count, fullScreen, song }: SimilarSongsListPr ) : ( - - data.data.uniqueId} - onCellContextMenu={onCellContextMenu} - onCellDoubleClicked={handleRowDoubleClick} - ref={tableRef} - rowBuffer={50} - rowData={songQuery.data ?? []} - rowHeight={tableConfig.rowHeight || 40} - shouldUpdateSong - /> - + data.data.uniqueId} + onCellContextMenu={onCellContextMenu} + onCellDoubleClicked={handleRowDoubleClick} + ref={tableRef} + rowBuffer={50} + rowData={songQuery.data ?? []} + rowHeight={tableConfig.rowHeight || 40} + shouldUpdateSong + /> ); }; diff --git a/src/renderer/store/album-artist.store.ts b/src/renderer/store/album-artist.store.ts index 6627988ab..5d4dbe64c 100644 --- a/src/renderer/store/album-artist.store.ts +++ b/src/renderer/store/album-artist.store.ts @@ -5,7 +5,7 @@ import { createWithEqualityFn } from 'zustand/traditional'; import { DataTableProps } from '/@/renderer/store/settings.store'; import { mergeOverridingColumns } from '/@/renderer/store/utils'; import { AlbumArtistListArgs, AlbumArtistListSort, SortOrder } from '/@/shared/types/domain-types'; -import { ListDisplayType, TableColumn, TablePagination } from '/@/shared/types/types'; +import { ListDisplayType, ListPagination, TableColumn } from '/@/shared/types/types'; export type AlbumArtistListFilter = Omit; @@ -33,7 +33,7 @@ type ListProps = { }; type TableProps = DataTableProps & { - pagination: TablePagination; + pagination: ListPagination; scrollOffset: number; }; diff --git a/src/renderer/store/list.store.ts b/src/renderer/store/list.store.ts index a6c75aa9d..298bfecfc 100644 --- a/src/renderer/store/list.store.ts +++ b/src/renderer/store/list.store.ts @@ -3,7 +3,9 @@ import { immer } from 'zustand/middleware/immer'; import { shallow } from 'zustand/shallow'; import { createWithEqualityFn } from 'zustand/traditional'; -import { DataTableProps, PersistedTableColumn } from '/@/renderer/store/settings.store'; +import { ItemTableListColumnConfig } from '/@/renderer/components/item-list/types'; +import { ListDeterministicArgs } from '/@/renderer/store'; +import { DataTableProps } from '/@/renderer/store/settings.store'; import { mergeOverridingColumns } from '/@/renderer/store/utils'; import { AlbumArtistListArgs, @@ -20,7 +22,7 @@ import { SongListSort, SortOrder, } from '/@/shared/types/domain-types'; -import { ListDisplayType, TableColumn, TablePagination } from '/@/shared/types/types'; +import { ListDisplayType, ListPagination, TableColumn } from '/@/shared/types/types'; export const generatePageKey = (page: string, id?: string) => { return id ? `${page}_${id}` : page; @@ -35,13 +37,14 @@ export type ListGridProps = { itemGap?: number; itemSize?: number; itemsPerRow?: number; - scrollOffset?: number; }; export type ListItemProps = { display: ListDisplayType; filter: TFilter; - grid?: ListGridProps; + grid: ListGridProps; + itemsPerPage: number; + pagination: ListPagination; table: ListTableProps; }; @@ -67,9 +70,11 @@ export interface ListSlice extends ListState { setGrid: (args: ListDeterministicArgs & { data: Partial }) => void; setStore: (data: Partial) => void; setTable: (args: ListDeterministicArgs & { data: Partial }) => void; - setTableColumns: (args: ListDeterministicArgs & { data: PersistedTableColumn[] }) => void; + setTableColumns: ( + args: ListDeterministicArgs & { data: ItemTableListColumnConfig[] }, + ) => void; setTablePagination: ( - args: ListDeterministicArgs & { data: Partial }, + args: ListDeterministicArgs & { data: Partial }, ) => void; }; } @@ -90,10 +95,7 @@ export interface ListState { }; } -export type ListTableProps = DataTableProps & { - pagination: TablePagination; - scrollOffset: number; -}; +export type ListTableProps = DataTableProps; export type PlaylistListFilter = Omit; diff --git a/src/renderer/store/playlist.store.ts b/src/renderer/store/playlist.store.ts index a5396152f..59001bf1f 100644 --- a/src/renderer/store/playlist.store.ts +++ b/src/renderer/store/playlist.store.ts @@ -6,7 +6,7 @@ import { PlaylistListFilter, SongListFilter } from '/@/renderer/store/list.store import { DataTableProps } from '/@/renderer/store/settings.store'; import { mergeOverridingColumns } from '/@/renderer/store/utils'; import { PlaylistListSort, SortOrder } from '/@/shared/types/domain-types'; -import { ListDisplayType, TableColumn, TablePagination } from '/@/shared/types/types'; +import { ListDisplayType, TableColumn, ListPagination } from '/@/shared/types/types'; export interface PlaylistSlice extends PlaylistState { actions: { @@ -17,11 +17,11 @@ export interface PlaylistSlice extends PlaylistState { setGrid: (args: { data: Partial }) => void; setStore: (data: Partial) => void; setTable: (data: Partial) => void; - setTablePagination: (args: { data: Partial }) => void; + setTablePagination: (args: { data: Partial }) => void; }; } -type DetailPaginationProps = TablePagination & { +type DetailPaginationProps = ListPagination & { scrollOffset: number; }; @@ -54,7 +54,7 @@ interface PlaylistState { } type TableProps = DataTableProps & { - pagination: TablePagination; + pagination: ListPagination; scrollOffset: number; };