@windward/games 0.18.0 → 0.19.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/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ### Hotfix [0.19.1] created - 2025-06-16
4
+
5
+
6
+ ### Release [0.19.0] created - 2025-05-30
7
+
3
8
  ### Release [0.18.0] created - 2025-05-20
4
9
 
5
10
  ### Hotfix [0.17.1] created - 2025-05-21
@@ -52,7 +52,9 @@
52
52
  :key="question.id"
53
53
  >
54
54
  <v-col class="d-flex justify-center">
55
- <p>{{ question.body }}</p>
55
+ <p>
56
+ <TextViewer v-model="question.body"></TextViewer>
57
+ </p>
56
58
  </v-col>
57
59
  <v-container :key="updateKey" class="pl-16 pr-16">
58
60
  <v-row
@@ -77,16 +79,20 @@
77
79
  @keyup.enter="onChooseAnswer(answer, question)"
78
80
  >
79
81
  <div class="d-flex justify-space-between">
80
- <p
81
- class="mb-0"
82
- :aria-labelledby="answer.value"
83
- >
84
- {{
85
- getCharacter(question, answer) +
86
- '. ' +
87
- answer.value
88
- }}
89
- </p>
82
+ <div class="d-flex">
83
+ <p
84
+ class="mb-0"
85
+ :aria-labelledby="answer.value"
86
+ >
87
+ {{
88
+ getCharacter(question, answer) +
89
+ '. '
90
+ }}
91
+ </p>
92
+ <TextViewer
93
+ v-model="answer.value"
94
+ ></TextViewer>
95
+ </div>
90
96
  <div>
91
97
  <v-icon
92
98
  v-if="
@@ -223,12 +229,11 @@
223
229
  </template>
224
230
  <template #form="{ on, attrs }">
225
231
  <div v-bind="attrs" v-on="on">
226
- <div v-if="hint">
227
- {{ hintText }}
228
- </div>
229
- <div v-if="answerDescriptionModal">
230
- {{ answerDescription }}
231
- </div>
232
+ <TextViewer v-if="hint" v-model="hintText"></TextViewer>
233
+ <TextViewer
234
+ v-if="answerDescriptionModal"
235
+ v-model="answerDescription"
236
+ ></TextViewer>
232
237
  </div>
233
238
  </template>
234
239
  </DialogBox>
@@ -241,11 +246,12 @@ import Crypto from '~/helpers/Crypto'
241
246
  import { mapGetters } from 'vuex'
242
247
  import BaseContentBlock from '~/components/Content/Blocks/BaseContentBlock'
243
248
  import DialogBox from '~/components/Core/DialogBox.vue'
249
+ import TextViewer from '~/components/Text/TextViewer.vue'
244
250
 
