@windward/games 0.22.0 → 0.24.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.
@@ -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>
@@ -267,7 +267,8 @@ export default {
267
267
  },
268
268
  computed: {},
269
269
  beforeMount() {
270
- if (_.isEmpty(this.block.metadata.config)) {
270
+ // since display_title is already set so we'll look at a quiz show property to set settings for first mount
271
+ if (_.isEmpty(this.block.metadata.config.title)) {
271
272
  this.block.metadata.config = _.cloneDeep(this.quizShowSettings)
272
273
  }
273
274
  if (!_.isBoolean(this.block.metadata.config.display_title)) {
@@ -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>
@@ -82,14 +82,18 @@
82
82
  <v-container class="pa-4 mb-6">
83
83
  <v-row>
84
84
  <v-col cols="12">
85
- <GenerateAIQuestionButton
86
- :course="course"
87
- :content="currentContent"
88
- :block="block"
89
- question-type="sorting_game"
90
- :replace-existing-mode="replaceExisting"
91
- @click:generate="onGeneratedSortingGame"
92
- ></GenerateAIQuestionButton>
85
+ <PluginRef
86
+ target="contentBlockSettingTool"
87
+ :attrs="{
88
+ value: block,
89
+ course: course,
90
+ content: currentContent,
91
+ }"
92
+ :on="{
93
+ input: onPluginSetBlock,
94
+ append: onPluginAppendBlock,
95
+ }"
96
+ ></PluginRef>
93
97
  </v-col>
94
98
  </v-row>
95
99
  </v-container>
@@ -107,16 +111,20 @@
107
111
  <script>
108
112
  import _ from 'lodash'
109
113
  import { mapGetters } from 'vuex'
110
- import { GenerateAIQuestionButton } from '@windward/core/utils'
111
114
  import BaseContentSettings from '~/components/Content/Settings/BaseContentSettings.js'
112
115
  import BaseContentBlockSettings from '~/components/Content/Settings/BaseContentBlockSettings.vue'
113
116
  import SortableExpansionPanel from '~/components/Core/SortableExpansionPanel.vue'
117
+ import PluginRef from '~/components/Core/PluginRef.vue'
114
118
  import Uuid from '~/helpers/Uuid'
115
119
 
116
120
  export default {
117
121
  name: 'SortingGameSettingsManager',
118
122
  extends: BaseContentSettings,
119
- components: { SortableExpansionPanel, BaseContentBlockSettings, GenerateAIQuestionButton },
123
+ components: {
124
+ SortableExpansionPanel,
125
+ BaseContentBlockSettings,
126
+ PluginRef,
127
+ },
120
128
  beforeMount() {
121
129
  if (_.isEmpty(this.block)) {
122
130
  this.block = {}
@@ -202,79 +210,116 @@ export default {
202
210
  return (element.id = startingIndex)
203
211
  })
204
212
  },
205
- onGeneratedSortingGame(activityData, replaceMode) {
213
+ onPluginSetBlock(activityData) {
206
214
  this.loading = true
207
215
  try {
208
216
  // Process the activity data
209
- if (activityData && activityData.metadata &&
217
+ if (
218
+ activityData &&
219
+ activityData.metadata &&
210
220
  activityData.metadata.config &&
211
221
  activityData.metadata.config.answer &&
212
- Array.isArray(activityData.metadata.config.answer)) {
213
-
222
+ Array.isArray(activityData.metadata.config.answer)
223
+ ) {
214
224
  // Save new items
215
225
  const newItems = activityData.metadata.config.answer
216
226
 
217
- if (replaceMode) {
218
- // Replace mode: Clear existing items
219
- this.block.metadata.config.answer.splice(0, this.block.metadata.config.answer.length)
227
+ // Replace mode: Clear existing items
228
+ this.block.metadata.config.answer.splice(
229
+ 0,
230
+ this.block.metadata.config.answer.length
231
+ )
220
232
 
221
- // Add all new items - ensure they have the correct structure
222
- newItems.forEach((item, index) => {
223
- this.block.metadata.config.answer.push({
224
- id: index,
225
- value: item.value || '',
226
- expand: false,
227
- })
233
+ // Add all new items - ensure they have the correct structure
234
+ newItems.forEach((item, index) => {
235
+ this.block.metadata.config.answer.push({
236
+ id: index,
237
+ value: item.value || '',
238
+ expand: false,
228
239
  })
229
- } else {
230
- // Merge mode: Add new items to existing ones
231
- const hasOnlyEmptyAnswer = this.block.metadata.config.answer.length === 1 &&
232
- this.block.metadata.config.answer[0].value === ''
233
-
234
- if (hasOnlyEmptyAnswer) {
235
- // If there's only one empty answer, replace it instead of merging
236
- this.block.metadata.config.answer.splice(0, 1)
237
- }
240
+ })
238
241
 
239
- let currentId = this.block.metadata.config.answer.length
242
+ // Update title and instructions if provided and we're in replace mode
240
243
 
241
- newItems.forEach((item) => {
242
- this.block.metadata.config.answer.push({
243
- id: currentId,
244
- value: item.value || '',
245
- expand: false,
246
- })
247
- currentId += 1
248
- })
244
+ if (activityData.metadata.config.title) {
245
+ this.block.metadata.config.title =
246
+ activityData.metadata.config.title
249
247
  }
250
248
 
251
- // Update title and instructions if provided and we're in replace mode
252
- if (replaceMode) {
253
- if (activityData.metadata.config.title) {
254
- this.block.metadata.config.title = activityData.metadata.config.title
255
- }
249
+ if (activityData.metadata.config.instructions) {
250
+ this.block.metadata.config.instructions =
251
+ activityData.metadata.config.instructions
252
+ }
253
+
254
+ // Update feedback if provided
255
+ if (activityData.metadata.config.feedback_correct) {
256
+ this.block.metadata.config.feedback_correct =
257
+ activityData.metadata.config.feedback_correct
258
+ }
259
+
260
+ this.$toast.success(
261
+ this.$t(
262
+ 'windward.games.components.settings.sorting_game.form.replaced_successfully'
263
+ ),
264
+
265
+ { duration: 3000 }
266
+ )
267
+ } else {
268
+ throw new Error('activity.error.technical')
269
+ }
270
+ } catch (error) {
271
+ // Let the error bubble up to Plugin with proper error type
272
+ throw error
273
+ } finally {
274
+ this.loading = false
275
+ }
276
+ },
277
+ onPluginAppendBlock(activityData) {
278
+ this.loading = true
279
+ try {
280
+ // Process the activity data
281
+ if (
282
+ activityData &&
283
+ activityData.metadata &&
284
+ activityData.metadata.config &&
285
+ activityData.metadata.config.answer &&
286
+ Array.isArray(activityData.metadata.config.answer)
287
+ ) {
288
+ // Save new items
289
+ const newItems = activityData.metadata.config.answer
256
290
 
257
- if (activityData.metadata.config.instructions) {
258
- this.block.metadata.config.instructions = activityData.metadata.config.instructions
259
- }
291
+ // Merge mode: Add new items to existing ones
292
+ const hasOnlyEmptyAnswer =
293
+ this.block.metadata.config.answer.length === 1 &&
294
+ this.block.metadata.config.answer[0].value === ''
260
295
 
261
- // Update feedback if provided
262
- if (activityData.metadata.config.feedback_correct) {
263
- this.block.metadata.config.feedback_correct = activityData.metadata.config.feedback_correct
264
- }
296
+ if (hasOnlyEmptyAnswer) {
297
+ // If there's only one empty answer, replace it instead of merging
298
+ this.block.metadata.config.answer.splice(0, 1)
265
299
  }
266
300
 
301
+ let currentId = this.block.metadata.config.answer.length
302
+
303
+ newItems.forEach((item) => {
304
+ this.block.metadata.config.answer.push({
305
+ id: currentId,
306
+ value: item.value || '',
307
+ expand: false,
308
+ })
309
+ currentId += 1
310
+ })
311
+
267
312
  this.$toast.success(
268
- replaceMode
269
- ? this.$t('windward.games.components.settings.sorting_game.form.replaced_successfully')
270
- : this.$t('windward.games.components.settings.sorting_game.form.added_successfully'),
313
+ this.$t(
314
+ 'windward.games.components.settings.sorting_game.form.added_successfully'
315
+ ),
271
316
  { duration: 3000 }
272
317
  )
273
318
  } else {
274
319
  throw new Error('activity.error.technical')
275
320
  }
276
321
  } catch (error) {
277
- // Let the error bubble up to GenerateAIQuestionButton with proper error type
322
+ // Let the error bubble up to Plugin with proper error type
278
323
  throw error
279
324
  } finally {
280
325
  this.loading = false