ywana-core8 0.2.6 → 0.2.9

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.
@@ -2,7 +2,9 @@ import React, { useRef, useEffect, useState, createContext, useContext } from 'r
2
2
  import { WindowProvider, useWindows } from './WindowContext'
3
3
  import { Window } from './window'
4
4
  import { ApplicationMenu } from './ApplicationMenu'
5
- import { defaultAppManager } from './AppManager'
5
+ import { AppManager, AppLoader, defaultDesktopConfig } from './AppManager'
6
+ import { ToggleButton } from '../html/selector'
7
+ import { Icon } from '../html/icon'
6
8
  import './desktop.css'
7
9
  import './desktop-windows.css'
8
10
  import './desktop-linux.css'
@@ -32,9 +34,22 @@ export const useAppManager = () => {
32
34
  /**
33
35
  * AppProvider - Provides Application state and AppManager to React components
34
36
  */
35
- export const AppProvider = ({ children, appManager = defaultAppManager }) => {
37
+ export const AppProvider = ({
38
+ children,
39
+ desktopConfig = defaultDesktopConfig
40
+ }) => {
41
+ // Create a new AppManager instance if none provided
42
+ const appManager = new AppManager()
36
43
  const [isApplicationMenuOpen, setIsApplicationMenuOpen] = useState(false)
37
44
 
45
+ // Load configuration on mount
46
+ useEffect(() => {
47
+ // Only load if the appManager is empty (no apps registered)
48
+ if (appManager.getAllApps().length === 0 && desktopConfig) {
49
+ AppLoader.load(appManager, desktopConfig)
50
+ }
51
+ }, [appManager, desktopConfig])
52
+
38
53
  const value = {
39
54
  // Application Menu state
40
55
  applicationMenu: {
@@ -44,7 +59,7 @@ export const AppProvider = ({ children, appManager = defaultAppManager }) => {
44
59
  toggle: () => setIsApplicationMenuOpen(!isApplicationMenuOpen)
45
60
  },
46
61
  // App Manager instance
47
- appManager: appManager
62
+ appManager
48
63
  }
49
64
 
50
65
  return (
@@ -57,17 +72,17 @@ export const AppProvider = ({ children, appManager = defaultAppManager }) => {
57
72
  /**
58
73
  * Desktop layout component - manages overall desktop structure and sizing
59
74
  */
60
- const DesktopLayout = ({ children, className = '', theme = 'windows', ...props }) => {
75
+ const DesktopLayout = ({ children, className = '', theme = 'windows', workspaceRef, ...props }) => {
61
76
  const desktopRef = useRef(null)
62
77
  const { windowManager } = useWindows()
63
78
 
64
- // Measure desktop size on mount and resize
79
+ // Measure workspace size on mount and resize
65
80
  useEffect(() => {
66
- let currentSize = { width: 1200, height: 800 }
81
+ let currentSize = { width: 1200, height: 750 } // Default workspace size (desktop - taskbar)
67
82
 
68
- const measureDesktop = () => {
69
- if (desktopRef.current) {
70
- const rect = desktopRef.current.getBoundingClientRect()
83
+ const measureWorkspace = () => {
84
+ if (workspaceRef.current) {
85
+ const rect = workspaceRef.current.getBoundingClientRect()
71
86
  const newSize = {
72
87
  width: rect.width,
73
88
  height: rect.height
@@ -82,12 +97,12 @@ const DesktopLayout = ({ children, className = '', theme = 'windows', ...props }
82
97
  }
83
98
 
84
99
  // Initial measurement
85
- measureDesktop()
100
+ measureWorkspace()
86
101
 
87
102
  // Listen for resize
88
- const resizeObserver = new ResizeObserver(measureDesktop)
89
- if (desktopRef.current) {
90
- resizeObserver.observe(desktopRef.current)
103
+ const resizeObserver = new ResizeObserver(measureWorkspace)
104
+ if (workspaceRef.current) {
105
+ resizeObserver.observe(workspaceRef.current)
91
106
  }
92
107
 
93
108
  return () => {
@@ -124,13 +139,13 @@ const DesktopLayout = ({ children, className = '', theme = 'windows', ...props }
124
139
  }
125
140
 
126
141
  /**
127
- * Workspace - renders windows only
142
+ * Workspace - container for windows with proper boundaries
128
143
  */
129
144
  export const Workspace = ({ children }) => {
130
145
  const { windows } = useWindows()
131
146
 
132
147
  return (
133
- <>
148
+ <div className="workspace">
134
149
  {children}
135
150
 
136
151
  {/* Render windows using Window component */}
@@ -148,7 +163,7 @@ export const Workspace = ({ children }) => {
148
163
  </div>
149
164
  </Window>
150
165
  ))}
151
- </>
166
+ </div>
152
167
  )
153
168
  }
154
169
 
@@ -156,6 +171,11 @@ export const Workspace = ({ children }) => {
156
171
  * DesktopTaskbar - handles window creation and management
157
172
  */
158
173
  export const DesktopTaskbar = () => {
174
+ console.log('DesktopTaskbar render')
175
+
176
+ const windowsContext = useWindows()
177
+ console.log('useWindows result:', windowsContext)
178
+
159
179
  const {
160
180
  createWindow,
161
181
  windows,
@@ -163,12 +183,20 @@ export const DesktopTaskbar = () => {
163
183
  activeWindowId,
164
184
  focusWindow,
165
185
  minimizeWindow,
166
- closeWindow
167
- } = useWindows()
186
+ closeWindow,
187
+ cascadeWindows,
188
+ tileWindows,
189
+ centerAllWindows,
190
+ clearAllWindows
191
+ } = windowsContext
168
192
 
169
- const { open: openApplicationMenu } = useApplicationMenu()
193
+ const { isOpen, toggle } = useApplicationMenu()
194
+ console.log('DesktopTaskbar - isOpen:', isOpen, 'toggle:', toggle)
170
195
 
171
196
  const handleCreateWindow = () => {
197
+ console.log('handleCreateWindow called!')
198
+ console.log('createWindow function:', createWindow)
199
+
172
200
  const windowTypes = [
173
201
  { title: 'File Explorer', icon: '📁', size: { width: 600, height: 400 } },
174
202
  { title: 'Text Editor', icon: '📝', size: { width: 500, height: 350 } },
@@ -178,6 +206,7 @@ export const DesktopTaskbar = () => {
178
206
  ]
179
207
 
180
208
  const randomType = windowTypes[Math.floor(Math.random() * windowTypes.length)]
209
+ console.log('Creating window:', randomType)
181
210
 
182
211
  createWindow({
183
212
  title: randomType.title,
@@ -226,58 +255,25 @@ export const DesktopTaskbar = () => {
226
255
  }
227
256
 
228
257
  return (
229
- <div style={{
230
- position: 'absolute',
231
- bottom: 0,
232
- left: 0,
233
- right: 0,
234
- height: '50px',
235
- background: 'rgba(0,0,0,0.8)',
236
- display: 'flex',
237
- alignItems: 'center',
238
- padding: '0 16px',
239
- gap: '8px'
240
- }}>
241
- {/* Start button */}
242
- <button
243
- onClick={openApplicationMenu}
244
- style={{
245
- padding: '8px 16px',
246
- background: '#1976d2',
247
- color: 'white',
248
- border: 'none',
249
- borderRadius: '4px',
250
- cursor: 'pointer',
251
- fontSize: '14px',
252
- fontWeight: 'bold',
253
- flexShrink: 0,
254
- display: 'flex',
255
- alignItems: 'center',
256
- gap: '6px'
257
- }}
258
- title="Open application menu"
259
- >
260
- <span style={{ fontSize: '16px' }}>🚀</span>
261
- Start
262
- </button>
263
-
264
- {/* Create window button (for testing) */}
265
- <button
266
- onClick={handleCreateWindow}
267
- style={{
268
- padding: '8px 12px',
269
- background: '#666',
270
- color: 'white',
271
- border: 'none',
272
- borderRadius: '4px',
273
- cursor: 'pointer',
274
- fontSize: '12px',
275
- flexShrink: 0
276
- }}
277
- title="Create random window (for testing)"
278
- >
279
- +
280
- </button>
258
+ <div className='desktop-taskbar' style={{
259
+ height: '50px',
260
+ background: 'rgba(0,0,0,0.8)',
261
+ display: 'flex',
262
+ alignItems: 'center',
263
+ padding: '0 16px',
264
+ gap: '8px'
265
+ }}>
266
+ {/* Start button */}
267
+ <ToggleButton
268
+ checked={isOpen}
269
+ onToggle={() => {
270
+ console.log('Start button toggled, current state:', isOpen)
271
+ toggle()
272
+ }}
273
+ icon="start"
274
+ label="Start"
275
+ >
276
+ </ToggleButton>
281
277
 
282
278
  {/* Separator */}
283
279
  <div style={{
@@ -356,6 +352,49 @@ export const DesktopTaskbar = () => {
356
352
  ))}
357
353
  </div>
358
354
 
355
+ {/* Window Layout Controls */}
356
+ <div style={{
357
+ display: 'flex',
358
+ gap: '4px',
359
+ alignItems: 'center',
360
+ marginLeft: 'auto',
361
+ marginRight: '8px'
362
+ }}>
363
+
364
+ <Icon clickable size="small"
365
+ action={() => {
366
+ console.log('Cascade windows clicked!')
367
+ cascadeWindows()
368
+ }}
369
+ icon="view_stream"
370
+ label="Cascade"
371
+ />
372
+ <Icon clickable size="small"
373
+ action={() => {
374
+ console.log('Tile windows clicked!')
375
+ tileWindows()
376
+ }}
377
+ icon="view_module"
378
+ label="Tile"
379
+ />
380
+ <Icon clickable size="small"
381
+ action={() => {
382
+ console.log('Center all windows clicked!')
383
+ centerAllWindows()
384
+ }}
385
+ icon="center_focus_strong"
386
+ label="Center"
387
+ />
388
+ <Icon clickable size="small"
389
+ action={() => {
390
+ console.log('Clear all windows clicked!')
391
+ clearAllWindows()
392
+ }}
393
+ icon="clear_all"
394
+ label="Clear"
395
+ />
396
+ </div>
397
+
359
398
  {/* Debug info */}
360
399
  <div style={{
361
400
  color: 'rgba(255,255,255,0.7)',
@@ -375,18 +414,26 @@ export const DesktopTaskbar = () => {
375
414
  */
376
415
  const DesktopInternal = ({ desktopSize, children, ...props }) => {
377
416
  const { isOpen, close } = useApplicationMenu()
417
+ const workspaceRef = useRef(null)
378
418
 
379
419
  return (
380
420
  <WindowProvider desktopSize={desktopSize}>
381
- <DesktopLayout {...props}>
382
- <Workspace>
383
- {children}
384
- </Workspace>
421
+ <DesktopLayout workspaceRef={workspaceRef} {...props}>
422
+ {/* Workspace container - has its own boundaries */}
423
+ <div ref={workspaceRef} className="desktop__workspace-container">
424
+ <Workspace>
425
+ {children}
426
+ </Workspace>
427
+
428
+ {/* ApplicationMenu positioned over workspace */}
429
+ <ApplicationMenu
430
+ isOpen={isOpen}
431
+ onClose={close}
432
+ />
433
+ </div>
434
+
435
+ {/* Taskbar at bottom - separate from workspace */}
385
436
  <DesktopTaskbar />
386
- <ApplicationMenu
387
- isOpen={isOpen}
388
- onClose={close}
389
- />
390
437
  </DesktopLayout>
391
438
  </WindowProvider>
392
439
  )
@@ -395,9 +442,16 @@ const DesktopInternal = ({ desktopSize, children, ...props }) => {
395
442
  /**
396
443
  * Main Desktop component with AppProvider wrapper
397
444
  */
398
- export const Desktop = ({ desktopSize, children, ...props }) => {
445
+ export const Desktop = ({
446
+ desktopSize,
447
+ children,
448
+ desktopConfig,
449
+ ...props
450
+ }) => {
399
451
  return (
400
- <AppProvider>
452
+ <AppProvider
453
+ desktopConfig={desktopConfig}
454
+ >
401
455
  <DesktopInternal desktopSize={desktopSize} {...props}>
402
456
  {children}
403
457
  </DesktopInternal>
@@ -10,6 +10,7 @@
10
10
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
11
11
  overflow: hidden;
12
12
  user-select: none;
13
+ pointer-events: auto; /* Ensure windows are clickable */
13
14
  }
14
15
 
15
16
  .window:focus-within {
@@ -249,3 +250,100 @@
249
250
  .window--dragging * {
250
251
  user-select: none !important;
251
252
  }
253
+
254
+ /* Window Resize Handles */
255
+ .window__resize-handle {
256
+ position: absolute;
257
+ background: transparent;
258
+ z-index: 10;
259
+ }
260
+
261
+ /* Edge handles */
262
+ .window__resize-handle--n {
263
+ top: -3px;
264
+ left: 8px;
265
+ right: 8px;
266
+ height: 6px;
267
+ cursor: n-resize;
268
+ }
269
+
270
+ .window__resize-handle--s {
271
+ bottom: -3px;
272
+ left: 8px;
273
+ right: 8px;
274
+ height: 6px;
275
+ cursor: s-resize;
276
+ }
277
+
278
+ .window__resize-handle--e {
279
+ top: 8px;
280
+ bottom: 8px;
281
+ right: -3px;
282
+ width: 6px;
283
+ cursor: e-resize;
284
+ }
285
+
286
+ .window__resize-handle--w {
287
+ top: 8px;
288
+ bottom: 8px;
289
+ left: -3px;
290
+ width: 6px;
291
+ cursor: w-resize;
292
+ }
293
+
294
+ /* Corner handles */
295
+ .window__resize-handle--ne {
296
+ top: -3px;
297
+ right: -3px;
298
+ width: 12px;
299
+ height: 12px;
300
+ cursor: ne-resize;
301
+ }
302
+
303
+ .window__resize-handle--nw {
304
+ top: -3px;
305
+ left: -3px;
306
+ width: 12px;
307
+ height: 12px;
308
+ cursor: nw-resize;
309
+ }
310
+
311
+ .window__resize-handle--se {
312
+ bottom: -3px;
313
+ right: -3px;
314
+ width: 12px;
315
+ height: 12px;
316
+ cursor: se-resize;
317
+ }
318
+
319
+ .window__resize-handle--sw {
320
+ bottom: -3px;
321
+ left: -3px;
322
+ width: 12px;
323
+ height: 12px;
324
+ cursor: sw-resize;
325
+ }
326
+
327
+ /* Resize handle hover effects */
328
+ .window__resize-handle:hover {
329
+ background: rgba(25, 118, 210, 0.2);
330
+ }
331
+
332
+ /* Window resizing state */
333
+ .window--resizing {
334
+ pointer-events: none;
335
+ }
336
+
337
+ .window--resizing * {
338
+ pointer-events: none !important;
339
+ }
340
+
341
+ /* Allow resize handles to work during resize */
342
+ .window--resizing .window__resize-handle {
343
+ pointer-events: auto !important;
344
+ }
345
+
346
+ /* Disable text selection during resize */
347
+ .window--resizing * {
348
+ user-select: none !important;
349
+ }
@@ -130,14 +130,119 @@ export const Window = ({
130
130
  updateWindowPosition(id, finalPosition)
131
131
  }
132
132
 
133
- // Setup drag event listeners
133
+ // Handle resize start
134
+ const handleResizeStart = (e, direction) => {
135
+ if (!resizable || maximized) return
136
+
137
+ e.preventDefault()
138
+ e.stopPropagation()
139
+
140
+ setIsResizing(true)
141
+ setResizeDirection(direction)
142
+ setResizeStartSize(size)
143
+ setResizeStartPosition(position)
144
+ setResizeStartMouse({ x: e.clientX, y: e.clientY })
145
+
146
+ // Focus window
147
+ focusWindow(id)
148
+ }
149
+
150
+ // Handle resize move
151
+ const handleResizeMove = (e) => {
152
+ if (!isResizing) return
153
+
154
+ e.preventDefault()
155
+
156
+ const deltaX = e.clientX - resizeStartMouse.x
157
+ const deltaY = e.clientY - resizeStartMouse.y
158
+
159
+ let newSize = { ...resizeStartSize }
160
+ let newPosition = { ...resizeStartPosition }
161
+
162
+ // Calculate new size and position based on resize direction
163
+ switch (resizeDirection) {
164
+ case 'n': // North
165
+ newSize.height = Math.max(150, resizeStartSize.height - deltaY)
166
+ newPosition.y = resizeStartPosition.y + (resizeStartSize.height - newSize.height)
167
+ break
168
+ case 's': // South
169
+ newSize.height = Math.max(150, resizeStartSize.height + deltaY)
170
+ break
171
+ case 'e': // East
172
+ newSize.width = Math.max(200, resizeStartSize.width + deltaX)
173
+ break
174
+ case 'w': // West
175
+ newSize.width = Math.max(200, resizeStartSize.width - deltaX)
176
+ newPosition.x = resizeStartPosition.x + (resizeStartSize.width - newSize.width)
177
+ break
178
+ case 'ne': // Northeast
179
+ newSize.width = Math.max(200, resizeStartSize.width + deltaX)
180
+ newSize.height = Math.max(150, resizeStartSize.height - deltaY)
181
+ newPosition.y = resizeStartPosition.y + (resizeStartSize.height - newSize.height)
182
+ break
183
+ case 'nw': // Northwest
184
+ newSize.width = Math.max(200, resizeStartSize.width - deltaX)
185
+ newSize.height = Math.max(150, resizeStartSize.height - deltaY)
186
+ newPosition.x = resizeStartPosition.x + (resizeStartSize.width - newSize.width)
187
+ newPosition.y = resizeStartPosition.y + (resizeStartSize.height - newSize.height)
188
+ break
189
+ case 'se': // Southeast
190
+ newSize.width = Math.max(200, resizeStartSize.width + deltaX)
191
+ newSize.height = Math.max(150, resizeStartSize.height + deltaY)
192
+ break
193
+ case 'sw': // Southwest
194
+ newSize.width = Math.max(200, resizeStartSize.width - deltaX)
195
+ newSize.height = Math.max(150, resizeStartSize.height + deltaY)
196
+ newPosition.x = resizeStartPosition.x + (resizeStartSize.width - newSize.width)
197
+ break
198
+ }
199
+
200
+ // Apply changes visually
201
+ if (windowRef.current) {
202
+ windowRef.current.style.width = `${newSize.width}px`
203
+ windowRef.current.style.height = `${newSize.height}px`
204
+ windowRef.current.style.left = `${newPosition.x}px`
205
+ windowRef.current.style.top = `${newPosition.y}px`
206
+ }
207
+ }
208
+
209
+ // Handle resize end
210
+ const handleResizeEnd = () => {
211
+ if (!isResizing) return
212
+
213
+ setIsResizing(false)
214
+ setResizeDirection('')
215
+
216
+ // Get final size and position
217
+ const desktopContainer = windowRef.current?.parentElement
218
+ if (!desktopContainer) return
219
+
220
+ const windowRect = windowRef.current.getBoundingClientRect()
221
+ const desktopRect = desktopContainer.getBoundingClientRect()
222
+
223
+ const finalSize = {
224
+ width: windowRect.width,
225
+ height: windowRect.height
226
+ }
227
+
228
+ const finalPosition = {
229
+ x: windowRect.left - desktopRect.left,
230
+ y: windowRect.top - desktopRect.top
231
+ }
232
+
233
+ // Update WindowManager
234
+ updateWindowSize(id, finalSize)
235
+ updateWindowPosition(id, finalPosition)
236
+ }
237
+
238
+ // Setup drag and resize event listeners
134
239
  useEffect(() => {
135
240
  if (isDragging) {
136
241
  document.addEventListener('mousemove', handleMouseMove)
137
242
  document.addEventListener('mouseup', handleMouseUp)
138
243
  document.body.style.userSelect = 'none'
139
244
  document.body.style.cursor = 'move'
140
-
245
+
141
246
  return () => {
142
247
  document.removeEventListener('mousemove', handleMouseMove)
143
248
  document.removeEventListener('mouseup', handleMouseUp)
@@ -147,6 +252,20 @@ export const Window = ({
147
252
  }
148
253
  }, [isDragging])
149
254
 
255
+ useEffect(() => {
256
+ if (isResizing) {
257
+ document.addEventListener('mousemove', handleResizeMove)
258
+ document.addEventListener('mouseup', handleResizeEnd)
259
+ document.body.style.userSelect = 'none'
260
+
261
+ return () => {
262
+ document.removeEventListener('mousemove', handleResizeMove)
263
+ document.removeEventListener('mouseup', handleResizeEnd)
264
+ document.body.style.userSelect = ''
265
+ }
266
+ }
267
+ }, [isResizing])
268
+
150
269
  // Window control handlers
151
270
  const handleMinimize = (e) => {
152
271
  e.stopPropagation()
@@ -189,6 +308,7 @@ export const Window = ({
189
308
  'window',
190
309
  maximized && 'window--maximized',
191
310
  isDragging && 'window--dragging',
311
+ isResizing && 'window--resizing',
192
312
  className
193
313
  ].filter(Boolean).join(' ')
194
314
 
@@ -264,6 +384,47 @@ export const Window = ({
264
384
  {statusBar}
265
385
  </div>
266
386
  )}
387
+
388
+ {/* Resize Handles */}
389
+ {resizable && !maximized && (
390
+ <>
391
+ {/* Edge handles */}
392
+ <div
393
+ className="window__resize-handle window__resize-handle--n"
394
+ onMouseDown={(e) => handleResizeStart(e, 'n')}
395
+ />
396
+ <div
397
+ className="window__resize-handle window__resize-handle--s"
398
+ onMouseDown={(e) => handleResizeStart(e, 's')}
399
+ />
400
+ <div
401
+ className="window__resize-handle window__resize-handle--e"
402
+ onMouseDown={(e) => handleResizeStart(e, 'e')}
403
+ />
404
+ <div
405
+ className="window__resize-handle window__resize-handle--w"
406
+ onMouseDown={(e) => handleResizeStart(e, 'w')}
407
+ />
408
+
409
+ {/* Corner handles */}
410
+ <div
411
+ className="window__resize-handle window__resize-handle--ne"
412
+ onMouseDown={(e) => handleResizeStart(e, 'ne')}
413
+ />
414
+ <div
415
+ className="window__resize-handle window__resize-handle--nw"
416
+ onMouseDown={(e) => handleResizeStart(e, 'nw')}
417
+ />
418
+ <div
419
+ className="window__resize-handle window__resize-handle--se"
420
+ onMouseDown={(e) => handleResizeStart(e, 'se')}
421
+ />
422
+ <div
423
+ className="window__resize-handle window__resize-handle--sw"
424
+ onMouseDown={(e) => handleResizeStart(e, 'sw')}
425
+ />
426
+ </>
427
+ )}
267
428
  </div>
268
429
  )
269
430
  }
@@ -20,6 +20,11 @@
20
20
  white-space: nowrap;
21
21
  }
22
22
 
23
+ /* Ensure icons inside buttons don't block clicks */
24
+ .btn .icon {
25
+ pointer-events: none;
26
+ }
27
+
23
28
  .btn.outlined {
24
29
  color: var(--primary-color);
25
30
  border: solid 1px var(--primary-color);
@@ -111,7 +111,8 @@ export const Button = (props) => {
111
111
  icon: loading ? 'hourglass_empty' : icon,
112
112
  size: size === 'small' ? 'small' : size === 'large' ? 'normal' : 'small',
113
113
  disabled: disabled || loading,
114
- className: loading ? 'loading-icon' : ''
114
+ className: loading ? 'loading-icon' : '',
115
+ eventPropagation: true, // Allow click events to bubble up to button
115
116
  }
116
117
 
117
118
  return (
@@ -1,6 +0,0 @@
1
- .desktop {
2
- height: 100vh;
3
- width: 100vw;
4
- background-color: rgb(113, 102, 226);
5
- position: relative;
6
- }
@@ -1,13 +0,0 @@
1
- import React from 'react'
2
- import './desktop.css'
3
-
4
- export const Desktop = (props) => {
5
-
6
- const { children } = props
7
-
8
- return (
9
- <div className='desktop'>
10
- { children }
11
- </div>
12
- )
13
- }