ywana-core8 0.2.20 → 0.2.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,46 +1,85 @@
1
1
  import React, { useRef, useEffect, useState, createContext, useContext } from 'react'
2
2
  import { WindowProvider, useWindows } from './WindowContext'
3
3
  import { Window } from './window'
4
- import { ApplicationMenu } from './ApplicationMenu'
4
+ import { LaunchPad } from './LaunchPad'
5
5
  import { AppManager, AppLoader, defaultDesktopConfig } from './AppManager'
6
6
  import { ToggleButton } from '../html/selector'
7
- import { Icon } from '../html/icon'
7
+ import { MenuIcon, MenuItem, MenuSeparator } from '../html/menu'
8
8
  import './desktop.css'
9
9
  import './desktop-windows.css'
10
- import './desktop-linux.css'
11
10
  import './desktop-macos.css'
11
+ import './desktop-gnome.css'
12
+ import './desktop-windowsxp.css'
13
+ import './desktop-windows98.css'
14
+ import './desktop-nextstep.css'
15
+ import './desktop-macintosh.css'
16
+ import './desktop-msx.css'
12
17
 
13
18
  /**
14
- * Context for Application state
19
+ * Z-INDEX LAYERS DOCUMENTATION
20
+ * ============================
21
+ * Workspace: 1
22
+ * Windows: 100-9999
23
+ * Desktop Overlays: 9000-9999
24
+ * Taskbar: 10000
25
+ * Taskbar Menus: 10001
15
26
  */
16
- const AppContext = createContext()
17
27
 
