wad-lab 1.2.0 → 1.2.2
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/bin/index.js +855 -124
- package/package.json +1 -1
- package/templates/servlet-basic/script.sh +89 -0
- package/templates/servlet-flow/script.sh +89 -0
- package/templates/servlet-jdbc/script.sh +89 -0
- package/templates/servlet-session-tracking/script.sh +89 -0
- package/templates/servlet-voter-eligibility/script.sh +89 -0
package/bin/index.js
CHANGED
|
@@ -41,114 +41,630 @@ const frontendOptions = [
|
|
|
41
41
|
{ name: 'vue-axios-interceptor (Vue 3 + Axios Interceptors + Toast Alerts)', value: 'vue-axios-interceptor' }
|
|
42
42
|
];
|
|
43
43
|
|
|
44
|
+
const featuredTemplates = [
|
|
45
|
+
{
|
|
46
|
+
key: '1',
|
|
47
|
+
value: 'fullstack-springboot-vue',
|
|
48
|
+
name: '[1] fullstack-springboot-vue (Spring Boot 2-Model API + Selectable Vue Frontend)',
|
|
49
|
+
desc: 'Complete stack: 2-model Spring Boot backend + matching Vue frontend (Router/Pinia/Props)'
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
key: '2',
|
|
53
|
+
value: 'springboot-2model-1controller-api',
|
|
54
|
+
name: '[2] springboot-2model-1controller-api (2 Models Dept & Emp, 2 Repos, 1 Service)',
|
|
55
|
+
desc: 'Extensible 2-model Spring Boot architecture (Department + Employee) with full CRUD & PATCH'
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
key: '3',
|
|
59
|
+
value: 'springboot-mongo-aggregation-join',
|
|
60
|
+
name: '[3] springboot-mongo-aggregation-join ($lookup JOIN, @Query, @Aggregation)',
|
|
61
|
+
desc: 'MongoDB $lookup multi-collection JOIN, pipeline stages ($match/$group), and @Query filters'
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
key: '4',
|
|
65
|
+
value: 'vue-axios-router-pinia-props',
|
|
66
|
+
name: '[4] vue-axios-router-pinia-props (Matching Frontend: Router + Pinia + Props/Emits)',
|
|
67
|
+
desc: 'Matching frontend for 2-model backend: Vue Router + Pinia centralized store + Props/Emits'
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
key: '5',
|
|
71
|
+
value: 'vue-axios-router-props',
|
|
72
|
+
name: '[5] vue-axios-router-props (Matching Frontend: Router Views + Props/Emits)',
|
|
73
|
+
desc: 'Matching frontend for 2-model backend: Vue Router pages + reusable components with Props/Emits'
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
key: '6',
|
|
77
|
+
value: 'servlet-jdbc',
|
|
78
|
+
name: '[6] servlet-jdbc (Tomcat 9 + MySQL Database Connection via JDBC)',
|
|
79
|
+
desc: 'Tomcat Servlet with MySQL Driver, JDBC connection, SQL statement execution, and HTML table'
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
key: '7',
|
|
83
|
+
value: 'servlet-flow',
|
|
84
|
+
name: '[7] servlet-flow (Tomcat 9 Execution Flow with GET/POST Form — No DB)',
|
|
85
|
+
desc: 'Pure servlet without DB: HTML form submission, doGet / doPost processing, and response render'
|
|
86
|
+
}
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
const categories = [
|
|
90
|
+
{
|
|
91
|
+
id: 'fullstack',
|
|
92
|
+
title: 'Fullstack Web Applications',
|
|
93
|
+
desc: 'Integrated Spring Boot + MongoDB + Vue 3 apps'
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
id: 'springboot',
|
|
97
|
+
title: 'Spring Boot Backend APIs',
|
|
98
|
+
desc: 'REST APIs, MongoDB, Aggregations, Join & MockMvc'
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
id: 'servlet',
|
|
102
|
+
title: 'Java Servlets & Tomcat 9',
|
|
103
|
+
desc: 'Classic Tomcat Servlets, JDBC, Sessions & Web.xml'
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
id: 'vue-axios',
|
|
107
|
+
title: 'Vue 3 + Axios HTTP Variants',
|
|
108
|
+
desc: 'All 10+ architectural combinations of Vue 3 with Axios'
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
id: 'vue-core',
|
|
112
|
+
title: 'Vue 3 Core Portals & State',
|
|
113
|
+
desc: 'Router, Pinia, Native Fetch & Complete UI Portals'
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
id: 'games',
|
|
117
|
+
title: 'Interactive Games (JS & Vue)',
|
|
118
|
+
desc: 'Logic, DOM & State Management Games'
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
id: 'html-css',
|
|
122
|
+
title: 'HTML5 & CSS3 Foundations',
|
|
123
|
+
desc: 'Form Validation, DOM Manipulation & 50 CSS Properties'
|
|
124
|
+
}
|
|
125
|
+
];
|
|
126
|
+
|
|
44
127
|
const templates = [
|
|
45
|
-
|
|
46
|
-
{
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
{
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
{
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
{
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
{
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
{
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
{
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
{
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
{
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
{
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
{
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
128
|
+
// Fullstack (4)
|
|
129
|
+
{
|
|
130
|
+
category: 'fullstack',
|
|
131
|
+
value: 'fullstack-springboot-vue',
|
|
132
|
+
name: 'fullstack-springboot-vue (Spring Boot 2-Model API + Selectable Vue 3 Frontend)'
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
category: 'fullstack',
|
|
136
|
+
value: 'vue-springboot-mongo-crud',
|
|
137
|
+
name: 'vue-springboot-mongo-crud (Spring Boot + MongoDB + Vue 3 Direct CRUD)'
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
category: 'fullstack',
|
|
141
|
+
value: 'vue-springboot-mongo-aggregation',
|
|
142
|
+
name: 'vue-springboot-mongo-aggregation (Spring Boot Aggregation + Vue 3 Dashboard)'
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
category: 'fullstack',
|
|
146
|
+
value: 'vue-router-pinia-springboot',
|
|
147
|
+
name: 'vue-router-pinia-springboot (Fullstack Enterprise: Router + Pinia + Spring Boot)'
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
// Spring Boot (9)
|
|
151
|
+
{
|
|
152
|
+
category: 'springboot',
|
|
153
|
+
value: 'springboot-mongo-crud',
|
|
154
|
+
name: 'springboot-mongo-crud (4-Layer REST API: Controller -> Service -> Repo -> Mongo)'
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
category: 'springboot',
|
|
158
|
+
value: 'springboot-mongo-aggregation',
|
|
159
|
+
name: 'springboot-mongo-aggregation (Aggregation Pipeline: $group, $sum, $avg, $sort)'
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
category: 'springboot',
|
|
163
|
+
value: 'springboot-mongo-aggregation-join',
|
|
164
|
+
name: 'springboot-mongo-aggregation-join (MongoDB $lookup JOIN, @Query, @Aggregation)'
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
category: 'springboot',
|
|
168
|
+
value: 'springboot-mockmvc',
|
|
169
|
+
name: 'springboot-mockmvc (Controller Unit Testing with MockMvc)'
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
category: 'springboot',
|
|
173
|
+
value: 'springboot-mongo-aggregation-mockmvc',
|
|
174
|
+
name: 'springboot-mongo-aggregation-mockmvc (Full Testing Lab: MockMvc + Aggregation)'
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
category: 'springboot',
|
|
178
|
+
value: 'springboot-2model-1controller-api',
|
|
179
|
+
name: 'springboot-2model-1controller-api (2 Models Dept & Emp, 2 Repos, 1 Service, 1 Controller)'
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
category: 'springboot',
|
|
183
|
+
value: 'springboot-course-registration',
|
|
184
|
+
name: 'springboot-course-registration (Exercise 5 — Course Registration REST API)'
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
category: 'springboot',
|
|
188
|
+
value: 'springboot-hospital-api',
|
|
189
|
+
name: 'springboot-hospital-api (Hospital OPD Appointments & Doctors API)'
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
category: 'springboot',
|
|
193
|
+
value: 'springboot-library-api',
|
|
194
|
+
name: 'springboot-library-api (Library Catalog, Loans & Analytics API)'
|
|
195
|
+
},
|
|
196
|
+
|
|
197
|
+
// Servlets (5)
|
|
198
|
+
{
|
|
199
|
+
category: 'servlet',
|
|
200
|
+
value: 'servlet-basic',
|
|
201
|
+
name: 'servlet-basic (Tomcat GET + POST Basic Servlet)'
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
category: 'servlet',
|
|
205
|
+
value: 'servlet-flow',
|
|
206
|
+
name: 'servlet-flow (HelloServlet Execution Flow with GET/POST Form)'
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
category: 'servlet',
|
|
210
|
+
value: 'servlet-jdbc',
|
|
211
|
+
name: 'servlet-jdbc (Tomcat + MySQL JDBC Database Connection)'
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
category: 'servlet',
|
|
215
|
+
value: 'servlet-session-tracking',
|
|
216
|
+
name: 'servlet-session-tracking (HttpSession, Cookies, Redirect & URL Rewriting)'
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
category: 'servlet',
|
|
220
|
+
value: 'servlet-voter-eligibility',
|
|
221
|
+
name: 'servlet-voter-eligibility (Voting Eligibility Check: Name & Age Validation)'
|
|
222
|
+
},
|
|
223
|
+
|
|
224
|
+
// Vue + Axios (11)
|
|
225
|
+
{
|
|
226
|
+
category: 'vue-axios',
|
|
227
|
+
value: 'vue-axios-basic',
|
|
228
|
+
name: 'vue-axios-basic (Vue 3 + Axios Direct CRUD in Single View)'
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
category: 'vue-axios-props-emits',
|
|
232
|
+
value: 'vue-axios-props-emits',
|
|
233
|
+
name: 'vue-axios-props-emits (Vue 3 + Axios + Parent/Child Props & Emits)'
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
category: 'vue-axios-router',
|
|
237
|
+
value: 'vue-axios-router',
|
|
238
|
+
name: 'vue-axios-router (Vue 3 + Axios + Router Views Dashboard & Details)'
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
category: 'vue-axios-pinia',
|
|
242
|
+
value: 'vue-axios-pinia',
|
|
243
|
+
name: 'vue-axios-pinia (Vue 3 + Axios + Pinia Centralized State Store)'
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
category: 'vue-axios-router-props',
|
|
247
|
+
value: 'vue-axios-router-props',
|
|
248
|
+
name: 'vue-axios-router-props (Vue 3 + Axios + Router + Component Props/Emits)'
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
category: 'vue-axios-pinia-props',
|
|
252
|
+
value: 'vue-axios-pinia-props',
|
|
253
|
+
name: 'vue-axios-pinia-props (Vue 3 + Axios + Pinia Store + Props/Emits)'
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
category: 'vue-axios-router-pinia',
|
|
257
|
+
value: 'vue-axios-router-pinia',
|
|
258
|
+
name: 'vue-axios-router-pinia (Vue 3 + Axios + Router Views + Pinia Store)'
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
category: 'vue-axios-router-pinia-props',
|
|
262
|
+
value: 'vue-axios-router-pinia-props',
|
|
263
|
+
name: 'vue-axios-router-pinia-props (The Complete Stack: Router + Pinia + Props + Emits)'
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
category: 'vue-axios-router-children',
|
|
267
|
+
value: 'vue-axios-router-children',
|
|
268
|
+
name: 'vue-axios-router-children (Vue 3 + Axios + Router Nested Children Layouts)'
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
category: 'vue-axios-interceptor',
|
|
272
|
+
value: 'vue-axios-interceptor',
|
|
273
|
+
name: 'vue-axios-interceptor (Vue 3 + Axios Interceptors + Toast Notifications)'
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
category: 'vue-axios',
|
|
277
|
+
value: 'vue-axios',
|
|
278
|
+
name: 'vue-axios (Classic Vue 3 Axios Service Layer CRUD)'
|
|
279
|
+
},
|
|
280
|
+
|
|
281
|
+
// Vue Core & Portals (16)
|
|
282
|
+
{
|
|
283
|
+
category: 'vue-core',
|
|
284
|
+
value: 'vue-basics',
|
|
285
|
+
name: 'vue-basics (Core Reactivity: ref, reactive, computed, v-model, watch)'
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
category: 'vue-props-emits',
|
|
289
|
+
value: 'vue-props-emits',
|
|
290
|
+
name: 'vue-props-emits (Parent/Child Component Communication & Custom Events)'
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
category: 'vue-router',
|
|
294
|
+
value: 'vue-router',
|
|
295
|
+
name: 'vue-router (Vue Router Parent Layout & Nested Children)'
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
category: 'vue-pinia',
|
|
299
|
+
value: 'vue-pinia',
|
|
300
|
+
name: 'vue-pinia (Pinia State Store: State, Getters, Actions)'
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
category: 'vue-router-pinia',
|
|
304
|
+
value: 'vue-router-pinia',
|
|
305
|
+
name: 'vue-router-pinia (Router Multi-Page Views + Pinia Store)'
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
category: 'vue-router-pinia-props',
|
|
309
|
+
value: 'vue-router-pinia-props',
|
|
310
|
+
name: 'vue-router-pinia-props (Router + Pinia Store + Component Props/Emits)'
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
category: 'vue-fetch',
|
|
314
|
+
value: 'vue-fetch',
|
|
315
|
+
name: 'vue-fetch (Native Fetch API CRUD without Axios)'
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
category: 'vue-todo-pinia',
|
|
319
|
+
value: 'vue-todo-pinia',
|
|
320
|
+
name: 'vue-todo-pinia (Pinia Task & Todo Manager)'
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
category: 'vue-todo-router',
|
|
324
|
+
value: 'vue-todo-router',
|
|
325
|
+
name: 'vue-todo-router (Router Multi-View Task Application)'
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
category: 'vue-cart-router-pinia',
|
|
329
|
+
value: 'vue-cart-router-pinia',
|
|
330
|
+
name: 'vue-cart-router-pinia (E-Commerce Shopping Cart with Router & Pinia)'
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
category: 'vue-food-ordering',
|
|
334
|
+
value: 'vue-food-ordering',
|
|
335
|
+
name: 'vue-food-ordering (Exercise 7 — Online Food Ordering Application)'
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
category: 'vue-symposium-system',
|
|
339
|
+
value: 'vue-symposium-system',
|
|
340
|
+
name: 'vue-symposium-system (Exercise 6 — College Symposium Management System)'
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
category: 'vue-banking-app',
|
|
344
|
+
value: 'vue-banking-app',
|
|
345
|
+
name: 'vue-banking-app (Banking Portal & Transaction Ledger System)'
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
category: 'vue-employee-system',
|
|
349
|
+
value: 'vue-employee-system',
|
|
350
|
+
name: 'vue-employee-system (Employee HR, Payroll & Records System)'
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
category: 'vue-hospital-portal',
|
|
354
|
+
value: 'vue-hospital-portal',
|
|
355
|
+
name: 'vue-hospital-portal (Hospital OPD & Patient Vitals Portal)'
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
category: 'vue-library-system',
|
|
359
|
+
value: 'vue-library-system',
|
|
360
|
+
name: 'vue-library-system (Library Catalog & Book Circulation System)'
|
|
361
|
+
},
|
|
362
|
+
|
|
363
|
+
// Games (9)
|
|
364
|
+
{
|
|
365
|
+
category: 'games',
|
|
366
|
+
value: 'game-bingo',
|
|
367
|
+
name: 'game-bingo (Exercise 3 — Two-Player JavaScript Bingo Game)'
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
category: 'games',
|
|
371
|
+
value: 'game-rock-paper-scissors',
|
|
372
|
+
name: 'game-rock-paper-scissors (Player vs Computer Rock Paper Scissors — Vanilla JS)'
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
category: 'games',
|
|
376
|
+
value: 'game-tic-tac-toe',
|
|
377
|
+
name: 'game-tic-tac-toe (3x3 Turn Grid with Win Detection — Vanilla JS)'
|
|
378
|
+
},
|
|
379
|
+
{
|
|
380
|
+
category: 'games',
|
|
381
|
+
value: 'game-whack-a-mole',
|
|
382
|
+
name: 'game-whack-a-mole (3x3 Mole Whack with Timer & Score — Vanilla JS)'
|
|
383
|
+
},
|
|
384
|
+
{
|
|
385
|
+
category: 'games',
|
|
386
|
+
value: 'game-memory-cards',
|
|
387
|
+
name: 'game-memory-cards (3x4 Flashcard Matching Game — Vanilla JS)'
|
|
388
|
+
},
|
|
389
|
+
{
|
|
390
|
+
category: 'games',
|
|
391
|
+
value: 'vue-game-rps',
|
|
392
|
+
name: 'vue-game-rps (Rock Paper Scissors with Vue 3 + Pinia Scoreboard)'
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
category: 'games',
|
|
396
|
+
value: 'vue-game-tictactoe',
|
|
397
|
+
name: 'vue-game-tictactoe (Tic Tac Toe with Vue 3 + Pinia Move History)'
|
|
398
|
+
},
|
|
399
|
+
{
|
|
400
|
+
category: 'games',
|
|
401
|
+
value: 'vue-game-whackamole',
|
|
402
|
+
name: 'vue-game-whackamole (Whack-A-Mole with Vue 3 + Pinia Countdown)'
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
category: 'games',
|
|
406
|
+
value: 'vue-memory-game',
|
|
407
|
+
name: 'vue-memory-game (Card Matching Game with Vue 3 + Pinia Flips)'
|
|
408
|
+
},
|
|
409
|
+
|
|
410
|
+
// HTML / CSS (2)
|
|
411
|
+
{
|
|
412
|
+
category: 'html-css',
|
|
413
|
+
value: 'html-css-js',
|
|
414
|
+
name: 'html-css-js (Form Validation Regex, Required & Dynamic DOM)'
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
category: 'html-css',
|
|
418
|
+
value: 'html-css-styles',
|
|
419
|
+
name: 'html-css-styles (50 CSS Properties Showcase: Box Model, Flex, Grid, Animations)'
|
|
420
|
+
}
|
|
102
421
|
];
|
|
103
422
|
|
|
104
423
|
const PAGE_SIZE = 12;
|
|
424
|
+
const templatesRoot = path.join(__dirname, '..', 'templates');
|
|
425
|
+
|
|
426
|
+
let currentView = 'MAIN'; // 'MAIN' | 'CATEGORY_LIST' | 'CATEGORY' | 'SEARCH' | 'ALL'
|
|
427
|
+
let activeCategory = null;
|
|
428
|
+
let searchQuery = '';
|
|
105
429
|
let selectedIndex = 0;
|
|
106
430
|
const selectedValues = new Set();
|
|
107
|
-
const templatesRoot = path.join(__dirname, '..', 'templates');
|
|
108
431
|
let isPrompting = false;
|
|
109
432
|
|
|
110
|
-
function
|
|
433
|
+
function getActiveItemList() {
|
|
434
|
+
if (currentView === 'MAIN') {
|
|
435
|
+
const list = featuredTemplates.map(f => ({
|
|
436
|
+
type: 'featured',
|
|
437
|
+
id: f.value,
|
|
438
|
+
name: f.name,
|
|
439
|
+
desc: f.desc
|
|
440
|
+
}));
|
|
441
|
+
list.push({ type: 'action', id: 'categories', name: '[C] Browse by Category (7 Groups, 56 Templates)', desc: 'Explore all 56 templates categorized into 7 groups' });
|
|
442
|
+
list.push({ type: 'action', id: 'search', name: '[S] Search by keyword across all 56 templates', desc: 'Type any keyword to find templates instantly' });
|
|
443
|
+
list.push({ type: 'action', id: 'all_view', name: '[L] View all 56 templates (grouped catalog)', desc: 'Browse the entire catalog with section dividers' });
|
|
444
|
+
list.push({ type: 'action', id: 'unpack_all', name: '[A] Unpack all 56 templates in 1-click', desc: 'Copy every single template into target folder' });
|
|
445
|
+
return list;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
if (currentView === 'CATEGORY_LIST') {
|
|
449
|
+
return categories.map((cat, idx) => {
|
|
450
|
+
const count = templates.filter(t => t.category === cat.id).length;
|
|
451
|
+
return {
|
|
452
|
+
type: 'cat_entry',
|
|
453
|
+
id: cat.id,
|
|
454
|
+
name: `[${idx + 1}] ${cat.title} (${count} templates)`,
|
|
455
|
+
desc: cat.desc
|
|
456
|
+
};
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
if (currentView === 'CATEGORY') {
|
|
461
|
+
return templates.filter(t => t.category === activeCategory.id);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
if (currentView === 'SEARCH') {
|
|
465
|
+
const q = searchQuery.toLowerCase().trim();
|
|
466
|
+
if (!q) return [];
|
|
467
|
+
return templates.filter(t =>
|
|
468
|
+
t.value.toLowerCase().includes(q) ||
|
|
469
|
+
t.name.toLowerCase().includes(q) ||
|
|
470
|
+
t.category.toLowerCase().includes(q)
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
if (currentView === 'ALL') {
|
|
475
|
+
return templates;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
return [];
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function renderScreen() {
|
|
111
482
|
console.clear();
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
483
|
+
|
|
484
|
+
if (currentView === 'MAIN') {
|
|
485
|
+
console.log('========================================================');
|
|
486
|
+
console.log(' WAD LAB STARTER TEMPLATES ');
|
|
487
|
+
console.log(' Exam Quick-Start & 56 Ready Templates ');
|
|
488
|
+
console.log('========================================================\n');
|
|
489
|
+
console.log('CORE EXAM STARTERS:');
|
|
490
|
+
console.log(' Press [1-7] for instant pick, or navigate with [UP / DOWN]');
|
|
491
|
+
console.log(' [SPACE] Select | [ENTER] Confirm | [C] Categories | [S] Search | [A] All\n');
|
|
492
|
+
|
|
493
|
+
const items = getActiveItemList();
|
|
494
|
+
items.forEach((item, idx) => {
|
|
495
|
+
if (item.id === 'categories') {
|
|
496
|
+
console.log('\nFULL CATALOG & EXPLORATION:');
|
|
497
|
+
}
|
|
498
|
+
const isCursor = idx === selectedIndex;
|
|
499
|
+
const isChecked = selectedValues.has(item.id);
|
|
500
|
+
const box = item.type === 'featured' ? (isChecked ? '[x] ' : '[ ] ') : '';
|
|
501
|
+
const prefix = isCursor ? ' > ' : ' ';
|
|
502
|
+
console.log(prefix + box + item.name);
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
const activeItem = items[selectedIndex];
|
|
506
|
+
if (activeItem && activeItem.desc) {
|
|
507
|
+
console.log('\n Info: ' + activeItem.desc);
|
|
508
|
+
}
|
|
509
|
+
if (selectedValues.size > 0) {
|
|
510
|
+
console.log(`\n Selected: ${selectedValues.size} template(s) [Press ENTER to proceed]`);
|
|
511
|
+
}
|
|
512
|
+
return;
|
|
122
513
|
}
|
|
123
|
-
const end = Math.min(start + PAGE_SIZE, total);
|
|
124
514
|
|
|
125
|
-
if (
|
|
126
|
-
console.log(
|
|
127
|
-
|
|
128
|
-
console.log('
|
|
515
|
+
if (currentView === 'CATEGORY_LIST') {
|
|
516
|
+
console.log('========================================================');
|
|
517
|
+
console.log(' Browse Templates by Category (56 Total)');
|
|
518
|
+
console.log('========================================================\n');
|
|
519
|
+
console.log(' Press [1-7] or [ENTER] to open category');
|
|
520
|
+
console.log(' [B / ESC] Back to Main Menu\n');
|
|
521
|
+
|
|
522
|
+
const items = getActiveItemList();
|
|
523
|
+
items.forEach((item, idx) => {
|
|
524
|
+
const isCursor = idx === selectedIndex;
|
|
525
|
+
const prefix = isCursor ? ' > ' : ' ';
|
|
526
|
+
console.log(prefix + item.name);
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
const activeItem = items[selectedIndex];
|
|
530
|
+
if (activeItem && activeItem.desc) {
|
|
531
|
+
console.log('\n Info: ' + activeItem.desc);
|
|
532
|
+
}
|
|
533
|
+
return;
|
|
129
534
|
}
|
|
130
535
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
console.log(
|
|
536
|
+
if (currentView === 'CATEGORY') {
|
|
537
|
+
console.log('========================================================');
|
|
538
|
+
console.log(` ${activeCategory.title}`);
|
|
539
|
+
console.log(` ${activeCategory.desc}`);
|
|
540
|
+
console.log('========================================================\n');
|
|
541
|
+
console.log(' [SPACE] Select / Deselect (multi-select supported)');
|
|
542
|
+
console.log(' [ENTER] Confirm selection and proceed');
|
|
543
|
+
console.log(' [B / ESC] Back to Categories Menu\n');
|
|
544
|
+
|
|
545
|
+
const items = getActiveItemList();
|
|
546
|
+
const total = items.length;
|
|
547
|
+
|
|
548
|
+
let start = selectedIndex - Math.floor(PAGE_SIZE / 2);
|
|
549
|
+
if (start < 0) start = 0;
|
|
550
|
+
if (start + PAGE_SIZE > total) {
|
|
551
|
+
start = Math.max(0, total - PAGE_SIZE);
|
|
552
|
+
}
|
|
553
|
+
const end = Math.min(start + PAGE_SIZE, total);
|
|
554
|
+
|
|
555
|
+
if (start > 0) {
|
|
556
|
+
console.log(` ^ (${start} more above)`);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
for (let i = start; i < end; i++) {
|
|
560
|
+
const t = items[i];
|
|
561
|
+
const isCursor = i === selectedIndex;
|
|
562
|
+
const isChecked = selectedValues.has(t.value);
|
|
563
|
+
const box = isChecked ? '[x]' : '[ ]';
|
|
564
|
+
const prefix = isCursor ? ' > ' : ' ';
|
|
565
|
+
console.log(prefix + box + ' ' + t.name);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
if (end < total) {
|
|
569
|
+
console.log(` v (${total - end} more below)`);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
const selCount = selectedValues.size;
|
|
573
|
+
console.log(`\n Selected: ${selCount} template(s) [${selectedIndex + 1}/${total}]`);
|
|
574
|
+
return;
|
|
138
575
|
}
|
|
139
576
|
|
|
140
|
-
if (
|
|
141
|
-
console.log(
|
|
142
|
-
|
|
143
|
-
console.log('
|
|
577
|
+
if (currentView === 'SEARCH') {
|
|
578
|
+
console.log('========================================================');
|
|
579
|
+
console.log(' Instant Template Search');
|
|
580
|
+
console.log('========================================================');
|
|
581
|
+
console.log(` Query: "${searchQuery}" (type letters/numbers, Backspace to delete)`);
|
|
582
|
+
console.log(' [SPACE] Select | [ENTER] Confirm | [ESC] Back\n');
|
|
583
|
+
|
|
584
|
+
const items = getActiveItemList();
|
|
585
|
+
if (!searchQuery.trim()) {
|
|
586
|
+
console.log(' Start typing to search across all 56 templates...');
|
|
587
|
+
console.log(' Try keywords: "join", "2model", "props", "servlet", "pinia", "mongo"\n');
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
if (items.length === 0) {
|
|
592
|
+
console.log(` No templates matched "${searchQuery}".`);
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
const total = items.length;
|
|
597
|
+
let start = selectedIndex - Math.floor(PAGE_SIZE / 2);
|
|
598
|
+
if (start < 0) start = 0;
|
|
599
|
+
if (start + PAGE_SIZE > total) {
|
|
600
|
+
start = Math.max(0, total - PAGE_SIZE);
|
|
601
|
+
}
|
|
602
|
+
const end = Math.min(start + PAGE_SIZE, total);
|
|
603
|
+
|
|
604
|
+
if (start > 0) {
|
|
605
|
+
console.log(` ^ (${start} more above)`);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
for (let i = start; i < end; i++) {
|
|
609
|
+
const t = items[i];
|
|
610
|
+
const isCursor = i === selectedIndex;
|
|
611
|
+
const isChecked = selectedValues.has(t.value);
|
|
612
|
+
const box = isChecked ? '[x]' : '[ ]';
|
|
613
|
+
const prefix = isCursor ? ' > ' : ' ';
|
|
614
|
+
console.log(prefix + box + ' ' + t.name);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
if (end < total) {
|
|
618
|
+
console.log(` v (${total - end} more below)`);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
console.log(`\n Matches: ${total} | Selected: ${selectedValues.size}`);
|
|
622
|
+
return;
|
|
144
623
|
}
|
|
145
624
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
625
|
+
if (currentView === 'ALL') {
|
|
626
|
+
console.log('========================================================');
|
|
627
|
+
console.log(' Full Catalog: All 56 Templates');
|
|
628
|
+
console.log('========================================================\n');
|
|
629
|
+
console.log(' [SPACE] Select / Deselect | [ENTER] Confirm | [ESC] Back\n');
|
|
630
|
+
|
|
631
|
+
const items = getActiveItemList();
|
|
632
|
+
const total = items.length;
|
|
633
|
+
|
|
634
|
+
let start = selectedIndex - Math.floor(PAGE_SIZE / 2);
|
|
635
|
+
if (start < 0) start = 0;
|
|
636
|
+
if (start + PAGE_SIZE > total) {
|
|
637
|
+
start = Math.max(0, total - PAGE_SIZE);
|
|
638
|
+
}
|
|
639
|
+
const end = Math.min(start + PAGE_SIZE, total);
|
|
640
|
+
|
|
641
|
+
if (start > 0) {
|
|
642
|
+
console.log(` ^ (${start} more above)`);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
let lastCat = null;
|
|
646
|
+
for (let i = start; i < end; i++) {
|
|
647
|
+
const t = items[i];
|
|
648
|
+
if (t.category !== lastCat) {
|
|
649
|
+
const catObj = categories.find(c => c.id === t.category);
|
|
650
|
+
if (catObj) {
|
|
651
|
+
console.log(` --- ${catObj.title} ---`);
|
|
652
|
+
}
|
|
653
|
+
lastCat = t.category;
|
|
654
|
+
}
|
|
655
|
+
const isCursor = i === selectedIndex;
|
|
656
|
+
const isChecked = selectedValues.has(t.value);
|
|
657
|
+
const box = isChecked ? '[x]' : '[ ]';
|
|
658
|
+
const prefix = isCursor ? ' > ' : ' ';
|
|
659
|
+
console.log(prefix + box + ' ' + t.name);
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
if (end < total) {
|
|
663
|
+
console.log(` v (${total - end} more below)`);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
console.log(`\n [${selectedIndex + 1}/${total}] Selected: ${selectedValues.size}`);
|
|
667
|
+
}
|
|
152
668
|
}
|
|
153
669
|
|
|
154
670
|
function promptFolderName(choices) {
|
|
@@ -208,7 +724,7 @@ function promptFolderName(choices) {
|
|
|
208
724
|
|
|
209
725
|
if (isAll) {
|
|
210
726
|
copyDirRecursive(templatesRoot, destDir);
|
|
211
|
-
console.log('\nSuccessfully unpacked ALL templates into: ' + destDir);
|
|
727
|
+
console.log('\nSuccessfully unpacked ALL 56 templates into: ' + destDir);
|
|
212
728
|
} else {
|
|
213
729
|
choices.forEach(c => {
|
|
214
730
|
if (c === 'fullstack-springboot-vue') {
|
|
@@ -245,22 +761,49 @@ function promptFolderName(choices) {
|
|
|
245
761
|
});
|
|
246
762
|
}
|
|
247
763
|
|
|
764
|
+
// Check command-line arguments for direct non-interactive use
|
|
248
765
|
if (process.argv[2]) {
|
|
249
|
-
const arg = process.argv[2];
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
766
|
+
const arg = process.argv[2].trim();
|
|
767
|
+
if (arg === 'all' || arg === '--all' || arg === '-a') {
|
|
768
|
+
promptFolderName(['all']);
|
|
769
|
+
} else if (arg === '--cat' && process.argv[3]) {
|
|
770
|
+
const catId = process.argv[3].trim().toLowerCase();
|
|
771
|
+
const foundCat = categories.find(c => c.id === catId);
|
|
772
|
+
if (foundCat) {
|
|
773
|
+
activeCategory = foundCat;
|
|
774
|
+
currentView = 'CATEGORY';
|
|
775
|
+
selectedIndex = 0;
|
|
776
|
+
startInteractive();
|
|
777
|
+
} else {
|
|
778
|
+
console.error(`Unknown category: "${catId}". Available: ${categories.map(c => c.id).join(', ')}`);
|
|
779
|
+
process.exit(1);
|
|
780
|
+
}
|
|
781
|
+
} else if (arg === '--search' && process.argv[3]) {
|
|
782
|
+
searchQuery = process.argv.slice(3).join(' ');
|
|
783
|
+
currentView = 'SEARCH';
|
|
784
|
+
selectedIndex = 0;
|
|
785
|
+
startInteractive();
|
|
253
786
|
} else {
|
|
254
|
-
|
|
255
|
-
|
|
787
|
+
const found = templates.find(t => t.value === arg);
|
|
788
|
+
if (found) {
|
|
789
|
+
promptFolderName([found.value]);
|
|
790
|
+
} else {
|
|
791
|
+
console.error('Unknown template: ' + arg);
|
|
792
|
+
console.error(`Available templates (56 total) or run without arguments for the interactive menu.`);
|
|
793
|
+
process.exit(1);
|
|
794
|
+
}
|
|
256
795
|
}
|
|
257
796
|
} else {
|
|
797
|
+
startInteractive();
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
function startInteractive() {
|
|
258
801
|
readline.emitKeypressEvents(process.stdin);
|
|
259
802
|
if (process.stdin.isTTY) {
|
|
260
803
|
process.stdin.setRawMode(true);
|
|
261
804
|
}
|
|
262
805
|
|
|
263
|
-
|
|
806
|
+
renderScreen();
|
|
264
807
|
|
|
265
808
|
process.stdin.on('keypress', (str, key) => {
|
|
266
809
|
if (isPrompting) return;
|
|
@@ -269,36 +812,224 @@ if (process.argv[2]) {
|
|
|
269
812
|
process.exit(0);
|
|
270
813
|
}
|
|
271
814
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
815
|
+
const items = getActiveItemList();
|
|
816
|
+
|
|
817
|
+
// MAIN VIEW INPUT
|
|
818
|
+
if (currentView === 'MAIN') {
|
|
819
|
+
if (key.name === 'up') {
|
|
820
|
+
selectedIndex = (selectedIndex - 1 + items.length) % items.length;
|
|
821
|
+
renderScreen();
|
|
822
|
+
} else if (key.name === 'down') {
|
|
823
|
+
selectedIndex = (selectedIndex + 1) % items.length;
|
|
824
|
+
renderScreen();
|
|
825
|
+
} else if (str === 'c' || str === 'C') {
|
|
826
|
+
currentView = 'CATEGORY_LIST';
|
|
827
|
+
selectedIndex = 0;
|
|
828
|
+
renderScreen();
|
|
829
|
+
} else if (str === 's' || str === 'S') {
|
|
830
|
+
currentView = 'SEARCH';
|
|
831
|
+
searchQuery = '';
|
|
832
|
+
selectedIndex = 0;
|
|
833
|
+
renderScreen();
|
|
834
|
+
} else if (str === 'a' || str === 'A') {
|
|
835
|
+
promptFolderName(['all']);
|
|
836
|
+
} else if (str === 'l' || str === 'L') {
|
|
837
|
+
currentView = 'ALL';
|
|
838
|
+
selectedIndex = 0;
|
|
839
|
+
renderScreen();
|
|
840
|
+
} else if (str >= '1' && str <= '7') {
|
|
841
|
+
const num = parseInt(str, 10);
|
|
842
|
+
const target = featuredTemplates[num - 1];
|
|
843
|
+
if (target) {
|
|
844
|
+
promptFolderName([target.value]);
|
|
286
845
|
}
|
|
287
|
-
} else {
|
|
288
|
-
|
|
289
|
-
if (
|
|
290
|
-
selectedValues.
|
|
846
|
+
} else if (key.name === 'space') {
|
|
847
|
+
const item = items[selectedIndex];
|
|
848
|
+
if (item.type === 'featured') {
|
|
849
|
+
if (selectedValues.has(item.id)) {
|
|
850
|
+
selectedValues.delete(item.id);
|
|
851
|
+
} else {
|
|
852
|
+
selectedValues.add(item.id);
|
|
853
|
+
}
|
|
854
|
+
renderScreen();
|
|
855
|
+
}
|
|
856
|
+
} else if (key.name === 'return' || key.name === 'enter') {
|
|
857
|
+
const item = items[selectedIndex];
|
|
858
|
+
if (selectedValues.size > 0) {
|
|
859
|
+
promptFolderName(Array.from(selectedValues));
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
if (item.type === 'featured') {
|
|
863
|
+
promptFolderName([item.id]);
|
|
864
|
+
} else if (item.id === 'categories') {
|
|
865
|
+
currentView = 'CATEGORY_LIST';
|
|
866
|
+
selectedIndex = 0;
|
|
867
|
+
renderScreen();
|
|
868
|
+
} else if (item.id === 'search') {
|
|
869
|
+
currentView = 'SEARCH';
|
|
870
|
+
searchQuery = '';
|
|
871
|
+
selectedIndex = 0;
|
|
872
|
+
renderScreen();
|
|
873
|
+
} else if (item.id === 'all_view') {
|
|
874
|
+
currentView = 'ALL';
|
|
875
|
+
selectedIndex = 0;
|
|
876
|
+
renderScreen();
|
|
877
|
+
} else if (item.id === 'unpack_all') {
|
|
878
|
+
promptFolderName(['all']);
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
return;
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
// CATEGORY_LIST VIEW INPUT
|
|
885
|
+
if (currentView === 'CATEGORY_LIST') {
|
|
886
|
+
if (key.name === 'escape' || key.name === 'left' || str === 'b' || str === 'B') {
|
|
887
|
+
currentView = 'MAIN';
|
|
888
|
+
selectedIndex = 0;
|
|
889
|
+
renderScreen();
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
if (key.name === 'up') {
|
|
893
|
+
selectedIndex = (selectedIndex - 1 + items.length) % items.length;
|
|
894
|
+
renderScreen();
|
|
895
|
+
} else if (key.name === 'down') {
|
|
896
|
+
selectedIndex = (selectedIndex + 1) % items.length;
|
|
897
|
+
renderScreen();
|
|
898
|
+
} else if (str >= '1' && str <= '7') {
|
|
899
|
+
const num = parseInt(str, 10);
|
|
900
|
+
activeCategory = categories[num - 1];
|
|
901
|
+
currentView = 'CATEGORY';
|
|
902
|
+
selectedIndex = 0;
|
|
903
|
+
renderScreen();
|
|
904
|
+
} else if (key.name === 'return' || key.name === 'enter') {
|
|
905
|
+
const item = items[selectedIndex];
|
|
906
|
+
activeCategory = categories.find(c => c.id === item.id);
|
|
907
|
+
currentView = 'CATEGORY';
|
|
908
|
+
selectedIndex = 0;
|
|
909
|
+
renderScreen();
|
|
910
|
+
}
|
|
911
|
+
return;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
// SEARCH VIEW INPUT
|
|
915
|
+
if (currentView === 'SEARCH') {
|
|
916
|
+
if (key.name === 'escape') {
|
|
917
|
+
currentView = 'MAIN';
|
|
918
|
+
selectedIndex = 0;
|
|
919
|
+
renderScreen();
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
if (key.name === 'up') {
|
|
924
|
+
if (items.length > 0) {
|
|
925
|
+
selectedIndex = (selectedIndex - 1 + items.length) % items.length;
|
|
926
|
+
renderScreen();
|
|
927
|
+
}
|
|
928
|
+
return;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
if (key.name === 'down') {
|
|
932
|
+
if (items.length > 0) {
|
|
933
|
+
selectedIndex = (selectedIndex + 1) % items.length;
|
|
934
|
+
renderScreen();
|
|
935
|
+
}
|
|
936
|
+
return;
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
if (key.name === 'space') {
|
|
940
|
+
if (items.length > 0) {
|
|
941
|
+
const currentVal = items[selectedIndex].value;
|
|
942
|
+
if (selectedValues.has(currentVal)) {
|
|
943
|
+
selectedValues.delete(currentVal);
|
|
944
|
+
} else {
|
|
945
|
+
selectedValues.add(currentVal);
|
|
946
|
+
}
|
|
947
|
+
renderScreen();
|
|
948
|
+
}
|
|
949
|
+
return;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
if (key.name === 'return' || key.name === 'enter') {
|
|
953
|
+
let chosen = Array.from(selectedValues);
|
|
954
|
+
if (chosen.length === 0 && items.length > 0) {
|
|
955
|
+
chosen = [items[selectedIndex].value];
|
|
956
|
+
}
|
|
957
|
+
if (chosen.length > 0) {
|
|
958
|
+
promptFolderName(chosen);
|
|
959
|
+
}
|
|
960
|
+
return;
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
if (key.name === 'backspace') {
|
|
964
|
+
if (searchQuery.length > 0) {
|
|
965
|
+
searchQuery = searchQuery.slice(0, -1);
|
|
966
|
+
selectedIndex = 0;
|
|
967
|
+
renderScreen();
|
|
291
968
|
} else {
|
|
292
|
-
|
|
969
|
+
currentView = 'MAIN';
|
|
970
|
+
selectedIndex = 0;
|
|
971
|
+
renderScreen();
|
|
293
972
|
}
|
|
973
|
+
return;
|
|
294
974
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
975
|
+
|
|
976
|
+
// Typing search query
|
|
977
|
+
if (str && str.length === 1 && !key.ctrl && !key.meta) {
|
|
978
|
+
searchQuery += str;
|
|
979
|
+
selectedIndex = 0;
|
|
980
|
+
renderScreen();
|
|
981
|
+
return;
|
|
982
|
+
}
|
|
983
|
+
return;
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
// CATEGORY / ALL VIEW INPUT
|
|
987
|
+
if (currentView === 'CATEGORY' || currentView === 'ALL') {
|
|
988
|
+
if (key.name === 'escape' || key.name === 'left' || str === 'b' || str === 'B') {
|
|
989
|
+
currentView = currentView === 'CATEGORY' ? 'CATEGORY_LIST' : 'MAIN';
|
|
990
|
+
selectedIndex = 0;
|
|
991
|
+
renderScreen();
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
if (key.name === 'up') {
|
|
996
|
+
if (items.length > 0) {
|
|
997
|
+
selectedIndex = (selectedIndex - 1 + items.length) % items.length;
|
|
998
|
+
renderScreen();
|
|
999
|
+
}
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
if (key.name === 'down') {
|
|
1004
|
+
if (items.length > 0) {
|
|
1005
|
+
selectedIndex = (selectedIndex + 1) % items.length;
|
|
1006
|
+
renderScreen();
|
|
1007
|
+
}
|
|
1008
|
+
return;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
if (key.name === 'space') {
|
|
1012
|
+
if (items.length > 0) {
|
|
1013
|
+
const currentVal = items[selectedIndex].value;
|
|
1014
|
+
if (selectedValues.has(currentVal)) {
|
|
1015
|
+
selectedValues.delete(currentVal);
|
|
1016
|
+
} else {
|
|
1017
|
+
selectedValues.add(currentVal);
|
|
1018
|
+
}
|
|
1019
|
+
renderScreen();
|
|
1020
|
+
}
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
if (key.name === 'return' || key.name === 'enter') {
|
|
1025
|
+
let chosen = Array.from(selectedValues);
|
|
1026
|
+
if (chosen.length === 0 && items.length > 0) {
|
|
1027
|
+
chosen = [items[selectedIndex].value];
|
|
1028
|
+
}
|
|
1029
|
+
if (chosen.length > 0) {
|
|
1030
|
+
promptFolderName(chosen);
|
|
1031
|
+
}
|
|
300
1032
|
}
|
|
301
|
-
promptFolderName(chosen);
|
|
302
1033
|
}
|
|
303
1034
|
});
|
|
304
1035
|
}
|