@windward/games 0.16.0 → 0.17.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # Changelog
2
2
 
3
+ ### Release [0.17.0] created - 2025-05-13
4
+
5
+
3
6
  ### Release [0.16.0] created - 2025-04-29
4
7
 
5
8
 
@@ -8,7 +8,8 @@
8
8
  </v-row>
9
9
  <v-row
10
10
  v-if="
11
- settings.fileConfig.asset && settings.fileConfig.asset.file_asset_id
11
+ settings.fileConfig.asset &&
12
+ settings.fileConfig.asset.file_asset_id
12
13
  "
13
14
  >
14
15
  <v-col>
@@ -1,15 +1,17 @@
1
1
  <template>
2
2
  <v-container
3
+ :key="updateCardKey"
3
4
  style="height: 100%"
4
5
  class="div-container"
5
- :key="updateCardKey"
6
6
  >
7
7
  <v-card
8
- outlined
9
- @click="toggleCard"
8
+ ref="cardFront"
10
9
  v-show="side"
10
+ outlined
11
11
  :class="cardClass"
12
12
  style="height: 90%"
13
+ @keyup.enter="toggleCard"
14
+ @click="toggleCard"
13
15
  >
14
16
  <v-card-text class="fill-height">
15
17
  <CardFace
@@ -20,11 +22,13 @@
20
22
  </v-card-text>
21
23
  </v-card>
22
24
  <v-card
23
- outlined
24
- @click="toggleCard"
25
+ ref="cardBack"
25
26
  v-show="!side"
27
+ outlined
26
28
  :class="cardClass"
27
29
  style="height: 90%"
30
+ @keyup.enter="toggleCard"
31
+ @click="toggleCard"
28
32
  >
29
33
  <v-card-title class="card-title">
30
34
  <v-row align="center" justify="center">{{
@@ -118,6 +122,12 @@ export default {
118
122
  this.side = !this.side
119
123
  // update container with correct side
120
124
  this.updateCardKey = Crypto.id()
125
+ this.$nextTick(() => {
126
+ const target = this.side
127
+ ? this.$refs.cardFront
128
+ : this.$refs.cardBack
129
+ target.$el.focus()
130
+ })
121
131
  },
122
132
  },
123
133
  }
@@ -71,7 +71,10 @@
71
71
  @keyup.enter="onChooseAnswer(answer, question)"
72
72
  >
73
73
  <div class="d-flex justify-space-between">
