forked from 77media/video-flow
重新设计镜头页面
This commit is contained in:
parent
46bda04605
commit
250fa8441e
182
components/ui/shot-editor/ShotEditor copy.tsx
Normal file
182
components/ui/shot-editor/ShotEditor copy.tsx
Normal file
@ -0,0 +1,182 @@
|
||||
import React, { useState, useCallback, useEffect } from 'react';
|
||||
import { EditorContent, useEditor } from '@tiptap/react';
|
||||
import StarterKit from '@tiptap/starter-kit';
|
||||
import { motion } from "framer-motion";
|
||||
import { CharacterTokenExtension } from './CharacterToken';
|
||||
import { ShotTitle } from './ShotTitle';
|
||||
import { ReadonlyText } from './ReadonlyText';
|
||||
import { Sparkles } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
const initialContent = {
|
||||
type: 'doc',
|
||||
content: [
|
||||
{
|
||||
type: 'shotTitle',
|
||||
attrs: { title: `分镜1` },
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
content: [
|
||||
{ type: 'text', text: '镜头聚焦在' },
|
||||
{ type: 'characterToken', attrs: { name: '"沙利"·沙利文中士', gender: '男', age: '28', avatar: 'https://i.pravatar.cc/40?u=z3' }},
|
||||
{ type: 'text', text: ' 愤怒的脸上,他被压制住了。他发出一声绝望的吼叫,盖过了枪声。' },
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
content: [
|
||||
{ type: 'shotTitle', attrs: { title: `对话` } },
|
||||
{ type: 'characterToken', attrs: { name: '"沙利"·沙利文中士', gender: '男', age: '28', avatar: 'https://i.pravatar.cc/40?u=z3' }},
|
||||
{ type: 'text', text: ':' },
|
||||
{ type: 'text', text: '掩护!趴下!' }
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'shotTitle',
|
||||
attrs: { title: `分镜2` },
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
content: [
|
||||
{ type: 'characterToken', attrs: { name: '李四', gender: '女', age: '26', avatar: 'https://i.pravatar.cc/40?u=l4' }},
|
||||
{ type: 'text', text: ' 微微低头,没有说话。' }
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
interface ShotEditorProps {
|
||||
roles?: any[];
|
||||
onAddSegment?: () => void;
|
||||
onCharacterClick?: (attrs: any) => void;
|
||||
}
|
||||
|
||||
declare module '@tiptap/core' {
|
||||
interface Commands<ReturnType> {
|
||||
characterToken: {
|
||||
setCharacterToken: (attrs: any) => ReturnType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const ShotEditor = React.forwardRef<{ addSegment: () => void, onCharacterClick: (attrs: any) => void }, ShotEditorProps>(
|
||||
function ShotEditor({ onAddSegment, onCharacterClick, roles }, ref) {
|
||||
const [segments, setSegments] = useState(initialContent.content);
|
||||
const [isOptimizing, setIsOptimizing] = useState(false);
|
||||
|
||||
const handleSmartPolish = () => {
|
||||
setIsOptimizing(true);
|
||||
setTimeout(() => {
|
||||
setIsOptimizing(false);
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
const editor = useEditor({
|
||||
extensions: [
|
||||
StarterKit,
|
||||
CharacterTokenExtension.configure({
|
||||
roles
|
||||
}),
|
||||
ShotTitle,
|
||||
ReadonlyText,
|
||||
],
|
||||
content: { type: 'doc', content: segments },
|
||||
editorProps: {
|
||||
attributes: {
|
||||
class: 'prose prose-invert max-w-none min-h-[150px] focus:outline-none'
|
||||
}
|
||||
},
|
||||
immediatelyRender: false,
|
||||
onCreate: ({ editor }) => {
|
||||
editor.setOptions({ editable: true })
|
||||
},
|
||||
})
|
||||
|
||||
const addSegment = () => {
|
||||
if (!editor) return;
|
||||
|
||||
// 自动编号(获取已有 shotTitle 节点数量)
|
||||
const doc = editor.state.doc;
|
||||
let shotCount = 0;
|
||||
doc.descendants((node) => {
|
||||
if (node.type.name === 'shotTitle' && node.attrs.title.includes('分镜')) {
|
||||
shotCount++;
|
||||
}
|
||||
});
|
||||
|
||||
// 不能超过4个分镜
|
||||
if (shotCount >= 4) {
|
||||
toast.error('不能超过4个分镜', {
|
||||
duration: 3000,
|
||||
position: 'top-center',
|
||||
richColors: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
editor.chain().focus('end').insertContent([
|
||||
{
|
||||
type: 'shotTitle',
|
||||
attrs: { title: `分镜${shotCount + 1}` },
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
content: [
|
||||
{ type: 'text', text: '镜头描述' }
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'shotTitle',
|
||||
attrs: { title: `对话` },
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
content: [
|
||||
{ type: 'characterToken', attrs: { name: '讲话人', gender: '女', age: '26', avatar: 'https://i.pravatar.cc/40?u=l4' }},
|
||||
{ type: 'text', text: ':' },
|
||||
{ type: 'text', text: '讲话内容' }
|
||||
]
|
||||
}
|
||||
])
|
||||
.focus('end') // 聚焦到文档末尾
|
||||
.run();
|
||||
|
||||
// 调用外部传入的回调函数
|
||||
onAddSegment?.();
|
||||
};
|
||||
|
||||
// 暴露方法给父组件
|
||||
React.useImperativeHandle(ref, () => ({
|
||||
addSegment,
|
||||
onCharacterClick: (attrs: any) => {
|
||||
onCharacterClick?.(attrs);
|
||||
}
|
||||
}));
|
||||
|
||||
if (!editor) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full relative p-[0.5rem] pb-[2.5rem] border border-white/10 rounded-[0.5rem]">
|
||||
<EditorContent editor={editor} />
|
||||
{/* 智能润色按钮 */}
|
||||
<motion.button
|
||||
onClick={handleSmartPolish}
|
||||
disabled={isOptimizing}
|
||||
className="absolute bottom-3 right-3 flex items-center gap-1.5 px-3 py-1.5
|
||||
bg-purple-500/10 hover:bg-purple-500/20 text-purple-500 rounded-full
|
||||
transition-colors text-xs disabled:opacity-50"
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
>
|
||||
<Sparkles className="w-3.5 h-3.5" />
|
||||
<span>{isOptimizing ? "优化中..." : "智能优化"}</span>
|
||||
</motion.button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
export default ShotEditor;
|
||||
@ -47,8 +47,8 @@ const initialContent = {
|
||||
};
|
||||
|
||||
interface ShotEditorProps {
|
||||
content: any[];
|
||||
roles?: any[];
|
||||
onAddSegment?: () => void;
|
||||
onCharacterClick?: (attrs: any) => void;
|
||||
}
|
||||
|
||||
@ -60,9 +60,25 @@ declare module '@tiptap/core' {
|
||||
}
|
||||
}
|
||||
|
||||
const ShotEditor = React.forwardRef<{ addSegment: () => void, onCharacterClick: (attrs: any) => void }, ShotEditorProps>(
|
||||
function ShotEditor({ onAddSegment, onCharacterClick, roles }, ref) {
|
||||
const [segments, setSegments] = useState(initialContent.content);
|
||||
interface CharacterToken {
|
||||
type: 'characterToken';
|
||||
attrs: {
|
||||
name: string;
|
||||
gender: string;
|
||||
age: string;
|
||||
avatar: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface EditorRef {
|
||||
editor: any;
|
||||
insertCharacter: (character: CharacterToken) => void;
|
||||
insertContent: (content: any) => void;
|
||||
}
|
||||
|
||||
const ShotEditor = React.forwardRef<EditorRef, ShotEditorProps>(
|
||||
function ShotEditor({ content, onCharacterClick, roles }, ref) {
|
||||
const [segments, setSegments] = useState(content);
|
||||
const [isOptimizing, setIsOptimizing] = useState(false);
|
||||
|
||||
const handleSmartPolish = () => {
|
||||
@ -84,7 +100,7 @@ const ShotEditor = React.forwardRef<{ addSegment: () => void, onCharacterClick:
|
||||
content: { type: 'doc', content: segments },
|
||||
editorProps: {
|
||||
attributes: {
|
||||
class: 'prose prose-invert max-w-none min-h-[150px] focus:outline-none'
|
||||
class: 'prose prose-invert max-w-none focus:outline-none'
|
||||
}
|
||||
},
|
||||
immediatelyRender: false,
|
||||
@ -93,64 +109,18 @@ const ShotEditor = React.forwardRef<{ addSegment: () => void, onCharacterClick:
|
||||
},
|
||||
})
|
||||
|
||||
const addSegment = () => {
|
||||
if (!editor) return;
|
||||
|
||||
// 自动编号(获取已有 shotTitle 节点数量)
|
||||
const doc = editor.state.doc;
|
||||
let shotCount = 0;
|
||||
doc.descendants((node) => {
|
||||
if (node.type.name === 'shotTitle' && node.attrs.title.includes('分镜')) {
|
||||
shotCount++;
|
||||
}
|
||||
});
|
||||
|
||||
// 不能超过4个分镜
|
||||
if (shotCount >= 4) {
|
||||
toast.error('不能超过4个分镜', {
|
||||
duration: 3000,
|
||||
position: 'top-center',
|
||||
richColors: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
editor.chain().focus('end').insertContent([
|
||||
{
|
||||
type: 'shotTitle',
|
||||
attrs: { title: `分镜${shotCount + 1}` },
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
content: [
|
||||
{ type: 'text', text: '镜头描述' }
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'shotTitle',
|
||||
attrs: { title: `对话` },
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
content: [
|
||||
{ type: 'characterToken', attrs: { name: '讲话人', gender: '女', age: '26', avatar: 'https://i.pravatar.cc/40?u=l4' }},
|
||||
{ type: 'text', text: ':' },
|
||||
{ type: 'text', text: '讲话内容' }
|
||||
]
|
||||
}
|
||||
])
|
||||
.focus('end') // 聚焦到文档末尾
|
||||
.run();
|
||||
|
||||
// 调用外部传入的回调函数
|
||||
onAddSegment?.();
|
||||
};
|
||||
|
||||
// 暴露方法给父组件
|
||||
React.useImperativeHandle(ref, () => ({
|
||||
addSegment,
|
||||
onCharacterClick: (attrs: any) => {
|
||||
onCharacterClick?.(attrs);
|
||||
editor,
|
||||
insertCharacter: (character: CharacterToken) => {
|
||||
editor?.commands.insertContent([
|
||||
{ type: 'text', text: ' ' },
|
||||
character,
|
||||
{ type: 'text', text: ' ' }
|
||||
]);
|
||||
},
|
||||
insertContent: (content: any) => {
|
||||
editor?.commands.insertContent(content);
|
||||
}
|
||||
}));
|
||||
|
||||
@ -159,22 +129,7 @@ const ShotEditor = React.forwardRef<{ addSegment: () => void, onCharacterClick:
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full relative p-[0.5rem] pb-[2.5rem] border border-white/10 rounded-[0.5rem]">
|
||||
<EditorContent editor={editor} />
|
||||
{/* 智能润色按钮 */}
|
||||
<motion.button
|
||||
onClick={handleSmartPolish}
|
||||
disabled={isOptimizing}
|
||||
className="absolute bottom-3 right-3 flex items-center gap-1.5 px-3 py-1.5
|
||||
bg-purple-500/10 hover:bg-purple-500/20 text-purple-500 rounded-full
|
||||
transition-colors text-xs disabled:opacity-50"
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
>
|
||||
<Sparkles className="w-3.5 h-3.5" />
|
||||
<span>{isOptimizing ? "优化中..." : "智能优化"}</span>
|
||||
</motion.button>
|
||||
</div>
|
||||
<EditorContent editor={editor} />
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
271
components/ui/shot-editor/ShotsEditor.tsx
Normal file
271
components/ui/shot-editor/ShotsEditor.tsx
Normal file
@ -0,0 +1,271 @@
|
||||
import React, { forwardRef, useRef, useState } from "react";
|
||||
import { Plus, X, UserRoundPlus, MessageCirclePlus, MessageCircleMore, ClipboardType } from "lucide-react";
|
||||
import ShotEditor from "./ShotEditor";
|
||||
import { toast } from "sonner";
|
||||
|
||||
interface Shot {
|
||||
id: string;
|
||||
shotDescContent: any[];
|
||||
shotDialogsContent: any[];
|
||||
}
|
||||
|
||||
interface CharacterToken {
|
||||
type: 'characterToken';
|
||||
attrs: {
|
||||
name: string;
|
||||
gender: string;
|
||||
age: string;
|
||||
avatar: string;
|
||||
};
|
||||
}
|
||||
|
||||
const mockShotsData = [
|
||||
{
|
||||
id: 'shot1',
|
||||
shotDescContent: [{
|
||||
type: 'paragraph',
|
||||
content: [
|
||||
{ type: 'text', text: '镜头聚焦在' },
|
||||
{ type: 'characterToken', attrs: { name: '"沙利"·沙利文中士', gender: '男', age: '28', avatar: 'https://i.pravatar.cc/40?u=z3' }},
|
||||
{ type: 'text', text: ' 愤怒的脸上,他被压制住了。他发出一声绝望的吼叫,盖过了枪声。' },
|
||||
]
|
||||
}],
|
||||
shotDialogsContent: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
content: [
|
||||
{ type: 'characterToken', attrs: { name: '李四', gender: '女', age: '26', avatar: 'https://i.pravatar.cc/40?u=l4' }},
|
||||
{ type: 'text', text: ' 微微低头,没有说话。' }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
const createEmptyShot = (): Shot => ({
|
||||
id: `shot${Date.now()}`,
|
||||
shotDescContent: [{
|
||||
type: 'paragraph',
|
||||
content: [
|
||||
{ type: 'text', text: '在这里添加分镜描述...' }
|
||||
]
|
||||
}],
|
||||
shotDialogsContent: [{
|
||||
type: 'paragraph',
|
||||
content: [
|
||||
{ type: 'text', text: '在这里添加分镜对话...' }
|
||||
]
|
||||
}]
|
||||
});
|
||||
|
||||
interface ShotsEditorProps {
|
||||
roles: any[];
|
||||
}
|
||||
|
||||
export const ShotsEditor = forwardRef<any, ShotsEditorProps>(({ roles }, ref) => {
|
||||
const [currentShotIndex, setCurrentShotIndex] = useState(0);
|
||||
const [shots, setShots] = useState<Shot[]>(mockShotsData);
|
||||
const descEditorRef = useRef<any>(null);
|
||||
const dialogEditorRef = useRef<any>(null);
|
||||
|
||||
const addShot = () => {
|
||||
if (shots.length > 3) {
|
||||
toast.error('不能超过4个分镜', {
|
||||
duration: 3000,
|
||||
position: 'top-center',
|
||||
richColors: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
const newShot = createEmptyShot();
|
||||
setShots([...shots, newShot]);
|
||||
// onShotsChange([...shots, newShot]);
|
||||
// 自动切换到新创建的分镜
|
||||
setCurrentShotIndex(shots.length);
|
||||
};
|
||||
|
||||
const handleDeleteShot = (index: number) => {
|
||||
if (shots.length <= 1) return; // 保留最后一个分镜
|
||||
|
||||
const newShots = shots.filter((_, i) => i !== index);
|
||||
setShots(newShots);
|
||||
// onShotsChange(newShots);
|
||||
|
||||
// 如果删除的是当前选中的分镜,或者删除的是最后一个分镜
|
||||
if (currentShotIndex === index || currentShotIndex >= newShots.length) {
|
||||
setCurrentShotIndex(Math.max(0, newShots.length - 1));
|
||||
} else if (currentShotIndex > index) {
|
||||
// 如果删除的分镜在当前选中分镜之前,需要更新索引
|
||||
setCurrentShotIndex(currentShotIndex - 1);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddCharacterToDesc = () => {
|
||||
if (!descEditorRef.current) return;
|
||||
|
||||
// 创建一个默认角色Token
|
||||
const defaultCharacter: CharacterToken = {
|
||||
type: 'characterToken',
|
||||
attrs: {
|
||||
name: '新角色',
|
||||
gender: '男',
|
||||
age: '25',
|
||||
avatar: 'https://i.pravatar.cc/40'
|
||||
}
|
||||
};
|
||||
|
||||
// 在当前位置插入角色Token
|
||||
descEditorRef.current.insertCharacter(defaultCharacter);
|
||||
};
|
||||
|
||||
const handleAddCharacterToDialog = () => {
|
||||
if (!dialogEditorRef.current) return;
|
||||
|
||||
// 创建一个默认角色Token
|
||||
const defaultCharacter: CharacterToken = {
|
||||
type: 'characterToken',
|
||||
attrs: {
|
||||
name: '新角色',
|
||||
gender: '男',
|
||||
age: '25',
|
||||
avatar: 'https://i.pravatar.cc/40'
|
||||
}
|
||||
};
|
||||
|
||||
// 在当前位置插入角色Token
|
||||
dialogEditorRef.current.insertCharacter(defaultCharacter);
|
||||
};
|
||||
|
||||
const handleAddNewDialog = () => {
|
||||
if (!dialogEditorRef.current) return;
|
||||
|
||||
// 创建一个新的对话行
|
||||
const newDialog = {
|
||||
type: 'paragraph',
|
||||
content: [
|
||||
{
|
||||
type: 'characterToken',
|
||||
attrs: {
|
||||
name: '新角色',
|
||||
gender: '男',
|
||||
age: '25',
|
||||
avatar: 'https://i.pravatar.cc/40'
|
||||
}
|
||||
},
|
||||
{ type: 'text', text: ' 说道:' }
|
||||
]
|
||||
};
|
||||
|
||||
// 在编辑器末尾添加新对话
|
||||
dialogEditorRef.current.editor?.commands.focus('end');
|
||||
dialogEditorRef.current.insertContent([
|
||||
{ type: 'text', text: '\n' },
|
||||
newDialog
|
||||
]);
|
||||
};
|
||||
|
||||
// 暴露方法给父组件
|
||||
React.useImperativeHandle(ref, () => ({
|
||||
addShot,
|
||||
}));
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
{/* 分镜标签(可删除)、新增分镜标签 */}
|
||||
<div data-alt="shots-tabs" className="flex items-center gap-2">
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
{shots.map((shot, index) => (
|
||||
<div
|
||||
key={shot.id}
|
||||
data-alt="shot-tab"
|
||||
className={`group flex items-center gap-2 px-3 py-1.5 rounded-md cursor-pointer transition-all
|
||||
${currentShotIndex === index
|
||||
? 'bg-blue-500/10 text-blue-500'
|
||||
: 'text-white/60 bg-white/5'
|
||||
}`}
|
||||
onClick={() => setCurrentShotIndex(index)}
|
||||
>
|
||||
<span className="text-sm font-medium">镜头{index + 1}</span>
|
||||
{shots.length > 1 && (
|
||||
<button
|
||||
className="p-0.5 rounded hover:bg-white/10 transition-colors"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleDeleteShot(index);
|
||||
}}
|
||||
>
|
||||
<X className="w-3.5 h-3.5 text-red-400 hidden group-hover:block transition-all" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* <button
|
||||
data-alt="add-shot-button"
|
||||
onClick={handleAddShot}
|
||||
className="flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium text-white/60 hover:text-white hover:bg-white/5 rounded-md transition-colors"
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
新增镜头
|
||||
</button> */}
|
||||
</div>
|
||||
|
||||
{/* 分镜内容 */}
|
||||
<div className="flex flex-col gap-3 border border-white/10 p-2 rounded-[0.5rem]" key={currentShotIndex}>
|
||||
{/* 分镜描述 添加角色 */}
|
||||
<div data-alt="shot-description-section" className="flex flex-col gap-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<ClipboardType className="w-5 h-5 text-white/60" />
|
||||
<span className="text-lg font-medium text-white/60">分镜描述</span>
|
||||
<button
|
||||
data-alt="add-character-desc"
|
||||
className="p-1 rounded-md"
|
||||
onClick={() => handleAddCharacterToDesc()}
|
||||
>
|
||||
<UserRoundPlus className="w-5 h-5 text-blue-600" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 分镜描述内容 可视化编辑 */}
|
||||
<ShotEditor
|
||||
ref={descEditorRef}
|
||||
content={shots[currentShotIndex].shotDescContent}
|
||||
onCharacterClick={() => {}}
|
||||
roles={roles}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 分镜对话 添加角色 添加对话 */}
|
||||
<div data-alt="shot-dialog-section" className="flex flex-col gap-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<MessageCircleMore className="w-5 h-5 text-white/60" />
|
||||
<span className="text-lg font-medium text-white/60">分镜对话</span>
|
||||
<button
|
||||
data-alt="add-character-dialog"
|
||||
className="p-1 rounded-md"
|
||||
onClick={() => handleAddCharacterToDialog()}
|
||||
>
|
||||
<UserRoundPlus className="w-5 h-5 text-blue-600" />
|
||||
</button>
|
||||
<button
|
||||
data-alt="add-new-dialog"
|
||||
className="p-1 rounded-md"
|
||||
onClick={() => handleAddNewDialog()}
|
||||
>
|
||||
<MessageCirclePlus className="w-5 h-5 text-blue-600" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 分镜对话内容 可视化编辑 */}
|
||||
<ShotEditor
|
||||
ref={dialogEditorRef}
|
||||
content={shots[currentShotIndex].shotDialogsContent}
|
||||
onCharacterClick={() => {}}
|
||||
roles={roles}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
@ -9,7 +9,7 @@ import { ReplaceVideoModal } from './replace-video-modal';
|
||||
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 { ShotsEditor } from './shot-editor/ShotsEditor';
|
||||
import { CharacterLibrarySelector } from './character-library-selector';
|
||||
import FloatingGlassPanel from './FloatingGlassPanel';
|
||||
import { ReplaceCharacterPanel, mockShots, mockCharacter } from './replace-character-panel';
|
||||
@ -43,6 +43,8 @@ export function ShotTabContent({
|
||||
|
||||
const [shots, setShots] = useState<any[]>([]);
|
||||
|
||||
const shotsEditorRef = useRef<any>(null);
|
||||
|
||||
|
||||
// 监听外部播放状态变化
|
||||
useEffect(() => {
|
||||
@ -123,6 +125,12 @@ export function ShotTabContent({
|
||||
|
||||
};
|
||||
|
||||
// 新增分镜
|
||||
const handleAddShot = () => {
|
||||
console.log('add shot');
|
||||
shotsEditorRef.current.addShot();
|
||||
};
|
||||
|
||||
// 切换选择分镜
|
||||
const handleSelectShot = (index: number) => {
|
||||
// 切换前 判断数据是否发生变化
|
||||
@ -326,23 +334,15 @@ export function ShotTabContent({
|
||||
|
||||
{/* 基础配置 */}
|
||||
<div className='space-y-4 col-span-1'>
|
||||
<ShotEditor
|
||||
ref={editorRef}
|
||||
<ShotsEditor
|
||||
ref={shotsEditorRef}
|
||||
roles={roles}
|
||||
onAddSegment={() => {
|
||||
// 可以在这里添加其他逻辑
|
||||
console.log('分镜添加成功');
|
||||
}}
|
||||
onCharacterClick={(attrs) => {
|
||||
console.log('attrs', attrs);
|
||||
setIsReplaceLibraryOpen(true);
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* 重新生成按钮、新增分镜按钮 */}
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<motion.button
|
||||
onClick={() => editorRef.current?.addSegment()}
|
||||
onClick={() => handleAddShot()}
|
||||
className="flex items-center justify-center gap-2 px-4 py-3 bg-pink-500/10 hover:bg-pink-500/20
|
||||
text-pink-500 rounded-lg transition-colors"
|
||||
whileHover={{ scale: 1.02 }}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user