From a40d81331c0bbb523ab869037d49c1abee62695e Mon Sep 17 00:00:00 2001 From: ct Date: Wed, 18 Jun 2025 12:26:07 +0800 Subject: [PATCH] Update --- package-lock.json | 26 ++++ package.json | 1 + resources/js/modules/editor/editor.jsx | 16 ++- .../editor/partials/canvas/video-editor.jsx | 3 +- .../editor/partials/canvas/video-preview.jsx | 115 +++++++++++------- .../modules/editor/partials/editor-canvas.jsx | 8 +- .../editor/partials/editor-controls.jsx | 12 +- 7 files changed, 125 insertions(+), 56 deletions(-) diff --git a/package-lock.json b/package-lock.json index 36680ca..f1174ca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -60,6 +60,7 @@ "react-error-boundary": "^6.0.0", "react-hook-form": "^7.57.0", "react-konva": "^19.0.6", + "react-konva-utils": "^1.1.0", "react-resizable-panels": "^3.0.2", "recharts": "^2.15.3", "simple-zustand-devtools": "^1.1.0", @@ -7274,6 +7275,21 @@ "react-dom": "^18.3.1 || ^19.0.0" } }, + "node_modules/react-konva-utils": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/react-konva-utils/-/react-konva-utils-1.1.0.tgz", + "integrity": "sha512-3ylRN4eUeYU553tmY2Hgi69efrlUBjE8MLXXAQT+rLDBCPW4CwlvJFUCgPQoSEZmaJKTH/gvZz0Y4f8tUfV0rw==", + "license": "MIT", + "dependencies": { + "use-image": "^1.1.1" + }, + "peerDependencies": { + "konva": "^8.3.5 || ^9.0.0", + "react": "^18.2.0 || ^19.0.0", + "react-dom": "^18.2.0 || ^19.0.0", + "react-konva": "^18.2.10 || ^19.0.1" + } + }, "node_modules/react-reconciler": { "version": "0.32.0", "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.32.0.tgz", @@ -8435,6 +8451,16 @@ } } }, + "node_modules/use-image": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/use-image/-/use-image-1.1.4.tgz", + "integrity": "sha512-P+swhszzHHgEb2X2yQ+vQNPCq/8Ks3hyfdXAVN133pvnvK7UK++bUaZUa5E+A3S02Mw8xOCBr9O6CLhk2fjrWA==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, "node_modules/use-sidecar": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", diff --git a/package.json b/package.json index e344180..06f851f 100644 --- a/package.json +++ b/package.json @@ -78,6 +78,7 @@ "react-error-boundary": "^6.0.0", "react-hook-form": "^7.57.0", "react-konva": "^19.0.6", + "react-konva-utils": "^1.1.0", "react-resizable-panels": "^3.0.2", "recharts": "^2.15.3", "simple-zustand-devtools": "^1.1.0", diff --git a/resources/js/modules/editor/editor.jsx b/resources/js/modules/editor/editor.jsx index 9989d15..a894491 100644 --- a/resources/js/modules/editor/editor.jsx +++ b/resources/js/modules/editor/editor.jsx @@ -119,14 +119,11 @@ const Editor = () => { init(); }, []); - // Listen for text element selection + // Listen for text element selection (but don't auto-open sidebar) useEffect(() => { const handleTextElementSelected = (textElement) => { setSelectedTextElement(textElement); - setIsTextSidebarOpen(true); - // Close other sidebars when text sidebar opens - setIsEditSidebarOpen(false); - setIsEditNavSidebarOpen(false); + // Remove automatic sidebar opening - user will click the button instead }; emitter.on('text-element-selected', handleTextElementSelected); @@ -152,6 +149,13 @@ const Editor = () => { setIsEditSidebarOpen(false); }; + const handleTextSidebarOpen = () => { + setIsTextSidebarOpen(true); + // Close other sidebars when text sidebar opens + setIsEditSidebarOpen(false); + setIsEditNavSidebarOpen(false); + }; + const handleTextSidebarClose = () => { setIsTextSidebarOpen(false); setSelectedTextElement(null); @@ -197,7 +201,7 @@ const Editor = () => { ) : ( <> - + { +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} /> diff --git a/resources/js/modules/editor/partials/canvas/video-preview.jsx b/resources/js/modules/editor/partials/canvas/video-preview.jsx index edd5ffd..4ec78cc 100644 --- a/resources/js/modules/editor/partials/canvas/video-preview.jsx +++ b/resources/js/modules/editor/partials/canvas/video-preview.jsx @@ -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 ( -
+
{activeElements.map((element) => { @@ -133,45 +139,72 @@ const VideoPreview = ({ ); } else if (element.type === 'text') { return ( - { - 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 - /> + + { + 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 && ( + + + + )} + ); } else if (element.type === 'image' && element.imageElement && element.isImageReady) { return ( diff --git a/resources/js/modules/editor/partials/editor-canvas.jsx b/resources/js/modules/editor/partials/editor-canvas.jsx index 79a3d2e..9a5ed90 100644 --- a/resources/js/modules/editor/partials/editor-canvas.jsx +++ b/resources/js/modules/editor/partials/editor-canvas.jsx @@ -51,7 +51,7 @@ const useResponsiveCanvas = (maxWidth = 350) => { return scale; }; -const EditorCanvas = ({ maxWidth = 350 }) => { +const EditorCanvas = ({ maxWidth = 350, onOpenTextSidebar }) => { const scale = useResponsiveCanvas(maxWidth); const displayWidth = LAYOUT_CONSTANTS.CANVAS_WIDTH * scale; const displayHeight = LAYOUT_CONSTANTS.CANVAS_HEIGHT * scale; @@ -86,7 +86,11 @@ const EditorCanvas = ({ maxWidth = 350 }) => { console.log(`Canvas coordinates: x=${x}, y=${y}`); }} > - +
diff --git a/resources/js/modules/editor/partials/editor-controls.jsx b/resources/js/modules/editor/partials/editor-controls.jsx index 974c089..d26ca3b 100644 --- a/resources/js/modules/editor/partials/editor-controls.jsx +++ b/resources/js/modules/editor/partials/editor-controls.jsx @@ -4,7 +4,7 @@ import { Button } from '@/components/ui/button'; import { cn } from '@/lib/utils'; import { useMitt } from '@/plugins/MittContext'; import useVideoEditorStore from '@/stores/VideoEditorStore'; -import { Download, Edit3, Play, Square, Type } from 'lucide-react'; +import { Download, Edit3, Play, Square } from 'lucide-react'; const EditorControls = ({ className = '', onEditClick = () => {}, isEditActive = false }) => { const { videoIsPlaying } = useVideoEditorStore(); @@ -28,7 +28,7 @@ const EditorControls = ({ className = '', onEditClick = () => {}, isEditActive = return (
- @@ -40,13 +40,13 @@ const EditorControls = ({ className = '', onEditClick = () => {}, isEditActive = 9:16 */} - + */} -