74
- <p class="mb-0">
74
+ <p
75
+ class="mb-0"
76
+ :aria-labelledby="answer.value"
77
+ >
75
78
  {{
76
79
  getCharacter(question, answer) +
77
80
  '. ' +
@@ -86,6 +89,18 @@
86
89
  answer
87
90
  ) !== undefined
88
91
  "
92
+ :ref="
93
+ 'icon-information-' +
94
+ question.id +
95
+ '-' +
96
+ answer.id
97
+ "
98
+ tabindex="0"
99
+ :aria-label="
100
+ $t(
101
+ 'windward.games.components.content.blocks.multiple_choice.information'
102
+ )
103
+ "
89
104
  :class="
90
105
  handleInformationClass(
91
106
  question,
@@ -93,7 +108,10 @@
93
108
  )
94
109
  "
95
110
  @click="
96
- onAnswerDescription(question)
111
+ onAnswerDescription(
112
+ question,
113
+ answer
114
+ )
97
115
  "
98
116
  >{{
99
117
  handleInformationOutline(
@@ -112,6 +130,7 @@
112
130
  <v-col cols="12" md="12">
113
131
  <v-row class="d-flex justify-center">
114
132
  <v-btn
133
+ :ref="'btn-hint-' + question.id"
115
134
  color="primary"
116
135
  text
117
136
  elevation="0"
@@ -179,7 +198,6 @@
179
198
  :trigger="false"
180
199
  @click:outside="close"
181
200
  @click:close="close"
182
- @keydown.esc="close"
183
201
  >
184
202
  <template #title>
185
203
  <div v-if="hint">
@@ -253,6 +271,8 @@ export default {
253
271
  answerDescriptionModal: false,
254
272
  answerDescription: '',
255
273
  studentResponses: [],
274
+ activeQuestion: '',
275
+ activeAnswer: '',
256
276
  }
257
277
  },
258
278
  computed: {
@@ -282,7 +302,9 @@ export default {
282
302
  },
283
303
  },
284
304
  methods: {
285
- onAnswerDescription(question) {
305
+ onAnswerDescription(question, answer) {
306
+ this.activeQuestion = question
307
+ this.activeAnswer = answer
286
308
  //launches modal and displays anwer description
287
309
  this.dialog = true
288
310
  this.answerDescriptionModal = true
@@ -471,6 +493,7 @@ export default {
471
493
  return letters[answerIndex]
472
494
  },
473
495
  onHint(question) {
496
+ this.activeQuestion = question
474
497
  // launches dialog modal
475
498
  this.dialog = true
476
499
  this.hint = true
@@ -514,11 +537,28 @@ export default {
514
537
  return array
515
538
  },
516
539
  close() {
540
+ if (this.answerDescriptionModal) {
541
+ this.answerDescriptionModal = false
542
+ this.answerDescription = ''
543
+ const iconButton =
544
+ this.$refs[
545
+ 'icon-information-' +
546
+ this.activeQuestion.id +
547
+ '-' +
548
+ this.activeAnswer.id
549
+ ][0].$el
550
+ iconButton.focus()
551
+ this.activeAnswer = ''
552
+ this.activeQuestion = ''
553
+ } else if (this.hint) {
554
+ this.hint = false
555
+ this.hintText = ''
556
+ const hintButton =
557
+ this.$refs['btn-hint-' + this.activeQuestion.id][0].$el
558
+ hintButton.focus()
559
+ this.activeQuestion = ''
560
+ }
517
561
  this.dialog = false
518
- this.hint = false
519
- this.answerDescriptionModal = false
520
- this.hintText = ''
521
- this.answerDescription = ''
522
562
  },
523
563
  },
524
564
  }
@@ -277,15 +277,31 @@
277
277
  indeterminate
278
278
  ></v-progress-circular>
279
279
  </div>
280
+ <v-container class="pa-4 mb-6">
281
+ <v-row>
282
+ <v-col cols="12">
283
+ <GenerateAIQuestionButton
284
+ :course="course"
285
+ :content="content"
286
+ :block="block"
287
+ question-type="flashcard"
288
+ :replace-existing-mode="replaceExisting"
289
+ @click:generate="onGeneratedFlashcards"
290
+ ></GenerateAIQuestionButton>
291
+ </v-col>
292
+ </v-row>
293
+ </v-container>
280
294
  </div>
281
295
  </template>
282
296
 
283
297
  <script>
284
298
  import _ from 'lodash'
299
+ import { mapGetters } from 'vuex'
285
300
  import {
286
301
  MathExpressionEditor,
287
302
  MathLiveWrapper,
288
303
  ContentViewer,
304
+ GenerateAIQuestionButton
289
305
  } from '@windward/core/utils'
290
306
  import BaseContentSettings from '~/components/Content/Settings/BaseContentSettings.js'
291
307
  import ContentBlockAsset from '~/components/Content/ContentBlockAsset.vue'
@@ -305,11 +321,13 @@ export default {
305
321
  TextEditor,
306
322
  SortableExpansionPanel,
307
323
  ImageAssetSettings,
324
+ GenerateAIQuestionButton,
308
325
  },
309
326
  data() {
310
327
  return {
311
328
  loading: false,
312
329
  valid: true,
330
+ replaceExisting: false,
313
331
  }
314
332
  },
315
333
  beforeMount() {
@@ -342,6 +360,10 @@ export default {
342
360
  },
343
361
 
344
362
  computed: {
363
+ ...mapGetters({
364
+ course: 'course/get',
365
+ content: 'content/get',
366
+ }),
345
367
  defaultCard() {
346
368
  return {
347
369
  front: {
@@ -440,6 +462,72 @@ export default {
440
462
  }
441
463
  return htmlString.replace(/(<([^>]+)>)/gi, '')
442
464
  },
465
+ // Handler for receiving flashcards from GenerateAIQuestionButton
466
+ onGeneratedFlashcards(activityData, replaceCards) {
467
+ this.loading = true
468
+ try {
469
+ // Now process the activity data
470
+ if (activityData && activityData.metadata &&
471
+ activityData.metadata.config &&
472
+ activityData.metadata.config.cards &&
473
+ Array.isArray(activityData.metadata.config.cards)) {
474
+
475
+ // Save new cards
476
+ const newCards = activityData.metadata.config.cards.map(card => ({
477
+ front: { ...card.front },
478
+ back: { ...card.back },
479
+ side: card.side !== undefined ? card.side : true,
480
+ expand: false
481
+ }))
482
+
483
+ if (replaceCards) {
484
+ // Replace mode: Clear existing cards
485
+ this.block.metadata.config.cards.splice(0, this.block.metadata.config.cards.length)
486
+
487
+ // Add all new cards
488
+ newCards.forEach(card => {
489
+ this.block.metadata.config.cards.push(card)
490
+ })
491
+ } else {
492
+ // Merge mode: Add new cards to existing ones
493
+ newCards.forEach(card => {
494
+ this.block.metadata.config.cards.push(card)
495
+ })
496
+ }
497
+
498
+ // Update title and instructions if provided and we're in replace mode
499
+ if (replaceCards) {
500
+ if (activityData.metadata.config.title) {
501
+ this.block.metadata.config.title = activityData.metadata.config.title
502
+ }
503
+
504
+ if (activityData.metadata.config.instructions) {
505
+ this.block.metadata.config.instructions = activityData.metadata.config.instructions
506
+ }
507
+ }
508
+
509
+ this.$toast.success(
510
+ replaceCards
511
+ ? this.$t('windward.games.components.settings.flashcard.form.replaced_successfully')
512
+ : this.$t('windward.games.components.settings.flashcard.form.added_successfully'),
513
+ { duration: 3000 }
514
+ )
515
+ } else {
516
+ this.$toast.error(this.$t('windward.games.components.settings.flashcard.form.invalid_response'), {
517
+ duration: 5000
518
+ })
519
+ }
520
+ } catch (error) {
521
+ // Extract error message from the response
522
+ const errorMessage = error.message || 'Unknown error occurred'
523
+ this.$toast.error(`${this.$t('windward.games.components.settings.flashcard.form.failed_to_process')}: ${errorMessage}`, {
524
+ duration: 5000
525
+ })
526
+ } finally {
527
+ this.loading = false
528
+ }
529
+ }
443
530
  },
444
531
  }
445
532
  </script>
533
+
@@ -5,4 +5,5 @@ export default {
5
5
  hint_title: 'Hint',
6
6
  answer_feedback: 'Answer Feedback',
7
7
  total_points: 'Total Points for Course',
8
+ information: 'Answer Information',
8
9
  }
@@ -26,5 +26,10 @@ export default {
26
26
  'The character count for this field must be less than',
27
27
  },
28
28
  instructions:"Click on each card to reveal the term's definition. Use the arrows to move through the deck.",
29
+ replace_existing: 'Replace existing flashcards',
30
+ replaced_successfully: 'Flashcards replaced successfully',
31
+ added_successfully: 'New flashcards added successfully',
32
+ invalid_response: 'Invalid response from flashcard generation',
33
+ failed_to_process: 'Failed to process generated flashcards'
29
34
  },
30
35
  }
@@ -5,4 +5,5 @@ export default {
5
5
  flip_card: 'Haga clic para ver atras',
6
6
  click_to_show_front: 'Haga clic para mostrar el frente',
7
7
  click_to_show_back: 'Haga clic para mostrar Atrás',
8
+ replace_existing: 'Reemplazar tarjetas existentes'
8
9
  }
@@ -5,4 +5,5 @@ export default {
5
5
  hint_title: 'Pista',
6
6
  answer_feedback: 'Comentarios de respuesta',
7
7
  total_points: 'Puntos totales por Curso',
8
+ information: 'Información de respuesta',
8
9
  }
@@ -26,5 +26,10 @@ export default {
26
26
  'El número de caracteres para este campo debe ser menor que',
27
27
  },
28
28
  instructions:"Haga clic en cada tarjeta para revelar la definición del término. Usa las flechas para moverte por el mazo.",
29
+ replace_existing: 'Reemplazar tarjetas existentes',
30
+ replaced_successfully: 'Tarjetas reemplazadas con éxito',
31
+ added_successfully: 'Nuevas tarjetas agregadas con éxito',
32
+ invalid_response: 'Respuesta inválida de la generación de tarjetas',
33
+ failed_to_process: 'Error al procesar las tarjetas generadas'
29
34
  },
30
35
  }
@@ -5,4 +5,5 @@ export default {
5
5
  flip_card: 'Klicka för att vända kortet',
6
6
  click_to_show_front: 'Klicka för att visa fronten',
7
7
  click_to_show_back: 'Klicka för att visa tillbaka',
8
+ replace_existing: 'Ersätt befintliga flashcards'
8
9
  }
@@ -5,4 +5,5 @@ export default {
5
5
  hint_title: 'Tips',
6
6
  answer_feedback: 'Svara feedback',
7
7
  total_points: 'Totala poäng för Kurs',
8
+ information: 'Svarsinformation',
8
9
  }
@@ -26,5 +26,10 @@ export default {
26
26
  'Teckenantalet för detta fält måste vara mindre än',
27
27
  },
28
28
  instructions:"Klicka på varje kort för att avslöja termens definition. Använd pilarna för att flytta genom däcket.",
29
+ replace_existing: 'Ersätt befintliga flashcards',
30
+ replaced_successfully: 'Flashkort har ersatts framgångsrikt',
31
+ added_successfully: 'Nya flashkort har lagts till',
32
+ invalid_response: 'Ogiltigt svar från genereringen av flashkort',
33
+ failed_to_process: 'Kunde inte bearbeta genererade flashkort'
29
34
  },
30
35
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windward/games",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
4
  "description": "Windward UI Plugin Games",
5
5
  "main": "plugin.js",
6
6
  "scripts": {