From b87f470dc4716d267f618df14461d2c85d92a7b4 Mon Sep 17 00:00:00 2001 From: Karan Singh Date: Wed, 23 Jul 2025 13:59:55 +0530 Subject: [PATCH] feat: implement smooth text dragging with boundary constraints --- .../src/components/editor/preview-panel.tsx | 109 +++++++++++++++++- 1 file changed, 104 insertions(+), 5 deletions(-) diff --git a/apps/web/src/components/editor/preview-panel.tsx b/apps/web/src/components/editor/preview-panel.tsx index 2cba1374..2359ecd8 100644 --- a/apps/web/src/components/editor/preview-panel.tsx +++ b/apps/web/src/components/editor/preview-panel.tsx @@ -31,7 +31,7 @@ interface ActiveElement { } export function PreviewPanel() { - const { tracks, getTotalDuration } = useTimelineStore(); + const { tracks, getTotalDuration, updateTextElement } = useTimelineStore(); const { mediaItems } = useMediaStore(); const { currentTime, toggle, setCurrentTime, isPlaying } = usePlaybackStore(); const { canvasSize } = useEditorStore(); @@ -43,6 +43,33 @@ export function PreviewPanel() { }); const [isExpanded, setIsExpanded] = useState(false); const { activeProject } = useProjectStore(); + + // Drag state for text elements + const [dragState, setDragState] = useState<{ + isDragging: boolean; + elementId: string | null; + trackId: string | null; + startX: number; + startY: number; + initialElementX: number; + initialElementY: number; + currentX: number; + currentY: number; + elementWidth: number; + elementHeight: number; + }>({ + isDragging: false, + elementId: null, + trackId: null, + startX: 0, + startY: 0, + initialElementX: 0, + initialElementY: 0, + currentX: 0, + currentY: 0, + elementWidth: 0, + elementHeight: 0, + }); useEffect(() => { const updatePreviewSize = () => { @@ -129,6 +156,77 @@ export function PreviewPanel() { }; }, [isExpanded]); + // Handle mouse events for text dragging + useEffect(() => { + const handleMouseMove = (e: MouseEvent) => { + if (!dragState.isDragging) return; + + const deltaX = e.clientX - dragState.startX; + const deltaY = e.clientY - dragState.startY; + + const scaleRatio = previewDimensions.width / canvasSize.width; + const newX = dragState.initialElementX + deltaX / scaleRatio; + const newY = dragState.initialElementY + deltaY / scaleRatio; + + const halfWidth = (dragState.elementWidth / scaleRatio) / 2; + const halfHeight = (dragState.elementHeight / scaleRatio) / 2; + + const constrainedX = Math.max(-canvasSize.width / 2 + halfWidth, Math.min(canvasSize.width / 2 - halfWidth, newX)); + const constrainedY = Math.max(-canvasSize.height / 2 + halfHeight, Math.min(canvasSize.height / 2 - halfHeight, newY)); + + setDragState((prev) => ({ + ...prev, + currentX: constrainedX, + currentY: constrainedY, + })); + }; + + const handleMouseUp = () => { + if (dragState.isDragging && dragState.trackId && dragState.elementId) { + updateTextElement(dragState.trackId, dragState.elementId, { + x: dragState.currentX, + y: dragState.currentY, + }); + } + setDragState((prev) => ({ ...prev, isDragging: false })); + }; + + if (dragState.isDragging) { + document.addEventListener('mousemove', handleMouseMove); + document.addEventListener('mouseup', handleMouseUp); + document.body.style.cursor = 'grabbing'; + document.body.style.userSelect = 'none'; + } + + return () => { + document.removeEventListener('mousemove', handleMouseMove); + document.removeEventListener('mouseup', handleMouseUp); + document.body.style.cursor = ''; + document.body.style.userSelect = ''; + }; + }, [dragState, previewDimensions, canvasSize, updateTextElement]); + + const handleTextMouseDown = (e: React.MouseEvent, element: any, trackId: string) => { + e.preventDefault(); + e.stopPropagation(); + + const rect = e.currentTarget.getBoundingClientRect(); + + setDragState({ + isDragging: true, + elementId: element.id, + trackId: trackId, + startX: e.clientX, + startY: e.clientY, + initialElementX: element.x, + initialElementY: element.y, + currentX: element.x, + currentY: element.y, + elementWidth: rect.width, + elementHeight: rect.height, + }); + }; + const toggleExpanded = useCallback(() => { setIsExpanded((prev) => !prev); }, []); @@ -151,7 +249,7 @@ export function PreviewPanel() { element.mediaId === "test" ? null : mediaItems.find((item) => item.id === element.mediaId) || - null; + null; } activeElements.push({ element, track, mediaItem }); } @@ -256,10 +354,11 @@ export function PreviewPanel() { return (
handleTextMouseDown(e, element, elementData.track.id)} style={{ - left: `${50 + (element.x / canvasSize.width) * 100}%`, - top: `${50 + (element.y / canvasSize.height) * 100}%`, + left: `${50 + ((dragState.isDragging && dragState.elementId === element.id ? dragState.currentX : element.x) / canvasSize.width) * 100}%`, + top: `${50 + ((dragState.isDragging && dragState.elementId === element.id ? dragState.currentY : element.y) / canvasSize.height) * 100}%`, transform: `translate(-50%, -50%) rotate(${element.rotation}deg) scale(${scaleRatio})`, opacity: element.opacity, zIndex: 100 + index, // Text elements on top