diff --git a/src/renderer/features/now-playing/components/drawer-play-queue.tsx b/src/renderer/features/now-playing/components/drawer-play-queue.tsx index 800ca043d..84a54f6d3 100644 --- a/src/renderer/features/now-playing/components/drawer-play-queue.tsx +++ b/src/renderer/features/now-playing/components/drawer-play-queue.tsx @@ -27,7 +27,7 @@ export const DrawerPlayQueue = () => { /> - + ); 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 4893a7810..d39a1d27c 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 @@ -1,21 +1,18 @@ import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact'; -import isElectron from 'is-electron'; -import { type MutableRefObject, useCallback } from 'react'; +import { type MutableRefObject } from 'react'; import { useTranslation } from 'react-i18next'; -import { TableConfigDropdown } from '/@/renderer/components/virtual-table'; +import { SONG_TABLE_COLUMNS } from '/@/renderer/components/virtual-table'; +import { usePlayerContext } from '/@/renderer/features/player/context/player-context'; import { updateSong } from '/@/renderer/features/player/update-remote-song'; -import { SearchInput } from '/@/renderer/features/shared/components/search-input'; +import { ListConfigMenu } from '/@/renderer/features/shared/components/list-config-menu'; import { usePlaybackType } from '/@/renderer/store/settings.store'; 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 { ItemListKey } from '/@/shared/types/types'; -const mpvPlayer = isElectron() ? window.api.mpvPlayer : null; - interface PlayQueueListOptionsProps { handleSearch: (value: string) => void; searchTerm?: string; @@ -41,54 +38,49 @@ export const PlayQueueListControls = ({ // const { pause } = usePlayerControls(); + const player = usePlayerContext(); + const playbackType = usePlaybackType(); // const setCurrentTime = useSetCurrentTime(); const handleMoveToNext = () => { - const selectedRows = tableRef?.current?.grid.api.getSelectedRows(); - const uniqueIds = selectedRows?.map((row) => row.uniqueId); - if (!uniqueIds?.length) return; - - // const playerData = moveToNextOfQueue(uniqueIds); - - // if (playbackType === PlaybackType.LOCAL) { - // setQueueNext(playerData); - // } + // const selectedRows = tableRef?.current?.grid.api.getSelectedRows(); + // const uniqueIds = selectedRows?.map((row) => row.uniqueId); + // if (!uniqueIds?.length) return; + // // const playerData = moveToNextOfQueue(uniqueIds); + // // if (playbackType === PlaybackType.LOCAL) { + // // setQueueNext(playerData); + // // } + // player.moveSelectedToNext(selectedRows); }; const handleMoveToBottom = () => { - const selectedRows = tableRef?.current?.grid.api.getSelectedRows(); - const uniqueIds = selectedRows?.map((row) => row.uniqueId); - if (!uniqueIds?.length) return; - + // const selectedRows = tableRef?.current?.grid.api.getSelectedRows(); + // const uniqueIds = selectedRows?.map((row) => row.uniqueId); + // if (!uniqueIds?.length) return; // const playerData = moveToBottomOfQueue(uniqueIds); - // if (playbackType === PlaybackType.LOCAL) { // setQueueNext(playerData); // } }; const handleMoveToTop = () => { - const selectedRows = tableRef?.current?.grid.api.getSelectedRows(); - const uniqueIds = selectedRows?.map((row) => row.uniqueId); - if (!uniqueIds?.length) return; - + // const selectedRows = tableRef?.current?.grid.api.getSelectedRows(); + // const uniqueIds = selectedRows?.map((row) => row.uniqueId); + // if (!uniqueIds?.length) return; // const playerData = moveToTopOfQueue(uniqueIds); - // if (playbackType === PlaybackType.LOCAL) { // setQueueNext(playerData); // } }; const handleRemoveSelected = () => { - const selectedRows = tableRef?.current?.grid.api.getSelectedRows(); - const uniqueIds = selectedRows?.map((row) => row.uniqueId); - if (!uniqueIds?.length) return; - + // const selectedRows = tableRef?.current?.grid.api.getSelectedRows(); + // const uniqueIds = selectedRows?.map((row) => row.uniqueId); + // if (!uniqueIds?.length) return; // const currentSong = usePlayerStore.getState().current.song; // const playerData = removeFromQueue(uniqueIds); // const isCurrentSongRemoved = currentSong && uniqueIds.includes(currentSong.uniqueId); - // if (playbackType === PlaybackType.LOCAL) { // if (isCurrentSongRemoved) { // setQueue(playerData); @@ -96,7 +88,6 @@ export const PlayQueueListControls = ({ // setQueueNext(playerData); // } // } - // if (isCurrentSongRemoved) { // updateSong(playerData.current.song); // } @@ -123,25 +114,9 @@ export const PlayQueueListControls = ({ // } }; - const handleSearchTerm = useCallback( - (term: string) => { - handleSearch(term); - tableRef.current?.grid.api.redrawRows(); - }, - [handleSearch, tableRef], - ); - - const hasSearch = !!searchTerm; - return ( - - + + - handleSearchTerm(e.target.value)} - value={searchTerm} - /> - - - - - - - - + ); diff --git a/src/renderer/features/now-playing/components/play-queue.tsx b/src/renderer/features/now-playing/components/play-queue.tsx index 880100031..b8bcf9ca6 100644 --- a/src/renderer/features/now-playing/components/play-queue.tsx +++ b/src/renderer/features/now-playing/components/play-queue.tsx @@ -1,51 +1,25 @@ -import type { RowClassRules, RowDragEvent, RowNode } from '@ag-grid-community/core'; -import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact'; import type { Ref } from 'react'; -import '@ag-grid-community/styles/ag-theme-alpine.css'; -import { useMergedRef } from '@mantine/hooks'; -import debounce from 'lodash/debounce'; -import { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'; -import { ErrorBoundary } from 'react-error-boundary'; +import { forwardRef, useMemo } from 'react'; -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'; -import { useHandleTableContextMenu } from '/@/renderer/features/context-menu/hooks/use-handle-context-menu'; -import { useAppFocus } from '/@/renderer/hooks'; -import { - useAppStoreActions, - usePlayerQueue, - usePlayerSong, - usePlayerStatus, -} from '/@/renderer/store'; -import { - useSettingsStore, - useSettingsStoreActions, - useTableSettings, -} from '/@/renderer/store/settings.store'; +import { ItemTableList } from '/@/renderer/components/item-list/item-table-list/item-table-list'; +import { ItemTableListColumn } from '/@/renderer/components/item-list/item-table-list/item-table-list-column'; +import { useListSettings, usePlayerQueue } from '/@/renderer/store'; import { searchSongs } from '/@/renderer/utils/search-songs'; import { LibraryItem, QueueSong } from '/@/shared/types/domain-types'; import { ItemListKey } from '/@/shared/types/types'; type QueueProps = { - type: ItemListKey; + listKey: ItemListKey; + searchTerm: string | undefined; }; -export const PlayQueue = forwardRef(({ searchTerm, type }: QueueProps, ref: Ref) => { - const tableRef = useRef(null); - const mergedRef = useMergedRef(ref, tableRef); - const queue = usePlayerQueue(); - const currentSong = usePlayerSong(); - const status = usePlayerStatus(); - const { setSettings } = useSettingsStoreActions(); - const { setAppStore } = useAppStoreActions(); - const tableConfig = useTableSettings(type); - const [gridApi, setGridApi] = useState(); - const isFocused = useAppFocus(); - const isFocusedRef = useRef(isFocused); +export const PlayQueue = forwardRef(({ listKey, searchTerm }: QueueProps, ref: Ref) => { + const { table } = useListSettings(listKey); - const songs = useMemo(() => { + const queue = usePlayerQueue(); + + const data: QueueSong[] = useMemo(() => { if (searchTerm) { return searchSongs(queue, searchTerm); } @@ -53,230 +27,21 @@ export const PlayQueue = forwardRef(({ searchTerm, type }: QueueProps, ref: Ref< return queue; }, [queue, searchTerm]); - useEffect(() => { - if (tableRef.current) { - setGridApi(tableRef.current); - } - }, []); - - useImperativeHandle(ref, () => ({ - get grid() { - return gridApi; - }, - })); - - const columnDefs = useMemo( - () => getColumnDefs(tableConfig.columns, false, 'generic'), - [tableConfig.columns], - ); - - // const handleDoubleClick = (e: CellDoubleClickedEvent) => { - // const playerData = setCurrentTrack(e.data.uniqueId); - // updateSong(playerData.current.song); - - // if (playbackType === PlaybackType.LOCAL) { - // mpvPlayer!.volume(volume); - // setQueue(playerData, false); - // } else { - // const player = - // playerData.current.player === 1 - // ? PlayersRef.current?.player1 - // : PlayersRef.current?.player2; - // const underlying = player?.getInternalPlayer(); - // if (underlying) { - // underlying.currentTime = 0; - // } - // } - - // play(); - // }; - - const handleDragStart = () => { - if (type === 'sideDrawerQueue') { - setAppStore({ isReorderingQueue: true }); - } - }; - - let timeout: any; - const handleDragEnd = (e: RowDragEvent) => { - if (!e.nodes.length) return; - const selectedUniqueIds = e.nodes - .map((node) => node.data?._uniqueId) - .filter((e) => e !== undefined); - - // const playerData = reorderQueue(selectedUniqueIds as string[], e.overNode?.data?.uniqueId); - - // if (playbackType === PlaybackType.LOCAL) { - // setQueueNext(playerData); - // } - - if (type === 'sideDrawerQueue') { - setAppStore({ isReorderingQueue: false }); - } - - const { api } = tableRef?.current || {}; - clearTimeout(timeout); - timeout = setTimeout(() => api?.redrawRows(), 250); - }; - - const handleGridReady = () => { - const { api } = tableRef?.current || {}; - - if (currentSong?._uniqueId) { - const currentNode = api?.getRowNode(currentSong?._uniqueId); - - if (!currentNode) return; - api?.ensureNodeVisible(currentNode, 'middle'); - } - }; - - const handleColumnChange = () => { - const { columnApi } = tableRef?.current || {}; - const columnsOrder = columnApi?.getAllGridColumns(); - if (!columnsOrder) return; - - const columnsInSettings = useSettingsStore.getState().lists[type].columns; - - const updatedColumns: PersistedTableColumn[] = []; - for (const column of columnsOrder) { - const columnInSettings = columnsInSettings.find( - (c) => c.column === column.getColDef().colId, - ); - - if (columnInSettings) { - updatedColumns.push({ - ...columnInSettings, - ...(!useSettingsStore.getState().lists[type].autoFit && { - width: column.getActualWidth(), - }), - }); - } - } - - setSettings({ - lists: { - ...useSettingsStore.getState().lists, - [type]: { - ...useSettingsStore.getState().lists[type], - columns: updatedColumns, - }, - }, - }); - }; - - const debouncedColumnChange = debounce(handleColumnChange, 250); - - const handleGridSizeChange = () => { - if (tableConfig.autoFit) { - tableRef?.current?.api?.sizeColumnsToFit(); - } - }; - - const rowClassRules = useMemo(() => { - return { - 'current-song': (params) => { - return params.data.uniqueId === currentSong?._uniqueId; - }, - }; - }, [currentSong?._uniqueId]); - - const previousSongRef = useRef(undefined); - - useEffect(() => { - if (currentSong) { - previousSongRef.current = currentSong; - } - }, [currentSong]); - - // Redraw the current song row when the previous song changes - useEffect(() => { - if (tableRef?.current) { - const { api, columnApi } = tableRef?.current || {}; - if (api == null || columnApi == null) { - return; - } - - const currentNode = currentSong?._uniqueId - ? api.getRowNode(currentSong._uniqueId) - : undefined; - const previousNode = previousSongRef.current?._uniqueId - ? api.getRowNode(previousSongRef.current?._uniqueId) - : undefined; - - const rowNodes = [currentNode, previousNode].filter( - (e) => e !== undefined, - ) as RowNode[]; - - if (rowNodes) { - api.redrawRows({ rowNodes }); - if (tableConfig.followCurrentSong) { - if (!currentNode) return; - api.ensureNodeVisible(currentNode, 'middle'); - } - } - } - }, [currentSong, previousSongRef, tableConfig.followCurrentSong, status]); - - // As a separate rule, update the current row when focus changes. This is - // to prevent queue scrolling when the application loses and then gains focus. - // The body should only fire when focus changes, even though it depends on current song - useEffect(() => { - if (isFocused !== isFocusedRef.current && tableRef?.current) { - const { api, columnApi } = tableRef.current; - if (api == null || columnApi == null) { - return; - } - - const currentNode = currentSong?._uniqueId - ? api.getRowNode(currentSong._uniqueId) - : undefined; - - if (currentNode) { - api.redrawRows({ rowNodes: [currentNode] }); - } - - isFocusedRef.current = isFocused; - } - }, [currentSong, isFocused]); - - const onCellContextMenu = useHandleTableContextMenu(LibraryItem.SONG, QUEUE_CONTEXT_MENU_ITEMS); - return ( - - - data.data.uniqueId} - onCellContextMenu={onCellContextMenu} - // onCellDoubleClicked={handleDoubleClick} - onColumnMoved={handleColumnChange} - onColumnResized={debouncedColumnChange} - onDragStarted={handleDragStart} - onGridReady={handleGridReady} - onGridSizeChanged={handleGridSizeChange} - onRowDragEnd={handleDragEnd} - ref={mergedRef} - rowBuffer={50} - rowClassRules={rowClassRules} - rowData={songs} - rowDragEntireRow - rowDragMultiRow - rowHeight={tableConfig.rowHeight || 40} - suppressCellFocus={type === 'fullScreen'} - /> - - + ); }); 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 ef7b03a24..8e6cd4144 100644 --- a/src/renderer/features/now-playing/components/sidebar-play-queue.tsx +++ b/src/renderer/features/now-playing/components/sidebar-play-queue.tsx @@ -1,17 +1,24 @@ -import type { AgGridReact as AgGridReactType } from '@ag-grid-community/react/lib/agGridReact'; - import { useRef, useState } from 'react'; -import { useWindowSettings } from '/@/renderer/store/settings.store'; -import { Song } from '/@/shared/types/domain-types'; -import { Platform } from '/@/shared/types/types'; +import { PlayQueue } from '/@/renderer/features/now-playing/components/play-queue'; +import { PlayQueueListControls } from '/@/renderer/features/now-playing/components/play-queue-list-controls'; +import { Flex } from '/@/shared/components/flex/flex'; +import { ItemListKey } from '/@/shared/types/types'; export const SidebarPlayQueue = () => { - const queueRef = useRef }>(null); + const tableRef = useRef(null); + // const queueRef = useRef }>(null); const [search, setSearch] = useState(undefined); - const { windowBarStyle } = useWindowSettings(); - const isWeb = windowBarStyle === Platform.WEB; - - return null; + return ( + + + + + ); }; diff --git a/src/renderer/features/player/components/full-screen-player-queue.tsx b/src/renderer/features/player/components/full-screen-player-queue.tsx index f06e24340..82f84f7d6 100644 --- a/src/renderer/features/player/components/full-screen-player-queue.tsx +++ b/src/renderer/features/player/components/full-screen-player-queue.tsx @@ -15,7 +15,7 @@ import { } from '/@/renderer/store/full-screen-player.store'; import { Button } from '/@/shared/components/button/button'; import { Group } from '/@/shared/components/group/group'; -import { PlayerType } from '/@/shared/types/types'; +import { ItemListKey, PlayerType } from '/@/shared/types/types'; const Visualizer = lazy(() => import('/@/renderer/features/player/components/visualizer').then((module) => ({ @@ -99,7 +99,7 @@ export const FullScreenPlayerQueue = () => { {activeTab === 'queue' ? (
- +
) : activeTab === 'related' ? (
diff --git a/src/renderer/layouts/default-layout/right-sidebar.module.css b/src/renderer/layouts/default-layout/right-sidebar.module.css index 153ef8f6e..9258643ef 100644 --- a/src/renderer/layouts/default-layout/right-sidebar.module.css +++ b/src/renderer/layouts/default-layout/right-sidebar.module.css @@ -1,7 +1,8 @@ .right-sidebar-container { position: relative; grid-area: right-sidebar; - height: 100%; + min-height: 0; + overflow: hidden; border-left: 1px solid alpha(var(--theme-colors-border), 0.3); .current-song-cell:not(.current-playlist-song-cell) svg { diff --git a/src/renderer/utils/search-songs.ts b/src/renderer/utils/search-songs.ts index 0ab83a7c8..e0c6a4692 100644 --- a/src/renderer/utils/search-songs.ts +++ b/src/renderer/utils/search-songs.ts @@ -1,8 +1,8 @@ import Fuse from 'fuse.js'; -import { Song } from '/@/shared/types/domain-types'; +import { QueueSong } from '/@/shared/types/domain-types'; -export const searchSongs = (songs: Song[], searchTerm: string) => { +export const searchSongs = (songs: QueueSong[], searchTerm: string) => { const fuse = new Fuse(songs, { fieldNormWeight: 1, ignoreLocation: true,