import React, { useState, useCallback, useRef, useEffect, RefObject, useMemo } from 'react'; import { Maximize2, Minimize2, Minimize, Expand, X, SquareMinus, Maximize, ChevronDown, CommandIcon, LogOut } from 'lucide-react'; import { WindowData, WindowPosition } from '../types'; import classNames from 'clsx'; import Draggable from 'react-draggable'; import { ResizableBox } from 'react-resizable'; import { getIconForWindowType } from './WindowIcons'; import { useImperativeHandle } from 'react'; import { emitter } from '../modules'; interface WindowManagerProps { windows: WindowData[]; showTaskbar?: boolean; onSave?: (windows: WindowData[]) => void; onCommand?: () => void; } // Minimum window dimensions const MIN_WINDOW_WIDTH = 300; const MIN_WINDOW_HEIGHT = 200; const WindowManager = React.forwardRef(({ windows: initialWindows, showTaskbar = true, onSave, onCommand }: WindowManagerProps, ref) => { const [windows, setWindows] = useState(initialWindows); const [fullscreenWindow, setFullscreenWindow] = useState(null); const [windowPositions, setWindowPositions] = useState>({}); const [activeWindow, setActiveWindow] = useState(null); const [maxZIndex, setMaxZIndex] = useState(100); const containerRef = useRef(null); const [mount, setMount] = useState(false); const [update, setUpdate] = useState(0); // Create stable refs for each window const windowRefs = useRef>>({}); const draggableRefs = useRef>>({}); useImperativeHandle(ref, () => ({ addWindow: (window: WindowData) => { addWindow(window); }, getWindows: () => { return windows; }, setWindows: (windows: WindowData[]) => { console.log('setWindows in manager', windows); setWindows(windows); setUpdate((prev) => prev + 1); }, })); useEffect(() => { console.log('initialWindows', initialWindows); setWindows(initialWindows); }, [initialWindows]); // Initialize refs for all windows useEffect(() => { windows.forEach((window) => { if (!windowRefs.current[window.id]) { windowRefs.current[window.id] = React.createRef(); } if (!draggableRefs.current[window.id]) { draggableRefs.current[window.id] = React.createRef(); } }); }, [windows]); // Initialize window positions useEffect(() => { const positions: Record = {}; windows.forEach((window) => { positions[window.id] = { x: 0, y: 0, width: 0, height: 0, zIndex: 1000, ...window.position, }; }); setWindowPositions(positions); setMaxZIndex(1000 + windows.length); setMount(true); }, [windows.length, update]); useEffect(() => { if (mount) { const newWindows = windows .map((window) => { return { ...window, position: windowPositions[window.id], }; }) .sort((a, b) => a.position.zIndex - b.position.zIndex) .map((item, index) => { return { ...item, position: { ...item.position, zIndex: 1000 + index, }, }; }); onSave?.(newWindows); } }, [mount, windowPositions]); const addWindow = useCallback((window: WindowData) => { const has = windows.find((w) => w.id === window.id); if (has) { setWindows((prev) => prev.map((w) => (w.id === window.id ? window : w))); } else { setWindows((prev) => [...prev, window]); } }, []); // Handle window removal const handleRemoveWindow = useCallback( (windowId: string) => { const window = windows.find((w) => w.id === windowId); const command = window?.commandList?.find((c) => c.key === 'close'); if (command) { emitter.emit('window-command', { windowData: window, command }); return; } setWindows((prev) => prev.filter((w) => w.id !== windowId)); setWindows((prev) => prev.map((w) => { if (w.id === windowId) { return { ...w, isMinimized: false }; } return w; }), ); if (fullscreenWindow === windowId) { setFullscreenWindow(null); } }, [fullscreenWindow], ); // Handle window minimize const handleMinimizeWindow = useCallback( (windowId: string) => { let needBringToFront = false; setWindows((prev) => prev.map((w) => { if (w.id === windowId) { needBringToFront = !w.isMinimized; return { ...w, isMinimized: !w.isMinimized }; } return w; }), ); if (fullscreenWindow === windowId) { setFullscreenWindow(null); } if (needBringToFront) { bringToFront(windowId); } }, [, fullscreenWindow], ); // Handle window fullscreen const handleFullscreenWindow = useCallback((windowId: string) => { setFullscreenWindow((prev) => (prev === windowId ? null : windowId)); // Ensure window is not minimized when going fullscreen setWindows((prev) => prev.map((w) => { if (w.id === windowId) { return { ...w, isMinimized: false }; } return w; }), ); // Bring to front when going fullscreen bringToFront(windowId); }, []); // Bring window to front const bringToFront = useCallback( (windowId: string, e?: any) => { setActiveWindow(windowId); setMaxZIndex((prev) => prev + 1); setWindowPositions((prev) => ({ ...prev, [windowId]: { ...prev[windowId], zIndex: maxZIndex + 1, }, })); if (e) { e.stopPropagation(); return e.target.className.includes('window-draggable'); } }, [maxZIndex], ); // Handle window resize const handleResize = useCallback((windowId: string, e: any, { size }: { size: { width: number; height: number } }) => { // Ensure minimum dimensions are respected const width = Math.max(MIN_WINDOW_WIDTH, size.width); const height = Math.max(MIN_WINDOW_HEIGHT, size.height); setWindowPositions((prev) => ({ ...prev, [windowId]: { ...prev[windowId], width, height, }, })); }, []); // Render window controls const renderWindowControls = useCallback( (windowId: string) => { const isFullscreen = fullscreenWindow === windowId; return (
); }, [handleMinimizeWindow, handleFullscreenWindow, handleRemoveWindow], ); // Render the taskbar with minimized windows const renderTaskbar = () => { const showWindowsList = windows.filter((window) => window.show && window.showTaskbar); // if (showWindowsList.length === 0) return null; // useEffect(() => { // const handleResize = () => { // // const icons = document.querySelectorAll('.more-icon'); // // icons.forEach((iconEl) => { // // const icon = iconEl as HTMLElement; // // const button = icon.closest('button'); // // if (button && button.offsetWidth <= 150) { // // icon.style.display = 'none'; // // } else { // // icon.style.display = 'block'; // // } // // }); // }; // window.addEventListener('resize', handleResize); // handleResize(); // Initial check // return () => { // window.removeEventListener('resize', handleResize); // }; // }, []); const showLogout = useMemo(() => { return localStorage.getItem('token'); }, []); return (
{showLogout && (
{ context?.app?.call?.({ path: 'user', key: 'logout', }); }}>
)}
{ onCommand?.(); }}>
{showWindowsList.map((window) => { const isMinimized = window.isMinimized; return ( ); })}
); }; // Add this useEffect to handle window resize // Render a fixed position window const renderFixedWindow = (windowData: WindowData) => { const isMinimized = windowData.isMinimized; const isFullscreen = fullscreenWindow === windowData.id; const position = windowPositions[windowData.id]; const Icon = getIconForWindowType(windowData.type || 'welcome'); const showRounded = windowData.showRounded ?? true; if (!position) return null; // Convert width and height to numbers for Resizable component const width = isFullscreen ? window.innerWidth : position.width; const height = isFullscreen ? window.innerHeight - 40 : position.height; // Get or create refs for this window if (!windowRefs.current[windowData.id]) { windowRefs.current[windowData.id] = React.createRef(); } if (!draggableRefs.current[windowData.id]) { draggableRefs.current[windowData.id] = React.createRef(); } const windowRef = windowRefs.current[windowData.id]; const draggableRef = draggableRefs.current[windowData.id]; const zIndex = isFullscreen ? 9999 : windowData.id == '__ai__' ? 3000 : position.zIndex; return (
bringToFront(windowData.id)} onStop={(e, data) => { if (!isFullscreen) { // Update the window's position in the state const newX = position.x + data.x; const newY = position.y + data.y; setWindowPositions((prev) => ({ ...prev, [windowData.id]: { ...prev[windowData.id], x: newX, y: newY, }, })); } }} nodeRef={draggableRef as RefObject} allowAnyClick={true} disabled={isFullscreen}>
!isFullscreen && handleResize(windowData.id, e, data)} // resizeHandles={isFullscreen ? [] : ['e', 's', 'se']} resizeHandles={windowData.resizeHandles || ['e', 's', 'se']} minConstraints={[MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT]} draggableOpts={{ disabled: isFullscreen }}>
bringToFront(windowData.id)}>
{windowData.showTitle && ( <> {windowData.title} )}
{renderWindowControls(windowData.id)}
); }; return (
{windows.map((window) => renderFixedWindow(window))} {showTaskbar && renderTaskbar()}
); }); WindowManager.displayName = 'WindowManager'; export const WindowContent = React.memo((props: { window: WindowData }) => { const { window } = props; const ref = useRef(null); useEffect(() => { emitter.emit('window-load', { windowData: window, el: ref.current }); return () => { emitter.emit('window-unload', { windowData: window, el: ref.current }); }; }, []); return
; }); export default WindowManager;