'use client'; import { useState, useRef, useCallback, useMemo, memo, useEffect } from 'react'; import { VideoCard } from './VideoCard'; import { VideoGroupCard, GroupedVideo } from './VideoGroupCard'; import { settingsStore } from '@/lib/store/settings-store'; import { Video } from '@/lib/types'; interface VideoGridProps { videos: Video[]; className?: string; isPremium?: boolean; } export const VideoGrid = memo(function VideoGrid({ videos, className = '', isPremium = false }: VideoGridProps) { const [activeCardId, setActiveCardId] = useState(null); const [visibleCount, setVisibleCount] = useState(24); const [displayMode, setDisplayMode] = useState<'normal' | 'grouped'>('normal'); const gridRef = useRef(null); const observerRef = useRef(null); // Load display mode from settings useEffect(() => { const settings = settingsStore.getSettings(); setDisplayMode(settings.searchDisplayMode); const unsubscribe = settingsStore.subscribe(() => { const newSettings = settingsStore.getSettings(); setDisplayMode(newSettings.searchDisplayMode); }); return () => unsubscribe(); }, []); if (videos.length === 0) { return null; } // Group videos by name when in grouped mode const groupedVideos = useMemo(() => { if (displayMode !== 'grouped') return []; const groups = new Map(); videos.forEach(video => { const name = video.vod_name.toLowerCase().trim(); if (!groups.has(name)) { groups.set(name, []); } groups.get(name)!.push(video); }); return Array.from(groups.entries()).map(([, groupVideos]) => { // Sort by latency (lowest first) const sorted = [...groupVideos].sort((a, b) => { if (a.latency === undefined) return 1; if (b.latency === undefined) return -1; return a.latency - b.latency; }); return { representative: sorted[0], videos: sorted, name: sorted[0].vod_name, }; }); }, [videos, displayMode]); // Callback ref for the load more trigger const loadMoreRef = useCallback((node: HTMLDivElement | null) => { if (observerRef.current) observerRef.current.disconnect(); if (node) { observerRef.current = new IntersectionObserver(entries => { if (entries[0].isIntersecting) { setVisibleCount(prev => prev + 24); } }, { rootMargin: '400px' }); observerRef.current.observe(node); } }, []); // Memoize the click handler const handleCardClick = useCallback((e: React.MouseEvent, videoId: string, videoUrl: string) => { const isMobile = window.innerWidth < 1024; if (isMobile) { if (activeCardId === videoId) { window.location.href = videoUrl; } else { e.preventDefault(); setActiveCardId(videoId); } } }, [activeCardId]); // Normal mode items const videoItems = useMemo(() => { if (displayMode === 'grouped') return []; return videos.map((video, index) => { const params: Record = { id: String(video.vod_id), source: video.source, title: video.vod_name, }; if (isPremium) { params.premium = '1'; } const videoUrl = `/player?${new URLSearchParams(params).toString()}`; const cardId = `${video.vod_id}-${index}`; return { video, videoUrl, cardId }; }); }, [videos, displayMode, isPremium]); // Grouped mode items const groupItems = useMemo(() => { if (displayMode !== 'grouped') return []; return groupedVideos.map((group, index) => ({ group, cardId: `group-${group.representative.vod_id}-${index}`, })); }, [groupedVideos, displayMode]); const totalItems = displayMode === 'grouped' ? groupItems.length : videoItems.length; return ( <>
{displayMode === 'grouped' ? ( // Grouped mode groupItems.slice(0, visibleCount).map(({ group, cardId }) => { const isActive = activeCardId === cardId; return ( ); }) ) : ( // Normal mode videoItems.slice(0, visibleCount).map(({ video, videoUrl, cardId }) => { const isActive = activeCardId === cardId; return ( ); }) )}
{/* Load more trigger */} {visibleCount < totalItems && (