@windward/games 0.22.0 → 0.23.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.
@@ -251,14 +251,18 @@
251
251
  <v-container class="pa-4 mb-6">
252
252
  <v-row>
253
253
  <v-col cols="12">
254
- <GenerateAIQuestionButton
255
- :course="course"
256
- :content="content"
257
- :block="block"
258
- question-type="matching_game"
259
- :replace-existing-mode="replaceExisting"
260
- @click:generate="onGeneratedMatchingGame"
261
- ></GenerateAIQuestionButton>
254
+ <PluginRef
255
+ target="contentBlockSettingTool"
256
+ :attrs="{
257
+ value: block,
258
+ course: course,
259
+ content: currentContent,
260
+ }"
261
+ :on="{
262
+ input: onPluginSetBlock,
263
+ append: onPluginAppendBlock,
264
+ }"
265
+ ></PluginRef>
262
266
  </v-col>
263
267
  </v-row>
264
268
  </v-container>
@@ -269,7 +273,7 @@
269
273
  import BaseContentSettings from '~/components/Content/Settings/BaseContentSettings.js'
270
274
  import _ from 'lodash'
271
275
  import { mapGetters } from 'vuex'
272
- import { GenerateAIQuestionButton } from '@windward/core/utils'
276
+ import PluginRef from '~/components/Core/PluginRef.vue'
273
277
  import BaseContentBlockSettings from '~/components/Content/Settings/BaseContentBlockSettings.vue'
274
278
  import SortableExpansionPanel from '~/components/Core/SortableExpansionPanel.vue'
275
279
  import Uuid from '~/helpers/Uuid'
@@ -282,7 +286,7 @@ export default {
282
286
  SortableExpansionPanel,
283
287
  ImageAssetSettings,
284
288
  BaseContentBlockSettings,
285
- GenerateAIQuestionButton
289
+ PluginRef,
286
290
  },
