void),
+ // }
+ // },
+
addNodeView() {
return ReactNodeViewRenderer(CharacterToken);
},
diff --git a/components/ui/shot-editor/ShotEditor.tsx b/components/ui/shot-editor/ShotEditor.tsx
index 54a1319..e9ab0c0 100644
--- a/components/ui/shot-editor/ShotEditor.tsx
+++ b/components/ui/shot-editor/ShotEditor.tsx
@@ -1,4 +1,4 @@
-import React, { useState } from 'react';
+import React, { useState, useCallback, useEffect } from 'react';
import { EditorContent, useEditor } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import { motion } from "framer-motion";
@@ -36,9 +36,10 @@ const initialContent = {
interface ShotEditorProps {
onAddSegment?: () => void;
+ onCharacterClick?: (attrs: any) => void;
}
-const ShotEditor = React.forwardRef<{ addSegment: () => void }, ShotEditorProps>(function ShotEditor({ onAddSegment }, ref) {
+const ShotEditor = React.forwardRef<{ addSegment: () => void, onCharacterClick: (attrs: any) => void }, ShotEditorProps>(function ShotEditor({ onAddSegment, onCharacterClick }, ref) {
const [segments, setSegments] = useState(initialContent.content);
const editor = useEditor({
@@ -60,6 +61,20 @@ const ShotEditor = React.forwardRef<{ addSegment: () => void }, ShotEditorProps>
},
})
+ useEffect(() => {
+ const handleCharacterClick = (attrs: any) => {
+ console.log('SceneEditor 收到角色点击事件:', attrs)
+ // 你可以这里 setState 打开一个弹窗 / 面板等
+ onCharacterClick?.(attrs);
+ };
+
+ editor?.on('character-clicked', handleCharacterClick as any);
+
+ return () => {
+ editor?.off('character-clicked', handleCharacterClick as any);
+ };
+ }, [editor]);
+
const addSegment = () => {
if (!editor) return;
diff --git a/components/ui/shot-tab-content.tsx b/components/ui/shot-tab-content.tsx
index 6161876..a8d492f 100644
--- a/components/ui/shot-tab-content.tsx
+++ b/components/ui/shot-tab-content.tsx
@@ -10,6 +10,10 @@ import { MediaPropertiesModal } from './media-properties-modal';
import { DramaLineChart } from './drama-line-chart';
import { PersonDetection, PersonDetectionScene } from './person-detection';
import ShotEditor from './shot-editor/ShotEditor';
+import { CharacterLibrarySelector } from './character-library-selector';
+import FloatingGlassPanel from './FloatingGlassPanel';
+import { ReplaceCharacterPanel, mockShots, mockCharacter } from './replace-character-panel';
+import HorizontalScroller from './HorizontalScroller';
interface ShotTabContentProps {
taskSketch: any[];
@@ -24,21 +28,19 @@ export function ShotTabContent({
onSketchSelect,
isPlaying: externalIsPlaying = true
}: ShotTabContentProps) {
- const thumbnailsRef = useRef
(null);
const editorRef = useRef(null);
- const videosRef = useRef(null);
const videoPlayerRef = useRef(null);
const [isPlaying, setIsPlaying] = React.useState(externalIsPlaying);
- const [isMuted, setIsMuted] = React.useState(false);
- const [progress, setProgress] = React.useState(0);
- const [isReplaceModalOpen, setIsReplaceModalOpen] = React.useState(false);
- const [activeReplaceMethod, setActiveReplaceMethod] = React.useState<'upload' | 'library' | 'generate'>('upload');
const [isMediaPropertiesModalOpen, setIsMediaPropertiesModalOpen] = React.useState(false);
- const [triggerScan, setTriggerScan] = useState(false);
const [detections, setDetections] = useState([]);
const [scanState, setScanState] = useState<'idle' | 'scanning' | 'detected'>('idle');
+ const [isReplaceLibraryOpen, setIsReplaceLibraryOpen] = useState(false);
+ const [isReplacePanelOpen, setIsReplacePanelOpen] = useState(false);
+
+ const [shots, setShots] = useState([]);
+
// 监听外部播放状态变化
useEffect(() => {
@@ -48,31 +50,6 @@ export function ShotTabContent({
// 确保 taskSketch 是数组
const sketches = Array.isArray(taskSketch) ? taskSketch : [];
- // 自动滚动到选中项
- useEffect(() => {
- if (thumbnailsRef.current && videosRef.current) {
- const thumbnailContainer = thumbnailsRef.current;
- const videoContainer = videosRef.current;
-
- const thumbnailWidth = thumbnailContainer.children[0]?.clientWidth ?? 0;
- const thumbnailGap = 16; // gap-4 = 16px
- const thumbnailScrollPosition = (thumbnailWidth + thumbnailGap) * currentSketchIndex;
-
- const videoElement = videoContainer.children[currentSketchIndex] as HTMLElement;
- const videoScrollPosition = videoElement?.offsetLeft ?? 0;
-
- thumbnailContainer.scrollTo({
- left: thumbnailScrollPosition - thumbnailContainer.clientWidth / 2 + thumbnailWidth / 2,
- behavior: 'smooth'
- });
-
- videoContainer.scrollTo({
- left: videoScrollPosition - videoContainer.clientWidth / 2 + videoElement?.clientWidth / 2,
- behavior: 'smooth'
- });
- }
- }, [currentSketchIndex]);
-
// 视频播放控制
useEffect(() => {
if (videoPlayerRef.current) {
@@ -87,13 +64,6 @@ export function ShotTabContent({
}
}, [isPlaying, currentSketchIndex]);
- // 更新进度条
- const handleTimeUpdate = () => {
- if (videoPlayerRef.current) {
- const progress = (videoPlayerRef.current.currentTime / videoPlayerRef.current.duration) * 100;
- setProgress(progress);
- }
- };
// 处理扫描开始
const handleScan = () => {
@@ -130,6 +100,33 @@ export function ShotTabContent({
}
};
+ // 处理人物点击 打开角色库
+ const handlePersonClick = (person: PersonDetection) => {
+ console.log('person', person);
+ setIsReplaceLibraryOpen(true);
+ };
+
+ // 从角色库中选择角色
+ const handleSelectCharacter = (index: number) => {
+ console.log('index', index);
+ setIsReplaceLibraryOpen(false);
+ // 模拟打开替换面板
+ setTimeout(() => {
+ setIsReplacePanelOpen(true);
+ }, 1000);
+ };
+
+ // 确认替换角色
+ const handleConfirmReplace = (selectedShots: string[], addToLibrary: boolean) => {
+
+ };
+
+ // 切换选择分镜
+ const handleSelectShot = (index: number) => {
+ // 切换前 判断数据是否发生变化
+ onSketchSelect(index);
+ };
+
// 如果没有数据,显示空状态
if (sketches.length === 0) {
return (
@@ -148,9 +145,11 @@ export function ShotTabContent({
>
{/* 分镜缩略图行 */}
-
handleSelectShot(i)}
>
{sketches.map((sketch, index) => (
onSketchSelect(index)}
- whileHover={{ scale: 1.05 }}
- whileTap={{ scale: 0.95 }}
+ whileHover={{ scale: 1.02 }}
+ whileTap={{ scale: 0.98 }}
>
Shot {index + 1}
{/* 鼠标悬浮/移出 显示/隐藏 删除图标 */}
-
+ {/*
console.log('Delete sketch')}
className="text-red-500"
>
-
+
*/}
))}
-
+
{/* 视频描述行 - 单行滚动 */}
-
handleSelectShot(i)}
>
{sketches.map((video, index) => {
const isActive = currentSketchIndex === index;
@@ -204,7 +204,6 @@ export function ShotTabContent({
'flex-shrink-0 cursor-pointer transition-all duration-300',
isActive ? 'text-white' : 'text-white/50 hover:text-white/80'
)}
- onClick={() => onSketchSelect(index)}
initial={false}
animate={{
scale: isActive ? 1.02 : 1,
@@ -221,7 +220,7 @@ export function ShotTabContent({
);
})}
-
+
{/* 渐变遮罩 */}
@@ -251,6 +250,7 @@ export function ShotTabContent({
onScanTimeout={handleScanTimeout}
onScanExit={handleScanTimeout}
onDetectionsChange={handleDetectionsChange}
+ onPersonClick={handlePersonClick}
/>
{/*
- {
- // 可以在这里添加其他逻辑
- console.log('分镜添加成功');
- }} />
+ {
+ // 可以在这里添加其他逻辑
+ console.log('分镜添加成功');
+ }}
+ onCharacterClick={(attrs) => {
+ console.log('attrs', attrs);
+ setIsReplaceLibraryOpen(true);
+ }}
+ />
{/* 重新生成按钮、新增分镜按钮 */}
@@ -357,17 +364,6 @@ export function ShotTabContent({
- {/* 替换视频弹窗 */}
- setIsReplaceModalOpen(false)}
- onVideoSelect={(video) => {
- console.log('Selected video:', video);
- setIsReplaceModalOpen(false);
- }}
- />
-
{/* Media Properties 弹窗 */}
-
+ setIsReplacePanelOpen(false)}
+ >
+ setIsReplacePanelOpen(false)}
+ onConfirm={handleConfirmReplace}
+ />
+
+
+
);
}
\ No newline at end of file