@windward/games 0.0.4 → 0.0.6

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.
Files changed (29) hide show
  1. package/components/content/DatableEditor.vue +0 -3
  2. package/components/content/blocks/crosswordPuzzle/Crossword.ts +231 -153
  3. package/components/content/blocks/crosswordPuzzle/CrosswordClues.vue +91 -0
  4. package/components/content/blocks/crosswordPuzzle/CrosswordElements.ts +8 -6
  5. package/components/content/blocks/crosswordPuzzle/CrosswordPuzzle.vue +502 -371
  6. package/components/content/blocks/matchingGame/MatchingGame.vue +12 -1
  7. package/components/content/blocks/multipleChoice/MultipleChoice.vue +187 -127
  8. package/components/content/blocks/multipleChoice/QuestionDialog.vue +37 -13
  9. package/components/content/blocks/sevenStrikes/SevenStikes.vue +368 -0
  10. package/components/content/blocks/sevenStrikes/keyboard.vue +71 -0
  11. package/components/content/blocks/wordJumble/Jumble.vue +2 -10
  12. package/components/settings/CrosswordPuzzleSettingsManager.vue +12 -15
  13. package/components/settings/MatchingGameManager.vue +1 -1
  14. package/components/settings/MultipleChoiceSettingsManager.vue +21 -7
  15. package/components/settings/SevenStrikesSettingsManager.vue +288 -0
  16. package/i18n/en-US/components/content/blocks/crossword.ts +18 -3
  17. package/i18n/en-US/components/content/blocks/index.ts +2 -0
  18. package/i18n/en-US/components/content/blocks/multiple_choice.ts +2 -4
  19. package/i18n/en-US/components/content/blocks/seven_strikes.ts +6 -0
  20. package/i18n/en-US/components/settings/crossword.ts +2 -1
  21. package/i18n/en-US/components/settings/index.ts +2 -0
  22. package/i18n/en-US/components/settings/multiple_choice.ts +1 -1
  23. package/i18n/en-US/components/settings/seven_strikes.ts +8 -0
  24. package/i18n/en-US/shared/content_blocks.ts +1 -0
  25. package/i18n/en-US/shared/settings.ts +1 -0
  26. package/package.json +2 -1
  27. package/plugin.js +21 -0
  28. package/test/blocks/sevenStrikes/sevenStrikes.spec.js +24 -0
  29. package/test/settings/SevenStrikesManager.spec.js +53 -0
@@ -118,6 +118,7 @@
118
118
  >
119
119
  <v-card-text>
120
120
  <draggable
121
+ v-if="mainPrompt['file']"
121
122
  class="dragArea list-group"
122
123
  :key="mainPrompt['prompt']"
123
124
  v-bind="dragOptions"
@@ -393,7 +394,17 @@ export default {
393
394
  solvedQuestions: [],
394
395
  status: 'default',
395
396
  allowDrag: true,
396
- mainPrompt: {},
397
+ mainPrompt: {
398
+ altText: '',
399
+ answer: {},
400
+ file: {
401
+ asset: {
402
+ public_url: '',
403
+ },
404
+ name: '',
405
+ },
406
+ prompt: '',
407
+ },
397
408
  editedPrompt: {},
398
409
  startingIndex: 0,
399
410
  }
@@ -11,19 +11,16 @@
11
11
  <v-carousel height="600">
12
12
  <v-container>
13
13
  <v-carousel-item
14
- v-for="(question, index) in block.metadata.config
15
- .questions"
16
- :key="index"
14
+ v-for="question in block.metadata.config.questions"
15
+ :key="question.id"
17
16
  >
18
17
  <v-col class="d-flex justify-center">
19
18
  <p class="questionBody">{{ question.body }}</p>
20
19
  </v-col>
21
20
  <v-container :key="updateKey">
22
21
  <v-row
23
- v-for="(
24
- answer, answerIndex
25
- ) in question.answer_options"
26
- :key="answerIndex"
22
+ v-for="answer in question.answer_options"
23
+ :key="answer.id"
27
24
  class="mb-2"
