import clsx from 'clsx'; import { forwardRef, ReactElement, useEffect, useMemo, useRef, useState } from 'react'; import styles from './play-queue.module.css'; import { useItemListColumnReorder } from '/@/renderer/components/item-list/helpers/use-item-list-column-reorder'; import { useItemListColumnResize } from '/@/renderer/components/item-list/helpers/use-item-list-column-resize'; import { ItemTableList, TableGroupHeader, } 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 { ItemListHandle } from '/@/renderer/components/item-list/types'; import { useIsPlayerFetching, usePlayer } from '/@/renderer/features/player/context/player-context'; import { searchLibraryItems } from '/@/renderer/features/shared/utils'; import { useDragDrop } from '/@/renderer/hooks/use-drag-drop'; import { subscribeCurrentTrack, subscribePlayerQueue, useListSettings, usePlayerActions, usePlayerQueueType, usePlayerSong, } from '/@/renderer/store'; import { Flex } from '/@/shared/components/flex/flex'; import { LoadingOverlay } from '/@/shared/components/loading-overlay/loading-overlay'; import { Text } from '/@/shared/components/text/text'; import { useDebouncedValue } from '/@/shared/hooks/use-debounced-value'; import { useFocusWithin } from '/@/shared/hooks/use-focus-within'; import { useHotkeys } from '/@/shared/hooks/use-hotkeys'; import { useMergedRef } from '/@/shared/hooks/use-merged-ref'; import { LibraryItem, QueueSong, Song } from '/@/shared/types/domain-types'; import { DragTarget } from '/@/shared/types/drag-and-drop'; import { ItemListKey, Play, PlayerQueueType } from '/@/shared/types/types'; type QueueProps = { listKey: ItemListKey; searchTerm: string | undefined; }; export const PlayQueue = forwardRef(({ listKey, searchTerm }, ref) => { const { table } = useListSettings(listKey) || {}; const isFetching = useIsPlayerFetching(); const tableRef = useRef(null); const previousSongCountRef = useRef(0); const mergedRef = useMergedRef(ref, tableRef); const { getQueue } = usePlayerActions(); const queueType = usePlayerQueueType(); const [debouncedSearchTerm] = useDebouncedValue(searchTerm, 200); const [data, setData] = useState([]); const [groups, setGroups] = useState([]); const [containerKey, setContainerKey] = useState(() => Math.random().toString(36)); useEffect(() => { const setQueue = () => { const queue = getQueue() || { groups: [], items: [] }; setData(queue.items); if (queueType === PlayerQueueType.PRIORITY && queue.groups && queue.groups.length > 0) { const transformedGroups: TableGroupHeader[] = queue.groups.map((group) => ({ itemCount: group.count, render: (): ReactElement => { return (
{group.name}
); }, rowHeight: 40, })); setGroups(transformedGroups); } else { setGroups([]); } }; const unsub = subscribePlayerQueue(() => { setQueue(); }); const unsubCurrentTrack = subscribeCurrentTrack((e) => { if (e.index !== -1) { tableRef.current?.scrollToIndex(e.index, { align: 'top', behavior: 'smooth', }); } }); setQueue(); return () => { unsub(); unsubCurrentTrack(); }; }, [getQueue, queueType, tableRef]); useEffect(() => { const currentCount = data.length; const previousCount = previousSongCountRef.current; if (previousCount === 0 && currentCount > 0) { setContainerKey(Math.random().toString(36)); } previousSongCountRef.current = currentCount; }, [data.length]); const filteredData: QueueSong[] = useMemo(() => { if (debouncedSearchTerm) { const searched = searchLibraryItems(data, debouncedSearchTerm, LibraryItem.SONG); return searched; } return data; }, [data, debouncedSearchTerm]); const isEmpty = filteredData.length === 0; const { handleColumnReordered } = useItemListColumnReorder({ itemListKey: listKey, }); const { handleColumnResized } = useItemListColumnResize({ itemListKey: listKey, }); const currentSong = usePlayerSong(); const currentSongUniqueId = currentSong?._uniqueId; const { focused, ref: containerFocusRef } = useFocusWithin(); const player = usePlayer(); useHotkeys([ [ 'delete', () => { if (focused) { const selectedItems = tableRef.current?.internalState.getSelected() as QueueSong[]; if (!selectedItems || selectedItems.length === 0) { return; } player.clearSelected(selectedItems); } }, ], ]); return (
0 ? groups : undefined} initialTop={{ to: 0, type: 'offset', }} itemType={LibraryItem.QUEUE_SONG} onColumnReordered={handleColumnReordered} onColumnResized={handleColumnResized} ref={mergedRef} size={table.size} /> {isEmpty && }
); }); const EmptyQueueDropZone = () => { const playerContext = usePlayer(); const { isDraggedOver, ref } = useDragDrop({ drop: { canDrop: () => { return true; }, getData: () => { return { id: [], item: [], itemType: LibraryItem.QUEUE_SONG, type: DragTarget.QUEUE_SONG, }; }, onDrag: () => { return; }, onDragLeave: () => { return; }, onDrop: (args) => { if (args.self.type === DragTarget.QUEUE_SONG) { const sourceServerId = ( args.source.item?.[0] as unknown as { _serverId: string } )?._serverId; const sourceItemType = args.source.itemType as LibraryItem; switch (args.source.type) { case DragTarget.ALBUM: { if (sourceServerId) { playerContext.addToQueueByFetch( sourceServerId, args.source.id, sourceItemType, Play.NOW, ); } break; } case DragTarget.ALBUM_ARTIST: { if (sourceServerId) { playerContext.addToQueueByFetch( sourceServerId, args.source.id, sourceItemType, Play.NOW, ); } break; } case DragTarget.ARTIST: { if (sourceServerId) { playerContext.addToQueueByFetch( sourceServerId, args.source.id, sourceItemType, Play.NOW, ); } break; } case DragTarget.GENRE: { if (sourceServerId) { playerContext.addToQueueByFetch( sourceServerId, args.source.id, sourceItemType, Play.NOW, ); } break; } case DragTarget.PLAYLIST: { if (sourceServerId) { playerContext.addToQueueByFetch( sourceServerId, args.source.id, sourceItemType, Play.NOW, ); } break; } case DragTarget.QUEUE_SONG: { const sourceItems = (args.source.item || []) as QueueSong[]; if (sourceItems.length > 0) { playerContext.addToQueueByData(sourceItems, Play.NOW); } break; } case DragTarget.SONG: { const sourceItems = (args.source.item || []) as Song[]; if (sourceItems.length > 0) { playerContext.addToQueueByData(sourceItems, Play.NOW); } break; } default: { break; } } } return; }, }, isEnabled: true, }); return ( ); };