Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 12 additions & 10 deletions frontend/src/components/Media/MediaView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ export function MediaView({
}, [dispatch, resetViewerState]);

// Slideshow functionality
const { isSlideshowActive, toggleSlideshow } = useSlideshow(
const { isSlideshowActive, toggleSlideshow, duration, setDuration } = useSlideshow(
totalImages,
handleNextImage,
handleLoopToStart,
Expand Down Expand Up @@ -175,15 +175,17 @@ export function MediaView({
<div className="fixed inset-0 z-50 mt-0 flex flex-col bg-gradient-to-b from-black/95 to-black/98 backdrop-blur-lg">
{/* Controls */}
<MediaViewControls
showInfo={showInfo}
onToggleInfo={toggleInfo}
onToggleFavourite={handleToggleFavourite}
isFavourite={currentImage.isFavourite || false}
onOpenFolder={handleOpenFolder}
isSlideshowActive={isSlideshowActive}
onToggleSlideshow={toggleSlideshow}
onClose={handleClose}
type={type}
showInfo={showInfo}
onToggleInfo={toggleInfo}
onToggleFavourite={handleToggleFavourite}
isFavourite={currentImage.isFavourite || false}
onOpenFolder={handleOpenFolder}
isSlideshowActive={isSlideshowActive}
onToggleSlideshow={toggleSlideshow}
onClose={handleClose}
type={type}
duration={duration}
onDurationChange={setDuration}
/>

{/* Main viewer area */}
Expand Down
85 changes: 68 additions & 17 deletions frontend/src/components/Media/MediaViewControls.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useState } from 'react';
import { Info, Heart, Play, Pause, X, Folder } from 'lucide-react';

interface MediaViewControlsProps {
Expand All @@ -11,8 +11,13 @@ interface MediaViewControlsProps {
onToggleSlideshow: () => void;
onClose: () => void;
type?: string;
duration: number;
onDurationChange: (duration: number) => void;
}

// snap points in ms, mapped to a 0-2 slider scale
const DURATION_STEPS = [2000, 5000, 10000];

/** Control buttons for the full-screen media viewer. */
export const MediaViewControls: React.FC<MediaViewControlsProps> = ({
showInfo,
Expand All @@ -24,7 +29,20 @@ export const MediaViewControls: React.FC<MediaViewControlsProps> = ({
onToggleSlideshow,
onClose,
type = 'image',
duration,
onDurationChange,
}) => {
const [showSettings, setShowSettings] = useState(false);

// convert current duration (ms) to a step index (0,1,2) for the slider
const currentStepIndex = DURATION_STEPS.indexOf(duration);
const sliderValue = currentStepIndex === -1 ? 0 : currentStepIndex;

const handleSliderChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const stepIndex = Number(e.target.value);
onDurationChange(DURATION_STEPS[stepIndex]);
};

return (
<div className="absolute top-4 right-4 z-50 flex items-center gap-3">
<button
Expand All @@ -37,7 +55,6 @@ export const MediaViewControls: React.FC<MediaViewControlsProps> = ({
>
<Info className="h-5 w-5" />
</button>

<button
onClick={onOpenFolder}
className="cursor-pointer rounded-full bg-black/50 p-2.5 text-white/90 transition-all duration-200 hover:bg-black/20 hover:text-white hover:shadow-lg"
Expand All @@ -46,7 +63,6 @@ export const MediaViewControls: React.FC<MediaViewControlsProps> = ({
>
<Folder className="h-5 w-5" />
</button>

<button
onClick={onToggleFavourite}
className={`cursor-pointer rounded-full p-2.5 text-white transition-all duration-300 ${
Expand All @@ -63,21 +79,56 @@ export const MediaViewControls: React.FC<MediaViewControlsProps> = ({
</button>

{type === 'image' && (
<button
onClick={onToggleSlideshow}
className="flex cursor-pointer items-center gap-2 rounded-full bg-indigo-500/70 px-4 py-2 text-white transition-all duration-200 hover:bg-indigo-600/80 hover:shadow-lg"
aria-label="Toggle Slideshow"
title="SlideShow"
<div
className="relative"
onMouseEnter={() => setShowSettings(true)}
onMouseLeave={() => setShowSettings(false)}
onFocus={() => setShowSettings(true)}
onBlur={() => setShowSettings(false)}
>
{isSlideshowActive ? (
<Pause className="h-4 w-4" />
) : (
<Play className="h-4 w-4" />
<button
onClick={onToggleSlideshow}
className="flex cursor-pointer items-center gap-2 rounded-full bg-indigo-500/70 px-4 py-2 text-white transition-all duration-200 hover:bg-indigo-600/80 hover:shadow-lg"
aria-label="Toggle Slideshow"
title="SlideShow"
>
{isSlideshowActive ? (
<Pause className="h-4 w-4" />
) : (
<Play className="h-4 w-4" />
)}
<span className="text-sm font-medium">
{isSlideshowActive ? 'Pause' : 'Slideshow'}
</span>
</button>

{showSettings && (
<div className="absolute right-0 top-full pt-2">
<div className="w-48 rounded-xl bg-black/80 p-4 shadow-lg">
<div className="mb-2 flex justify-between text-xs text-white/70">
<span>2s</span>
<span>10s</span>
</div>
<input
type="range"
min={0}
max={2}
step={1}
value={sliderValue}
onChange={handleSliderChange}
aria-label="Slideshow duration"
className="w-full cursor-pointer accent-indigo-500 h-1.5 rounded-lg appearance-none"
style={{
background: `linear-gradient(to right, #6366f1 0%, #6366f1 ${(sliderValue / 2) * 100}%, rgba(255,255,255,0.2) ${(sliderValue / 2) * 100}%, rgba(255,255,255,0.2) 100%)`,
}}
/>
<div className="mt-2 text-center text-xs text-white/70">
{DURATION_STEPS[sliderValue] / 1000}s per image
</div>
</div>
</div>
)}
<span className="text-sm font-medium">
{isSlideshowActive ? 'Pause' : 'Slideshow'}
</span>
</button>
</div>
)}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

<button
Expand All @@ -90,4 +141,4 @@ export const MediaViewControls: React.FC<MediaViewControlsProps> = ({
</button>
</div>
);
};
};
11 changes: 6 additions & 5 deletions frontend/src/hooks/useSlideshow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,12 @@ export const useSlideshow = (
currentIndex?: number,
) => {
const [isSlideshowActive, setIsSlideshowActive] = useState(false);
const [duration, setDuration] = useState(2000); // default 2s

useEffect(() => {
let slideshowInterval: NodeJS.Timeout | null = null;

if (isSlideshowActive && totalImages > 1) {
slideshowInterval = setInterval(() => {
// Loop back to first image when at the end
if (
currentIndex !== undefined &&
onLoopToStart &&
Expand All @@ -23,9 +22,8 @@ export const useSlideshow = (
} else {
onNextImage();
}
}, 3000);
}, duration); // <-- was 3000, now uses the duration state
}

return () => {
if (slideshowInterval) {
clearInterval(slideshowInterval);
Expand All @@ -37,6 +35,7 @@ export const useSlideshow = (
onNextImage,
onLoopToStart,
currentIndex,
duration, // <-- added, so timer restarts if duration changes
]);

const toggleSlideshow = useCallback(() => {
Expand All @@ -46,5 +45,7 @@ export const useSlideshow = (
return {
isSlideshowActive,
toggleSlideshow,
duration, // <-- exposing current duration
setDuration, // <-- exposing the setter so other files can change it
};
};
};
Loading