@windward/games 0.23.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.
- package/CHANGELOG.md +6 -0
- package/components/content/blocks/quizshowGame/feedbackIcons.vue +2 -2
- package/components/settings/CrosswordPuzzleSettingsManager.vue +248 -1
- package/components/settings/QuizShowSettingsManager.vue +2 -1
- package/i18n/en-US/components/settings/crossword.ts +4 -0
- package/i18n/es-ES/components/settings/crossword.ts +4 -0
- package/i18n/sv-SE/components/settings/crossword.ts +4 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Release [0.24.0] - 2025-10-23
|
|
4
|
+
|
|
5
|
+
* Merged in feature/LE-2034/generate-crossword (pull request #262)
|
|
6
|
+
* Merged in bugfix/LE-2020-the-quiz-show-manager-panel-does (pull request #263)
|
|
7
|
+
|
|
8
|
+
|
|
3
9
|
## Release [0.23.0] - 2025-09-16
|
|
4
10
|
|
|
5
11
|
* Merged in feature/LE-2035/seven-strikes (pull request #259)
|
|
@@ -90,6 +90,24 @@
|
|
|
90
90
|
</v-row>
|
|
91
91
|
</v-container>
|
|
92
92
|
</v-container>
|
|
93
|
+
<v-container class="pa-4 mb-6">
|
|
94
|
+
<v-row>
|
|
95
|
+
<v-col cols="12">
|
|
96
|
+
<PluginRef
|
|
97
|
+
target="contentBlockSettingTool"
|
|
98
|
+
:attrs="{
|
|
99
|
+
value: block,
|
|
100
|
+
course: course,
|
|
101
|
+
content: currentContent,
|
|
102
|
+
}"
|
|
103
|
+
:on="{
|
|
104
|
+
input: onPluginSetBlock,
|
|
105
|
+
append: onPluginAppendBlock,
|
|
106
|
+
}"
|
|
107
|
+
></PluginRef>
|
|
108
|
+
</v-col>
|
|
109
|
+
</v-row>
|
|
110
|
+
</v-container>
|
|
93
111
|
<div v-if="loading" class="text-center">
|
|
94
112
|
<v-progress-circular
|
|
95
113
|
:size="70"
|
|
@@ -103,15 +121,17 @@
|
|
|
103
121
|
|
|
104
122
|
<script>
|
|
105
123
|
import _ from 'lodash'
|
|
124
|
+
import { mapGetters } from 'vuex'
|
|
106
125
|
import BaseContentBlockSettings from '~/components/Content/Settings/BaseContentBlockSettings.vue'
|
|
107
126
|
import BaseContentSettings from '~/components/Content/Settings/BaseContentSettings.js'
|
|
108
127
|
import SortableExpansionPanel from '~/components/Core/SortableExpansionPanel.vue'
|
|
128
|
+
import PluginRef from '~/components/Core/PluginRef.vue'
|
|
109
129
|
import Uuid from '~/helpers/Uuid'
|
|
110
130
|
|
|
111
131
|
export default {
|
|
112
132
|
name: 'CrosswordPuzzleSettingsManager',
|
|
113
133
|
extends: BaseContentSettings,
|
|
114
|
-
components: { SortableExpansionPanel, BaseContentBlockSettings },
|
|
134
|
+
components: { SortableExpansionPanel, BaseContentBlockSettings, PluginRef },
|
|
115
135
|
beforeMount() {
|
|
116
136
|
if (_.isEmpty(this.block)) {
|
|
117
137
|
this.block = {}
|
|
@@ -186,6 +206,12 @@ export default {
|
|
|
186
206
|
},
|
|
187
207
|
}
|
|
188
208
|
},
|
|
209
|
+
computed: {
|
|
210
|
+
...mapGetters({
|
|
211
|
+
course: 'course/get',
|
|
212
|
+
currentContent: 'content/get',
|
|
213
|
+
}),
|
|
214
|
+
},
|
|
189
215
|
methods: {
|
|
190
216
|
onAddElement() {
|
|
191
217
|
// pushes in new crossword object
|
|
@@ -207,6 +233,227 @@ export default {
|
|
|
207
233
|
this.onAddElement()
|
|
208
234
|
}
|
|
209
235
|
},
|
|
236
|
+
onPluginSetBlock(activityData) {
|
|
237
|
+
this.loading = true
|
|
238
|
+
try {
|
|
239
|
+
const result = this.processGeneratedCrossword(activityData)
|
|
240
|
+
if (!result) {
|
|
241
|
+
this.showInvalidResponseToast()
|
|
242
|
+
return
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
this.$set(
|
|
246
|
+
this.block.metadata,
|
|
247
|
+
'config',
|
|
248
|
+
this.block.metadata.config || {}
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
this.$set(
|
|
252
|
+
this.block.metadata.config,
|
|
253
|
+
'words',
|
|
254
|
+
result.words.map((item, index) => ({
|
|
255
|
+
id: index,
|
|
256
|
+
word: item.word,
|
|
257
|
+
clue: item.clue,
|
|
258
|
+
expand: false,
|
|
259
|
+
}))
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
if (result.title) {
|
|
263
|
+
this.block.metadata.config.title = result.title
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
this.$toast.success(
|
|
267
|
+
this.$t(
|
|
268
|
+
'windward.games.components.settings.crossword.replaced_successfully'
|
|
269
|
+
),
|
|
270
|
+
{ duration: 3000 }
|
|
271
|
+
)
|
|
272
|
+
} catch (error) {
|
|
273
|
+
console.error('Failed to process generated crossword items', error)
|
|
274
|
+
this.$toast.error(
|
|
275
|
+
this.$t(
|
|
276
|
+
'windward.games.components.settings.crossword.failed_to_process'
|
|
277
|
+
),
|
|
278
|
+
{ duration: 5000 }
|
|
279
|
+
)
|
|
280
|
+
} finally {
|
|
281
|
+
this.loading = false
|
|
282
|
+
}
|
|
283
|
+
},
|
|
284
|
+
onPluginAppendBlock(activityData) {
|
|
285
|
+
this.loading = true
|
|
286
|
+
try {
|
|
287
|
+
const result = this.processGeneratedCrossword(activityData)
|
|
288
|
+
if (!result) {
|
|
289
|
+
this.showInvalidResponseToast()
|
|
290
|
+
return
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (!Array.isArray(this.block.metadata.config.words)) {
|
|
294
|
+
this.$set(this.block.metadata.config, 'words', [])
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const startingIndex = this.block.metadata.config.words.length
|
|
298
|
+
|
|
299
|
+
result.words.forEach((item, index) => {
|
|
300
|
+
this.block.metadata.config.words.push({
|
|
301
|
+
id: startingIndex + index,
|
|
302
|
+
word: item.word,
|
|
303
|
+
clue: item.clue,
|
|
304
|
+
expand: false,
|
|
305
|
+
})
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
this.renumberWordIds()
|
|
309
|
+
|
|
310
|
+
if (result.title) {
|
|
311
|
+
const defaultTitle = this.$t(
|
|
312
|
+
'windward.games.components.content.blocks.crossword.crossword'
|
|
313
|
+
)
|
|
314
|
+
if (
|
|
315
|
+
!this.block.metadata.config.title ||
|
|
316
|
+
this.block.metadata.config.title === defaultTitle
|
|
317
|
+
) {
|
|
318
|
+
this.block.metadata.config.title = result.title
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
this.$toast.success(
|
|
323
|
+
this.$t(
|
|
324
|
+
'windward.games.components.settings.crossword.added_successfully'
|
|
325
|
+
),
|
|
326
|
+
{ duration: 3000 }
|
|
327
|
+
)
|
|
328
|
+
} catch (error) {
|
|
329
|
+
console.error('Failed to append generated crossword items', error)
|
|
330
|
+
this.$toast.error(
|
|
331
|
+
this.$t(
|
|
332
|
+
'windward.games.components.settings.crossword.failed_to_process'
|
|
333
|
+
),
|
|
334
|
+
{ duration: 5000 }
|
|
335
|
+
)
|
|
336
|
+
} finally {
|
|
337
|
+
this.loading = false
|
|
338
|
+
}
|
|
339
|
+
},
|
|
340
|
+
processGeneratedCrossword(activityData) {
|
|
341
|
+
const config = _.get(activityData, 'metadata.config', null)
|
|
342
|
+
const words = _.get(config, 'words', [])
|
|
343
|
+
|
|
344
|
+
if (!Array.isArray(words) || words.length === 0) {
|
|
345
|
+
return null
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const sanitizedWords = words
|
|
349
|
+
.map((item) => {
|
|
350
|
+
const sanitizedWord = this.normalizeWord(item?.word)
|
|
351
|
+
const sanitizedClue = this.normalizeClue(item?.clue)
|
|
352
|
+
|
|
353
|
+
if (!sanitizedWord || !sanitizedClue) {
|
|
354
|
+
return null
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return {
|
|
358
|
+
word: sanitizedWord,
|
|
359
|
+
clue: sanitizedClue,
|
|
360
|
+
expand: false,
|
|
361
|
+
}
|
|
362
|
+
})
|
|
363
|
+
.filter((item) => item !== null)
|
|
364
|
+
|
|
365
|
+
if (sanitizedWords.length < 2) {
|
|
366
|
+
return null
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const limitedWords = sanitizedWords.slice(0, 10)
|
|
370
|
+
|
|
371
|
+
const normalizedTitle = this.normalizeTitle(_.get(config, 'title', ''))
|
|
372
|
+
|
|
373
|
+
return {
|
|
374
|
+
title: normalizedTitle,
|
|
375
|
+
words: limitedWords,
|
|
376
|
+
}
|
|
377
|
+
},
|
|
378
|
+
normalizeWord(word) {
|
|
379
|
+
if (!word) {
|
|
380
|
+
return ''
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const formatted = word
|
|
384
|
+
.toString()
|
|
385
|
+
.normalize('NFKD')
|
|
386
|
+
.replace(/[^A-Za-z]/g, '')
|
|
387
|
+
.toUpperCase()
|
|
388
|
+
.slice(0, 20)
|
|
389
|
+
|
|
390
|
+
return formatted
|
|
391
|
+
},
|
|
392
|
+
normalizeClue(clue) {
|
|
393
|
+
if (!clue) {
|
|
394
|
+
return ''
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
let normalized = clue
|
|
398
|
+
.toString()
|
|
399
|
+
.replace(/\s+/g, ' ')
|
|
400
|
+
.trim()
|
|
401
|
+
|
|
402
|
+
if (!normalized) {
|
|
403
|
+
return ''
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if (normalized.endsWith('?')) {
|
|
407
|
+
normalized = normalized.slice(0, -1).trim()
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const maxLength = 155
|
|
411
|
+
if (normalized.length > maxLength) {
|
|
412
|
+
normalized = normalized.slice(0, maxLength).trim()
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (normalized && !/[.!]$/.test(normalized)) {
|
|
416
|
+
if (normalized.length >= maxLength) {
|
|
417
|
+
normalized = normalized.slice(0, maxLength - 1).trim()
|
|
418
|
+
}
|
|
419
|
+
normalized += '.'
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
return normalized
|
|
423
|
+
},
|
|
424
|
+
normalizeTitle(title) {
|
|
425
|
+
if (!title) {
|
|
426
|
+
return ''
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
let normalized = title
|
|
430
|
+
.toString()
|
|
431
|
+
.replace(/\s+/g, ' ')
|
|
432
|
+
.trim()
|
|
433
|
+
|
|
434
|
+
if (!normalized) {
|
|
435
|
+
return ''
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if (normalized.length > 100) {
|
|
439
|
+
normalized = normalized.slice(0, 100).trim()
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
return normalized
|
|
443
|
+
},
|
|
444
|
+
renumberWordIds() {
|
|
445
|
+
this.block.metadata.config.words.forEach((item, index) => {
|
|
446
|
+
item.id = index
|
|
447
|
+
})
|
|
448
|
+
},
|
|
449
|
+
showInvalidResponseToast() {
|
|
450
|
+
this.$toast.error(
|
|
451
|
+
this.$t(
|
|
452
|
+
'windward.games.components.settings.crossword.invalid_response'
|
|
453
|
+
),
|
|
454
|
+
{ duration: 5000 }
|
|
455
|
+
)
|
|
456
|
+
},
|
|
210
457
|
},
|
|
211
458
|
}
|
|
212
459
|
</script>
|
|
@@ -267,7 +267,8 @@ export default {
|
|
|
267
267
|
},
|
|
268
268
|
computed: {},
|
|
269
269
|
beforeMount() {
|
|
270
|
-
|
|
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)) {
|
|
@@ -5,4 +5,8 @@ export default {
|
|
|
5
5
|
min_length: 'Cannot have less than two words',
|
|
6
6
|
alert: 'Alert',
|
|
7
7
|
instructions: 'Complete the crossword by clicking on each square to type the appropriate letter. Use the tab key to move left to right across the grid. Click on the clue to highlight the corresponding word in the puzzle.',
|
|
8
|
+
replaced_successfully: 'Crossword items replaced successfully',
|
|
9
|
+
added_successfully: 'Crossword items added successfully',
|
|
10
|
+
failed_to_process: 'Failed to process generated crossword items',
|
|
11
|
+
invalid_response: 'Unable to process generated crossword items',
|
|
8
12
|
}
|
|
@@ -6,4 +6,8 @@ export default {
|
|
|
6
6
|
alert: 'Alerta',
|
|
7
7
|
instructions:
|
|
8
8
|
'Complete el crucigrama haciendo clic en cada cuadrado para escribir la letra correspondiente. Utilice la tecla de tabulación para moverse de izquierda a derecha a través de la cuadrícula. Haga clic en la pista para resaltar la palabra correspondiente en el rompecabezas.',
|
|
9
|
+
replaced_successfully: 'Elementos del crucigrama reemplazados correctamente',
|
|
10
|
+
added_successfully: 'Elementos del crucigrama agregados correctamente',
|
|
11
|
+
failed_to_process: 'Error al procesar los elementos generados del crucigrama',
|
|
12
|
+
invalid_response: 'No se pudieron procesar los elementos generados del crucigrama',
|
|
9
13
|
}
|
|
@@ -5,4 +5,8 @@ export default {
|
|
|
5
5
|
min_length: 'Kan inte ha mindre än två ord',
|
|
6
6
|
alert: 'Alert',
|
|
7
7
|
instructions: 'Fyll i korsordet genom att klicka på varje ruta för att skriva rätt bokstav. Använd tabbtangenten för att flytta från vänster till höger över rutnätet. Klicka på ledtråden för att markera motsvarande ord i pusslet.',
|
|
8
|
+
replaced_successfully: 'Korsordsobjekt ersattes',
|
|
9
|
+
added_successfully: 'Korsordsobjekt lades till',
|
|
10
|
+
failed_to_process: 'Det gick inte att bearbeta genererade korsordsobjekt',
|
|
11
|
+
invalid_response: 'Det gick inte att behandla genererade korsordsobjekt',
|
|
8
12
|
}
|