28
25
  >
29
26
  <v-col
@@ -32,37 +29,15 @@
32
29
  class="d-flex justify-end"
33
30
  >
34
31
  <v-icon
35
- class="isCorrect"
36
- v-if="
37
- studentResponses[index] &&
38
- answer.correctAnswer === true &&
39
- studentResponses[index]
40
- .answer_options[answerIndex]
41
- .chosen === true
42
- "
43
- >mdi-check-circle</v-icon
44
- >
45
- <v-icon
46
- class="isCorrect"
47
- v-if="
48
- studentResponses[index] &&
49
- answer.correctAnswer === true &&
50
- studentResponses[index]
51
- .answer_options[answerIndex]
52
- .chosen !== true
53
- "
54
- >mdi-check-circle-outline</v-icon
55
- >
56
- <v-icon
57
- class="isIncorrect"
58
- v-if="
59
- studentResponses[index] &&
60
- answer.correctAnswer !== true &&
61
- studentResponses[index]
62
- .answer_options[answerIndex]
63
- .chosen === true
32
+ :class="
33
+ handleIconClass(
34
+ question,
35
+ answer
36
+ )
64
37
  "
65
- >mdi-alpha-x-circle</v-icon
38
+ >{{
39
+ handleIcon(question, answer)
40
+ }}</v-icon
66
41
  >
67
42
  </v-col>
68
43
  <v-col cols="12" md="8" class="pa-0">
@@ -70,12 +45,15 @@
70
45
  class="optionOutline pa-2"
71
46
  :disabled="answer.disabled"
72
47
  :class="
73
- onIsThisCorrect(answer, index)
48
+ onIsThisCorrect(
49
+ answer,
50
+ question
51
+ )
74
52
  "
75
53
  outlined
76
54
  tile
77
55
  @click="
78
- onChooseAnswer(answer, index)
56
+ onChooseAnswer(answer, question)
79
57
  "
80
58
  >
81
59
  <div
@@ -93,50 +71,24 @@
93
71
  </p>
94
72
  <div>
95
73
  <v-icon
96
- class="isCorrect"
97
- v-if="
98
- studentResponses[
99
- index
100
- ] &&
101
- answer.correctAnswer ===
102
- true &&
103
- studentResponses[
104
- index
105
- ].answer_options[
106
- answerIndex
107
- ].chosen !== true
108
- "
109
- @click="
110
- onAnswerDescription(
111
- question
74
+ :class="
75
+ handleInformationClass(
76
+ question,
77
+ answer
112
78
  )
113
79
  "
114
- >mdi-information</v-icon
115
- >
116
- <v-icon
117
- right
118
- dark
119
- nontranslate
120
- v-if="
121
- studentResponses[
122
- index
123
- ] &&
124
- answer.correctAnswer ==
125
- true &&
126
- studentResponses[
127
- index
128
- ].answer_options[
129
- answerIndex
130
- ].chosen === true
131
- "
132
80
  @click="
133
81
  onAnswerDescription(
134
82
  question
135
83
  )
136
84
  "
85
+ >{{
86
+ handleInformationOutline(
87
+ question,
88
+ answer
89
+ )
90
+ }}</v-icon
137
91
  >
138
- mdi-information-outline
139
- </v-icon>
140
92
  </div>
141
93
  </div>
142
94
  </v-card>
@@ -188,7 +140,7 @@
188
140
  )
189
141
  }}
190
142
  <v-progress-linear
191
- color="primary"
143
+ color="success"
192
144
  outlined
193
145
  v-model="completedPercent"
194
146
  rounded
@@ -198,10 +150,10 @@
198
150
  </v-flex>
199
151
  <v-flex xs4></v-flex>
200
152
  </v-layout>
201
- <v-layout class="mt-2" v-if="updateTotals !== 1">
153
+ <v-layout class="mt-2" v-if="mountCourseCounter">
202
154
  <v-flex xs8></v-flex>
203
155
  <v-flex xs4>
204
- <v-col :key="updateTotals">
156
+ <v-col>
205
157
  <p>
