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:
sebastjanartic 2025-08-28 20:24:30 +00:00
parent b9fa335300
commit 22aac877b9
3 changed files with 58 additions and 132 deletions

View File

@ -1,5 +1,5 @@
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 { Button } from "@/components/ui/button";
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) {
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);
}
};
useEffect(() => {
const handleEscape = (e: KeyboardEvent) => {
@ -142,99 +162,6 @@ export default function BunnyVideoModal({ video, isOpen, onClose, onEdit, videos
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;
@ -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">
{/* Main video player */}
<div className="flex-1">
<div
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}
>
<div className="relative w-full h-0 pb-[56.25%] bg-black rounded-lg overflow-hidden">
{video.videoUrlIframe ? (
<iframe
src={video.videoUrlIframe}
@ -356,26 +273,34 @@ export default function BunnyVideoModal({ video, isOpen, onClose, onEdit, videos
<p>Video ni na voljo</p>
</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>
{/* 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>
{/* Video info sidebar */}

View File

@ -31,6 +31,7 @@ export default function VideoGrid({ videos, isLoading, hasMore, onLoadMore, view
setSelectedVideo(video);
};
if (isLoading && videos.length === 0) {
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">
@ -51,10 +52,10 @@ export default function VideoGrid({ videos, isLoading, hasMore, onLoadMore, view
return (
<div className="text-center py-12">
<div className="text-bunny-muted text-lg mb-4" data-testid="text-no-videos">
No videos found
Ni najdenih videjev
</div>
<p className="text-sm text-bunny-muted">
Try adjusting your search or filter criteria
Poskusi prilagoditi iskalne ali filter kriterije
</p>
</div>
);
@ -87,11 +88,11 @@ export default function VideoGrid({ videos, isLoading, hasMore, onLoadMore, view
{isLoading ? (
<>
<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" />
</>
)}

View File

@ -108,7 +108,7 @@ export default function VideoPage() {
if (videoLoading) {
return (
<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>
);
}