245
251
  export default {
246
252
  name: 'MultipleChoice',
247
253
  extends: BaseContentBlock,
248
- components: { DialogBox },
254
+ components: { DialogBox, TextViewer },
249
255
  beforeMount() {
250
256
  if (_.isEmpty(this.block)) {
251
257
  this.block = {}
@@ -1,20 +1,40 @@
1
1
  <template>
2
- <v-form ref="form" v-model="formValid" :key="updateKey">
3
- <v-row>
4
- <v-col cols="12">
5
- <v-textarea
6
- v-model="question.body"
7
- :label="
2
+ <v-container>
3
+ <v-row :key="updateKey">
4
+ <v-col xl="12" lg="12" md="12" sm="12" cols="12">
5
+ <p class="font-weight-bold p-bold-text">
6
+ {{
8
7
  $t(
9
- 'windward.games.components.settings.multiple_choice.question'
8
+ 'components.content.blocks.assessment.questions.question'
10
9
  )
11
- "
12
- :autofocus="true"
13
- rows="2"
14
- :rules="$Validation.getRule('exists')"
15
- prepend-inner-icon="mdi-help"
16
- :disabled="disabled"
17
- ></v-textarea>
10
+ }}
11
+ </p>
12
+ <TextEditor
13
+ id="question-body"
14
+ :key="'question-body-' + updateKey"
15
+ v-model="question.body"
16
+ :height="200"
17
+ lazy-load
18
+ @input="onCheckValidation"
19
+ ></TextEditor>
20
+ </v-col>
21
+ <v-col xl="3" lg="3" md="4" sm="12" cols="12" v-if="false">
22
+ <GenerateAIQuestionButton
23
+ :course="course"
24
+ :content="content"
25
+ :block="block"
26
+ :question-type="questionType"
27
+ @click:generate="onQuestionGenerated"
28
+ ></GenerateAIQuestionButton>
29
+ </v-col>
30
+ <v-col cols="12">
31
+ <ImageAssetSettings
32
+ v-model="question.question_metadata.body_asset"
33
+ :assets.sync="question.assets"
34
+ hide-background
35
+ hide-decorative
36
+ hide-modal
37
+ ></ImageAssetSettings>
18
38
  <v-textarea
19
39
  v-model="question.hint"
20
40
  :label="
@@ -22,120 +42,233 @@
22
42
  'windward.games.components.settings.multiple_choice.question_hint'
23
43
  )
24
44
  "
45
+ outlined
25
46
  rows="2"
26
- :rules="$Validation.getRule('exists')"
27
47
  prepend-inner-icon="mdi-lightbulb-on-10"
28
48
  :disabled="disabled"
29
49
  ></v-textarea>
30
- <v-textarea
31
- v-model="question.answer_description"
32
- :label="
33
- $t(
34
- 'windward.games.components.settings.multiple_choice.answer_feedback'
35
- )
36
- "
37
- rows="2"
38
- :rules="$Validation.getRule('exists')"
39
- prepend-inner-icon="mdi-comment"
40
- :disabled="disabled"
41
- ></v-textarea>
42
- <br />
50
+ </v-col>
51
+ <br />
52
+ <v-col cols="12">
43
53
  <h4 class="pb-2">
44
54
  {{
45
55
  $t(
46
- 'windward.games.components.settings.multiple_choice.answer_options'
56
+ 'windward.games.components.settings.multiple_choice.answer'
47
57
  )
48
58
  }}
49
59
  </h4>
50
- <p>
60
+ <v-radio-group
61
+ :key="updateKey"
62
+ v-model="question.correctAnswer"
63
+ :rules="validation.optionRules"
64
+ >
65
+ <draggable
66
+ :list="question.answer_options"
67
+ tag="tbody"
68
+ v-bind="dragOptions"
69
+ handle=".question-drag-handle"
70
+ @change="onDragChange"
71
+ >
72
+ <div
73
+ v-for="(answer, index) in question.answer_options"
74
+ :key="index"
75
+ >
76
+ <v-alert outlined elevation="2">
77
+ <v-row>
78
+ <v-col
79
+ cols="1"
80
+ lg="1"
81
+ md="1"
82
+ class="d-flex justify-center align-center pa-0"
83
+ >
84
+ <v-btn
85
+ elevation="0"
86
+ text
87
+ class="question-drag-handle"
88
+ >
89
+ <v-icon>mdi-drag-vertical</v-icon>
90
+ </v-btn>
91
+ </v-col>
92
+ <v-col
93
+ cols="10"
94
+ lg="10"
95
+ md="10"
96
+ class="pl-0 pr-0"
97
+ >
98
+ <v-row>
99
+ <v-col cols="12">
100
+ <v-radio
101
+ color="success"
102
+ :value="answer.id"
103
+ :disabled="disabled"
104
+ v-on:keydown.enter="
105
+ onSetAnswer(
106
+ answer,
107
+ true
108
+ )
109
+ "
110
+ @click="onSetAnswer(answer)"
111
+ >
112
+ <template #label>
113
+ <div
114
+ :class="
115
+ onContainerLabel(
116
+ answer
117
+ )
118
+ "
119
+ >
120
+ {{
121
+ answer.correctAnswer
122
+ ? $t(
123
+ 'components.content.blocks.assessment.questions.correct'
124
+ )
125
+ : $t(
126
+ 'components.content.blocks.assessment.questions.incorrect'
127
+ )
128
+ }}
129
+ </div>
130
+ </template>
131
+ </v-radio>
132
+ </v-col>
133
+ </v-row>
134
+ <v-row>
135
+ <v-col cols="12" md="6">
136
+ <TextEditor
137
+ :id="'option-' + answer.id"
138
+ :key="
139
+ 'option-' +
140
+ answer.id +
141
+ '-' +
142
+ updateKey
143
+ "
144
+ v-model="answer.value"
145
+ :label="
146
+ $t(
147
+ 'components.content.blocks.assessment.answer_option'
148
+ )
149
+ "
150
+ :height="200"
151
+ lazy-load
152
+ @input="onCheckValidation"
153
+ ></TextEditor>
154
+ </v-col>
155
+ <v-col cols="12" md="6">
156
+ <TextEditor
157
+ :id="
158
+ 'feedback-' + answer.id
159
+ "
160
+ :key="
161
+ 'feedback-' +
162
+ answer.id +
163
+ '-' +
164
+ updateKey
165
+ "
166
+ v-model="
167
+ question.answer_options[
168
+ index
169
+ ].feedback
170
+ "
171
+ :label="
172
+ $t(
173
+ 'components.content.blocks.assessment.answer_explanation'
174
+ )
175
+ "
176
+ :height="200"
177
+ lazy-load
178
+ ></TextEditor>
179
+ </v-col>
180
+ </v-row>
181
+ </v-col>
182
+ <v-col
183
+ cols="1"
184
+ lg="1"
185
+ md="1"
186
+ class="d-flex justify-center align-center pa-0"
187
+ >
188
+ <v-btn
189
+ elevation="0"
190
+ text
191
+ class="pa-0 ma-0"
192
+ color="error"
193
+ :disabled="removeDisabled"
194
+ @click="onDelete(index)"
195
+ >
196
+ <span class="sr-only">{{
197
+ $t(
198
+ 'components.content.blocks.assessment.questions.types.multi_choice_single_answer.add_option'
199
+ )
200
+ }}</span>
201
+ <v-icon>mdi-delete-outline</v-icon>
202
+ </v-btn>
203
+ </v-col>
204
+ </v-row>
205
+ </v-alert>
206
+ </div>
207
+ </draggable>
208
+ </v-radio-group>
209
+ <v-container class="d-flex justify-center" v-if="overLength">
210
+ <v-btn
211
+ :class="cursor"
212
+ color="primary"
213
+ elevation="0"
214
+ :disabled="disabled"
215
+ @click="onAddAnswer"
216
+ >
217
+ <v-icon>mdi-plus</v-icon>
218
+ {{
219
+ $t(
220
+ 'windward.games.components.settings.multiple_choice.add_answer'
221
+ )
222
+ }}
223
+ </v-btn>
224
+ </v-container>
225
+ </v-col>
226
+ <v-col cols="12">
227
+ <p class="font-weight-bold p-bold-text">
51
228
  {{
52
229
  $t(
53
- 'windward.games.components.settings.multiple_choice.correct_answer'
230
+ 'components.content.blocks.assessment.questions.question_feedback'
54
231
  )
55
232
  }}
56
233
  </p>
57
- </v-col>
58
- <v-container
59
- v-for="(answer, index) in question.answer_options"
60
- :key="index"
61
- >
62
- <v-row>
63
- <v-col
64
- cols="12"
65
- md="1"
66
- class="d-flex justify-center"
67
- @mouseover="onHover"
68
- @mouseleave="onHoverLeave"
69
- >
70
- <v-radio-group v-model="question.correctAnswer">
71
- <v-radio
72
- :ref="'checkbox' + index"
73
- :value="answer.id"
74
- :disabled="disabled"
75
- v-on:keydown.enter="onSetAnswer(answer, true)"
76
- @click="onSetAnswer(answer)"
77
- ></v-radio>
78
- </v-radio-group>
79
- </v-col>
80
- <v-col cols="12" md="10">
81
- <v-textarea
82
- v-model="answer.value"
83
- flat
84
- solo
85
- :autofocus="answer.focus"
86
- :outlined="setOutline(answer)"
87
- hide-details
88
- :class="getCorrectAnswer(answer)"
89
- :label="
90
- $t(
91
- 'windward.games.components.settings.multiple_choice.answer_option'
92
- )
93
- "
94
- rows="2"
95
- :rules="$Validation.getRule('exists')"
96
- :disabled="disabled"
97
- ></v-textarea>
98
- </v-col>
99
- <v-col cols="12" md="1" class="d-flex justify-center">
100
- <v-btn
101
- text
102
- elevation="0"
103
- :disabled="disabled"
104
- @click="onDelete(index)"
105
- >
106
- <v-icon color="error">mdi-delete-outline</v-icon>
107
- <span class="d-sr-only">{{
108
- $t('shared.forms.delete')
109
- }}</span>
110
- </v-btn>
111
- </v-col>
112
- </v-row>
113
- </v-container>
114
- <v-container class="d-flex justify-center" v-if="overLength">
115
- <v-btn
116
- :class="cursor"
117
- color="primary"
118
- elevation="0"
119
- :disabled="disabled"
120
- @click="onAddAnswer"
121
- >
122
- <v-icon>mdi-plus</v-icon>
123
- {{
234
+ <v-switch
235
+ v-model="question.metadata.config.single_feedback"
236
+ :label="
124
237
  $t(
125
- 'windward.games.components.settings.multiple_choice.add_answer'
238
+ 'components.content.blocks.assessment.single_feedback_label'
126
239
  )
127
- }}
128
- </v-btn>
129
- </v-container>
240
+ "
241
+ ></v-switch>
242
+ <TextEditor
243
+ v-if="question.metadata.config.single_feedback"
244
+ id="question-feedback"
245
+ :key="'question-feedback-' + updateKey"
246
+ v-model="question.answer_description"
247
+ :height="200"
248
+ lazy-load
249
+ ></TextEditor>
250
+ </v-col>
251
+ <v-col cols="12" md="12" v-if="false">
252
+ <QuestionSkills></QuestionSkills>
253
+ </v-col>
130
254
  </v-row>
131
- </v-form>
255
+ </v-container>
132
256
  </template>
133
257
  <script>
134
258
  import Form from '~/components/Core/Form'
135
259
  import _ from 'lodash'
260
+ import draggable from 'vuedraggable'
136
261
  import Crypto from '~/helpers/Crypto'
262
+ import ImageAssetSettings from '~/components/Content/Settings/ImageAssetSettings.vue'
263
+ import TextEditor from '~/components/Text/TextEditor.vue'
264
+
137
265
  export default {
138
266
  name: 'QuestionDialog',
267
+ components: {
268
+ TextEditor,
269
+ ImageAssetSettings,
270
+ draggable,
271
+ },
139
272
  extends: Form,
140
273
  layout: 'authenticated',
141
274
  middleware: ['auth', 'course'],
@@ -159,6 +292,7 @@ export default {
159
292
  disabled: false,
160
293
  chosen: false,
161
294
  focus: false,
295
+ feedback: '',
162
296
  },
163
297
  {
164
298
  id: Crypto.id(),
@@ -167,8 +301,17 @@ export default {
167
301
  disabled: false,
168
302
  chosen: false,
169
303
  focus: false,
304
+ feedback: '',
170
305
  },
171
306
  ],
307
+ question_metadata: {
308
+ body_asset: null,
309
+ },
310
+ metadata: {
311
+ config: {
312
+ single_feedback: false,
313
+ },
314
+ },
172
315
  // needed to add this bc radio group needs the id of the answer to set correct
173
316
  correctAnswer: 1,
174
317
  body: '',
@@ -177,8 +320,9 @@ export default {
177
320
  id: Crypto.id(),
178
321
  }
179
322
  }
323
+ // resets form if brand new question was created
180
324
  if (newValue && newValue.body === '') {
181
- this.$refs.form.resetValidation()
325
+ this.updateKey = Crypto.id()
182
326
  }
183
327
  },
184
328
  },
