@vue-skuilder/cli 0.1.7 → 0.1.8-1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/README.md +122 -8
  2. package/dist/cli.d.ts +1 -1
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +2 -1
  5. package/dist/cli.js.map +1 -1
  6. package/dist/commands/init.d.ts.map +1 -1
  7. package/dist/commands/init.js +34 -3
  8. package/dist/commands/init.js.map +1 -1
  9. package/dist/commands/pack.d.ts +10 -0
  10. package/dist/commands/pack.d.ts.map +1 -1
  11. package/dist/commands/pack.js +1 -1
  12. package/dist/commands/pack.js.map +1 -1
  13. package/dist/commands/studio.d.ts.map +1 -1
  14. package/dist/commands/studio.js +641 -106
  15. package/dist/commands/studio.js.map +1 -1
  16. package/dist/commands/unpack.d.ts +12 -0
  17. package/dist/commands/unpack.d.ts.map +1 -1
  18. package/dist/commands/unpack.js +1 -1
  19. package/dist/commands/unpack.js.map +1 -1
  20. package/dist/express-assets/app.d.ts +6 -0
  21. package/dist/express-assets/app.d.ts.map +1 -0
  22. package/dist/express-assets/app.js +209 -0
  23. package/dist/express-assets/app.js.map +1 -0
  24. package/dist/express-assets/assets/classroomDesignDoc.js +24 -0
  25. package/dist/express-assets/assets/courseValidateDocUpdate.js +56 -0
  26. package/dist/express-assets/assets/get-tagsDesignDoc.json +9 -0
  27. package/dist/express-assets/attachment-preprocessing/index.d.ts +11 -0
  28. package/dist/express-assets/attachment-preprocessing/index.d.ts.map +1 -0
  29. package/dist/express-assets/attachment-preprocessing/index.js +204 -0
  30. package/dist/express-assets/attachment-preprocessing/index.js.map +1 -0
  31. package/dist/express-assets/attachment-preprocessing/normalize.d.ts +7 -0
  32. package/dist/express-assets/attachment-preprocessing/normalize.d.ts.map +1 -0
  33. package/dist/express-assets/attachment-preprocessing/normalize.js +90 -0
  34. package/dist/express-assets/attachment-preprocessing/normalize.js.map +1 -0
  35. package/dist/express-assets/client-requests/classroom-requests.d.ts +26 -0
  36. package/dist/express-assets/client-requests/classroom-requests.d.ts.map +1 -0
  37. package/dist/express-assets/client-requests/classroom-requests.js +171 -0
  38. package/dist/express-assets/client-requests/classroom-requests.js.map +1 -0
  39. package/dist/express-assets/client-requests/course-requests.d.ts +10 -0
  40. package/dist/express-assets/client-requests/course-requests.d.ts.map +1 -0
  41. package/dist/express-assets/client-requests/course-requests.js +135 -0
  42. package/dist/express-assets/client-requests/course-requests.js.map +1 -0
  43. package/dist/express-assets/client-requests/pack-requests.d.ts +19 -0
  44. package/dist/express-assets/client-requests/pack-requests.d.ts.map +1 -0
  45. package/dist/express-assets/client-requests/pack-requests.js +130 -0
  46. package/dist/express-assets/client-requests/pack-requests.js.map +1 -0
  47. package/dist/express-assets/client.d.ts +31 -0
  48. package/dist/express-assets/client.d.ts.map +1 -0
  49. package/dist/express-assets/client.js +70 -0
  50. package/dist/express-assets/client.js.map +1 -0
  51. package/dist/express-assets/couchdb/authentication.d.ts +4 -0
  52. package/dist/express-assets/couchdb/authentication.d.ts.map +1 -0
  53. package/dist/express-assets/couchdb/authentication.js +69 -0
  54. package/dist/express-assets/couchdb/authentication.js.map +1 -0
  55. package/dist/express-assets/couchdb/index.d.ts +18 -0
  56. package/dist/express-assets/couchdb/index.d.ts.map +1 -0
  57. package/dist/express-assets/couchdb/index.js +52 -0
  58. package/dist/express-assets/couchdb/index.js.map +1 -0
  59. package/dist/express-assets/design-docs.d.ts +63 -0
  60. package/dist/express-assets/design-docs.d.ts.map +1 -0
  61. package/dist/express-assets/design-docs.js +90 -0
  62. package/dist/express-assets/design-docs.js.map +1 -0
  63. package/dist/express-assets/logger.d.ts +3 -0
  64. package/dist/express-assets/logger.d.ts.map +1 -0
  65. package/dist/express-assets/logger.js +62 -0
  66. package/dist/express-assets/logger.js.map +1 -0
  67. package/dist/express-assets/routes/logs.d.ts +3 -0
  68. package/dist/express-assets/routes/logs.d.ts.map +1 -0
  69. package/dist/express-assets/routes/logs.js +274 -0
  70. package/dist/express-assets/routes/logs.js.map +1 -0
  71. package/dist/express-assets/utils/env.d.ts +11 -0
  72. package/dist/express-assets/utils/env.d.ts.map +1 -0
  73. package/dist/express-assets/utils/env.js +39 -0
  74. package/dist/express-assets/utils/env.js.map +1 -0
  75. package/dist/express-assets/utils/processQueue.d.ts +39 -0
  76. package/dist/express-assets/utils/processQueue.d.ts.map +1 -0
  77. package/dist/express-assets/utils/processQueue.js +175 -0
  78. package/dist/express-assets/utils/processQueue.js.map +1 -0
  79. package/dist/studio-ui-src/App.vue +132 -0
  80. package/dist/studio-ui-src/api/index.ts +30 -0
  81. package/dist/studio-ui-src/components/StudioFlush.vue +108 -0
  82. package/dist/studio-ui-src/config/development.ts +98 -0
  83. package/dist/studio-ui-src/index.html +13 -0
  84. package/dist/studio-ui-src/main.ts +186 -0
  85. package/dist/studio-ui-src/package.json +35 -0
  86. package/dist/studio-ui-src/router/index.ts +32 -0
  87. package/dist/studio-ui-src/stores/useAuthStore.ts +3 -0
  88. package/dist/studio-ui-src/tsconfig.json +28 -0
  89. package/dist/studio-ui-src/utils/courseConfigRegistration.ts +297 -0
  90. package/dist/studio-ui-src/views/BrowseView.vue +82 -0
  91. package/dist/studio-ui-src/views/BulkImportView.vue +89 -0
  92. package/dist/studio-ui-src/views/CourseEditorView.vue +62 -0
  93. package/dist/studio-ui-src/views/CreateCardView.vue +144 -0
  94. package/dist/studio-ui-src/vite.config.base.js +103 -0
  95. package/dist/studio-ui-src/vite.config.ts +26 -0
  96. package/dist/templates/.skuilder/README.md +29 -0
  97. package/dist/types.d.ts +5 -0
  98. package/dist/types.d.ts.map +1 -1
  99. package/dist/types.js.map +1 -1
  100. package/dist/utils/ExpressManager.d.ts +28 -0
  101. package/dist/utils/ExpressManager.d.ts.map +1 -0
  102. package/dist/utils/ExpressManager.js +166 -0
  103. package/dist/utils/ExpressManager.js.map +1 -0
  104. package/dist/utils/NodeFileSystemAdapter.d.ts +6 -0
  105. package/dist/utils/NodeFileSystemAdapter.d.ts.map +1 -1
  106. package/dist/utils/NodeFileSystemAdapter.js +29 -1
  107. package/dist/utils/NodeFileSystemAdapter.js.map +1 -1
  108. package/dist/utils/error-reporting.d.ts +54 -0
  109. package/dist/utils/error-reporting.d.ts.map +1 -0
  110. package/dist/utils/error-reporting.js +143 -0
  111. package/dist/utils/error-reporting.js.map +1 -0
  112. package/dist/utils/pack-courses.d.ts.map +1 -1
  113. package/dist/utils/pack-courses.js +10 -27
  114. package/dist/utils/pack-courses.js.map +1 -1
  115. package/dist/utils/prompts.d.ts.map +1 -1
  116. package/dist/utils/prompts.js +24 -0
  117. package/dist/utils/prompts.js.map +1 -1
  118. package/dist/utils/questions-hash.d.ts +22 -0
  119. package/dist/utils/questions-hash.d.ts.map +1 -0
  120. package/dist/utils/questions-hash.js +96 -0
  121. package/dist/utils/questions-hash.js.map +1 -0
  122. package/dist/utils/template.d.ts +1 -1
  123. package/dist/utils/template.d.ts.map +1 -1
  124. package/dist/utils/template.js +209 -27
  125. package/dist/utils/template.js.map +1 -1
  126. package/eslint.config.mjs +1 -1
  127. package/package.json +30 -11
  128. package/src/cli.ts +3 -1
  129. package/src/commands/init.ts +48 -3
  130. package/src/commands/pack.ts +1 -1
  131. package/src/commands/studio.ts +851 -122
  132. package/src/commands/unpack.ts +1 -1
  133. package/src/types.ts +5 -0
  134. package/src/utils/ExpressManager.ts +210 -0
  135. package/src/utils/NodeFileSystemAdapter.ts +46 -2
  136. package/src/utils/error-reporting.ts +192 -0
  137. package/src/utils/pack-courses.ts +11 -36
  138. package/src/utils/prompts.ts +34 -0
  139. package/src/utils/questions-hash.ts +109 -0
  140. package/src/utils/template.ts +243 -29
  141. package/templates/.skuilder/README.md +29 -0
  142. package/dist/studio-ui-assets/assets/BrowseView-BJbixGOU.js +0 -2
  143. package/dist/studio-ui-assets/assets/BrowseView-BJbixGOU.js.map +0 -1
  144. package/dist/studio-ui-assets/assets/BrowseView-CM4HBO4j.css +0 -1
  145. package/dist/studio-ui-assets/assets/BulkImportView-DB6DYDJU.js +0 -2
  146. package/dist/studio-ui-assets/assets/BulkImportView-DB6DYDJU.js.map +0 -1
  147. package/dist/studio-ui-assets/assets/BulkImportView-g4wQUfPA.css +0 -1
  148. package/dist/studio-ui-assets/assets/CourseEditorView-BIlhlhw1.js +0 -2
  149. package/dist/studio-ui-assets/assets/CourseEditorView-BIlhlhw1.js.map +0 -1
  150. package/dist/studio-ui-assets/assets/CourseEditorView-WuPNLVKp.css +0 -1
  151. package/dist/studio-ui-assets/assets/CreateCardView-CyNOKCkm.css +0 -1
  152. package/dist/studio-ui-assets/assets/CreateCardView-DPjPvzzt.js +0 -2
  153. package/dist/studio-ui-assets/assets/CreateCardView-DPjPvzzt.js.map +0 -1
  154. package/dist/studio-ui-assets/assets/edit-ui.es-DiUxqbgF.js +0 -330
  155. package/dist/studio-ui-assets/assets/edit-ui.es-DiUxqbgF.js.map +0 -1
  156. package/dist/studio-ui-assets/assets/index--zY88pg6.css +0 -14
  157. package/dist/studio-ui-assets/assets/index-BnAv1C72.js +0 -287
  158. package/dist/studio-ui-assets/assets/index-BnAv1C72.js.map +0 -1
  159. package/dist/studio-ui-assets/assets/index-DHMXQY3-.js +0 -192
  160. package/dist/studio-ui-assets/assets/index-DHMXQY3-.js.map +0 -1
  161. package/dist/studio-ui-assets/assets/materialdesignicons-webfont-B7mPwVP_.ttf +0 -0
  162. package/dist/studio-ui-assets/assets/materialdesignicons-webfont-CSr8KVlo.eot +0 -0
  163. package/dist/studio-ui-assets/assets/materialdesignicons-webfont-Dp5v-WZN.woff2 +0 -0
  164. package/dist/studio-ui-assets/assets/materialdesignicons-webfont-PXm3-2wK.woff +0 -0
  165. package/dist/studio-ui-assets/assets/vue-DZcMATiC.js +0 -28
  166. package/dist/studio-ui-assets/assets/vue-DZcMATiC.js.map +0 -1
  167. package/dist/studio-ui-assets/assets/vuetify-qg7mRxy_.js +0 -6
  168. package/dist/studio-ui-assets/assets/vuetify-qg7mRxy_.js.map +0 -1
  169. package/dist/studio-ui-assets/index.html +0 -16
