ai-chatbot/components/canvas/GroupStoryboardPreview.tsx

123 lines
3.4 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
import { ArrowLeft, ArrowRight, X } from "lucide-react";
export type GroupShot = {
id: string;
title: string;
url: string;
type: "image" | "video";
};
export function GroupStoryboardPreview({
shots,
onOrder,
onClose,
}: {
shots: GroupShot[];
onOrder: (ids: string[]) => void;
onClose: () => void;
}) {
const [index, setIndex] = useState(0),
shot = shots[Math.min(index, shots.length - 1)];
useEffect(() => {
const key = (e: KeyboardEvent) => {
if (e.key === "Escape") onClose();
};
window.addEventListener("keydown", key);
return () => window.removeEventListener("keydown", key);
}, [onClose]);
if (!shot) return null;
const move = (delta: number) => {
const next = index + delta;
if (next < 0 || next >= shots.length) return;
const ids = shots.map((s) => s.id);
[ids[index], ids[next]] = [ids[next], ids[index]];
onOrder(ids);
setIndex(next);
};
return (
<div
data-canvas-overlay="true"
role="dialog"
aria-label="编组分镜预览"
className="fixed inset-0 z-[120] flex flex-col bg-black/95 p-8 text-white"
onPointerDown={(e) => e.stopPropagation()}
>
<header className="mb-4 flex items-center justify-between">
<h2>
分镜 {index + 1}/{shots.length} · {shot.title}
</h2>
<button aria-label="关闭分镜预览" onClick={onClose}>
<X />
</button>
</header>
<div className="flex min-h-0 flex-1 items-center justify-center">
{shot.type === "video" ? (
<video
key={shot.id}
src={shot.url}
controls
autoPlay
className="h-full max-w-full object-contain"
onEnded={() => setIndex((i) => Math.min(i + 1, shots.length - 1))}
/>
) : (
<img
src={shot.url}
alt={shot.title}
className="h-full max-w-full object-contain"
/>
)}
</div>
<div className="mt-4 flex items-center justify-center gap-6">
<button
type="button"
disabled={index === 0}
onClick={() => setIndex((i) => i - 1)}
className="disabled:opacity-30"
>
<ArrowLeft />
</button>
<span>
{index + 1} / {shots.length}
</span>
<button
type="button"
disabled={index === shots.length - 1}
onClick={() => setIndex((i) => i + 1)}
className="disabled:opacity-30"
>
<ArrowRight />
</button>
<button
type="button"
disabled={index === 0}
onClick={() => move(-1)}
className="disabled:opacity-30"
>
镜头前移
</button>
<button
type="button"
disabled={index === shots.length - 1}
onClick={() => move(1)}
className="disabled:opacity-30"
>
镜头后移
</button>
</div>
<div className="mt-5 flex justify-center gap-3 overflow-x-auto">
{shots.map((s, i) => (
<button
type="button"
key={s.id}
onClick={() => setIndex(i)}
className={`shrink-0 rounded-lg border px-4 py-2 text-sm ${i === index ? "border-white bg-white/20" : "border-white/20"}`}
>
{i + 1} · {s.title}
</button>
))}
</div>
</div>
);
}