import React, { useState, useRef, useCallback, useEffect } from "react"; import { cn } from "@/lib/utils.ts"; import { Minus, 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; onResize?: () => void; isMaximized?: boolean; zIndex?: number; onFocus?: () => void; targetSize?: { width: number; height: number }; titleActions?: React.ReactNode; } export function DraggableWindow({ title, children, initialX = 100, initialY = 100, initialWidth = 600, initialHeight = 400, minWidth = 300, minHeight = 200, onClose, onMinimize, onMaximize, onResize, isMaximized = false, zIndex = 1000, onFocus, targetSize, titleActions, }: 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 containerBoundsRef = useRef({ width: 0, height: 0 }); const windowRef = useRef(null); const titleBarRef = useRef(null); useEffect(() => { if (targetSize && !isMaximized) { const container = windowRef.current?.offsetParent as HTMLElement | null; const maxWidth = container ? Math.min(container.clientWidth * 0.9, 1200) : Math.min(window.innerWidth * 0.9, 1200); const maxHeight = container ? Math.min(container.clientHeight * 0.8, 800) : 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, (container ? container.clientWidth : window.innerWidth) / 2 - newWidth / 2, ), y: Math.max( 0, (container ? container.clientHeight : window.innerHeight) / 2 - 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 }); const container = windowRef.current?.offsetParent as HTMLElement | null; containerBoundsRef.current = { width: container ? container.clientWidth : window.innerWidth, height: container ? container.clientHeight : window.innerHeight, }; 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 { width: containerW, height: containerH } = containerBoundsRef.current; const maxX = containerW - size.width; const maxY = containerH - size.height; setPosition({ x: Math.max(0, Math.min(maxX, newX)), y: Math.max(49, Math.min(maxY, newY)), }); } 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); } } const { width: containerW, height: containerH } = containerBoundsRef.current; newX = Math.max(0, Math.min(containerW - newWidth, newX)); newY = Math.max(49, Math.min(containerH - newHeight, newY)); setSize({ width: newWidth, height: newHeight }); setPosition({ x: newX, y: newY }); if (onResize) { onResize(); } } }, [ isDragging, isResizing, isMaximized, dragStart, windowStart, sizeStart, size, minWidth, minHeight, resizeDirection, onResize, ], ); 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 }); const container = windowRef.current?.offsetParent as HTMLElement | null; containerBoundsRef.current = { width: container ? container.clientWidth : window.innerWidth, height: container ? container.clientHeight : window.innerHeight, }; 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 (
{t("fileManager.editor")}
{title}
{titleActions} {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")} /> )}
); }