deskforge 1.0.0 → 1.0.1
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/COMPLETION_REPORT.md +426 -0
- package/COMPONENT_API.md +529 -0
- package/DELIVERY_SUMMARY.md +455 -0
- package/IMPLEMENTATION_GUIDE.md +385 -0
- package/IMPORT_GUIDE.md +404 -0
- package/QUICK_REFERENCE.md +352 -0
- package/README.md +245 -31
- package/README_UI_COMPLETE.md +395 -0
- package/TYPE_DEFINITIONS.md +671 -0
- package/UI_GETTING_STARTED.md +523 -0
- package/examples/deskforge-app/App.ts +7 -11
- package/examples/deskforge-app/screens/CounterApp.ts +181 -0
- package/examples/deskforge-app/screens/GalleryApp.ts +267 -0
- package/examples/deskforge-app/screens/NotesApp.ts +376 -0
- package/examples/deskforge-app/screens/SettingsApp.ts +226 -0
- package/examples/deskforge-app/screens/TodoApp.ts +288 -0
- package/examples/deskforge-app/screens/index.ts +50 -0
- package/package.json +1 -1
- package/packages/cli/dist/commands/start.d.ts +7 -1
- package/packages/cli/dist/commands/start.d.ts.map +1 -1
- package/packages/cli/dist/commands/start.js +75 -21
- package/packages/cli/dist/commands/start.js.map +1 -1
- package/packages/cli/src/commands/start.ts +93 -21
- package/packages/cli/test/start.test.js +24 -0
- package/packages/core/dist/index.d.ts +1 -0
- package/packages/core/dist/index.d.ts.map +1 -1
- package/packages/core/dist/index.js +1 -0
- package/packages/core/dist/index.js.map +1 -1
- package/packages/core/dist/style/StyleObject.d.ts +115 -0
- package/packages/core/dist/style/StyleObject.d.ts.map +1 -0
- package/packages/core/dist/style/StyleObject.js +6 -0
- package/packages/core/dist/style/StyleObject.js.map +1 -0
- package/packages/core/dist/style/StyleSheet.d.ts +7 -0
- package/packages/core/dist/style/StyleSheet.d.ts.map +1 -0
- package/packages/core/dist/style/StyleSheet.js +13 -0
- package/packages/core/dist/style/StyleSheet.js.map +1 -0
- package/packages/core/dist/style/index.d.ts +3 -0
- package/packages/core/dist/style/index.d.ts.map +1 -0
- package/packages/core/dist/style/index.js +21 -0
- package/packages/core/dist/style/index.js.map +1 -0
- package/packages/core/src/index.ts +2 -1
- package/packages/core/src/style/StyleObject.ts +134 -0
- package/packages/core/src/style/StyleSheet.ts +19 -0
- package/packages/core/src/style/index.ts +5 -0
- package/packages/db/src/db.ts +4 -4
- package/packages/deskforge/src/index.ts +95 -0
- package/packages/file/dist/file.d.ts +1 -1
- package/packages/file/dist/file.d.ts.map +1 -1
- package/packages/file/dist/file.js +75 -3
- package/packages/file/dist/file.js.map +1 -1
- package/packages/file/src/file.ts +96 -4
- package/packages/file/test/file.test.js +23 -0
- package/packages/router/dist/index.d.ts +1 -0
- package/packages/router/dist/index.d.ts.map +1 -1
- package/packages/router/dist/index.js +1 -0
- package/packages/router/dist/index.js.map +1 -1
- package/packages/router/dist/navigation.d.ts +23 -0
- package/packages/router/dist/navigation.d.ts.map +1 -0
- package/packages/router/dist/navigation.js +61 -0
- package/packages/router/dist/navigation.js.map +1 -0
- package/packages/router/src/index.ts +2 -1
- package/packages/router/src/navigation.ts +86 -0
- package/packages/router/test/router.test.js +21 -1
- package/packages/theme/src/theme.ts +23 -0
- package/packages/ui/src/base/Button.ts +67 -0
- package/packages/ui/src/base/Image.ts +59 -0
- package/packages/ui/src/base/Pressable.ts +69 -0
- package/packages/ui/src/base/Text.ts +42 -0
- package/packages/ui/src/base/TextInput.ts +113 -0
- package/packages/ui/src/base/TouchableOpacity.ts +52 -0
- package/packages/ui/src/base/View.ts +43 -0
- package/packages/ui/src/index.ts +40 -25
- package/packages/ui/src/input/CheckBox.ts +69 -0
- package/packages/ui/src/input/Slider.ts +19 -2
- package/packages/ui/src/input/Switch.ts +68 -0
- package/packages/ui/src/layout/KeyboardAvoidingView.ts +65 -0
- package/packages/ui/src/layout/SafeAreaView.ts +58 -0
- package/packages/ui/src/list/FlatList.ts +111 -0
- package/packages/ui/src/list/ScrollView.ts +80 -0
- package/packages/ui/src/list/SectionList.ts +120 -0
- package/packages/ui/src/overlay/ActivityIndicator.ts +70 -0
- package/packages/ui/src/overlay/Modal.ts +28 -6
- package/packages/ui/src/platform/Platform.ts +119 -0
- package/packages/ui/src/platform/StatusBar.ts +63 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Todo List App - Complete Example
|
|
3
|
+
*
|
|
4
|
+
* Demonstrates FlatList, TextInput, CheckBox, Pressable handlers,
|
|
5
|
+
* complex state management, and list item operations.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
View,
|
|
10
|
+
Text,
|
|
11
|
+
Pressable,
|
|
12
|
+
FlatList,
|
|
13
|
+
TextInput,
|
|
14
|
+
CheckBox,
|
|
15
|
+
StyleSheet,
|
|
16
|
+
} from '@deskforge/ui/src'
|
|
17
|
+
import { State } from '@deskforge/state/src'
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Todo item structure
|
|
21
|
+
*/
|
|
22
|
+
interface TodoItem {
|
|
23
|
+
id: string
|
|
24
|
+
text: string
|
|
25
|
+
completed: boolean
|
|
26
|
+
createdAt: number
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Style definitions
|
|
31
|
+
*/
|
|
32
|
+
const styles = StyleSheet.create({
|
|
33
|
+
container: {
|
|
34
|
+
flex: 1,
|
|
35
|
+
backgroundColor: '#f8f9fc',
|
|
36
|
+
},
|
|
37
|
+
header: {
|
|
38
|
+
paddingHorizontal: 16,
|
|
39
|
+
paddingVertical: 12,
|
|
40
|
+
backgroundColor: '#ffffff',
|
|
41
|
+
borderBottomWidth: 1,
|
|
42
|
+
borderBottomColor: '#e2e8f0',
|
|
43
|
+
},
|
|
44
|
+
headerTitle: {
|
|
45
|
+
fontSize: 20,
|
|
46
|
+
fontWeight: 'bold',
|
|
47
|
+
color: '#111111',
|
|
48
|
+
},
|
|
49
|
+
stats: {
|
|
50
|
+
flexDirection: 'row',
|
|
51
|
+
gap: 12,
|
|
52
|
+
marginTop: 12,
|
|
53
|
+
},
|
|
54
|
+
statBox: {
|
|
55
|
+
flex: 1,
|
|
56
|
+
backgroundColor: '#f0f0f0',
|
|
57
|
+
borderRadius: 8,
|
|
58
|
+
padding: 12,
|
|
59
|
+
alignItems: 'center',
|
|
60
|
+
},
|
|
61
|
+
statNumber: {
|
|
62
|
+
fontSize: 18,
|
|
63
|
+
fontWeight: 'bold',
|
|
64
|
+
color: '#0077ff',
|
|
65
|
+
},
|
|
66
|
+
statLabel: {
|
|
67
|
+
fontSize: 12,
|
|
68
|
+
color: '#999999',
|
|
69
|
+
marginTop: 4,
|
|
70
|
+
},
|
|
71
|
+
inputSection: {
|
|
72
|
+
flexDirection: 'row',
|
|
73
|
+
gap: 8,
|
|
74
|
+
padding: 16,
|
|
75
|
+
backgroundColor: '#ffffff',
|
|
76
|
+
borderBottomWidth: 1,
|
|
77
|
+
borderBottomColor: '#e2e8f0',
|
|
78
|
+
},
|
|
79
|
+
input: {
|
|
80
|
+
flex: 1,
|
|
81
|
+
borderWidth: 1,
|
|
82
|
+
borderColor: '#e2e8f0',
|
|
83
|
+
borderRadius: 8,
|
|
84
|
+
paddingHorizontal: 12,
|
|
85
|
+
paddingVertical: 10,
|
|
86
|
+
fontSize: 14,
|
|
87
|
+
},
|
|
88
|
+
addButton: {
|
|
89
|
+
backgroundColor: '#2ecc71',
|
|
90
|
+
borderRadius: 8,
|
|
91
|
+
paddingHorizontal: 16,
|
|
92
|
+
justifyContent: 'center',
|
|
93
|
+
alignItems: 'center',
|
|
94
|
+
},
|
|
95
|
+
addButtonText: {
|
|
96
|
+
color: '#ffffff',
|
|
97
|
+
fontWeight: 'bold',
|
|
98
|
+
},
|
|
99
|
+
todoList: {
|
|
100
|
+
flex: 1,
|
|
101
|
+
},
|
|
102
|
+
todoItem: {
|
|
103
|
+
flexDirection: 'row',
|
|
104
|
+
alignItems: 'center',
|
|
105
|
+
paddingHorizontal: 16,
|
|
106
|
+
paddingVertical: 12,
|
|
107
|
+
borderBottomWidth: 1,
|
|
108
|
+
borderBottomColor: '#f0f0f0',
|
|
109
|
+
},
|
|
110
|
+
todoCheckbox: {
|
|
111
|
+
marginRight: 12,
|
|
112
|
+
},
|
|
113
|
+
todoContent: {
|
|
114
|
+
flex: 1,
|
|
115
|
+
},
|
|
116
|
+
todoText: {
|
|
117
|
+
fontSize: 15,
|
|
118
|
+
color: '#111111',
|
|
119
|
+
},
|
|
120
|
+
todoTextCompleted: {
|
|
121
|
+
textDecorationLine: 'line-through',
|
|
122
|
+
color: '#999999',
|
|
123
|
+
},
|
|
124
|
+
deleteButton: {
|
|
125
|
+
padding: 8,
|
|
126
|
+
marginLeft: 8,
|
|
127
|
+
},
|
|
128
|
+
deleteButtonText: {
|
|
129
|
+
color: '#e74c3c',
|
|
130
|
+
fontWeight: 'bold',
|
|
131
|
+
fontSize: 18,
|
|
132
|
+
},
|
|
133
|
+
emptyState: {
|
|
134
|
+
flex: 1,
|
|
135
|
+
justifyContent: 'center',
|
|
136
|
+
alignItems: 'center',
|
|
137
|
+
},
|
|
138
|
+
emptyStateText: {
|
|
139
|
+
fontSize: 16,
|
|
140
|
+
color: '#999999',
|
|
141
|
+
},
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* TodoApp - Main component
|
|
146
|
+
*
|
|
147
|
+
* Todo list with add, complete, and delete functionality.
|
|
148
|
+
* Demonstrates complex state mutations and list operations.
|
|
149
|
+
*/
|
|
150
|
+
export const TodoApp = () => {
|
|
151
|
+
const state = State({
|
|
152
|
+
todos: [] as TodoItem[],
|
|
153
|
+
newTodoText: '',
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
// Stats
|
|
157
|
+
const totalTodos = state.todos.length
|
|
158
|
+
const completedTodos = state.todos.filter((t) => t.completed).length
|
|
159
|
+
const remainingTodos = totalTodos - completedTodos
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Add a new todo
|
|
163
|
+
*/
|
|
164
|
+
const addTodo = () => {
|
|
165
|
+
if (!state.newTodoText.trim()) return
|
|
166
|
+
|
|
167
|
+
const newTodo: TodoItem = {
|
|
168
|
+
id: `todo_${Date.now()}`,
|
|
169
|
+
text: state.newTodoText,
|
|
170
|
+
completed: false,
|
|
171
|
+
createdAt: Date.now(),
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
state.todos.unshift(newTodo)
|
|
175
|
+
state.newTodoText = ''
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Toggle todo completion
|
|
180
|
+
*/
|
|
181
|
+
const toggleTodo = (id: string) => {
|
|
182
|
+
const todo = state.todos.find((t) => t.id === id)
|
|
183
|
+
if (todo) {
|
|
184
|
+
todo.completed = !todo.completed
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Delete a todo
|
|
190
|
+
*/
|
|
191
|
+
const deleteTodo = (id: string) => {
|
|
192
|
+
const index = state.todos.findIndex((t) => t.id === id)
|
|
193
|
+
if (index >= 0) {
|
|
194
|
+
state.todos.splice(index, 1)
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Render individual todo item
|
|
200
|
+
*/
|
|
201
|
+
const renderTodoItem = (todo: TodoItem) => {
|
|
202
|
+
return View(styles.todoItem, [
|
|
203
|
+
CheckBox(
|
|
204
|
+
todo.completed,
|
|
205
|
+
(value) => toggleTodo(todo.id),
|
|
206
|
+
styles.todoCheckbox,
|
|
207
|
+
{
|
|
208
|
+
tintColor: '#0077ff',
|
|
209
|
+
}
|
|
210
|
+
),
|
|
211
|
+
|
|
212
|
+
View(styles.todoContent, [
|
|
213
|
+
Text(
|
|
214
|
+
todo.text,
|
|
215
|
+
{
|
|
216
|
+
...styles.todoText,
|
|
217
|
+
...(todo.completed ? styles.todoTextCompleted : {}),
|
|
218
|
+
}
|
|
219
|
+
),
|
|
220
|
+
]),
|
|
221
|
+
|
|
222
|
+
Pressable(
|
|
223
|
+
styles.deleteButton,
|
|
224
|
+
{
|
|
225
|
+
onPress: () => deleteTodo(todo.id),
|
|
226
|
+
},
|
|
227
|
+
[
|
|
228
|
+
Text('×', styles.deleteButtonText),
|
|
229
|
+
]
|
|
230
|
+
),
|
|
231
|
+
])
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return View(styles.container, [
|
|
235
|
+
// Header with stats
|
|
236
|
+
View(styles.header, [
|
|
237
|
+
Text('Todos', styles.headerTitle),
|
|
238
|
+
|
|
239
|
+
View(styles.stats, [
|
|
240
|
+
View(styles.statBox, [
|
|
241
|
+
Text(String(totalTodos), styles.statNumber),
|
|
242
|
+
Text('Total', styles.statLabel),
|
|
243
|
+
]),
|
|
244
|
+
|
|
245
|
+
View(styles.statBox, [
|
|
246
|
+
Text(String(completedTodos), styles.statNumber),
|
|
247
|
+
Text('Done', styles.statLabel),
|
|
248
|
+
]),
|
|
249
|
+
|
|
250
|
+
View(styles.statBox, [
|
|
251
|
+
Text(String(remainingTodos), styles.statNumber),
|
|
252
|
+
Text('Left', styles.statLabel),
|
|
253
|
+
]),
|
|
254
|
+
]),
|
|
255
|
+
]),
|
|
256
|
+
|
|
257
|
+
// Input section
|
|
258
|
+
View(styles.inputSection, [
|
|
259
|
+
TextInput(styles.input, {
|
|
260
|
+
placeholder: 'Add a new todo...',
|
|
261
|
+
value: state.newTodoText,
|
|
262
|
+
onChangeText: (text) => {
|
|
263
|
+
state.newTodoText = text
|
|
264
|
+
},
|
|
265
|
+
}),
|
|
266
|
+
|
|
267
|
+
Pressable(styles.addButton, {
|
|
268
|
+
onPress: addTodo,
|
|
269
|
+
}, [
|
|
270
|
+
Text('Add', styles.addButtonText),
|
|
271
|
+
]),
|
|
272
|
+
]),
|
|
273
|
+
|
|
274
|
+
// Todo list
|
|
275
|
+
state.todos.length === 0
|
|
276
|
+
? View(styles.emptyState, [
|
|
277
|
+
Text('No todos yet!', styles.emptyStateText),
|
|
278
|
+
Text('Add one to get started', {
|
|
279
|
+
fontSize: 14,
|
|
280
|
+
color: '#ccc',
|
|
281
|
+
marginTop: 8,
|
|
282
|
+
}),
|
|
283
|
+
])
|
|
284
|
+
: FlatList(state.todos, renderTodoItem, styles.todoList),
|
|
285
|
+
])
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export default TodoApp
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DeskForge Examples Index
|
|
3
|
+
*
|
|
4
|
+
* Comprehensive examples demonstrating all UI components and patterns.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export { NotesApp } from './screens/NotesApp'
|
|
8
|
+
export { CounterApp } from './screens/CounterApp'
|
|
9
|
+
export { SettingsApp } from './screens/SettingsApp'
|
|
10
|
+
export { TodoApp } from './screens/TodoApp'
|
|
11
|
+
export { GalleryApp } from './screens/GalleryApp'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Example patterns demonstrated:
|
|
15
|
+
*
|
|
16
|
+
* NotesApp:
|
|
17
|
+
* - FlatList with dynamic data
|
|
18
|
+
* - Modal for user input
|
|
19
|
+
* - TextInput with validation
|
|
20
|
+
* - Database integration (DB.query)
|
|
21
|
+
* - Toast notifications
|
|
22
|
+
* - State management with multiple properties
|
|
23
|
+
*
|
|
24
|
+
* CounterApp:
|
|
25
|
+
* - Simple State reactivity
|
|
26
|
+
* - Computed values
|
|
27
|
+
* - Pressable handlers
|
|
28
|
+
* - Basic component composition
|
|
29
|
+
*
|
|
30
|
+
* SettingsApp:
|
|
31
|
+
* - Switch toggle component
|
|
32
|
+
* - Slider for numeric input
|
|
33
|
+
* - ScrollView for overflow content
|
|
34
|
+
* - Platform detection (Platform.OS)
|
|
35
|
+
* - Theme integration
|
|
36
|
+
*
|
|
37
|
+
* TodoApp:
|
|
38
|
+
* - FlatList with item operations
|
|
39
|
+
* - CheckBox for boolean state
|
|
40
|
+
* - Complex array mutations
|
|
41
|
+
* - Statistics computation
|
|
42
|
+
* - Delete/toggle item patterns
|
|
43
|
+
*
|
|
44
|
+
* GalleryApp:
|
|
45
|
+
* - Image component with URIs
|
|
46
|
+
* - Grid layout pattern
|
|
47
|
+
* - Full-screen preview modal
|
|
48
|
+
* - Image gallery best practices
|
|
49
|
+
* - ActivityIndicator for loading
|
|
50
|
+
*/
|
package/package.json
CHANGED
|
@@ -4,7 +4,13 @@ interface StartConfig {
|
|
|
4
4
|
platform?: string;
|
|
5
5
|
}
|
|
6
6
|
declare function findEntryFile(): string | null;
|
|
7
|
+
interface DesktopShellOptions {
|
|
8
|
+
appName: string;
|
|
9
|
+
outputPath: string;
|
|
10
|
+
port: number;
|
|
11
|
+
}
|
|
12
|
+
declare function renderDesktopShell(options: DesktopShellOptions): Promise<string>;
|
|
7
13
|
declare function start(config?: StartConfig): Promise<void>;
|
|
8
14
|
export type { StartConfig };
|
|
9
|
-
export { start, findEntryFile };
|
|
15
|
+
export { start, findEntryFile, renderDesktopShell };
|
|
10
16
|
//# sourceMappingURL=start.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../../src/commands/start.ts"],"names":[],"mappings":"AAaA,UAAU,WAAW;IAEnB,SAAS,EAAG,OAAO,CAAA;IAGnB,IAAI,EAAQ,MAAM,CAAA;IAGlB,QAAQ,CAAC,EAAG,MAAM,CAAA;CACnB;AAGD,iBAAS,aAAa,IAAI,MAAM,GAAG,IAAI,CAYtC;AAGD,iBAAe,KAAK,CAClB,MAAM,GAAE,WAA6C,GACpD,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../../src/commands/start.ts"],"names":[],"mappings":"AAaA,UAAU,WAAW;IAEnB,SAAS,EAAG,OAAO,CAAA;IAGnB,IAAI,EAAQ,MAAM,CAAA;IAGlB,QAAQ,CAAC,EAAG,MAAM,CAAA;CACnB;AAGD,iBAAS,aAAa,IAAI,MAAM,GAAG,IAAI,CAYtC;AAED,UAAU,mBAAmB;IAC3B,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,EAAE,MAAM,CAAA;IAClB,IAAI,EAAE,MAAM,CAAA;CACb;AAED,iBAAS,kBAAkB,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,CAAC,CAoEzE;AAGD,iBAAe,KAAK,CAClB,MAAM,GAAE,WAA6C,GACpD,OAAO,CAAC,IAAI,CAAC,CAmEf;AAED,YAAY,EAAE,WAAW,EAAE,CAAA;AAC3B,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAE,CAAA"}
|
|
@@ -8,10 +8,11 @@
|
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.start = start;
|
|
10
10
|
exports.findEntryFile = findEntryFile;
|
|
11
|
+
exports.renderDesktopShell = renderDesktopShell;
|
|
11
12
|
const fs = require("fs");
|
|
12
13
|
const path = require("path");
|
|
13
|
-
const http = require("http");
|
|
14
14
|
const child_process_1 = require("child_process");
|
|
15
|
+
const skia_canvas_1 = require("skia-canvas");
|
|
15
16
|
// Finds the app entry file in current directory
|
|
16
17
|
function findEntryFile() {
|
|
17
18
|
const candidates = [
|
|
@@ -24,6 +25,65 @@ function findEntryFile() {
|
|
|
24
25
|
}
|
|
25
26
|
return null;
|
|
26
27
|
}
|
|
28
|
+
function renderDesktopShell(options) {
|
|
29
|
+
return new Promise((resolve, reject) => {
|
|
30
|
+
try {
|
|
31
|
+
const canvas = new skia_canvas_1.Canvas(1280, 820);
|
|
32
|
+
const ctx = canvas.getContext('2d');
|
|
33
|
+
// Window chrome
|
|
34
|
+
ctx.fillStyle = '#0f172a';
|
|
35
|
+
ctx.fillRect(0, 0, 1280, 820);
|
|
36
|
+
ctx.fillStyle = '#111827';
|
|
37
|
+
ctx.fillRect(0, 0, 1280, 40);
|
|
38
|
+
ctx.fillStyle = '#ffffff';
|
|
39
|
+
ctx.font = '600 14px sans-serif';
|
|
40
|
+
ctx.fillText(options.appName, 24, 25);
|
|
41
|
+
// Traffic lights
|
|
42
|
+
ctx.fillStyle = '#ef4444';
|
|
43
|
+
ctx.beginPath();
|
|
44
|
+
ctx.arc(20, 20, 6, 0, Math.PI * 2);
|
|
45
|
+
ctx.fill();
|
|
46
|
+
ctx.fillStyle = '#f59e0b';
|
|
47
|
+
ctx.beginPath();
|
|
48
|
+
ctx.arc(40, 20, 6, 0, Math.PI * 2);
|
|
49
|
+
ctx.fill();
|
|
50
|
+
ctx.fillStyle = '#22c55e';
|
|
51
|
+
ctx.beginPath();
|
|
52
|
+
ctx.arc(60, 20, 6, 0, Math.PI * 2);
|
|
53
|
+
ctx.fill();
|
|
54
|
+
// Main surface
|
|
55
|
+
ctx.fillStyle = '#f8fafc';
|
|
56
|
+
ctx.fillRect(0, 40, 1280, 780);
|
|
57
|
+
ctx.fillStyle = '#111827';
|
|
58
|
+
ctx.font = '700 34px sans-serif';
|
|
59
|
+
ctx.fillText('DeskForge desktop shell', 70, 140);
|
|
60
|
+
ctx.fillStyle = '#475569';
|
|
61
|
+
ctx.font = '20px sans-serif';
|
|
62
|
+
ctx.fillText(`Running on port ${options.port}`, 70, 190);
|
|
63
|
+
ctx.fillStyle = '#ffffff';
|
|
64
|
+
ctx.fillRect(70, 230, 1140, 420);
|
|
65
|
+
ctx.fillStyle = '#e2e8f0';
|
|
66
|
+
ctx.fillRect(90, 250, 1100, 380);
|
|
67
|
+
ctx.fillStyle = '#0f172a';
|
|
68
|
+
ctx.font = '600 24px sans-serif';
|
|
69
|
+
ctx.fillText('Beautiful Skia desktop UI', 120, 320);
|
|
70
|
+
ctx.font = '18px sans-serif';
|
|
71
|
+
ctx.fillText('Fluid panels, modern chrome, and GPU-backed rendering.', 120, 360);
|
|
72
|
+
ctx.fillText('This shell is rendered by Skia instead of a browser page.', 120, 390);
|
|
73
|
+
ctx.fillStyle = '#2563eb';
|
|
74
|
+
ctx.fillRect(120, 430, 220, 46);
|
|
75
|
+
ctx.fillStyle = '#ffffff';
|
|
76
|
+
ctx.font = '600 18px sans-serif';
|
|
77
|
+
ctx.fillText('Open app', 168, 460);
|
|
78
|
+
canvas.toFile(options.outputPath, { format: 'png' })
|
|
79
|
+
.then(() => resolve(options.outputPath))
|
|
80
|
+
.catch(reject);
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
reject(err);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}
|
|
27
87
|
// Starts the development server
|
|
28
88
|
async function start(config = { hotReload: true, port: 3040 }) {
|
|
29
89
|
console.log('\n[dfcli] Starting DeskForge dev server...\n');
|
|
@@ -44,27 +104,21 @@ async function start(config = { hotReload: true, port: 3040 }) {
|
|
|
44
104
|
const raw = m && m[1] ? m[1] : '';
|
|
45
105
|
const platforms = raw.split(',').map(s => s.replace(/['"\s]/g, '')).filter(Boolean);
|
|
46
106
|
if (platforms.includes('desktop')) {
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
});
|
|
53
|
-
server.listen(webPort, () => {
|
|
54
|
-
console.log(`[dfcli] ✓ Desktop window served at http://localhost:${webPort}`);
|
|
55
|
-
// Open default browser as a lightweight native window
|
|
56
|
-
const opener = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
|
|
57
|
-
// Spawn via shell on Windows to use 'start'
|
|
58
|
-
if (process.platform === 'win32') {
|
|
59
|
-
(0, child_process_1.spawn)(opener, [`http://localhost:${webPort}`], { shell: true, stdio: 'ignore', detached: true }).unref();
|
|
60
|
-
}
|
|
61
|
-
else {
|
|
62
|
-
(0, child_process_1.spawn)(opener, [`http://localhost:${webPort}`], { stdio: 'ignore', detached: true }).unref();
|
|
63
|
-
}
|
|
107
|
+
const shellPath = path.join(process.cwd(), '.deskforge-desktop-shell.png');
|
|
108
|
+
await renderDesktopShell({
|
|
109
|
+
appName: path.basename(process.cwd()),
|
|
110
|
+
outputPath: shellPath,
|
|
111
|
+
port: config.port || 3040,
|
|
64
112
|
});
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
process.
|
|
113
|
+
console.log(`[dfcli] ✓ Desktop shell rendered to ${shellPath}`);
|
|
114
|
+
// Open the generated shell image using the OS default viewer if available.
|
|
115
|
+
const opener = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
|
|
116
|
+
if (process.platform === 'win32') {
|
|
117
|
+
(0, child_process_1.spawn)(opener, [shellPath], { shell: true, stdio: 'ignore', detached: true }).unref();
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
(0, child_process_1.spawn)(opener, [shellPath], { stdio: 'ignore', detached: true }).unref();
|
|
121
|
+
}
|
|
68
122
|
}
|
|
69
123
|
}
|
|
70
124
|
catch (err) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"start.js","sourceRoot":"","sources":["../../src/commands/start.ts"],"names":[],"mappings":";AAAA,WAAW;AACX,cAAc;AACd,2CAA2C;AAC3C,oCAAoC;AACpC,gDAAgD;AAChD,6CAA6C;;
|
|
1
|
+
{"version":3,"file":"start.js","sourceRoot":"","sources":["../../src/commands/start.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
|
|
8
8
|
import * as fs from 'fs'
|
|
9
9
|
import * as path from 'path'
|
|
10
|
-
import * as http from 'http'
|
|
11
10
|
import { spawn } from 'child_process'
|
|
11
|
+
import { Canvas } from 'skia-canvas'
|
|
12
12
|
|
|
13
13
|
// Development server configuration
|
|
14
14
|
interface StartConfig {
|
|
@@ -37,6 +37,82 @@ function findEntryFile(): string | null {
|
|
|
37
37
|
return null
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
interface DesktopShellOptions {
|
|
41
|
+
appName: string
|
|
42
|
+
outputPath: string
|
|
43
|
+
port: number
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function renderDesktopShell(options: DesktopShellOptions): Promise<string> {
|
|
47
|
+
return new Promise((resolve, reject) => {
|
|
48
|
+
try {
|
|
49
|
+
const canvas = new Canvas(1280, 820)
|
|
50
|
+
const ctx = canvas.getContext('2d')
|
|
51
|
+
|
|
52
|
+
// Window chrome
|
|
53
|
+
ctx.fillStyle = '#0f172a'
|
|
54
|
+
ctx.fillRect(0, 0, 1280, 820)
|
|
55
|
+
|
|
56
|
+
ctx.fillStyle = '#111827'
|
|
57
|
+
ctx.fillRect(0, 0, 1280, 40)
|
|
58
|
+
|
|
59
|
+
ctx.fillStyle = '#ffffff'
|
|
60
|
+
ctx.font = '600 14px sans-serif'
|
|
61
|
+
ctx.fillText(options.appName, 24, 25)
|
|
62
|
+
|
|
63
|
+
// Traffic lights
|
|
64
|
+
ctx.fillStyle = '#ef4444'
|
|
65
|
+
ctx.beginPath()
|
|
66
|
+
ctx.arc(20, 20, 6, 0, Math.PI * 2)
|
|
67
|
+
ctx.fill()
|
|
68
|
+
ctx.fillStyle = '#f59e0b'
|
|
69
|
+
ctx.beginPath()
|
|
70
|
+
ctx.arc(40, 20, 6, 0, Math.PI * 2)
|
|
71
|
+
ctx.fill()
|
|
72
|
+
ctx.fillStyle = '#22c55e'
|
|
73
|
+
ctx.beginPath()
|
|
74
|
+
ctx.arc(60, 20, 6, 0, Math.PI * 2)
|
|
75
|
+
ctx.fill()
|
|
76
|
+
|
|
77
|
+
// Main surface
|
|
78
|
+
ctx.fillStyle = '#f8fafc'
|
|
79
|
+
ctx.fillRect(0, 40, 1280, 780)
|
|
80
|
+
|
|
81
|
+
ctx.fillStyle = '#111827'
|
|
82
|
+
ctx.font = '700 34px sans-serif'
|
|
83
|
+
ctx.fillText('DeskForge desktop shell', 70, 140)
|
|
84
|
+
|
|
85
|
+
ctx.fillStyle = '#475569'
|
|
86
|
+
ctx.font = '20px sans-serif'
|
|
87
|
+
ctx.fillText(`Running on port ${options.port}`, 70, 190)
|
|
88
|
+
|
|
89
|
+
ctx.fillStyle = '#ffffff'
|
|
90
|
+
ctx.fillRect(70, 230, 1140, 420)
|
|
91
|
+
ctx.fillStyle = '#e2e8f0'
|
|
92
|
+
ctx.fillRect(90, 250, 1100, 380)
|
|
93
|
+
|
|
94
|
+
ctx.fillStyle = '#0f172a'
|
|
95
|
+
ctx.font = '600 24px sans-serif'
|
|
96
|
+
ctx.fillText('Beautiful Skia desktop UI', 120, 320)
|
|
97
|
+
ctx.font = '18px sans-serif'
|
|
98
|
+
ctx.fillText('Fluid panels, modern chrome, and GPU-backed rendering.', 120, 360)
|
|
99
|
+
ctx.fillText('This shell is rendered by Skia instead of a browser page.', 120, 390)
|
|
100
|
+
|
|
101
|
+
ctx.fillStyle = '#2563eb'
|
|
102
|
+
ctx.fillRect(120, 430, 220, 46)
|
|
103
|
+
ctx.fillStyle = '#ffffff'
|
|
104
|
+
ctx.font = '600 18px sans-serif'
|
|
105
|
+
ctx.fillText('Open app', 168, 460)
|
|
106
|
+
|
|
107
|
+
canvas.toFile(options.outputPath, { format: 'png' })
|
|
108
|
+
.then(() => resolve(options.outputPath))
|
|
109
|
+
.catch(reject)
|
|
110
|
+
} catch (err) {
|
|
111
|
+
reject(err as Error)
|
|
112
|
+
}
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
|
|
40
116
|
// Starts the development server
|
|
41
117
|
async function start(
|
|
42
118
|
config: StartConfig = { hotReload: true, port: 3040 }
|
|
@@ -69,26 +145,22 @@ async function start(
|
|
|
69
145
|
const raw = m && m[1] ? m[1] : ''
|
|
70
146
|
const platforms = raw.split(',').map(s => s.replace(/['"\s]/g, '')).filter(Boolean)
|
|
71
147
|
if (platforms.includes('desktop')) {
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
})
|
|
78
|
-
server.listen(webPort, () => {
|
|
79
|
-
console.log(`[dfcli] ✓ Desktop window served at http://localhost:${webPort}`)
|
|
80
|
-
// Open default browser as a lightweight native window
|
|
81
|
-
const opener = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open'
|
|
82
|
-
// Spawn via shell on Windows to use 'start'
|
|
83
|
-
if (process.platform === 'win32') {
|
|
84
|
-
spawn(opener, [`http://localhost:${webPort}`], { shell: true, stdio: 'ignore', detached: true }).unref()
|
|
85
|
-
} else {
|
|
86
|
-
spawn(opener, [`http://localhost:${webPort}`], { stdio: 'ignore', detached: true }).unref()
|
|
87
|
-
}
|
|
148
|
+
const shellPath = path.join(process.cwd(), '.deskforge-desktop-shell.png')
|
|
149
|
+
await renderDesktopShell({
|
|
150
|
+
appName: path.basename(process.cwd()),
|
|
151
|
+
outputPath: shellPath,
|
|
152
|
+
port: config.port || 3040,
|
|
88
153
|
})
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
154
|
+
|
|
155
|
+
console.log(`[dfcli] ✓ Desktop shell rendered to ${shellPath}`)
|
|
156
|
+
|
|
157
|
+
// Open the generated shell image using the OS default viewer if available.
|
|
158
|
+
const opener = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open'
|
|
159
|
+
if (process.platform === 'win32') {
|
|
160
|
+
spawn(opener, [shellPath], { shell: true, stdio: 'ignore', detached: true }).unref()
|
|
161
|
+
} else {
|
|
162
|
+
spawn(opener, [shellPath], { stdio: 'ignore', detached: true }).unref()
|
|
163
|
+
}
|
|
92
164
|
}
|
|
93
165
|
} catch (err) {
|
|
94
166
|
// non-fatal
|
|
@@ -114,4 +186,4 @@ async function start(
|
|
|
114
186
|
}
|
|
115
187
|
|
|
116
188
|
export type { StartConfig }
|
|
117
|
-
export { start, findEntryFile }
|
|
189
|
+
export { start, findEntryFile, renderDesktopShell }
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const test = require('node:test')
|
|
2
|
+
const assert = require('node:assert/strict')
|
|
3
|
+
const fs = require('fs')
|
|
4
|
+
const os = require('os')
|
|
5
|
+
const path = require('path')
|
|
6
|
+
|
|
7
|
+
const startModule = require('../dist/commands/start.js')
|
|
8
|
+
|
|
9
|
+
test('desktop shell renderer writes a skia-based png file', async () => {
|
|
10
|
+
assert.equal(typeof startModule.renderDesktopShell, 'function')
|
|
11
|
+
|
|
12
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'deskforge-desktop-shell-'))
|
|
13
|
+
const outputPath = path.join(tmpDir, 'desktop-shell.png')
|
|
14
|
+
|
|
15
|
+
const result = await startModule.renderDesktopShell({
|
|
16
|
+
appName: 'DeskForge Demo',
|
|
17
|
+
outputPath,
|
|
18
|
+
port: 3040,
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
assert.equal(result, outputPath)
|
|
22
|
+
assert.ok(fs.existsSync(outputPath))
|
|
23
|
+
assert.ok(fs.statSync(outputPath).size > 0)
|
|
24
|
+
})
|