ywana-core8 0.2.3 → 0.2.5
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.
- package/dist/index.css +279 -0
- package/dist/index.js +423 -4
- package/dist/index.js.map +1 -1
- package/dist/index.modern.js +423 -4
- package/dist/index.modern.js.map +1 -1
- package/dist/index.umd.js +423 -4
- package/dist/index.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/desktop/AppManager.js +270 -0
- package/src/desktop/ApplicationMenu.css +279 -0
- package/src/desktop/ApplicationMenu.js +214 -0
- package/src/desktop/Desktop.stories.jsx +262 -5
- package/src/desktop/desktop.js +124 -43
- package/src/desktop/index.js +5 -1
- package/src/examples/ApplicationMenuExample.js +361 -0
@@ -0,0 +1,214 @@
|
|
1
|
+
import React, { useState, useEffect, useRef } from 'react'
|
2
|
+
import { useWindows } from './WindowContext'
|
3
|
+
import { useAppManager } from './desktop'
|
4
|
+
import './ApplicationMenu.css'
|
5
|
+
|
6
|
+
/**
|
7
|
+
* ApplicationMenu - Full-screen overlay menu for launching applications
|
8
|
+
*/
|
9
|
+
export const ApplicationMenu = ({ isOpen, onClose }) => {
|
10
|
+
const appManager = useAppManager()
|
11
|
+
const [searchTerm, setSearchTerm] = useState('')
|
12
|
+
const [selectedCategory, setSelectedCategory] = useState('all')
|
13
|
+
const [apps, setApps] = useState([])
|
14
|
+
const [categories, setCategories] = useState([])
|
15
|
+
const searchInputRef = useRef(null)
|
16
|
+
const { createWindow } = useWindows()
|
17
|
+
|
18
|
+
// Load apps and categories
|
19
|
+
useEffect(() => {
|
20
|
+
const loadData = () => {
|
21
|
+
setApps(appManager.getAllApps())
|
22
|
+
setCategories(appManager.getAllCategories())
|
23
|
+
}
|
24
|
+
|
25
|
+
loadData()
|
26
|
+
appManager.addListener(loadData)
|
27
|
+
|
28
|
+
return () => {
|
29
|
+
appManager.removeListener(loadData)
|
30
|
+
}
|
31
|
+
}, [appManager])
|
32
|
+
|
33
|
+
// Focus search input when menu opens
|
34
|
+
useEffect(() => {
|
35
|
+
if (isOpen && searchInputRef.current) {
|
36
|
+
setTimeout(() => {
|
37
|
+
searchInputRef.current.focus()
|
38
|
+
}, 100)
|
39
|
+
}
|
40
|
+
}, [isOpen])
|
41
|
+
|
42
|
+
// Handle escape key to close menu
|
43
|
+
useEffect(() => {
|
44
|
+
const handleKeyDown = (e) => {
|
45
|
+
if (e.key === 'Escape' && isOpen) {
|
46
|
+
onClose()
|
47
|
+
}
|
48
|
+
}
|
49
|
+
|
50
|
+
if (isOpen) {
|
51
|
+
document.addEventListener('keydown', handleKeyDown)
|
52
|
+
return () => document.removeEventListener('keydown', handleKeyDown)
|
53
|
+
}
|
54
|
+
}, [isOpen, onClose])
|
55
|
+
|
56
|
+
// Filter apps based on search and category
|
57
|
+
const filteredApps = apps.filter(app => {
|
58
|
+
const matchesSearch = searchTerm === '' ||
|
59
|
+
app.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
60
|
+
app.description.toLowerCase().includes(searchTerm.toLowerCase())
|
61
|
+
|
62
|
+
const matchesCategory = selectedCategory === 'all' ||
|
63
|
+
app.category.toLowerCase() === selectedCategory.toLowerCase()
|
64
|
+
|
65
|
+
return matchesSearch && matchesCategory
|
66
|
+
})
|
67
|
+
|
68
|
+
// Group filtered apps by category
|
69
|
+
const groupedApps = filteredApps.reduce((groups, app) => {
|
70
|
+
const category = app.category
|
71
|
+
if (!groups[category]) {
|
72
|
+
groups[category] = []
|
73
|
+
}
|
74
|
+
groups[category].push(app)
|
75
|
+
return groups
|
76
|
+
}, {})
|
77
|
+
|
78
|
+
// Handle app launch
|
79
|
+
const handleLaunchApp = (app) => {
|
80
|
+
createWindow({
|
81
|
+
id: `${app.id}-${Date.now()}`,
|
82
|
+
title: app.name,
|
83
|
+
icon: app.icon,
|
84
|
+
size: app.size,
|
85
|
+
toolbar: app.toolbar,
|
86
|
+
statusBar: app.statusBar,
|
87
|
+
content: app.component || (
|
88
|
+
<div style={{ padding: '20px', textAlign: 'center' }}>
|
89
|
+
<div style={{ fontSize: '48px', marginBottom: '16px' }}>{app.icon}</div>
|
90
|
+
<h2>{app.name}</h2>
|
91
|
+
<p style={{ color: '#666', marginBottom: '20px' }}>{app.description}</p>
|
92
|
+
<p style={{ fontSize: '14px', color: '#999' }}>
|
93
|
+
This is a placeholder for the {app.name} application.
|
94
|
+
</p>
|
95
|
+
</div>
|
96
|
+
)
|
97
|
+
})
|
98
|
+
onClose()
|
99
|
+
}
|
100
|
+
|
101
|
+
// Handle category selection
|
102
|
+
const handleCategorySelect = (categoryId) => {
|
103
|
+
setSelectedCategory(categoryId)
|
104
|
+
setSearchTerm('')
|
105
|
+
}
|
106
|
+
|
107
|
+
if (!isOpen) return null
|
108
|
+
|
109
|
+
return (
|
110
|
+
<div className="application-menu-overlay" onClick={onClose}>
|
111
|
+
<div className="application-menu" onClick={(e) => e.stopPropagation()}>
|
112
|
+
{/* Header */}
|
113
|
+
<div className="application-menu__header">
|
114
|
+
<h2>Applications</h2>
|
115
|
+
<button
|
116
|
+
className="application-menu__close"
|
117
|
+
onClick={onClose}
|
118
|
+
title="Close menu"
|
119
|
+
>
|
120
|
+
×
|
121
|
+
</button>
|
122
|
+
</div>
|
123
|
+
|
124
|
+
{/* Search */}
|
125
|
+
<div className="application-menu__search">
|
126
|
+
<input
|
127
|
+
ref={searchInputRef}
|
128
|
+
type="text"
|
129
|
+
placeholder="Search applications..."
|
130
|
+
value={searchTerm}
|
131
|
+
onChange={(e) => setSearchTerm(e.target.value)}
|
132
|
+
className="application-menu__search-input"
|
133
|
+
/>
|
134
|
+
</div>
|
135
|
+
|
136
|
+
{/* Categories */}
|
137
|
+
<div className="application-menu__categories">
|
138
|
+
<button
|
139
|
+
className={`application-menu__category ${selectedCategory === 'all' ? 'active' : ''}`}
|
140
|
+
onClick={() => handleCategorySelect('all')}
|
141
|
+
>
|
142
|
+
<span className="category-icon">📱</span>
|
143
|
+
All Apps
|
144
|
+
</button>
|
145
|
+
{categories.map(category => (
|
146
|
+
<button
|
147
|
+
key={category.id}
|
148
|
+
className={`application-menu__category ${selectedCategory === category.id ? 'active' : ''}`}
|
149
|
+
onClick={() => handleCategorySelect(category.id)}
|
150
|
+
>
|
151
|
+
<span className="category-icon">{category.icon}</span>
|
152
|
+
{category.name}
|
153
|
+
</button>
|
154
|
+
))}
|
155
|
+
</div>
|
156
|
+
|
157
|
+
{/* Applications Grid */}
|
158
|
+
<div className="application-menu__content">
|
159
|
+
{searchTerm && (
|
160
|
+
<div className="application-menu__search-results">
|
161
|
+
<h3>Search Results ({filteredApps.length})</h3>
|
162
|
+
<div className="application-menu__apps-grid">
|
163
|
+
{filteredApps.map(app => (
|
164
|
+
<div
|
165
|
+
key={app.id}
|
166
|
+
className="application-menu__app"
|
167
|
+
onClick={() => handleLaunchApp(app)}
|
168
|
+
title={app.description}
|
169
|
+
>
|
170
|
+
<div className="app-icon">{app.icon}</div>
|
171
|
+
<div className="app-name">{app.name}</div>
|
172
|
+
</div>
|
173
|
+
))}
|
174
|
+
</div>
|
175
|
+
</div>
|
176
|
+
)}
|
177
|
+
|
178
|
+
{!searchTerm && (
|
179
|
+
<div className="application-menu__categories-content">
|
180
|
+
{Object.entries(groupedApps).map(([categoryName, categoryApps]) => (
|
181
|
+
<div key={categoryName} className="application-menu__category-section">
|
182
|
+
<h3 className="category-title">{categoryName}</h3>
|
183
|
+
<div className="application-menu__apps-grid">
|
184
|
+
{categoryApps.map(app => (
|
185
|
+
<div
|
186
|
+
key={app.id}
|
187
|
+
className="application-menu__app"
|
188
|
+
onClick={() => handleLaunchApp(app)}
|
189
|
+
title={app.description}
|
190
|
+
>
|
191
|
+
<div className="app-icon">{app.icon}</div>
|
192
|
+
<div className="app-name">{app.name}</div>
|
193
|
+
</div>
|
194
|
+
))}
|
195
|
+
</div>
|
196
|
+
</div>
|
197
|
+
))}
|
198
|
+
</div>
|
199
|
+
)}
|
200
|
+
|
201
|
+
{filteredApps.length === 0 && (
|
202
|
+
<div className="application-menu__no-results">
|
203
|
+
<div style={{ fontSize: '48px', marginBottom: '16px' }}>🔍</div>
|
204
|
+
<h3>No applications found</h3>
|
205
|
+
<p>Try adjusting your search or category filter.</p>
|
206
|
+
</div>
|
207
|
+
)}
|
208
|
+
</div>
|
209
|
+
</div>
|
210
|
+
</div>
|
211
|
+
)
|
212
|
+
}
|
213
|
+
|
214
|
+
export default ApplicationMenu
|
@@ -1,5 +1,6 @@
|
|
1
1
|
import React from 'react'
|
2
|
-
import { Desktop } from './desktop.js'
|
2
|
+
import { Desktop, useApplicationMenu, useAppManager } from './desktop.js'
|
3
|
+
import { defaultAppManager } from './AppManager.js'
|
3
4
|
|
4
5
|
/**
|
5
6
|
* Basic Desktop with WindowManager
|
@@ -88,10 +89,10 @@ export const DarkDesktop = () => (
|
|
88
89
|
export const LightDesktop = () => (
|
89
90
|
<div style={{ height: '600px', width: '100%', position: 'relative', overflow: 'hidden' }}>
|
90
91
|
<Desktop className="desktop--light">
|
91
|
-
<div style={{
|
92
|
-
position: 'absolute',
|
93
|
-
top: '50%',
|
94
|
-
left: '50%',
|
92
|
+
<div style={{
|
93
|
+
position: 'absolute',
|
94
|
+
top: '50%',
|
95
|
+
left: '50%',
|
95
96
|
transform: 'translate(-50%, -50%)',
|
96
97
|
background: 'rgba(255,255,255,0.9)',
|
97
98
|
color: '#333',
|
@@ -108,3 +109,259 @@ export const LightDesktop = () => (
|
|
108
109
|
</Desktop>
|
109
110
|
</div>
|
110
111
|
)
|
112
|
+
|
113
|
+
// Component that demonstrates using both AppProvider hooks
|
114
|
+
const DemoControls = () => {
|
115
|
+
const { isOpen, open, close, toggle } = useApplicationMenu()
|
116
|
+
const appManager = useAppManager()
|
117
|
+
const [appCount, setAppCount] = React.useState(0)
|
118
|
+
|
119
|
+
React.useEffect(() => {
|
120
|
+
const updateCount = () => setAppCount(appManager.getAllApps().length)
|
121
|
+
updateCount()
|
122
|
+
appManager.addListener(updateCount)
|
123
|
+
return () => appManager.removeListener(updateCount)
|
124
|
+
}, [appManager])
|
125
|
+
|
126
|
+
const addCustomApp = () => {
|
127
|
+
const randomId = `custom-app-${Date.now()}`
|
128
|
+
appManager.registerApp({
|
129
|
+
id: randomId,
|
130
|
+
name: `Custom App ${Math.floor(Math.random() * 100)}`,
|
131
|
+
description: 'Dynamically added application',
|
132
|
+
icon: ['🎨', '🎯', '🎪', '🎭', '🎨'][Math.floor(Math.random() * 5)],
|
133
|
+
category: 'Custom',
|
134
|
+
component: React.createElement('div', {
|
135
|
+
style: { padding: '20px', textAlign: 'center' }
|
136
|
+
}, [
|
137
|
+
React.createElement('h2', { key: 'title' }, 'Dynamic App'),
|
138
|
+
React.createElement('p', { key: 'desc' }, 'This app was added dynamically using AppManager!'),
|
139
|
+
React.createElement('p', { key: 'id' }, `ID: ${randomId}`)
|
140
|
+
]),
|
141
|
+
size: { width: 400, height: 300 }
|
142
|
+
})
|
143
|
+
}
|
144
|
+
|
145
|
+
return (
|
146
|
+
<div style={{
|
147
|
+
position: 'absolute',
|
148
|
+
top: '20px',
|
149
|
+
right: '20px',
|
150
|
+
background: 'rgba(255,255,255,0.9)',
|
151
|
+
padding: '20px',
|
152
|
+
borderRadius: '8px',
|
153
|
+
boxShadow: '0 2px 10px rgba(0,0,0,0.1)',
|
154
|
+
maxWidth: '300px'
|
155
|
+
}}>
|
156
|
+
<h4 style={{ margin: '0 0 15px 0' }}>🎮 App Controls</h4>
|
157
|
+
<p style={{ margin: '0 0 10px 0', fontSize: '14px', color: '#666' }}>
|
158
|
+
Menu: <strong>{isOpen ? 'Open' : 'Closed'}</strong><br/>
|
159
|
+
Apps: <strong>{appCount}</strong>
|
160
|
+
</p>
|
161
|
+
|
162
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
163
|
+
<button
|
164
|
+
onClick={open}
|
165
|
+
style={{
|
166
|
+
padding: '8px 12px',
|
167
|
+
background: '#4caf50',
|
168
|
+
color: 'white',
|
169
|
+
border: 'none',
|
170
|
+
borderRadius: '4px',
|
171
|
+
cursor: 'pointer',
|
172
|
+
fontSize: '12px'
|
173
|
+
}}
|
174
|
+
>
|
175
|
+
Open Menu
|
176
|
+
</button>
|
177
|
+
<button
|
178
|
+
onClick={close}
|
179
|
+
style={{
|
180
|
+
padding: '8px 12px',
|
181
|
+
background: '#f44336',
|
182
|
+
color: 'white',
|
183
|
+
border: 'none',
|
184
|
+
borderRadius: '4px',
|
185
|
+
cursor: 'pointer',
|
186
|
+
fontSize: '12px'
|
187
|
+
}}
|
188
|
+
>
|
189
|
+
Close Menu
|
190
|
+
</button>
|
191
|
+
<button
|
192
|
+
onClick={toggle}
|
193
|
+
style={{
|
194
|
+
padding: '8px 12px',
|
195
|
+
background: '#2196f3',
|
196
|
+
color: 'white',
|
197
|
+
border: 'none',
|
198
|
+
borderRadius: '4px',
|
199
|
+
cursor: 'pointer',
|
200
|
+
fontSize: '12px'
|
201
|
+
}}
|
202
|
+
>
|
203
|
+
Toggle Menu
|
204
|
+
</button>
|
205
|
+
<hr style={{ margin: '10px 0', border: 'none', borderTop: '1px solid #eee' }} />
|
206
|
+
<button
|
207
|
+
onClick={addCustomApp}
|
208
|
+
style={{
|
209
|
+
padding: '8px 12px',
|
210
|
+
background: '#ff9800',
|
211
|
+
color: 'white',
|
212
|
+
border: 'none',
|
213
|
+
borderRadius: '4px',
|
214
|
+
cursor: 'pointer',
|
215
|
+
fontSize: '12px'
|
216
|
+
}}
|
217
|
+
>
|
218
|
+
Add Custom App
|
219
|
+
</button>
|
220
|
+
</div>
|
221
|
+
</div>
|
222
|
+
)
|
223
|
+
}
|
224
|
+
|
225
|
+
/**
|
226
|
+
* Desktop with ApplicationMenu - Basic functionality
|
227
|
+
*/
|
228
|
+
export const ApplicationMenuBasic = () => (
|
229
|
+
<div style={{ height: '600px', width: '100%', position: 'relative', overflow: 'hidden' }}>
|
230
|
+
<Desktop desktopSize={{ width: 1200, height: 600 }}>
|
231
|
+
<div style={{
|
232
|
+
position: 'absolute',
|
233
|
+
top: '20px',
|
234
|
+
left: '20px',
|
235
|
+
background: 'rgba(255,255,255,0.9)',
|
236
|
+
padding: '20px',
|
237
|
+
borderRadius: '8px',
|
238
|
+
boxShadow: '0 2px 10px rgba(0,0,0,0.1)',
|
239
|
+
maxWidth: '400px'
|
240
|
+
}}>
|
241
|
+
<h3 style={{ margin: '0 0 15px 0' }}>🚀 Application Menu Demo</h3>
|
242
|
+
<p style={{ margin: '0 0 15px 0', lineHeight: '1.5' }}>
|
243
|
+
Click the <strong>"Start"</strong> button in the taskbar to open the Application Menu.
|
244
|
+
Browse applications by category and launch them to create windows.
|
245
|
+
</p>
|
246
|
+
<div style={{ fontSize: '14px', color: '#666' }}>
|
247
|
+
<p><strong>Features:</strong></p>
|
248
|
+
<ul style={{ margin: '5px 0', paddingLeft: '20px' }}>
|
249
|
+
<li>Browse apps by category</li>
|
250
|
+
<li>Search functionality</li>
|
251
|
+
<li>Launch applications as windows</li>
|
252
|
+
<li>Full-screen overlay menu</li>
|
253
|
+
</ul>
|
254
|
+
</div>
|
255
|
+
</div>
|
256
|
+
</Desktop>
|
257
|
+
</div>
|
258
|
+
)
|
259
|
+
|
260
|
+
/**
|
261
|
+
* Desktop with ApplicationMenu - Advanced with controls
|
262
|
+
*/
|
263
|
+
export const ApplicationMenuAdvanced = () => {
|
264
|
+
// Setup custom apps
|
265
|
+
React.useEffect(() => {
|
266
|
+
// Register some custom applications
|
267
|
+
defaultAppManager.registerApp({
|
268
|
+
id: 'advanced-calculator',
|
269
|
+
name: 'Advanced Calculator',
|
270
|
+
description: 'Scientific calculator with advanced functions',
|
271
|
+
icon: '🧮',
|
272
|
+
category: 'Utilities',
|
273
|
+
component: React.createElement('div', {
|
274
|
+
style: { padding: '20px', textAlign: 'center' }
|
275
|
+
}, [
|
276
|
+
React.createElement('h2', { key: 'title' }, '🧮 Advanced Calculator'),
|
277
|
+
React.createElement('p', { key: 'desc' }, 'A powerful calculator with scientific functions.'),
|
278
|
+
React.createElement('div', {
|
279
|
+
key: 'buttons',
|
280
|
+
style: { display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '8px', marginTop: '20px' }
|
281
|
+
}, [
|
282
|
+
...['7', '8', '9', '/', '4', '5', '6', '*', '1', '2', '3', '-', '0', '.', '=', '+'].map((btn, i) =>
|
283
|
+
React.createElement('button', {
|
284
|
+
key: i,
|
285
|
+
style: {
|
286
|
+
padding: '12px',
|
287
|
+
background: '#f0f0f0',
|
288
|
+
border: '1px solid #ccc',
|
289
|
+
borderRadius: '4px',
|
290
|
+
cursor: 'pointer'
|
291
|
+
}
|
292
|
+
}, btn)
|
293
|
+
)
|
294
|
+
])
|
295
|
+
]),
|
296
|
+
size: { width: 300, height: 400 }
|
297
|
+
})
|
298
|
+
|
299
|
+
defaultAppManager.registerApp({
|
300
|
+
id: 'text-editor-pro',
|
301
|
+
name: 'Text Editor Pro',
|
302
|
+
description: 'Professional text editor with syntax highlighting',
|
303
|
+
icon: '📝',
|
304
|
+
category: 'Productivity',
|
305
|
+
component: React.createElement('div', {
|
306
|
+
style: { padding: '20px', height: '100%', display: 'flex', flexDirection: 'column' }
|
307
|
+
}, [
|
308
|
+
React.createElement('div', {
|
309
|
+
key: 'toolbar',
|
310
|
+
style: { marginBottom: '10px', display: 'flex', gap: '10px' }
|
311
|
+
}, [
|
312
|
+
React.createElement('button', { key: 'new', style: { padding: '5px 10px', fontSize: '12px' } }, 'New'),
|
313
|
+
React.createElement('button', { key: 'open', style: { padding: '5px 10px', fontSize: '12px' } }, 'Open'),
|
314
|
+
React.createElement('button', { key: 'save', style: { padding: '5px 10px', fontSize: '12px' } }, 'Save')
|
315
|
+
]),
|
316
|
+
React.createElement('textarea', {
|
317
|
+
key: 'editor',
|
318
|
+
style: {
|
319
|
+
flex: 1,
|
320
|
+
border: '1px solid #ccc',
|
321
|
+
padding: '10px',
|
322
|
+
fontFamily: 'monospace',
|
323
|
+
fontSize: '14px',
|
324
|
+
resize: 'none'
|
325
|
+
},
|
326
|
+
placeholder: 'Start typing your code here...',
|
327
|
+
defaultValue: '// Welcome to Text Editor Pro!\nfunction hello() {\n console.log("Hello, World!");\n}'
|
328
|
+
})
|
329
|
+
]),
|
330
|
+
size: { width: 600, height: 400 }
|
331
|
+
})
|
332
|
+
}, [])
|
333
|
+
|
334
|
+
return (
|
335
|
+
<div style={{ height: '600px', width: '100%', position: 'relative', overflow: 'hidden' }}>
|
336
|
+
<Desktop desktopSize={{ width: 1200, height: 600 }}>
|
337
|
+
<div style={{
|
338
|
+
position: 'absolute',
|
339
|
+
top: '20px',
|
340
|
+
left: '20px',
|
341
|
+
background: 'rgba(255,255,255,0.9)',
|
342
|
+
padding: '20px',
|
343
|
+
borderRadius: '8px',
|
344
|
+
boxShadow: '0 2px 10px rgba(0,0,0,0.1)',
|
345
|
+
maxWidth: '400px'
|
346
|
+
}}>
|
347
|
+
<h3 style={{ margin: '0 0 15px 0' }}>🎮 AppProvider + AppManager</h3>
|
348
|
+
<p style={{ margin: '0 0 15px 0', lineHeight: '1.5' }}>
|
349
|
+
This demo shows AppProvider and AppManager collaboration. Use the controls
|
350
|
+
on the right to manage the menu and add custom applications dynamically.
|
351
|
+
</p>
|
352
|
+
<div style={{ fontSize: '14px', color: '#666' }}>
|
353
|
+
<p><strong>Advanced Features:</strong></p>
|
354
|
+
<ul style={{ margin: '5px 0', paddingLeft: '20px' }}>
|
355
|
+
<li>Dynamic app registration</li>
|
356
|
+
<li>Real-time app count updates</li>
|
357
|
+
<li>Custom app components</li>
|
358
|
+
<li>Context-based state management</li>
|
359
|
+
</ul>
|
360
|
+
</div>
|
361
|
+
</div>
|
362
|
+
|
363
|
+
<DemoControls />
|
364
|
+
</Desktop>
|
365
|
+
</div>
|
366
|
+
)
|
367
|
+
}
|