@@ -195,6 +339,14 @@ export default {
195
339
  return false
196
340
  }
197
341
  },
342
+ dragOptions() {
343
+ return {
344
+ animation: 200,
345
+ }
346
+ },
347
+ removeDisabled() {
348
+ return this.question.answer_options.length <= 2
349
+ },
198
350
  },
199
351
  data() {
200
352
  return {
@@ -213,19 +365,81 @@ export default {
213
365
  },
214
366
  cursor: null,
215
367
  updateKey: 0,
368
+ formValid: false,
216
369
  }
217
370
  },
218
- mounted() {
371
+ beforeMount() {
219
372
  if (this.value.body === '') {
220
- this.$refs.form.resetValidation()
373
+ // this.$refs.form.resetValidation()
221
374
  this.question = _.cloneDeep(this.value)
222
375
  } else if (!_.isEmpty(this.value)) {
223
376
  this.question = _.cloneDeep(this.value)
377
+ if (_.isEmpty(this.question.question_metadata)) {
378
+ this.question.question_metadata = {
379
+ body_asset: null,
380
+ }
381
+ }
382
+ if (_.isEmpty(this.question.metadata)) {
383
+ // if legacy has no feedback than set toggle to false
384
+ if (_.isEmpty(this.question.answer_description)) {
385
+ this.question.metadata = {
386
+ config: {
387
+ single_feedback: false,
388
+ },
389
+ }
390
+ } else {
391
+ this.question.metadata = {
392
+ config: {
393
+ single_feedback: true,
394
+ },
395
+ }
396
+ }
397
+ }
224
398
  }
225
- // refreshes data for modal on mount
226
- this.updateKey = Crypto.id()
227
399
  },
228
400
  methods: {
401
+ onContainerLabel(answer) {
402
+ if (answer.correctAnswer) {
403
+ return 'container-label-correct'
404
+ } else {
405
+ return ''
406
+ }
407
+ },
408
+ onDragChange() {
409
+ this.updateKey = Crypto.id()
410
+ },
411
+ onCheckValidation() {
412
+ let questionValid = true
413
+ const condition1 = '<div>&nbsp;</div><div>&nbsp;</div>'
414
+ const condition2 = '<div>&nbsp;</div>'
415
+ const condition3 = '<div>&nbsp;</div>\n<div>&nbsp;</div>'
416
+ if (
417
+ _.isEmpty(this.question.body) ||
418
+ this.question.body === condition1 ||
419
+ this.question.body === condition2 ||
420
+ this.question.body === condition3
421
+ ) {
422
+ questionValid = false
423
+ }
424
+ let correctAnswerChosen = false
425
+ this.question.answer_options.forEach((answer) => {
426
+ if (
427
+ _.isEmpty(answer.value) ||
428
+ answer.value === condition1 ||
429
+ answer.value === condition2 ||
430
+ answer.value === condition3
431
+ ) {
432
+ questionValid = false
433
+ }
434
+ if (answer.correctAnswer) {
435
+ correctAnswerChosen = true
436
+ }
437
+ })
438
+ if (!correctAnswerChosen) {
439
+ questionValid = false
440
+ }
441
+ this.$emit('change:valid', questionValid)
442
+ },
229
443
  onSave() {
230
444
  // clones questions so that input areas aren't linked when reset
231
445
  const emittedQuestion = _.cloneDeep(this.question)
@@ -253,9 +467,11 @@ export default {
253
467
  element.correctAnswer = false
254
468
  }
255
469
  })
470
+ this.onCheckValidation()
256
471
  },
