feat: implement smooth text dragging with boundary constraints

This commit is contained in:
Karan Singh 2025-07-23 13:59:55 +05:30
parent cabf860330
commit b87f470dc4
1 changed files with 104 additions and 5 deletions

View File

@ -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<HTMLDivElement>, 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 (
<div
key={element.id}
className="absolute flex items-center justify-center"
className="absolute flex items-center justify-center cursor-grab"
onMouseDown={(e) => 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