feat(upload): preview uploaded videos with ProtectedVideoPlayer
This commit is contained in:
@@ -1,53 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { Loader2 } from 'lucide-react';
|
||||
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import { useAnnotatedVideoQuery } from '@/app/(modules)/results/hooks/useVideoResults';
|
||||
import type { UploadVideo } from '@/types';
|
||||
|
||||
interface ProcessedVideoDialogProps {
|
||||
upload: UploadVideo | null;
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
}
|
||||
|
||||
export function ProcessedVideoDialog({
|
||||
upload,
|
||||
open,
|
||||
onOpenChange,
|
||||
}: ProcessedVideoDialogProps) {
|
||||
const { videoUrl, isLoading, isError } = useAnnotatedVideoQuery(
|
||||
open ? upload?.processed_video_url ?? undefined : undefined,
|
||||
);
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="sm:max-w-4xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{upload?.filename ?? 'Processed Video'}</DialogTitle>
|
||||
<DialogDescription>Processed video preview</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="flex aspect-video items-center justify-center overflow-hidden rounded-md border bg-muted/30">
|
||||
{isLoading ? (
|
||||
<Loader2 className="size-6 animate-spin text-primary" />
|
||||
) : isError || !videoUrl ? (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Processed video is not available.
|
||||
</p>
|
||||
) : (
|
||||
<video src={videoUrl} controls className="h-full w-full" />
|
||||
)}
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,14 +13,14 @@ import type { UploadVideo } from '@/types';
|
||||
|
||||
interface UploadActionsMenuProps {
|
||||
upload: UploadVideo;
|
||||
onViewVideo: (upload: UploadVideo) => void;
|
||||
onViewDetails: (upload: UploadVideo) => void;
|
||||
onViewProcessedVideo: (upload: UploadVideo) => void;
|
||||
}
|
||||
|
||||
export function UploadActionsMenu({
|
||||
upload,
|
||||
onViewVideo,
|
||||
onViewDetails,
|
||||
onViewProcessedVideo,
|
||||
}: UploadActionsMenuProps) {
|
||||
return (
|
||||
<DropdownMenu>
|
||||
@@ -37,16 +37,26 @@ export function UploadActionsMenu({
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem onClick={() => onViewDetails(upload)}>
|
||||
{upload.video_url ? (
|
||||
<DropdownMenuItem
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
onViewVideo(upload);
|
||||
}}
|
||||
>
|
||||
<PlayCircle className="size-4" />
|
||||
View Video
|
||||
</DropdownMenuItem>
|
||||
) : null}
|
||||
<DropdownMenuItem
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
onViewDetails(upload);
|
||||
}}
|
||||
>
|
||||
<Eye className="size-4" />
|
||||
View Details
|
||||
</DropdownMenuItem>
|
||||
{upload.processed_video_url ? (
|
||||
<DropdownMenuItem onClick={() => onViewProcessedVideo(upload)}>
|
||||
<PlayCircle className="size-4" />
|
||||
View Processed Video
|
||||
</DropdownMenuItem>
|
||||
) : null}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
|
||||
@@ -21,8 +21,8 @@ interface UploadCardGridProps {
|
||||
onLimitChange: (limit: number) => void;
|
||||
onUploadNew: () => void;
|
||||
onOpenUpload: (upload: UploadVideo) => void;
|
||||
onViewVideo: (upload: UploadVideo) => void;
|
||||
onViewDetails: (upload: UploadVideo) => void;
|
||||
onViewProcessedVideo: (upload: UploadVideo) => void;
|
||||
}
|
||||
|
||||
export function UploadCardGrid({
|
||||
@@ -35,8 +35,8 @@ export function UploadCardGrid({
|
||||
onLimitChange,
|
||||
onUploadNew,
|
||||
onOpenUpload,
|
||||
onViewVideo,
|
||||
onViewDetails,
|
||||
onViewProcessedVideo,
|
||||
}: UploadCardGridProps) {
|
||||
const skeletonItems = Array.from({ length: Math.min(limit, 20) });
|
||||
|
||||
@@ -85,8 +85,8 @@ export function UploadCardGrid({
|
||||
</div>
|
||||
<UploadActionsMenu
|
||||
upload={upload}
|
||||
onViewVideo={onViewVideo}
|
||||
onViewDetails={onViewDetails}
|
||||
onViewProcessedVideo={onViewProcessedVideo}
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
|
||||
@@ -10,13 +10,13 @@ import { Badge } from '@/components/ui/badge';
|
||||
import type { UploadVideo } from '@/types';
|
||||
|
||||
interface UseUploadColumnsProps {
|
||||
onViewVideo: (upload: UploadVideo) => void;
|
||||
onViewDetails: (upload: UploadVideo) => void;
|
||||
onViewProcessedVideo: (upload: UploadVideo) => void;
|
||||
}
|
||||
|
||||
export function useUploadColumns({
|
||||
onViewVideo,
|
||||
onViewDetails,
|
||||
onViewProcessedVideo,
|
||||
}: UseUploadColumnsProps) {
|
||||
return useMemo<ColumnDef<UploadVideo>[]>(
|
||||
() => [
|
||||
@@ -65,13 +65,13 @@ export function useUploadColumns({
|
||||
<div className="flex justify-center">
|
||||
<UploadActionsMenu
|
||||
upload={row.original}
|
||||
onViewVideo={onViewVideo}
|
||||
onViewDetails={onViewDetails}
|
||||
onViewProcessedVideo={onViewProcessedVideo}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
],
|
||||
[onViewDetails, onViewProcessedVideo],
|
||||
[onViewDetails, onViewVideo],
|
||||
);
|
||||
}
|
||||
|
||||
44
src/app/(modules)/upload/components/UploadVideoDialog.tsx
Normal file
44
src/app/(modules)/upload/components/UploadVideoDialog.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
'use client';
|
||||
|
||||
import { ProtectedVideoPlayer } from '@/components/media/ProtectedVideoPlayer';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import type { UploadVideo } from '@/types';
|
||||
|
||||
interface UploadVideoDialogProps {
|
||||
upload: UploadVideo | null;
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
}
|
||||
|
||||
export function UploadVideoDialog({
|
||||
upload,
|
||||
open,
|
||||
onOpenChange,
|
||||
}: UploadVideoDialogProps) {
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="gap-3 p-3 sm:max-w-5xl sm:p-4">
|
||||
<DialogHeader className="min-w-0 pr-8">
|
||||
<DialogTitle className="truncate text-left text-base">
|
||||
Video Preview
|
||||
</DialogTitle>
|
||||
<DialogDescription className="truncate text-xs">
|
||||
{upload?.filename ?? 'Uploaded video'}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<ProtectedVideoPlayer
|
||||
url={open ? upload?.video_url : undefined}
|
||||
className="rounded-lg"
|
||||
unavailableTitle="Video preview unavailable"
|
||||
/>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user