@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.
- package/LICENCE +661 -0
- package/README.md +64 -0
- package/dist/assets/Roboto-Black-B0ZKieaB.woff +0 -0
- package/dist/assets/Roboto-Black-VhoA2qKx.woff2 +0 -0
- package/dist/assets/Roboto-BlackItalic-D0gSnuIb.woff +0 -0
- package/dist/assets/Roboto-BlackItalic-D4yie1YO.woff2 +0 -0
- package/dist/assets/Roboto-Bold-D9plYbeK.woff +0 -0
- package/dist/assets/Roboto-Bold-hN3duQhD.woff2 +0 -0
- package/dist/assets/Roboto-BoldItalic-BWDm51uc.woff2 +0 -0
- package/dist/assets/Roboto-BoldItalic-CyLKvOHD.woff +0 -0
- package/dist/assets/Roboto-Light-Cu-PAxXt.woff +0 -0
- package/dist/assets/Roboto-Light-DHTugVNA.woff2 +0 -0
- package/dist/assets/Roboto-LightItalic-CZg5kHIB.woff +0 -0
- package/dist/assets/Roboto-LightItalic-JQyp2Y3P.woff2 +0 -0
- package/dist/assets/Roboto-Medium-ByKogCTi.woff2 +0 -0
- package/dist/assets/Roboto-Medium-b81vv18W.woff +0 -0
- package/dist/assets/Roboto-MediumItalic-DFQ-RYa0.woff +0 -0
- package/dist/assets/Roboto-MediumItalic-i1eR0KbF.woff2 +0 -0
- package/dist/assets/Roboto-Regular-BX5l9hRW.woff +0 -0
- package/dist/assets/Roboto-Regular-C6rbFxYz.woff2 +0 -0
- package/dist/assets/Roboto-RegularItalic-BjnLZsam.woff +0 -0
- package/dist/assets/Roboto-RegularItalic-CvPUdkvM.woff2 +0 -0
- package/dist/assets/Roboto-Thin-BfJvJcog.woff +0 -0
- package/dist/assets/Roboto-Thin-NicBC1pN.woff2 +0 -0
- package/dist/assets/Roboto-ThinItalic-CKlCjrO_.woff2 +0 -0
- package/dist/assets/Roboto-ThinItalic-DnIWFxRE.woff +0 -0
- package/dist/assets/index-CQ-sNKGW.css +14 -0
- package/dist/assets/index-EbqpUgvM.js +161 -0
- package/dist/assets/materialdesignicons-webfont-B7mPwVP_.ttf +0 -0
- package/dist/assets/materialdesignicons-webfont-CSr8KVlo.eot +0 -0
- package/dist/assets/materialdesignicons-webfont-Dp5v-WZN.woff2 +0 -0
- package/dist/assets/materialdesignicons-webfont-PXm3-2wK.woff +0 -0
- package/dist/assets/workbox-window.prod.es5-p40uij6f.js +1 -0
- package/dist/favicon.ico +0 -0
- package/dist/img/icons/safari-pinned-tab.svg +149 -0
- package/dist/index.html +19 -0
- package/dist/manifest.json +20 -0
- package/dist/manifest.webmanifest +1 -0
- package/dist/robots.txt +2 -0
- package/dist/sw.js +1 -0
- package/dist/workbox-1be04862.js +1 -0
- package/package.json +105 -0
- package/src/App.vue +156 -0
- package/src/ENVIRONMENT_VARS.ts +79 -0
- package/src/components/Classrooms/ClassroomCtrlPanel.vue +206 -0
- package/src/components/Classrooms/CreateClassroom.vue +159 -0
- package/src/components/Classrooms/JoinCode.vue +83 -0
- package/src/components/Courses/CourseCardBrowser.vue +365 -0
- package/src/components/Courses/CourseEditor.vue +164 -0
- package/src/components/Courses/CourseInformation.vue +164 -0
- package/src/components/Courses/CourseRouter.vue +116 -0
- package/src/components/Courses/CourseStubCard.vue +76 -0
- package/src/components/Courses/EloModeration.vue +122 -0
- package/src/components/Courses/TagInformation.vue +209 -0
- package/src/components/Edit/BulkImport/CardPreviewList.vue +345 -0
- package/src/components/Edit/BulkImportView.vue +633 -0
- package/src/components/Edit/CardBrowser.vue +79 -0
- package/src/components/Edit/ComponentRegistration/ComponentRegistration.vue +235 -0
- package/src/components/Edit/ComponentRegistration/UnregisteredComponentsTable.vue +19 -0
- package/src/components/Edit/CourseEditor.vue +162 -0
- package/src/components/Edit/NavigationStrategy/NavigationStrategyEditor.vue +170 -0
- package/src/components/Edit/NavigationStrategy/NavigationStrategyList.vue +92 -0
- package/src/components/Edit/TagsInput.vue +247 -0
- package/src/components/Edit/ViewableDataInputForm/DataInputForm.vue +524 -0
- package/src/components/Edit/ViewableDataInputForm/FieldInput.types.ts +33 -0
- package/src/components/Edit/ViewableDataInputForm/FieldInputs/AudioInput.vue +188 -0
- package/src/components/Edit/ViewableDataInputForm/FieldInputs/ChessPuzzleInput.vue +79 -0
- package/src/components/Edit/ViewableDataInputForm/FieldInputs/FieldInput.css +12 -0
- package/src/components/Edit/ViewableDataInputForm/FieldInputs/ImageInput.vue +231 -0
- package/src/components/Edit/ViewableDataInputForm/FieldInputs/IntegerInput.vue +49 -0
- package/src/components/Edit/ViewableDataInputForm/FieldInputs/MarkdownInput.vue +34 -0
- package/src/components/Edit/ViewableDataInputForm/FieldInputs/MediaDragDropUploader.vue +246 -0
- package/src/components/Edit/ViewableDataInputForm/FieldInputs/MidiInput.vue +113 -0
- package/src/components/Edit/ViewableDataInputForm/FieldInputs/NumberInput.vue +49 -0
- package/src/components/Edit/ViewableDataInputForm/FieldInputs/StringInput.vue +49 -0
- package/src/components/Edit/ViewableDataInputForm/FieldInputs/typeValidators.ts +49 -0
- package/src/components/Edit/ViewableDataInputForm/OptionsFieldInput.ts +161 -0
- package/src/components/Study/SessionConfiguration.vue +371 -0
- package/src/components/TextSwap.vue +65 -0
- package/src/components/User/UserStats.vue +30 -0
- package/src/dev/DataInputFormTester.vue +117 -0
- package/src/dev/readme.md +3 -0
- package/src/enums.ts +0 -0
- package/src/glyphs.txt +933 -0
- package/src/main.ts +45 -0
- package/src/plugins/vuetify.ts +41 -0
- package/src/registerServiceWorker.ts +18 -0
- package/src/router.ts +184 -0
- package/src/server/index.spec.ts +192 -0
- package/src/server/index.ts +71 -0
- package/src/shims-vue.d.ts +5 -0
- package/src/store.mock.ts +122 -0
- package/src/stores/useDataInputFormStore.ts +49 -0
- package/src/stores/useFieldInputStore.ts +191 -0
- package/src/types/shims-vuetify.d.ts +12 -0
- package/src/types/svg.d.ts +4 -0
- package/src/utils/bulkImport/index.ts +94 -0
- package/src/views/About.vue +29 -0
- package/src/views/Admin.vue +128 -0
- package/src/views/Classrooms.vue +258 -0
- package/src/views/Courses.vue +265 -0
- package/src/views/Home.vue +154 -0
- package/src/views/Login.vue +75 -0
- package/src/views/ReleaseNotes.vue +20 -0
- package/src/views/SignUp.vue +32 -0
- package/src/views/Study.vue +261 -0
- package/src/views/User.vue +109 -0
- 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;
|