18
- export const useApplicationMenu = () => {
19
- const context = useContext(AppContext)
28
+ /**
29
+ * Context for Desktop state
30
+ */
31
+ const DesktopContext = createContext()
32
+
33
+ export const useLaunchPad = () => {
34
+ const context = useContext(DesktopContext)
20
35
  if (!context) {
21
- throw new Error('useApplicationMenu must be used within AppProvider')
36
+ throw new Error('useLaunchPad must be used within DesktopProvider')
22
37
  }
23
- return context.applicationMenu
38
+ return context.launchPad
24
39
  }
25
40
 
26
41
  export const useAppManager = () => {
27
- const context = useContext(AppContext)
42
+ const context = useContext(DesktopContext)
28
43
  if (!context) {
29
- throw new Error('useAppManager must be used within AppProvider')
44
+ throw new Error('useAppManager must be used within DesktopProvider')
30
45
  }
31
46
  return context.appManager
32
47
  }
33
48
 
49
+ export const useDesktopOverlays = () => {
50
+ const context = useContext(DesktopContext)
51
+ if (!context) {
52
+ throw new Error('useDesktopOverlays must be used within DesktopProvider')
53
+ }
54
+ return context.overlays
55
+ }
56
+
57
+ export const useDesktopTheme = () => {
58
+ const context = useContext(DesktopContext)
59
+ if (!context) {
60
+ throw new Error('useDesktopTheme must be used within DesktopProvider')
61
+ }
62
+ return context.theme
63
+ }
64
+
34
65
  /**
35
- * AppProvider - Provides Application state and AppManager to React components
66
+ * DesktopProvider - Provides Desktop state and AppManager to React components
36
67
  */
37
- export const AppProvider = ({
68
+ export const DesktopProvider = ({
38
69
  children,
39
- desktopConfig = defaultDesktopConfig
70
+ desktopConfig = defaultDesktopConfig,
71
+ initialTheme = 'windows'
40
72
  }) => {
41
73
  // Create a new AppManager instance if none provided
42
74
  const appManager = new AppManager()
43
- const [isApplicationMenuOpen, setIsApplicationMenuOpen] = useState(false)
75
+ const [isLaunchPadOpen, setIsLaunchPadOpen] = useState(false)
76
+
77
+ // Desktop Theme state
78
+ const [currentTheme, setCurrentTheme] = useState(initialTheme)
79
+
80
+ // Desktop Overlays state
81
+ const [overlays, setOverlays] = useState([])
82
+ const overlayIdCounter = useRef(0)
44
83
 
45
84
  // Load configuration on mount
46
85
  useEffect(() => {
@@ -50,31 +89,219 @@ export const AppProvider = ({
50
89
  }
51
90
  }, [appManager, desktopConfig])
52
91
 
92
+ // Overlay management functions
93
+ const showOverlay = (config) => {
94
+ const id = `overlay-${overlayIdCounter.current++}`
95
+ const overlay = {
96
+ id,
97
+ content: config.content,
98
+ onClose: config.onClose || (() => hideOverlay(id)),
99
+ backdrop: config.backdrop !== false,
100
+ backdropBlur: config.backdropBlur !== false,
101
+ closeOnBackdrop: config.closeOnBackdrop !== false,
102
+ closeOnEscape: config.closeOnEscape !== false,
103
+ zIndex: config.zIndex || 9500,
104
+ animation: config.animation || 'fade',
105
+ animationDuration: config.animationDuration || 300,
106
+ position: config.position || 'center',
107
+ customPosition: config.customPosition,
108
+ size: config.size || 'auto',
109
+ customSize: config.customSize,
110
+ padding: config.padding,
111
+ margin: config.margin,
112
+ isClosing: false
113
+ }
114
+
115
+ setOverlays(prev => [...prev, overlay])
116
+ return id
117
+ }
118
+
119
+ const hideOverlay = (id, animation) => {
120
+ // Mark as closing to trigger exit animation
121
+ setOverlays(prev => prev.map(o =>
122
+ o.id === id ? { ...o, isClosing: true, exitAnimation: animation } : o
123
+ ))
124
+
125
+ // Remove after animation completes
126
+ const overlay = overlays.find(o => o.id === id)
127
+ const duration = overlay?.animationDuration || 300
128
+ setTimeout(() => {
129
+ setOverlays(prev => prev.filter(o => o.id !== id))
130
+ }, duration)
131
+ }
132
+
133
+ const hideAllOverlays = () => {
134
+ setOverlays([])
135
+ }
136
+
137
+ const updateOverlay = (id, updates) => {
138
+ setOverlays(prev => prev.map(o =>
139
+ o.id === id ? { ...o, ...updates } : o
140
+ ))
141
+ }
142
+
53
143
  const value = {
54
- // Application Menu state
55
- applicationMenu: {
56
- isOpen: isApplicationMenuOpen,
57
- open: () => setIsApplicationMenuOpen(true),
58
- close: () => setIsApplicationMenuOpen(false),
59
- toggle: () => setIsApplicationMenuOpen(!isApplicationMenuOpen)
144
+ // LaunchPad state
145
+ launchPad: {
146
+ isOpen: isLaunchPadOpen,
147
+ open: () => setIsLaunchPadOpen(true),
148
+ close: () => setIsLaunchPadOpen(false),
149
+ toggle: () => setIsLaunchPadOpen(!isLaunchPadOpen)
60
150
  },
61
151
  // App Manager instance
62
- appManager
152
+ appManager,
153
+ // Desktop Overlays API
154
+ overlays: {
155
+ items: overlays,
156
+ show: showOverlay,
157
+ hide: hideOverlay,
158
+ hideAll: hideAllOverlays,
159
+ update: updateOverlay
160
+ },
161
+ // Desktop Theme API
162
+ theme: {
163
+ current: currentTheme,
164
+ set: setCurrentTheme,
165
+ available: ['windows', 'macos', 'gnome', 'windowsxp', 'windows98', 'nextstep', 'macintosh', 'msx']
166
+ }
63
167
  }
64
168
 
65
169
  return (
66
- <AppContext.Provider value={value}>
170
+ <DesktopContext.Provider value={value}>
67
171
  {children}
68
- </AppContext.Provider>
172
+ </DesktopContext.Provider>
173
+ )
174
+ }
175
+
176
+ /**
177
+ * DesktopOverlay - Individual overlay container with positioning and animations
178
+ */
179
+ const DesktopOverlay = ({
180
+ id,
181
+ content,
182
+ zIndex,
183
+ onClose,
184
+ backdrop,
185
+ backdropBlur,
186
+ closeOnBackdrop,
187
+ closeOnEscape,
188
+ animation,
189
+ animationDuration,
190
+ position,
191
+ customPosition,
192
+ size,
193
+ customSize,
194
+ padding,
195
+ margin,
196
+ isClosing,
197
+ exitAnimation
198
+ }) => {
199
+ const { hide } = useDesktopOverlays()
200
+
201
+ // Handle backdrop click
202
+ const handleBackdropClick = () => {
203
+ if (closeOnBackdrop && onClose) {
204
+ onClose()
205
+ }
206
+ }
207
+
208
+ // Handle escape key
209
+ useEffect(() => {
210
+ if (!closeOnEscape) return
211
+
212
+ const handleKeyDown = (e) => {
213
+ if (e.key === 'Escape') {
214
+ onClose()
215
+ }
216
+ }
217
+
218
+ document.addEventListener('keydown', handleKeyDown)
219
+ return () => document.removeEventListener('keydown', handleKeyDown)
220
+ }, [closeOnEscape, onClose])
221
+
222
+ // Build class names
223
+ const overlayClasses = [
224
+ 'desktop-overlay',
225
+ backdrop ? 'desktop-overlay--backdrop' : '',
226
+ backdropBlur ? 'desktop-overlay--blur' : '',
227
+ `desktop-overlay--position-${position}`,
228
+ `desktop-overlay--size-${size}`,
229
+ isClosing ? `desktop-overlay--exit-${exitAnimation || animation}` : `desktop-overlay--enter-${animation}`
230
+ ].filter(Boolean).join(' ')
231
+
232
+ // Build inline styles
233
+ const overlayStyle = {
234
+ zIndex,
235
+ animationDuration: `${animationDuration}ms`,
236
+ ...customPosition
237
+ }
238
+
239
+ const contentStyle = {
240
+ padding,
241
+ margin,
242
+ ...customSize
243
+ }
244
+
245
+ return (
246
+ <div
247
+ className={overlayClasses}
248
+ style={overlayStyle}
249
+ onClick={handleBackdropClick}
250
+ >
251
+ <div
252
+ className="desktop-overlay__content"
253
+ style={contentStyle}
254
+ onClick={(e) => e.stopPropagation()}
255
+ >
256
+ {content}
257
+ </div>
258
+ </div>
259
+ )
260
+ }
261
+
262
+ /**
263
+ * DesktopOverlayLayer - Renders all desktop-level overlays
264
+ */
265
+ const DesktopOverlayLayer = () => {
266
+ const { items } = useDesktopOverlays()
267
+ const { isOpen, close } = useLaunchPad()
268
+
269
+ return (
270
+ <div className="desktop-overlay-layer">
271
+ {/* LaunchPad as special overlay */}
272
+ {isOpen && (
273
+ <div
274
+ className="desktop-overlay desktop-overlay--backdrop desktop-overlay--blur desktop-overlay--launchpad desktop-overlay--enter-fade"
275
+ style={{ zIndex: 9000 }}
276
+ onClick={close}
277
+ >
278
+ <div
279
+ className="desktop-overlay__content desktop-overlay__content--launchpad"
280
+ onClick={(e) => e.stopPropagation()}
281
+ >
282
+ <LaunchPad isOpen={isOpen} onClose={close} />
283
+ </div>
284
+ </div>
285
+ )}
286
+
287
+ {/* Dynamic overlays */}
288
+ {items.map(overlay => (
289
+ <DesktopOverlay
290
+ key={overlay.id}
291
+ {...overlay}
292
+ />
293
+ ))}
294
+ </div>
69
295
  )
70
296
  }
71
297
 
72
298
  /**
73
299
  * Desktop layout component - manages overall desktop structure and sizing
74
300
  */
75
- const DesktopLayout = ({ children, className = '', theme = 'windows', workspaceRef, ...props }) => {
301
+ const DesktopLayout = ({ children, className = '', workspaceRef, ...props }) => {
76
302
  const desktopRef = useRef(null)
77
303
  const { windowManager } = useWindows()
304
+ const { current: theme } = useDesktopTheme()
78
305
 
79
306
  // Measure workspace size on mount and resize
80
307
  useEffect(() => {
@@ -165,6 +392,78 @@ export const Workspace = ({ children }) => {
165
392
  )
166
393
  }
167
394
 
395
+ /**
396
+ * ThemeMenu - Theme selector menu for taskbar
397
+ */
398
+ const ThemeMenu = () => {
399
+ const { current: currentTheme, set: setTheme, available: availableThemes } = useDesktopTheme()
400
+
401
+ const themeIcons = {
402
+ 'windows': 'computer',
403
+ 'macos': 'laptop_mac',
404
+ 'gnome': 'terminal',
405
+ 'windowsxp': 'desktop_windows',
406
+ 'windows98': 'computer',
407
+ 'nextstep': 'business_center',
408
+ 'macintosh': 'desktop_mac',
409
+ 'msx': 'videogame_asset'
410
+ }
411
+
412
+ const themeLabels = {
413
+ 'windows': 'Windows 11',
414
+ 'macos': 'macOS',
415
+ 'gnome': 'GNOME',
416
+ 'windowsxp': 'Windows XP',
417
+ 'windows98': 'Windows 98',
418
+ 'nextstep': 'NeXTSTEP',
419
+ 'macintosh': 'Macintosh',
420
+ 'msx': 'MSX'
421
+ }
422
+
423
+ // Group themes by category
424
+ const modernThemes = ['windows', 'macos', 'gnome']
425
+ const retroThemes = ['windowsxp', 'windows98', 'nextstep', 'macintosh', 'msx']
426
+
427
+ return (
428
+ <MenuIcon
429
+ icon="palette"
430
+ size="small"
431
+ position="top-right"
432
+ menuSize="compact"
433
+ >
434
+ {/* Modern Themes */}
435
+ {modernThemes.map(theme => (
436
+ <MenuItem
437
+ key={theme}
438
+ icon={themeIcons[theme] || 'desktop_windows'}
439
+ label={themeLabels[theme] || theme}
440
+ meta={currentTheme === theme ? '✓' : ''}
441
+ onSelect={() => {
442
+ console.log('Theme changed to:', theme)
443
+ setTheme(theme)
444
+ }}
445
+ />
446
+ ))}
447
+
448
+ <MenuSeparator />
449
+
450
+ {/* Retro Themes */}
451
+ {retroThemes.map(theme => (
452
+ <MenuItem
453
+ key={theme}
454
+ icon={themeIcons[theme] || 'desktop_windows'}
455
+ label={themeLabels[theme] || theme}
456
+ meta={currentTheme === theme ? '✓' : ''}
457
+ onSelect={() => {
458
+ console.log('Theme changed to:', theme)
459
+ setTheme(theme)
460
+ }}
461
+ />
462
+ ))}
463
+ </MenuIcon>
464
+ )
465
+ }
466
+
168
467
  /**
169
468
  * DesktopTaskbar - handles window creation and management
170
469
  */
@@ -184,11 +483,13 @@ export const DesktopTaskbar = () => {
184
483
  closeWindow,
185
484
  cascadeWindows,
186
485
  tileWindows,
486
+ arrangeWindowsInColumns,
487
+ arrangeWindowsInRows,
187
488
  centerAllWindows,
188
489
  clearAllWindows
189
490
  } = windowsContext
190
491
 
191
- const { isOpen, toggle } = useApplicationMenu()
492
+ const { isOpen, toggle } = useLaunchPad()
192
493
  console.log('DesktopTaskbar - isOpen:', isOpen, 'toggle:', toggle)
193
494
 
194
495
  const handleCreateWindow = () => {
@@ -255,7 +556,6 @@ export const DesktopTaskbar = () => {
255
556
  return (
256
557
  <div className='desktop-taskbar' style={{
257
558
  height: '50px',
258
- background: 'rgba(0,0,0,0.8)',
259
559
  display: 'flex',
260
560
  alignItems: 'center',
261
561
  padding: '0 16px',
@@ -358,39 +658,66 @@ export const DesktopTaskbar = () => {
358
658
  marginLeft: 'auto',
359
659
  marginRight: '8px'
360
660
  }}>
661
+ <MenuIcon
662
+ icon="window"
663
+ size="small"
664
+ position="top-right"
665
+ menuSize="compact"
666
+ >
667
+ <MenuItem
668
+ icon="view_stream"
669
+ label="Cascade"
670
+ onSelect={() => {
671
+ console.log('Cascade windows clicked!')
672
+ cascadeWindows()
673
+ }}
674
+ />
675
+ <MenuItem
676
+ icon="view_module"
677
+ label="Tile"
678
+ onSelect={() => {
679
+ console.log('Tile windows clicked!')
680
+ tileWindows()
681
+ }}
682
+ />
683
+ <MenuSeparator />
684
+ <MenuItem
685
+ icon="view_week"
686
+ label="Columns"
687
+ onSelect={() => {
688
+ console.log('Arrange in columns clicked!')
689
+ arrangeWindowsInColumns()
690
+ }}
691
+ />
692
+ <MenuItem
693
+ icon="view_agenda"
694
+ label="Rows"
695
+ onSelect={() => {
696
+ console.log('Arrange in rows clicked!')
697
+ arrangeWindowsInRows()
698
+ }}
699
+ />
700
+ <MenuSeparator />
701
+ <MenuItem
702
+ icon="center_focus_strong"
703
+ label="Center All"
704
+ onSelect={() => {
705
+ console.log('Center all windows clicked!')
706
+ centerAllWindows()
707
+ }}
708
+ />
709
+ <MenuItem
710
+ icon="clear_all"
711
+ label="Clear All"
712
+ onSelect={() => {
713
+ console.log('Clear all windows clicked!')
714
+ clearAllWindows()
715
+ }}
716
+ />
717
+ </MenuIcon>
361
718
 
362
- <Icon clickable size="small"
363
- action={() => {
364
- console.log('Cascade windows clicked!')
365
- cascadeWindows()
366
- }}
367
- icon="view_stream"
368
- label="Cascade"
369
- />
370
- <Icon clickable size="small"
371
- action={() => {
372
- console.log('Tile windows clicked!')
373
- tileWindows()
374
- }}
375
- icon="view_module"
376
- label="Tile"
377
- />
378
- <Icon clickable size="small"
379
- action={() => {
380
- console.log('Center all windows clicked!')
381
- centerAllWindows()
382
- }}
383
- icon="center_focus_strong"
384
- label="Center"
385
- />
386
- <Icon clickable size="small"
387
- action={() => {
388
- console.log('Clear all windows clicked!')
389
- clearAllWindows()
390
- }}
391
- icon="clear_all"
392
- label="Clear"
393
- />
719
+ {/* Theme Selector Menu */}
720
+ <ThemeMenu />
394
721
  </div>
395
722
 
396
723
  {/* Debug info */}
@@ -401,7 +728,7 @@ export const DesktopTaskbar = () => {
401
728
  flexShrink: 0,
402
729
  textAlign: 'right'
403
730
  }}>
404
- {desktopSize.width}×{desktopSize.height}
731
+ {Math.round(desktopSize.width)}×{Math.round(desktopSize.height)}
405
732
  </div>
406
733
  </div>
407
734
  )
@@ -411,7 +738,6 @@ export const DesktopTaskbar = () => {
411
738
  * Internal Desktop component that uses the contexts
412
739
  */
413
740
  const DesktopInternal = ({ desktopSize, children, ...props }) => {
414
- const { isOpen, close } = useApplicationMenu()
415
741
  const workspaceRef = useRef(null)
416
742
 
417
743
  return (
@@ -423,11 +749,9 @@ const DesktopInternal = ({ desktopSize, children, ...props }) => {
423
749
  {children}
424
750
  </Workspace>
425
751
 
426
- {/* ApplicationMenu positioned over workspace */}
427
- <ApplicationMenu
428
- isOpen={isOpen}
429
- onClose={close}
430
- />
752
+ {/* Desktop Overlay Layer - for LaunchPad and other floating content */}
753
+ {/* Positioned inside workspace-container to not cover taskbar */}
754
+ <DesktopOverlayLayer />
431
755
  </div>
432
756
 
433
757
  {/* Taskbar at bottom - separate from workspace */}
@@ -438,7 +762,7 @@ const DesktopInternal = ({ desktopSize, children, ...props }) => {
438
762
  }
439
763
 
440
764
  /**
441
- * Main Desktop component with AppProvider wrapper
765
+ * Main Desktop component with DesktopProvider wrapper
442
766
  */
443
767
  export const Desktop = ({
444
768
  desktopSize,
@@ -447,12 +771,12 @@ export const Desktop = ({
447
771
  ...props
448
772
  }) => {
449
773
  return (
450
- <AppProvider
774
+ <DesktopProvider
451
775
  desktopConfig={desktopConfig}
452
776
  >
453
777
  <DesktopInternal desktopSize={desktopSize} {...props}>
454
778
  {children}
455
779
  </DesktopInternal>
456
- </AppProvider>
780
+ </DesktopProvider>
457
781
  )
458
782
  }
@@ -1,7 +1,7 @@
1
1
  // Desktop Components - Window management system for web applications
2
- export { Desktop, Workspace, DesktopTaskbar, AppProvider, useApplicationMenu, useAppManager } from './desktop'
2
+ export { Desktop, Workspace, DesktopTaskbar, DesktopProvider, useLaunchPad, useAppManager, useDesktopOverlays, useDesktopTheme } from './desktop'
3
3
  export { Window } from './window'
4
- export { ApplicationMenu } from './ApplicationMenu'
4
+ export { LaunchPad } from './LaunchPad'
5
5
 
6
6
  // Application Management
7
7
  export { AppManager, defaultAppManager } from './AppManager'