forked from 77media/video-flow
220 lines
8.8 KiB
TypeScript
220 lines
8.8 KiB
TypeScript
import React from 'react';
|
|
import { Camera } from 'lucide-react';
|
|
import { TypewriterText } from './common/TypewriterText';
|
|
import { ContentCard } from './common/ContentCard';
|
|
import { SkeletonCard } from './common/SkeletonCard';
|
|
import { IconLoading } from './common/IconLoading';
|
|
|
|
interface StoryboardContent {
|
|
shotLanguage?: Array<{
|
|
id: string;
|
|
stableId: string;
|
|
type: string;
|
|
purpose: string;
|
|
usage: string;
|
|
}>;
|
|
composition?: {
|
|
stableId: string;
|
|
principles: string;
|
|
aesthetics: string;
|
|
framing: string;
|
|
};
|
|
cameraMovement?: Array<{
|
|
id: string;
|
|
stableId: string;
|
|
type: string;
|
|
purpose: string;
|
|
application: string;
|
|
}>;
|
|
visualNarrative?: {
|
|
stableId: string;
|
|
logic: string;
|
|
progression: string;
|
|
emphasis: string;
|
|
};
|
|
editingPoints?: Array<{
|
|
id: string;
|
|
stableId: string;
|
|
moment: string;
|
|
cut: string;
|
|
}>;
|
|
}
|
|
|
|
interface StoryboardArtistProps {
|
|
currentContent: StoryboardContent;
|
|
isPlaying: boolean;
|
|
}
|
|
|
|
const StoryboardArtist: React.FC<StoryboardArtistProps> = ({ currentContent, isPlaying }) => {
|
|
return (
|
|
<div className="grid grid-cols-2 gap-4 h-full">
|
|
{/* 左侧:镜头语言和构图美学 */}
|
|
<div className="space-y-4 overflow-y-auto">
|
|
{/* 镜头语言选择 */}
|
|
<div className="bg-black/30 rounded-lg p-4">
|
|
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
|
|
<span>Shot language selection</span>
|
|
<IconLoading icon={Camera} isActive={isPlaying} color="#06b6d4" />
|
|
</h3>
|
|
<div className="space-y-3">
|
|
{currentContent.shotLanguage && currentContent.shotLanguage.length > 0 ? (
|
|
currentContent.shotLanguage.map((shot) => (
|
|
<ContentCard
|
|
key={shot.stableId}
|
|
className="bg-cyan-500/20 rounded-lg p-3 border border-cyan-500/30"
|
|
>
|
|
<div className="flex justify-between items-center mb-2">
|
|
<span className="text-cyan-300 font-medium text-sm">{shot.type}</span>
|
|
<span className="text-gray-400 text-xs">{shot.purpose}</span>
|
|
</div>
|
|
<div className="text-gray-300 text-xs leading-relaxed">
|
|
<TypewriterText text={shot.usage} stableId={shot.stableId} />
|
|
</div>
|
|
</ContentCard>
|
|
))
|
|
) : (
|
|
Array.from({length: 4}, (_, i) => (
|
|
<SkeletonCard key={i} className="bg-cyan-500/20 rounded-lg p-3 border border-cyan-500/30" />
|
|
))
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* 构图美学运用 */}
|
|
<div className="bg-black/30 rounded-lg p-4">
|
|
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
|
|
<span>Composition aesthetics</span>
|
|
<IconLoading icon={Camera} isActive={isPlaying} color="#06b6d4" />
|
|
</h3>
|
|
{currentContent.composition ? (
|
|
<ContentCard className="space-y-3">
|
|
<div>
|
|
<div className="text-cyan-300 text-sm font-medium mb-1">Composition principles</div>
|
|
<div className="text-gray-300 text-xs">
|
|
<TypewriterText text={currentContent.composition.principles} stableId={`${currentContent.composition.stableId}-principles`} />
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div className="text-cyan-300 text-sm font-medium mb-1">Aesthetics expression</div>
|
|
<div className="text-gray-300 text-xs">
|
|
<TypewriterText text={currentContent.composition.aesthetics} stableId={`${currentContent.composition.stableId}-aesthetics`} />
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div className="text-cyan-300 text-sm font-medium mb-1">Frame setting</div>
|
|
<div className="text-gray-300 text-xs">
|
|
<TypewriterText text={currentContent.composition.framing} stableId={`${currentContent.composition.stableId}-framing`} />
|
|
</div>
|
|
</div>
|
|
</ContentCard>
|
|
) : (
|
|
<div className="space-y-3">
|
|
{Array.from({length: 3}, (_, i) => (
|
|
<SkeletonCard key={i} className="bg-cyan-500/20 rounded-lg p-3 border border-cyan-500/30" />
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* 右侧:摄影机运动和叙事逻辑 */}
|
|
<div className="space-y-4 overflow-y-auto">
|
|
{/* 摄影机运动设计 */}
|
|
<div className="bg-black/30 rounded-lg p-4">
|
|
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
|
|
<span>Camera movement design</span>
|
|
<IconLoading icon={Camera} isActive={isPlaying} color="#06b6d4" />
|
|
</h3>
|
|
<div className="space-y-3">
|
|
{currentContent.cameraMovement ? (
|
|
currentContent.cameraMovement.map((move) => (
|
|
<ContentCard
|
|
key={move.stableId}
|
|
className="bg-cyan-400/20 rounded-lg p-3"
|
|
>
|
|
<div className="text-cyan-200 font-medium text-sm mb-1">{move.type}</div>
|
|
<div className="text-gray-300 text-xs mb-2">
|
|
<TypewriterText text={move.purpose} stableId={`${move.stableId}-purpose`} />
|
|
</div>
|
|
<div className="text-cyan-300 text-xs">
|
|
<TypewriterText text={move.application} stableId={`${move.stableId}-application`} />
|
|
</div>
|
|
</ContentCard>
|
|
))
|
|
) : (
|
|
Array.from({length: 3}, (_, i) => (
|
|
<SkeletonCard key={i} className="bg-cyan-500/20 rounded-lg p-3 border border-cyan-500/30" />
|
|
))
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* 视觉叙事逻辑 */}
|
|
<div className="bg-black/30 rounded-lg p-4">
|
|
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
|
|
<span>Visual storytelling logic</span>
|
|
<IconLoading icon={Camera} isActive={isPlaying} color="#06b6d4" />
|
|
</h3>
|
|
{currentContent.visualNarrative ? (
|
|
<ContentCard className="space-y-3">
|
|
<div>
|
|
<div className="text-cyan-300 text-sm font-medium mb-1">Storytelling logic</div>
|
|
<div className="text-gray-300 text-xs">
|
|
<TypewriterText text={currentContent.visualNarrative.logic} stableId={`${currentContent.visualNarrative.stableId}-logic`} />
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div className="text-cyan-300 text-sm font-medium mb-1">Progression</div>
|
|
<div className="text-gray-300 text-xs">
|
|
<TypewriterText text={currentContent.visualNarrative.progression} stableId={`${currentContent.visualNarrative.stableId}-progression`} />
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div className="text-cyan-300 text-sm font-medium mb-1">Key emphasis</div>
|
|
<div className="text-gray-300 text-xs">
|
|
<TypewriterText text={currentContent.visualNarrative.emphasis} stableId={`${currentContent.visualNarrative.stableId}-emphasis`} />
|
|
</div>
|
|
</div>
|
|
</ContentCard>
|
|
) : (
|
|
<div className="space-y-3">
|
|
{Array.from({length: 3}, (_, i) => (
|
|
<SkeletonCard key={i} className="bg-cyan-500/20 rounded-lg p-3 border border-cyan-500/30" />
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* 剪辑点预设 */}
|
|
<div className="bg-black/30 rounded-lg p-4">
|
|
<h3 className="text-white font-semibold mb-3 flex items-center space-x-2">
|
|
<span>Editing point preset</span>
|
|
<IconLoading icon={Camera} isActive={isPlaying} color="#06b6d4" />
|
|
</h3>
|
|
<div className="space-y-2">
|
|
{currentContent.editingPoints ? (
|
|
currentContent.editingPoints.map((point) => (
|
|
<ContentCard
|
|
key={point.stableId}
|
|
className="bg-cyan-300/20 rounded p-2"
|
|
>
|
|
<div className="text-cyan-200 text-sm font-medium">{point.moment}</div>
|
|
<div className="text-gray-300 text-xs">
|
|
<TypewriterText text={point.cut} stableId={point.stableId} />
|
|
</div>
|
|
</ContentCard>
|
|
))
|
|
) : (
|
|
Array.from({length: 4}, (_, i) => (
|
|
<SkeletonCard key={i} className="bg-cyan-500/20 rounded-lg p-3 border border-cyan-500/30" />
|
|
))
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default StoryboardArtist;
|