287
291
  beforeMount() {
288
292
  if (_.isEmpty(this.block)) {
@@ -488,129 +492,220 @@ export default {
488
492
  // holder array now has new answer positions
489
493
  this.block.metadata.config.prompts = holderArray
490
494
  },
491
- // Handler for receiving matching game data from GenerateAIQuestionButton
492
- onGeneratedMatchingGame(activityData, replaceMode) {
493
-
495
+ // Handler for receiving matching game data from Plugins
496
+ onPluginSetBlock(activityData) {
494
497
  this.loading = true
495
498
  try {
496
499
  // Process the activity data
497
- if (activityData && activityData.metadata &&
500
+ if (
501
+ activityData &&
502
+ activityData.metadata &&
498
503
  activityData.metadata.config &&
499
504
  activityData.metadata.config.answerObjects &&
500
505
  activityData.metadata.config.prompts &&
501
506
  Array.isArray(activityData.metadata.config.answerObjects) &&
502
- Array.isArray(activityData.metadata.config.prompts)) {
503
-
504
- if (replaceMode) {
505
- // Replace mode: Clear existing answers and prompts
506
- this.block.metadata.config.answerObjects.splice(0, this.block.metadata.config.answerObjects.length)
507
- this.block.metadata.config.prompts.splice(0, this.block.metadata.config.prompts.length)
507
+ Array.isArray(activityData.metadata.config.prompts)
508
+ ) {
509
+ // Replace mode: Clear existing answers and prompts
510
+ this.block.metadata.config.answerObjects.splice(
511
+ 0,
512
+ this.block.metadata.config.answerObjects.length
513
+ )
514
+ this.block.metadata.config.prompts.splice(
515
+ 0,
516
+ this.block.metadata.config.prompts.length
517
+ )
508
518
 
509
- // Add all new answer objects and prompts
510
- activityData.metadata.config.answerObjects.forEach((answerObj, index) => {
519
+ // Add all new answer objects and prompts
520
+ activityData.metadata.config.answerObjects.forEach(
521
+ (answerObj, index) => {
511
522
  this.block.metadata.config.answerObjects.push({
512
- id: index.toString(), // string id
513
- display: answerObj.display || ''
523
+ id: index.toString(), // string id
524
+ display: answerObj.display || '',
514
525
  })
515
- })
526
+ }
527
+ )
516
528
 
517
- // Add all prompts - following the exact pattern from BucketGameSettingsManager
518
- activityData.metadata.config.prompts.forEach((promptArray, index) => {
529
+ // Add all prompts - following the exact pattern from BucketGameSettingsManager
530
+ activityData.metadata.config.prompts.forEach(
531
+ (promptArray, index) => {
519
532
  this.block.metadata.config.prompts[index] = []
520
533
  if (Array.isArray(promptArray)) {
521
- promptArray.forEach(prompt => {
534
+ promptArray.forEach((prompt) => {
522
535
  // Ensure the answer object reference is correct
523
- const answerObj = this.block.metadata.config.answerObjects[index]
524
- this.block.metadata.config.prompts[index].push({
525
- id: index.toString(), // string id
526
- textOrImage: prompt.textOrImage || 'text',
536
+ const answerObj =
537
+ this.block.metadata.config
538
+ .answerObjects[index]
539
+ this.block.metadata.config.prompts[
540
+ index
541
+ ].push({
542
+ id: index.toString(), // string id
543
+ textOrImage:
544
+ prompt.textOrImage || 'text',
527
545
  prompt: prompt.prompt || '',
528
- matchExplanation: prompt.matchExplanation || this.$t('windward.games.components.settings.matching_game.form.correct_match'),
546
+ matchExplanation:
547
+ prompt.matchExplanation ||
548
+ this.$t(
549
+ 'windward.games.components.settings.matching_game.form.correct_match'
550
+ ),
529
551
  fileConfig: prompt.fileConfig || {
530
552
  hideBackground: true,
531
553
  },
532
- answer: answerObj
554
+ answer: answerObj,
533
555
  })
534
556
  })
535
557
  }
536
- })
537
- } else {
538
- // Merge mode: Add new terms and prompts to existing ones
539
- // Check if there's only one empty answer (the default one created on init)
540
- const hasOnlyEmptyAnswer = this.block.metadata.config.answerObjects.length === 1 &&
541
- this.block.metadata.config.answerObjects[0].display === '';
542
-
543
- if (hasOnlyEmptyAnswer) {
544
- // If there's only one empty answer, replace it instead of merging
545
- this.block.metadata.config.answerObjects.splice(0, 1);
546
- this.block.metadata.config.prompts.splice(0, 1);
547
558
  }
548
-
549
- const existingAnswerCount = this.block.metadata.config.answerObjects.length
559
+ )
550
560
 
551
- // Add new answer objects
552
- activityData.metadata.config.answerObjects.forEach((answerObj, index) => {
561
+ // Update title and instructions if provided and we're in replace mode
562
+
563
+ if (activityData.metadata.config.title) {
564
+ this.block.metadata.config.title =
565
+ activityData.metadata.config.title
566
+ }
567
+
568
+ if (activityData.metadata.config.instructions) {
569
+ this.block.metadata.config.instructions =
570
+ activityData.metadata.config.instructions
571
+ }
572
+
573
+ this.$toast.success(
574
+ this.$t(
575
+ 'windward.games.components.settings.matching_game.form.replaced_successfully'
576
+ ),
577
+ { duration: 3000 }
578
+ )
579
+ } else {
580
+ this.$toast.error(
581
+ this.$t(
582
+ 'windward.games.components.settings.matching_game.form.invalid_response'
583
+ ),
584
+ {
585
+ duration: 5000,
586
+ }
587
+ )
588
+ }
589
+ } catch (error) {
590
+ // Extract error message from the response
591
+ const errorMessage = error.message || 'Unknown error occurred'
592
+ this.$toast.error(
593
+ `${this.$t(
594
+ 'windward.games.components.settings.matching_game.form.failed_to_process'
595
+ )}: ${errorMessage}`,
596
+ {
597
+ duration: 5000,
598
+ }
599
+ )
600
+ } finally {
601
+ this.loading = false
602
+ }
603
+ },
604
+
605
+ onPluginAppendBlock(activityData) {
606
+ this.loading = true
607
+ try {
608
+ // Process the activity data
609
+ if (
610
+ activityData &&
611
+ activityData.metadata &&
612
+ activityData.metadata.config &&
613
+ activityData.metadata.config.answerObjects &&
614
+ activityData.metadata.config.prompts &&
615
+ Array.isArray(activityData.metadata.config.answerObjects) &&
616
+ Array.isArray(activityData.metadata.config.prompts)
617
+ ) {
618
+ // Merge mode: Add new terms and prompts to existing ones
619
+ // Check if there's only one empty answer (the default one created on init)
620
+ const hasOnlyEmptyAnswer =
621
+ this.block.metadata.config.answerObjects.length === 1 &&
622
+ this.block.metadata.config.answerObjects[0].display ===
623
+ ''
624
+
625
+ if (hasOnlyEmptyAnswer) {
626
+ // If there's only one empty answer, replace it instead of merging
627
+ this.block.metadata.config.answerObjects.splice(0, 1)
628
+ this.block.metadata.config.prompts.splice(0, 1)
629
+ }
630
+
631
+ const existingAnswerCount =
632
+ this.block.metadata.config.answerObjects.length
633
+
634
+ // Add new answer objects
635
+ activityData.metadata.config.answerObjects.forEach(
636
+ (answerObj, index) => {
553
637
  const newIndex = existingAnswerCount + index
554
638
  this.block.metadata.config.answerObjects.push({
555
- id: newIndex.toString(), // string id
556
- display: answerObj.display || ''
639
+ id: newIndex.toString(), // string id
640
+ display: answerObj.display || '',
557
641
  })
558
- })
642
+ }
643
+ )
559
644
 
560
- // Add new prompts
561
- activityData.metadata.config.prompts.forEach((promptArray, index) => {
645
+ // Add new prompts
646
+ activityData.metadata.config.prompts.forEach(
647
+ (promptArray, index) => {
562
648
  const adjustedIndex = existingAnswerCount + index
563
- this.block.metadata.config.prompts[adjustedIndex] = []
649
+ this.block.metadata.config.prompts[adjustedIndex] =
650
+ []
564
651
  if (Array.isArray(promptArray)) {
565
- promptArray.forEach(prompt => {
566
- const answerObj = this.block.metadata.config.answerObjects[adjustedIndex]
567
- this.block.metadata.config.prompts[adjustedIndex].push({
568
- id: adjustedIndex.toString(), // string id
569
- textOrImage: prompt.textOrImage || 'text',
652
+ promptArray.forEach((prompt) => {
653
+ const answerObj =
654
+ this.block.metadata.config
655
+ .answerObjects[adjustedIndex]
656
+ this.block.metadata.config.prompts[
657
+ adjustedIndex
658
+ ].push({
659
+ id: adjustedIndex.toString(), // string id
660
+ textOrImage:
661
+ prompt.textOrImage || 'text',
570
662
  prompt: prompt.prompt || '',
571
- matchExplanation: prompt.matchExplanation || this.$t('windward.games.components.settings.matching_game.form.correct_match'),
663
+ matchExplanation:
664
+ prompt.matchExplanation ||
665
+ this.$t(
666
+ 'windward.games.components.settings.matching_game.form.correct_match'
667
+ ),
572
668
  fileConfig: prompt.fileConfig || {
573
669
  hideBackground: true,
574
670
  },
575
- answer: answerObj
671
+ answer: answerObj,
576
672
  })
577
673
  })
578
674
  }
579
- })
580
- }
581
-
582
- // Update title and instructions if provided and we're in replace mode
583
- if (replaceMode) {
584
- if (activityData.metadata.config.title) {
585
- this.block.metadata.config.title = activityData.metadata.config.title
586
675
  }
587
-
588
- if (activityData.metadata.config.instructions) {
589
- this.block.metadata.config.instructions = activityData.metadata.config.instructions
590
- }
591
- }
676
+ )
592
677
 
593
678
  this.$toast.success(
594
- replaceMode
595
- ? this.$t('windward.games.components.settings.matching_game.form.replaced_successfully')
596
- : this.$t('windward.games.components.settings.matching_game.form.added_successfully'),
679
+ this.$t(
680
+ 'windward.games.components.settings.matching_game.form.added_successfully'
681
+ ),
597
682
  { duration: 3000 }
598
683
  )
599
684
  } else {
600
- this.$toast.error(this.$t('windward.games.components.settings.matching_game.form.invalid_response'), {
601
- duration: 5000
602
- })
685
+ this.$toast.error(
686
+ this.$t(
687
+ 'windward.games.components.settings.matching_game.form.invalid_response'
688
+ ),
689
+ {
690
+ duration: 5000,
691
+ }
692
+ )
603
693
  }
604
694
  } catch (error) {
605
695
  // Extract error message from the response
606
696
  const errorMessage = error.message || 'Unknown error occurred'
607
- this.$toast.error(`${this.$t('windward.games.components.settings.matching_game.form.failed_to_process')}: ${errorMessage}`, {
608
- duration: 5000
609
- })
697
+ this.$toast.error(
698
+ `${this.$t(
699
+ 'windward.games.components.settings.matching_game.form.failed_to_process'
700
+ )}: ${errorMessage}`,
701
+ {
702
+ duration: 5000,
703
+ }
704
+ )
610
705
  } finally {
611
706
  this.loading = false
612
707
  }
613
- }
708
+ },
614
709
  },
615
710
  }
616
711
  </script>
@@ -47,6 +47,25 @@
47
47
  }}
48
48
  </v-btn>
49
49
  </v-row>
50
+ <!-- AI Assistant: Generate Questions (appears below Add Question) -->
51
+ <v-container class="pa-4 mb-6">
52
+ <v-row>
53
+ <v-col cols="12">
54
+ <PluginRef
55
+ target="contentBlockSettingTool"
56
+ :attrs="{
57
+ value: block,
58
+ course: course,
59
+ content: currentContent,
60
+ }"
61
+ :on="{
62
+ input: onPluginSetBlock,
63
+ append: onPluginAppendBlock,
64
+ }"
65
+ ></PluginRef>
66
+ </v-col>
67
+ </v-row>
68
+ </v-container>
50
69
  </v-container>
51
70
  <DialogBox
52
71
  v-model="dialog"
@@ -88,6 +107,7 @@
88
107
  </template>
89
108
  <script>
90
109
  import _ from 'lodash'
110
+ import { mapGetters } from 'vuex'
91
111
  import BaseContentSettings from '~/components/Content/Settings/BaseContentSettings.js'
92
112
  import SortableExpansionPanel from '~/components/Core/SortableExpansionPanel.vue'
93
113
  import DialogBox from '~/components/Core/DialogBox.vue'
@@ -95,6 +115,7 @@ import QuestionDialog from '../content/blocks/multipleChoice/QuestionDialog.vue'
95
115
  import Crypto from '~/helpers/Crypto'
96
116
  import BaseContentBlockSettings from '~/components/Content/Settings/BaseContentBlockSettings.vue'
97
117
  import Uuid from '~/helpers/Uuid'
118
+ import PluginRef from '~/components/Core/PluginRef.vue'
98
119
 
99
120
  export default {
100
121
  name: 'MultipleChoiceSettingsManager',
@@ -104,6 +125,7 @@ export default {
104
125
  QuestionDialog,
105
126
  DialogBox,
106
127
  BaseContentBlockSettings,
128
+ PluginRef,
107
129
  },
108
130
  beforeMount() {
109
131
  if (_.isEmpty(this.block)) {
@@ -150,6 +172,12 @@ export default {
150
172
  mounted() {
151
173
  this.emittedQuestion = {}
152
174
  },
175
+ computed: {
176
+ ...mapGetters({
177
+ course: 'course/get',
178
+ currentContent: 'content/get',
179
+ }),
180
+ },
153
181
  methods: {
154
182
  stripHtml(htmlString) {
155
183
  if (htmlString) {
@@ -255,6 +283,110 @@ export default {
255
283
  this.dialog = false
256
284
  this.editingIndex = null
257
285
  },
286
+ // Handle AI Assistant replace (input) event
287
+ onPluginSetBlock(activityData) {
288
+ this.loading = true
289
+ try {
290
+ if (
291
+ activityData &&
292
+ activityData.metadata &&
293
+ activityData.metadata.config &&
294
+ Array.isArray(activityData.metadata.config.questions)
295
+ ) {
296
+ const newConfig = activityData.metadata.config
297
+
298
+ // Replace questions entirely
299
+ this.$set(
300
+ this.block.metadata.config,
301
+ 'questions',
302
+ _.cloneDeep(newConfig.questions)
303
+ )
304
+
305
+ // Update title and instructions if provided
306
+ if (newConfig.title) {
307
+ this.$set(
308
+ this.block.metadata.config,
309
+ 'title',
310
+ newConfig.title
311
+ )
312
+ }
313
+ if (newConfig.instructions) {
314
+ this.$set(
315
+ this.block.metadata.config,
316
+ 'instructions',
317
+ newConfig.instructions
318
+ )
319
+ }
320
+
321
+ this.$toast.success(
322
+ this.$t(
323
+ 'windward.games.components.settings.multiple_choice.replaced_successfully'
324
+ ),
325
+ { duration: 3000 }
326
+ )
327
+ } else {
328
+ this.$toast.error(
329
+ this.$t(
330
+ 'windward.games.components.settings.multiple_choice.invalid_response'
331
+ ),
332
+ { duration: 5000 }
333
+ )
334
+ }
335
+ } catch (error) {
336
+ const errorMessage = error.message || 'Unknown error occurred'
337
+ this.$toast.error(
338
+ `${this.$t(
339
+ 'windward.games.components.settings.multiple_choice.failed_to_process'
340
+ )}: ${errorMessage}`,
341
+ { duration: 5000 }
342
+ )
343
+ } finally {
344
+ this.loading = false
345
+ }
346
+ },
347
+ // Handle AI Assistant append event: add new questions to existing
348
+ onPluginAppendBlock(activityData) {
349
+ this.loading = true
350
+ try {
351
+ if (
352
+ activityData &&
353
+ activityData.metadata &&
354
+ activityData.metadata.config &&
355
+ Array.isArray(activityData.metadata.config.questions)
356
+ ) {
357
+ const newQuestions = activityData.metadata.config.questions
358
+ if (!Array.isArray(this.block.metadata.config.questions)) {
359
+ this.$set(this.block.metadata.config, 'questions', [])
360
+ }
361
+ newQuestions.forEach((q) => {
362
+ this.block.metadata.config.questions.push(_.cloneDeep(q))
363
+ })
364
+ this.$toast.success(
365
+ this.$t(
366
+ 'windward.games.components.settings.multiple_choice.added_successfully'
367
+ ),
368
+ { duration: 3000 }
369
+ )
370
+ } else {
371
+ this.$toast.error(
372
+ this.$t(
373
+ 'windward.games.components.settings.multiple_choice.invalid_response'
374
+ ),
375
+ { duration: 5000 }
376
+ )
377
+ }
378
+ } catch (error) {
379
+ const errorMessage = error.message || 'Unknown error occurred'
380
+ this.$toast.error(
381
+ `${this.$t(
382
+ 'windward.games.components.settings.multiple_choice.failed_to_process'
383
+ )}: ${errorMessage}`,
384
+ { duration: 5000 }
385
+ )
386
+ } finally {
387
+ this.loading = false
388
+ }
389
+ },
258
390
  },
259
391
  }
260
392
  </script>
@@ -112,17 +112,45 @@
112
112
  :disabled="render"
113
113
  ></v-textarea>
114
114
  </v-row>
115
+ <div v-if="loading" class="text-center">
116
+ <v-progress-circular
117
+ :size="70"
118
+ :width="7"
119
+ color="primary"
120
+ indeterminate
121
+ ></v-progress-circular>
122
+ </div>
123
+ <v-container class="pa-4 mb-6">
124
+ <v-row>
125
+ <v-col cols="12">
126
+ <PluginRef
127
+ target="contentBlockSettingTool"
128
+ :attrs="{
129
+ value: block,
130
+ course: course,
131
+ content: currentContent,
132
+ }"
133
+ :on="{
134
+ input: onPluginSetBlock,
135
+ append: onPluginAppendBlock,
136
+ }"
137
+ ></PluginRef>
138
+ </v-col>
139
+ </v-row>
140
+ </v-container>
115
141
  </div>
116
142
  </template>
117
143
 
118
144
  <script>
119
145
  import _ from 'lodash'
120
146
  import draggable from 'vuedraggable'
147
+ import { mapGetters } from 'vuex'
121
148
  import BaseContentSettings from '~/components/Content/Settings/BaseContentSettings.js'
122
149
  import CrudTable from '../content/CrudTable.vue'
123
150
  import SortableExpansionPanel from '~/components/Core/SortableExpansionPanel.vue'
124
151
  import BaseContentBlockSettings from '~/components/Content/Settings/BaseContentBlockSettings.vue'
125
152
  import Uuid from '~/helpers/Uuid'
153
+ import PluginRef from '~/components/Core/PluginRef.vue'
126
154
 
127
155
  export default {
128
156
  name: 'SevenStrikesSettingsManager',
@@ -132,6 +160,7 @@ export default {
132
160
  draggable,
133
161
  SortableExpansionPanel,
134
162
  BaseContentBlockSettings,
163
+ PluginRef,
135
164
  },
136
165
  beforeMount() {
137
166
  if (_.isEmpty(this.block)) {
@@ -186,6 +215,12 @@ export default {
186
215
  loading: false,
187
216
  }
188
217
  },
218
+ computed: {
219
+ ...mapGetters({
220
+ course: 'course/get',
221
+ currentContent: 'content/get',
222
+ }),
223
+ },
189
224
  methods: {
190
225
  onBeforeSave() {
191
226
  this.block.metadata.config.words.forEach((element) => {
@@ -220,6 +255,62 @@ export default {
220
255
  this.block.metadata.config.currentWord =
221
256
  this.block.metadata.config.words.length - 1
222
257
  },
258
+ onPluginSetBlock(activityData) {
259
+ this.loading = true
260
+ try {
261
+ if (
262
+ activityData &&
263
+ activityData.metadata &&
264
+ activityData.metadata.config &&
265
+ Array.isArray(activityData.metadata.config.words)
266
+ ) {
267
+ const newWords = activityData.metadata.config.words.map(
268
+ (w) => ({
269
+ value: w.value || '',
270
+ hint: w.hint || '',
271
+ expand: false,
272
+ splitWord: '',
273
+ })
274
+ )
275
+ this.block.metadata.config.words = newWords
276
+ this.block.metadata.config.currentWord = 0
277
+
278
+ if (activityData.metadata.config.title) {
279
+ this.block.metadata.config.title =
280
+ activityData.metadata.config.title
281
+ }
282
+ // Keep existing instructions (do not override)
283
+ }
284
+ } finally {
285
+ this.loading = false
286
+ }
287
+ },
288
+ onPluginAppendBlock(activityData) {
289
+ this.loading = true
290
+ try {
291
+ if (
292
+ activityData &&
293
+ activityData.metadata &&
294
+ activityData.metadata.config &&
295
+ Array.isArray(activityData.metadata.config.words)
296
+ ) {
297
+ const appendWords = activityData.metadata.config.words.map(
298
+ (w) => ({
299
+ value: w.value || '',
300
+ hint: w.hint || '',
301
+ expand: false,
302
+ splitWord: '',
303
+ })
304
+ )
305
+ if (!Array.isArray(this.block.metadata.config.words)) {
306
+ this.block.metadata.config.words = []
307
+ }
308
+ this.block.metadata.config.words.push(...appendWords)
309
+ }
310
+ } finally {
311
+ this.loading = false
312
+ }
313
+ },
223
314
  },
224
315
  }
225
316
  </script>