import { useEffect, useState } from "react"; import { X, Share2, Edit3, ChevronLeft, ChevronRight } from "lucide-react"; import { type Video } from "@shared/schema"; import { Button } from "@/components/ui/button"; import { apiRequest } from "@/lib/queryClient"; import { FacebookIcon, TwitterIcon, WhatsappIcon } from "react-share"; interface BunnyVideoModalProps { video: Video | null; isOpen: boolean; onClose: () => void; onEdit?: () => void; videos?: Video[]; onVideoChange?: (video: Video) => void; } function formatDuration(seconds: number): string { const minutes = Math.floor(seconds / 60); const remainingSeconds = seconds % 60; return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`; } function formatViews(views: number): string { if (views >= 1000000) { return `${(views / 1000000).toFixed(1)}M ogledov`; } else if (views >= 1000) { return `${(views / 1000).toFixed(1)}K ogledov`; } return `${views} ogledov`; } function formatDate(date: Date | string): string { const now = new Date(); const createdDate = typeof date === 'string' ? new Date(date) : date; if (!createdDate || isNaN(createdDate.getTime())) { return "Neznano"; } const diffTime = Math.abs(now.getTime() - createdDate.getTime()); const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24)); if (diffDays === 0) return "Danes"; if (diffDays === 1) return "Pred 1 dnem"; if (diffDays < 7) return `Pred ${diffDays} dnevi`; if (diffDays < 30) return `Pred ${Math.floor(diffDays / 7)} tednom${Math.floor(diffDays / 7) > 1 ? 'a' : ''}`; return `Pred ${Math.floor(diffDays / 30)} mesec${Math.floor(diffDays / 30) > 1 ? 'i' : 'em'}`; } export default function BunnyVideoModal({ video, isOpen, onClose, onEdit, videos = [], onVideoChange }: BunnyVideoModalProps) { const [showShareMenu, setShowShareMenu] = useState(false); const [isDragging, setIsDragging] = useState(false); const [dragStart, setDragStart] = useState({ x: 0, y: 0 }); const [dragOffset, setDragOffset] = useState(0); // Navigation functions const getCurrentVideoIndex = () => { if (!video || !videos.length) return -1; return videos.findIndex((v: Video) => v.id === video.id); }; const navigateToVideo = (direction: 'next' | 'prev') => { const currentIndex = getCurrentVideoIndex(); if (currentIndex === -1 || !onVideoChange) return; let newIndex; if (direction === 'next') { newIndex = currentIndex + 1 >= videos.length ? 0 : currentIndex + 1; } else { newIndex = currentIndex - 1 < 0 ? videos.length - 1 : currentIndex - 1; } const newVideo = videos[newIndex]; if (newVideo) { onVideoChange(newVideo); } }; // Touch and mouse drag handlers const handleDragStart = (clientX: number, clientY: number) => { setIsDragging(true); setDragStart({ x: clientX, y: clientY }); setDragOffset(0); }; const handleDragMove = (clientX: number, clientY: number) => { if (!isDragging) return; const deltaX = clientX - dragStart.x; const deltaY = Math.abs(clientY - dragStart.y); // Only allow horizontal drag if it's more horizontal than vertical if (deltaY < Math.abs(deltaX)) { // Limit drag distance to prevent excessive movement const maxDrag = 300; const limitedDelta = Math.max(-maxDrag, Math.min(maxDrag, deltaX)); setDragOffset(limitedDelta); } }; const handleDragEnd = () => { if (!isDragging) return; const threshold = 100; // minimum drag distance to trigger navigation if (Math.abs(dragOffset) > threshold && videos.length > 1) { if (dragOffset > 0) { navigateToVideo('prev'); // drag right = previous video } else { navigateToVideo('next'); // drag left = next video } } setIsDragging(false); setDragOffset(0); }; // Mouse events const handleMouseDown = (e: React.MouseEvent) => { // Allow drag on video area but not on buttons if ((e.target as Element).closest('button')) return; handleDragStart(e.clientX, e.clientY); }; const handleMouseMove = (e: React.MouseEvent) => { handleDragMove(e.clientX, e.clientY); }; const handleMouseUp = () => { handleDragEnd(); }; // Touch events const handleTouchStart = (e: React.TouchEvent) => { if (e.touches.length !== 1) return; // Allow drag on video area but not on buttons if ((e.target as Element).closest('button')) return; const touch = e.touches[0]; handleDragStart(touch.clientX, touch.clientY); e.preventDefault(); }; const handleTouchMove = (e: React.TouchEvent) => { if (e.touches.length !== 1) return; if (!isDragging) return; const touch = e.touches[0]; handleDragMove(touch.clientX, touch.clientY); e.preventDefault(); }; const handleTouchEnd = (e: React.TouchEvent) => { handleDragEnd(); e.preventDefault(); }; useEffect(() => { const handleEscape = (e: KeyboardEvent) => { if (e.key === "Escape" && isOpen) { onClose(); } }; if (isOpen) { document.addEventListener("keydown", handleEscape); document.body.style.overflow = "hidden"; } else { document.body.style.overflow = ""; } return () => { document.removeEventListener("keydown", handleEscape); document.body.style.overflow = ""; }; }, [isOpen, onClose]); const handleVideoPlay = async () => { if (video) { try { await apiRequest("POST", `/api/videos/${video.id}/view`); } catch (error) { console.error("Failed to track video view:", error); } } }; const getShareUrl = () => { if (!video?.id) return window.location.origin; return `${window.location.origin}?video=${video.id}`; }; const copyToClipboard = async () => { try { await navigator.clipboard.writeText(getShareUrl()); const notification = document.createElement('div'); notification.textContent = 'Povezava kopirana!'; notification.className = 'fixed top-4 right-4 bg-green-500 text-white px-4 py-2 rounded-lg z-50 transition-opacity duration-300'; document.body.appendChild(notification); setTimeout(() => { notification.style.opacity = '0'; setTimeout(() => document.body.removeChild(notification), 300); }, 2000); setShowShareMenu(false); } catch (error) { console.error('Failed to copy link:', error); const textArea = document.createElement('textarea'); textArea.value = getShareUrl(); document.body.appendChild(textArea); textArea.select(); document.execCommand('copy'); document.body.removeChild(textArea); setShowShareMenu(false); } }; const handleBackdropClick = (e: React.MouseEvent) => { if (e.target === e.currentTarget) { onClose(); } }; const shareOnFacebook = () => { const url = `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(getShareUrl())}&t=${encodeURIComponent(video?.title || '')}`; window.open(url, 'facebook-share', 'width=600,height=400'); setShowShareMenu(false); }; const shareOnTwitter = () => { const text = `Poglej si "${video?.title}" na go4.video`; const url = `https://twitter.com/intent/tweet?text=${encodeURIComponent(text)}&url=${encodeURIComponent(getShareUrl())}`; window.open(url, 'twitter-share', 'width=600,height=400'); setShowShareMenu(false); }; const shareOnWhatsApp = () => { const text = `Poglej si "${video?.title}" na go4.video: ${getShareUrl()}`; const url = `https://wa.me/?text=${encodeURIComponent(text)}`; window.open(url, 'whatsapp-share', 'width=600,height=400'); setShowShareMenu(false); }; if (!isOpen || !video) return null; return (
Video ni na voljo
{video.description}