|
1 | 1 | <template> |
2 | | - <v-card v-if="!updatePending"> |
3 | | - <paginating-toolbar |
4 | | - title="Exercises" |
5 | | - :page="page" |
6 | | - :pages="pages" |
7 | | - :subtitle="`(${questionCount})`" |
8 | | - @first="first" |
9 | | - @prev="prev" |
10 | | - @next="next" |
11 | | - @last="last" |
12 | | - @set-page="(n) => setPage(n)" |
13 | | - /> |
| 2 | + <v-card> |
| 3 | + <div v-if="updatePending" class="d-flex justify-center align-center pa-6"> |
| 4 | + <v-progress-circular indeterminate color="primary" /> |
| 5 | + </div> |
| 6 | + <div v-else> |
| 7 | + <paginating-toolbar |
| 8 | + title="Exercises" |
| 9 | + :page="page" |
| 10 | + :pages="pages" |
| 11 | + :subtitle="`(${questionCount})`" |
| 12 | + @first="first" |
| 13 | + @prev="prev" |
| 14 | + @next="next" |
| 15 | + @last="last" |
| 16 | + @set-page="(n) => setPage(n)" |
| 17 | + /> |
14 | 18 |
|
15 | | - <v-list> |
16 | | - <template v-for="c in cards" :key="c.id"> |
17 | | - <v-list-item |
18 | | - :class="{ |
19 | | - 'bg-blue-grey-lighten-5': c.isOpen, |
20 | | - 'elevation-4': c.isOpen, |
21 | | - }" |
22 | | - density="compact" |
23 | | - data-cy="course-card" |
24 | | - > |
25 | | - <template #prepend> |
26 | | - <div> |
27 | | - <v-list-item-title :class="{ 'text-blue-grey-darken-1': c.isOpen }" class="font-weight-medium"> |
28 | | - {{ cardPreview[c.id] }} |
29 | | - </v-list-item-title> |
30 | | - <v-list-item-subtitle> |
31 | | - {{ c.id.split('-').length === 3 ? c.id.split('-')[2] : '' }} |
32 | | - </v-list-item-subtitle> |
33 | | - </div> |
34 | | - </template> |
| 19 | + <v-list> |
| 20 | + <template v-for="c in cards" :key="c.id"> |
| 21 | + <v-list-item |
| 22 | + :class="{ |
| 23 | + 'bg-blue-grey-lighten-5': c.isOpen, |
| 24 | + 'elevation-4': c.isOpen, |
| 25 | + }" |
| 26 | + density="compact" |
| 27 | + data-cy="course-card" |
| 28 | + > |
| 29 | + <template #prepend> |
| 30 | + <div> |
| 31 | + <v-list-item-title :class="{ 'text-blue-grey-darken-1': c.isOpen }" class="font-weight-medium"> |
| 32 | + {{ cardPreview[c.id] }} |
| 33 | + </v-list-item-title> |
| 34 | + <v-list-item-subtitle> |
| 35 | + {{ c.id.split('-').length === 3 ? c.id.split('-')[2] : '' }} |
| 36 | + </v-list-item-subtitle> |
| 37 | + </div> |
| 38 | + </template> |
| 39 | + |
| 40 | + <template #append> |
| 41 | + <v-speed-dial |
| 42 | + v-model="c.isOpen" |
| 43 | + location="left center" |
| 44 | + transition="slide-x-transition" |
| 45 | + style="display: flex; flex-direction: row-reverse" |
| 46 | + > |
| 47 | + <template #activator="{ props }"> |
| 48 | + <v-btn |
| 49 | + v-bind="props" |
| 50 | + :icon="c.isOpen ? 'mdi-close' : 'mdi-plus'" |
| 51 | + size="small" |
| 52 | + variant="text" |
| 53 | + @click="clearSelections(c.id)" |
| 54 | + /> |
| 55 | + </template> |
35 | 56 |
|
36 | | - <template #append> |
37 | | - <v-speed-dial |
38 | | - v-model="c.isOpen" |
39 | | - location="left center" |
40 | | - transition="slide-x-transition" |
41 | | - style="display: flex; flex-direction: row-reverse" |
42 | | - > |
43 | | - <template #activator="{ props }"> |
44 | 57 | <v-btn |
45 | | - v-bind="props" |
46 | | - :icon="c.isOpen ? 'mdi-close' : 'mdi-plus'" |
| 58 | + key="tags" |
| 59 | + icon |
47 | 60 | size="small" |
48 | | - variant="text" |
49 | | - @click="clearSelections(c.id)" |
50 | | - /> |
51 | | - </template> |
| 61 | + :variant="editMode !== 'tags' ? 'outlined' : 'elevated'" |
| 62 | + :color="editMode === 'tags' ? 'teal' : 'teal-darken-3'" |
| 63 | + @click.stop="editMode = 'tags'" |
| 64 | + > |
| 65 | + <v-icon>mdi-bookmark</v-icon> |
| 66 | + </v-btn> |
52 | 67 |
|
53 | | - <v-btn |
54 | | - key="tags" |
55 | | - icon |
56 | | - size="small" |
57 | | - :variant="editMode !== 'tags' ? 'outlined' : 'elevated'" |
58 | | - :color="editMode === 'tags' ? 'teal' : 'teal-darken-3'" |
59 | | - @click.stop="editMode = 'tags'" |
60 | | - > |
61 | | - <v-icon>mdi-bookmark</v-icon> |
62 | | - </v-btn> |
63 | | - |
64 | | - <v-btn |
65 | | - key="flag" |
66 | | - icon |
67 | | - size="small" |
68 | | - :variant="editMode !== 'flag' ? 'outlined' : 'elevated'" |
69 | | - :color="editMode === 'flag' ? 'error' : 'error-darken-3'" |
70 | | - @click.stop="editMode = 'flag'" |
71 | | - > |
72 | | - <v-icon>mdi-flag</v-icon> |
73 | | - </v-btn> |
74 | | - </v-speed-dial> |
75 | | - </template> |
76 | | - </v-list-item> |
| 68 | + <v-btn |
| 69 | + key="flag" |
| 70 | + icon |
| 71 | + size="small" |
| 72 | + :variant="editMode !== 'flag' ? 'outlined' : 'elevated'" |
| 73 | + :color="editMode === 'flag' ? 'error' : 'error-darken-3'" |
| 74 | + @click.stop="editMode = 'flag'" |
| 75 | + > |
| 76 | + <v-icon>mdi-flag</v-icon> |
| 77 | + </v-btn> |
| 78 | + </v-speed-dial> |
| 79 | + </template> |
| 80 | + </v-list-item> |
77 | 81 |
|
78 | | - <div v-if="c.isOpen" class="px-4 py-2 bg-blue-grey-lighten-5"> |
79 | | - <card-loader :qualified_id="c.id" :view-lookup="viewLookup" class="elevation-1" /> |
| 82 | + <div v-if="c.isOpen" class="px-4 py-2 bg-blue-grey-lighten-5"> |
| 83 | + <card-loader :qualified_id="c.id" :view-lookup="viewLookup" class="elevation-1" /> |
80 | 84 |
|
81 | | - <tags-input v-show="editMode === 'tags'" :course-i-d="courseId" :card-i-d="c.id.split('-')[1]" class="mt-4" /> |
| 85 | + <tags-input |
| 86 | + v-show="editMode === 'tags'" |
| 87 | + :course-i-d="courseId" |
| 88 | + :card-i-d="c.id.split('-')[1]" |
| 89 | + class="mt-4" |
| 90 | + /> |
82 | 91 |
|
83 | | - <div v-show="editMode === 'flag'" class="mt-4"> |
84 | | - <v-btn color="error" variant="outlined" @click="c.delBtn = true"> Delete this card </v-btn> |
85 | | - <span v-if="c.delBtn" class="ml-4"> |
86 | | - <span class="mr-2">Are you sure?</span> |
87 | | - <v-btn color="error" variant="elevated" @click="deleteCard(c.id)"> Confirm </v-btn> |
88 | | - </span> |
| 92 | + <div v-show="editMode === 'flag'" class="mt-4"> |
| 93 | + <v-btn color="error" variant="outlined" @click="c.delBtn = true"> Delete this card </v-btn> |
| 94 | + <span v-if="c.delBtn" class="ml-4"> |
| 95 | + <span class="mr-2">Are you sure?</span> |
| 96 | + <v-btn color="error" variant="elevated" @click="deleteCard(c.id)"> Confirm </v-btn> |
| 97 | + </span> |
| 98 | + </div> |
89 | 99 | </div> |
90 | | - </div> |
91 | | - </template> |
92 | | - </v-list> |
| 100 | + </template> |
| 101 | + </v-list> |
93 | 102 |
|
94 | | - <paginating-toolbar |
95 | | - class="elevation-0" |
96 | | - :page="page" |
97 | | - :pages="pages" |
98 | | - @first="first" |
99 | | - @prev="prev" |
100 | | - @next="next" |
101 | | - @last="last" |
102 | | - @set-page="(n) => setPage(n)" |
103 | | - /> |
| 103 | + <paginating-toolbar |
| 104 | + class="elevation-0" |
| 105 | + :page="page" |
| 106 | + :pages="pages" |
| 107 | + @first="first" |
| 108 | + @prev="prev" |
| 109 | + @next="next" |
| 110 | + @last="last" |
| 111 | + @set-page="(n) => setPage(n)" |
| 112 | + /> |
| 113 | + </div> |
104 | 114 | </v-card> |
105 | 115 | </template> |
106 | 116 |
|
@@ -164,19 +174,25 @@ export default defineComponent({ |
164 | 174 | }, |
165 | 175 |
|
166 | 176 | async created() { |
167 | | - this.courseDB = getDataLayer().getCourseDB(this.courseId); |
| 177 | + try { |
| 178 | + this.courseDB = getDataLayer().getCourseDB(this.courseId); |
168 | 179 |
|
169 | | - if (this.tagId) { |
170 | | - this.questionCount = (await this.courseDB.getTag(this.tagId)).taggedCards.length; |
171 | | - } else { |
172 | | - this.questionCount = (await this.courseDB!.getCourseInfo()).cardCount; |
173 | | - } |
| 180 | + if (this.tagId) { |
| 181 | + this.questionCount = (await this.courseDB.getTag(this.tagId)).taggedCards.length; |
| 182 | + } else { |
| 183 | + this.questionCount = (await this.courseDB!.getCourseInfo()).cardCount; |
| 184 | + } |
174 | 185 |
|
175 | | - for (let i = 1; (i - 1) * 25 < this.questionCount; i++) { |
176 | | - this.pages.push(i); |
177 | | - } |
| 186 | + for (let i = 1; (i - 1) * 25 < this.questionCount; i++) { |
| 187 | + this.pages.push(i); |
| 188 | + } |
178 | 189 |
|
179 | | - await this.populateTableData(); |
| 190 | + await this.populateTableData(); |
| 191 | + } catch (error) { |
| 192 | + console.error('Error initializing CourseCardBrowser:', error); |
| 193 | + } finally { |
| 194 | + this.updatePending = false; |
| 195 | + } |
180 | 196 | }, |
181 | 197 |
|
182 | 198 | methods: { |
@@ -224,6 +240,7 @@ export default defineComponent({ |
224 | 240 | } |
225 | 241 | }, |
226 | 242 | async populateTableData() { |
| 243 | + this.updatePending = true; |
227 | 244 | if (this.tagId) { |
228 | 245 | const tag = await this.courseDB!.getTag(this.tagId); |
229 | 246 | this.cards = tag.taggedCards.map((c) => { |
@@ -270,47 +287,53 @@ export default defineComponent({ |
270 | 287 | } |
271 | 288 | }); |
272 | 289 |
|
273 | | - this.cards.forEach(async (c) => { |
274 | | - const _cardID: string = c.id.split('-')[1]; |
| 290 | + try { |
| 291 | + await Promise.all( |
| 292 | + this.cards.map(async (c) => { |
| 293 | + const _cardID: string = c.id.split('-')[1]; |
275 | 294 |
|
276 | | - const tmpCardData = hydratedCardData.find((c) => c._id == _cardID); |
277 | | - if (!tmpCardData || !tmpCardData.id_displayable_data) { |
278 | | - console.error(`No valid data found for card ${_cardID}`); |
279 | | - return; |
280 | | - } |
281 | | - const tmpView: ViewComponent = allCourses.getView( |
282 | | - tmpCardData.id_view || 'default.question.BlanksCard.FillInView' |
283 | | - ); |
| 295 | + const tmpCardData = hydratedCardData.find((c) => c._id == _cardID); |
| 296 | + if (!tmpCardData || !tmpCardData.id_displayable_data) { |
| 297 | + console.error(`No valid data found for card ${_cardID}`); |
| 298 | + return; |
| 299 | + } |
| 300 | + const tmpView: ViewComponent = allCourses.getView( |
| 301 | + tmpCardData.id_view || 'default.question.BlanksCard.FillInView' |
| 302 | + ); |
284 | 303 |
|
285 | | - const tmpDataDocs = tmpCardData.id_displayable_data.map((id) => { |
286 | | - return this.courseDB!.getCourseDoc<DisplayableData>(id, { |
287 | | - attachments: false, |
288 | | - binary: true, |
289 | | - }); |
290 | | - }); |
| 304 | + const tmpDataDocs = tmpCardData.id_displayable_data.map((id) => { |
| 305 | + return this.courseDB!.getCourseDoc<DisplayableData>(id, { |
| 306 | + attachments: false, |
| 307 | + binary: true, |
| 308 | + }); |
| 309 | + }); |
291 | 310 |
|
292 | | - const allDocs = await Promise.all(tmpDataDocs); |
293 | | - await Promise.all( |
294 | | - allDocs.map((doc) => { |
295 | | - const tmpData = []; |
296 | | - tmpData.unshift(displayableDataToViewData(doc)); |
| 311 | + const allDocs = await Promise.all(tmpDataDocs); |
| 312 | + await Promise.all( |
| 313 | + allDocs.map((doc) => { |
| 314 | + const tmpData = []; |
| 315 | + tmpData.unshift(displayableDataToViewData(doc)); |
297 | 316 |
|
298 | | - // [ ] remove/replace this after the vue 3 migration is complete |
299 | | - // see PR #510 |
300 | | - if (isConstructor(tmpView)) { |
301 | | - const view = new tmpView(); |
302 | | - view.data = tmpData; |
| 317 | + // [ ] remove/replace this after the vue 3 migration is complete |
| 318 | + // see PR #510 |
| 319 | + if (isConstructor(tmpView)) { |
| 320 | + const view = new tmpView(); |
| 321 | + view.data = tmpData; |
303 | 322 |
|
304 | | - this.cardPreview[c.id] = view.toString(); |
305 | | - } else { |
306 | | - this.cardPreview[c.id] = tmpView.name ? tmpView.name : 'Unknown'; |
307 | | - } |
| 323 | + this.cardPreview[c.id] = view.toString(); |
| 324 | + } else { |
| 325 | + this.cardPreview[c.id] = tmpView.name ? tmpView.name : 'Unknown'; |
| 326 | + } |
| 327 | + }) |
| 328 | + ); |
308 | 329 | }) |
309 | 330 | ); |
310 | | -
|
| 331 | + } catch (error) { |
| 332 | + console.error('Error populating table data:', error); |
| 333 | + } finally { |
311 | 334 | this.updatePending = false; |
312 | 335 | this.$forceUpdate(); |
313 | | - }); |
| 336 | + } |
314 | 337 | }, |
315 | 338 | }, |
316 | 339 | }); |
|
0 commit comments