@vue-skuilder/cli 0.1.7 → 0.1.8-0

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 (168) 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 +640 -105
  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 +148 -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/views/BrowseView.vue +82 -0
  90. package/dist/studio-ui-src/views/BulkImportView.vue +89 -0
  91. package/dist/studio-ui-src/views/CourseEditorView.vue +62 -0
  92. package/dist/studio-ui-src/views/CreateCardView.vue +140 -0
  93. package/dist/studio-ui-src/vite.config.base.js +100 -0
  94. package/dist/studio-ui-src/vite.config.ts +26 -0
  95. package/dist/templates/.skuilder/README.md +29 -0
  96. package/dist/types.d.ts +5 -0
  97. package/dist/types.d.ts.map +1 -1
  98. package/dist/types.js.map +1 -1
  99. package/dist/utils/ExpressManager.d.ts +28 -0
  100. package/dist/utils/ExpressManager.d.ts.map +1 -0
  101. package/dist/utils/ExpressManager.js +166 -0
  102. package/dist/utils/ExpressManager.js.map +1 -0
  103. package/dist/utils/NodeFileSystemAdapter.d.ts +6 -0
  104. package/dist/utils/NodeFileSystemAdapter.d.ts.map +1 -1
  105. package/dist/utils/NodeFileSystemAdapter.js +29 -1
  106. package/dist/utils/NodeFileSystemAdapter.js.map +1 -1
  107. package/dist/utils/error-reporting.d.ts +54 -0
  108. package/dist/utils/error-reporting.d.ts.map +1 -0
  109. package/dist/utils/error-reporting.js +143 -0
  110. package/dist/utils/error-reporting.js.map +1 -0
  111. package/dist/utils/pack-courses.d.ts.map +1 -1
  112. package/dist/utils/pack-courses.js +10 -27
  113. package/dist/utils/pack-courses.js.map +1 -1
  114. package/dist/utils/prompts.d.ts.map +1 -1
  115. package/dist/utils/prompts.js +24 -0
  116. package/dist/utils/prompts.js.map +1 -1
  117. package/dist/utils/questions-hash.d.ts +22 -0
  118. package/dist/utils/questions-hash.d.ts.map +1 -0
  119. package/dist/utils/questions-hash.js +96 -0
  120. package/dist/utils/questions-hash.js.map +1 -0
  121. package/dist/utils/template.d.ts +1 -1
  122. package/dist/utils/template.d.ts.map +1 -1
  123. package/dist/utils/template.js +202 -25
  124. package/dist/utils/template.js.map +1 -1
  125. package/eslint.config.mjs +1 -1
  126. package/package.json +30 -11
  127. package/src/cli.ts +3 -1
  128. package/src/commands/init.ts +48 -3
  129. package/src/commands/pack.ts +1 -1
  130. package/src/commands/studio.ts +850 -121
  131. package/src/commands/unpack.ts +1 -1
  132. package/src/types.ts +5 -0
  133. package/src/utils/ExpressManager.ts +210 -0
  134. package/src/utils/NodeFileSystemAdapter.ts +46 -2
  135. package/src/utils/error-reporting.ts +192 -0
  136. package/src/utils/pack-courses.ts +11 -36
  137. package/src/utils/prompts.ts +34 -0
  138. package/src/utils/questions-hash.ts +109 -0
  139. package/src/utils/template.ts +231 -27
  140. package/templates/.skuilder/README.md +29 -0
  141. package/dist/studio-ui-assets/assets/BrowseView-BJbixGOU.js +0 -2
  142. package/dist/studio-ui-assets/assets/BrowseView-BJbixGOU.js.map +0 -1
  143. package/dist/studio-ui-assets/assets/BrowseView-CM4HBO4j.css +0 -1
  144. package/dist/studio-ui-assets/assets/BulkImportView-DB6DYDJU.js +0 -2
  145. package/dist/studio-ui-assets/assets/BulkImportView-DB6DYDJU.js.map +0 -1
  146. package/dist/studio-ui-assets/assets/BulkImportView-g4wQUfPA.css +0 -1
  147. package/dist/studio-ui-assets/assets/CourseEditorView-BIlhlhw1.js +0 -2
  148. package/dist/studio-ui-assets/assets/CourseEditorView-BIlhlhw1.js.map +0 -1
  149. package/dist/studio-ui-assets/assets/CourseEditorView-WuPNLVKp.css +0 -1
  150. package/dist/studio-ui-assets/assets/CreateCardView-CyNOKCkm.css +0 -1
  151. package/dist/studio-ui-assets/assets/CreateCardView-DPjPvzzt.js +0 -2
  152. package/dist/studio-ui-assets/assets/CreateCardView-DPjPvzzt.js.map +0 -1
  153. package/dist/studio-ui-assets/assets/edit-ui.es-DiUxqbgF.js +0 -330
  154. package/dist/studio-ui-assets/assets/edit-ui.es-DiUxqbgF.js.map +0 -1
  155. package/dist/studio-ui-assets/assets/index--zY88pg6.css +0 -14
  156. package/dist/studio-ui-assets/assets/index-BnAv1C72.js +0 -287
  157. package/dist/studio-ui-assets/assets/index-BnAv1C72.js.map +0 -1
  158. package/dist/studio-ui-assets/assets/index-DHMXQY3-.js +0 -192
  159. package/dist/studio-ui-assets/assets/index-DHMXQY3-.js.map +0 -1
  160. package/dist/studio-ui-assets/assets/materialdesignicons-webfont-B7mPwVP_.ttf +0 -0
  161. package/dist/studio-ui-assets/assets/materialdesignicons-webfont-CSr8KVlo.eot +0 -0
  162. package/dist/studio-ui-assets/assets/materialdesignicons-webfont-Dp5v-WZN.woff2 +0 -0
  163. package/dist/studio-ui-assets/assets/materialdesignicons-webfont-PXm3-2wK.woff +0 -0
  164. package/dist/studio-ui-assets/assets/vue-DZcMATiC.js +0 -28
  165. package/dist/studio-ui-assets/assets/vue-DZcMATiC.js.map +0 -1
  166. package/dist/studio-ui-assets/assets/vuetify-qg7mRxy_.js +0 -6
  167. package/dist/studio-ui-assets/assets/vuetify-qg7mRxy_.js.map +0 -1
  168. 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,23 @@ 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 (typeof version === 'string' && (version.startsWith('workspace:') || version.startsWith('file:'))) {
82
+ // Replace workspace and file references with CLI's version
81
83
  packageJson.dependencies[depName] = `^${cliVersion}`;
82
84
  }
