1+ // src/components/EnhancedWindow.js
2+
3+ import React , { useState , useRef , useEffect } from 'react' ;
4+ import { useApp } from '@/context/AppContext' ;
5+ import { useTheme } from '@/context/ThemeContext' ;
6+ import { motion , AnimatePresence } from 'framer-motion' ;
7+ import { InformationCircleIcon } from '@heroicons/react/24/outline' ;
8+
9+ export default function EnhancedWindow ( { app, children, topBarService, dropdownService, infoService, keyShortcutService } ) {
10+ const { dispatch } = useApp ( ) ;
11+ const { theme } = useTheme ( ) ;
12+ const [ position , setPosition ] = useState ( {
13+ x : 100 + Math . random ( ) * 200 ,
14+ y : 100 + Math . random ( ) * 100 ,
15+ } ) ;
16+ const [ isDragging , setIsDragging ] = useState ( false ) ;
17+ const [ activeDropdown , setActiveDropdown ] = useState ( null ) ;
18+ const [ markdownActions , setMarkdownActions ] = useState ( null ) ;
19+ const dragRef = useRef ( { startX : 0 , startY : 0 } ) ;
20+ const windowRef = useRef ( null ) ;
21+
22+ const handleMouseDown = ( e ) => {
23+ setIsDragging ( true ) ;
24+ dragRef . current = {
25+ startX : e . clientX - position . x ,
26+ startY : e . clientY - position . y ,
27+ } ;
28+ dispatch ( { type : 'SET_ACTIVE_APP' , payload : app . id } ) ;
29+ if ( windowRef . current ) {
30+ windowRef . current . focus ( ) ;
31+ }
32+ } ;
33+
34+ const handleMouseMove = ( e ) => {
35+ if ( ! isDragging ) return ;
36+ setPosition ( {
37+ x : e . clientX - dragRef . current . startX ,
38+ y : e . clientY - dragRef . current . startY ,
39+ } ) ;
40+ } ;
41+
42+ const handleMouseUp = ( ) => {
43+ setIsDragging ( false ) ;
44+ } ;
45+
46+ const handleClose = ( ) => {
47+ dispatch ( { type : 'CLOSE_APP' , payload : app . id } ) ;
48+ } ;
49+
50+ const handleMinimize = ( ) => {
51+ dispatch ( { type : 'MINIMIZE_APP' , payload : app . id } ) ;
52+ } ;
53+
54+ const toggleDropdown = ( dropdownLabel ) => {
55+ setActiveDropdown ( activeDropdown === dropdownLabel ? null : dropdownLabel ) ;
56+ } ;
57+
58+ const handleMenuItemClick = ( action ) => {
59+ setActiveDropdown ( null ) ;
60+ if ( action ) action ( ) ;
61+ } ;
62+
63+ // Close dropdown when clicking outside
64+ useEffect ( ( ) => {
65+ const handleClickOutside = ( ) => {
66+ setActiveDropdown ( null ) ;
67+ } ;
68+
69+ if ( activeDropdown ) {
70+ document . addEventListener ( 'click' , handleClickOutside ) ;
71+ return ( ) => document . removeEventListener ( 'click' , handleClickOutside ) ;
72+ }
73+ } , [ activeDropdown ] ) ;
74+
75+ // Set window element for shortcut service
76+ useEffect ( ( ) => {
77+ if ( keyShortcutService && windowRef . current ) {
78+ keyShortcutService . windowElement = windowRef . current ;
79+ }
80+ } , [ keyShortcutService ] ) ;
81+
82+ return (
83+ < AnimatePresence >
84+ { ! app . minimized && (
85+ < motion . div
86+ ref = { windowRef }
87+ initial = { { scale : 0.8 , opacity : 0 , y : 50 } }
88+ animate = { { scale : 1 , opacity : 1 , y : 0 } }
89+ exit = { { scale : 0.8 , opacity : 0 , y : 50 } }
90+ transition = { { duration : 0.2 , ease : 'easeOut' } }
91+ className = { `absolute ${ theme . window . bg } ${ theme . window . border } border rounded-lg shadow-lg overflow-hidden` }
92+ style = { {
93+ left : position . x ,
94+ top : position . y ,
95+ width : 700 ,
96+ height : 500 ,
97+ zIndex : app . zIndex ,
98+ } }
99+ onMouseMove = { handleMouseMove }
100+ onMouseUp = { handleMouseUp }
101+ onMouseLeave = { handleMouseUp }
102+ tabIndex = { - 1 }
103+ >
104+ { /* Top Window Bar */ }
105+ < div
106+ className = { `${ theme . window . header } px-3 py-2 flex justify-between items-center window-drag border-b` }
107+ style = { { height : '32px' } }
108+ onMouseDown = { handleMouseDown }
109+ >
110+ < div className = "flex items-center gap-2" >
111+ { topBarService ?. icon && (
112+ < img src = { topBarService . icon } alt = "" className = "w-4 h-4" />
113+ ) }
114+ < span className = { `font-medium text-sm ${ theme . window . text } ` } >
115+ { topBarService ?. getDisplayTitle ( ) || app . name }
116+ </ span >
117+ </ div >
118+ < div className = "flex items-center gap-1" >
119+ < button
120+ onClick = { handleMinimize }
121+ className = "w-3 h-3 bg-orange-500 rounded-full hover:bg-orange-600"
122+ title = "Minimize"
123+ />
124+ < button
125+ className = "w-3 h-3 bg-blue-500 rounded-full hover:bg-blue-600"
126+ title = "Maximize"
127+ />
128+ < button
129+ onClick = { handleClose }
130+ className = "w-3 h-3 bg-red-500 rounded-full hover:bg-red-600"
131+ title = "Close"
132+ />
133+ </ div >
134+ </ div >
135+
136+ { /* Menu Bar */ }
137+ { dropdownService && (
138+ < div className = { `${ theme . window . header } px-3 py-1 flex items-center gap-4 border-b text-sm relative` } >
139+ { dropdownService . getDropdowns ( ) . map ( ( dropdown ) => (
140+ < div key = { dropdown . label } className = "relative" >
141+ < button
142+ onMouseDown = { ( e ) => e . preventDefault ( ) }
143+ onClick = { ( e ) => {
144+ e . stopPropagation ( ) ;
145+ e . preventDefault ( ) ;
146+ toggleDropdown ( dropdown . label ) ;
147+ } }
148+ className = { `px-2 py-1 rounded hover:bg-gray-100 ${
149+ activeDropdown === dropdown . label ? 'bg-gray-100' : ''
150+ } `}
151+ >
152+ { dropdown . label }
153+ </ button >
154+
155+ { activeDropdown === dropdown . label && (
156+ < div className = "absolute top-full left-0 mt-1 bg-white border rounded shadow-lg min-w-48 z-50" >
157+ { dropdown . items . map ( ( item , index ) => (
158+ item . type === 'separator' ? (
159+ < hr key = { index } className = "my-1" />
160+ ) : (
161+ < button
162+ key = { index }
163+ onMouseDown = { ( e ) => e . preventDefault ( ) }
164+ onClick = { ( e ) => {
165+ e . stopPropagation ( ) ;
166+ e . preventDefault ( ) ;
167+ handleMenuItemClick ( item . action ) ;
168+ } }
169+ disabled = { item . disabled }
170+ className = { `w-full text-left px-3 py-2 hover:bg-gray-100 flex justify-between items-center ${
171+ item . disabled ? 'text-gray-400 cursor-not-allowed' : ''
172+ } `}
173+ >
174+ < span > { item . label } </ span >
175+ { item . shortcut && (
176+ < span className = "text-xs text-gray-500 ml-4" > { item . shortcut } </ span >
177+ ) }
178+ </ button >
179+ )
180+ ) ) }
181+ </ div >
182+ ) }
183+ </ div >
184+ ) ) }
185+
186+ { /* Markdown Formatting Buttons */ }
187+ { app . id === 'notes' && markdownActions && (
188+ < div className = "flex items-center gap-1 ml-4 border-l pl-4" >
189+ < button
190+ onMouseDown = { ( e ) => e . preventDefault ( ) }
191+ onClick = { markdownActions . handleBold }
192+ className = "p-1 hover:bg-gray-100 rounded font-bold text-xs"
193+ title = "Bold"
194+ >
195+ B
196+ </ button >
197+ < button
198+ onMouseDown = { ( e ) => e . preventDefault ( ) }
199+ onClick = { markdownActions . handleItalic }
200+ className = "p-1 hover:bg-gray-100 rounded italic text-xs"
201+ title = "Italic"
202+ >
203+ I
204+ </ button >
205+ < button
206+ onMouseDown = { ( e ) => e . preventDefault ( ) }
207+ onClick = { markdownActions . handleStrikethrough }
208+ className = "p-1 hover:bg-gray-100 rounded line-through text-xs"
209+ title = "Strikethrough"
210+ >
211+ S
212+ </ button >
213+ < button
214+ onMouseDown = { ( e ) => e . preventDefault ( ) }
215+ onClick = { markdownActions . handleCode }
216+ className = "p-1 hover:bg-gray-100 rounded font-mono bg-gray-50 text-xs"
217+ title = "Code"
218+ >
219+ </>
220+ </ button >
221+ < button
222+ onMouseDown = { ( e ) => e . preventDefault ( ) }
223+ onClick = { markdownActions . handleHeader }
224+ className = "p-1 hover:bg-gray-100 rounded font-bold text-xs"
225+ title = "Header"
226+ >
227+ H
228+ </ button >
229+ </ div >
230+ ) }
231+
232+ { infoService && (
233+ < div className = "ml-auto" >
234+ < button
235+ onMouseDown = { ( e ) => e . preventDefault ( ) }
236+ onClick = { ( e ) => {
237+ e . preventDefault ( ) ;
238+ infoService . getInfoIcon ( ) . onClick ( ) ;
239+ } }
240+ className = "p-1 hover:bg-gray-100 rounded"
241+ title = "About"
242+ >
243+ < InformationCircleIcon className = "w-4 h-4" />
244+ </ button >
245+ </ div >
246+ ) }
247+ </ div >
248+ ) }
249+
250+ { /* Content Area */ }
251+ < div className = "flex-1 overflow-hidden" style = { { height : 'calc(100% - 64px)' } } >
252+ { React . cloneElement ( children , { setMarkdownActions } ) }
253+ </ div >
254+ </ motion . div >
255+ ) }
256+ </ AnimatePresence >
257+ ) ;
258+ }
0 commit comments