Add video navigation and translate UI elements to Slovenian
Introduce left/right arrow navigation for videos and translate several UI strings to Slovenian. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 2eb1084e-b728-4449-9231-f1665924c8d5 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/8cc42625-c1f5-4e43-99bd-77f2c4dedee2/2eb1084e-b728-4449-9231-f1665924c8d5/14Urb47
This commit is contained in:
parent
b9fa335300
commit
22aac877b9
@ -1,5 +1,5 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { X, Share2, Edit3 } from "lucide-react";
|
import { X, Share2, Edit3, ChevronLeft, ChevronRight } from "lucide-react";
|
||||||
import { type Video } from "@shared/schema";
|
import { type Video } from "@shared/schema";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { apiRequest } from "@/lib/queryClient";
|
import { apiRequest } from "@/lib/queryClient";
|
||||||
@ -53,9 +53,29 @@ function formatDate(date: Date | string): string {
|
|||||||
|
|
||||||
export default function BunnyVideoModal({ video, isOpen, onClose, onEdit, videos = [], onVideoChange }: BunnyVideoModalProps) {
|
export default function BunnyVideoModal({ video, isOpen, onClose, onEdit, videos = [], onVideoChange }: BunnyVideoModalProps) {
|
||||||
const [showShareMenu, setShowShareMenu] = useState(false);
|
const [showShareMenu, setShowShareMenu] = useState(false);
|
||||||
const [isDragging, setIsDragging] = useState(false);
|
|
||||||
const [dragStart, setDragStart] = useState({ x: 0, y: 0 });
|
// Navigation functions
|
||||||
const [dragOffset, setDragOffset] = useState(0);
|
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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleEscape = (e: KeyboardEvent) => {
|
const handleEscape = (e: KeyboardEvent) => {
|
||||||
@ -142,99 +162,6 @@ export default function BunnyVideoModal({ video, isOpen, onClose, onEdit, videos
|
|||||||
setShowShareMenu(false);
|
setShowShareMenu(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Navigation functions
|
|
||||||
const getCurrentVideoIndex = () => {
|
|
||||||
if (!video || !videos.length) return -1;
|
|
||||||
return videos.findIndex(v => v.id === video.id);
|
|
||||||
};
|
|
||||||
|
|
||||||
const navigateToVideo = (direction: 'next' | 'prev') => {
|
|
||||||
const currentIndex = getCurrentVideoIndex();
|
|
||||||
if (currentIndex === -1) 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) {
|
|
||||||
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)) {
|
|
||||||
setDragOffset(deltaX);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
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) => {
|
|
||||||
if (e.target === e.currentTarget) return; // only on video area
|
|
||||||
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;
|
|
||||||
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();
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!isOpen || !video) return null;
|
if (!isOpen || !video) return null;
|
||||||
|
|
||||||
@ -330,17 +257,7 @@ export default function BunnyVideoModal({ video, isOpen, onClose, onEdit, videos
|
|||||||
<div className="flex-1 flex flex-col lg:flex-row gap-4 min-h-0">
|
<div className="flex-1 flex flex-col lg:flex-row gap-4 min-h-0">
|
||||||
{/* Main video player */}
|
{/* Main video player */}
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<div
|
<div className="relative w-full h-0 pb-[56.25%] bg-black rounded-lg overflow-hidden">
|
||||||
className="relative w-full h-0 pb-[56.25%] bg-black rounded-lg overflow-hidden cursor-grab active:cursor-grabbing select-none"
|
|
||||||
style={{ transform: `translateX(${dragOffset}px)`, transition: isDragging ? 'none' : 'transform 0.3s ease-out' }}
|
|
||||||
onMouseDown={handleMouseDown}
|
|
||||||
onMouseMove={handleMouseMove}
|
|
||||||
onMouseUp={handleMouseUp}
|
|
||||||
onMouseLeave={handleMouseUp}
|
|
||||||
onTouchStart={handleTouchStart}
|
|
||||||
onTouchMove={handleTouchMove}
|
|
||||||
onTouchEnd={handleTouchEnd}
|
|
||||||
>
|
|
||||||
{video.videoUrlIframe ? (
|
{video.videoUrlIframe ? (
|
||||||
<iframe
|
<iframe
|
||||||
src={video.videoUrlIframe}
|
src={video.videoUrlIframe}
|
||||||
@ -356,26 +273,34 @@ export default function BunnyVideoModal({ video, isOpen, onClose, onEdit, videos
|
|||||||
<p>Video ni na voljo</p>
|
<p>Video ni na voljo</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Navigation buttons */}
|
||||||
|
{videos.length > 1 && (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
onClick={() => navigateToVideo('prev')}
|
||||||
|
className="absolute left-4 top-1/2 transform -translate-y-1/2 bg-black bg-opacity-50 hover:bg-opacity-80 text-white border-none p-2 rounded-full z-10"
|
||||||
|
size="sm"
|
||||||
|
data-testid="button-prev-video"
|
||||||
|
>
|
||||||
|
<ChevronLeft className="w-6 h-6" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => navigateToVideo('next')}
|
||||||
|
className="absolute right-4 top-1/2 transform -translate-y-1/2 bg-black bg-opacity-50 hover:bg-opacity-80 text-white border-none p-2 rounded-full z-10"
|
||||||
|
size="sm"
|
||||||
|
data-testid="button-next-video"
|
||||||
|
>
|
||||||
|
<ChevronRight className="w-6 h-6" />
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{/* Video counter */}
|
||||||
|
<div className="absolute bottom-4 left-1/2 transform -translate-x-1/2 bg-black bg-opacity-50 rounded-full px-3 py-1 text-white text-sm pointer-events-none">
|
||||||
|
{getCurrentVideoIndex() + 1} od {videos.length}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Navigation indicators */}
|
|
||||||
{videos.length > 1 && (
|
|
||||||
<div className="absolute top-1/2 left-4 right-4 flex justify-between items-center pointer-events-none z-10">
|
|
||||||
<div className={`bg-black bg-opacity-50 rounded-full p-2 text-white transition-opacity ${dragOffset > 50 ? 'opacity-100' : 'opacity-30'}`}>
|
|
||||||
<span className="text-sm">← Prejšnji</span>
|
|
||||||
</div>
|
|
||||||
<div className={`bg-black bg-opacity-50 rounded-full p-2 text-white transition-opacity ${dragOffset < -50 ? 'opacity-100' : 'opacity-30'}`}>
|
|
||||||
<span className="text-sm">Naslednji →</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Video counter */}
|
|
||||||
{videos.length > 1 && (
|
|
||||||
<div className="absolute bottom-4 left-1/2 transform -translate-x-1/2 bg-black bg-opacity-50 rounded-full px-3 py-1 text-white text-sm pointer-events-none">
|
|
||||||
{getCurrentVideoIndex() + 1} od {videos.length}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Video info sidebar */}
|
{/* Video info sidebar */}
|
||||||
|
|||||||
@ -31,6 +31,7 @@ export default function VideoGrid({ videos, isLoading, hasMore, onLoadMore, view
|
|||||||
setSelectedVideo(video);
|
setSelectedVideo(video);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
if (isLoading && videos.length === 0) {
|
if (isLoading && videos.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6" data-testid="grid-loading">
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6" data-testid="grid-loading">
|
||||||
@ -51,10 +52,10 @@ export default function VideoGrid({ videos, isLoading, hasMore, onLoadMore, view
|
|||||||
return (
|
return (
|
||||||
<div className="text-center py-12">
|
<div className="text-center py-12">
|
||||||
<div className="text-bunny-muted text-lg mb-4" data-testid="text-no-videos">
|
<div className="text-bunny-muted text-lg mb-4" data-testid="text-no-videos">
|
||||||
No videos found
|
Ni najdenih videjev
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-bunny-muted">
|
<p className="text-sm text-bunny-muted">
|
||||||
Try adjusting your search or filter criteria
|
Poskusi prilagoditi iskalne ali filter kriterije
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -87,11 +88,11 @@ export default function VideoGrid({ videos, isLoading, hasMore, onLoadMore, view
|
|||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<>
|
<>
|
||||||
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div>
|
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div>
|
||||||
<span>Loading...</span>
|
<span>Nalagam...</span>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<span>Load More Videos</span>
|
<span>Naloži Več Videjev</span>
|
||||||
<ChevronDown className="w-4 h-4" />
|
<ChevronDown className="w-4 h-4" />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -108,7 +108,7 @@ export default function VideoPage() {
|
|||||||
if (videoLoading) {
|
if (videoLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-bunny-dark flex items-center justify-center">
|
<div className="min-h-screen bg-bunny-dark flex items-center justify-center">
|
||||||
<div className="text-white">Loading video...</div>
|
<div className="text-white">Nalagam video...</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user