257
472
  onDelete(index) {
258
473
  this.question.answer_options.splice(index, 1)
474
+ this.onCheckValidation()
259
475
  },
260
476
  onAddAnswer() {
261
477
  // pushes new answer object into answer options
@@ -268,11 +484,12 @@ export default {
268
484
  focus: true,
269
485
  }
270
486
  this.question.answer_options.push(answerObject)
487
+ this.onCheckValidation()
271
488
  },
272
489
  getCorrectAnswer(answer) {
273
490
  // if input area is the correct answer adds a green border
274
491
  if (answer.correctAnswer === true) {
275
- return 'successOutline'
492
+ return 'container-success-outline'
276
493
  } else {
277
494
  return ''
278
495
  }
@@ -285,7 +502,7 @@ export default {
285
502
  }
286
503
  },
287
504
  onHover() {
288
- this.cursor = 'changePointer'
505
+ this.cursor = 'cursor-pointer'
289
506
  },
290
507
  onHoverLeave() {
291
508
  this.cursor = ''
@@ -294,11 +511,17 @@ export default {
294
511
  }
295
512
  </script>
296
513
  <style lang="scss" scoped>
297
- .successOutline {
514
+ .container-success-outline {
298
515
  border: 4px solid var(--v-success-base);
299
516
  color: var(--v-success-base);
300
517
  }
301
- .changePointer {
518
+ .cursor-pointer {
302
519
  cursor: pointer !important;
303
520
  }
521
+ .p-bold-text {
522
+ color: var(--v-primary-base);
523
+ }
524
+ .container-label-correct {
525
+ color: var(--v-success-base) !important;
526
+ }
304
527
  </style>
@@ -193,15 +193,31 @@
193
193
  indeterminate
194
194
  ></v-progress-circular>
195
195
  </div>
196
+ <v-container class="pa-4 mb-6">
197
+ <v-row>
198
+ <v-col cols="12">
199
+ <GenerateAIQuestionButton
200
+ :course="course"
201
+ :content="content"
202
+ :block="block"
203
+ question-type="bucket_game"
204
+ :replace-existing-mode="replaceExisting"
205
+ @click:generate="onGeneratedBucketGame"
206
+ ></GenerateAIQuestionButton>
207
+ </v-col>
208
+ </v-row>
209
+ </v-container>
196
210
  </div>
197
211
  </template>
198
212
 
199
213
  <script>
200
214
  import _ from 'lodash'
215
+ import { mapGetters } from 'vuex'
201
216
  import {
202
217
  MathExpressionEditor,
203
218
  MathLiveWrapper,
204
219
  ContentViewer,
220
+ GenerateAIQuestionButton
205
221
  } from '@windward/core/utils'
206
222
  import colors from 'vuetify/lib/util/colors'
207
223
  import TextEditor from '~/components/Text/TextEditor'
@@ -221,6 +237,7 @@ export default {
221
237
  SortableExpansionPanel,
222
238
  BaseContentBlockSettings,
223
239
  TextEditor,
240
+ GenerateAIQuestionButton,
224
241
  },
225
242
  beforeMount() {
226
243
  if (_.isEmpty(this.block)) {
@@ -271,6 +288,7 @@ export default {
271
288
  loading: false,
272
289
  panel: 0,
273
290
  expansionPanelKey: '0',
291
+ replaceExisting: false,
274
292
  swatches: [
275
293
  [colors.grey.lighten1, colors.blueGrey.lighten5],
276
294
  [colors.brown.lighten3, colors.red.lighten3],
@@ -280,6 +298,12 @@ export default {
280
298
  ],
281
299
  }
282
300
  },
301
+ computed: {
302
+ ...mapGetters({
303
+ course: 'course/get',
304
+ content: 'content/get',
305
+ }),
306
+ },
283
307
  methods: {
284
308
  onDragged(panels, oldPanelOrder) {
285
309
  const oldAnswerArray = _.cloneDeep(
@@ -400,6 +424,125 @@ export default {
400
424
  return htmlString.replace(/(<([^>]+)>)/gi, '')
401
425
  }
402
426
  },
427
+ // Handler for receiving bucket game data from GenerateAIQuestionButton
428
+ onGeneratedBucketGame(activityData, replaceMode) {
429
+ this.loading = true
430
+ try {
431
+ // Process the activity data
432
+ if (activityData && activityData.metadata &&
433
+ activityData.metadata.config &&
434
+ activityData.metadata.config.bucket_titles &&
435
+ activityData.metadata.config.bucket_answers &&
436
+ Array.isArray(activityData.metadata.config.bucket_titles) &&
437
+ Array.isArray(activityData.metadata.config.bucket_answers)) {
438
+
439
+ // Save new buckets and answers
440
+ const newBuckets = activityData.metadata.config.bucket_titles
441
+ const newAnswers = activityData.metadata.config.bucket_answers
442
+
443
+ if (replaceMode) {
444
+ // Replace mode: Clear existing buckets and answers
445
+ this.block.metadata.config.bucket_titles.splice(0, this.block.metadata.config.bucket_titles.length)
446
+ this.block.metadata.config.bucket_answers.splice(0, this.block.metadata.config.bucket_answers.length)
447
+
448
+ // Add all new buckets
449
+ newBuckets.forEach(bucket => {
450
+ this.block.metadata.config.bucket_titles.push({
451
+ title: bucket.title || '',
452
+ color: bucket.color || colors.blueGrey.lighten5,
453
+ expand: false
454
+ })
455
+ })
456
+
457
+ // Add all new answers
458
+ newAnswers.forEach((bucketAnswers, index) => {
459
+ this.block.metadata.config.bucket_answers[index] = []
460
+ if (Array.isArray(bucketAnswers)) {
461
+ bucketAnswers.forEach(answer => {
462
+ this.block.metadata.config.bucket_answers[index].push({
463
+ bucket_index: index,
464
+ display: answer.display || '',
465
+ feedback: answer.feedback || '',
466
+ expand: false,
467
+ id: Crypto.id()
468
+ })
469
+ })
470
+ }
471
+ })
472
+ } else {
473
+ // Merge mode: Add new buckets and answers to existing ones
474
+ const existingBucketCount = this.block.metadata.config.bucket_titles.length
475
+
476
+ // Add new buckets
477
+ newBuckets.forEach(bucket => {
478
+ this.block.metadata.config.bucket_titles.push({
479
+ title: bucket.title || '',
480
+ color: bucket.color || colors.blueGrey.lighten5,
481
+ expand: false
482
+ })
483
+ })
484
+
485
+ // Add new answers with adjusted bucket indices
486
+ newAnswers.forEach((bucketAnswers, index) => {
487
+ const adjustedIndex = existingBucketCount + index
488
+ if (!this.block.metadata.config.bucket_answers[adjustedIndex]) {
489
+ this.block.metadata.config.bucket_answers[adjustedIndex] = []
490
+ }
491
+ if (Array.isArray(bucketAnswers)) {
492
+ bucketAnswers.forEach(answer => {
493
+ this.block.metadata.config.bucket_answers[adjustedIndex].push({
494
+ bucket_index: adjustedIndex,
495
+ display: answer.display || '',
496
+ feedback: answer.feedback || '',
497
+ expand: false,
498
+ id: Crypto.id()
499
+ })
500
+ })
501
+ }
502
+ })
503
+ }
504
+
505
+ // Update title and instructions if provided and we're in replace mode
506
+ if (replaceMode) {
507
+ if (activityData.metadata.config.title) {
508
+ this.block.metadata.config.title = activityData.metadata.config.title
509
+ }
510
+
511
+ if (activityData.metadata.config.instructions) {
512
+ this.block.metadata.config.instructions = activityData.metadata.config.instructions
513
+ }
514
+
515
+ // Update feedback messages if provided
516
+ if (activityData.metadata.config.feedback_correct) {
517
+ this.block.metadata.config.feedback_correct = activityData.metadata.config.feedback_correct
518
+ }
519
+
520
+ if (activityData.metadata.config.feedback_incorrect) {
521
+ this.block.metadata.config.feedback_incorrect = activityData.metadata.config.feedback_incorrect
522
+ }
523
+ }
524
+
525
+ this.$toast.success(
526
+ replaceMode
527
+ ? this.$t('windward.games.components.settings.bucket_game.form.replaced_successfully')
528
+ : this.$t('windward.games.components.settings.bucket_game.form.added_successfully'),
529
+ { duration: 3000 }
530
+ )
531
+ } else {
532
+ this.$toast.error(this.$t('windward.games.components.settings.bucket_game.form.invalid_response'), {
533
+ duration: 5000
534
+ })
535
+ }
536
+ } catch (error) {
537
+ // Extract error message from the response
538
+ const errorMessage = error.message || 'Unknown error occurred'
539
+ this.$toast.error(`${this.$t('windward.games.components.settings.bucket_game.form.failed_to_process')}: ${errorMessage}`, {
540
+ duration: 5000
541
+ })
542
+ } finally {
543
+ this.loading = false
544
+ }
545
+ }
403
546
  },
404
547
  }
405
548
  </script>
@@ -24,8 +24,8 @@
24
24
  >
25
25
  <template #header="{ item }">
26
26
  {{
27
- item.body
28
- ? item.body
27
+ stripHtml(item.body)
28
+ ? stripHtml(item.body)
29
29
  : $t(
30
30
  'windward.games.components.settings.bucket_game.form.enter_text'
31
31
  )
@@ -68,6 +68,7 @@
68
68
  v-bind="attrs"
69
69
  v-on="on"
70
70
  v-model="block.metadata.config.questions[editingIndex]"
71
+ :block="block"
71
72
  :disabled="render"
72
73
  @saveAndNew="saveAndNewCalled"
73
74
  ></QuestionDialog>
@@ -150,6 +151,11 @@ export default {
150
151
  this.emittedQuestion = {}
151
152
  },
152
153
  methods: {
154
+ stripHtml(htmlString) {
155
+ if (htmlString) {
156
+ return htmlString.replace(/(<([^>]+)>)/gi, '')
157
+ }
158
+ },
153
159
  onOpenModal(e, questionIndex = null) {
154
160
  //checks if modal is opening to edit or create new
155
161
  if (questionIndex !== null) {
@@ -179,6 +185,14 @@ export default {
179
185
  focus: false,
180
186
  },
181
187
  ],
188
+ question_metadata: {
189
+ body_asset: null,
190
+ },
191
+ metadata: {
192
+ config: {
193
+ single_feedback: false,
194
+ },
195
+ },
182
196
  correctAnswer: firstAnswerId,
183
197
  body: '',
184
198
  hint: '',
@@ -211,6 +225,14 @@ export default {
211
225
  chosen: false,
212
226
  },
213
227
  ],
228
+ question_metadata: {
229
+ body_asset: null,
230
+ },
231
+ metadata: {
232
+ config: {
233
+ single_feedback: false,
234
+ },
235
+ },
214
236
  // needed to add this bc radio group needs the id of the answer in answerobjects array to set correct value
215
237
  correctAnswer: firstAnswerId,
216
238
  body: '',
@@ -5,4 +5,5 @@ export default {
5
5
  flip_card: 'Click to flip card',
6
6
  click_to_show_front: 'Click to show Front',
7
7
  click_to_show_back: 'Click to show Back',
8
+ replace_existing: 'Replace existing flashcards',
8
9
  }
@@ -36,5 +36,10 @@ export default {
36
36
  click_here: 'Click here to enter text',
37
37
  add_answer: 'Add Answer',
38
38
  color: 'Bucket Color',
39
+ replace_existing: 'Replace existing buckets',
40
+ replaced_successfully: 'Buckets replaced successfully',
41
+ added_successfully: 'New buckets added successfully',
42
+ invalid_response: 'Invalid response from bucket game generation',
43
+ failed_to_process: 'Failed to process generated buckets'
39
44
  },
40
45
  }
@@ -2,7 +2,8 @@ export default {
2
2
  game_title: 'Multiple Choice Game',
3
3
  title_placeholder: 'Title',
4
4
  instructions: 'Instructions',
5
- default_instructions: 'Read the question and click the correct answer below. Use the arrows to move between questions.',
5
+ default_instructions:
6
+ 'Read the question and click the correct answer below. Use the arrows to move between questions.',
6
7
  questions: 'Questions',
7
8
  modal_title: 'Multiple Choice Question',
8
9
  question_hint: 'Question Hint',
@@ -11,7 +12,7 @@ export default {
11
12
  answer_options: 'Answer Options',
12
13
  answer_option: 'Answer Option',
13
14
  correct: 'Correct',
14
- correct_answer: 'Click an answer to set it as the correct answer',
15
- add_answer: 'Add Answer',
15
+ add_answer: 'Add Answer Option',
16
16
  add_question: 'Add Question',
17
+ answer: 'Answers',
17
18
  }
@@ -37,5 +37,10 @@ export default {
37
37
  click_here: 'Haga clic aquí para ingresar texto',
38
38
  add_answer: 'Agregar respuesta',
39
39
  color: 'Color del cubo',
40
+ replace_existing: 'Reemplazar cubos existentes',
41
+ replaced_successfully: 'Cubos reemplazados exitosamente',
42
+ added_successfully: 'Nuevos cubos añadidos exitosamente',
43
+ invalid_response: 'Respuesta inválida de la generación del juego de cubos',
44
+ failed_to_process: 'Error al procesar los cubos generados'
40
45
  },
41
46
  }
@@ -12,8 +12,7 @@ export default {
12
12
  answer_options: 'Opciones de respuesta',
13
13
  answer_option: 'Opción de respuesta',
14
14
  correct: 'Correcto',
15
- correct_answer:
16
- 'Haga clic en una respuesta para establecerla como la respuesta correcta',
17
- add_answer: 'Agregar respuesta',
15
+ add_answer: 'Agregar opción de respuesta',
18
16
  add_question: 'Agregar pregunta',
17
+ answer: 'Respuestas',
19
18
  }
@@ -36,5 +36,10 @@ export default {
36
36
  click_here: 'Klicka här för att skriva in text',
37
37
  add_answer: 'Lägg till svar',
38
38
  color: 'Bucket Color',
39
+ replace_existing: 'Ersätt befintliga hinkar',
40
+ replaced_successfully: 'Hinkar ersatta',
41
+ added_successfully: 'Nya hinkar tillagda',
42
+ invalid_response: 'Ogiltig svar från hinkspel',
43
+ failed_to_process: 'Misslyckades att bearbeta hinkar'
39
44
  },
40
45
  }
@@ -11,7 +11,7 @@ export default {
11
11
  answer_options: 'Svarsalternativ',
12
12
  answer_option: 'Svarsalternativ',
13
13
  correct: 'Korrekt',
14
- correct_answer: 'Klicka ett svar för att ställa in det som rätt svar',
15
- add_answer: 'Lägg till svar',
14
+ add_answer: 'Lägg till svarsalternativ',
16
15
  add_question: 'Lägg till fråga',
16
+ answer: 'Svar',
17
17
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@windward/games",
3
- "version": "0.18.0",
3
+ "version": "0.19.1",
4
4
  "description": "Windward UI Plugin Games",
5
5
  "main": "plugin.js",
6
6
  "scripts": {
@@ -21,7 +21,7 @@
21
21
  "license": "MIT",
22
22
  "homepage": "https://bitbucket.org/mindedge/windward-ui-plugin-games#readme",
23
23
  "dependencies": {
24
- "@windward/core": "^0.17.0",
24
+ "@windward/core": "^0.19.0",
25
25
  "eslint": "^8.11.0",
26
26
  "lodash": "^4.17.21",
27
27
  "prettier": "^2.6.0",