From 4d8158ab9475f305715429deb2c7f2b13c49acac Mon Sep 17 00:00:00 2001 From: Tanya Date: Wed, 11 Feb 2026 12:12:34 -0500 Subject: [PATCH 1/2] feat: add Play button overlay for videos in search results - Create VideoPlayer component with centered Play button overlay - Add /video/:id route to display videos with Play button - Update Search page to open videos in VideoPlayer page instead of direct URL - Play button shows when video is paused/stopped and hides when playing - Button includes hover effects and doesn't interfere with native controls --- admin-frontend/src/App.tsx | 9 ++ admin-frontend/src/pages/Search.tsx | 10 +-- admin-frontend/src/pages/VideoPlayer.tsx | 108 +++++++++++++++++++++++ 3 files changed, 122 insertions(+), 5 deletions(-) create mode 100644 admin-frontend/src/pages/VideoPlayer.tsx diff --git a/admin-frontend/src/App.tsx b/admin-frontend/src/App.tsx index e58b82e..04f7c28 100644 --- a/admin-frontend/src/App.tsx +++ b/admin-frontend/src/App.tsx @@ -20,6 +20,7 @@ import UserTaggedPhotos from './pages/UserTaggedPhotos' import ManagePhotos from './pages/ManagePhotos' import Settings from './pages/Settings' import Help from './pages/Help' +import VideoPlayer from './pages/VideoPlayer' import Layout from './components/Layout' import PasswordChangeModal from './components/PasswordChangeModal' import AdminRoute from './components/AdminRoute' @@ -93,6 +94,14 @@ function AppRoutes() { return ( } /> + + + + } + /> { const isVideo = mediaType === 'video' - let photoUrl: string if (isVideo) { - // Use video endpoint for videos - photoUrl = `${apiClient.defaults.baseURL}/api/v1/videos/${photoId}/video` + // Open video in VideoPlayer page with Play button + const videoPlayerUrl = `/video/${photoId}` + window.open(videoPlayerUrl, '_blank') } else { // Use image endpoint for images - photoUrl = `${apiClient.defaults.baseURL}/api/v1/photos/${photoId}/image` + const photoUrl = `${apiClient.defaults.baseURL}/api/v1/photos/${photoId}/image` + window.open(photoUrl, '_blank') } - window.open(photoUrl, '_blank') } const openFolder = async (photoId: number) => { diff --git a/admin-frontend/src/pages/VideoPlayer.tsx b/admin-frontend/src/pages/VideoPlayer.tsx new file mode 100644 index 0000000..feb9da2 --- /dev/null +++ b/admin-frontend/src/pages/VideoPlayer.tsx @@ -0,0 +1,108 @@ +import { useState, useRef, useEffect } from 'react' +import { useParams } from 'react-router-dom' +import videosApi from '../api/videos' + +export default function VideoPlayer() { + const { id } = useParams<{ id: string }>() + const videoId = id ? parseInt(id, 10) : null + const videoRef = useRef(null) + const [isPlaying, setIsPlaying] = useState(false) + const [showPlayButton, setShowPlayButton] = useState(true) + + const videoUrl = videoId ? videosApi.getVideoUrl(videoId) : '' + + const handlePlay = () => { + if (videoRef.current) { + videoRef.current.play() + setIsPlaying(true) + setShowPlayButton(false) + } + } + + const handlePause = () => { + setIsPlaying(false) + setShowPlayButton(true) + } + + const handlePlayClick = () => { + handlePlay() + } + + // Hide play button when video starts playing + useEffect(() => { + const video = videoRef.current + if (!video) return + + const handlePlayEvent = () => { + setIsPlaying(true) + setShowPlayButton(false) + } + + const handlePauseEvent = () => { + setIsPlaying(false) + setShowPlayButton(true) + } + + const handleEnded = () => { + setIsPlaying(false) + setShowPlayButton(true) + } + + video.addEventListener('play', handlePlayEvent) + video.addEventListener('pause', handlePauseEvent) + video.addEventListener('ended', handleEnded) + + return () => { + video.removeEventListener('play', handlePlayEvent) + video.removeEventListener('pause', handlePauseEvent) + video.removeEventListener('ended', handleEnded) + } + }, []) + + if (!videoId || !videoUrl) { + return ( +
+
Video not found
+
+ ) + } + + return ( +
+
+
+ ) +} + From 726b36db801d4d49d5c3e6d8f8a1269fd4654b46 Mon Sep 17 00:00:00 2001 From: Tanya Date: Wed, 11 Feb 2026 12:15:44 -0500 Subject: [PATCH 2/2] feat: add informational message for bulk auto-match operation - Show confirmation dialog before starting auto-match operation - Inform users that it's a bulk operation processing entire photo library - Warn that some matches may not be 100% accurate - Recommend reviewing results after completion --- admin-frontend/src/pages/AutoMatch.tsx | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/admin-frontend/src/pages/AutoMatch.tsx b/admin-frontend/src/pages/AutoMatch.tsx index 45a0b17..8d4908a 100644 --- a/admin-frontend/src/pages/AutoMatch.tsx +++ b/admin-frontend/src/pages/AutoMatch.tsx @@ -449,6 +449,22 @@ export default function AutoMatch() { return } + // Show informational message about bulk operation + const infoMessage = [ + 'ℹ️ Bulk Auto-Match Operation', + '', + 'This operation will automatically match faces across your entire photo library.', + 'While the system uses advanced matching algorithms, some matches may not be 100% accurate.', + '', + 'Please review the results after completion to ensure accuracy.', + '', + 'Do you want to proceed with the auto-match operation?' + ].join('\n') + + if (!confirm(infoMessage)) { + return + } + setBusy(true) try { const response = await facesApi.autoMatch({