@@ -0,0 +1,109 @@
1
+ import { createHash } from 'crypto';
2
+ import { promises as fs, existsSync } from 'fs';
3
+ import path from 'path';
4
+
5
+ /**
6
+ * Calculate hash of src/questions/ directory contents
7
+ * Returns consistent hash based on file names, content, and modification times
8
+ */
9
+ export async function hashQuestionsDirectory(coursePath: string): Promise<string> {
10
+ const questionsPath = path.join(coursePath, 'src', 'questions');
11
+
12
+ // If questions directory doesn't exist, return special "no-questions" hash
13
+ if (!existsSync(questionsPath)) {
14
+ return 'no-questions';
15
+ }
16
+
17
+ const hash = createHash('sha256');
18
+
19
+ try {
20
+ // Get all files recursively, sort for consistent ordering
21
+ const files = await getAllFiles(questionsPath);
22
+ files.sort();
23
+
24
+ // If no files, return "empty-questions" hash
25
+ if (files.length === 0) {
26
+ return 'empty-questions';
27
+ }
28
+
29
+ // Hash each file's relative path, content, and mtime
30
+ for (const file of files) {
31
+ const relativePath = path.relative(questionsPath, file);
32
+ const stat = await fs.stat(file);
33
+ const content = await fs.readFile(file);
34
+
35
+ // Include relative path, modification time, and content in hash
36
+ hash.update(relativePath);
37
+ hash.update(stat.mtime.toISOString());
38
+ hash.update(content);
39
+ }
40
+
41
+ return hash.digest('hex').substring(0, 12); // First 12 chars for readability
42
+ } catch (error) {
43
+ console.warn(`Warning: Failed to hash questions directory: ${error}`);
44
+ return 'hash-error';
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Recursively get all files in a directory
50
+ */
51
+ async function getAllFiles(dirPath: string): Promise<string[]> {
52
+ const files: string[] = [];
53
+
54
+ try {
55
+ const entries = await fs.readdir(dirPath, { withFileTypes: true });
56
+
57
+ for (const entry of entries) {
58
+ const fullPath = path.join(dirPath, entry.name);
59
+
60
+ if (entry.isDirectory()) {
61
+ // Recursively get files from subdirectories
62
+ const subFiles = await getAllFiles(fullPath);
63
+ files.push(...subFiles);
64
+ } else if (entry.isFile()) {
65
+ // Only include source files (TypeScript, Vue, JavaScript)
66
+ if (/\.(ts|vue|js|tsx|jsx)$/.test(entry.name)) {
67
+ files.push(fullPath);
68
+ }
69
+ }
70
+ }
71
+ } catch (error) {
72
+ // If directory can't be read, return empty array
73
+ console.warn(`Warning: Could not read directory ${dirPath}: ${error}`);
74
+ }
75
+
76
+ return files;
77
+ }
78
+
79
+ /**
80
+ * Get the studio build directory path for a given questions hash
81
+ */
82
+ export function getStudioBuildPath(coursePath: string, questionsHash: string): string {
83
+ return path.join(coursePath, '.skuilder', 'studio-builds', questionsHash);
84
+ }
85
+
86
+ /**
87
+ * Check if a studio build exists for the given questions hash
88
+ */
89
+ export function studioBuildExists(coursePath: string, questionsHash: string): boolean {
90
+ const buildPath = getStudioBuildPath(coursePath, questionsHash);
91
+ return existsSync(path.join(buildPath, 'index.html'));
92
+ }
93
+
94
+ /**
95
+ * Ensure the cache directory structure exists
96
+ */
97
+ export async function ensureCacheDirectory(coursePath: string): Promise<void> {
98
+ const cacheDir = path.join(coursePath, '.skuilder', 'studio-builds');
99
+ await fs.mkdir(cacheDir, { recursive: true });
100
+ }
101
+
102
+ /**
103
+ * Ensure a specific build directory exists
104
+ */
105
+ export async function ensureBuildDirectory(coursePath: string, questionsHash: string): Promise<string> {
106
+ const buildPath = getStudioBuildPath(coursePath, questionsHash);
107
+ await fs.mkdir(buildPath, { recursive: true });
108
+ return buildPath;
109
+ }
@@ -1,8 +1,10 @@
1
1
  import { promises as fs, existsSync } from 'fs';
2
2
  import path from 'path';
3
3
  import { fileURLToPath } from 'url';
4
+ import { randomUUID } from 'crypto';
4
5
  import chalk from 'chalk';
5
6
  import { ProjectConfig, SkuilderConfig } from '../types.js';
7
+ import { CourseConfig } from '@vue-skuilder/common';
6
8
 
7
9
  const __filename = fileURLToPath(import.meta.url);
8
10
  const __dirname = path.dirname(__filename);
@@ -73,20 +75,26 @@ export async function transformPackageJson(
73
75
  packageJson.description = `Skuilder course application: ${projectName}`;
74
76
  packageJson.version = '1.0.0';
75
77
 
76
- // Transform workspace dependencies to published versions
78
+ // Transform workspace and file dependencies to published versions
77
79
  if (packageJson.dependencies) {
78
80
  for (const [depName, version] of Object.entries(packageJson.dependencies)) {
79
- if (typeof version === 'string' && version.startsWith('workspace:')) {
80
- // Replace workspace references with CLI's version
81
+ if (
82
+ typeof version === 'string' &&
83
+ (version.startsWith('workspace:') || version.startsWith('file:'))
84
+ ) {
85
+ // Replace workspace and file references with CLI's version
81
86
  packageJson.dependencies[depName] = `^${cliVersion}`;
82
87
  }
83
88
  }
84
89
  }
85
90
 
86
- // Add missing terser devDependency for build minification
91
+ // Add missing devDependencies for build system
87
92
  if (packageJson.devDependencies && !packageJson.devDependencies['terser']) {
88
93
  packageJson.devDependencies['terser'] = '^5.39.0';
89
94
  }
95
+ if (packageJson.devDependencies && !packageJson.devDependencies['vite-plugin-dts']) {
96
+ packageJson.devDependencies['vite-plugin-dts'] = '^4.3.0';
97
+ }
90
98
 
91
99
  // Add CLI as devDependency for all projects
92
100
  if (!packageJson.devDependencies) {
@@ -114,19 +122,34 @@ export async function transformPackageJson(
114
122
  * // [ ] This should be revised so that it works from the existing vite.config.ts in standalone-ui. As is, it recreates 95% of the same config.
115
123
  */
116
124
  export async function createViteConfig(viteConfigPath: string): Promise<void> {
117
- // Create a clean vite config for standalone projects
118
- const transformedContent = `// packages/standalone-ui/vite.config.ts
119
- import { defineConfig } from 'vite';
125
+ // Create dual build config similar to standalone-ui but without workspace dependencies
126
+ const transformedContent = `import { defineConfig } from 'vite';
120
127
  import vue from '@vitejs/plugin-vue';
128
+ import dts from 'vite-plugin-dts';
129
+ import { resolve } from 'path';
121
130
  import { fileURLToPath, URL } from 'node:url';
122
131
 
132
+ // Determine build mode from environment variable
133
+ const buildMode = process.env.BUILD_MODE || 'webapp';
134
+
123
135
  export default defineConfig({
124
- plugins: [vue()],
136
+ plugins: [
137
+ vue(),
138
+ // Only include dts plugin for library builds
139
+ ...(buildMode === 'library'
140
+ ? [dts({
141
+ insertTypesEntry: true,
142
+ include: ['src/questions/**/*.ts', 'src/questions/**/*.vue'],
143
+ exclude: ['**/*.spec.ts', '**/*.test.ts'],
144
+ outDir: 'dist-lib',
145
+ })]
146
+ : []
147
+ )
148
+ ],
125
149
  resolve: {
126
150
  alias: {
127
151
  // Alias for internal src paths
128
152
  '@': fileURLToPath(new URL('./src', import.meta.url)),
129
-
130
153
  // Add events alias if needed (often required by dependencies)
131
154
  events: 'events',
132
155
  },
@@ -140,30 +163,81 @@ export default defineConfig({
140
163
  '@vue-skuilder/db',
141
164
  '@vue-skuilder/common',
142
165
  '@vue-skuilder/common-ui',
143
- '@vue-skuilder/courses',
166
+ '@vue-skuilder/courseware',
144
167
  ],
145
168
  },
146
169
  // --- Dependencies optimization ---
147
170
  optimizeDeps: {
148
- // Help Vite pre-bundle dependencies from published packages
149
171
  include: [
172
+ 'events',
150
173
  '@vue-skuilder/common-ui',
151
174
  '@vue-skuilder/db',
152
175
  '@vue-skuilder/common',
153
- '@vue-skuilder/courses',
176
+ '@vue-skuilder/courseware',
154
177
  ],
155
178
  },
156
179
  server: {
157
180
  port: 5173, // Use standard Vite port for standalone projects
158
181
  },
159
- build: {
160
- sourcemap: true,
161
- target: 'es2020',
162
- minify: 'terser',
163
- terserOptions: {
164
- keep_classnames: true,
165
- },
166
- },
182
+ build: buildMode === 'library'
183
+ ? {
184
+ // Library build configuration
185
+ sourcemap: true,
186
+ target: 'es2020',
187
+ minify: 'terser',
188
+ terserOptions: {
189
+ keep_classnames: true,
190
+ },
191
+ lib: {
192
+ entry: resolve(__dirname, 'src/questions/index.ts'),
193
+ name: 'VueSkuilderStandaloneQuestions',
194
+ fileName: (format) => \`questions.\${format === 'es' ? 'mjs' : 'cjs.js'}\`,
195
+ },
196
+ rollupOptions: {
197
+ // External packages that shouldn't be bundled in library mode
198
+ external: [
199
+ 'vue',
200
+ 'vue-router',
201
+ 'vuetify',
202
+ 'pinia',
203
+ '@vue-skuilder/common',
204
+ '@vue-skuilder/common-ui',
205
+ '@vue-skuilder/courseware',
206
+ '@vue-skuilder/db',
207
+ ],
208
+ output: {
209
+ // Global variables for UMD build
210
+ globals: {
211
+ 'vue': 'Vue',
212
+ 'vue-router': 'VueRouter',
213
+ 'vuetify': 'Vuetify',
214
+ 'pinia': 'Pinia',
215
+ '@vue-skuilder/common': 'VueSkuilderCommon',
216
+ '@vue-skuilder/common-ui': 'VueSkuilderCommonUI',
217
+ '@vue-skuilder/courseware': 'VueSkuilderCourseWare',
218
+ '@vue-skuilder/db': 'VueSkuilderDB',
219
+ },
220
+ exports: 'named',
221
+ // Preserve CSS in the output bundle
222
+ assetFileNames: 'assets/[name].[ext]',
223
+ },
224
+ },
225
+ // Output to separate directory for library build
226
+ outDir: 'dist-lib',
227
+ // Allow CSS code splitting for component libraries
228
+ cssCodeSplit: true,
229
+ }
230
+ : {
231
+ // Webapp build configuration (existing)
232
+ sourcemap: true,
233
+ target: 'es2020',
234
+ minify: 'terser',
235
+ terserOptions: {
236
+ keep_classnames: true,
237
+ },
238
+ // Standard webapp output directory
239
+ outDir: 'dist',
240
+ },
167
241
  // Add define block for process polyfills
168
242
  define: {
169
243
  global: 'window',
@@ -182,7 +256,8 @@ export default defineConfig({
182
256
  */
183
257
  export async function generateSkuilderConfig(
184
258
  configPath: string,
185
- config: ProjectConfig
259
+ config: ProjectConfig,
260
+ outputPath?: string
186
261
  ): Promise<void> {
187
262
  const skuilderConfig: SkuilderConfig = {
188
263
  title: config.title,
@@ -194,13 +269,14 @@ export async function generateSkuilderConfig(
194
269
  skuilderConfig.course = config.course;
195
270
  }
196
271
 
197
- // For static data layer with imported courses, use the first course as primary
198
- if (
199
- config.dataLayerType === 'static' &&
200
- config.importCourseIds &&
201
- config.importCourseIds.length > 0
202
- ) {
203
- skuilderConfig.course = config.importCourseIds[0];
272
+ // For static data layer, use imported course ID or generate new one
273
+ if (config.dataLayerType === 'static') {
274
+ if (config.importCourseIds && config.importCourseIds.length > 0) {
275
+ skuilderConfig.course = config.importCourseIds[0];
276
+ } else {
277
+ // Generate UUID for new static courses without imports
278
+ skuilderConfig.course = randomUUID();
279
+ }
204
280
  }
205
281
 
206
282
  if (config.couchdbUrl) {
@@ -212,6 +288,15 @@ export async function generateSkuilderConfig(
212
288
  }
213
289
 
214
290
  await fs.writeFile(configPath, JSON.stringify(skuilderConfig, null, 2));
291
+
292
+ // For static data layer without imports, create empty course structure
293
+ if (
294
+ config.dataLayerType === 'static' &&
295
+ (!config.importCourseIds || config.importCourseIds.length === 0) &&
296
+ outputPath
297
+ ) {
298
+ await createEmptyCourseStructure(outputPath, skuilderConfig.course!, config.title);
299
+ }
215
300
  }
216
301
 
217
302
  /**
@@ -250,6 +335,75 @@ export async function transformTsConfig(tsconfigPath: string): Promise<void> {
250
335
  await fs.writeFile(tsconfigPath, JSON.stringify(tsconfig, null, 2));
251
336
  }
252
337
 
338
+ /**
339
+ * Create empty course structure for new static courses
340
+ */
341
+ async function createEmptyCourseStructure(
342
+ projectPath: string,
343
+ courseId: string,
344
+ title: string
345
+ ): Promise<void> {
346
+ const staticCoursesPath = path.join(projectPath, 'public', 'static-courses');
347
+ const coursePath = path.join(staticCoursesPath, courseId);
348
+
349
+ // Create directory structure
350
+ await fs.mkdir(coursePath, { recursive: true });
351
+ await fs.mkdir(path.join(coursePath, 'chunks'), { recursive: true });
352
+ await fs.mkdir(path.join(coursePath, 'indices'), { recursive: true });
353
+
354
+ // Create minimal CourseConfig
355
+ const courseConfig: CourseConfig = {
356
+ courseID: courseId,
357
+ name: title,
358
+ description: '',
359
+ public: false,
360
+ deleted: false,
361
+ creator: 'system',
362
+ admins: [],
363
+ moderators: [],
364
+ dataShapes: [],
365
+ questionTypes: [],
366
+ };
367
+
368
+ // Create manifest.json with proper structure
369
+ const manifest = {
370
+ version: '1.0.0',
371
+ courseId,
372
+ courseName: title,
373
+ courseConfig,
374
+ lastUpdated: new Date().toISOString(),
375
+ documentCount: 0,
376
+ chunks: [],
377
+ indices: [],
378
+ designDocs: [],
379
+ };
380
+
381
+ await fs.writeFile(path.join(coursePath, 'manifest.json'), JSON.stringify(manifest, null, 2));
382
+
383
+ // Create empty tags index
384
+ await fs.writeFile(
385
+ path.join(coursePath, 'indices', 'tags.json'),
386
+ JSON.stringify({ tags: [] }, null, 2)
387
+ );
388
+
389
+ // Create CourseConfig chunk
390
+ await fs.writeFile(
391
+ path.join(coursePath, 'chunks', 'CourseConfig.json'),
392
+ JSON.stringify(
393
+ [
394
+ {
395
+ _id: 'CourseConfig',
396
+ ...courseConfig,
397
+ },
398
+ ],
399
+ null,
400
+ 2
401
+ )
402
+ );
403
+
404
+ console.log(chalk.green(`✅ Created empty course structure for ${courseId}`));
405
+ }
406
+
253
407
  /**
254
408
  * Generate .gitignore file for the project
255
409
  */
@@ -368,6 +522,7 @@ Thumbs.db
368
522
 
369
523
  # Skuilder specific
370
524
  /src/data/local-*.json
525
+ .skuilder/
371
526
  `;
372
527
 
373
528
  await fs.writeFile(gitignorePath, gitignoreContent);
@@ -413,8 +568,34 @@ npm run dev
413
568
  Build for production:
414
569
  \`\`\`bash
415
570
  npm run build
571
+ \`\`\`${
572
+ config.dataLayerType === 'static'
573
+ ? `
574
+
575
+ ## Studio Mode (Content Editing)
576
+
577
+ This project supports **Studio Mode** - a content editing web interface for modifying course data:
578
+
579
+ \`\`\`bash
580
+ npm run studio
416
581
  \`\`\`
417
582
 
583
+ Studio mode provides:
584
+ - **Visual Course Editor**: Interactive interface for editing course content
585
+ - **Live Preview**: See changes immediately in the browser
586
+ - **Hot Reload**: Changes are saved automatically to your course files
587
+ - **No Setup Required**: Built into the Skuilder CLI - just run the command
588
+
589
+ When you run \`npm run studio\`, it will:
590
+ 1. Start a local CouchDB instance for temporary editing
591
+ 2. Load your course data from \`public/static-courses/\`
592
+ 3. Launch the studio interface at http://localhost:7174
593
+ 4. Save changes back to your static course files when you flush
594
+
595
+ **Important**: Studio mode **overwrites** existing static data source files in \`public/static-courses/\`. Make sure to commit or backup your course data before making major edits.`
596
+ : ''
597
+ }
598
+
418
599
  ## Configuration
419
600
 
420
601
  Course configuration is managed in \`skuilder.config.json\`. You can modify:
@@ -482,6 +663,36 @@ Visit the [Skuilder documentation](https://github.com/NiloCK/vue-skuilder) for m
482
663
  await fs.writeFile(readmePath, readme);
483
664
  }
484
665
 
666
+ /**
667
+ * Create .skuilder directory structure for studio builds cache
668
+ */
669
+ async function createSkuilderDirectory(projectPath: string): Promise<void> {
670
+ const skuilderPath = path.join(projectPath, '.skuilder');
671
+ const templatesPath = path.join(__dirname, '..', '..', 'templates', '.skuilder');
672
+
673
+ // Create .skuilder directory
674
+ await fs.mkdir(skuilderPath, { recursive: true });
675
+
676
+ // Create studio-builds subdirectory
677
+ await fs.mkdir(path.join(skuilderPath, 'studio-builds'), { recursive: true });
678
+
679
+ // Copy README template if it exists
680
+ if (existsSync(templatesPath)) {
681
+ await copyDirectory(templatesPath, skuilderPath);
682
+ } else {
683
+ // Fallback: create basic README
684
+ const readmeContent = `# .skuilder Directory
685
+
686
+ **⚠️ WARNING: GENERATED CONTENT - DO NOT EDIT MANUALLY ⚠️**
687
+
688
+ This directory contains files generated by the Skuilder CLI tools.
689
+
690
+ Generated by @vue-skuilder/cli
691
+ `;
692
+ await fs.writeFile(path.join(skuilderPath, 'README.md'), readmeContent);
693
+ }
694
+ }
695
+
485
696
  /**
486
697
  * Copy and transform the standalone-ui template to create a new project
487
698
  */
@@ -514,7 +725,7 @@ export async function processTemplate(
514
725
 
515
726
  console.log(chalk.blue('🔧 Generating configuration...'));
516
727
  const configPath = path.join(projectPath, 'skuilder.config.json');
517
- await generateSkuilderConfig(configPath, config);
728
+ await generateSkuilderConfig(configPath, config, projectPath);
518
729
 
519
730
  console.log(chalk.blue('📝 Creating README...'));
520
731
  const readmePath = path.join(projectPath, 'README.md');
@@ -524,5 +735,8 @@ export async function processTemplate(
524
735
  const gitignorePath = path.join(projectPath, '.gitignore');
525
736
  await generateGitignore(gitignorePath);
526
737
 
738
+ console.log(chalk.blue('📁 Creating .skuilder directory structure...'));
739
+ await createSkuilderDirectory(projectPath);
740
+
527
741
  console.log(chalk.green('✅ Template processing complete!'));
528
742
  }
@@ -0,0 +1,29 @@
1
+ # .skuilder Directory
2
+
3
+ **⚠️ WARNING: GENERATED CONTENT - DO NOT EDIT MANUALLY ⚠️**
4
+
5
+ This directory contains files generated by the Skuilder CLI tools and is **NOT** intended for manual editing except under experimental circumstances.
6
+
7
+ ## Contents
8
+
9
+ - `studio-builds/` - Cached builds of studio-ui with local question types
10
+ - Each subdirectory represents a different build based on your `src/questions/` content
11
+ - Builds are automatically managed by `skuilder studio` command
12
+ - Safe to delete - will be regenerated as needed
13
+
14
+ ## Purpose
15
+
16
+ The Skuilder CLI uses this directory to cache studio-ui builds that include your local question types. This enables:
17
+
18
+ - Fast startup of `skuilder studio` when your questions haven't changed
19
+ - Isolation between different question type configurations
20
+ - Automatic rebuild when your local questions are modified
21
+
22
+ ## Maintenance
23
+
24
+ - **Safe to delete**: The entire `.skuilder` directory can be deleted safely
25
+ - **Automatic cleanup**: Old builds may be cleaned up automatically
26
+ - **Version control**: This directory should be ignored by git (check your .gitignore)
27
+
28
+ ---
29
+ Generated by @vue-skuilder/cli
@@ -1,2 +0,0 @@
1
- import{g as _,a as w,C as g,_ as k}from"./index-DHMXQY3-.js";import{a as C}from"./index-BnAv1C72.js";import{h as x,d as u,x as y,X as m,ai as s,aj as r,m as a,ak as t,ao as d,Q as c,V as B,u as v}from"./vue-DZcMATiC.js";import"./vuetify-qg7mRxy_.js";const V={class:"browse-view"},E={key:0,class:"text-center pa-4"},N={key:1,class:"text-center pa-4"},b={key:2},z={key:3,class:"text-center pa-4"},I=x({__name:"BrowseView",setup(L){const n=u(!0),i=u(null),l=u(null);return y(async()=>{try{const o=_();if(!o)throw new Error(w());l.value=o.database.name,n.value=!1}catch(o){console.error("Browse view initialization error:",o),i.value=o instanceof Error?o.message:"Unknown error",n.value=!1}}),(o,e)=>{const f=m("v-progress-circular"),p=m("v-icon");return r(),s("div",V,[n.value?(r(),s("div",E,[a(f,{indeterminate:""}),e[0]||(e[0]=t("p",{class:"mt-2"},"Loading course...",-1))])):i.value?(r(),s("div",N,[a(p,{color:"error",size:"48"},{default:d(()=>e[1]||(e[1]=[c("mdi-alert-circle")])),_:1}),e[2]||(e[2]=t("h2",{class:"mt-2"},"Browse Error",-1)),t("p",null,B(i.value),1)])):l.value?(r(),s("div",b,[a(v(g),{"course-id":l.value,"view-lookup-function":v(C).getView,"edit-mode":"full"},{actions:d(()=>e[3]||(e[3]=[c(" ")])),_:1},8,["course-id","view-lookup-function"])])):(r(),s("div",z,[a(p,{size:"48"},{default:d(()=>e[4]||(e[4]=[c("mdi-school")])),_:1}),e[5]||(e[5]=t("h2",{class:"mt-2"},"No Course Loaded",-1)),e[6]||(e[6]=t("p",null,"Please load a course to start browsing.",-1))]))])}}}),P=k(I,[["__scopeId","data-v-2c3c618e"]]);export{P as default};
2
- //# sourceMappingURL=BrowseView-BJbixGOU.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"BrowseView-BJbixGOU.js","sources":["../../src/views/BrowseView.vue"],"sourcesContent":["<template>\n <div class=\"browse-view\">\n <div v-if=\"loading\" class=\"text-center pa-4\">\n <v-progress-circular indeterminate />\n <p class=\"mt-2\">Loading course...</p>\n </div>\n\n <div v-else-if=\"error\" class=\"text-center pa-4\">\n <v-icon color=\"error\" size=\"48\">mdi-alert-circle</v-icon>\n <h2 class=\"mt-2\">Browse Error</h2>\n <p>{{ error }}</p>\n </div>\n\n <div v-else-if=\"courseId\">\n <course-information :course-id=\"courseId\" :view-lookup-function=\"allCourses.getView\" :edit-mode=\"'full'\">\n <template #actions>&nbsp;</template>\n </course-information>\n </div>\n\n <div v-else class=\"text-center pa-4\">\n <v-icon size=\"48\">mdi-school</v-icon>\n <h2 class=\"mt-2\">No Course Loaded</h2>\n <p>Please load a course to start browsing.</p>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, onMounted } from 'vue';\nimport { CourseInformation } from '@vue-skuilder/common-ui';\nimport { allCourses } from '@vue-skuilder/courses';\nimport { getStudioConfig, getConfigErrorMessage } from '../config/development';\n\n// Browse view state\nconst loading = ref(true);\nconst error = ref<string | null>(null);\nconst courseId = ref<string | null>(null);\n\n// Initialize browse view\nonMounted(async () => {\n try {\n // Get studio configuration (CLI-injected or environment variables)\n const studioConfig = getStudioConfig();\n\n if (!studioConfig) {\n throw new Error(getConfigErrorMessage());\n }\n\n courseId.value = studioConfig.database.name;\n loading.value = false;\n } catch (err) {\n console.error('Browse view initialization error:', err);\n error.value = err instanceof Error ? err.message : 'Unknown error';\n loading.value = false;\n }\n});\n</script>\n\n<style scoped>\n.browse-view {\n height: 100%;\n}\n\n.studio-header {\n padding: 16px 0;\n}\n\n.studio-header h1 {\n color: rgb(var(--v-theme-primary));\n font-weight: 500;\n}\n\n.studio-header p {\n color: rgb(var(--v-theme-on-surface-variant));\n margin: 0;\n}\n</style>\n"],"names":["loading","ref","error","courseId","onMounted","studioConfig","getStudioConfig","getConfigErrorMessage","err"],"mappings":"6aAkCM,MAAAA,EAAUC,EAAI,EAAI,EAClBC,EAAQD,EAAmB,IAAI,EAC/BE,EAAWF,EAAmB,IAAI,EAGxC,OAAAG,EAAU,SAAY,CAChB,GAAA,CAEF,MAAMC,EAAeC,EAAgB,EAErC,GAAI,CAACD,EACG,MAAA,IAAI,MAAME,GAAuB,EAGhCJ,EAAA,MAAQE,EAAa,SAAS,KACvCL,EAAQ,MAAQ,SACTQ,EAAK,CACJ,QAAA,MAAM,oCAAqCA,CAAG,EACtDN,EAAM,MAAQM,aAAe,MAAQA,EAAI,QAAU,gBACnDR,EAAQ,MAAQ,EAAA,CAClB,CACD"}
@@ -1 +0,0 @@
1
- .browse-view[data-v-2c3c618e]{height:100%}.studio-header[data-v-2c3c618e]{padding:16px 0}.studio-header h1[data-v-2c3c618e]{color:rgb(var(--v-theme-primary));font-weight:500}.studio-header p[data-v-2c3c618e]{color:rgb(var(--v-theme-on-surface-variant));margin:0}
@@ -1,2 +0,0 @@
1
- import{B as x}from"./edit-ui.es-DiUxqbgF.js";import{a as B}from"./index-BnAv1C72.js";import{g as y,a as I,b as V,_ as b}from"./index-DHMXQY3-.js";import{h as D,d as i,x as E,X as r,ai as s,aj as a,m as t,ao as n,Q as m,ak as _,V as L,u as v}from"./vue-DZcMATiC.js";import"./vuetify-qg7mRxy_.js";const N={class:"bulk-import-view"},z={key:0,class:"text-center pa-4"},M={key:1,class:"text-center pa-4"},S={class:"mt-2 text-error"},h={key:2},j={key:3},Q=D({__name:"BulkImportView",setup(T){const c=i(!0),u=i(null),d=i(null),p=i(null);E(async()=>{try{const o=y();if(!o)throw new Error(I());d.value=o.database.name;const l=V().getCourseDB(d.value);p.value=await l.getCourseConfig(),c.value=!1}catch(o){console.error("Bulk import initialization error:",o),u.value=o instanceof Error?o.message:"Unknown error",c.value=!1}});const f=o=>{console.log("Import completed:",o)};return(o,e)=>{const l=r("v-icon"),g=r("v-card-title"),k=r("v-progress-circular"),C=r("v-card-text"),w=r("v-card");return a(),s("div",N,[t(w,null,{default:n(()=>[t(g,null,{default:n(()=>[t(l,{left:""},{default:n(()=>e[0]||(e[0]=[m("mdi-file-import")])),_:1}),e[1]||(e[1]=m(" Bulk Import Cards "))]),_:1}),t(C,null,{default:n(()=>[c.value?(a(),s("div",z,[t(k,{indeterminate:""}),e[2]||(e[2]=_("p",{class:"mt-2"},"Loading bulk import tool...",-1))])):u.value?(a(),s("div",M,[t(l,{color:"error",size:"24"},{default:n(()=>e[3]||(e[3]=[m("mdi-alert-circle")])),_:1}),_("p",S,L(u.value),1)])):d.value&&p.value?(a(),s("div",h,[t(v(x),{"course-cfg":p.value,"view-lookup-function":v(B).getView,onImportCompleted:f},null,8,["course-cfg","view-lookup-function"])])):(a(),s("div",j,e[4]||(e[4]=[_("p",{class:"text-center"},"No course loaded",-1)])))]),_:1})]),_:1})])}}}),F=b(Q,[["__scopeId","data-v-ba3b5194"]]);export{F as default};
2
- //# sourceMappingURL=BulkImportView-DB6DYDJU.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"BulkImportView-DB6DYDJU.js","sources":["../../src/views/BulkImportView.vue"],"sourcesContent":["<template>\n <div class=\"bulk-import-view\">\n <v-card>\n <v-card-title>\n <v-icon left>mdi-file-import</v-icon>\n Bulk Import Cards\n </v-card-title>\n \n <v-card-text>\n <div v-if=\"loading\" class=\"text-center pa-4\">\n <v-progress-circular indeterminate />\n <p class=\"mt-2\">Loading bulk import tool...</p>\n </div>\n\n <div v-else-if=\"error\" class=\"text-center pa-4\">\n <v-icon color=\"error\" size=\"24\">mdi-alert-circle</v-icon>\n <p class=\"mt-2 text-error\">{{ error }}</p>\n </div>\n\n <div v-else-if=\"courseId && courseConfig\">\n <!-- Bulk Import View from edit-ui package -->\n <bulk-import-view \n :course-cfg=\"courseConfig\"\n :view-lookup-function=\"allCourses.getView\"\n @import-completed=\"onImportCompleted\"\n />\n </div>\n\n <div v-else>\n <p class=\"text-center\">No course loaded</p>\n </div>\n </v-card-text>\n </v-card>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, onMounted } from 'vue';\nimport { BulkImportView } from '@vue-skuilder/edit-ui';\nimport { allCourses } from '@vue-skuilder/courses';\nimport { getStudioConfig, getConfigErrorMessage } from '../config/development';\nimport { getDataLayer } from '@vue-skuilder/db';\nimport type { CourseConfig } from '@vue-skuilder/common';\n\n// Bulk import state\nconst loading = ref(true);\nconst error = ref<string | null>(null);\nconst courseId = ref<string | null>(null);\nconst courseConfig = ref<CourseConfig | null>(null);\n\n// Initialize bulk import view\nonMounted(async () => {\n try {\n // Get studio configuration (CLI-injected or environment variables)\n const studioConfig = getStudioConfig();\n\n if (!studioConfig) {\n throw new Error(getConfigErrorMessage());\n }\n\n courseId.value = studioConfig.database.name;\n\n // Load course configuration\n const dataLayer = getDataLayer();\n const courseDB = dataLayer.getCourseDB(courseId.value);\n courseConfig.value = await courseDB.getCourseConfig();\n\n loading.value = false;\n } catch (err) {\n console.error('Bulk import initialization error:', err);\n error.value = err instanceof Error ? err.message : 'Unknown error';\n loading.value = false;\n }\n});\n\n// Handle import completion\nconst onImportCompleted = (result: any) => {\n console.log('Import completed:', result);\n // Could add success notification here\n};\n</script>\n\n<style scoped>\n.bulk-import-view {\n max-width: 1200px;\n margin: 0 auto;\n padding: 16px;\n}\n</style>"],"names":["loading","ref","error","courseId","courseConfig","onMounted","studioConfig","getStudioConfig","getConfigErrorMessage","courseDB","getDataLayer","err","onImportCompleted","result"],"mappings":"seA6CM,MAAAA,EAAUC,EAAI,EAAI,EAClBC,EAAQD,EAAmB,IAAI,EAC/BE,EAAWF,EAAmB,IAAI,EAClCG,EAAeH,EAAyB,IAAI,EAGlDI,EAAU,SAAY,CAChB,GAAA,CAEF,MAAMC,EAAeC,EAAgB,EAErC,GAAI,CAACD,EACG,MAAA,IAAI,MAAME,GAAuB,EAGhCL,EAAA,MAAQG,EAAa,SAAS,KAIvC,MAAMG,EADYC,EAAa,EACJ,YAAYP,EAAS,KAAK,EACxCC,EAAA,MAAQ,MAAMK,EAAS,gBAAgB,EAEpDT,EAAQ,MAAQ,SACTW,EAAK,CACJ,QAAA,MAAM,oCAAqCA,CAAG,EACtDT,EAAM,MAAQS,aAAe,MAAQA,EAAI,QAAU,gBACnDX,EAAQ,MAAQ,EAAA,CAClB,CACD,EAGK,MAAAY,EAAqBC,GAAgB,CACjC,QAAA,IAAI,oBAAqBA,CAAM,CAEzC"}
@@ -1 +0,0 @@
1
- .bulk-import-view[data-v-ba3b5194]{max-width:1200px;margin:0 auto;padding:16px}
@@ -1,2 +0,0 @@
1
- import{C as _}from"./edit-ui.es-DiUxqbgF.js";import{a as g}from"./index-BnAv1C72.js";import{g as C,a as k,_ as w}from"./index-DHMXQY3-.js";import{h as x,d as u,x as E,X as c,ai as r,aj as s,m as a,ak as t,ao as p,Q as m,V as y,u as v}from"./vue-DZcMATiC.js";import"./vuetify-qg7mRxy_.js";const V={class:"course-editor-view"},N={key:0,class:"text-center pa-4"},z={key:1,class:"text-center pa-4"},B={key:2},I={key:3,class:"text-center pa-4"},L=x({__name:"CourseEditorView",setup(M){const n=u(!0),i=u(null),l=u(null);return E(async()=>{try{const o=C();if(!o)throw new Error(k());l.value=o.database.name,n.value=!1}catch(o){console.error("Course editor initialization error:",o),i.value=o instanceof Error?o.message:"Unknown error",n.value=!1}}),(o,e)=>{const f=c("v-progress-circular"),d=c("v-icon");return s(),r("div",V,[n.value?(s(),r("div",N,[a(f,{indeterminate:""}),e[0]||(e[0]=t("p",{class:"mt-2"},"Loading course editor...",-1))])):i.value?(s(),r("div",z,[a(d,{color:"error",size:"48"},{default:p(()=>e[1]||(e[1]=[m("mdi-alert-circle")])),_:1}),e[2]||(e[2]=t("h2",{class:"mt-2"},"Editor Error",-1)),t("p",null,y(i.value),1)])):l.value?(s(),r("div",B,[a(v(_),{course:l.value,"view-lookup-function":v(g).getView},null,8,["course","view-lookup-function"])])):(s(),r("div",I,[a(d,{size:"48"},{default:p(()=>e[3]||(e[3]=[m("mdi-school")])),_:1}),e[4]||(e[4]=t("h2",{class:"mt-2"},"No Course Loaded",-1)),e[5]||(e[5]=t("p",null,"Please load a course to start editing.",-1))]))])}}}),P=w(L,[["__scopeId","data-v-c575e95c"]]);export{P as default};
2
- //# sourceMappingURL=CourseEditorView-BIlhlhw1.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"CourseEditorView-BIlhlhw1.js","sources":["../../src/views/CourseEditorView.vue"],"sourcesContent":["<template>\n <div class=\"course-editor-view\">\n <div v-if=\"loading\" class=\"text-center pa-4\">\n <v-progress-circular indeterminate />\n <p class=\"mt-2\">Loading course editor...</p>\n </div>\n\n <div v-else-if=\"error\" class=\"text-center pa-4\">\n <v-icon color=\"error\" size=\"48\">mdi-alert-circle</v-icon>\n <h2 class=\"mt-2\">Editor Error</h2>\n <p>{{ error }}</p>\n </div>\n\n <div v-else-if=\"courseId\">\n <!-- Course Editor from edit-ui package -->\n <course-editor :course=\"courseId\" :view-lookup-function=\"allCourses.getView\" />\n </div>\n\n <div v-else class=\"text-center pa-4\">\n <v-icon size=\"48\">mdi-school</v-icon>\n <h2 class=\"mt-2\">No Course Loaded</h2>\n <p>Please load a course to start editing.</p>\n </div>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, onMounted } from 'vue';\nimport { CourseEditor } from '@vue-skuilder/edit-ui';\nimport { allCourses } from '@vue-skuilder/courses';\nimport { getStudioConfig, getConfigErrorMessage } from '../config/development';\n\n// Course editor state\nconst loading = ref(true);\nconst error = ref<string | null>(null);\nconst courseId = ref<string | null>(null);\n\n// Initialize course editor\nonMounted(async () => {\n try {\n // Get studio configuration (CLI-injected or environment variables)\n const studioConfig = getStudioConfig();\n\n if (!studioConfig) {\n throw new Error(getConfigErrorMessage());\n }\n\n courseId.value = studioConfig.database.name;\n loading.value = false;\n } catch (err) {\n console.error('Course editor initialization error:', err);\n error.value = err instanceof Error ? err.message : 'Unknown error';\n loading.value = false;\n }\n});\n</script>\n\n<style scoped>\n.course-editor-view {\n height: 100%;\n}\n</style>\n"],"names":["loading","ref","error","courseId","onMounted","studioConfig","getStudioConfig","getConfigErrorMessage","err"],"mappings":"geAiCM,MAAAA,EAAUC,EAAI,EAAI,EAClBC,EAAQD,EAAmB,IAAI,EAC/BE,EAAWF,EAAmB,IAAI,EAGxC,OAAAG,EAAU,SAAY,CAChB,GAAA,CAEF,MAAMC,EAAeC,EAAgB,EAErC,GAAI,CAACD,EACG,MAAA,IAAI,MAAME,GAAuB,EAGhCJ,EAAA,MAAQE,EAAa,SAAS,KACvCL,EAAQ,MAAQ,SACTQ,EAAK,CACJ,QAAA,MAAM,sCAAuCA,CAAG,EACxDN,EAAM,MAAQM,aAAe,MAAQA,EAAI,QAAU,gBACnDR,EAAQ,MAAQ,EAAA,CAClB,CACD"}
@@ -1 +0,0 @@
1
- .course-editor-view[data-v-c575e95c]{height:100%}
@@ -1 +0,0 @@
1
- .create-card-view[data-v-6c9210e9]{max-width:1200px;margin:0 auto;padding:16px}
@@ -1,2 +0,0 @@
1
- import{D as I}from"./edit-ui.es-DiUxqbgF.js";import{a as x}from"./index-BnAv1C72.js";import{g as L,a as z,b as q,_ as M}from"./index-DHMXQY3-.js";import{h as T,d as c,c as w,x as U,X as r,ai as n,aj as t,m as s,ao as l,Q as v,ak as m,V as j,an as h,am as V,u as D}from"./vue-DZcMATiC.js";import"./vuetify-qg7mRxy_.js";const F={class:"create-card-view"},Q={key:0,class:"text-center pa-4"},X={key:1,class:"text-center pa-4"},A={class:"mt-2 text-error"},G={key:2},H={key:2,class:"text-center pa-4"},J={key:3},K=T({__name:"CreateCardView",setup(O){const f=c(!0),_=c(null),i=c(null),u=c(null),g=c(0),d=w(()=>u.value?.dataShapes?u.value.dataShapes:[]),k=w(()=>{const a=d.value;if(a.length===0)return null;const e=a[g.value]?.name;if(!e)return null;for(const o of x.courses)for(const C of o.questions)for(const p of C.dataShapes)if(p.name===e.split(".").pop())return p;return null});U(async()=>{try{const a=L();if(!a)throw new Error(z());i.value=a.database.name;const o=q().getCourseDB(i.value);u.value=await o.getCourseConfig(),f.value=!1}catch(a){console.error("Create card initialization error:",a),_.value=a instanceof Error?a.message:"Unknown error",f.value=!1}});const S=a=>{console.log("Card created:",a)};return(a,e)=>{const o=r("v-icon"),C=r("v-card-title"),p=r("v-progress-circular"),N=r("v-select"),b=r("v-card-text"),B=r("v-card");return t(),n("div",F,[s(B,null,{default:l(()=>[s(C,null,{default:l(()=>[s(o,{left:""},{default:l(()=>e[1]||(e[1]=[v("mdi-card-plus")])),_:1}),e[2]||(e[2]=v(" Create New Card "))]),_:1}),s(b,null,{default:l(()=>[f.value?(t(),n("div",Q,[s(p,{indeterminate:""}),e[3]||(e[3]=m("p",{class:"mt-2"},"Loading card creation form...",-1))])):_.value?(t(),n("div",X,[s(o,{color:"error",size:"24"},{default:l(()=>e[4]||(e[4]=[v("mdi-alert-circle")])),_:1}),m("p",A,j(_.value),1)])):i.value&&u.value?(t(),n("div",G,[d.value.length>1?(t(),h(N,{key:0,modelValue:g.value,"onUpdate:modelValue":e[0]||(e[0]=y=>g.value=y),items:d.value.map((y,E)=>({title:y.name.replace(/^.*\./,""),value:E})),label:"Card Type",class:"mb-4"},null,8,["modelValue","items"])):V("",!0),k.value?(t(),h(D(I),{key:1,"course-id":i.value,"course-cfg":u.value,"data-shape":k.value,"view-lookup-function":D(x).getView,onCardCreated:S},null,8,["course-id","course-cfg","data-shape","view-lookup-function"])):d.value.length===0?(t(),n("div",H,[s(o,{color:"warning",size:"24"},{default:l(()=>e[5]||(e[5]=[v("mdi-alert")])),_:1}),e[6]||(e[6]=m("p",{class:"mt-2"},"No card types available in this course",-1))])):V("",!0)])):(t(),n("div",J,e[7]||(e[7]=[m("p",{class:"text-center"},"No course loaded",-1)])))]),_:1})]),_:1})])}}}),$=M(K,[["__scopeId","data-v-6c9210e9"]]);export{$ as default};
2
- //# sourceMappingURL=CreateCardView-DPjPvzzt.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"CreateCardView-DPjPvzzt.js","sources":["../../src/views/CreateCardView.vue"],"sourcesContent":["<template>\n <div class=\"create-card-view\">\n <v-card>\n <v-card-title>\n <v-icon left>mdi-card-plus</v-icon>\n Create New Card\n </v-card-title>\n\n <v-card-text>\n <div v-if=\"loading\" class=\"text-center pa-4\">\n <v-progress-circular indeterminate />\n <p class=\"mt-2\">Loading card creation form...</p>\n </div>\n\n <div v-else-if=\"error\" class=\"text-center pa-4\">\n <v-icon color=\"error\" size=\"24\">mdi-alert-circle</v-icon>\n <p class=\"mt-2 text-error\">{{ error }}</p>\n </div>\n\n <div v-else-if=\"courseId && courseConfig\">\n <!-- Card type selector -->\n <v-select\n v-if=\"availableDataShapes.length > 1\"\n v-model=\"selectedDataShapeIndex\"\n :items=\"\n availableDataShapes.map((shape, index) => ({\n title: shape.name.replace(/^.*\\./, ''),\n value: index,\n }))\n \"\n label=\"Card Type\"\n class=\"mb-4\"\n />\n\n <!-- Data Input Form from edit-ui package -->\n <data-input-form\n v-if=\"selectedDataShape\"\n :course-id=\"courseId\"\n :course-cfg=\"courseConfig\"\n :data-shape=\"selectedDataShape\"\n :view-lookup-function=\"allCourses.getView\"\n @card-created=\"onCardCreated\"\n />\n\n <div v-else-if=\"availableDataShapes.length === 0\" class=\"text-center pa-4\">\n <v-icon color=\"warning\" size=\"24\">mdi-alert</v-icon>\n <p class=\"mt-2\">No card types available in this course</p>\n </div>\n </div>\n\n <div v-else>\n <p class=\"text-center\">No course loaded</p>\n </div>\n </v-card-text>\n </v-card>\n </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { ref, onMounted, computed } from 'vue';\nimport { DataInputForm } from '@vue-skuilder/edit-ui';\nimport { allCourses } from '@vue-skuilder/courses';\nimport { getStudioConfig, getConfigErrorMessage } from '../config/development';\nimport { getDataLayer } from '@vue-skuilder/db';\nimport type { CourseConfig, DataShape } from '@vue-skuilder/common';\n\n// Create card state\nconst loading = ref(true);\nconst error = ref<string | null>(null);\nconst courseId = ref<string | null>(null);\nconst courseConfig = ref<CourseConfig | null>(null);\nconst selectedDataShapeIndex = ref<number>(0);\n\n// Get available data shapes\nconst availableDataShapes = computed(() => {\n if (!courseConfig.value?.dataShapes) return [];\n return courseConfig.value.dataShapes;\n});\n\n// Get currently selected data shape\nconst selectedDataShape = computed((): DataShape | null => {\n const shapes = availableDataShapes.value;\n if (shapes.length === 0) return null;\n\n // Find the corresponding DataShape from allCourses\n const shapeName = shapes[selectedDataShapeIndex.value]?.name;\n if (!shapeName) return null;\n\n // Search through all courses to find the DataShape\n for (const course of allCourses.courses) {\n for (const question of course.questions) {\n for (const dataShape of question.dataShapes) {\n if (dataShape.name === shapeName.split('.').pop()) {\n return dataShape;\n }\n }\n }\n }\n return null;\n});\n\n// Initialize create card view\nonMounted(async () => {\n try {\n // Get studio configuration (CLI-injected or environment variables)\n const studioConfig = getStudioConfig();\n\n if (!studioConfig) {\n throw new Error(getConfigErrorMessage());\n }\n\n courseId.value = studioConfig.database.name;\n\n // Load course configuration\n const dataLayer = getDataLayer();\n const courseDB = dataLayer.getCourseDB(courseId.value);\n courseConfig.value = await courseDB.getCourseConfig();\n\n loading.value = false;\n } catch (err) {\n console.error('Create card initialization error:', err);\n error.value = err instanceof Error ? err.message : 'Unknown error';\n loading.value = false;\n }\n});\n\n// Handle card creation\nconst onCardCreated = (cardData: any) => {\n console.log('Card created:', cardData);\n // Could add success notification or redirect here\n};\n</script>\n\n<style scoped>\n.create-card-view {\n max-width: 1200px;\n margin: 0 auto;\n padding: 16px;\n}\n</style>\n"],"names":["loading","ref","error","courseId","courseConfig","selectedDataShapeIndex","availableDataShapes","computed","selectedDataShape","shapes","shapeName","course","allCourses","question","dataShape","onMounted","studioConfig","getStudioConfig","getConfigErrorMessage","courseDB","getDataLayer","err","onCardCreated","cardData"],"mappings":"giBAmEM,MAAAA,EAAUC,EAAI,EAAI,EAClBC,EAAQD,EAAmB,IAAI,EAC/BE,EAAWF,EAAmB,IAAI,EAClCG,EAAeH,EAAyB,IAAI,EAC5CI,EAAyBJ,EAAY,CAAC,EAGtCK,EAAsBC,EAAS,IAC9BH,EAAa,OAAO,WAClBA,EAAa,MAAM,WADkB,CAAC,CAE9C,EAGKI,EAAoBD,EAAS,IAAwB,CACzD,MAAME,EAASH,EAAoB,MAC/B,GAAAG,EAAO,SAAW,EAAU,OAAA,KAGhC,MAAMC,EAAYD,EAAOJ,EAAuB,KAAK,GAAG,KACpD,GAAA,CAACK,EAAkB,OAAA,KAGZ,UAAAC,KAAUC,EAAW,QACnB,UAAAC,KAAYF,EAAO,UACjB,UAAAG,KAAaD,EAAS,WAC/B,GAAIC,EAAU,OAASJ,EAAU,MAAM,GAAG,EAAE,MACnC,OAAAI,EAKR,OAAA,IAAA,CACR,EAGDC,EAAU,SAAY,CAChB,GAAA,CAEF,MAAMC,EAAeC,EAAgB,EAErC,GAAI,CAACD,EACG,MAAA,IAAI,MAAME,GAAuB,EAGhCf,EAAA,MAAQa,EAAa,SAAS,KAIvC,MAAMG,EADYC,EAAa,EACJ,YAAYjB,EAAS,KAAK,EACxCC,EAAA,MAAQ,MAAMe,EAAS,gBAAgB,EAEpDnB,EAAQ,MAAQ,SACTqB,EAAK,CACJ,QAAA,MAAM,oCAAqCA,CAAG,EACtDnB,EAAM,MAAQmB,aAAe,MAAQA,EAAI,QAAU,gBACnDrB,EAAQ,MAAQ,EAAA,CAClB,CACD,EAGK,MAAAsB,EAAiBC,GAAkB,CAC/B,QAAA,IAAI,gBAAiBA,CAAQ,CAEvC"}