206
158
  {{
207
159
  $t(
@@ -240,7 +192,7 @@
240
192
  <div v-if="answerDescriptionModal">
241
193
  {{
242
194
  $t(
243
- 'plugin.games.components.content.blocks.multiple_choice.answer_description'
195
+ 'plugin.games.components.content.blocks.multiple_choice.answer_feedback'
244
196
  )
245
197
  }}
246
198
  </div>
@@ -295,7 +247,6 @@ export default {
295
247
  if (_.isEmpty(this.block.metadata.config.questions)) {
296
248
  this.block.metadata.config.questions = []
297
249
  }
298
- this.updateTotals = 1
299
250
  },
300
251
  data() {
301
252
  return {
@@ -308,14 +259,15 @@ export default {
308
259
  answerDescription: '',
309
260
  studentAmountCorrect: null,
310
261
  totalQuestionsMultipleChoice: null,
311
- updateTotals: 1,
312
262
  studentResponses: [],
263
+ mountCourseCounter: false,
313
264
  }
314
265
  },
315
266
  computed: {
316
267
  ...mapGetters({
317
268
  organization: 'organization/get',
318
269
  course: 'course/get',
270
+ enrollment: 'enrollment/get',
319
271
  }),
320
272
  completedAmount() {
321
273
  if (this.completedItems) {
@@ -337,8 +289,15 @@ export default {
337
289
  return 0
338
290
  },
339
291
  },
292
+ watch: {
293
+ render(newValue) {
294
+ if (newValue) {
295
+ this.onTotalAmountOfQuestions()
296
+ }
297
+ },
298
+ },
340
299
  mounted() {
341
- this.studentAmountCorrect = 0
300
+ this.mountCourseCounter = false
342
301
  this.onAmountCorrect()
343
302
  this.onTotalAmountOfQuestions()
344
303
  },
@@ -348,6 +307,7 @@ export default {
348
307
  // grabs user state to get the total amount of questions a studentanswered correctly
349
308
  let userState = await UserContentBlockState.where({
350
309
  'metadata->block->tag': 'plugin-games-multiple-choice',
310
+ course_user_id: this.enrollment.id,
351
311
  }).get()
352
312
  userState.forEach((state) => {
353
313
  state.metadata.studentResponses.forEach((element) => {
@@ -356,12 +316,12 @@ export default {
356
316
  }
357
317
  })
358
318
  })
359
-
360
- // sets total
361
- this.studentAmountCorrect = correct
362
- // updates display area of totals
363
- this.updateTotals = Crypto.id()
364
- this.updateKey = Crypto.id()
319
+ // need to wait till correct has the new value to set the var
320
+ setTimeout(() => {
321
+ // sets total
322
+ this.studentAmountCorrect = correct
323
+ this.mountCourseCounter = true
324
+ }, 1000)
365
325
  },
366
326
  async onTotalAmountOfQuestions() {
367
327
  let multipleChoiceTotalQuestions = 0
@@ -388,27 +348,28 @@ export default {
388
348
  this.answerDescriptionModal = true
389
349
  this.answerDescription = question.answer_description
390
350
  },
391
- onChooseAnswer(answer, questionIndex) {
392
- const answerIndex =
393
- this.block.metadata.config.questions[
394
- questionIndex
395
- ].answer_options.indexOf(answer)
396
- // set answer,chosen and student_response on block state
397
- if (_.isEmpty(this.studentResponses[questionIndex])) {
398
- // clones question block into the correct index in the studentResponses araay
399
- this.studentResponses[questionIndex] = _.cloneDeep(
400
- this.block.metadata.config.questions[questionIndex]
401
- )
351
+ onChooseAnswer(answer, question) {
352
+ let studRep
353
+ this.studentResponses.find((obj) => {
354
+ if (obj.id === question.id) {
355
+ studRep = obj
356
+ }
357
+ })
358
+ if (_.isEmpty(this.studentResponses) || _.isEmpty(studRep)) {
402
359
  // lets html side know whish answer was chosen, this is important in determine css
403
- // used on the displayed side
404
- this.studentResponses[questionIndex].answer_options[
405
- answerIndex
406
- ].chosen = true
407
- this.studentResponses[questionIndex].student_response = answer
360
+ answer.chosen = true
361
+ // clones question block into the correct index in the studentResponses araay
362
+ const clonedQuestion = _.cloneDeep(question)
363
+ clonedQuestion.student_response = answer
364
+
365
+ if (answer.correctAnswer === true) {
366
+ this.studentAmountCorrect = this.studentAmountCorrect + 1
367
+ clonedQuestion.isStudentCorrect = true
368
+ } else {
369
+ clonedQuestion.isStudentCorrect = false
370
+ }
408
371
 
409
- // get question by index
410
- const question =
411
- this.block.metadata.config.questions[questionIndex]
372
+ this.studentResponses.push(clonedQuestion)
412
373
  // ensure the object is not already in the completed items array if not push into the array
413
374
  const questionDone = this.completedItems.find(
414
375
  (item) => item === question
@@ -417,26 +378,24 @@ export default {
417
378
  if (!questionDone) {
418
379
  this.completedItems.push(question)
419
380
  }
420
- if (answer.correctAnswer === true) {
421
- this.studentAmountCorrect = this.studentAmountCorrect + 1
422
- this.studentResponses[questionIndex].isStudentCorrect = true
423
- }
424
381
 
425
382
  this.updateKey = Crypto.id()
426
383
  }
427
384
  },
428
- onIsThisCorrect(answer, questionIndex) {
385
+ onIsThisCorrect(answer, question) {
386
+ const studentsQuestionResponse = this.studentResponses.find(
387
+ (obj) => {
388
+ if (obj.id === question.id) {
389
+ return obj
390
+ }
391
+ }
392
+ )
429
393
  // check if student already responded
430
- if (
431
- !_.isEmpty(
432
- this.studentResponses[questionIndex] &&
433
- this.studentResponses[questionIndex].student_response
434
- )
435
- ) {
394
+ if (studentsQuestionResponse) {
436
395
  // checks if answer is correct and applies class
437
396
  if (
438
- this.studentResponses[questionIndex].student_response
439
- .correctAnswer === true
397
+ studentsQuestionResponse.student_response.correctAnswer ===
398
+ true
440
399
  ) {
441
400
  if (answer.correctAnswer === true) {
442
401
  return 'studentAnswerCorrect'
@@ -444,17 +403,115 @@ export default {
444
403
  return ''
445
404
  }
446
405
  } else if (
447
- this.studentResponses[questionIndex].student_response
448
- .correctAnswer !== true
406
+ studentsQuestionResponse.student_response.correctAnswer !==
407
+ true &&
408
+ studentsQuestionResponse.student_response.id === answer.id
449
409
  ) {
450
- if (answer.correctAnswer === true) {
451
- return 'correctBorder'
452
- } else if (
453
- this.studentResponses[questionIndex].student_response
410
+ if (
411
+ studentsQuestionResponse.student_response
454
412
  .correctAnswer !== true
455
413
  ) {
456
414
  return 'incorrectBorder'
457
415
  }
416
+ } else if (
417
+ studentsQuestionResponse.student_response.correctAnswer !==
418
+ true
419
+ ) {
420
+ if (answer.correctAnswer === true) {
421
+ return 'correctBorder'
422
+ }
423
+ }
424
+ }
425
+ },
426
+ handleIcon(question, answer) {
427
+ const studentsQuestionResponse = this.studentResponses.find(
428
+ (obj) => {
429
+ if (obj.id === question.id) {
430
+ return obj
431
+ }
432
+ }
433
+ )
434
+ if (studentsQuestionResponse) {
435
+ // requirements for correct and chosen
436
+ if (
437
+ answer.id ===
438
+ studentsQuestionResponse.student_response.id &&
439
+ answer.correctAnswer === true &&
440
+ studentsQuestionResponse.student_response.chosen === true
441
+ ) {
442
+ return 'mdi-check-circle'
443
+ }
444
+ //requirements for correct and unchosen
445
+ if (answer.correctAnswer === true && answer.chosen === false) {
446
+ return 'mdi-check-circle-outline'
447
+ }
448
+ // requirements for wrong and chosen
449
+ if (answer.correctAnswer !== true && answer.chosen === true) {
450
+ return 'mdi-alpha-x-circle'
451
+ }
452
+ }
453
+ },
454
+ handleIconClass(question, answer) {
455
+ const studentsQuestionResponse = this.studentResponses.find(
456
+ (obj) => {
457
+ return obj.id === question.id
458
+ }
459
+ )
460
+ if (studentsQuestionResponse) {
461
+ // requirements for correct and chosen
462
+ if (
463
+ answer.id ===
464
+ studentsQuestionResponse.student_response.id &&
465
+ answer.correctAnswer === true &&
466
+ studentsQuestionResponse.student_response.chosen === true
467
+ ) {
468
+ return 'isCorrect'
469
+ }
470
+ //requirements for correct and unchosen
471
+ if (answer.correctAnswer === true && answer.chosen === false) {
472
+ return 'isCorrect'
473
+ }
474
+ // requirements for wrong and chosen
475
+ if (answer.correctAnswer !== true && answer.chosen === true) {
476
+ return 'isIncorrect'
477
+ }
478
+ }
479
+ },
480
+ handleInformationOutline(question, answer) {
481
+ const studentsQuestionResponse = this.studentResponses.find(
482
+ (obj) => {
483
+ if (obj.id === question.id) {
484
+ return obj
485
+ }
486
+ }
487
+ )
488
+ if (studentsQuestionResponse) {
489
+ if (answer.correctAnswer === true && answer.chosen === true) {
490
+ return 'mdi-information'
491
+ } else if (
492
+ answer.correctAnswer === true &&
493
+ answer.chosen === false
494
+ ) {
495
+ return 'mdi-information-outline'
496
+ }
497
+ }
498
+ },
499
+ handleInformationClass(question, answer) {
500
+ const studentsQuestionResponse = this.studentResponses.find(
501
+ (obj) => {
502
+ if (obj.id === question.id) {
503
+ return obj
504
+ }
505
+ }
506
+ )
507
+ if (studentsQuestionResponse) {
508
+ if (answer.correctAnswer === true && answer.chosen === true) {
509
+ return 'informationCorrect'
510
+ } else if (
511
+ answer.correctAnswer === true &&
512
+ answer.chosen === false
513
+ ) {
514
+ return 'isCorrect'
458
515
  }
459
516
  }
460
517
  },
@@ -475,7 +532,7 @@ export default {
475
532
  this.hint = true
476
533
  // sets text for modal, cannot just reference question.hint in html because
477
534
  // the carousel loads all elements regardless of if they are on a different slide or not
478
- // when mounted so it always shows the last question if referenced question.hint
535
+ // when mountCourseCounter so it always shows the last question if referenced question.hint
479
536
  this.hintText = question.hint
480
537
  },
481
538
  onFifty(question) {
@@ -549,6 +606,9 @@ export default {
549
606
  .isIncorrect {
550
607
  color: var(--v-error-base);
551
608
  }
609
+ .informationCorrect {
610
+ color: white;
611
+ }
552
612
  .iconStudentCorrect {
553
613
  color: var(--v-success-base);
554
614
  border-radius: 25px;
@@ -29,20 +29,21 @@
29
29
  v-model="question.answer_description"
30
30
  :label="
31
31
  $t(
32
- 'plugin.games.components.settings.multiple_choice.answer_description'
32
+ 'plugin.games.components.settings.multiple_choice.answer_feedback'
33
33
  )
34
34
  "
35
35
  rows="2"
36
36
  :rules="validation.textRules"
37
37
  prepend-inner-icon="mdi-comment"
38
38
  ></v-textarea>
39
- <h3>
39
+ <br />
40
+ <h4 class="pb-2">
40
41
  {{
41
42
  $t(
42
43
  'plugin.games.components.settings.multiple_choice.answer_options'
43
44
  )
44
45
  }}
45
- </h3>
46
+ </h4>
46
47
  <p>
47
48
  {{
48
49
  $t(
@@ -63,16 +64,20 @@
63
64
  @mouseover="onHover"
64
65
  @mouseleave="onHoverLeave"
65
66
  >
66
- <v-checkbox
67
- v-model="answer.correctAnswer"
68
- :ref="'checkbox' + index"
69
- @click="onSetAnswer(answer)"
70
- ></v-checkbox>
67
+ <v-radio-group v-model="question.correctAnswer">
68
+ <v-radio
69
+ :ref="'checkbox' + index"
70
+ v-on:keydown.enter="onSetAnswer(answer, true)"
71
+ :value="answer.id"
72
+ @click="onSetAnswer(answer)"
73
+ ></v-radio>
74
+ </v-radio-group>
71
75
  </v-col>
72
76
  <v-col cols="12" md="10">
73
77
  <v-textarea
74
78
  flat
75
79
  solo
80
+ :autofocus="answer.focus"
76
81
  v-model="answer.value"
77
82
  :outlined="setOutline(answer)"
78
83
  hide-details
@@ -142,23 +147,32 @@ export default {
142
147
  this.question = {
143
148
  answer_options: [
144
149
  {
145
- id: 1,
150
+ id: Crypto.id(),
146
151
  value: '',
147
152
  correctAnswer: true,
148
153
  disabled: false,
154
+ chosen: false,
155
+ focus: false,
149
156
  },
150
157
  {
151
- id: 2,
158
+ id: Crypto.id(),
152
159
  value: '',
153
160
  correctAnswer: false,
154
161
  disabled: false,
162
+ chosen: false,
163
+ focus: false,
155
164
  },
156
165
  ],
166
+ // needed to add this bc radio group needs the id of the answer to set correct
167
+ correctAnswer: 1,
157
168
  body: '',
158
169
  hint: '',
159
170
  answer_description: '',
160
171
  }
161
172
  }
173
+ if (newValue && newValue.body === '') {
174
+ this.$refs.form.resetValidation()
175
+ }
162
176
  },
163
177
  },
164
178
  },
@@ -198,7 +212,10 @@ export default {
198
212
  }
199
213
  },
200
214
  mounted() {
201
- if (!_.isEmpty(this.value)) {
215
+ if (this.value.body === '') {
216
+ this.$refs.form.resetValidation()
217
+ this.question = _.cloneDeep(this.value)
218
+ } else if (!_.isEmpty(this.value)) {
202
219
  this.question = _.cloneDeep(this.value)
203
220
  }
204
221
  // refreshes data for modal on mount
@@ -218,9 +235,14 @@ export default {
218
235
  this.$emit('input', emittedQuestion)
219
236
  this.$emit('saveAndNew')
220
237
  },
221
- onSetAnswer(answer) {
238
+ onSetAnswer(answer, onEnter = null) {
239
+ if (onEnter) {
240
+ this.question.correctAnswer = answer.id
241
+ }
222
242
  // changes all inputs that aren't the choosen correct answer to be unchecked
223
243
  const index = this.question.answer_options.indexOf(answer)
244
+ // programatically set to true here, was orginally set up as checkboxs so that was previously happening in the html
245
+ this.question.answer_options[index].correctAnswer = true
224
246
  this.question.answer_options.forEach((element) => {
225
247
  const loopIndex = this.question.answer_options.indexOf(element)
226
248
  if (loopIndex !== index) {
@@ -234,10 +256,12 @@ export default {
234
256
  onAddAnswer() {
235
257
  // pushes new answer object into answer options
236
258
  const answerObject = {
237
- id: this.question.answer_options.length + 1,
259
+ id: Crypto.id(),
238
260
  value: '',
239
261
  correctAnswer: false,
240
262
  disabled: false,
263
+ chosen: false,
264
+ focus: true,
241
265
  }
242
266
  this.question.answer_options.push(answerObject)
243
267
  },