This commit is contained in:
ct
2025-06-18 12:26:07 +08:00
parent aeb8fd6000
commit a40d81331c
7 changed files with 125 additions and 56 deletions

View File

@@ -7,7 +7,7 @@ import { generateTimelineFromTemplate } from '../../utils/timeline-template-proc
import useVideoExport from './video-export';
import VideoPreview from './video-preview';
const VideoEditor = ({ width, height }) => {
const VideoEditor = ({ width, height, onOpenTextSidebar }) => {
const [showConsoleLogs] = useState(true);
const [dimensions] = useState({
@@ -586,6 +586,7 @@ const VideoEditor = ({ width, height }) => {
copyFFmpegCommand={copyFFmpegCommand}
exportVideo={exportVideo}
onElementUpdate={handleElementUpdate}
onOpenTextSidebar={onOpenTextSidebar}
layerRef={layerRef}
/>
</div>

View File

@@ -1,6 +1,10 @@
import { Button } from '@/components/ui/button';
import { useMitt } from '@/plugins/MittContext';
import useVideoEditorStore from '@/stores/VideoEditorStore';
import { Type } from 'lucide-react';
import { useEffect, useRef } from 'react';
import { Image, Layer, Line, Stage, Text, Transformer } from 'react-konva';
import { Group, Image, Layer, Line, Stage, Text, Transformer } from 'react-konva';
import { Html } from 'react-konva-utils';
// Import our custom hooks and utilities
import { useElementSelection } from './video-preview/video-preview-element-selection';
@@ -38,11 +42,13 @@ const VideoPreview = ({
copyFFmpegCommand,
exportVideo,
onElementUpdate, // New prop for updating element properties
onOpenTextSidebar, // New prop for opening text sidebar
// Refs
layerRef,
}) => {
const emitter = useMitt();
const { selectedTextElement } = useVideoEditorStore();
// Refs for elements and transformer
const transformerRef = useRef(null);
@@ -87,7 +93,7 @@ const VideoPreview = ({
}, [selectedElementId, activeElements]);
return (
<div>
<div className="relative">
<Stage width={dimensions.width} height={dimensions.height} ref={stageRef} onClick={handleStageClick} onTap={handleStageClick}>
<Layer ref={layerRef}>
{activeElements.map((element) => {
@@ -133,45 +139,72 @@ const VideoPreview = ({
);
} else if (element.type === 'text') {
return (
<Text
key={element.id}
ref={(node) => {
if (node) {
elementRefs.current[element.id] = node;
}
}}
text={element.text}
x={element.x}
y={element.y}
fontSize={element.fontSize}
fontStyle={getTextFontStyle(element)}
fontFamily={element.fontFamily || 'Arial'}
fill={element.fill || '#ffffff'}
stroke={element.strokeWidth > 0 ? element.stroke || '#000000' : undefined}
strokeWidth={element.strokeWidth * 3 || 0}
fillAfterStrokeEnabled={true}
strokeScaleEnabled={false}
rotation={element.rotation || 0}
// Center the text horizontally
align="center"
verticalAlign="middle"
// Let text have natural width and height for multiline support
wrap="word"
// Always scale 1 - size changes go through fontSize
scaleX={1}
scaleY={1}
draggable
dragBoundFunc={createDragBoundFunc(element.id)}
onClick={() => handleElementSelect(element.id)}
onTap={() => handleElementSelect(element.id)}
onDragMove={(e) => handleDragMove(element.id, e)}
onDragEnd={(e) => handleDragEnd(element.id, e)}
onTransform={() => handleTransform(element.id)}
// Apply fixedWidth and offsetX if they exist
width={element.fixedWidth}
offsetX={element.offsetX}
// Visual feedback for selection
/>
<Group key={element.id}>
<Text
ref={(node) => {
if (node) {
elementRefs.current[element.id] = node;
}
}}
text={element.text}
x={element.x}
y={element.y}
fontSize={element.fontSize}
fontStyle={getTextFontStyle(element)}
fontFamily={element.fontFamily || 'Arial'}
fill={element.fill || '#ffffff'}
stroke={element.strokeWidth > 0 ? element.stroke || '#000000' : undefined}
strokeWidth={element.strokeWidth * 3 || 0}
fillAfterStrokeEnabled={true}
strokeScaleEnabled={false}
rotation={element.rotation || 0}
// Center the text horizontally
align="center"
verticalAlign="middle"
// Let text have natural width and height for multiline support
wrap="word"
// Always scale 1 - size changes go through fontSize
scaleX={1}
scaleY={1}
draggable
dragBoundFunc={createDragBoundFunc(element.id)}
onClick={() => handleElementSelect(element.id)}
onTap={() => handleElementSelect(element.id)}
onDragMove={(e) => handleDragMove(element.id, e)}
onDragEnd={(e) => handleDragEnd(element.id, e)}
onTransform={() => handleTransform(element.id)}
// Apply fixedWidth and offsetX if they exist
width={element.fixedWidth}
offsetX={element.offsetX}
// Visual feedback for selection
/>
{/* Edit button - only show when this text element is selected */}
{isSelected && (
<Html
groupProps={{
x: element.x - (element.offsetX || 0),
y: element.y - 50,
}}
divProps={{
style: {
zIndex: 10,
},
}}
>
<Button
size="icon"
className="h-12 w-12 rounded-full border shadow-sm"
onClick={() => {
handleElementSelect(element.id);
onOpenTextSidebar();
}}
>
<Type className="h-8 w-8" />
</Button>
</Html>
)}
</Group>
);
} else if (element.type === 'image' && element.imageElement && element.isImageReady) {
return (