83
85
  }
84
86
  }
85
87
 
86
- // Add missing terser devDependency for build minification
88
+ // Add missing devDependencies for build system
87
89
  if (packageJson.devDependencies && !packageJson.devDependencies['terser']) {
88
90
  packageJson.devDependencies['terser'] = '^5.39.0';
89
91
  }
92
+ if (packageJson.devDependencies && !packageJson.devDependencies['vite-plugin-dts']) {
93
+ packageJson.devDependencies['vite-plugin-dts'] = '^4.3.0';
94
+ }
90
95
 
91
96
  // Add CLI as devDependency for all projects
92
97
  if (!packageJson.devDependencies) {
@@ -114,19 +119,34 @@ export async function transformPackageJson(
114
119
  * // [ ] 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
120
  */
116
121
  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';
122
+ // Create dual build config similar to standalone-ui but without workspace dependencies
123
+ const transformedContent = `import { defineConfig } from 'vite';
120
124
  import vue from '@vitejs/plugin-vue';
125
+ import dts from 'vite-plugin-dts';
126
+ import { resolve } from 'path';
121
127
  import { fileURLToPath, URL } from 'node:url';
122
128
 
129
+ // Determine build mode from environment variable
130
+ const buildMode = process.env.BUILD_MODE || 'webapp';
131
+
123
132
  export default defineConfig({
124
- plugins: [vue()],
133
+ plugins: [
134
+ vue(),
135
+ // Only include dts plugin for library builds
136
+ ...(buildMode === 'library'
137
+ ? [dts({
138
+ insertTypesEntry: true,
139
+ include: ['src/questions/**/*.ts', 'src/questions/**/*.vue'],
140
+ exclude: ['**/*.spec.ts', '**/*.test.ts'],
141
+ outDir: 'dist-lib',
142
+ })]
143
+ : []
144
+ )
145
+ ],
125
146
  resolve: {
126
147
  alias: {
127
148
  // Alias for internal src paths
128
149
  '@': fileURLToPath(new URL('./src', import.meta.url)),
129
-
130
150
  // Add events alias if needed (often required by dependencies)
131
151
  events: 'events',
132
152
  },
@@ -145,8 +165,8 @@ export default defineConfig({
145
165
  },
146
166
  // --- Dependencies optimization ---
147
167
  optimizeDeps: {
148
- // Help Vite pre-bundle dependencies from published packages
149
168
  include: [
169
+ 'events',
150
170
  '@vue-skuilder/common-ui',
151
171
  '@vue-skuilder/db',
152
172
  '@vue-skuilder/common',
@@ -156,14 +176,65 @@ export default defineConfig({
156
176
  server: {
157
177
  port: 5173, // Use standard Vite port for standalone projects
158
178
  },
159
- build: {
160
- sourcemap: true,
161
- target: 'es2020',
162
- minify: 'terser',
163
- terserOptions: {
164
- keep_classnames: true,
165
- },
166
- },
179
+ build: buildMode === 'library'
180
+ ? {
181
+ // Library build configuration
182
+ sourcemap: true,
183
+ target: 'es2020',
184
+ minify: 'terser',
185
+ terserOptions: {
186
+ keep_classnames: true,
187
+ },
188
+ lib: {
189
+ entry: resolve(__dirname, 'src/questions/index.ts'),
190
+ name: 'VueSkuilderStandaloneQuestions',
191
+ fileName: (format) => \`questions.\${format === 'es' ? 'mjs' : 'cjs.js'}\`,
192
+ },
193
+ rollupOptions: {
194
+ // External packages that shouldn't be bundled in library mode
195
+ external: [
196
+ 'vue',
197
+ 'vue-router',
198
+ 'vuetify',
199
+ 'pinia',
200
+ '@vue-skuilder/common',
201
+ '@vue-skuilder/common-ui',
202
+ '@vue-skuilder/courses',
203
+ '@vue-skuilder/db',
204
+ ],
205
+ output: {
206
+ // Global variables for UMD build
207
+ globals: {
208
+ 'vue': 'Vue',
209
+ 'vue-router': 'VueRouter',
210
+ 'vuetify': 'Vuetify',
211
+ 'pinia': 'Pinia',
212
+ '@vue-skuilder/common': 'VueSkuilderCommon',
213
+ '@vue-skuilder/common-ui': 'VueSkuilderCommonUI',
214
+ '@vue-skuilder/courses': 'VueSkuilderCourses',
215
+ '@vue-skuilder/db': 'VueSkuilderDB',
216
+ },
217
+ exports: 'named',
218
+ // Preserve CSS in the output bundle
219
+ assetFileNames: 'assets/[name].[ext]',
220
+ },
221
+ },
222
+ // Output to separate directory for library build
223
+ outDir: 'dist-lib',
224
+ // Allow CSS code splitting for component libraries
225
+ cssCodeSplit: true,
226
+ }
227
+ : {
228
+ // Webapp build configuration (existing)
229
+ sourcemap: true,
230
+ target: 'es2020',
231
+ minify: 'terser',
232
+ terserOptions: {
233
+ keep_classnames: true,
234
+ },
235
+ // Standard webapp output directory
236
+ outDir: 'dist',
237
+ },
167
238
  // Add define block for process polyfills
168
239
  define: {
169
240
  global: 'window',
@@ -182,7 +253,8 @@ export default defineConfig({
182
253
  */
183
254
  export async function generateSkuilderConfig(
184
255
  configPath: string,
185
- config: ProjectConfig
256
+ config: ProjectConfig,
257
+ outputPath?: string
186
258
  ): Promise<void> {
187
259
  const skuilderConfig: SkuilderConfig = {
188
260
  title: config.title,
@@ -194,13 +266,14 @@ export async function generateSkuilderConfig(
194
266
  skuilderConfig.course = config.course;
195
267
  }
196
268
 
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];
269
+ // For static data layer, use imported course ID or generate new one
270
+ if (config.dataLayerType === 'static') {
271
+ if (config.importCourseIds && config.importCourseIds.length > 0) {
272
+ skuilderConfig.course = config.importCourseIds[0];
273
+ } else {
274
+ // Generate UUID for new static courses without imports
275
+ skuilderConfig.course = randomUUID();
276
+ }
204
277
  }
205
278
 
206
279
  if (config.couchdbUrl) {
@@ -212,6 +285,11 @@ export async function generateSkuilderConfig(
212
285
  }
213
286
 
214
287
  await fs.writeFile(configPath, JSON.stringify(skuilderConfig, null, 2));
288
+
289
+ // For static data layer without imports, create empty course structure
290
+ if (config.dataLayerType === 'static' && (!config.importCourseIds || config.importCourseIds.length === 0) && outputPath) {
291
+ await createEmptyCourseStructure(outputPath, skuilderConfig.course!, config.title);
292
+ }
215
293
  }
216
294
 
217
295
  /**
@@ -250,6 +328,72 @@ export async function transformTsConfig(tsconfigPath: string): Promise<void> {
250
328
  await fs.writeFile(tsconfigPath, JSON.stringify(tsconfig, null, 2));
251
329
  }
252
330
 
331
+ /**
332
+ * Create empty course structure for new static courses
333
+ */
334
+ async function createEmptyCourseStructure(
335
+ projectPath: string,
336
+ courseId: string,
337
+ title: string
338
+ ): Promise<void> {
339
+ const staticCoursesPath = path.join(projectPath, 'public', 'static-courses');
340
+ const coursePath = path.join(staticCoursesPath, courseId);
341
+
342
+ // Create directory structure
343
+ await fs.mkdir(coursePath, { recursive: true });
344
+ await fs.mkdir(path.join(coursePath, 'chunks'), { recursive: true });
345
+ await fs.mkdir(path.join(coursePath, 'indices'), { recursive: true });
346
+
347
+ // Create minimal CourseConfig
348
+ const courseConfig: CourseConfig = {
349
+ courseID: courseId,
350
+ name: title,
351
+ description: '',
352
+ public: false,
353
+ deleted: false,
354
+ creator: 'system',
355
+ admins: [],
356
+ moderators: [],
357
+ dataShapes: [],
358
+ questionTypes: []
359
+ };
360
+
361
+ // Create manifest.json with proper structure
362
+ const manifest = {
363
+ version: '1.0.0',
364
+ courseId,
365
+ courseName: title,
366
+ courseConfig,
367
+ lastUpdated: new Date().toISOString(),
368
+ documentCount: 0,
369
+ chunks: [],
370
+ indices: [],
371
+ designDocs: []
372
+ };
373
+
374
+ await fs.writeFile(
375
+ path.join(coursePath, 'manifest.json'),
376
+ JSON.stringify(manifest, null, 2)
377
+ );
378
+
379
+ // Create empty tags index
380
+ await fs.writeFile(
381
+ path.join(coursePath, 'indices', 'tags.json'),
382
+ JSON.stringify({ tags: [] }, null, 2)
383
+ );
384
+
385
+ // Create CourseConfig chunk
386
+ await fs.writeFile(
387
+ path.join(coursePath, 'chunks', 'CourseConfig.json'),
388
+ JSON.stringify([{
389
+ _id: 'CourseConfig',
390
+ ...courseConfig
391
+ }], null, 2)
392
+ );
393
+
394
+ console.log(chalk.green(`✅ Created empty course structure for ${courseId}`));
395
+ }
396
+
253
397
  /**
254
398
  * Generate .gitignore file for the project
255
399
  */
@@ -368,6 +512,7 @@ Thumbs.db
368
512
 
369
513
  # Skuilder specific
370
514
  /src/data/local-*.json
515
+ .skuilder/
371
516
  `;
372
517
 
373
518
  await fs.writeFile(gitignorePath, gitignoreContent);
@@ -413,8 +558,34 @@ npm run dev
413
558
  Build for production:
414
559
  \`\`\`bash
415
560
  npm run build
561
+ \`\`\`${
562
+ config.dataLayerType === 'static'
563
+ ? `
564
+
565
+ ## Studio Mode (Content Editing)
566
+
567
+ This project supports **Studio Mode** - a content editing web interface for modifying course data:
568
+
569
+ \`\`\`bash
570
+ npm run studio
416
571
  \`\`\`
417
572
 
573
+ Studio mode provides:
574
+ - **Visual Course Editor**: Interactive interface for editing course content
575
+ - **Live Preview**: See changes immediately in the browser
576
+ - **Hot Reload**: Changes are saved automatically to your course files
577
+ - **No Setup Required**: Built into the Skuilder CLI - just run the command
578
+
579
+ When you run \`npm run studio\`, it will:
580
+ 1. Start a local CouchDB instance for temporary editing
581
+ 2. Load your course data from \`public/static-courses/\`
582
+ 3. Launch the studio interface at http://localhost:7174
583
+ 4. Save changes back to your static course files when you flush
584
+
585
+ **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.`
586
+ : ''
587
+ }
588
+
418
589
  ## Configuration
419
590
 
420
591
  Course configuration is managed in \`skuilder.config.json\`. You can modify:
@@ -482,6 +653,36 @@ Visit the [Skuilder documentation](https://github.com/NiloCK/vue-skuilder) for m
482
653
  await fs.writeFile(readmePath, readme);
483
654
  }
484
655
 
656
+ /**
657
+ * Create .skuilder directory structure for studio builds cache
658
+ */
659
+ async function createSkuilderDirectory(projectPath: string): Promise<void> {
660
+ const skuilderPath = path.join(projectPath, '.skuilder');
661
+ const templatesPath = path.join(__dirname, '..', '..', 'templates', '.skuilder');
662
+
663
+ // Create .skuilder directory
664
+ await fs.mkdir(skuilderPath, { recursive: true });
665
+
666
+ // Create studio-builds subdirectory
667
+ await fs.mkdir(path.join(skuilderPath, 'studio-builds'), { recursive: true });
668
+
669
+ // Copy README template if it exists
670
+ if (existsSync(templatesPath)) {
671
+ await copyDirectory(templatesPath, skuilderPath);
672
+ } else {
673
+ // Fallback: create basic README
674
+ const readmeContent = `# .skuilder Directory
675
+
676
+ **⚠️ WARNING: GENERATED CONTENT - DO NOT EDIT MANUALLY ⚠️**
677
+
678
+ This directory contains files generated by the Skuilder CLI tools.
679
+
680
+ Generated by @vue-skuilder/cli
681
+ `;
682
+ await fs.writeFile(path.join(skuilderPath, 'README.md'), readmeContent);
683
+ }
684
+ }
685
+
485
686
  /**
486
687
  * Copy and transform the standalone-ui template to create a new project
487
688
  */
@@ -514,7 +715,7 @@ export async function processTemplate(
514
715
 
515
716
  console.log(chalk.blue('🔧 Generating configuration...'));
516
717
  const configPath = path.join(projectPath, 'skuilder.config.json');
517
- await generateSkuilderConfig(configPath, config);
718
+ await generateSkuilderConfig(configPath, config, projectPath);
518
719
 
519
720
  console.log(chalk.blue('📝 Creating README...'));
520
721
  const readmePath = path.join(projectPath, 'README.md');
@@ -524,5 +725,8 @@ export async function processTemplate(
524
725
  const gitignorePath = path.join(projectPath, '.gitignore');
525
726
  await generateGitignore(gitignorePath);
526
727
 
728
+ console.log(chalk.blue('📁 Creating .skuilder directory structure...'));
729
+ await createSkuilderDirectory(projectPath);
730
+
527
731
  console.log(chalk.green('✅ Template processing complete!'));
528
732
  }
@@ -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"}