@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,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>
|
package/src/enums.ts
ADDED
|
File without changes
|