import React, { useState, useRef, useCallback, useEffect } from "react"; import { cn } from "@/lib/utils"; import { Minus, Square, X, Maximize2, Minimize2 } from "lucide-react"; import { useTranslation } from "react-i18next"; interface DraggableWindowProps { title: string; children: React.ReactNode; initialX?: number; initialY?: number; initialWidth?: number; initialHeight?: number; minWidth?: number; minHeight?: number; onClose: () => void; onMinimize?: () => void; onMaximize?: () => void; isMaximized?: boolean; zIndex?: number; onFocus?: () => void; targetSize?: { width: number; height: number }; } export function DraggableWindow({ title, children, initialX = 100, initialY = 100, initialWidth = 600, initialHeight = 400, minWidth = 300, minHeight = 200, onClose, onMinimize, onMaximize, isMaximized = false, zIndex = 1000, onFocus, targetSize, }: DraggableWindowProps) { const { t } = useTranslation(); const [position, setPosition] = useState({ x: initialX, y: initialY }); const [size, setSize] = useState({ width: initialWidth, height: initialHeight, }); const [isDragging, setIsDragging] = useState(false); const [isResizing, setIsResizing] = useState(false); const [resizeDirection, setResizeDirection] = useState(""); const [dragStart, setDragStart] = useState({ x: 0, y: 0 }); const [windowStart, setWindowStart] = useState({ x: 0, y: 0 }); const [sizeStart, setSizeStart] = useState({ width: 0, height: 0 }); const windowRef = useRef(null); const titleBarRef = useRef(null); useEffect(() => { if (targetSize && !isMaximized) { const maxWidth = Math.min(window.innerWidth * 0.9, 1200); const maxHeight = Math.min(window.innerHeight * 0.8, 800); let newWidth = Math.min(targetSize.width + 50, maxWidth); let newHeight = Math.min(targetSize.height + 150, maxHeight); if (newWidth > maxWidth || newHeight > maxHeight) { const widthRatio = maxWidth / newWidth; const heightRatio = maxHeight / newHeight; const scale = Math.min(widthRatio, heightRatio); newWidth = Math.floor(newWidth * scale); newHeight = Math.floor(newHeight * scale); } newWidth = Math.max(newWidth, minWidth); newHeight = Math.max(newHeight, minHeight); setSize({ width: newWidth, height: newHeight }); setPosition({ x: Math.max(0, (window.innerWidth - newWidth) / 2), y: Math.max(0, (window.innerHeight - newHeight) / 2), }); } }, [targetSize, isMaximized, minWidth, minHeight]); const handleWindowClick = useCallback(() => { onFocus?.(); }, [onFocus]); const handleMouseDown = useCallback( (e: React.MouseEvent) => { if (isMaximized) return; e.preventDefault(); setIsDragging(true); setDragStart({ x: e.clientX, y: e.clientY }); setWindowStart({ x: position.x, y: position.y }); onFocus?.(); }, [isMaximized, position, onFocus], ); const handleMouseMove = useCallback( (e: MouseEvent) => { if (isDragging && !isMaximized) { const deltaX = e.clientX - dragStart.x; const deltaY = e.clientY - dragStart.y; const newX = windowStart.x + deltaX; const newY = windowStart.y + deltaY; const windowElement = windowRef.current; let positioningContainer = null; let currentElement = windowElement?.parentElement; while (currentElement && currentElement !== document.body) { const computedStyle = window.getComputedStyle(currentElement); const position = computedStyle.position; const transform = computedStyle.transform; if ( position === "relative" || position === "absolute" || position === "fixed" || transform !== "none" ) { positioningContainer = currentElement; break; } currentElement = currentElement.parentElement; } let maxX, maxY, minX, minY; if (positioningContainer) { const containerRect = positioningContainer.getBoundingClientRect(); maxX = containerRect.width - size.width; maxY = containerRect.height - size.height; minX = 0; minY = 0; } else { maxX = window.innerWidth - size.width; maxY = window.innerHeight - size.height; minX = 0; minY = 0; } const constrainedX = Math.max(minX, Math.min(maxX, newX)); const constrainedY = Math.max(minY, Math.min(maxY, newY)); setPosition({ x: constrainedX, y: constrainedY, }); } if (isResizing && !isMaximized) { const deltaX = e.clientX - dragStart.x; const deltaY = e.clientY - dragStart.y; let newWidth = sizeStart.width; let newHeight = sizeStart.height; let newX = windowStart.x; let newY = windowStart.y; if (resizeDirection.includes("right")) { newWidth = Math.max(minWidth, sizeStart.width + deltaX); } if (resizeDirection.includes("left")) { const widthChange = -deltaX; newWidth = Math.max(minWidth, sizeStart.width + widthChange); if (newWidth > minWidth || widthChange > 0) { newX = windowStart.x - (newWidth - sizeStart.width); } else { newX = windowStart.x - (minWidth - sizeStart.width); } } if (resizeDirection.includes("bottom")) { newHeight = Math.max(minHeight, sizeStart.height + deltaY); } if (resizeDirection.includes("top")) { const heightChange = -deltaY; newHeight = Math.max(minHeight, sizeStart.height + heightChange); if (newHeight > minHeight || heightChange > 0) { newY = windowStart.y - (newHeight - sizeStart.height); } else { newY = windowStart.y - (minHeight - sizeStart.height); } } newX = Math.max(0, Math.min(window.innerWidth - newWidth, newX)); newY = Math.max(0, Math.min(window.innerHeight - newHeight, newY)); setSize({ width: newWidth, height: newHeight }); setPosition({ x: newX, y: newY }); } }, [ isDragging, isResizing, isMaximized, dragStart, windowStart, sizeStart, size, position, minWidth, minHeight, resizeDirection, ], ); const handleMouseUp = useCallback(() => { setIsDragging(false); setIsResizing(false); setResizeDirection(""); }, []); const handleResizeStart = useCallback( (e: React.MouseEvent, direction: string) => { if (isMaximized) return; e.preventDefault(); e.stopPropagation(); setIsResizing(true); setResizeDirection(direction); setDragStart({ x: e.clientX, y: e.clientY }); setWindowStart({ x: position.x, y: position.y }); setSizeStart({ width: size.width, height: size.height }); onFocus?.(); }, [isMaximized, position, size, onFocus], ); useEffect(() => { if (isDragging || isResizing) { document.addEventListener("mousemove", handleMouseMove); document.addEventListener("mouseup", handleMouseUp); document.body.style.userSelect = "none"; document.body.style.cursor = isDragging ? "grabbing" : "resizing"; return () => { document.removeEventListener("mousemove", handleMouseMove); document.removeEventListener("mouseup", handleMouseUp); document.body.style.userSelect = ""; document.body.style.cursor = ""; }; } }, [isDragging, isResizing, handleMouseMove, handleMouseUp]); const handleTitleDoubleClick = useCallback(() => { onMaximize?.(); }, [onMaximize]); return (
{title}
{onMinimize && ( )} {onMaximize && ( )}
{children}
{!isMaximized && ( <>
handleResizeStart(e, "top")} />
handleResizeStart(e, "bottom")} />
handleResizeStart(e, "left")} />
handleResizeStart(e, "right")} />
handleResizeStart(e, "top-left")} />
handleResizeStart(e, "top-right")} />
handleResizeStart(e, "bottom-left")} />
handleResizeStart(e, "bottom-right")} /> )}
); }