@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,524 @@
1
+ <template>
2
+ <v-container fluid>
3
+ <v-row>
4
+ <v-col cols="12" xl="6">
5
+ <v-form ma-2 autocomplete="off">
6
+ <div v-for="(field, i) in dataShape.fields" :key="dataShape.fields.indexOf(field)">
7
+ <!-- image and audio inputs are semi deprecated - not in use right now -
8
+ superceded by the generic fillIn type that allows images and audio from the
9
+ general mediaDragDropUploader -->
10
+ <!-- <audio-input v-else-if="field.type === audio" :ref="(el: FieldInputInstance) => setFieldInputRef(el, i)" :field="field" :autofocus="i == 0" /> -->
11
+ <!-- <image-input v-else-if="field.type === img" :ref="(el: FieldInputInstance) => setFieldInputRef(el, i)" :field="field" :autofocus="i == 0" /> -->
12
+
13
+ <string-input
14
+ v-if="field.type === ftString"
15
+ :ref="(el: FieldInputInstance) => setFieldInputRef(el, i)"
16
+ :field="field"
17
+ :autofocus="i == 0"
18
+ />
19
+ <chess-puzzle-input
20
+ v-else-if="field.type === chessPuzzle"
21
+ :ref="(el: FieldInputInstance) => setFieldInputRef(el, i)"
22
+ :field="field"
23
+ :autofocus="i == 0"
24
+ />
25
+ <number-input
26
+ v-else-if="field.type === num"
27
+ :ref="(el: FieldInputInstance) => setFieldInputRef(el, i)"
28
+ :field="field"
29
+ :autofocus="i == 0"
30
+ />
31
+ <integer-input
32
+ v-else-if="field.type === int"
33
+ :ref="(el: FieldInputInstance) => setFieldInputRef(el, i)"
34
+ :field="field"
35
+ :autofocus="i == 0"
36
+ />
37
+ <markdown-input
38
+ v-else-if="field.type === mkd"
39
+ :ref="(el: FieldInputInstance) => setFieldInputRef(el, i)"
40
+ :field="field"
41
+ :autofocus="i == 0"
42
+ data-cy="markdown-input"
43
+ />
44
+ <midi-input
45
+ v-else-if="field.type === midi"
46
+ :ref="(el: FieldInputInstance) => setFieldInputRef(el, i)"
47
+ :field="field"
48
+ :autofocus="i == 0"
49
+ />
50
+ <media-drag-drop-uploader
51
+ v-else-if="field.type === uploader"
52
+ :ref="(el: FieldInputInstance) => setFieldInputRef(el, i)"
53
+ :field="field"
54
+ :autofocus="i == 0"
55
+ />
56
+ </div>
57
+
58
+ <tags-input ref="tagsInput" :hide-submit="true" :course-i-d="courseCfg.courseID" card-i-d="" />
59
+ <v-btn
60
+ v-if="!previewComponent"
61
+ data-cy="add-card-btn"
62
+ class="float-right"
63
+ type="submit"
64
+ color="primary"
65
+ :loading="uploading"
66
+ :disabled="!allowSubmit"
67
+ @click.prevent="submit"
68
+ >
69
+ Add card
70
+ <v-icon end>mdi-plus-circle</v-icon>
71
+ </v-btn>
72
+ <div v-else>Input validated: {{ inputIsValidated }}</div>
73
+ </v-form>
74
+ </v-col>
75
+ <v-col cols="12" xl="6">
76
+ <card-browser v-if="inputIsValidated" class="ml-4" :views="shapeViews" :data="[previewInput]" />
77
+ </v-col>
78
+ </v-row>
79
+ </v-container>
80
+ </template>
81
+
82
+ <script lang="ts">
83
+ import { defineComponent } from 'vue';
84
+ import { DataShape } from '@vue-skuilder/common';
85
+ import CardBrowser from '@/components/Edit/CardBrowser.vue';
86
+ import TagsInput, { TagsInputInstance } from '@/components/Edit/TagsInput.vue';
87
+ import { FieldInputInstance, isFieldInput } from '@/components/Edit/ViewableDataInputForm/FieldInput.types';
88
+ import { alertUser } from '@vue-skuilder/common-ui';
89
+ import { allCourses } from '@vue-skuilder/courses';
90
+ import { getDataLayer, CourseDBInterface } from '@vue-skuilder/db';
91
+ import { FieldType, Status, CourseConfig, NameSpacer, ShapeDescriptor } from '@vue-skuilder/common';
92
+ import _ from 'lodash';
93
+ import IntegerInput from './FieldInputs/IntegerInput.vue';
94
+ import MarkdownInput from './FieldInputs/MarkdownInput.vue';
95
+ import MediaDragDropUploader from './FieldInputs/MediaDragDropUploader.vue';
96
+ import MidiInput from './FieldInputs/MidiInput.vue';
97
+ import NumberInput from './FieldInputs/NumberInput.vue';
98
+ import StringInput from './FieldInputs/StringInput.vue';
99
+ import ChessPuzzleInput from './FieldInputs/ChessPuzzleInput.vue';
100
+ import { CourseElo } from '@vue-skuilder/common';
101
+ import { useDataInputFormStore } from '@/stores/useDataInputFormStore';
102
+ import { ViewData } from '@vue-skuilder/common';
103
+ import { Question, getCurrentUser } from '@vue-skuilder/common-ui';
104
+
105
+ type StringIndexable = { [x: string]: unknown };
106
+
107
+ type QorNull = null | typeof Question;
108
+
109
+ export interface ComponentData {
110
+ tag: string;
111
+ tags: string[];
112
+ autoCompleteSuggestions: string[];
113
+ timer?: NodeJS.Timeout;
114
+ dataInputFormStore: ReturnType<typeof useDataInputFormStore>;
115
+ fieldInputRefs: (FieldInputInstance | null)[];
116
+ courseDB: CourseDBInterface | null;
117
+ }
118
+
119
+ export default defineComponent({
120
+ name: 'DataInputForm',
121
+
122
+ components: {
123
+ // AudioInput,
124
+ // ImageInput,
125
+ NumberInput,
126
+ StringInput,
127
+ IntegerInput,
128
+ MarkdownInput,
129
+ MidiInput,
130
+ CardBrowser,
131
+ MediaDragDropUploader,
132
+ TagsInput,
133
+ ChessPuzzleInput,
134
+ },
135
+
136
+ props: {
137
+ courseCfg: {
138
+ type: Object as () => CourseConfig,
139
+ required: true,
140
+ default: () => ({
141
+ courseID: 'default-test',
142
+ }),
143
+ },
144
+ dataShape: {
145
+ type: Object as () => DataShape,
146
+ required: true,
147
+ },
148
+ previewComponent: {
149
+ type: Object as () => QorNull,
150
+ required: false,
151
+ default: null,
152
+ },
153
+ },
154
+
155
+ data(): ComponentData {
156
+ return {
157
+ tag: '',
158
+ tags: [],
159
+ autoCompleteSuggestions: [],
160
+ timer: undefined,
161
+ dataInputFormStore: useDataInputFormStore(),
162
+ fieldInputRefs: [] as (FieldInputInstance | null)[],
163
+ courseDB: null as CourseDBInterface | null,
164
+ };
165
+ },
166
+
167
+ computed: {
168
+ ftString() {
169
+ return FieldType.STRING;
170
+ },
171
+ int() {
172
+ return FieldType.INT;
173
+ },
174
+ num() {
175
+ return FieldType.NUMBER;
176
+ },
177
+ img() {
178
+ return FieldType.IMAGE;
179
+ },
180
+ mkd() {
181
+ return FieldType.MARKDOWN;
182
+ },
183
+ audio() {
184
+ return FieldType.AUDIO;
185
+ },
186
+ midi() {
187
+ return FieldType.MIDI;
188
+ },
189
+ uploader() {
190
+ return FieldType.MEDIA_UPLOADS;
191
+ },
192
+ chessPuzzle() {
193
+ return FieldType.CHESS_PUZZLE;
194
+ },
195
+ fieldInputs(): FieldInputInstance[] {
196
+ return Array.from(this.fieldInputRefs.values()) as FieldInputInstance[];
197
+ },
198
+
199
+ shapeViews: {
200
+ get() {
201
+ return this.dataInputFormStore.dataInputForm.shapeViews;
202
+ },
203
+ set(views: ViewData[]) {
204
+ this.dataInputFormStore.dataInputForm.shapeViews = views;
205
+ },
206
+ },
207
+
208
+ allowSubmit() {
209
+ return this.dataInputFormStore.dataInputForm.fieldStore.isValidated;
210
+ },
211
+
212
+ fieldStore() {
213
+ return this.dataInputFormStore.dataInputForm.fieldStore;
214
+ },
215
+
216
+ uploading: {
217
+ get(): boolean {
218
+ return this.dataInputFormStore.dataInputForm.uploading;
219
+ },
220
+ set(uploading: boolean) {
221
+ this.dataInputFormStore.dataInputForm.uploading = uploading;
222
+ },
223
+ },
224
+
225
+ previewInput() {
226
+ // this.convertInput();
227
+ return this.fieldStore.getPreview as unknown as ViewData;
228
+ },
229
+
230
+ convertedInput() {
231
+ // this.convertInput();
232
+ return this.fieldStore.convertedInput;
233
+ },
234
+
235
+ inputIsValidated(): boolean {
236
+ const store = this.dataInputFormStore.dataInputForm.fieldStore;
237
+ return store.isValidated;
238
+ },
239
+
240
+ datashapeDescriptor(): ShapeDescriptor {
241
+ for (const ds of this.courseCfg.dataShapes) {
242
+ const descriptor = NameSpacer.getDataShapeDescriptor(ds.name);
243
+ if (descriptor.dataShape === this.dataShape.name) {
244
+ return descriptor;
245
+ }
246
+ }
247
+
248
+ return {
249
+ course: '',
250
+ dataShape: '',
251
+ };
252
+ },
253
+ },
254
+
255
+ watch: {
256
+ dataShape: {
257
+ handler() {
258
+ if (!this.previewComponent) {
259
+ this.getImplementingViews();
260
+ }
261
+ },
262
+ immediate: true,
263
+ },
264
+ store: {
265
+ handler() {
266
+ this.convertInput();
267
+ },
268
+ },
269
+ 'dataShape.fields'(newFields) {
270
+ console.log(`[DataInputForm].watch(fields): newFields ${JSON.stringify(newFields)}`);
271
+ console.log(`[DataInputForm].watch(fields): fields ${JSON.stringify(this.dataShape.fields)}`);
272
+ this.fieldInputRefs = new Array(newFields.length).fill(null);
273
+ console.log(`[DataInputForm].watch(fields): fieldRefs ${JSON.stringify(this.fieldInputRefs)}`);
274
+ },
275
+ },
276
+
277
+ created() {
278
+ this.uploading = false;
279
+ this.courseDB = getDataLayer().getCourseDB(this.courseCfg.courseID!);
280
+
281
+ this.getCourseTags();
282
+ this.dataInputFormStore.setDataShape(this.dataShape);
283
+ console.log(`[DataInputForm].created: fields: ${JSON.stringify(this.dataShape.fields)}`);
284
+ this.fieldInputRefs = new Array(this.dataShape.fields.length).fill(null);
285
+
286
+ if (this.previewComponent) {
287
+ this.getImplementingViews();
288
+ }
289
+ },
290
+
291
+ beforeUnmount() {
292
+ this.fieldInputRefs = [];
293
+ },
294
+
295
+ methods: {
296
+ async updateTags(newTags: string[]) {
297
+ console.log(`[DataInputForm] tags updated: ${JSON.stringify(newTags)}`);
298
+ this.tags = newTags;
299
+ },
300
+
301
+ setFieldInputRef(el: FieldInputInstance, index: number) {
302
+ console.log(`[DataInputForm].setFieldInputRef: index: ${index}`);
303
+ // Ensure array is large enough
304
+ if (index >= this.fieldInputRefs.length) {
305
+ this.fieldInputRefs = this.fieldInputRefs.concat(new Array(index - this.fieldInputRefs.length + 1).fill(null));
306
+ }
307
+ // remove any null entries at the end
308
+
309
+ while (this.fieldInputRefs[this.fieldInputRefs.length - 1] === null) {
310
+ this.fieldInputRefs.pop();
311
+ }
312
+
313
+ this.fieldInputRefs[index] = el;
314
+ },
315
+
316
+ async getCourseTags() {
317
+ const existingTags = await this.courseDB!.getCourseTagStubs();
318
+ this.autoCompleteSuggestions = existingTags.rows.map((tagDoc) => {
319
+ return tagDoc.doc!.name;
320
+ });
321
+ },
322
+
323
+ expectedValidations(): number {
324
+ return this.dataShape.fields.length;
325
+ },
326
+
327
+ checkInput(): boolean {
328
+ return true;
329
+ // return this.fieldInputs.every((input) => input.validate());
330
+ },
331
+
332
+ convertInput() {},
333
+
334
+ inputContainsTranspositionFcns(): boolean {
335
+ this.convertInput();
336
+ for (const input in this.convertedInput) {
337
+ if (typeof this.convertedInput[input] === 'function') {
338
+ return true;
339
+ }
340
+ }
341
+ return false;
342
+ },
343
+
344
+ objectContainsFunction(o: StringIndexable): boolean {
345
+ for (const key in o) {
346
+ if (typeof o[key] === 'function') {
347
+ return true;
348
+ }
349
+ }
350
+ return false;
351
+ },
352
+
353
+ expandO(o: StringIndexable): StringIndexable[] {
354
+ let ret: StringIndexable[] = [];
355
+
356
+ if (this.objectContainsFunction(o)) {
357
+ for (const fKey in o) {
358
+ if (typeof o[fKey] === 'function') {
359
+ console.log(`[DataInputForm] Key ${fKey} is a function.`);
360
+ const replaced: StringIndexable[] = [];
361
+
362
+ (o[fKey]() as Array<unknown>).forEach((fcnOutput) => {
363
+ let copy: StringIndexable = {};
364
+ copy = _.cloneDeep(o);
365
+ copy[fKey] = fcnOutput;
366
+
367
+ console.log(`[DataInputForm] Replaced Copy: ${JSON.stringify(copy)}`);
368
+
369
+ replaced.push(copy);
370
+ });
371
+
372
+ replaced.forEach((obj) => {
373
+ if (this.objectContainsFunction(obj)) {
374
+ console.log('[DataInputForm] 2nd pass...');
375
+ const recursiveExpansion = this.expandO(obj);
376
+ ret = ret.concat(recursiveExpansion);
377
+ } else {
378
+ ret.push(obj);
379
+ }
380
+ });
381
+ }
382
+ }
383
+ return ret;
384
+ } else {
385
+ return [];
386
+ }
387
+ },
388
+
389
+ getTags(): string[] {
390
+ const dataShapeParsedTags: string[] = [];
391
+
392
+ this.fieldInputs.forEach((f) => {
393
+ if (f.generateTags) {
394
+ const fTags = f.generateTags();
395
+ dataShapeParsedTags.push(...fTags);
396
+ }
397
+ });
398
+
399
+ const manualTags = (this.$refs.tagsInput as unknown as TagsInputInstance).tags.map((t) => t.text);
400
+
401
+ return dataShapeParsedTags.concat(manualTags);
402
+ },
403
+
404
+ getElo(): CourseElo | undefined {
405
+ for (const f of this.fieldInputs) {
406
+ if (f.generateELO) {
407
+ return f.generateELO();
408
+ }
409
+ }
410
+ return undefined;
411
+ },
412
+
413
+ async submit() {
414
+ if (this.checkInput()) {
415
+ console.log(`[DataInputForm] Store: ${JSON.stringify(this.fieldStore.inputs)}`);
416
+ console.log(`[DataInputForm] ConvertedStore: ${JSON.stringify(this.convertedInput)}`);
417
+ this.uploading = true;
418
+
419
+ let inputs = [];
420
+
421
+ if (this.inputContainsTranspositionFcns()) {
422
+ console.log(`[DataInputForm] Expanded input: ${JSON.stringify(this.expandO(this.convertedInput))}`);
423
+ inputs = this.expandO(this.convertedInput);
424
+ } else {
425
+ console.log(`[DataInputForm] No Transposition fcn detected`);
426
+ inputs = [this.convertedInput];
427
+ }
428
+
429
+ const result = await Promise.all(
430
+ inputs.map(async (input) => {
431
+ return await this.courseDB!.addNote(
432
+ this.datashapeDescriptor.course,
433
+ this.dataShape,
434
+ input,
435
+ (await getCurrentUser()).getUsername(),
436
+ this.getTags(),
437
+ undefined,
438
+ this.getElo()
439
+ );
440
+ })
441
+ );
442
+
443
+ if (result[0].status === Status.ok) {
444
+ alertUser({
445
+ text: `Content added... Thank you!`,
446
+ status: Status.ok,
447
+ });
448
+ const ti = this.$refs.tagsInput as unknown as TagsInputInstance;
449
+ if (ti.tags.length) {
450
+ ti.updateAvailableCourseTags();
451
+ ti.tags = [];
452
+ }
453
+ this.reset();
454
+ } else {
455
+ alertUser({
456
+ text: `A problem occurred. Content has not been added.`,
457
+ status: Status.error,
458
+ });
459
+ console.error(`Error in DataInputForm.submit(). Result from addNote: ${JSON.stringify(result)}`);
460
+ this.uploading = false;
461
+ }
462
+ }
463
+ },
464
+
465
+ reset() {
466
+ console.log(`[DataInputForm].reset()`);
467
+ this.uploading = false;
468
+
469
+ // Clear all field inputs
470
+ this.fieldInputs.forEach((input) => {
471
+ input.clearData();
472
+ });
473
+
474
+ // Reset the field store
475
+ this.fieldStore.$reset();
476
+
477
+ // Focus the first input
478
+ this.fieldInputs[0].focus();
479
+
480
+ // Reinitialize converted inputs
481
+ this.convertInput();
482
+ },
483
+
484
+ getImplementingViews() {
485
+ if (this.previewComponent) {
486
+ console.log(`[DataInputForm] Getting previewComponent views`);
487
+ this.shapeViews = this.previewComponent.views;
488
+ return;
489
+ }
490
+
491
+ for (const ds of this.courseCfg.dataShapes) {
492
+ const descriptor = NameSpacer.getDataShapeDescriptor(ds.name);
493
+
494
+ console.log('[DataInputForm] descriptor', descriptor);
495
+ console.log('[DataInputForm] this.dataShape', this.dataShape);
496
+ console.log('[DataInputForm] this.dataShape.name', this.dataShape.name);
497
+
498
+ if (descriptor.dataShape === this.dataShape.name) {
499
+ const crs = allCourses.getCourse(descriptor.course)!;
500
+
501
+ this.shapeViews = [];
502
+
503
+ crs.getBaseQTypes().forEach((qType) => {
504
+ if (qType.dataShapes[0].name === this.dataShape.name) {
505
+ this.shapeViews = this.shapeViews.concat(qType.views);
506
+ }
507
+ });
508
+
509
+ for (const q of ds.questionTypes) {
510
+ const qDescriptor = NameSpacer.getQuestionDescriptor(q);
511
+ crs.getQuestion(qDescriptor.questionType)!.views.forEach((view) => {
512
+ this.shapeViews = this.shapeViews.concat(view);
513
+ });
514
+ }
515
+ }
516
+ }
517
+ },
518
+
519
+ isFieldInput(component: unknown): component is FieldInputInstance {
520
+ return isFieldInput(component);
521
+ },
522
+ },
523
+ });
524
+ </script>
@@ -0,0 +1,33 @@
1
+ import { CourseElo, ValidatingFunction, ValidationResult } from '@vue-skuilder/common';
2
+ import { ComponentPublicInstance } from 'vue';
3
+
4
+ export interface FieldInputInterface {
5
+ $refs: {
6
+ inputField: HTMLInputElement;
7
+ };
8
+ validationStatus: ValidationResult;
9
+ validators: ValidatingFunction[];
10
+ focus: () => void;
11
+ userInput: () => unknown;
12
+ setData: (data: unknown) => void;
13
+ clearData: () => void;
14
+ vuetifyRules: () => Array<(value: unknown) => boolean | string>;
15
+ generateTags: () => string[];
16
+ generateELO: () => CourseElo | undefined;
17
+ validate: () => ValidationResult;
18
+ }
19
+
20
+ // Type guard
21
+ export function isFieldInput(component: unknown): component is FieldInputInstance {
22
+ return (
23
+ component !== null &&
24
+ typeof component === 'object' &&
25
+ 'clearData' in component &&
26
+ 'validate' in component &&
27
+ typeof (component as Record<string, unknown>).clearData === 'function' &&
28
+ typeof (component as Record<string, unknown>).validate === 'function'
29
+ );
30
+ }
31
+
32
+ // This combines the Vue component instance type with our interface
33
+ export type FieldInputInstance = ComponentPublicInstance & FieldInputInterface;