@vue-skuilder/platform-ui 0.1.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 (108) hide show
  1. package/LICENCE +661 -0
  2. package/README.md +64 -0
  3. package/dist/assets/Roboto-Black-B0ZKieaB.woff +0 -0
  4. package/dist/assets/Roboto-Black-VhoA2qKx.woff2 +0 -0
  5. package/dist/assets/Roboto-BlackItalic-D0gSnuIb.woff +0 -0
  6. package/dist/assets/Roboto-BlackItalic-D4yie1YO.woff2 +0 -0
  7. package/dist/assets/Roboto-Bold-D9plYbeK.woff +0 -0
  8. package/dist/assets/Roboto-Bold-hN3duQhD.woff2 +0 -0
  9. package/dist/assets/Roboto-BoldItalic-BWDm51uc.woff2 +0 -0
  10. package/dist/assets/Roboto-BoldItalic-CyLKvOHD.woff +0 -0
  11. package/dist/assets/Roboto-Light-Cu-PAxXt.woff +0 -0
  12. package/dist/assets/Roboto-Light-DHTugVNA.woff2 +0 -0
  13. package/dist/assets/Roboto-LightItalic-CZg5kHIB.woff +0 -0
  14. package/dist/assets/Roboto-LightItalic-JQyp2Y3P.woff2 +0 -0
  15. package/dist/assets/Roboto-Medium-ByKogCTi.woff2 +0 -0
  16. package/dist/assets/Roboto-Medium-b81vv18W.woff +0 -0
  17. package/dist/assets/Roboto-MediumItalic-DFQ-RYa0.woff +0 -0
  18. package/dist/assets/Roboto-MediumItalic-i1eR0KbF.woff2 +0 -0
  19. package/dist/assets/Roboto-Regular-BX5l9hRW.woff +0 -0
  20. package/dist/assets/Roboto-Regular-C6rbFxYz.woff2 +0 -0
  21. package/dist/assets/Roboto-RegularItalic-BjnLZsam.woff +0 -0
  22. package/dist/assets/Roboto-RegularItalic-CvPUdkvM.woff2 +0 -0
  23. package/dist/assets/Roboto-Thin-BfJvJcog.woff +0 -0
  24. package/dist/assets/Roboto-Thin-NicBC1pN.woff2 +0 -0
  25. package/dist/assets/Roboto-ThinItalic-CKlCjrO_.woff2 +0 -0
  26. package/dist/assets/Roboto-ThinItalic-DnIWFxRE.woff +0 -0
  27. package/dist/assets/index-CQ-sNKGW.css +14 -0
  28. package/dist/assets/index-EbqpUgvM.js +161 -0
  29. package/dist/assets/materialdesignicons-webfont-B7mPwVP_.ttf +0 -0
  30. package/dist/assets/materialdesignicons-webfont-CSr8KVlo.eot +0 -0
  31. package/dist/assets/materialdesignicons-webfont-Dp5v-WZN.woff2 +0 -0
  32. package/dist/assets/materialdesignicons-webfont-PXm3-2wK.woff +0 -0
  33. package/dist/assets/workbox-window.prod.es5-p40uij6f.js +1 -0
  34. package/dist/favicon.ico +0 -0
  35. package/dist/img/icons/safari-pinned-tab.svg +149 -0
  36. package/dist/index.html +19 -0
  37. package/dist/manifest.json +20 -0
  38. package/dist/manifest.webmanifest +1 -0
  39. package/dist/robots.txt +2 -0
  40. package/dist/sw.js +1 -0
  41. package/dist/workbox-1be04862.js +1 -0
  42. package/package.json +105 -0
  43. package/src/App.vue +156 -0
  44. package/src/ENVIRONMENT_VARS.ts +79 -0
  45. package/src/components/Classrooms/ClassroomCtrlPanel.vue +206 -0
  46. package/src/components/Classrooms/CreateClassroom.vue +159 -0
  47. package/src/components/Classrooms/JoinCode.vue +83 -0
  48. package/src/components/Courses/CourseCardBrowser.vue +365 -0
  49. package/src/components/Courses/CourseEditor.vue +164 -0
  50. package/src/components/Courses/CourseInformation.vue +164 -0
  51. package/src/components/Courses/CourseRouter.vue +116 -0
  52. package/src/components/Courses/CourseStubCard.vue +76 -0
  53. package/src/components/Courses/EloModeration.vue +122 -0
  54. package/src/components/Courses/TagInformation.vue +209 -0
  55. package/src/components/Edit/BulkImport/CardPreviewList.vue +345 -0
  56. package/src/components/Edit/BulkImportView.vue +633 -0
  57. package/src/components/Edit/CardBrowser.vue +79 -0
  58. package/src/components/Edit/ComponentRegistration/ComponentRegistration.vue +235 -0
  59. package/src/components/Edit/ComponentRegistration/UnregisteredComponentsTable.vue +19 -0
  60. package/src/components/Edit/CourseEditor.vue +162 -0
  61. package/src/components/Edit/NavigationStrategy/NavigationStrategyEditor.vue +170 -0
  62. package/src/components/Edit/NavigationStrategy/NavigationStrategyList.vue +92 -0
  63. package/src/components/Edit/TagsInput.vue +247 -0
  64. package/src/components/Edit/ViewableDataInputForm/DataInputForm.vue +524 -0
  65. package/src/components/Edit/ViewableDataInputForm/FieldInput.types.ts +33 -0
  66. package/src/components/Edit/ViewableDataInputForm/FieldInputs/AudioInput.vue +188 -0
  67. package/src/components/Edit/ViewableDataInputForm/FieldInputs/ChessPuzzleInput.vue +79 -0
  68. package/src/components/Edit/ViewableDataInputForm/FieldInputs/FieldInput.css +12 -0
  69. package/src/components/Edit/ViewableDataInputForm/FieldInputs/ImageInput.vue +231 -0
  70. package/src/components/Edit/ViewableDataInputForm/FieldInputs/IntegerInput.vue +49 -0
  71. package/src/components/Edit/ViewableDataInputForm/FieldInputs/MarkdownInput.vue +34 -0
  72. package/src/components/Edit/ViewableDataInputForm/FieldInputs/MediaDragDropUploader.vue +246 -0
  73. package/src/components/Edit/ViewableDataInputForm/FieldInputs/MidiInput.vue +113 -0
  74. package/src/components/Edit/ViewableDataInputForm/FieldInputs/NumberInput.vue +49 -0
  75. package/src/components/Edit/ViewableDataInputForm/FieldInputs/StringInput.vue +49 -0
  76. package/src/components/Edit/ViewableDataInputForm/FieldInputs/typeValidators.ts +49 -0
  77. package/src/components/Edit/ViewableDataInputForm/OptionsFieldInput.ts +161 -0
  78. package/src/components/Study/SessionConfiguration.vue +371 -0
  79. package/src/components/TextSwap.vue +65 -0
  80. package/src/components/User/UserStats.vue +30 -0
  81. package/src/dev/DataInputFormTester.vue +117 -0
  82. package/src/dev/readme.md +3 -0
  83. package/src/enums.ts +0 -0
  84. package/src/glyphs.txt +933 -0
  85. package/src/main.ts +45 -0
  86. package/src/plugins/vuetify.ts +41 -0
  87. package/src/registerServiceWorker.ts +18 -0
  88. package/src/router.ts +184 -0
  89. package/src/server/index.spec.ts +192 -0
  90. package/src/server/index.ts +71 -0
  91. package/src/shims-vue.d.ts +5 -0
  92. package/src/store.mock.ts +122 -0
  93. package/src/stores/useDataInputFormStore.ts +49 -0
  94. package/src/stores/useFieldInputStore.ts +191 -0
  95. package/src/types/shims-vuetify.d.ts +12 -0
  96. package/src/types/svg.d.ts +4 -0
  97. package/src/utils/bulkImport/index.ts +94 -0
  98. package/src/views/About.vue +29 -0
  99. package/src/views/Admin.vue +128 -0
  100. package/src/views/Classrooms.vue +258 -0
  101. package/src/views/Courses.vue +265 -0
  102. package/src/views/Home.vue +154 -0
  103. package/src/views/Login.vue +75 -0
  104. package/src/views/ReleaseNotes.vue +20 -0
  105. package/src/views/SignUp.vue +32 -0
  106. package/src/views/Study.vue +261 -0
  107. package/src/views/User.vue +109 -0
  108. package/src/vite-env.d.ts +1 -0
