import { useQuery } from '@tanstack/react-query'; import isElectron from 'is-electron'; import { lazy, Suspense, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; // import { Group, Panel, Separator, useDefaultLayout } from 'react-resizable-panels'; import { Pane, SplitPane, usePersistence } from 'react-split-pane'; import styles from './sidebar-play-queue.module.css'; import { ItemListHandle } from '/@/renderer/components/item-list/types'; import { lyricsQueries } from '/@/renderer/features/lyrics/api/lyrics-api'; import { Lyrics } from '/@/renderer/features/lyrics/lyrics'; import { PlayQueue } from '/@/renderer/features/now-playing/components/play-queue'; import { PlayQueueListControls } from '/@/renderer/features/now-playing/components/play-queue-list-controls'; import { useCombinedLyricsAndVisualizer, useFullScreenPlayerStore, usePlaybackSettings, usePlayerSong, useSettingsStore, useSettingsStoreActions, useShowLyricsInSidebar, useShowVisualizerInSidebar, useSidebarPanelOrder, useWindowSettings, } from '/@/renderer/store'; import { ActionIcon, ActionIconGroup } from '/@/shared/components/action-icon/action-icon'; import { Flex } from '/@/shared/components/flex/flex'; import { Stack } from '/@/shared/components/stack/stack'; import { ItemListKey, Platform } from '/@/shared/types/types'; type SidebarPanelType = 'lyrics' | 'queue' | 'visualizer'; const AudioMotionAnalyzerVisualizer = lazy(() => import('../../visualizer/components/audiomotionanalyzer/visualizer').then((module) => ({ default: module.Visualizer, })), ); const ButterchurnVisualizer = lazy(() => import('../../visualizer/components/butternchurn/visualizer').then((module) => ({ default: module.Visualizer, })), ); export const SidebarPlayQueue = () => { const tableRef = useRef(null); const [search, setSearch] = useState(undefined); const { expanded: isFullScreenPlayerExpanded, visualizerExpanded: isFullScreenVisualizerExpanded, } = useFullScreenPlayerStore(); const [shouldRender, setShouldRender] = useState(!isFullScreenPlayerExpanded); const combinedLyricsAndVisualizer = useCombinedLyricsAndVisualizer(); const showLyricsInSidebar = useShowLyricsInSidebar(); const showVisualizerInSidebar = useShowVisualizerInSidebar(); const sidebarPanelOrder = useSidebarPanelOrder(); const { webAudio } = usePlaybackSettings(); const { windowBarStyle } = useWindowSettings(); const showVisualizer = showVisualizerInSidebar && webAudio; const showPanel = showLyricsInSidebar || showVisualizer; const shouldAddTopMargin = isElectron() && windowBarStyle === Platform.WEB; useEffect(() => { if (isFullScreenPlayerExpanded || isFullScreenVisualizerExpanded) { // Immediately hide when fullscreen player opens setShouldRender(false); return undefined; } else { // Wait 500ms before re-rendering when fullscreen player closes to avoid performance issues const timeoutId = setTimeout(() => { setShouldRender(true); }, 500); return () => { clearTimeout(timeoutId); }; } }, [isFullScreenPlayerExpanded, isFullScreenVisualizerExpanded]); const [defaultLayout, onLayoutChange] = usePersistence({ debounce: 300, key: 'sidebar-play-queue-container', storage: localStorage, }); // Filter and order panels based on what's enabled const orderedPanels = useMemo(() => { if (combinedLyricsAndVisualizer) { // When combined, use the order from settings but filter to only show queue and lyrics (combined) const visiblePanels = sidebarPanelOrder.filter((panel) => { if (panel === 'queue') return true; if (panel === 'lyrics') return showLyricsInSidebar || showVisualizer; return false; }); return visiblePanels; } const visiblePanels = sidebarPanelOrder.filter((panel) => { if (panel === 'queue') return true; if (panel === 'lyrics') return showLyricsInSidebar; if (panel === 'visualizer') return showVisualizer; return false; }); return visiblePanels; }, [combinedLyricsAndVisualizer, showLyricsInSidebar, showVisualizer, sidebarPanelOrder]); const renderPanel = (panelType: SidebarPanelType) => { if (panelType === 'queue') { return (
); } if (combinedLyricsAndVisualizer && (panelType === 'lyrics' || panelType === 'visualizer')) { return ; } if (panelType === 'lyrics') { return ; } if (panelType === 'visualizer') { return ; } return null; }; const getPanelSize = useCallback( (panelType: SidebarPanelType, index: number) => { // Queue panel should always autofit if (panelType === 'queue') { return undefined; } // If defaultLayout exists and has saved sizes, use them if ( defaultLayout && Array.isArray(defaultLayout) && defaultLayout[index] !== undefined ) { return defaultLayout[index]; } // Calculate default sizes for non-queue panels based on order const nonQueuePanels = orderedPanels.filter((p) => p !== 'queue'); const nonQueueCount = nonQueuePanels.length; if (nonQueueCount === 0) { return undefined; } // If only one non-queue panel, give it a default size if (nonQueueCount === 1) { return 100; } // If multiple non-queue panels, distribute sizes evenly // First non-queue panel gets a size, others get undefined to share remaining const nonQueueIndex = orderedPanels.slice(0, index).filter((p) => p !== 'queue').length; if (nonQueueIndex === 0) { // First non-queue panel gets a default size return 100; } // Other non-queue panels autofit return undefined; }, [defaultLayout, orderedPanels], ); // Unmount when fullscreen player is open if (!shouldRender) { return null; } return ( {shouldAddTopMargin &&
} {showPanel ? ( {orderedPanels.map((panel, index) => ( {renderPanel(panel)} ))} ) : (
)} ); }; const PanelReorderControls = ({ panelType }: { panelType: 'lyrics' | 'visualizer' }) => { const { t } = useTranslation(); const { setSettings } = useSettingsStoreActions(); const sidebarPanelOrder = useSidebarPanelOrder(); const combinedLyricsAndVisualizer = useCombinedLyricsAndVisualizer(); const currentIndex = sidebarPanelOrder.indexOf(panelType); const canMoveUp = currentIndex > 0; const canMoveDown = currentIndex < sidebarPanelOrder.length - 1; const handleMoveUp = useCallback(() => { if (!canMoveUp) return; const newOrder = [...sidebarPanelOrder]; const targetIndex = currentIndex - 1; [newOrder[currentIndex], newOrder[targetIndex]] = [ newOrder[targetIndex], newOrder[currentIndex], ]; setSettings({ general: { sidebarPanelOrder: newOrder, }, }); }, [canMoveUp, currentIndex, sidebarPanelOrder, setSettings]); const handleMoveDown = useCallback(() => { if (!canMoveDown) return; const newOrder = [...sidebarPanelOrder]; [newOrder[currentIndex], newOrder[currentIndex + 1]] = [ newOrder[currentIndex + 1], newOrder[currentIndex], ]; setSettings({ general: { sidebarPanelOrder: newOrder, }, }); }, [canMoveDown, currentIndex, sidebarPanelOrder, setSettings]); const handleClose = useCallback(() => { if (combinedLyricsAndVisualizer && panelType === 'lyrics') { setSettings({ general: { showLyricsInSidebar: false, showVisualizerInSidebar: false, }, }); } else if (panelType === 'lyrics') { setSettings({ general: { showLyricsInSidebar: false, }, }); } else if (panelType === 'visualizer') { setSettings({ general: { showVisualizerInSidebar: false, }, }); } }, [combinedLyricsAndVisualizer, panelType, setSettings]); return (
); }; const LyricsPanel = () => { return (
); }; const VisualizerPanel = () => { const visualizerType = useSettingsStore((store) => store.visualizer.type); return (
}> {visualizerType === 'butterchurn' ? ( ) : ( )}
); }; const CombinedLyricsAndVisualizerPanel = () => { const currentSong = usePlayerSong(); const visualizerType = useSettingsStore((store) => store.visualizer.type); const showLyricsInSidebar = useShowLyricsInSidebar(); const showVisualizerInSidebar = useShowVisualizerInSidebar(); const { webAudio } = usePlaybackSettings(); const showVisualizer = showVisualizerInSidebar && webAudio; const { data: lyricsData } = useQuery( lyricsQueries.songLyrics( { options: { enabled: !!currentSong?.id && showLyricsInSidebar, }, query: { songId: currentSong?.id || '' }, serverId: currentSong?._serverId || '', }, currentSong, ), ); const hasLyrics = useMemo(() => { if (!lyricsData) return false; if (Array.isArray(lyricsData)) { return lyricsData.length > 0 && !!lyricsData[0]?.lyrics; } const lyrics = lyricsData.selected?.lyrics; if (Array.isArray(lyrics)) { return lyrics.length > 0; } if (typeof lyrics === 'string') { return lyrics.trim().length > 0; } return false; }, [lyricsData]); return (
{showLyricsInSidebar && } {showVisualizer && (
}> {visualizerType === 'butterchurn' ? ( ) : ( )}
)}
); };