wad-lab 1.2.2 → 1.2.3
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 +472 -631
- package/package.json +1 -1
- package/templates/fullstack-springboot-vue/frontend/vue-axios-basic/src/App.vue +241 -429
- package/templates/fullstack-springboot-vue/frontend/vue-axios-basic/src/components/ItemFormModal.vue +183 -0
- package/templates/fullstack-springboot-vue/frontend/vue-axios-basic/src/components/ItemPatchModal.vue +193 -0
- package/templates/fullstack-springboot-vue/frontend/vue-axios-basic/src/components/ItemTable.vue +177 -0
- package/templates/fullstack-springboot-vue/frontend/vue-axios-pinia/src/App.vue +150 -441
- package/templates/fullstack-springboot-vue/frontend/vue-axios-pinia/src/components/ItemFormModal.vue +207 -0
- package/templates/fullstack-springboot-vue/frontend/vue-axios-pinia/src/components/ItemStats.vue +64 -0
- package/templates/fullstack-springboot-vue/frontend/vue-axios-pinia/src/components/ItemTable.vue +195 -0
- package/templates/fullstack-springboot-vue/frontend/vue-axios-router/src/App.vue +41 -90
- package/templates/fullstack-springboot-vue/frontend/vue-axios-router/src/components/ItemFormModal.vue +173 -0
- package/templates/fullstack-springboot-vue/frontend/vue-axios-router/src/components/ItemTable.vue +102 -0
- package/templates/fullstack-springboot-vue/frontend/vue-axios-router/src/components/NavBar.vue +57 -0
- package/templates/fullstack-springboot-vue/frontend/vue-axios-router/src/views/DepartmentView.vue +132 -296
- package/templates/fullstack-springboot-vue/frontend/vue-axios-router/src/views/EmployeeView.vue +126 -219
- package/templates/fullstack-springboot-vue/frontend/vue-axios-router-children/src/components/ItemDetail.vue +115 -0
- package/templates/fullstack-springboot-vue/frontend/vue-axios-router-children/src/components/ItemTable.vue +104 -0
- package/templates/fullstack-springboot-vue/frontend/vue-axios-router-children/src/components/SideBar.vue +59 -0
- package/templates/fullstack-springboot-vue/frontend/vue-axios-router-children/src/views/CompanyLayout.vue +2 -51
- package/templates/fullstack-springboot-vue/frontend/vue-axios-router-children/src/views/DepartmentListView.vue +92 -250
- package/templates/fullstack-springboot-vue/frontend/vue-axios-router-children/src/views/EmployeeDetailView.vue +59 -181
- package/templates/fullstack-springboot-vue/frontend/vue-axios-router-children/src/views/EmployeeListView.vue +67 -315
- package/templates/fullstack-springboot-vue/frontend/vue-axios-router-pinia/src/App.vue +19 -70
- package/templates/fullstack-springboot-vue/frontend/vue-axios-router-pinia/src/components/ItemFormModal.vue +173 -0
- package/templates/fullstack-springboot-vue/frontend/vue-axios-router-pinia/src/components/ItemPatchModal.vue +144 -0
- package/templates/fullstack-springboot-vue/frontend/vue-axios-router-pinia/src/components/ItemTable.vue +102 -0
- package/templates/fullstack-springboot-vue/frontend/vue-axios-router-pinia/src/components/NavBar.vue +53 -0
- package/templates/fullstack-springboot-vue/frontend/vue-axios-router-pinia/src/views/DepartmentView.vue +107 -229
- package/templates/fullstack-springboot-vue/frontend/vue-axios-router-pinia/src/views/EmployeeView.vue +143 -257
- package/templates/game-bingo/script.js +1 -1
- package/templates/game-memory-cards/script.js +2 -2
- package/templates/game-rock-paper-scissors/index.html +3 -3
- package/templates/game-rock-paper-scissors/script.js +2 -2
- package/templates/game-tic-tac-toe/script.js +2 -2
- package/templates/game-whack-a-mole/index.html +9 -9
- package/templates/servlet-basic/WEB-INF/ItemServlet.java +13 -0
- package/templates/servlet-basic/WEB-INF/classes/ItemServlet.class +0 -0
- package/templates/servlet-flow/WEB-INF/HelloServlet.java +34 -25
- package/templates/servlet-flow/WEB-INF/classes/HelloServlet.class +0 -0
- package/templates/servlet-jdbc/WEB-INF/JdbcItemServlet.java +13 -0
- package/templates/servlet-jdbc/WEB-INF/classes/JdbcItemServlet.class +0 -0
- package/templates/snippets/mongo-queries-cheatsheet.txt +105 -0
- package/templates/springboot-2model-aggregation-join/.mvn/wrapper/maven-wrapper.properties +3 -0
- package/templates/springboot-2model-aggregation-join/mvnw +295 -0
- package/templates/springboot-2model-aggregation-join/mvnw.cmd +189 -0
- package/templates/springboot-2model-aggregation-join/pom.xml +54 -0
- package/templates/springboot-2model-aggregation-join/src/main/java/com/example/aggregation/AggregationApplication.java +11 -0
- package/templates/springboot-2model-aggregation-join/src/main/java/com/example/aggregation/controller/DepartmentController.java +65 -0
- package/templates/springboot-2model-aggregation-join/src/main/java/com/example/aggregation/controller/EmployeeController.java +65 -0
- package/templates/springboot-2model-aggregation-join/src/main/java/com/example/aggregation/dto/DepartmentStatsDTO.java +15 -0
- package/templates/springboot-2model-aggregation-join/src/main/java/com/example/aggregation/dto/EmployeeJoinDTO.java +19 -0
- package/templates/springboot-2model-aggregation-join/src/main/java/com/example/aggregation/model/Department.java +20 -0
- package/templates/springboot-2model-aggregation-join/src/main/java/com/example/aggregation/model/Employee.java +21 -0
- package/templates/springboot-2model-aggregation-join/src/main/java/com/example/aggregation/repository/DepartmentRepository.java +9 -0
- package/templates/springboot-2model-aggregation-join/src/main/java/com/example/aggregation/repository/EmployeeRepository.java +9 -0
- package/templates/springboot-2model-aggregation-join/src/main/java/com/example/aggregation/service/DepartmentService.java +81 -0
- package/templates/springboot-2model-aggregation-join/src/main/java/com/example/aggregation/service/EmployeeService.java +80 -0
- package/templates/springboot-2model-aggregation-join/src/main/resources/application.properties +2 -0
- package/templates/springboot-2model-aggregation-join/src/test/java/com/example/aggregation/AggregationApplicationTests.java +13 -0
- package/templates/vue-2model-crud-patch/README.md +64 -0
- package/templates/vue-2model-crud-patch/index.html +12 -0
- package/templates/vue-2model-crud-patch/package-lock.json +1785 -0
- package/templates/vue-2model-crud-patch/package.json +21 -0
- package/templates/vue-2model-crud-patch/src/App.vue +63 -0
- package/templates/vue-2model-crud-patch/src/components/AnalyticsBoard.vue +291 -0
- package/templates/vue-2model-crud-patch/src/components/DepartmentFormModal.vue +138 -0
- package/templates/vue-2model-crud-patch/src/components/DepartmentPatchModal.vue +210 -0
- package/templates/vue-2model-crud-patch/src/components/DepartmentTable.vue +133 -0
- package/templates/vue-2model-crud-patch/src/components/EmployeeFormModal.vue +177 -0
- package/templates/vue-2model-crud-patch/src/components/EmployeePatchModal.vue +221 -0
- package/templates/vue-2model-crud-patch/src/components/EmployeeTable.vue +171 -0
- package/templates/vue-2model-crud-patch/src/components/NavBar.vue +156 -0
- package/templates/vue-2model-crud-patch/src/main.js +12 -0
- package/templates/vue-2model-crud-patch/src/router/index.js +33 -0
- package/templates/vue-2model-crud-patch/src/services/api.js +64 -0
- package/templates/vue-2model-crud-patch/src/stores/departmentStore.js +149 -0
- package/templates/vue-2model-crud-patch/src/stores/employeeStore.js +143 -0
- package/templates/vue-2model-crud-patch/src/style.css +415 -0
- package/templates/vue-2model-crud-patch/src/views/AnalyticsView.vue +59 -0
- package/templates/vue-2model-crud-patch/src/views/DepartmentsView.vue +135 -0
- package/templates/vue-2model-crud-patch/src/views/EmployeesView.vue +143 -0
- package/templates/vue-2model-crud-patch/vite.config.js +9 -0
- package/templates/vue-axios/src/App.vue +255 -264
- package/templates/vue-axios/src/components/ItemFormModal.vue +183 -0
- package/templates/vue-axios/src/components/ItemPatchModal.vue +193 -0
- package/templates/vue-axios/src/components/ItemTable.vue +177 -0
- package/templates/vue-axios-basic/src/App.vue +241 -429
- package/templates/vue-axios-basic/src/components/ItemFormModal.vue +183 -0
- package/templates/vue-axios-basic/src/components/ItemPatchModal.vue +193 -0
- package/templates/vue-axios-basic/src/components/ItemTable.vue +177 -0
- package/templates/vue-axios-pinia/src/App.vue +150 -441
- package/templates/vue-axios-pinia/src/components/ItemFormModal.vue +207 -0
- package/templates/vue-axios-pinia/src/components/ItemStats.vue +64 -0
- package/templates/vue-axios-pinia/src/components/ItemTable.vue +195 -0
- package/templates/vue-axios-router/src/App.vue +41 -90
- package/templates/vue-axios-router/src/components/ItemFormModal.vue +173 -0
- package/templates/vue-axios-router/src/components/ItemTable.vue +102 -0
- package/templates/vue-axios-router/src/components/NavBar.vue +57 -0
- package/templates/vue-axios-router/src/views/DepartmentView.vue +132 -296
- package/templates/vue-axios-router/src/views/EmployeeView.vue +126 -219
- package/templates/vue-axios-router-children/src/components/ItemDetail.vue +115 -0
- package/templates/vue-axios-router-children/src/components/ItemTable.vue +104 -0
- package/templates/vue-axios-router-children/src/components/SideBar.vue +59 -0
- package/templates/vue-axios-router-children/src/views/CompanyLayout.vue +2 -51
- package/templates/vue-axios-router-children/src/views/DepartmentListView.vue +92 -250
- package/templates/vue-axios-router-children/src/views/EmployeeDetailView.vue +59 -181
- package/templates/vue-axios-router-children/src/views/EmployeeListView.vue +67 -315
- package/templates/vue-axios-router-pinia/src/App.vue +19 -70
- package/templates/vue-axios-router-pinia/src/components/ItemFormModal.vue +173 -0
- package/templates/vue-axios-router-pinia/src/components/ItemPatchModal.vue +144 -0
- package/templates/vue-axios-router-pinia/src/components/ItemTable.vue +102 -0
- package/templates/vue-axios-router-pinia/src/components/NavBar.vue +53 -0
- package/templates/vue-axios-router-pinia/src/views/DepartmentView.vue +107 -229
- package/templates/vue-axios-router-pinia/src/views/EmployeeView.vue +143 -257
- package/templates/vue-banking-app/src/components/AccountOverview.vue +75 -0
- package/templates/vue-banking-app/src/components/TransactionForm.vue +89 -0
- package/templates/vue-banking-app/src/components/TransactionTable.vue +113 -0
- package/templates/vue-banking-app/src/stores/bankStore.js +16 -3
- package/templates/vue-banking-app/src/views/DashboardView.vue +23 -71
- package/templates/vue-banking-app/src/views/StatementView.vue +11 -158
- package/templates/vue-banking-app/src/views/TransactionFormView.vue +23 -121
- package/templates/vue-basics/src/App.vue +105 -76
- package/templates/vue-basics/src/components/CounterCard.vue +107 -0
- package/templates/vue-basics/src/components/ItemList.vue +99 -0
- package/templates/vue-basics/src/components/UserForm.vue +138 -0
- package/templates/vue-cart-router-pinia/src/components/CartSummary.vue +139 -0
- package/templates/vue-cart-router-pinia/src/components/CheckoutModal.vue +149 -0
- package/templates/vue-cart-router-pinia/src/components/ProductCard.vue +63 -0
- package/templates/vue-cart-router-pinia/src/stores/cartStore.js +11 -0
- package/templates/vue-cart-router-pinia/src/views/CartView.vue +52 -118
- package/templates/vue-cart-router-pinia/src/views/CheckoutView.vue +2 -5
- package/templates/vue-cart-router-pinia/src/views/ProductCatalogView.vue +20 -56
- package/templates/vue-employee-system/src/App.vue +75 -344
- package/templates/vue-employee-system/src/components/DepartmentFilter.vue +108 -0
- package/templates/vue-employee-system/src/components/EmployeeModal.vue +183 -0
- package/templates/vue-employee-system/src/components/EmployeeTable.vue +91 -0
- package/templates/vue-employee-system/src/stores/employeeStore.js +12 -0
- package/templates/vue-fetch/src/App.vue +261 -285
- package/templates/vue-fetch/src/components/FetchFormModal.vue +183 -0
- package/templates/vue-fetch/src/components/FetchPatchModal.vue +193 -0
- package/templates/vue-fetch/src/components/FetchTable.vue +177 -0
- package/templates/vue-food-ordering/src/components/CartSummary.vue +134 -0
- package/templates/vue-food-ordering/src/components/FoodMenu.vue +126 -0
- package/templates/vue-food-ordering/src/components/OrderModal.vue +150 -0
- package/templates/vue-food-ordering/src/views/MenuView.vue +11 -132
- package/templates/vue-food-ordering/src/views/OrderCartView.vue +31 -164
- package/templates/vue-game-rps/src/App.vue +29 -88
- package/templates/vue-game-tictactoe/src/App.vue +23 -53
- package/templates/vue-game-whackamole/src/App.vue +44 -63
- package/templates/vue-hospital-portal/src/components/AppointmentForm.vue +144 -0
- package/templates/vue-hospital-portal/src/components/DoctorCard.vue +73 -0
- package/templates/vue-hospital-portal/src/components/PatientTable.vue +123 -0
- package/templates/vue-hospital-portal/src/stores/hospitalStore.js +10 -1
- package/templates/vue-hospital-portal/src/views/OPDRegistrationView.vue +40 -416
- package/templates/vue-hospital-portal/src/views/PatientListView.vue +11 -301
- package/templates/vue-library-system/src/components/BookForm.vue +125 -0
- package/templates/vue-library-system/src/components/BookTable.vue +112 -0
- package/templates/vue-library-system/src/components/BorrowModal.vue +131 -0
- package/templates/vue-library-system/src/stores/libraryStore.js +26 -1
- package/templates/vue-library-system/src/views/BookCatalogView.vue +34 -415
- package/templates/vue-memory-game/src/App.vue +36 -40
- package/templates/vue-memory-game/src/stores/memoryStore.js +32 -37
- package/templates/vue-router/src/App.vue +24 -14
- package/templates/vue-router/src/components/AboutCard.vue +38 -0
- package/templates/vue-router/src/components/HomeHero.vue +75 -0
- package/templates/vue-router/src/components/NavBar.vue +54 -0
- package/templates/vue-router/src/views/ItemsLayout.vue +17 -9
- package/templates/vue-router/src/views/ListView.vue +106 -18
- package/templates/vue-router-pinia/src/App.vue +24 -14
- package/templates/vue-router-pinia/src/components/ItemFormModal.vue +163 -0
- package/templates/vue-router-pinia/src/components/ItemTable.vue +90 -0
- package/templates/vue-router-pinia/src/components/NavBar.vue +54 -0
- package/templates/vue-router-pinia/src/stores/itemStore.js +8 -1
- package/templates/vue-router-pinia/src/views/ListView.vue +78 -14
- package/templates/vue-router-pinia-springboot/frontend/src/App.vue +25 -8
- package/templates/vue-router-pinia-springboot/frontend/src/components/ItemFormModal.vue +183 -0
- package/templates/vue-router-pinia-springboot/frontend/src/components/ItemPatchModal.vue +193 -0
- package/templates/vue-router-pinia-springboot/frontend/src/components/ItemTable.vue +177 -0
- package/templates/vue-router-pinia-springboot/frontend/src/components/NavBar.vue +54 -0
- package/templates/vue-router-pinia-springboot/frontend/src/views/ListView.vue +92 -76
- package/templates/vue-springboot-mongo-aggregation/frontend/src/App.vue +184 -193
- package/templates/vue-springboot-mongo-aggregation/frontend/src/components/AnalyticsCards.vue +56 -0
- package/templates/vue-springboot-mongo-aggregation/frontend/src/components/CategorySummaryTable.vue +71 -0
- package/templates/vue-springboot-mongo-aggregation/frontend/src/components/ItemTable.vue +114 -0
- package/templates/vue-springboot-mongo-crud/frontend/src/App.vue +228 -158
- package/templates/vue-springboot-mongo-crud/frontend/src/components/ItemFormModal.vue +183 -0
- package/templates/vue-springboot-mongo-crud/frontend/src/components/ItemPatchModal.vue +193 -0
- package/templates/vue-springboot-mongo-crud/frontend/src/components/ItemTable.vue +177 -0
- package/templates/vue-symposium-system/src/App.vue +41 -256
- package/templates/vue-symposium-system/src/components/AttendeeTable.vue +100 -0
- package/templates/vue-symposium-system/src/components/EventCard.vue +67 -0
- package/templates/vue-symposium-system/src/components/RegistrationForm.vue +118 -0
- package/templates/vue-symposium-system/src/stores/symposiumStore.js +10 -0
- package/templates/vue-todo-pinia/src/App.vue +40 -138
- package/templates/vue-todo-pinia/src/components/TodoFilter.vue +74 -0
- package/templates/vue-todo-pinia/src/components/TodoInput.vue +53 -0
- package/templates/vue-todo-pinia/src/components/TodoList.vue +101 -0
- package/templates/vue-todo-pinia/src/stores/todoStore.js +6 -0
- package/templates/vue-todo-router/src/App.vue +5 -36
- package/templates/vue-todo-router/src/components/TodoForm.vue +78 -0
- package/templates/vue-todo-router/src/components/TodoList.vue +84 -0
- package/templates/vue-todo-router/src/components/TodoNavBar.vue +47 -0
- package/templates/vue-todo-router/src/data/todoState.js +7 -0
- package/templates/vue-todo-router/src/views/AddTodoView.vue +7 -66
- package/templates/vue-todo-router/src/views/TodoDetailView.vue +15 -1
- package/templates/vue-todo-router/src/views/TodoListView.vue +22 -64
package/bin/index.js
CHANGED
|
@@ -4,457 +4,407 @@ const fs = require('fs');
|
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const readline = require('readline');
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
return;
|
|
11
|
-
}
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// WAD LAB TEMPLATES CLI - STRICT ASCII (ZERO EMOJIS)
|
|
9
|
+
// ============================================================================
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
fs.mkdirSync(dest, { recursive: true });
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
18
|
-
for (const entry of entries) {
|
|
19
|
-
const srcPath = path.join(src, entry.name);
|
|
20
|
-
const destPath = path.join(dest, entry.name);
|
|
21
|
-
|
|
22
|
-
if (entry.isDirectory()) {
|
|
23
|
-
copyDirRecursive(srcPath, destPath);
|
|
24
|
-
} else {
|
|
25
|
-
fs.copyFileSync(srcPath, destPath);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const frontendOptions = [
|
|
31
|
-
{ name: 'all (Include all 10 Vue variants inside frontend/)', value: 'all' },
|
|
32
|
-
{ name: 'vue-axios-basic (Vue 3 + Axios Direct CRUD)', value: 'vue-axios-basic' },
|
|
33
|
-
{ name: 'vue-axios-props-emits (Vue 3 + Axios + Props & Emits)', value: 'vue-axios-props-emits' },
|
|
34
|
-
{ name: 'vue-axios-router (Vue 3 + Axios + Router Views)', value: 'vue-axios-router' },
|
|
35
|
-
{ name: 'vue-axios-pinia (Vue 3 + Axios + Pinia Centralized Store)', value: 'vue-axios-pinia' },
|
|
36
|
-
{ name: 'vue-axios-router-props (Vue 3 + Axios + Router + Props/Emits)', value: 'vue-axios-router-props' },
|
|
37
|
-
{ name: 'vue-axios-pinia-props (Vue 3 + Axios + Pinia + Props/Emits)', value: 'vue-axios-pinia-props' },
|
|
38
|
-
{ name: 'vue-axios-router-pinia (Vue 3 + Axios + Router + Pinia)', value: 'vue-axios-router-pinia' },
|
|
39
|
-
{ name: 'vue-axios-router-pinia-props (The Kitchen Sink — Router + Pinia + Props + Emits)', value: 'vue-axios-router-pinia-props' },
|
|
40
|
-
{ name: 'vue-axios-router-children (Vue 3 + Axios + Router Nested Children)', value: 'vue-axios-router-children' },
|
|
41
|
-
{ name: 'vue-axios-interceptor (Vue 3 + Axios Interceptors + Toast Alerts)', value: 'vue-axios-interceptor' }
|
|
42
|
-
];
|
|
43
|
-
|
|
44
|
-
const featuredTemplates = [
|
|
11
|
+
const categories = [
|
|
45
12
|
{
|
|
13
|
+
id: 'servlet',
|
|
46
14
|
key: '1',
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
desc: 'Complete stack: 2-model Spring Boot backend + matching Vue frontend (Router/Pinia/Props)'
|
|
15
|
+
title: 'Java Servlets & Tomcat 9',
|
|
16
|
+
desc: '5 Classic Tomcat Servlets, JDBC, Sessions, Voter Eligibility & script.sh'
|
|
50
17
|
},
|
|
51
18
|
{
|
|
19
|
+
id: 'springboot',
|
|
52
20
|
key: '2',
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
desc: 'Extensible 2-model Spring Boot architecture (Department + Employee) with full CRUD & PATCH'
|
|
21
|
+
title: 'Spring Boot Backend APIs',
|
|
22
|
+
desc: '10 Pure-Lombok REST APIs: CRUD, Aggregations, Join, 2-Model & MockMvc'
|
|
56
23
|
},
|
|
57
24
|
{
|
|
25
|
+
id: 'vue',
|
|
58
26
|
key: '3',
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
desc: 'MongoDB $lookup multi-collection JOIN, pipeline stages ($match/$group), and @Query filters'
|
|
27
|
+
title: 'Frontend (Vue 3)',
|
|
28
|
+
desc: '28 Modular Vue 3 Frontends: 2-Model CRUD+PATCH, Axios, Router & Pinia'
|
|
62
29
|
},
|
|
63
30
|
{
|
|
31
|
+
id: 'html-css',
|
|
64
32
|
key: '4',
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
desc: 'Matching frontend for 2-model backend: Vue Router + Pinia centralized store + Props/Emits'
|
|
33
|
+
title: 'HTML5 & CSS3 Foundations',
|
|
34
|
+
desc: 'Form Validation, DOM Manipulation & 50 CSS Properties'
|
|
68
35
|
},
|
|
69
36
|
{
|
|
37
|
+
id: 'games',
|
|
70
38
|
key: '5',
|
|
71
|
-
|
|
72
|
-
|
|
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'
|
|
39
|
+
title: 'Interactive Browser Games',
|
|
40
|
+
desc: '9 Web Games: Bingo, Tic-Tac-Toe, RPS, Whack-a-Mole, Memory'
|
|
86
41
|
}
|
|
87
42
|
];
|
|
88
43
|
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
id: 'fullstack',
|
|
92
|
-
title: 'Fullstack Web Applications',
|
|
93
|
-
desc: 'Integrated Spring Boot + MongoDB + Vue 3 apps'
|
|
94
|
-
},
|
|
44
|
+
const templates = [
|
|
45
|
+
// 1. Java Servlets (5)
|
|
95
46
|
{
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
47
|
+
category: 'servlet',
|
|
48
|
+
value: 'servlet-basic',
|
|
49
|
+
name: 'servlet-basic (Basic GET/POST Servlet + Form)',
|
|
50
|
+
desc: 'Classic doGet / doPost request processing with HTML form submission and Tomcat script.sh'
|
|
99
51
|
},
|
|
100
52
|
{
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
53
|
+
category: 'servlet',
|
|
54
|
+
value: 'servlet-flow',
|
|
55
|
+
name: 'servlet-flow (RequestDispatcher Forward & Include Flow - No DB)',
|
|
56
|
+
desc: 'Request dispatching between servlets, forward/include execution flow, and Tomcat script.sh'
|
|
104
57
|
},
|
|
105
58
|
{
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
59
|
+
category: 'servlet',
|
|
60
|
+
value: 'servlet-jdbc',
|
|
61
|
+
name: 'servlet-jdbc (Tomcat 9 + MySQL JDBC Connection & CRUD)',
|
|
62
|
+
desc: 'Servlet with MySQL Driver, JDBC connection, SQL statement execution, HTML table and script.sh'
|
|
109
63
|
},
|
|
110
64
|
{
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
65
|
+
category: 'servlet',
|
|
66
|
+
value: 'servlet-session-tracking',
|
|
67
|
+
name: 'servlet-session-tracking (HttpSession, Cookies, URL Rewriting, Hidden Fields)',
|
|
68
|
+
desc: 'All 4 session tracking mechanisms in Java Servlets with Tomcat script.sh'
|
|
114
69
|
},
|
|
115
70
|
{
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
71
|
+
category: 'servlet',
|
|
72
|
+
value: 'servlet-voter-eligibility',
|
|
73
|
+
name: 'servlet-voter-eligibility (Form Validation & Age Check Logic)',
|
|
74
|
+
desc: 'Server-side input validation, voter eligibility verification, and Tomcat script.sh'
|
|
119
75
|
},
|
|
120
|
-
{
|
|
121
|
-
id: 'html-css',
|
|
122
|
-
title: 'HTML5 & CSS3 Foundations',
|
|
123
|
-
desc: 'Form Validation, DOM Manipulation & 50 CSS Properties'
|
|
124
|
-
}
|
|
125
|
-
];
|
|
126
76
|
|
|
127
|
-
|
|
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
|
-
},
|
|
77
|
+
// 2. Spring Boot Backends (10)
|
|
139
78
|
{
|
|
140
|
-
category: '
|
|
141
|
-
value: '
|
|
142
|
-
name: '
|
|
79
|
+
category: 'springboot',
|
|
80
|
+
value: 'springboot-2model-aggregation-join',
|
|
81
|
+
name: 'springboot-2model-aggregation-join (Department + Employee, CRUD + PATCH, $lookup Join, $group Stats)',
|
|
82
|
+
desc: 'Pure Lombok (@Data, @NoArgsConstructor, @AllArgsConstructor), @Autowired injection, CRUD+PATCH, $lookup join & stats'
|
|
143
83
|
},
|
|
144
84
|
{
|
|
145
|
-
category: '
|
|
146
|
-
value: '
|
|
147
|
-
name: '
|
|
85
|
+
category: 'springboot',
|
|
86
|
+
value: 'springboot-2model-1controller-api',
|
|
87
|
+
name: 'springboot-2model-1controller-api (Department + Employee REST API, 2 Repos, 1 Controller)',
|
|
88
|
+
desc: 'Spring Boot 2-model REST API with Department and Employee endpoints in a unified controller'
|
|
148
89
|
},
|
|
149
|
-
|
|
150
|
-
// Spring Boot (9)
|
|
151
90
|
{
|
|
152
91
|
category: 'springboot',
|
|
153
|
-
value: 'springboot-mongo-
|
|
154
|
-
name: 'springboot-mongo-
|
|
92
|
+
value: 'springboot-mongo-aggregation-join',
|
|
93
|
+
name: 'springboot-mongo-aggregation-join (Customer + Order, $lookup Join Pipeline)',
|
|
94
|
+
desc: 'Order and Customer MongoDB aggregation join using $lookup, $unwind, and projected DTO'
|
|
155
95
|
},
|
|
156
96
|
{
|
|
157
97
|
category: 'springboot',
|
|
158
98
|
value: 'springboot-mongo-aggregation',
|
|
159
|
-
name: 'springboot-mongo-aggregation (
|
|
99
|
+
name: 'springboot-mongo-aggregation (Standalone Pipeline: $group, $sum, $avg, $sort)',
|
|
100
|
+
desc: 'Single collection aggregation analytics with MongoTemplate and aggregation stages'
|
|
160
101
|
},
|
|
161
102
|
{
|
|
162
103
|
category: 'springboot',
|
|
163
|
-
value: 'springboot-mongo-
|
|
164
|
-
name: 'springboot-mongo-
|
|
104
|
+
value: 'springboot-mongo-crud',
|
|
105
|
+
name: 'springboot-mongo-crud (4-Layer REST API: Controller -> Service -> Repo -> Mongo)',
|
|
106
|
+
desc: 'Standard MongoRepository CRUD starter with clean layers and full HTTP methods'
|
|
165
107
|
},
|
|
166
108
|
{
|
|
167
109
|
category: 'springboot',
|
|
168
110
|
value: 'springboot-mockmvc',
|
|
169
|
-
name: 'springboot-mockmvc (Controller
|
|
111
|
+
name: 'springboot-mockmvc (Controller Tests with MockMvc, Mockito & AssertJ)',
|
|
112
|
+
desc: 'Unit and integration testing for Spring Boot REST endpoints using MockMvc'
|
|
170
113
|
},
|
|
171
114
|
{
|
|
172
115
|
category: 'springboot',
|
|
173
116
|
value: 'springboot-mongo-aggregation-mockmvc',
|
|
174
|
-
name: 'springboot-mongo-aggregation-mockmvc (
|
|
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)'
|
|
117
|
+
name: 'springboot-mongo-aggregation-mockmvc (Aggregation Controller Covered by MockMvc Tests)',
|
|
118
|
+
desc: 'MongoDB aggregation API with complete test suite covering pipelines and projections'
|
|
180
119
|
},
|
|
181
120
|
{
|
|
182
121
|
category: 'springboot',
|
|
183
122
|
value: 'springboot-course-registration',
|
|
184
|
-
name: 'springboot-course-registration (
|
|
123
|
+
name: 'springboot-course-registration (Student & Course Enrollment REST API)',
|
|
124
|
+
desc: 'Domain REST API for university student registration, courses, and enrollments'
|
|
185
125
|
},
|
|
186
126
|
{
|
|
187
127
|
category: 'springboot',
|
|
188
128
|
value: 'springboot-hospital-api',
|
|
189
|
-
name: 'springboot-hospital-api (
|
|
129
|
+
name: 'springboot-hospital-api (Doctor & Patient Appointment Scheduling API)',
|
|
130
|
+
desc: 'Domain REST API for hospital appointment booking and patient record management'
|
|
190
131
|
},
|
|
191
132
|
{
|
|
192
133
|
category: 'springboot',
|
|
193
134
|
value: 'springboot-library-api',
|
|
194
|
-
name: 'springboot-library-api (
|
|
135
|
+
name: 'springboot-library-api (Book Catalog & Borrowing Transaction API)',
|
|
136
|
+
desc: 'Domain REST API for book lending, borrowing tracking, and member management'
|
|
195
137
|
},
|
|
196
138
|
|
|
197
|
-
//
|
|
139
|
+
// 3. Frontend (Vue 3) (28)
|
|
198
140
|
{
|
|
199
|
-
category: '
|
|
200
|
-
value: '
|
|
201
|
-
name: '
|
|
141
|
+
category: 'vue',
|
|
142
|
+
value: 'vue-2model-crud-patch',
|
|
143
|
+
name: 'vue-2model-crud-patch (2-Model Modular UI: Department & Employee, CRUD + PATCH + Join)',
|
|
144
|
+
desc: 'Vite + Vue 3 + Pinia + Router: Modular components (Tables, FormModals, PatchModals, AnalyticsBoard)'
|
|
202
145
|
},
|
|
203
146
|
{
|
|
204
|
-
category: '
|
|
205
|
-
value: '
|
|
206
|
-
name: '
|
|
147
|
+
category: 'vue',
|
|
148
|
+
value: 'vue-axios-router-pinia-props',
|
|
149
|
+
name: 'vue-axios-router-pinia-props (Full Enterprise: Router + Pinia + Axios + Props/Emits)',
|
|
150
|
+
desc: 'Complete standard stack with multi-view routes, Pinia store, and modular child components'
|
|
207
151
|
},
|
|
208
152
|
{
|
|
209
|
-
category: '
|
|
210
|
-
value: '
|
|
211
|
-
name: '
|
|
153
|
+
category: 'vue',
|
|
154
|
+
value: 'vue-axios-router-props',
|
|
155
|
+
name: 'vue-axios-router-props (Router Navigation + Modular Child Components with Props/Emits)',
|
|
156
|
+
desc: 'Multi-page routing passing state down via props and events up via emits'
|
|
212
157
|
},
|
|
213
158
|
{
|
|
214
|
-
category: '
|
|
215
|
-
value: '
|
|
216
|
-
name: '
|
|
159
|
+
category: 'vue',
|
|
160
|
+
value: 'vue-axios-pinia-props',
|
|
161
|
+
name: 'vue-axios-pinia-props (Pinia Store Passed Down to Child Components via Props)',
|
|
162
|
+
desc: 'Central store feeding child component sections with props and actions'
|
|
217
163
|
},
|
|
218
164
|
{
|
|
219
|
-
category: '
|
|
220
|
-
value: '
|
|
221
|
-
name: '
|
|
165
|
+
category: 'vue',
|
|
166
|
+
value: 'vue-axios-props-emits',
|
|
167
|
+
name: 'vue-axios-props-emits (Component Hierarchy: Form & List with Props Down and Emits Up)',
|
|
168
|
+
desc: 'Clean component isolation communicating purely via props and emit events'
|
|
222
169
|
},
|
|
223
|
-
|
|
224
|
-
// Vue + Axios (11)
|
|
225
170
|
{
|
|
226
|
-
category: 'vue
|
|
171
|
+
category: 'vue',
|
|
227
172
|
value: 'vue-axios-basic',
|
|
228
|
-
name: 'vue-axios-basic (
|
|
173
|
+
name: 'vue-axios-basic (Direct Axios CRUD with Modular Table, Form & Patch Modals)',
|
|
174
|
+
desc: 'Modular Vue 3 component CRUD with Axios HTTP client and partial PATCH modal'
|
|
229
175
|
},
|
|
230
176
|
{
|
|
231
|
-
category: 'vue
|
|
232
|
-
value: 'vue-axios
|
|
233
|
-
name: 'vue-axios
|
|
177
|
+
category: 'vue',
|
|
178
|
+
value: 'vue-axios',
|
|
179
|
+
name: 'vue-axios (Service-Layer Axios CRUD with Modular Components & PATCH)',
|
|
180
|
+
desc: 'ItemService abstraction layer with modular table and patch dialog'
|
|
234
181
|
},
|
|
235
182
|
{
|
|
236
|
-
category: 'vue
|
|
237
|
-
value: 'vue-axios-
|
|
238
|
-
name: 'vue-axios-
|
|
183
|
+
category: 'vue',
|
|
184
|
+
value: 'vue-axios-interceptor',
|
|
185
|
+
name: 'vue-axios-interceptor (Axios Request/Response Interceptors + Toast Notifications)',
|
|
186
|
+
desc: 'Global HTTP error catching, loading spinners, and toast alerts'
|
|
239
187
|
},
|
|
240
188
|
{
|
|
241
|
-
category: 'vue
|
|
189
|
+
category: 'vue',
|
|
242
190
|
value: 'vue-axios-pinia',
|
|
243
|
-
name: 'vue-axios-pinia (
|
|
191
|
+
name: 'vue-axios-pinia (Pinia Store with Axios HTTP Actions & Stats)',
|
|
192
|
+
desc: 'Centralized state store managing async CRUD requests with stats component'
|
|
244
193
|
},
|
|
245
194
|
{
|
|
246
|
-
category: 'vue
|
|
247
|
-
value: 'vue-axios-router
|
|
248
|
-
name: 'vue-axios-router
|
|
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)'
|
|
195
|
+
category: 'vue',
|
|
196
|
+
value: 'vue-axios-router',
|
|
197
|
+
name: 'vue-axios-router (Vue Router Multi-View Navigation + Axios API)',
|
|
198
|
+
desc: 'Page views with navigation bar and modular table/form components'
|
|
264
199
|
},
|
|
265
200
|
{
|
|
266
|
-
category: 'vue
|
|
201
|
+
category: 'vue',
|
|
267
202
|
value: 'vue-axios-router-children',
|
|
268
|
-
name: 'vue-axios-router-children (
|
|
203
|
+
name: 'vue-axios-router-children (Nested Routes: Sidebar + Child Detail Views)',
|
|
204
|
+
desc: 'Nested <router-view> routes with sidebar navigation and detail components'
|
|
269
205
|
},
|
|
270
206
|
{
|
|
271
|
-
category: 'vue
|
|
272
|
-
value: 'vue-axios-
|
|
273
|
-
name: 'vue-axios-
|
|
274
|
-
|
|
275
|
-
{
|
|
276
|
-
category: 'vue-axios',
|
|
277
|
-
value: 'vue-axios',
|
|
278
|
-
name: 'vue-axios (Classic Vue 3 Axios Service Layer CRUD)'
|
|
207
|
+
category: 'vue',
|
|
208
|
+
value: 'vue-axios-router-pinia',
|
|
209
|
+
name: 'vue-axios-router-pinia (Standard SPA: Router + Pinia + Axios Service + PATCH)',
|
|
210
|
+
desc: 'Full SPA architecture with modular tables, modals, and patch actions'
|
|
279
211
|
},
|
|
280
|
-
|
|
281
|
-
// Vue Core & Portals (16)
|
|
282
212
|
{
|
|
283
|
-
category: 'vue
|
|
213
|
+
category: 'vue',
|
|
284
214
|
value: 'vue-basics',
|
|
285
|
-
name: 'vue-basics (
|
|
215
|
+
name: 'vue-basics (Template Syntax, v-model, CounterCard, UserForm & ItemList)',
|
|
216
|
+
desc: 'Core Vue 3 reactivity and directives divided into modular components'
|
|
286
217
|
},
|
|
287
218
|
{
|
|
288
|
-
category: 'vue
|
|
219
|
+
category: 'vue',
|
|
289
220
|
value: 'vue-props-emits',
|
|
290
|
-
name: 'vue-props-emits (
|
|
221
|
+
name: 'vue-props-emits (Pure Component Communication: ItemCard & ItemForm)',
|
|
222
|
+
desc: 'Parent-child communication without Axios or external libraries'
|
|
291
223
|
},
|
|
292
224
|
{
|
|
293
|
-
category: 'vue
|
|
225
|
+
category: 'vue',
|
|
294
226
|
value: 'vue-router',
|
|
295
|
-
name: 'vue-router (Vue Router
|
|
227
|
+
name: 'vue-router (Vue Router 4 Setup: NavBar, HomeHero & ItemsLayout)',
|
|
228
|
+
desc: 'Clean routing configuration with modular view components'
|
|
296
229
|
},
|
|
297
230
|
{
|
|
298
|
-
category: 'vue
|
|
231
|
+
category: 'vue',
|
|
299
232
|
value: 'vue-pinia',
|
|
300
|
-
name: 'vue-pinia (Pinia State
|
|
233
|
+
name: 'vue-pinia (Pinia State Management: ItemForm, ItemList & State Actions)',
|
|
234
|
+
desc: 'Reactive store getters, actions, and state modifications'
|
|
301
235
|
},
|
|
302
236
|
{
|
|
303
|
-
category: 'vue
|
|
237
|
+
category: 'vue',
|
|
304
238
|
value: 'vue-router-pinia',
|
|
305
|
-
name: 'vue-router-pinia (Router
|
|
239
|
+
name: 'vue-router-pinia (Router + Pinia: NavBar, Store Table & Modal Dialogs)',
|
|
240
|
+
desc: 'Integration of routing and global state without external HTTP'
|
|
306
241
|
},
|
|
307
242
|
{
|
|
308
|
-
category: 'vue
|
|
243
|
+
category: 'vue',
|
|
309
244
|
value: 'vue-router-pinia-props',
|
|
310
|
-
name: 'vue-router-pinia-props (Router + Pinia
|
|
245
|
+
name: 'vue-router-pinia-props (Router + Pinia + Child Props/Emits Architecture)',
|
|
246
|
+
desc: 'Hybrid architecture combining global store with prop-driven child components'
|
|
311
247
|
},
|
|
312
248
|
{
|
|
313
|
-
category: 'vue
|
|
249
|
+
category: 'vue',
|
|
314
250
|
value: 'vue-fetch',
|
|
315
|
-
name: 'vue-fetch (Native
|
|
251
|
+
name: 'vue-fetch (Native fetch() API with Modular Table, Form & Patch Modals)',
|
|
252
|
+
desc: 'Native browser fetch without Axios dependency, modular components included'
|
|
316
253
|
},
|
|
317
254
|
{
|
|
318
|
-
category: 'vue
|
|
319
|
-
value: 'vue-
|
|
320
|
-
name: 'vue-
|
|
255
|
+
category: 'vue',
|
|
256
|
+
value: 'vue-banking-app',
|
|
257
|
+
name: 'vue-banking-app (Account Overview, Transaction Form & History Table)',
|
|
258
|
+
desc: 'Banking UI with deposit, withdrawal, transaction ledger, and balance tracking'
|
|
321
259
|
},
|
|
322
260
|
{
|
|
323
|
-
category: 'vue
|
|
324
|
-
value: 'vue-
|
|
325
|
-
name: 'vue-
|
|
261
|
+
category: 'vue',
|
|
262
|
+
value: 'vue-cart-router-pinia',
|
|
263
|
+
name: 'vue-cart-router-pinia (E-Commerce Cart: Products, Cart Drawer & Badge)',
|
|
264
|
+
desc: 'Shopping cart with product listing, quantity updates, and cart calculations'
|
|
326
265
|
},
|
|
327
266
|
{
|
|
328
|
-
category: 'vue
|
|
329
|
-
value: 'vue-
|
|
330
|
-
name: 'vue-
|
|
267
|
+
category: 'vue',
|
|
268
|
+
value: 'vue-employee-system',
|
|
269
|
+
name: 'vue-employee-system (Employee Directory: Department Filter & Modal)',
|
|
270
|
+
desc: 'Staff management with department filtering, payroll calculator, and modals'
|
|
331
271
|
},
|
|
332
272
|
{
|
|
333
|
-
category: 'vue
|
|
273
|
+
category: 'vue',
|
|
334
274
|
value: 'vue-food-ordering',
|
|
335
|
-
name: 'vue-food-ordering (
|
|
275
|
+
name: 'vue-food-ordering (Restaurant App: Food Menu, Cart Summary & Order Modal)',
|
|
276
|
+
desc: 'Menu catalog with category filters, cart tally, tax computation, and checkout'
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
category: 'vue',
|
|
280
|
+
value: 'vue-hospital-portal',
|
|
281
|
+
name: 'vue-hospital-portal (Healthcare Portal: Doctor Cards, Admissions & Patients)',
|
|
282
|
+
desc: 'Patient admission portal with vitals scoring, doctor bookings, and discharge'
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
category: 'vue',
|
|
286
|
+
value: 'vue-library-system',
|
|
287
|
+
name: 'vue-library-system (Library Catalog: Book Table, Inventory Form & Borrow Modal)',
|
|
288
|
+
desc: 'Book catalog with borrowing records, due date tracking, and fine calculation'
|
|
336
289
|
},
|
|
337
290
|
{
|
|
338
|
-
category: 'vue
|
|
291
|
+
category: 'vue',
|
|
339
292
|
value: 'vue-symposium-system',
|
|
340
|
-
name: 'vue-symposium-system (
|
|
293
|
+
name: 'vue-symposium-system (Event Manager: Event Cards, Registration & Attendees)',
|
|
294
|
+
desc: 'College symposium event registration and attendee roster tracking'
|
|
341
295
|
},
|
|
342
296
|
{
|
|
343
|
-
category: 'vue
|
|
344
|
-
value: 'vue-
|
|
345
|
-
name: 'vue-
|
|
297
|
+
category: 'vue',
|
|
298
|
+
value: 'vue-todo-pinia',
|
|
299
|
+
name: 'vue-todo-pinia (Task Manager: Todo Input, Filter Controls & List)',
|
|
300
|
+
desc: 'Todo tracker with Pinia actions, completion toggles, and badge counters'
|
|
346
301
|
},
|
|
347
302
|
{
|
|
348
|
-
category: 'vue
|
|
349
|
-
value: 'vue-
|
|
350
|
-
name: 'vue-
|
|
303
|
+
category: 'vue',
|
|
304
|
+
value: 'vue-todo-router',
|
|
305
|
+
name: 'vue-todo-router (Multi-Page Todo: Todo NavBar, Task List & Form View)',
|
|
306
|
+
desc: 'Multi-route todo manager with parameter passing and task creation'
|
|
351
307
|
},
|
|
308
|
+
|
|
309
|
+
// 4. HTML5 & CSS3 Foundations (2)
|
|
352
310
|
{
|
|
353
|
-
category: '
|
|
354
|
-
value: '
|
|
355
|
-
name: '
|
|
311
|
+
category: 'html-css',
|
|
312
|
+
value: 'html-css-js',
|
|
313
|
+
name: 'html-css-js (HTML5 Semantic Layout, Form Validation & DOM Manipulation)',
|
|
314
|
+
desc: 'Pure vanilla web fundamentals with modern semantic HTML, CSS Flexbox/Grid, and JS validation'
|
|
356
315
|
},
|
|
357
316
|
{
|
|
358
|
-
category: '
|
|
359
|
-
value: '
|
|
360
|
-
name: '
|
|
317
|
+
category: 'html-css',
|
|
318
|
+
value: 'html-css-styles',
|
|
319
|
+
name: 'html-css-styles (50 Core CSS Properties Showcase & Playground)',
|
|
320
|
+
desc: '50 CSS properties reference: Flexbox, CSS Grid, Transitions, Keyframe Animations & Shadows'
|
|
361
321
|
},
|
|
362
322
|
|
|
363
|
-
// Games (9)
|
|
323
|
+
// 5. Interactive Browser Games (9)
|
|
364
324
|
{
|
|
365
325
|
category: 'games',
|
|
366
326
|
value: 'game-bingo',
|
|
367
|
-
name: 'game-bingo (
|
|
327
|
+
name: 'game-bingo (5x5 Randomized Bingo Grid with Win-Line Detection)',
|
|
328
|
+
desc: 'Classic 5x5 Bingo board with random number draw and winning line checks'
|
|
368
329
|
},
|
|
369
330
|
{
|
|
370
331
|
category: 'games',
|
|
371
|
-
value: 'game-
|
|
372
|
-
name: 'game-
|
|
332
|
+
value: 'game-memory-cards',
|
|
333
|
+
name: 'game-memory-cards (Card-Flip Pattern Matching Game in Vanilla JS)',
|
|
334
|
+
desc: 'Memory card matching with flip animations, move count, and match detection'
|
|
373
335
|
},
|
|
374
336
|
{
|
|
375
337
|
category: 'games',
|
|
376
|
-
value: 'game-
|
|
377
|
-
name: 'game-
|
|
338
|
+
value: 'game-rock-paper-scissors',
|
|
339
|
+
name: 'game-rock-paper-scissors (Classic Rock-Paper-Scissors with Score Tracker)',
|
|
340
|
+
desc: 'RPS game against computer AI with streak counting and score history'
|
|
378
341
|
},
|
|
379
342
|
{
|
|
380
343
|
category: 'games',
|
|
381
|
-
value: 'game-
|
|
382
|
-
name: 'game-
|
|
344
|
+
value: 'game-tic-tac-toe',
|
|
345
|
+
name: 'game-tic-tac-toe (2-Player Turn-Based Tic-Tac-Toe with Win Logic)',
|
|
346
|
+
desc: 'Classic 3x3 grid with turn toggling, win condition scanning, and reset'
|
|
383
347
|
},
|
|
384
348
|
{
|
|
385
349
|
category: 'games',
|
|
386
|
-
value: 'game-
|
|
387
|
-
name: 'game-
|
|
350
|
+
value: 'game-whack-a-mole',
|
|
351
|
+
name: 'game-whack-a-mole (Timed Click-Reaction Game with Score Counter)',
|
|
352
|
+
desc: 'Reaction game with randomized mole pops, timer countdown, and high scores'
|
|
388
353
|
},
|
|
389
354
|
{
|
|
390
355
|
category: 'games',
|
|
391
356
|
value: 'vue-game-rps',
|
|
392
|
-
name: 'vue-game-rps (Rock
|
|
357
|
+
name: 'vue-game-rps (Vue 3 Rock-Paper-Scissors with Reactive Scoreboard)',
|
|
358
|
+
desc: 'Vue 3 reactive game with computer AI choice and round history'
|
|
393
359
|
},
|
|
394
360
|
{
|
|
395
361
|
category: 'games',
|
|
396
362
|
value: 'vue-game-tictactoe',
|
|
397
|
-
name: 'vue-game-tictactoe (Tic
|
|
363
|
+
name: 'vue-game-tictactoe (Vue 3 Reactive Tic-Tac-Toe with Board State)',
|
|
364
|
+
desc: 'Vue 3 component game with computed winning lines and reset action'
|
|
398
365
|
},
|
|
399
366
|
{
|
|
400
367
|
category: 'games',
|
|
401
368
|
value: 'vue-game-whackamole',
|
|
402
|
-
name: 'vue-game-whackamole (Whack-
|
|
369
|
+
name: 'vue-game-whackamole (Vue 3 Whack-a-Mole with Reactive Timer & Score)',
|
|
370
|
+
desc: 'Vue 3 reactive game loop with timer interval and hit detection'
|
|
403
371
|
},
|
|
404
372
|
{
|
|
405
373
|
category: 'games',
|
|
406
374
|
value: 'vue-memory-game',
|
|
407
|
-
name: 'vue-memory-game (Card
|
|
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)'
|
|
375
|
+
name: 'vue-memory-game (Vue 3 Card Match Game with Pinia State Store)',
|
|
376
|
+
desc: 'Vue 3 memory card flip game backed by Pinia store actions and timers'
|
|
420
377
|
}
|
|
421
378
|
];
|
|
422
379
|
|
|
423
|
-
const PAGE_SIZE = 12;
|
|
424
380
|
const templatesRoot = path.join(__dirname, '..', 'templates');
|
|
425
381
|
|
|
426
|
-
let currentView = 'MAIN'; // 'MAIN' | '
|
|
382
|
+
let currentView = 'MAIN'; // 'MAIN' | 'CATEGORY' | 'SEARCH' | 'ALL'
|
|
427
383
|
let activeCategory = null;
|
|
428
384
|
let searchQuery = '';
|
|
429
385
|
let selectedIndex = 0;
|
|
430
386
|
const selectedValues = new Set();
|
|
431
387
|
let isPrompting = false;
|
|
432
388
|
|
|
389
|
+
// ============================================================================
|
|
390
|
+
// MULTI-WORD SEARCH & ACTIVE ITEM RETRIEVAL
|
|
391
|
+
// ============================================================================
|
|
392
|
+
|
|
433
393
|
function getActiveItemList() {
|
|
434
394
|
if (currentView === 'MAIN') {
|
|
435
|
-
const list =
|
|
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) => {
|
|
395
|
+
const list = categories.map((cat, idx) => {
|
|
450
396
|
const count = templates.filter(t => t.category === cat.id).length;
|
|
451
397
|
return {
|
|
452
|
-
type: '
|
|
398
|
+
type: 'category',
|
|
453
399
|
id: cat.id,
|
|
400
|
+
key: String(idx + 1),
|
|
454
401
|
name: `[${idx + 1}] ${cat.title} (${count} templates)`,
|
|
455
402
|
desc: cat.desc
|
|
456
403
|
};
|
|
457
404
|
});
|
|
405
|
+
list.push({ type: 'action', id: 'search', key: 'S', name: '[S] Search Templates (Supports multi-word: e.g. "mongo join")', desc: 'Search across all templates with multiple keywords' });
|
|
406
|
+
list.push({ type: 'action', id: 'unpack_all', key: 'A', name: '[A] Unpack All 58 Templates in 1-Click', desc: 'Extract the entire catalog into target directory' });
|
|
407
|
+
return list;
|
|
458
408
|
}
|
|
459
409
|
|
|
460
410
|
if (currentView === 'CATEGORY') {
|
|
@@ -464,11 +414,12 @@ function getActiveItemList() {
|
|
|
464
414
|
if (currentView === 'SEARCH') {
|
|
465
415
|
const q = searchQuery.toLowerCase().trim();
|
|
466
416
|
if (!q) return [];
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
t.
|
|
471
|
-
|
|
417
|
+
// Tokenized multi-word search
|
|
418
|
+
const tokens = q.split(/\s+/).filter(Boolean);
|
|
419
|
+
return templates.filter(t => {
|
|
420
|
+
const text = (t.value + ' ' + t.name + ' ' + (t.desc || '') + ' ' + t.category).toLowerCase();
|
|
421
|
+
return tokens.every(token => text.includes(token));
|
|
422
|
+
});
|
|
472
423
|
}
|
|
473
424
|
|
|
474
425
|
if (currentView === 'ALL') {
|
|
@@ -478,46 +429,21 @@ function getActiveItemList() {
|
|
|
478
429
|
return [];
|
|
479
430
|
}
|
|
480
431
|
|
|
432
|
+
// ============================================================================
|
|
433
|
+
// TERMINAL SCREEN RENDERING (ZERO EMOJIS)
|
|
434
|
+
// ============================================================================
|
|
435
|
+
|
|
481
436
|
function renderScreen() {
|
|
482
437
|
console.clear();
|
|
483
438
|
|
|
484
439
|
if (currentView === 'MAIN') {
|
|
485
440
|
console.log('========================================================');
|
|
486
|
-
console.log(' WAD LAB
|
|
487
|
-
console.log('
|
|
441
|
+
console.log(' WAD LAB TEMPLATES CLI ');
|
|
442
|
+
console.log(' 54 Clean Starter Templates + Tomcat Deploy ');
|
|
488
443
|
console.log('========================================================\n');
|
|
489
|
-
console.log('
|
|
490
|
-
console.log(' Press [1-
|
|
491
|
-
console.log('
|
|
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;
|
|
513
|
-
}
|
|
514
|
-
|
|
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');
|
|
444
|
+
console.log('MAIN MENU:');
|
|
445
|
+
console.log(' Press [1-5] to open category, [S] to Search, [A] for All');
|
|
446
|
+
console.log(' Navigate with [UP / DOWN], [ENTER] to confirm\n');
|
|
521
447
|
|
|
522
448
|
const items = getActiveItemList();
|
|
523
449
|
items.forEach((item, idx) => {
|
|
@@ -530,279 +456,216 @@ function renderScreen() {
|
|
|
530
456
|
if (activeItem && activeItem.desc) {
|
|
531
457
|
console.log('\n Info: ' + activeItem.desc);
|
|
532
458
|
}
|
|
459
|
+
console.log('\n [1] Servlets | [2] Spring Boot | [3] Vue 3 | [4] HTML/CSS | [5] Games | [S] Search | [Q] Quit');
|
|
533
460
|
return;
|
|
534
461
|
}
|
|
535
462
|
|
|
536
463
|
if (currentView === 'CATEGORY') {
|
|
464
|
+
const items = getActiveItemList();
|
|
537
465
|
console.log('========================================================');
|
|
538
|
-
console.log(` ${activeCategory.title}`);
|
|
539
|
-
console.log(` ${activeCategory.desc}`);
|
|
466
|
+
console.log(` ${activeCategory.title} (${items.length} templates)`);
|
|
540
467
|
console.log('========================================================\n');
|
|
541
|
-
console.log(' [SPACE] Select /
|
|
542
|
-
console.log(' [ENTER] Confirm selection and proceed');
|
|
543
|
-
console.log(' [B / ESC] Back to Categories Menu\n');
|
|
468
|
+
console.log(' [SPACE] Select | [ENTER] Unpack | [B / ESC] Back to Main Menu\n');
|
|
544
469
|
|
|
545
|
-
const
|
|
470
|
+
const PAGE_SIZE = 12;
|
|
546
471
|
const total = items.length;
|
|
547
|
-
|
|
548
472
|
let start = selectedIndex - Math.floor(PAGE_SIZE / 2);
|
|
549
473
|
if (start < 0) start = 0;
|
|
550
|
-
if (start + PAGE_SIZE > total)
|
|
551
|
-
|
|
552
|
-
}
|
|
553
|
-
const end = Math.min(start + PAGE_SIZE, total);
|
|
474
|
+
if (start + PAGE_SIZE > total) start = Math.max(0, total - PAGE_SIZE);
|
|
475
|
+
const visibleItems = items.slice(start, start + PAGE_SIZE);
|
|
554
476
|
|
|
555
|
-
if (start > 0)
|
|
556
|
-
console.log(` ^ (${start} more above)`);
|
|
557
|
-
}
|
|
477
|
+
if (start > 0) console.log(' ... (scroll up)');
|
|
558
478
|
|
|
559
|
-
|
|
560
|
-
const
|
|
561
|
-
const isCursor =
|
|
479
|
+
visibleItems.forEach((t, i) => {
|
|
480
|
+
const realIndex = start + i;
|
|
481
|
+
const isCursor = realIndex === selectedIndex;
|
|
562
482
|
const isChecked = selectedValues.has(t.value);
|
|
563
|
-
const box = isChecked ? '[x]' : '[ ]';
|
|
483
|
+
const box = isChecked ? '[x] ' : '[ ] ';
|
|
564
484
|
const prefix = isCursor ? ' > ' : ' ';
|
|
565
|
-
console.log(prefix + box +
|
|
566
|
-
}
|
|
485
|
+
console.log(prefix + box + `[${realIndex + 1}] ` + t.name);
|
|
486
|
+
});
|
|
567
487
|
|
|
568
|
-
if (
|
|
569
|
-
console.log(` v (${total - end} more below)`);
|
|
570
|
-
}
|
|
488
|
+
if (start + PAGE_SIZE < total) console.log(' ... (scroll down)');
|
|
571
489
|
|
|
572
|
-
const
|
|
573
|
-
|
|
490
|
+
const activeItem = items[selectedIndex];
|
|
491
|
+
if (activeItem && activeItem.desc) {
|
|
492
|
+
console.log('\n Info: ' + activeItem.desc);
|
|
493
|
+
}
|
|
494
|
+
if (selectedValues.size > 0) {
|
|
495
|
+
console.log(`\n Selected: ${selectedValues.size} template(s) [Press ENTER to proceed]`);
|
|
496
|
+
}
|
|
574
497
|
return;
|
|
575
498
|
}
|
|
576
499
|
|
|
577
500
|
if (currentView === 'SEARCH') {
|
|
578
501
|
console.log('========================================================');
|
|
579
|
-
console.log('
|
|
580
|
-
console.log('
|
|
581
|
-
console.log(` Query: "${searchQuery}" (
|
|
582
|
-
console.log(' [SPACE] Select | [ENTER]
|
|
502
|
+
console.log(' Multi-Word Template Search');
|
|
503
|
+
console.log('========================================================\n');
|
|
504
|
+
console.log(` Query: "${searchQuery}" (Supports multi-word: e.g. "mongo join", "servlet jdbc")`);
|
|
505
|
+
console.log(' [SPACE] Select | [ENTER] Unpack | [ESC] Back to Main Menu\n');
|
|
583
506
|
|
|
584
507
|
const items = getActiveItemList();
|
|
585
508
|
if (!searchQuery.trim()) {
|
|
586
|
-
console.log(' Start typing to search across all
|
|
587
|
-
console.log(' Try
|
|
509
|
+
console.log(' Start typing keywords to search across all templates...');
|
|
510
|
+
console.log(' Try queries:');
|
|
511
|
+
console.log(' - "mongo join"');
|
|
512
|
+
console.log(' - "2model"');
|
|
513
|
+
console.log(' - "servlet jdbc"');
|
|
514
|
+
console.log(' - "vue pinia router"');
|
|
515
|
+
console.log(' - "patch"');
|
|
516
|
+
console.log(' - "aggregation"\n');
|
|
588
517
|
return;
|
|
589
518
|
}
|
|
590
519
|
|
|
591
520
|
if (items.length === 0) {
|
|
592
|
-
console.log(` No templates matched "${searchQuery}".`);
|
|
521
|
+
console.log(` No templates matched all tokens in "${searchQuery}".`);
|
|
593
522
|
return;
|
|
594
523
|
}
|
|
595
524
|
|
|
596
525
|
const total = items.length;
|
|
526
|
+
const PAGE_SIZE = 12;
|
|
597
527
|
let start = selectedIndex - Math.floor(PAGE_SIZE / 2);
|
|
598
528
|
if (start < 0) start = 0;
|
|
599
|
-
if (start + PAGE_SIZE > total)
|
|
600
|
-
|
|
601
|
-
}
|
|
602
|
-
const end = Math.min(start + PAGE_SIZE, total);
|
|
529
|
+
if (start + PAGE_SIZE > total) start = Math.max(0, total - PAGE_SIZE);
|
|
530
|
+
const visibleItems = items.slice(start, start + PAGE_SIZE);
|
|
603
531
|
|
|
604
|
-
if (start > 0)
|
|
605
|
-
console.log(` ^ (${start} more above)`);
|
|
606
|
-
}
|
|
532
|
+
if (start > 0) console.log(' ... (scroll up)');
|
|
607
533
|
|
|
608
|
-
|
|
609
|
-
const
|
|
610
|
-
const isCursor =
|
|
534
|
+
visibleItems.forEach((t, i) => {
|
|
535
|
+
const realIndex = start + i;
|
|
536
|
+
const isCursor = realIndex === selectedIndex;
|
|
611
537
|
const isChecked = selectedValues.has(t.value);
|
|
612
|
-
const box = isChecked ? '[x]' : '[ ]';
|
|
538
|
+
const box = isChecked ? '[x] ' : '[ ] ';
|
|
613
539
|
const prefix = isCursor ? ' > ' : ' ';
|
|
614
|
-
console.log(prefix + box +
|
|
615
|
-
}
|
|
540
|
+
console.log(prefix + box + t.name + ` [${t.category}]`);
|
|
541
|
+
});
|
|
616
542
|
|
|
617
|
-
if (
|
|
618
|
-
console.log(` v (${total - end} more below)`);
|
|
619
|
-
}
|
|
543
|
+
if (start + PAGE_SIZE < total) console.log(' ... (scroll down)');
|
|
620
544
|
|
|
621
|
-
|
|
545
|
+
const activeItem = items[selectedIndex];
|
|
546
|
+
if (activeItem && activeItem.desc) {
|
|
547
|
+
console.log('\n Info: ' + activeItem.desc);
|
|
548
|
+
}
|
|
549
|
+
if (selectedValues.size > 0) {
|
|
550
|
+
console.log(`\n Selected: ${selectedValues.size} template(s) [Press ENTER to proceed]`);
|
|
551
|
+
}
|
|
622
552
|
return;
|
|
623
553
|
}
|
|
554
|
+
}
|
|
624
555
|
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
console.log('========================================================\n');
|
|
629
|
-
console.log(' [SPACE] Select / Deselect | [ENTER] Confirm | [ESC] Back\n');
|
|
556
|
+
// ============================================================================
|
|
557
|
+
// COPY & EXTRACTION ENGINE
|
|
558
|
+
// ============================================================================
|
|
630
559
|
|
|
631
|
-
|
|
632
|
-
|
|
560
|
+
function copyDirectorySync(src, dest) {
|
|
561
|
+
if (!fs.existsSync(dest)) {
|
|
562
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
563
|
+
}
|
|
633
564
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
}
|
|
639
|
-
const end = Math.min(start + PAGE_SIZE, total);
|
|
565
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
566
|
+
for (const entry of entries) {
|
|
567
|
+
const srcPath = path.join(src, entry.name);
|
|
568
|
+
const destPath = path.join(dest, entry.name);
|
|
640
569
|
|
|
641
|
-
if (
|
|
642
|
-
|
|
570
|
+
if (entry.name === 'node_modules' || entry.name === 'target' || entry.name === '.git' || entry.name === 'dist') {
|
|
571
|
+
continue;
|
|
643
572
|
}
|
|
644
573
|
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
console.log(` --- ${catObj.title} ---`);
|
|
652
|
-
}
|
|
653
|
-
lastCat = t.category;
|
|
574
|
+
if (entry.isDirectory()) {
|
|
575
|
+
copyDirectorySync(srcPath, destPath);
|
|
576
|
+
} else {
|
|
577
|
+
fs.copyFileSync(srcPath, destPath);
|
|
578
|
+
if (entry.name === 'script.sh' || entry.name === 'mvnw') {
|
|
579
|
+
try { fs.chmodSync(destPath, 0o755); } catch (e) {}
|
|
654
580
|
}
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
function unpackTemplates(selected, folderName) {
|
|
586
|
+
const currentDir = process.cwd();
|
|
587
|
+
const targetDir = folderName && folderName !== '.'
|
|
588
|
+
? path.join(currentDir, folderName)
|
|
589
|
+
: currentDir;
|
|
590
|
+
|
|
591
|
+
console.log('\n--------------------------------------------------------');
|
|
592
|
+
console.log(`Extracting templates into: ${targetDir}`);
|
|
593
|
+
console.log('--------------------------------------------------------\n');
|
|
594
|
+
|
|
595
|
+
let listToUnpack = [];
|
|
596
|
+
|
|
597
|
+
if (selected.includes('all')) {
|
|
598
|
+
const allDirs = fs.readdirSync(templatesRoot).filter(f =>
|
|
599
|
+
fs.statSync(path.join(templatesRoot, f)).isDirectory()
|
|
600
|
+
);
|
|
601
|
+
listToUnpack = allDirs;
|
|
602
|
+
} else {
|
|
603
|
+
listToUnpack = selected;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
let count = 0;
|
|
607
|
+
for (const tName of listToUnpack) {
|
|
608
|
+
const sourcePath = path.join(templatesRoot, tName);
|
|
609
|
+
if (!fs.existsSync(sourcePath)) {
|
|
610
|
+
console.log(`[SKIPPED] Template not found: ${tName}`);
|
|
611
|
+
continue;
|
|
660
612
|
}
|
|
661
613
|
|
|
662
|
-
|
|
663
|
-
|
|
614
|
+
let destPath = targetDir;
|
|
615
|
+
if (listToUnpack.length > 1) {
|
|
616
|
+
destPath = path.join(targetDir, tName);
|
|
664
617
|
}
|
|
665
618
|
|
|
666
|
-
|
|
619
|
+
try {
|
|
620
|
+
copyDirectorySync(sourcePath, destPath);
|
|
621
|
+
console.log(`[COPIED] ${tName} -> ${destPath}`);
|
|
622
|
+
count++;
|
|
623
|
+
} catch (err) {
|
|
624
|
+
console.error(`[ERROR] Failed to copy ${tName}: ${err.message}`);
|
|
625
|
+
}
|
|
667
626
|
}
|
|
627
|
+
|
|
628
|
+
console.log('\n========================================================');
|
|
629
|
+
console.log(`SUCCESS: ${count} template(s) unpacked cleanly.`);
|
|
630
|
+
console.log('========================================================');
|
|
631
|
+
console.log('Ready for development.\n');
|
|
632
|
+
process.exit(0);
|
|
668
633
|
}
|
|
669
634
|
|
|
670
|
-
function promptFolderName(
|
|
635
|
+
function promptFolderName(selected) {
|
|
671
636
|
isPrompting = true;
|
|
672
|
-
process.stdin.removeAllListeners('keypress');
|
|
673
637
|
if (process.stdin.isTTY) {
|
|
674
638
|
process.stdin.setRawMode(false);
|
|
675
639
|
}
|
|
676
640
|
|
|
641
|
+
const defaultFolder = selected.length === 1 && selected[0] !== 'all' ? selected[0] : '.';
|
|
677
642
|
const rl = readline.createInterface({
|
|
678
643
|
input: process.stdin,
|
|
679
644
|
output: process.stdout
|
|
680
645
|
});
|
|
681
646
|
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
});
|
|
688
|
-
rl.question('\nChoose frontend (1-11, default: 1 [all]): ', (ans) => {
|
|
689
|
-
const val = ans.trim();
|
|
690
|
-
let selectedFrontend = 'all';
|
|
691
|
-
const num = parseInt(val, 10);
|
|
692
|
-
if (!isNaN(num) && num >= 1 && num <= frontendOptions.length) {
|
|
693
|
-
selectedFrontend = frontendOptions[num - 1].value;
|
|
694
|
-
} else {
|
|
695
|
-
const directMatch = frontendOptions.find(f => f.value === val);
|
|
696
|
-
if (directMatch) {
|
|
697
|
-
selectedFrontend = directMatch.value;
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
done(selectedFrontend);
|
|
701
|
-
});
|
|
702
|
-
} else {
|
|
703
|
-
done(null);
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
askFrontendIfRequired((selectedFrontend) => {
|
|
708
|
-
const isAll = choices.includes('all');
|
|
709
|
-
const defaultFolder = isAll || choices.length > 1 ? 'wad-templates' : choices[0];
|
|
710
|
-
|
|
711
|
-
console.log('\nSelected to unpack: ' + choices.join(', '));
|
|
712
|
-
if (selectedFrontend) {
|
|
713
|
-
console.log('Selected frontend variant: ' + selectedFrontend);
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
rl.question(`Enter destination folder name (default: "${defaultFolder}"): `, (answer) => {
|
|
717
|
-
rl.close();
|
|
718
|
-
const folder = answer.trim() || defaultFolder;
|
|
719
|
-
const destDir = path.resolve(process.cwd(), folder);
|
|
720
|
-
|
|
721
|
-
if (!fs.existsSync(destDir)) {
|
|
722
|
-
fs.mkdirSync(destDir, { recursive: true });
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
if (isAll) {
|
|
726
|
-
copyDirRecursive(templatesRoot, destDir);
|
|
727
|
-
console.log('\nSuccessfully unpacked ALL 56 templates into: ' + destDir);
|
|
728
|
-
} else {
|
|
729
|
-
choices.forEach(c => {
|
|
730
|
-
if (c === 'fullstack-springboot-vue') {
|
|
731
|
-
const targetFullstack = choices.length === 1 ? destDir : path.join(destDir, c);
|
|
732
|
-
if (!fs.existsSync(targetFullstack)) {
|
|
733
|
-
fs.mkdirSync(targetFullstack, { recursive: true });
|
|
734
|
-
}
|
|
735
|
-
const backendSrc = path.join(templatesRoot, 'springboot-2model-1controller-api');
|
|
736
|
-
const backendDest = path.join(targetFullstack, 'backend');
|
|
737
|
-
copyDirRecursive(backendSrc, backendDest);
|
|
738
|
-
console.log('Unpacked [backend] (Spring Boot 2-Model API) into: ' + backendDest);
|
|
739
|
-
|
|
740
|
-
const frontendDest = path.join(targetFullstack, 'frontend');
|
|
741
|
-
if (selectedFrontend === 'all') {
|
|
742
|
-
const fullstackTemplateFrontendSrc = path.join(templatesRoot, 'fullstack-springboot-vue', 'frontend');
|
|
743
|
-
copyDirRecursive(fullstackTemplateFrontendSrc, frontendDest);
|
|
744
|
-
console.log('Unpacked [frontend] (All 10 Vue variants) into: ' + frontendDest);
|
|
745
|
-
} else {
|
|
746
|
-
const singleFrontendSrc = path.join(templatesRoot, selectedFrontend);
|
|
747
|
-
copyDirRecursive(singleFrontendSrc, frontendDest);
|
|
748
|
-
console.log('Unpacked [frontend] (' + selectedFrontend + ') into: ' + frontendDest);
|
|
749
|
-
}
|
|
750
|
-
} else {
|
|
751
|
-
const src = path.join(templatesRoot, c);
|
|
752
|
-
const dest = path.join(destDir, c);
|
|
753
|
-
copyDirRecursive(src, dest);
|
|
754
|
-
console.log('Unpacked [' + c + '] into: ' + dest);
|
|
755
|
-
}
|
|
756
|
-
});
|
|
757
|
-
console.log('\nSuccessfully unpacked all chosen templates into: ' + destDir);
|
|
758
|
-
}
|
|
759
|
-
process.exit(0);
|
|
760
|
-
});
|
|
647
|
+
console.log('\n');
|
|
648
|
+
rl.question(`Enter destination directory (default: "${defaultFolder}"): `, (answer) => {
|
|
649
|
+
rl.close();
|
|
650
|
+
const folder = answer.trim() || defaultFolder;
|
|
651
|
+
unpackTemplates(selected, folder);
|
|
761
652
|
});
|
|
762
653
|
}
|
|
763
654
|
|
|
764
|
-
//
|
|
765
|
-
|
|
766
|
-
|
|
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();
|
|
786
|
-
} else {
|
|
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
|
-
}
|
|
795
|
-
}
|
|
796
|
-
} else {
|
|
797
|
-
startInteractive();
|
|
798
|
-
}
|
|
655
|
+
// ============================================================================
|
|
656
|
+
// INTERACTIVE INPUT HANDLER
|
|
657
|
+
// ============================================================================
|
|
799
658
|
|
|
800
659
|
function startInteractive() {
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
process.
|
|
660
|
+
if (!process.stdin.isTTY) {
|
|
661
|
+
console.log('Interactive mode requires a TTY terminal. Usage: npx wad-lab [template-name]');
|
|
662
|
+
process.exit(1);
|
|
804
663
|
}
|
|
805
664
|
|
|
665
|
+
readline.emitKeypressEvents(process.stdin);
|
|
666
|
+
process.stdin.setRawMode(true);
|
|
667
|
+
process.stdin.resume();
|
|
668
|
+
|
|
806
669
|
renderScreen();
|
|
807
670
|
|
|
808
671
|
process.stdin.on('keypress', (str, key) => {
|
|
@@ -812,20 +675,18 @@ function startInteractive() {
|
|
|
812
675
|
process.exit(0);
|
|
813
676
|
}
|
|
814
677
|
|
|
815
|
-
const items = getActiveItemList();
|
|
816
|
-
|
|
817
678
|
// MAIN VIEW INPUT
|
|
818
679
|
if (currentView === 'MAIN') {
|
|
680
|
+
const items = getActiveItemList();
|
|
681
|
+
|
|
819
682
|
if (key.name === 'up') {
|
|
820
683
|
selectedIndex = (selectedIndex - 1 + items.length) % items.length;
|
|
821
684
|
renderScreen();
|
|
822
685
|
} else if (key.name === 'down') {
|
|
823
686
|
selectedIndex = (selectedIndex + 1) % items.length;
|
|
824
687
|
renderScreen();
|
|
825
|
-
} else if (str === '
|
|
826
|
-
|
|
827
|
-
selectedIndex = 0;
|
|
828
|
-
renderScreen();
|
|
688
|
+
} else if (str === 'q' || str === 'Q') {
|
|
689
|
+
process.exit(0);
|
|
829
690
|
} else if (str === 's' || str === 'S') {
|
|
830
691
|
currentView = 'SEARCH';
|
|
831
692
|
searchQuery = '';
|
|
@@ -833,47 +694,28 @@ function startInteractive() {
|
|
|
833
694
|
renderScreen();
|
|
834
695
|
} else if (str === 'a' || str === 'A') {
|
|
835
696
|
promptFolderName(['all']);
|
|
836
|
-
} else if (str
|
|
837
|
-
currentView = 'ALL';
|
|
838
|
-
selectedIndex = 0;
|
|
839
|
-
renderScreen();
|
|
840
|
-
} else if (str >= '1' && str <= '7') {
|
|
697
|
+
} else if (str >= '1' && str <= '5') {
|
|
841
698
|
const num = parseInt(str, 10);
|
|
842
|
-
|
|
843
|
-
if (
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
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
|
-
}
|
|
699
|
+
activeCategory = categories[num - 1];
|
|
700
|
+
if (activeCategory) {
|
|
701
|
+
currentView = 'CATEGORY';
|
|
702
|
+
selectedIndex = 0;
|
|
703
|
+
selectedValues.clear();
|
|
854
704
|
renderScreen();
|
|
855
705
|
}
|
|
856
706
|
} else if (key.name === 'return' || key.name === 'enter') {
|
|
857
707
|
const item = items[selectedIndex];
|
|
858
|
-
if (
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
}
|
|
862
|
-
if (item.type === 'featured') {
|
|
863
|
-
promptFolderName([item.id]);
|
|
864
|
-
} else if (item.id === 'categories') {
|
|
865
|
-
currentView = 'CATEGORY_LIST';
|
|
708
|
+
if (item.type === 'category') {
|
|
709
|
+
activeCategory = categories.find(c => c.id === item.id);
|
|
710
|
+
currentView = 'CATEGORY';
|
|
866
711
|
selectedIndex = 0;
|
|
712
|
+
selectedValues.clear();
|
|
867
713
|
renderScreen();
|
|
868
714
|
} else if (item.id === 'search') {
|
|
869
715
|
currentView = 'SEARCH';
|
|
870
716
|
searchQuery = '';
|
|
871
717
|
selectedIndex = 0;
|
|
872
718
|
renderScreen();
|
|
873
|
-
} else if (item.id === 'all_view') {
|
|
874
|
-
currentView = 'ALL';
|
|
875
|
-
selectedIndex = 0;
|
|
876
|
-
renderScreen();
|
|
877
719
|
} else if (item.id === 'unpack_all') {
|
|
878
720
|
promptFolderName(['all']);
|
|
879
721
|
}
|
|
@@ -881,8 +723,10 @@ function startInteractive() {
|
|
|
881
723
|
return;
|
|
882
724
|
}
|
|
883
725
|
|
|
884
|
-
//
|
|
885
|
-
if (currentView === '
|
|
726
|
+
// CATEGORY VIEW INPUT
|
|
727
|
+
if (currentView === 'CATEGORY') {
|
|
728
|
+
const items = getActiveItemList();
|
|
729
|
+
|
|
886
730
|
if (key.name === 'escape' || key.name === 'left' || str === 'b' || str === 'B') {
|
|
887
731
|
currentView = 'MAIN';
|
|
888
732
|
selectedIndex = 0;
|
|
@@ -895,23 +739,28 @@ function startInteractive() {
|
|
|
895
739
|
} else if (key.name === 'down') {
|
|
896
740
|
selectedIndex = (selectedIndex + 1) % items.length;
|
|
897
741
|
renderScreen();
|
|
898
|
-
} else if (
|
|
899
|
-
const
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
742
|
+
} else if (key.name === 'space') {
|
|
743
|
+
const t = items[selectedIndex];
|
|
744
|
+
if (t) {
|
|
745
|
+
if (selectedValues.has(t.value)) {
|
|
746
|
+
selectedValues.delete(t.value);
|
|
747
|
+
} else {
|
|
748
|
+
selectedValues.add(t.value);
|
|
749
|
+
}
|
|
750
|
+
renderScreen();
|
|
751
|
+
}
|
|
904
752
|
} else if (key.name === 'return' || key.name === 'enter') {
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
753
|
+
if (selectedValues.size > 0) {
|
|
754
|
+
promptFolderName(Array.from(selectedValues));
|
|
755
|
+
} else {
|
|
756
|
+
const t = items[selectedIndex];
|
|
757
|
+
if (t) promptFolderName([t.value]);
|
|
758
|
+
}
|
|
910
759
|
}
|
|
911
760
|
return;
|
|
912
761
|
}
|
|
913
762
|
|
|
914
|
-
// SEARCH VIEW INPUT
|
|
763
|
+
// SEARCH VIEW INPUT (MULTI-WORD)
|
|
915
764
|
if (currentView === 'SEARCH') {
|
|
916
765
|
if (key.name === 'escape') {
|
|
917
766
|
currentView = 'MAIN';
|
|
@@ -920,43 +769,31 @@ function startInteractive() {
|
|
|
920
769
|
return;
|
|
921
770
|
}
|
|
922
771
|
|
|
923
|
-
|
|
924
|
-
if (items.length > 0) {
|
|
925
|
-
selectedIndex = (selectedIndex - 1 + items.length) % items.length;
|
|
926
|
-
renderScreen();
|
|
927
|
-
}
|
|
928
|
-
return;
|
|
929
|
-
}
|
|
772
|
+
const items = getActiveItemList();
|
|
930
773
|
|
|
931
|
-
if (key.name === '
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
renderScreen();
|
|
935
|
-
}
|
|
774
|
+
if (key.name === 'up' && items.length > 0) {
|
|
775
|
+
selectedIndex = (selectedIndex - 1 + items.length) % items.length;
|
|
776
|
+
renderScreen();
|
|
936
777
|
return;
|
|
937
778
|
}
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
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
|
-
}
|
|
779
|
+
if (key.name === 'down' && items.length > 0) {
|
|
780
|
+
selectedIndex = (selectedIndex + 1) % items.length;
|
|
781
|
+
renderScreen();
|
|
949
782
|
return;
|
|
950
783
|
}
|
|
951
784
|
|
|
952
785
|
if (key.name === 'return' || key.name === 'enter') {
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
786
|
+
if (selectedValues.size > 0) {
|
|
787
|
+
promptFolderName(Array.from(selectedValues));
|
|
788
|
+
return;
|
|
956
789
|
}
|
|
957
|
-
if (
|
|
958
|
-
promptFolderName(
|
|
790
|
+
if (items.length > 0) {
|
|
791
|
+
promptFolderName([items[selectedIndex].value]);
|
|
792
|
+
return;
|
|
959
793
|
}
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
if (key.name === 'space' && key.ctrl) {
|
|
960
797
|
return;
|
|
961
798
|
}
|
|
962
799
|
|
|
@@ -973,63 +810,67 @@ function startInteractive() {
|
|
|
973
810
|
return;
|
|
974
811
|
}
|
|
975
812
|
|
|
976
|
-
// Typing search
|
|
977
|
-
if (str &&
|
|
813
|
+
// Typing characters into search (allows spaces for multi-word search!)
|
|
814
|
+
if (str && !key.ctrl && !key.meta) {
|
|
978
815
|
searchQuery += str;
|
|
979
816
|
selectedIndex = 0;
|
|
980
817
|
renderScreen();
|
|
981
818
|
return;
|
|
982
819
|
}
|
|
983
|
-
return;
|
|
984
820
|
}
|
|
821
|
+
});
|
|
822
|
+
}
|
|
985
823
|
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
currentView = currentView === 'CATEGORY' ? 'CATEGORY_LIST' : 'MAIN';
|
|
990
|
-
selectedIndex = 0;
|
|
991
|
-
renderScreen();
|
|
992
|
-
return;
|
|
993
|
-
}
|
|
824
|
+
// ============================================================================
|
|
825
|
+
// CLI ARGUMENT DISPATCHER
|
|
826
|
+
// ============================================================================
|
|
994
827
|
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
selectedIndex = (selectedIndex - 1 + items.length) % items.length;
|
|
998
|
-
renderScreen();
|
|
999
|
-
}
|
|
1000
|
-
return;
|
|
1001
|
-
}
|
|
828
|
+
function runCli() {
|
|
829
|
+
const arg = process.argv[2];
|
|
1002
830
|
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
}
|
|
1008
|
-
return;
|
|
1009
|
-
}
|
|
831
|
+
if (!arg) {
|
|
832
|
+
startInteractive();
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
1010
835
|
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
}
|
|
1021
|
-
return;
|
|
1022
|
-
}
|
|
836
|
+
if (arg === '--help' || arg === '-h') {
|
|
837
|
+
console.log('WAD Lab Templates CLI (Zero Emojis)');
|
|
838
|
+
console.log('Usage:');
|
|
839
|
+
console.log(' npx wad-lab Launch interactive UI');
|
|
840
|
+
console.log(' npx wad-lab [template-name] Directly unpack a template');
|
|
841
|
+
console.log(' npx wad-lab --list List all available templates');
|
|
842
|
+
console.log(' npx wad-lab --search [words...] Search templates with multi-word filter');
|
|
843
|
+
process.exit(0);
|
|
844
|
+
}
|
|
1023
845
|
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
846
|
+
if (arg === '--list' || arg === '-l') {
|
|
847
|
+
console.log('Available WAD Lab Templates (54 Standalone):');
|
|
848
|
+
categories.forEach(cat => {
|
|
849
|
+
console.log(`\n=== ${cat.title} ===`);
|
|
850
|
+
templates.filter(t => t.category === cat.id).forEach(t => {
|
|
851
|
+
console.log(` - ${t.value.padEnd(38)} : ${t.desc}`);
|
|
852
|
+
});
|
|
853
|
+
});
|
|
854
|
+
process.exit(0);
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
if (arg === '--search' && process.argv[3]) {
|
|
858
|
+
searchQuery = process.argv.slice(3).join(' ');
|
|
859
|
+
currentView = 'SEARCH';
|
|
860
|
+
selectedIndex = 0;
|
|
861
|
+
startInteractive();
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
const found = templates.find(t => t.value === arg);
|
|
866
|
+
if (found) {
|
|
867
|
+
const dest = process.argv[3] || found.value;
|
|
868
|
+
unpackTemplates([found.value], dest);
|
|
869
|
+
} else {
|
|
870
|
+
console.log(`Unknown template or command: "${arg}".`);
|
|
871
|
+
console.log('Run "npx wad-lab --list" to view all available templates.');
|
|
872
|
+
process.exit(1);
|
|
873
|
+
}
|
|
1035
874
|
}
|
|
875
|
+
|
|
876
|
+
runCli();
|