@@ -0,0 +1,371 @@
1
+ <template>
2
+ <div v-if="hasRegistrations">
3
+ <div data-cy="select-quilts-header" class="text-h4 mb-4">Study Session Setup</div>
4
+
5
+ <div class="session-layout">
6
+ <!-- Left Column: Course Selection -->
7
+ <div class="course-selection-container">
8
+ <div class="text-h6 mb-3">Select Quilts to Study</div>
9
+ <table width="100%">
10
+ <thead>
11
+ <tr>
12
+ <th>
13
+ <v-checkbox
14
+ id="SelectAll"
15
+ ref="selectAll"
16
+ v-model="allSelected"
17
+ autofocus
18
+ label="Select All"
19
+ @update:model-value="toggleAll"
20
+ ></v-checkbox>
21
+ </th>
22
+
23
+ <th>
24
+ Reviews
25
+ <!-- <v-icon>info</v-icon> -->
26
+ </th>
27
+ </tr>
28
+ </thead>
29
+ <tbody>
30
+ <tr v-for="classroom in activeClasses" :key="classroom.classID">
31
+ <td>
32
+ <v-checkbox v-model="classroom.selected" :label="`Class: ${classroom.name}`" @click.capture="update" />
33
+ </td>
34
+ <td>-</td>
35
+ </tr>
36
+ <tr v-for="course in activeCourses" :key="course.courseID">
37
+ <td>
38
+ <v-checkbox
39
+ v-model="course.selected"
40
+ data-cy="course-checkbox"
41
+ :label="`q/${course.name}`"
42
+ @click.capture="update"
43
+ />
44
+ </td>
45
+ <td>{{ course.reviews }}</td>
46
+ </tr>
47
+ </tbody>
48
+ </table>
49
+ </div>
50
+
51
+ <!-- Right Column: Time Configuration and Start Button -->
52
+ <div class="fixed-controls-container">
53
+ <div class="fixed-controls">
54
+ <div class="text-h6 mb-3">Session Settings</div>
55
+
56
+ <div class="mb-5">
57
+ <v-text-field
58
+ ref="numberField"
59
+ v-model="timeLimit"
60
+ class="time-limit-field"
61
+ variant="outlined"
62
+ label="Study Session Timelimit"
63
+ prepend-inner-icon="mdi-clock-outline"
64
+ prepend-icon="mdi-minus"
65
+ append-icon="mdi-plus"
66
+ :suffix="timeLimit > 1 ? 'minutes' : 'minute'"
67
+ mask="##"
68
+ type="number"
69
+ @click:prepend="dec"
70
+ @click:append="inc"
71
+ />
72
+ </div>
73
+
74
+ <SkMouseTrapToolTip
75
+ hotkey="enter"
76
+ command="Start Session"
77
+ :disabled="!activeCourses.length && !activeClasses.length"
78
+ highlight-effect="scale"
79
+ >
80
+ <v-btn
81
+ data-cy="start-studying-button"
82
+ color="success"
83
+ size="large"
84
+ block
85
+ class="start-btn"
86
+ @click="startSession"
87
+ >
88
+ <v-icon start>mdi-play</v-icon>
89
+ Start!
90
+ </v-btn>
91
+ </SkMouseTrapToolTip>
92
+ </div>
93
+ </div>
94
+ </div>
95
+ </div>
96
+ <div v-else class="text-h4">
97
+ <p>You don't have anything to study!</p>
98
+ <p>Head over to the <router-link to="/quilts">Quilts</router-link> page to find something for you.</p>
99
+ </div>
100
+ </template>
101
+
102
+ <script lang="ts">
103
+ import { defineComponent } from 'vue';
104
+ import { SkldrMouseTrap, getCurrentUser, SkMouseTrapToolTip } from '@vue-skuilder/common-ui';
105
+ import { CourseRegistration, UserDBInterface, getDataLayer, ContentSourceID } from '@vue-skuilder/db';
106
+
107
+ export interface SessionConfigMetaData {
108
+ selected: boolean;
109
+ name: string;
110
+ reviews: number;
111
+ }
112
+
113
+ export default defineComponent({
114
+ name: 'SessionConfiguration',
115
+
116
+ components: {
117
+ SkMouseTrapToolTip,
118
+ },
119
+
120
+ props: {
121
+ initialTimeLimit: {
122
+ type: Number,
123
+ required: true,
124
+ default: 5,
125
+ },
126
+ },
127
+
128
+ emits: ['initStudySession'],
129
+
130
+ data() {
131
+ return {
132
+ registeredHotkeys: [] as (string | string[])[],
133
+ allSelected: true,
134
+ activeCourses: [] as (CourseRegistration & SessionConfigMetaData)[],
135
+ activeClasses: [] as ({ classID: string } & SessionConfigMetaData)[],
136
+ hasRegistrations: true,
137
+ user: null as UserDBInterface | null,
138
+ timeLimit: this.initialTimeLimit,
139
+ };
140
+ },
141
+
142
+ watch: {
143
+ timeLimit: {
144
+ handler() {
145
+ if (this.timeLimit <= 0) {
146
+ this.timeLimit = 1;
147
+ }
148
+ },
149
+ },
150
+ },
151
+
152
+ beforeUnmount() {
153
+ // Clean up registered hotkeys when component unmounts
154
+ if (this.registeredHotkeys) {
155
+ this.registeredHotkeys.forEach(key => {
156
+ SkldrMouseTrap.removeBinding(key);
157
+ });
158
+ }
159
+ },
160
+
161
+ async created() {
162
+ this.user = await getCurrentUser();
163
+ this.timeLimit = this.initialTimeLimit;
164
+
165
+ this.setHotkeys();
166
+ await Promise.all([this.getActiveCourses(), this.getActiveClassrooms()]);
167
+
168
+ if (this.activeCourses.length === 0 && this.activeClasses.length === 0) {
169
+ this.hasRegistrations = false;
170
+ }
171
+ },
172
+
173
+ mounted() {
174
+ document.getElementById('SelectAll')!.focus();
175
+ },
176
+
177
+ unmounted() {
178
+ // Clean up registered hotkeys when component unmounts
179
+ if (this.registeredHotkeys) {
180
+ this.registeredHotkeys.forEach(key => {
181
+ SkldrMouseTrap.removeBinding(key);
182
+ });
183
+ }
184
+ },
185
+
186
+ methods: {
187
+ inc() {
188
+ this.timeLimit = this.timeLimit + 1;
189
+ console.log(`inc to ${this.timeLimit}`);
190
+ },
191
+
192
+ dec() {
193
+ this.timeLimit--;
194
+ console.log(`dec to ${this.timeLimit}`);
195
+ },
196
+
197
+ update() {
198
+ console.log(JSON.stringify(this.activeCourses));
199
+ console.log(JSON.stringify(this.activeClasses));
200
+ },
201
+
202
+ toggleAll(): void {
203
+ console.log(`Toggling all courses`);
204
+ this.activeCourses.forEach((crs) => {
205
+ crs.selected = this.allSelected;
206
+ });
207
+ this.activeClasses.forEach((cl) => {
208
+ cl.selected = this.allSelected;
209
+ });
210
+ console.log(JSON.stringify(this.activeCourses));
211
+ },
212
+
213
+ startSession() {
214
+ // Clean up any registered hotkeys before starting session
215
+ if (this.registeredHotkeys) {
216
+ this.registeredHotkeys.forEach(key => {
217
+ SkldrMouseTrap.removeBinding(key);
218
+ });
219
+ }
220
+ const selectedCourses: ContentSourceID[] = this.activeCourses
221
+ .filter((c) => c.selected)
222
+ .map((c) => ({
223
+ type: 'course',
224
+ id: c.courseID,
225
+ }));
226
+ const selectedClassrooms: ContentSourceID[] = this.activeClasses
227
+ .filter((cl) => cl.selected)
228
+ .map((cl) => ({
229
+ type: 'classroom',
230
+ id: cl.classID,
231
+ }));
232
+ const allSelectedSources = [...selectedCourses, ...selectedClassrooms];
233
+ this.$emit('initStudySession', allSelectedSources, this.timeLimit);
234
+ },
235
+
236
+ async getActiveClassrooms() {
237
+ const classes = await (await getCurrentUser()).getActiveClasses();
238
+ const activeClasses: ({ classID: string } & SessionConfigMetaData)[] = [];
239
+
240
+ console.log(`Active classes: ${JSON.stringify(classes)}`);
241
+
242
+ await Promise.all(
243
+ classes.map((c) =>
244
+ (async (classID: string) => {
245
+ const classDb = await getDataLayer().getClassroomDB(classID, `student`);
246
+ activeClasses.push({
247
+ classID,
248
+ name: classDb.getConfig().name,
249
+ selected: true,
250
+ reviews: 0,
251
+ });
252
+ })(c)
253
+ )
254
+ );
255
+ this.activeClasses = activeClasses;
256
+ },
257
+
258
+ async getActiveCourses() {
259
+ this.activeCourses = (await this.user!.getActiveCourses()).map((c) => ({
260
+ ...c,
261
+ selected: true,
262
+ name: '',
263
+ reviews: 0,
264
+ }));
265
+
266
+ Promise.all(
267
+ this.activeCourses.map(async (c, i) => {
268
+ const cfg = await getDataLayer().getCoursesDB().getCourseConfig(c.courseID);
269
+ const crsInterface = await this.user?.getCourseInterface(c.courseID);
270
+ return (async () => {
271
+ return Promise.all([
272
+ (this.activeCourses[i].name = cfg.name),
273
+ (this.activeCourses[i].reviews = await crsInterface!.getScheduledReviewCount()),
274
+ ]);
275
+ })();
276
+ })
277
+ );
278
+ },
279
+
280
+ setHotkeys() {
281
+ const hotkeys = [
282
+ {
283
+ hotkey: 'right',
284
+ callback: () => {
285
+ this.timeLimit++;
286
+ },
287
+ command: 'Increase time limit',
288
+ },
289
+ {
290
+ hotkey: 'left',
291
+ callback: () => {
292
+ this.timeLimit--;
293
+ },
294
+ command: 'Decrease time limit',
295
+ },
296
+ ];
297
+ SkldrMouseTrap.addBinding(hotkeys);
298
+ this.registeredHotkeys = hotkeys.map(k => k.hotkey);
299
+ },
300
+ },
301
+ });
302
+ </script>
303
+
304
+ <style scoped>
305
+ td {
306
+ text-align: center;
307
+ align-content: center;
308
+ }
309
+
310
+ .cb {
311
+ align-content: center !important;
312
+ text-align: center !important;
313
+ }
314
+
315
+ /* Layout for session configuration */
316
+ .session-layout {
317
+ display: flex;
318
+ flex-direction: column;
319
+ }
320
+
321
+ /* Fixed controls container */
322
+ .fixed-controls-container {
323
+ width: 100%;
324
+ margin-bottom: 20px;
325
+ }
326
+
327
+ .fixed-controls {
328
+ display: flex;
329
+ flex-direction: column;
330
+ }
331
+
332
+ .time-limit-field {
333
+ width: 100%;
334
+ margin-bottom: 16px;
335
+ }
336
+
337
+ .start-btn {
338
+ margin-top: 8px;
339
+ max-height: 150px;
340
+ }
341
+
342
+ /* Course selection styles */
343
+ .course-selection-container {
344
+ width: 100%;
345
+ }
346
+
347
+ /* Media queries for desktop layout */
348
+ @media (min-width: 960px) {
349
+ .session-layout {
350
+ flex-direction: row;
351
+ gap: 40px;
352
+ }
353
+
354
+ .fixed-controls-container {
355
+ width: 300px;
356
+ flex-shrink: 0;
357
+ }
358
+
359
+ .fixed-controls {
360
+ position: sticky;
361
+ top: 20px;
362
+ padding-left: 20px;
363
+ }
364
+
365
+ .course-selection-container {
366
+ flex-grow: 1;
367
+ border-right: 1px solid rgba(0, 0, 0, 0.12);
368
+ padding-right: 20px;
369
+ }
370
+ }
371
+ </style>
@@ -0,0 +1,65 @@
1
+ <template>
2
+ <transition appear name="fade" mode="out-in">
3
+ <a :key="index" @click="next()">{{ text[index] }}</a>
4
+ </transition>
5
+ </template>
6
+
7
+ <script lang="ts">
8
+ import { defineComponent } from 'vue';
9
+
10
+ export interface ITextSwap {
11
+ next: () => void;
12
+ text: string[];
13
+ index: number;
14
+ }
15
+
16
+ export default defineComponent({
17
+ name: 'TextSwap',
18
+
19
+ props: {
20
+ text: {
21
+ type: Array as () => string[],
22
+ required: true,
23
+ },
24
+ },
25
+
26
+ data() {
27
+ return {
28
+ index: 0,
29
+ };
30
+ },
31
+
32
+ methods: {
33
+ next() {
34
+ if (this.text.length > 1) {
35
+ const previous = this.index;
36
+ // this.index = -1; // triggering animation
37
+ // this.index = previous;
38
+ while (this.index === previous) {
39
+ this.index = Math.floor(Math.random() * this.text.length);
40
+ }
41
+ }
42
+ },
43
+ },
44
+ });
45
+ </script>
46
+
47
+ <style scoped>
48
+ a {
49
+ color: inherit;
50
+ text-decoration: underline;
51
+ text-decoration-color: #aaa;
52
+ text-decoration-style: dotted;
53
+ text-underline-offset: 2px;
54
+ text-underline-position: below;
55
+ }
56
+
57
+ .fade-enter-active,
58
+ .fade-leave-active {
59
+ transition: opacity 0.5s ease;
60
+ }
61
+ .fade-enter,
62
+ .fade-leave-to {
63
+ opacity: 0;
64
+ }
65
+ </style>
@@ -0,0 +1,30 @@
1
+ <template>
2
+ <div>
3
+ <h2>Stats!</h2>
4
+ One day: {{ scheduledReviews[0] }}
5
+ <br />
6
+ Seven day: {{ scheduledReviews[1] }}
7
+ <br />
8
+ Thirty Day: {{ scheduledReviews[2] }}
9
+ </div>
10
+ </template>
11
+
12
+ <script setup lang="ts">
13
+ import { ref, onMounted } from 'vue';
14
+ import { User } from '@vue-skuilder/db';
15
+ import { getCurrentUser } from '@vue-skuilder/common-ui';
16
+
17
+ const user = ref<User | null>(null);
18
+ const scheduledReviews = ref<number[]>([]);
19
+
20
+ onMounted(async () => {
21
+ user.value = await getCurrentUser();
22
+
23
+ // Using Promise.all to handle all forecasts concurrently
24
+ const days = [1, 7, 30];
25
+ const forecasts = await Promise.all(days.map((d) => user.value!.getReviewsForcast(d)));
26
+ scheduledReviews.value = forecasts.map((f) => f.length);
27
+ });
28
+ </script>
29
+
30
+ <style scoped></style>
@@ -0,0 +1,117 @@
1
+ <template>
2
+ <div>
3
+ <data-input-form
4
+ v-if="component && dataShape"
5
+ :data-shape="dataShape"
6
+ :course-cfg="{
7
+ courseID: 'testCourse',
8
+ name: 'testCourseName',
9
+ description: 'All of these courses will be the same!',
10
+ admins: ['admin'],
11
+ creator: 'admin',
12
+ deleted: false,
13
+ moderators: [],
14
+ public: true,
15
+ disambiguator: 'testCourseName',
16
+ questionTypes: [],
17
+ dataShapes: [],
18
+ }"
19
+ :preview-component="component"
20
+ />
21
+ <div v-else>
22
+ No component loaded :/
23
+ <!--
24
+
25
+ todo: provide list of Question type components, with router links
26
+ to their DataInputFormTester loaders.
27
+
28
+ --></div>
29
+ </div>
30
+ </template>
31
+
32
+ <script lang="ts">
33
+ import { defineComponent } from 'vue';
34
+ import DataInputForm from '../../src/components/Edit/ViewableDataInputForm/DataInputForm.vue';
35
+ import { DataShape } from '../../src/base-course/Interfaces/DataShape';
36
+ import { useRoute } from 'vue-router';
37
+ import { Question } from '@vue-skuilder/common-ui';
38
+
39
+ type CourseShapes = {
40
+ courseID: string;
41
+ dataShapes: DataShape[];
42
+ };
43
+
44
+ const questionModules = import.meta.glob('../**/questions/**/index.ts', {
45
+ // Adding eager: false ensures lazy loading
46
+ eager: false,
47
+ });
48
+
49
+ type ModuleExports = {
50
+ [key: string]: unknown;
51
+ };
52
+
53
+ export default defineComponent({
54
+ name: 'DataInputFormTester',
55
+
56
+ components: {
57
+ DataInputForm,
58
+ },
59
+
60
+ setup() {
61
+ return {
62
+ route: useRoute(),
63
+ };
64
+ },
65
+
66
+ data() {
67
+ return {
68
+ dataShape: null as DataShape | null,
69
+ cds: null as CourseShapes[] | null,
70
+ component: null as typeof Question | null,
71
+ };
72
+ },
73
+
74
+ async created() {
75
+ try {
76
+ const q = this.route.params.pathMatch;
77
+ const modulePath = `../${q}/index.ts`;
78
+
79
+ console.log('Looking for module:', modulePath);
80
+ console.log('Available paths:', Object.keys(questionModules));
81
+
82
+ if (modulePath in questionModules) {
83
+ const module = (await questionModules[modulePath]()) as ModuleExports;
84
+
85
+ // Find the Question implementation
86
+ const matchingExport = Object.entries(module).find(([name, exportValue]) => {
87
+ const isClass = typeof exportValue === 'function';
88
+ const extendsQuestion = isClass && exportValue.prototype instanceof Question;
89
+
90
+ console.log(`Checking export "${name}":`, {
91
+ isClass,
92
+ extendsQuestion,
93
+ type: typeof exportValue,
94
+ });
95
+
96
+ return extendsQuestion;
97
+ });
98
+
99
+ if (!matchingExport) {
100
+ throw new Error('No export found extending Question class');
101
+ }
102
+
103
+ this.component = matchingExport[1] as typeof Question;
104
+
105
+ const ds = this.component?.dataShapes[0];
106
+ if (ds) {
107
+ this.dataShape = ds;
108
+ }
109
+ } else {
110
+ throw new Error(`No module found at path: ${modulePath}`);
111
+ }
112
+ } catch (e) {
113
+ console.log('[difTester] Error loading component', e);
114
+ }
115
+ },
116
+ });
117
+ </script>
@@ -0,0 +1,3 @@
1
+ This folder contains components, etc, useful for dev-time only.
2
+
3
+ It is not included in the final build.
package/src/enums.ts ADDED
File without changes